diff options
author | Timothy Pearson <[email protected]> | 2011-07-10 15:24:15 -0500 |
---|---|---|
committer | Timothy Pearson <[email protected]> | 2011-07-10 15:24:15 -0500 |
commit | bd0f3345a938b35ce6a12f6150373b0955b8dd12 (patch) | |
tree | 7a520322212d48ebcb9fbe1087e7fca28b76185c /src/table/qtable.cpp | |
download | qt3-bd0f3345a938b35ce6a12f6150373b0955b8dd12.tar.gz qt3-bd0f3345a938b35ce6a12f6150373b0955b8dd12.zip |
Add Qt3 development HEAD version
Diffstat (limited to 'src/table/qtable.cpp')
-rw-r--r-- | src/table/qtable.cpp | 7370 |
1 files changed, 7370 insertions, 0 deletions
diff --git a/src/table/qtable.cpp b/src/table/qtable.cpp new file mode 100644 index 0000000..8b93f94 --- /dev/null +++ b/src/table/qtable.cpp @@ -0,0 +1,7370 @@ +/**************************************************************************** +** +** Implementation of QTable widget class +** +** Created : 000607 +** +** Copyright (C) 1992-2008 Trolltech ASA. All rights reserved. +** +** This file is part of the table module of the Qt GUI Toolkit. +** +** This file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free +** Software Foundation and appearing in the files LICENSE.GPL2 +** and LICENSE.GPL3 included in the packaging of this file. +** Alternatively you may (at your option) use any later version +** of the GNU General Public License if such license has been +** publicly approved by Trolltech ASA (or its successors, if any) +** and the KDE Free Qt Foundation. +** +** Please review the following information to ensure GNU General +** Public Licensing requirements will be met: +** http://trolltech.com/products/qt/licenses/licensing/opensource/. +** If you are unsure which license is appropriate for your use, please +** review the following information: +** http://trolltech.com/products/qt/licenses/licensing/licensingoverview +** or contact the sales department at [email protected]. +** +** This file may be used under the terms of the Q Public License as +** defined by Trolltech ASA and appearing in the file LICENSE.QPL +** included in the packaging of this file. Licensees holding valid Qt +** Commercial licenses may use this file in accordance with the Qt +** Commercial License Agreement provided with the Software. +** +** This file is provided "AS IS" with NO WARRANTY OF ANY KIND, +** INCLUDING THE WARRANTIES OF DESIGN, MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE. Trolltech reserves all rights not granted +** herein. +** +**********************************************************************/ + +#include "qglobal.h" +#if defined(Q_CC_BOR) +// needed for qsort() because of a std namespace problem on Borland +#include "qplatformdefs.h" +#endif + +#include "qtable.h" + +#ifndef QT_NO_TABLE + +#include <qpainter.h> +#include <qlineedit.h> +#include <qcursor.h> +#include <qapplication.h> +#include <qtimer.h> +#include <qobjectlist.h> +#include <qiconset.h> +#include <qcombobox.h> +#include <qcheckbox.h> +#include <qdragobject.h> +#include <qevent.h> +#include <qlistbox.h> +#include <qstyle.h> +#include <qdatatable.h> +#include <qvalidator.h> + +#include <stdlib.h> +#include <limits.h> + +static bool qt_update_cell_widget = TRUE; +static bool qt_table_clipper_enabled = TRUE; +#ifndef QT_INTERNAL_TABLE +Q_EXPORT +#endif +void qt_set_table_clipper_enabled( bool enabled ) +{ + qt_table_clipper_enabled = enabled; +} + +class QM_EXPORT_TABLE QTableHeader : public QHeader +{ + friend class QTable; + Q_OBJECT + +public: + enum SectionState { + Normal, + Bold, + Selected + }; + + QTableHeader( int, QTable *t, QWidget* parent=0, const char* name=0 ); + ~QTableHeader() {}; + void addLabel( const QString &s, int size ); + void setLabel( int section, const QString & s, int size = -1 ); + void setLabel( int section, const QIconSet & iconset, const QString & s, + int size = -1 ); + + void setLabels(const QStringList & labels); + + void removeLabel( int section ); + + void setSectionState( int s, SectionState state ); + void setSectionStateToAll( SectionState state ); + SectionState sectionState( int s ) const; + + int sectionSize( int section ) const; + int sectionPos( int section ) const; + int sectionAt( int section ) const; + + void setSectionStretchable( int s, bool b ); + bool isSectionStretchable( int s ) const; + + void updateCache(); + +signals: + void sectionSizeChanged( int s ); + +protected: + void paintEvent( QPaintEvent *e ); + void paintSection( QPainter *p, int index, const QRect& fr ); + void mousePressEvent( QMouseEvent *e ); + void mouseMoveEvent( QMouseEvent *e ); + void mouseReleaseEvent( QMouseEvent *e ); + void mouseDoubleClickEvent( QMouseEvent *e ); + void resizeEvent( QResizeEvent *e ); + +private slots: + void doAutoScroll(); + void sectionWidthChanged( int col, int os, int ns ); + void indexChanged( int sec, int oldIdx, int newIdx ); + void updateStretches(); + void updateWidgetStretches(); + +private: + void updateSelections(); + void saveStates(); + void setCaching( bool b ); + void swapSections( int oldIdx, int newIdx, bool swapTable = TRUE ); + bool doSelection( QMouseEvent *e ); + void sectionLabelChanged( int section ); + void resizeArrays( int n ); + +private: + QMemArray<int> states, oldStates; + QMemArray<bool> stretchable; + QMemArray<int> sectionSizes, sectionPoses; + bool mousePressed; + int pressPos, startPos, endPos; + QTable *table; + QTimer *autoScrollTimer; + QWidget *line1, *line2; + bool caching; + int resizedSection; + bool isResizing; + int numStretches; + QTimer *stretchTimer, *widgetStretchTimer; + QTableHeaderPrivate *d; + +}; + +#ifdef _WS_QWS_ +# define NO_LINE_WIDGET +#endif + + + +struct QTablePrivate +{ + QTablePrivate() : hasRowSpan( FALSE ), hasColSpan( FALSE ), + inMenuMode( FALSE ), redirectMouseEvent( FALSE ) + { + hiddenRows.setAutoDelete( TRUE ); + hiddenCols.setAutoDelete( TRUE ); + } + uint hasRowSpan : 1; + uint hasColSpan : 1; + uint inMenuMode : 1; + uint redirectMouseEvent : 1; + QIntDict<int> hiddenRows, hiddenCols; + QTimer *geomTimer; + int lastVisRow; + int lastVisCol; +}; + +struct QTableHeaderPrivate +{ +#ifdef NO_LINE_WIDGET + int oldLinePos; +#endif +}; + +static bool isRowSelection( QTable::SelectionMode selMode ) +{ + return selMode == QTable::SingleRow || selMode == QTable::MultiRow; +} + +/*! + \class QTableSelection + \brief The QTableSelection class provides access to a selected area in a + QTable. +\if defined(commercial) + It is part of the <a href="commercialeditions.html">Qt Enterprise Edition</a>. +\endif + + \ingroup advanced + \module table + + The selection is a rectangular set of cells in a QTable. One of + the rectangle's cells is called the anchor cell; this is the cell + that was selected first. The init() function sets the anchor and + the selection rectangle to exactly this cell; the expandTo() + function expands the selection rectangle to include additional + cells. + + There are various access functions to find out about the area: + anchorRow() and anchorCol() return the anchor's position; + leftCol(), rightCol(), topRow() and bottomRow() return the + rectangle's four edges. All four are part of the selection. + + A newly created QTableSelection is inactive -- isActive() returns + FALSE. You must use init() and expandTo() to activate it. + + \sa QTable QTable::addSelection() QTable::selection() + QTable::selectCells() QTable::selectRow() QTable::selectColumn() +*/ + +/*! + Creates an inactive selection. Use init() and expandTo() to + activate it. +*/ + +QTableSelection::QTableSelection() + : active( FALSE ), inited( FALSE ), tRow( -1 ), lCol( -1 ), + bRow( -1 ), rCol( -1 ), aRow( -1 ), aCol( -1 ) +{ +} + +/*! + Creates an active selection, starting at \a start_row and \a + start_col, ending at \a end_row and \a end_col. +*/ + +QTableSelection::QTableSelection( int start_row, int start_col, int end_row, int end_col ) + : active( FALSE ), inited( FALSE ), tRow( -1 ), lCol( -1 ), + bRow( -1 ), rCol( -1 ), aRow( -1 ), aCol( -1 ) +{ + init( start_row, start_col ); + expandTo( end_row, end_col ); +} + +/*! + Sets the selection anchor to cell \a row, \a col and the selection + to only contain this cell. The selection is not active until + expandTo() is called. + + To extend the selection to include additional cells, call + expandTo(). + + \sa isActive() +*/ + +void QTableSelection::init( int row, int col ) +{ + aCol = lCol = rCol = col; + aRow = tRow = bRow = row; + active = FALSE; + inited = TRUE; +} + +/*! + Expands the selection to include cell \a row, \a col. The new + selection rectangle is the bounding rectangle of \a row, \a col + and the previous selection rectangle. After calling this function + the selection is active. + + If you haven't called init(), this function does nothing. + + \sa init() isActive() +*/ + +void QTableSelection::expandTo( int row, int col ) +{ + if ( !inited ) + return; + active = TRUE; + + if ( row < aRow ) { + tRow = row; + bRow = aRow; + } else { + tRow = aRow; + bRow = row; + } + + if ( col < aCol ) { + lCol = col; + rCol = aCol; + } else { + lCol = aCol; + rCol = col; + } +} + +/*! + Returns TRUE if \a s includes the same cells as the selection; + otherwise returns FALSE. +*/ + +bool QTableSelection::operator==( const QTableSelection &s ) const +{ + return ( s.active == active && + s.tRow == tRow && s.bRow == bRow && + s.lCol == lCol && s.rCol == rCol ); +} + +/*! + \fn bool QTableSelection::operator!=( const QTableSelection &s ) const + + Returns TRUE if \a s does not include the same cells as the + selection; otherwise returns FALSE. +*/ + + +/*! + \fn int QTableSelection::topRow() const + + Returns the top row of the selection. + + \sa bottomRow() leftCol() rightCol() +*/ + +/*! + \fn int QTableSelection::bottomRow() const + + Returns the bottom row of the selection. + + \sa topRow() leftCol() rightCol() +*/ + +/*! + \fn int QTableSelection::leftCol() const + + Returns the left column of the selection. + + \sa topRow() bottomRow() rightCol() +*/ + +/*! + \fn int QTableSelection::rightCol() const + + Returns the right column of the selection. + + \sa topRow() bottomRow() leftCol() +*/ + +/*! + \fn int QTableSelection::anchorRow() const + + Returns the anchor row of the selection. + + \sa anchorCol() expandTo() +*/ + +/*! + \fn int QTableSelection::anchorCol() const + + Returns the anchor column of the selection. + + \sa anchorRow() expandTo() +*/ + +/*! + \fn int QTableSelection::numRows() const + + Returns the number of rows in the selection. + + \sa numCols() +*/ +int QTableSelection::numRows() const +{ + return ( tRow < 0 ) ? 0 : bRow - tRow + 1; +} + +/*! + Returns the number of columns in the selection. + + \sa numRows() +*/ +int QTableSelection::numCols() const +{ + return ( lCol < 0 ) ? 0 : rCol - lCol + 1; +} + +/*! + \fn bool QTableSelection::isActive() const + + Returns whether the selection is active or not. A selection is + active after init() \e and expandTo() have been called. +*/ + +/*! + \fn bool QTableSelection::isEmpty() const + + Returns whether the selection is empty or not. + + \sa numRows(), numCols() +*/ + +/*! + \class QTableItem + \brief The QTableItem class provides the cell content for QTable cells. +\if defined(commercial) + It is part of the <a href="commercialeditions.html">Qt Enterprise Edition</a>. +\endif + + \ingroup advanced + \module table + + For many applications QTableItems are ideal for presenting and + editing the contents of QTable cells. In situations where you need + to create very large tables you may prefer an alternative approach + to using QTableItems: see the notes on large tables. + + A QTableItem contains a cell's data, by default, a string and a + pixmap. The table item also holds the cell's display size and how + the data should be aligned. The table item specifies the cell's + \l EditType and the editor used for in-place editing (by default a + QLineEdit). If you want checkboxes use \l{QCheckTableItem}, and if + you want comboboxes use \l{QComboTableItem}. The \l EditType (set + in the constructor) determines whether the cell's contents may be + edited. + + If a pixmap is specified it is displayed to the left of any text. + You can change the text or pixmap with setText() and setPixmap() + respectively. For text you can use setWordWrap(). + + When sorting table items the key() function is used; by default + this returns the table item's text(). Reimplement key() to + customize how your table items will sort. + + Table items are inserted into a table using QTable::setItem(). If + you insert an item into a cell that already contains a table item + the original item will be deleted. + + Example: + \code + for ( int row = 0; row < table->numRows(); row++ ) { + for ( int col = 0; col < table->numCols(); col++ ) { + table->setItem( row, col, + new QTableItem( table, QTableItem::WhenCurrent, QString::number( row * col ) ) ); + } + } + \endcode + + You can move a table item from one cell to another, in the same or + a different table, using QTable::takeItem() and QTable::setItem() + but see also QTable::swapCells(). + + Table items can be deleted with delete in the standard way; the + table and cell will be updated accordingly. + + Note, that if you have a table item that is not currently in a table + then anything you do to that item other than insert it into a table + will result in undefined behaviour. + + Reimplement createEditor() and setContentFromEditor() if you want + to use your own widget instead of a QLineEdit for editing cell + contents. Reimplement paint() if you want to display custom + content. + + It is important to ensure that your custom widget can accept the + keyboard focus, so that the user can use the tab key to navigate the + table as normal. Therefore, if the widget returned by createEditor() + does not itself accept the keyboard focus, it is necessary to + nominate a child widget to do so on its behalf. For example, a + QHBox with two child QLineEdit widgets may use one of them to + accept the keyboard focus: + + \code + QWidget* MyTableItem::createEditor() const + { + QHBox* hbox = new QHBox( table()->viewport() ); + hbox->setFocusProxy(new QLineEdit( hbox )); + new QLineEdit( hbox ); + return hbox; + } + \endcode + + By default, table items may be replaced by new QTableItems + during the lifetime of a QTable. Therefore, if you create your + own subclass of QTableItem, and you want to ensure that + this does not happen, you must call setReplaceable(FALSE) + in the constructor of your subclass. + + \img qtableitems.png Table Items + + \sa QCheckTableItem QComboTableItem + +*/ + +/*! + \fn QTable *QTableItem::table() const + + Returns the QTable the table item belongs to. + + \sa QTable::setItem() QTableItem() +*/ + +/*! + \enum QTableItem::EditType + + \target wheneditable + This enum is used to define whether a cell is editable or + read-only (in conjunction with other settings), and how the cell + should be displayed. + + \value Always + The cell always \e looks editable. + + Using this EditType ensures that the editor created with + createEditor() (by default a QLineEdit) is always visible. This + has implications for the alignment of the content: the default + editor aligns everything (even numbers) to the left whilst + numerical values in the cell are by default aligned to the right. + + If a cell with the edit type \c Always looks misaligned you could + reimplement createEditor() for these items. + + \value WhenCurrent + The cell \e looks editable only when it has keyboard focus (see + QTable::setCurrentCell()). + + \value OnTyping + The cell \e looks editable only when the user types in it or + double-clicks it. It resembles the \c WhenCurrent functionality + but is, perhaps, nicer. + + The \c OnTyping edit type is the default when QTableItem objects + are created by the convenience functions QTable::setText() and + QTable::setPixmap(). + + \value Never The cell is not editable. + + The cell is actually editable only if QTable::isRowReadOnly() is + FALSE for its row, QTable::isColumnReadOnly() is FALSE for its + column, and QTable::isReadOnly() is FALSE. + + QComboTableItems have an isEditable() property. This property is + used to indicate whether the user may enter their own text or are + restricted to choosing one of the choices in the list. + QComboTableItems may be interacted with only if they are editable + in accordance with their EditType as described above. + +*/ + +/*! + Creates a table item that is a child of table \a table with no + text. The item has the \l EditType \a et. + + The table item will use a QLineEdit for its editor, will not + word-wrap and will occupy a single cell. Insert the table item + into a table with QTable::setItem(). + + The table takes ownership of the table item, so a table item + should not be inserted into more than one table at a time. +*/ + +QTableItem::QTableItem( QTable *table, EditType et ) + : txt(), pix(), t( table ), edType( et ), wordwrap( FALSE ), + tcha( TRUE ), rw( -1 ), cl( -1 ), rowspan( 1 ), colspan( 1 ) +{ + enabled = TRUE; +} + +/*! + Creates a table item that is a child of table \a table with text + \a text. The item has the \l EditType \a et. + + The table item will use a QLineEdit for its editor, will not + word-wrap and will occupy a single cell. Insert the table item + into a table with QTable::setItem(). + + The table takes ownership of the table item, so a table item + should not be inserted into more than one table at a time. +*/ + +QTableItem::QTableItem( QTable *table, EditType et, const QString &text ) + : txt( text ), pix(), t( table ), edType( et ), wordwrap( FALSE ), + tcha( TRUE ), rw( -1 ), cl( -1 ), rowspan( 1 ), colspan( 1 ) +{ + enabled = TRUE; +} + +/*! + Creates a table item that is a child of table \a table with text + \a text and pixmap \a p. The item has the \l EditType \a et. + + The table item will display the pixmap to the left of the text. It + will use a QLineEdit for editing the text, will not word-wrap and + will occupy a single cell. Insert the table item into a table with + QTable::setItem(). + + The table takes ownership of the table item, so a table item + should not be inserted in more than one table at a time. +*/ + +QTableItem::QTableItem( QTable *table, EditType et, + const QString &text, const QPixmap &p ) + : txt( text ), pix( p ), t( table ), edType( et ), wordwrap( FALSE ), + tcha( TRUE ), rw( -1 ), cl( -1 ), rowspan( 1 ), colspan( 1 ) +{ + enabled = TRUE; +} + +/*! + The destructor deletes this item and frees all allocated + resources. + + If the table item is in a table (i.e. was inserted with + setItem()), it will be removed from the table and the cell it + occupied. +*/ + +QTableItem::~QTableItem() +{ + if ( table() ) + table()->takeItem( this ); +} + +int QTableItem::RTTI = 0; + +/*! + Returns the Run Time Type Identification value for this table item + which for QTableItems is 0. + + When you create subclasses based on QTableItem make sure that each + subclass returns a unique rtti() value. It is advisable to use + values greater than 1000, preferably large random numbers, to + allow for extensions to this class. + + \sa QCheckTableItem::rtti() QComboTableItem::rtti() +*/ + +int QTableItem::rtti() const +{ + return RTTI; +} + +/*! + Returns the table item's pixmap or a null pixmap if no pixmap has + been set. + + \sa setPixmap() text() +*/ + +QPixmap QTableItem::pixmap() const +{ + return pix; +} + + +/*! + Returns the text of the table item or QString::null if there is no + text. + + To ensure that the current value of the editor is returned, + setContentFromEditor() is called: + \list 1 + \i if the editMode() is \c Always, or + \i if editMode() is \e not \c Always but the editor of the cell is + active and the editor is not a QLineEdit. + \endlist + + This means that text() returns the original text value of the item + if the editor is a line edit, until the user commits an edit (e.g. + by pressing Enter or Tab) in which case the new text is returned. + For other editors (e.g. a combobox) setContentFromEditor() is + always called so the currently display value is the one returned. + + \sa setText() pixmap() +*/ + +QString QTableItem::text() const +{ + QWidget *w = table()->cellWidget( rw, cl ); + if ( w && ( edType == Always || + rtti() == QComboTableItem::RTTI || + rtti() == QCheckTableItem::RTTI ) ) + ( (QTableItem*)this )->setContentFromEditor( w ); + return txt; +} + +/*! + Sets pixmap \a p to be this item's pixmap. + + Note that setPixmap() does not update the cell the table item + belongs to. Use QTable::updateCell() to repaint the cell's + contents. + + For \l{QComboTableItem}s and \l{QCheckTableItem}s this function + has no visible effect. + + \sa QTable::setPixmap() pixmap() setText() +*/ + +void QTableItem::setPixmap( const QPixmap &p ) +{ + pix = p; +} + +/*! + Changes the table item's text to \a str. + + Note that setText() does not update the cell the table item + belongs to. Use QTable::updateCell() to repaint the cell's + contents. + + \sa QTable::setText() text() setPixmap() QTable::updateCell() +*/ + +void QTableItem::setText( const QString &str ) +{ + txt = str; +} + +/*! + This virtual function is used to paint the contents of an item + using the painter \a p in the rectangular area \a cr using the + color group \a cg. + + If \a selected is TRUE the cell is displayed in a way that + indicates that it is highlighted. + + You don't usually need to use this function but if you want to + draw custom content in a cell you will need to reimplement it. + + The painter passed to this function is translated so that 0, 0 + is the top-left corner of the item that is being painted. + + Note that the painter is not clipped by default in order to get + maximum efficiency. If you want clipping, use + + \code + p->setClipRect( table()->cellRect(row, col), QPainter::ClipPainter ); + //... your drawing code + p->setClipping( FALSE ); + \endcode + +*/ + +void QTableItem::paint( QPainter *p, const QColorGroup &cg, + const QRect &cr, bool selected ) +{ + p->fillRect( 0, 0, cr.width(), cr.height(), + selected ? cg.brush( QColorGroup::Highlight ) + : cg.brush( QColorGroup::Base ) ); + + int w = cr.width(); + int h = cr.height(); + + int x = 0; + if ( !pix.isNull() ) { + p->drawPixmap( 0, ( cr.height() - pix.height() ) / 2, pix ); + x = pix.width() + 2; + } + + if ( selected ) + p->setPen( cg.highlightedText() ); + else + p->setPen( cg.text() ); + p->drawText( x + 2, 0, w - x - 4, h, + wordwrap ? (alignment() | WordBreak) : alignment(), text() ); +} + +/*! +This virtual function creates an editor which the user can +interact with to edit the cell's contents. The default +implementation creates a QLineEdit. + +If the function returns 0, the cell is read-only. + +The returned widget should preferably be invisible, ideally with +QTable::viewport() as parent. + +If you reimplement this function you'll almost certainly need to +reimplement setContentFromEditor(), and may need to reimplement +sizeHint(). + +\quotefile table/statistics/statistics.cpp +\skipto createEditor +\printto } + +\sa QTable::createEditor() setContentFromEditor() QTable::viewport() setReplaceable() +*/ + +QWidget *QTableItem::createEditor() const +{ + QLineEdit *e = new QLineEdit( table()->viewport(), "qt_tableeditor" ); + e->setFrame( FALSE ); + e->setText( text() ); + return e; +} + +/*! +Whenever the content of a cell has been edited by the editor \a w, +QTable calls this virtual function to copy the new values into the +QTableItem. + +If you reimplement createEditor() and return something that is not +a QLineEdit you will need to reimplement this function. + +\quotefile table/statistics/statistics.cpp +\skipto setContentFromEditor +\printto } + +\sa QTable::setCellContentFromEditor() +*/ + +void QTableItem::setContentFromEditor( QWidget *w ) +{ + QLineEdit *le = ::qt_cast<QLineEdit*>(w); + if ( le ) { + QString input = le->text(); + if ( le->validator() ) + le->validator()->fixup( input ); + setText( input ); + } +} + +/*! + The alignment function returns how the text contents of the cell + are aligned when drawn. The default implementation aligns numbers + to the right and any other text to the left. + + \sa Qt::AlignmentFlags +*/ + +// ed: For consistency reasons a setAlignment() should be provided +// as well. + +int QTableItem::alignment() const +{ + bool num; + bool ok1 = FALSE, ok2 = FALSE; + (void)text().toInt( &ok1 ); + if ( !ok1 ) + (void)text().toDouble( &ok2 ); // ### should be .-aligned + num = ok1 || ok2; + + return ( num ? AlignRight : AlignLeft ) | AlignVCenter; +} + +/*! + If \a b is TRUE, the cell's text will be wrapped over multiple + lines, when necessary, to fit the width of the cell; otherwise the + text will be written as a single line. + + \sa wordWrap() QTable::adjustColumn() QTable::setColumnStretchable() +*/ + +void QTableItem::setWordWrap( bool b ) +{ + wordwrap = b; +} + +/*! + Returns TRUE if word wrap is enabled for the cell; otherwise + returns FALSE. + + \sa setWordWrap() +*/ + +bool QTableItem::wordWrap() const +{ + return wordwrap; +} + +/*! \internal */ + +void QTableItem::updateEditor( int oldRow, int oldCol ) +{ + if ( edType != Always ) + return; + if ( oldRow != -1 && oldCol != -1 ) + table()->clearCellWidget( oldRow, oldCol ); + if ( rw != -1 && cl != -1 ) + table()->setCellWidget( rw, cl, createEditor() ); +} + +/*! + Returns the table item's edit type. + + This is set when the table item is constructed. + + \sa EditType QTableItem() +*/ + +QTableItem::EditType QTableItem::editType() const +{ + return edType; +} + +/*! + If \a b is TRUE it is acceptable to replace the contents of the + cell with the contents of another QTableItem. If \a b is FALSE the + contents of the cell may not be replaced by the contents of + another table item. Table items that span more than one cell may + not have their contents replaced by another table item. + + (This differs from \l EditType because EditType is concerned with + whether the \e user is able to change the contents of a cell.) + + \sa isReplaceable() +*/ + +void QTableItem::setReplaceable( bool b ) +{ + tcha = b; +} + +/*! + This function returns whether the contents of the cell may be + replaced with the contents of another table item. Regardless of + this setting, table items that span more than one cell may not + have their contents replaced by another table item. + + (This differs from \l EditType because EditType is concerned with + whether the \e user is able to change the contents of a cell.) + + \sa setReplaceable() EditType +*/ + +bool QTableItem::isReplaceable() const +{ + if ( rowspan > 1 || colspan > 1 ) + return FALSE; + return tcha; +} + +/*! + This virtual function returns the key that should be used for + sorting. The default implementation returns the text() of the + relevant item. + + \sa QTable::setSorting() +*/ + +QString QTableItem::key() const +{ + return text(); +} + +/*! + This virtual function returns the size a cell needs to show its + entire content. + + If you subclass QTableItem you will often need to reimplement this + function. +*/ + +QSize QTableItem::sizeHint() const +{ + QSize strutSize = QApplication::globalStrut(); + if ( edType == Always && table()->cellWidget( rw, cl ) ) + return table()->cellWidget( rw, cl )->sizeHint().expandedTo( strutSize ); + + QSize s; + int x = 0; + if ( !pix.isNull() ) { + s = pix.size(); + s.setWidth( s.width() + 2 ); + x = pix.width() + 2; + } + + QString t = text(); + if ( !wordwrap && t.find( '\n' ) == -1 ) + return QSize( s.width() + table()->fontMetrics().width( text() ) + 10, + QMAX( s.height(), table()->fontMetrics().height() ) ).expandedTo( strutSize ); + + QRect r = table()->fontMetrics().boundingRect( x + 2, 0, table()->columnWidth( col() ) - x - 4, 0, + wordwrap ? (alignment() | WordBreak) : alignment(), + text() ); + r.setWidth( QMAX( r.width() + 10, table()->columnWidth( col() ) ) ); + return QSize( r.width(), QMAX( s.height(), r.height() ) ).expandedTo( strutSize ); +} + +/*! + Changes the extent of the QTableItem so that it spans multiple + cells covering \a rs rows and \a cs columns. The top left cell is + the original cell. + + \warning This function only works if the item has already been + inserted into the table using e.g. QTable::setItem(). This + function also checks to make sure if \a rs and \a cs are within + the bounds of the table and returns without changing the span if + they are not. In addition swapping, inserting or removing rows and + columns that cross QTableItems spanning more than one cell is not + supported. + + \sa rowSpan() colSpan() +*/ + +void QTableItem::setSpan( int rs, int cs ) +{ + if ( rs == rowspan && cs == colspan ) + return; + + if ( !table()->d->hasRowSpan ) + table()->d->hasRowSpan = rs > 1; + if ( !table()->d->hasColSpan ) + table()->d->hasColSpan = cs > 1; + // return if we are thinking too big... + if ( rw + rs > table()->numRows() ) + return; + + if ( cl + cs > table()->numCols() ) + return; + + if ( rw == -1 || cl == -1 ) + return; + + int rrow = rw; + int rcol = cl; + if ( rowspan > 1 || colspan > 1 ) { + QTable* t = table(); + t->takeItem( this ); + t->setItem( rrow, rcol, this ); + } + + rowspan = rs; + colspan = cs; + + for ( int r = 0; r < rowspan; ++r ) { + for ( int c = 0; c < colspan; ++c ) { + if ( r == 0 && c == 0 ) + continue; + qt_update_cell_widget = FALSE; + table()->setItem( r + rw, c + cl, this ); + qt_update_cell_widget = TRUE; + rw = rrow; + cl = rcol; + } + } + + table()->updateCell( rw, cl ); + QWidget *w = table()->cellWidget( rw, cl ); + if ( w ) + w->resize( table()->cellGeometry( rw, cl ).size() ); +} + +/*! + Returns the row span of the table item, usually 1. + + \sa setSpan() colSpan() +*/ + +int QTableItem::rowSpan() const +{ + return rowspan; +} + +/*! + Returns the column span of the table item, usually 1. + + \sa setSpan() rowSpan() +*/ + +int QTableItem::colSpan() const +{ + return colspan; +} + +/*! + Sets row \a r as the table item's row. Usually you do not need to + call this function. + + If the cell spans multiple rows, this function sets the top row + and retains the height of the multi-cell table item. + + \sa row() setCol() rowSpan() +*/ + +void QTableItem::setRow( int r ) +{ + rw = r; +} + +/*! + Sets column \a c as the table item's column. Usually you will not + need to call this function. + + If the cell spans multiple columns, this function sets the + left-most column and retains the width of the multi-cell table + item. + + \sa col() setRow() colSpan() +*/ + +void QTableItem::setCol( int c ) +{ + cl = c; +} + +/*! + Returns the row where the table item is located. If the cell spans + multiple rows, this function returns the top-most row. + + \sa col() setRow() +*/ + +int QTableItem::row() const +{ + return rw; +} + +/*! + Returns the column where the table item is located. If the cell + spans multiple columns, this function returns the left-most + column. + + \sa row() setCol() +*/ + +int QTableItem::col() const +{ + return cl; +} + +/*! + If \a b is TRUE, the table item is enabled; if \a b is FALSE the + table item is disabled. + + A disabled item doesn't respond to user interaction. + + \sa isEnabled() +*/ + +void QTableItem::setEnabled( bool b ) +{ + if ( b == (bool)enabled ) + return; + enabled = b; + table()->updateCell( row(), col() ); +} + +/*! + Returns TRUE if the table item is enabled; otherwise returns FALSE. + + \sa setEnabled() +*/ + +bool QTableItem::isEnabled() const +{ + return (bool)enabled; +} + +/*! + \class QComboTableItem + \brief The QComboTableItem class provides a means of using + comboboxes in QTables. +\if defined(commercial) + It is part of the <a href="commercialeditions.html">Qt Enterprise Edition</a>. +\endif + + \ingroup advanced + \module table + + A QComboTableItem is a table item which looks and behaves like a + combobox. The advantage of using QComboTableItems rather than real + comboboxes is that a QComboTableItem uses far less resources than + real comboboxes in \l{QTable}s. When the cell has the focus it + displays a real combobox which the user can interact with. When + the cell does not have the focus the cell \e looks like a + combobox. Only text items (i.e. no pixmaps) may be used in + QComboTableItems. + + QComboTableItem items have the edit type \c WhenCurrent (see + \l{EditType}). The QComboTableItem's list of items is provided by + a QStringList passed to the constructor. + + The list of items may be changed using setStringList(). The + current item can be set with setCurrentItem() and retrieved with + currentItem(). The text of the current item can be obtained with + currentText(), and the text of a particular item can be retrieved + with text(). + + If isEditable() is TRUE the QComboTableItem will permit the user + to either choose an existing list item, or create a new list item + by entering their own text; otherwise the user may only choose one + of the existing list items. + + To populate a table cell with a QComboTableItem use + QTable::setItem(). + + QComboTableItems may be deleted with QTable::clearCell(). + + QComboTableItems can be distinguished from \l{QTableItem}s and + \l{QCheckTableItem}s using their Run Time Type Identification + number (see rtti()). + + \img qtableitems.png Table Items + + \sa QCheckTableItem QTableItem QComboBox +*/ + +QComboBox *QComboTableItem::fakeCombo = 0; +QWidget *QComboTableItem::fakeComboWidget = 0; +int QComboTableItem::fakeRef = 0; + +/*! + Creates a combo table item for the table \a table. The combobox's + list of items is passed in the \a list argument. If \a editable is + TRUE the user may type in new list items; if \a editable is FALSE + the user may only select from the list of items provided. + + By default QComboTableItems cannot be replaced by other table + items since isReplaceable() returns FALSE by default. + + \sa QTable::clearCell() EditType +*/ + +QComboTableItem::QComboTableItem( QTable *table, const QStringList &list, bool editable ) + : QTableItem( table, WhenCurrent, "" ), entries( list ), current( 0 ), edit( editable ) +{ + setReplaceable( FALSE ); + if ( !QComboTableItem::fakeCombo ) { + QComboTableItem::fakeComboWidget = new QWidget( 0, 0 ); + QComboTableItem::fakeCombo = new QComboBox( FALSE, QComboTableItem::fakeComboWidget, 0 ); + QComboTableItem::fakeCombo->hide(); + } + ++QComboTableItem::fakeRef; + if ( entries.count() ) + setText( *entries.at( current ) ); +} + +/*! + QComboTableItem destructor. +*/ +QComboTableItem::~QComboTableItem() +{ + if (--QComboTableItem::fakeRef <= 0) { + delete QComboTableItem::fakeComboWidget; + QComboTableItem::fakeComboWidget = 0; + QComboTableItem::fakeCombo = 0; + } +} + +/*! + Sets the list items of this QComboTableItem to the strings in the + string list \a l. +*/ + +void QComboTableItem::setStringList( const QStringList &l ) +{ + entries = l; + current = 0; + if ( entries.count() ) + setText( *entries.at( current ) ); + if ( table()->cellWidget( row(), col() ) ) { + cb->clear(); + cb->insertStringList( entries ); + } + table()->updateCell( row(), col() ); +} + +/*! \reimp */ + +QWidget *QComboTableItem::createEditor() const +{ + // create an editor - a combobox in our case + ( (QComboTableItem*)this )->cb = new QComboBox( edit, table()->viewport(), "qt_editor_cb" ); + cb->insertStringList( entries ); + cb->setCurrentItem( current ); + QObject::connect( cb, SIGNAL( activated(int) ), table(), SLOT( doValueChanged() ) ); + return cb; +} + +/*! \reimp */ + +void QComboTableItem::setContentFromEditor( QWidget *w ) +{ + QComboBox *cb = ::qt_cast<QComboBox*>(w); + if ( cb ) { + entries.clear(); + for ( int i = 0; i < cb->count(); ++i ) + entries << cb->text( i ); + current = cb->currentItem(); + setText( *entries.at( current ) ); + } +} + +/*! \reimp */ + +void QComboTableItem::paint( QPainter *p, const QColorGroup &cg, + const QRect &cr, bool selected ) +{ + fakeCombo->resize( cr.width(), cr.height() ); + + QColorGroup c( cg ); + if ( selected ) { + c.setBrush( QColorGroup::Base, cg.brush( QColorGroup::Highlight ) ); + c.setColor( QColorGroup::Text, cg.highlightedText() ); + } + + QStyle::SFlags flags = QStyle::Style_Default; + if(isEnabled() && table()->isEnabled()) + flags |= QStyle::Style_Enabled; + table()->style().drawComplexControl( QStyle::CC_ComboBox, p, fakeCombo, fakeCombo->rect(), c, flags ); + + p->save(); + QRect textR = table()->style().querySubControlMetrics(QStyle::CC_ComboBox, fakeCombo, + QStyle::SC_ComboBoxEditField); + int align = alignment(); // alignment() changes entries + p->drawText( textR, wordWrap() ? ( align | WordBreak ) : align, *entries.at( current ) ); + p->restore(); +} + +/*! + Sets the list item \a i to be the combo table item's current list + item. + + \sa currentItem() +*/ + +void QComboTableItem::setCurrentItem( int i ) +{ + QWidget *w = table()->cellWidget( row(), col() ); + QComboBox *cb = ::qt_cast<QComboBox*>(w); + if ( cb ) { + cb->setCurrentItem( i ); + current = i; + setText( cb->currentText() ); + } else { + current = i; + setText( *entries.at( i ) ); + table()->updateCell( row(), col() ); + } +} + +/*! + \overload + + Sets the list item whose text is \a s to be the combo table item's + current list item. Does nothing if no list item has the text \a s. + + \sa currentItem() +*/ + +void QComboTableItem::setCurrentItem( const QString &s ) +{ + int i = entries.findIndex( s ); + if ( i != -1 ) + setCurrentItem( i ); +} + +/*! + Returns the index of the combo table item's current list item. + + \sa setCurrentItem() +*/ + +int QComboTableItem::currentItem() const +{ + QWidget *w = table()->cellWidget( row(), col() ); + QComboBox *cb = ::qt_cast<QComboBox*>(w); + if ( cb ) + return cb->currentItem(); + return current; +} + +/*! + Returns the text of the combo table item's current list item. + + \sa currentItem() text() +*/ + +QString QComboTableItem::currentText() const +{ + QWidget *w = table()->cellWidget( row(), col() ); + QComboBox *cb = ::qt_cast<QComboBox*>(w); + if ( cb ) + return cb->currentText(); + return *entries.at( current ); +} + +/*! + Returns the total number of list items in the combo table item. +*/ + +int QComboTableItem::count() const +{ + QWidget *w = table()->cellWidget( row(), col() ); + QComboBox *cb = ::qt_cast<QComboBox*>(w); + if ( cb ) + return cb->count(); + return (int)entries.count(); //### size_t/int cast +} + +/*! + Returns the text of the combo's list item at index \a i. + + \sa currentText() +*/ + +QString QComboTableItem::text( int i ) const +{ + QWidget *w = table()->cellWidget( row(), col() ); + QComboBox *cb = ::qt_cast<QComboBox*>(w); + if ( cb ) + return cb->text( i ); + return *entries.at( i ); +} + +/*! + If \a b is TRUE the combo table item can be edited, i.e. the user + may enter a new text item themselves. If \a b is FALSE the user may + may only choose one of the existing items. + + \sa isEditable() +*/ + +void QComboTableItem::setEditable( bool b ) +{ + edit = b; +} + +/*! + Returns TRUE if the user can add their own list items to the + combobox's list of items; otherwise returns FALSE. + + \sa setEditable() +*/ + +bool QComboTableItem::isEditable() const +{ + return edit; +} + +int QComboTableItem::RTTI = 1; + +/*! + \fn int QComboTableItem::rtti() const + + Returns 1. + + Make your derived classes return their own values for rtti()to + distinguish between different table item subclasses. You should + use values greater than 1000, preferably a large random number, to + allow for extensions to this class. + + + \sa QTableItem::rtti() +*/ + +int QComboTableItem::rtti() const +{ + return RTTI; +} + +/*! \reimp */ + +QSize QComboTableItem::sizeHint() const +{ + fakeCombo->insertItem( currentText() ); + fakeCombo->setCurrentItem( fakeCombo->count() - 1 ); + QSize sh = fakeCombo->sizeHint(); + fakeCombo->removeItem( fakeCombo->count() - 1 ); + return sh.expandedTo( QApplication::globalStrut() ); +} + +/*! + \class QCheckTableItem + \brief The QCheckTableItem class provides checkboxes in QTables. +\if defined(commercial) + It is part of the <a href="commercialeditions.html">Qt Enterprise Edition</a>. +\endif + + \ingroup advanced + \module table + + A QCheckTableItem is a table item which looks and behaves like a + checkbox. The advantage of using QCheckTableItems rather than real + checkboxes is that a QCheckTableItem uses far less resources than + a real checkbox would in a \l{QTable}. When the cell has the focus + it displays a real checkbox which the user can interact with. When + the cell does not have the focus the cell \e looks like a + checkbox. Pixmaps may not be used in QCheckTableItems. + + QCheckTableItem items have the edit type \c WhenCurrent (see + \l{EditType}). + + To change the checkbox's label use setText(). The checkbox can be + checked and unchecked with setChecked() and its state retrieved + using isChecked(). + + To populate a table cell with a QCheckTableItem use + QTable::setItem(). + + QCheckTableItems can be distinguished from \l{QTableItem}s and + \l{QComboTableItem}s using their Run Time Type Identification + (rtti) value. + + \img qtableitems.png Table Items + + \sa rtti() EditType QComboTableItem QTableItem QCheckBox +*/ + +/*! + Creates a QCheckTableItem with an \l{EditType} of \c WhenCurrent + as a child of \a table. The checkbox is initially unchecked and + its label is set to the string \a txt. +*/ + +QCheckTableItem::QCheckTableItem( QTable *table, const QString &txt ) + : QTableItem( table, WhenCurrent, txt ), checked( FALSE ) +{ +} + +/*! \reimp */ + +void QCheckTableItem::setText( const QString &t ) +{ + QTableItem::setText( t ); + QWidget *w = table()->cellWidget( row(), col() ); + QCheckBox *cb = ::qt_cast<QCheckBox*>(w); + if ( cb ) + cb->setText( t ); +} + + +/*! \reimp */ + +QWidget *QCheckTableItem::createEditor() const +{ + // create an editor - a combobox in our case + ( (QCheckTableItem*)this )->cb = new QCheckBox( table()->viewport(), "qt_editor_checkbox" ); + cb->setChecked( checked ); + cb->setText( text() ); + cb->setBackgroundColor( table()->viewport()->backgroundColor() ); + QObject::connect( cb, SIGNAL( toggled(bool) ), table(), SLOT( doValueChanged() ) ); + return cb; +} + +/*! \reimp */ + +void QCheckTableItem::setContentFromEditor( QWidget *w ) +{ + QCheckBox *cb = ::qt_cast<QCheckBox*>(w); + if ( cb ) + checked = cb->isChecked(); +} + +/*! \reimp */ + +void QCheckTableItem::paint( QPainter *p, const QColorGroup &cg, + const QRect &cr, bool selected ) +{ + p->fillRect( 0, 0, cr.width(), cr.height(), + selected ? cg.brush( QColorGroup::Highlight ) + : cg.brush( QColorGroup::Base ) ); + + int w = cr.width(); + int h = cr.height(); + QSize sz = QSize( table()->style().pixelMetric( QStyle::PM_IndicatorWidth ), + table()->style().pixelMetric( QStyle::PM_IndicatorHeight ) ); + QColorGroup c( cg ); + c.setBrush( QColorGroup::Background, c.brush( QColorGroup::Base ) ); + QStyle::SFlags flags = QStyle::Style_Default; + if(isEnabled()) + flags |= QStyle::Style_Enabled; + if ( checked ) + flags |= QStyle::Style_On; + else + flags |= QStyle::Style_Off; + if ( isEnabled() && table()->isEnabled() ) + flags |= QStyle::Style_Enabled; + + table()->style().drawPrimitive( QStyle::PE_Indicator, p, + QRect( 0, ( cr.height() - sz.height() ) / 2, sz.width(), sz.height() ), c, flags ); + int x = sz.width() + 6; + w = w - x; + if ( selected ) + p->setPen( cg.highlightedText() ); + else + p->setPen( cg.text() ); + p->drawText( x, 0, w, h, wordWrap() ? ( alignment() | WordBreak ) : alignment(), text() ); +} + +/*! + If \a b is TRUE the checkbox is checked; if \a b is FALSE the + checkbox is unchecked. + + \sa isChecked() +*/ + +void QCheckTableItem::setChecked( bool b ) +{ + checked = b; + table()->updateCell( row(), col() ); + QWidget *w = table()->cellWidget( row(), col() ); + QCheckBox *cb = ::qt_cast<QCheckBox*>(w); + if ( cb ) + cb->setChecked( b ); +} + +/*! + Returns TRUE if the checkbox table item is checked; otherwise + returns FALSE. + + \sa setChecked() +*/ + +bool QCheckTableItem::isChecked() const +{ + // #### why was this next line here. It must not be here, as + // #### people want to call isChecked() from within paintCell() + // #### and end up in an infinite loop that way + // table()->updateCell( row(), col() ); + QWidget *w = table()->cellWidget( row(), col() ); + QCheckBox *cb = ::qt_cast<QCheckBox*>(w); + if ( cb ) + return cb->isChecked(); + return checked; +} + +int QCheckTableItem::RTTI = 2; + +/*! + \fn int QCheckTableItem::rtti() const + + Returns 2. + + Make your derived classes return their own values for rtti()to + distinguish between different table item subclasses. You should + use values greater than 1000, preferably a large random number, to + allow for extensions to this class. + + \sa QTableItem::rtti() +*/ + +int QCheckTableItem::rtti() const +{ + return RTTI; +} + +/*! \reimp */ + +QSize QCheckTableItem::sizeHint() const +{ + QSize sz = QSize( table()->style().pixelMetric( QStyle::PM_IndicatorWidth ), + table()->style().pixelMetric( QStyle::PM_IndicatorHeight ) ); + sz.setWidth( sz.width() + 6 ); + QSize sh( QTableItem::sizeHint() ); + return QSize( sh.width() + sz.width(), QMAX( sh.height(), sz.height() ) ). + expandedTo( QApplication::globalStrut() ); +} + +/*! \file table/small-table-demo/main.cpp */ +/*! \file table/bigtable/main.cpp */ +/*! \file table/statistics/statistics.cpp */ + +/*! + \class QTable + \brief The QTable class provides a flexible editable table widget. +\if defined(commercial) + It is part of the <a href="commercialeditions.html">Qt Enterprise Edition</a>. +\endif + + \mainclass + \ingroup advanced + \module table + + QTable is easy to use, although it does have a large API because + of the comprehensive functionality that it provides. QTable + includes functions for manipulating \link #headers + headers\endlink, \link #columnsrows rows and columns\endlink, + \link #cells cells\endlink and \link #selections + selections\endlink. QTable also provides in-place editing and + \link dnd.html drag and drop\endlink, as well as a useful set of + \link #signals signals\endlink. QTable efficiently supports very + large tables, for example, tables one million by one million cells + are perfectly possible. QTable is economical with memory, using + none for unused cells. + + \code + QTable *table = new QTable( 100, 250, this ); + table->setPixmap( 3, 2, pix ); + table->setText( 3, 2, "A pixmap" ); + \endcode + + The first line constructs the table specifying its size in rows + and columns. We then insert a pixmap and some text into the \e + same \link #cells cell\endlink, with the pixmap appearing to the + left of the text. QTable cells can be populated with + \l{QTableItem}s, \l{QComboTableItem}s or by \l{QCheckTableItem}s. + By default a vertical header appears at the left of the table + showing row numbers and a horizontal header appears at the top of + the table showing column numbers. (The numbers displayed start at + 1, although row and column numbers within QTable begin at 0.) + + If you want to use mouse tracking call setMouseTracking( TRUE ) on + the \e viewport; (see \link qscrollview.html#allviews + QScrollView\endlink). + + \img qtableitems.png Table Items + + \target headers + \section1 Headers + + QTable supports a header column, e.g. to display row numbers, and + a header row, e.g to display column titles. To set row or column + labels use QHeader::setLabel() on the pointers returned by + verticalHeader() and horizontalHeader() respectively. The vertical + header is displayed within the table's left margin whose width is + set with setLeftMargin(). The horizontal header is displayed + within the table's top margin whose height is set with + setTopMargin(). The table's grid can be switched off with + setShowGrid(). If you want to hide a horizontal header call + hide(), and call setTopMargin( 0 ) so that the area the header + would have occupied is reduced to zero size. + + Header labels are indexed via their section numbers. Note that the + default behavior of QHeader regarding section numbers is overriden + for QTable. See the explanation below in the Rows and Columns + section in the discussion of moving columns and rows. + + \target columnsrows + \section1 Rows and Columns + + Row and column sizes are set with setRowHeight() and + setColumnWidth(). If you want a row high enough to show the + tallest item in its entirety, use adjustRow(). Similarly, to make + a column wide enough to show the widest item use adjustColumn(). + If you want the row height and column width to adjust + automatically as the height and width of the table changes use + setRowStretchable() and setColumnStretchable(). + + Rows and columns can be hidden and shown with hideRow(), + hideColumn(), showRow() and showColumn(). New rows and columns are + inserted using insertRows() and insertColumns(). Additional rows + and columns are added at the bottom (rows) or right (columns) if + you set setNumRows() or setNumCols() to be larger than numRows() + or numCols(). Existing rows and columns are removed with + removeRow() and removeColumn(). Multiple rows and columns can be + removed with removeRows() and removeColumns(). + + Rows and columns can be set to be moveable using + rowMovingEnabled() and columnMovingEnabled(). The user can drag + them to reorder them holding down the Ctrl key and dragging the + mouse. For performance reasons, the default behavior of QHeader + section numbers is overridden by QTable. Currently in QTable, when + a row or column is dragged and reordered, the section number is + also changed to its new position. Therefore, there is no + difference between the section and the index fields in QHeader. + The QTable QHeader classes do not provide a mechanism for indexing + independently of the user interface ordering. + + The table can be sorted using sortColumn(). Users can sort a + column by clicking its header if setSorting() is set to TRUE. Rows + can be swapped with swapRows(), columns with swapColumns() and + cells with swapCells(). + + For editable tables (see setReadOnly()) you can set the read-only + property of individual rows and columns with setRowReadOnly() and + setColumnReadOnly(). (Whether a cell is editable or read-only + depends on these settings and the cell's \link + qtableitem.html#wheneditable QTableItem::EditType\endlink.) + + The row and column which have the focus are returned by + currentRow() and currentColumn() respectively. + + Although many QTable functions operate in terms of rows and + columns the indexOf() function returns a single integer + identifying a particular cell. + + \target cells + \section1 Cells + + All of a QTable's cells are empty when the table is constructed. + + There are two approaches to populating the table's cells. The + first and simplest approach is to use QTableItems or QTableItem + subclasses. The second approach doesn't use QTableItems at all + which is useful for very large sparse tables but requires you to + reimplement a number of functions. We'll look at each approach in + turn. + + To put a string in a cell use setText(). This function will create + a new QTableItem for the cell if one doesn't already exist, and + displays the text in it. By default the table item's widget will + be a QLineEdit. A pixmap may be put in a cell with setPixmap(), + which also creates a table item if required. A cell may contain \e + both a pixmap and text; the pixmap is displayed to the left of the + text. Another approach is to construct a QTableItem or QTableItem + subclass, set its properties, then insert it into a cell with + setItem(). + + If you want cells which contain comboboxes use the QComboTableItem + class. Similarly if you require cells containing checkboxes use + the QCheckTableItem class. These table items look and behave just + like the combobox or checkbox widgets but consume far less memory. + + \quotefile table/small-table-demo/main.cpp + \skipto int j + \printuntil QCheckTableItem + In the example above we create a column of QCheckTableItems and + insert them into the table using setItem(). + + QTable takes ownership of its QTableItems and will delete them + when the table itself is destroyed. You can take ownership of a + table item using takeItem() which you use to move a cell's + contents from one cell to another, either within the same table, + or from one table to another. (See also, swapCells()). + + In-place editing of the text in QTableItems, and the values in + QComboTableItems and QCheckTableItems works automatically. Cells + may be editable or read-only, see \link + qtableitem.html#wheneditable QTableItem::EditType\endlink. If you + want fine control over editing see beginEdit() and endEdit(). + + The contents of a cell can be retrieved as a QTableItem using + item(), or as a string with text() or as a pixmap (if there is + one) with pixmap(). A cell's bounding rectangle is given by + cellGeometry(). Use updateCell() to repaint a cell, for example to + clear away a cell's visual representation after it has been + deleted with clearCell(). The table can be forced to scroll to + show a particular cell with ensureCellVisible(). The isSelected() + function indicates if a cell is selected. + + It is possible to use your own widget as a cell's widget using + setCellWidget(), but subclassing QTableItem might be a simpler + approach. The cell's widget (if there is one) can be removed with + clearCellWidget(). + + \keyword notes on large tables + \target bigtables + \section2 Large tables + + For large, sparse, tables using QTableItems or other widgets is + inefficient. The solution is to \e draw the cell as it should + appear and to create and destroy cell editors on demand. + + This approach requires that you reimplement various functions. + Reimplement paintCell() to display your data, and createEditor() + and setCellContentFromEditor() to support in-place editing. It + is very important to reimplement resizeData() to have no + functionality, to prevent QTable from attempting to create a huge + array. You will also need to reimplement item(), setItem(), + takeItem(), clearCell(), and insertWidget(), cellWidget() and + clearCellWidget(). In almost every circumstance (for sorting, + removing and inserting columns and rows, etc.), you also need + to reimplement swapRows(), swapCells() and swapColumns(), including + header handling. + + If you represent active cells with a dictionary of QTableItems and + QWidgets, i.e. only store references to cells that are actually + used, many of the functions can be implemented with a single line + of code. (See the \l table/bigtable/main.cpp example.) + + For more information on cells see the QTableItem documenation. + + \target selections + \section1 Selections + + QTable's support single selection, multi-selection (multiple + cells) or no selection. The selection mode is set with + setSelectionMode(). Use isSelected() to determine if a particular + cell is selected, and isRowSelected() and isColumnSelected() to + see if a row or column is selected. + + QTable's support many simultaneous selections. You can + programmatically select cells with addSelection(). The number of + selections is given by numSelections(). The current selection is + returned by currentSelection(). You can remove a selection with + removeSelection() and remove all selections with + clearSelection(). Selections are QTableSelection objects. + + To easily add a new selection use selectCells(), selectRow() or + selectColumn(). + + Alternatively, use addSelection() to add new selections using + QTableSelection objects. The advantage of using QTableSelection + objects is that you can call QTableSelection::expandTo() to resize + the selection and can query and compare them. + + The number of selections is given by numSelections(). The current + selection is returned by currentSelection(). You can remove a + selection with removeSelection() and remove all selections with + clearSelection(). + + \target signals + \section1 Signals + + When the user clicks a cell the currentChanged() signal is + emitted. You can also connect to the lower level clicked(), + doubleClicked() and pressed() signals. If the user changes the + selection the selectionChanged() signal is emitted; similarly if + the user changes a cell's value the valueChanged() signal is + emitted. If the user right-clicks (or presses the appropriate + platform-specific key sequence) the contextMenuRequested() signal + is emitted. If the user drops a drag and drop object the dropped() + signal is emitted with the drop event. +*/ + +/*! + \fn void QTable::currentChanged( int row, int col ) + + This signal is emitted when the current cell has changed to \a + row, \a col. +*/ + +/*! + \fn void QTable::valueChanged( int row, int col ) + + This signal is emitted when the user changed the value in the cell + at \a row, \a col. +*/ + +/*! + \fn int QTable::currentRow() const + + Returns the current row. + + \sa currentColumn() +*/ + +/*! + \fn int QTable::currentColumn() const + + Returns the current column. + + \sa currentRow() +*/ + +/*! + \enum QTable::EditMode + + \value NotEditing No cell is currently being edited. + + \value Editing A cell is currently being edited. The editor was + initialised with the cell's contents. + + \value Replacing A cell is currently being edited. The editor was + not initialised with the cell's contents. +*/ + +/*! + \enum QTable::SelectionMode + + \value NoSelection No cell can be selected by the user. + + \value Single The user may only select a single range of cells. + + \value Multi The user may select multiple ranges of cells. + + \value SingleRow The user may select one row at once. + + \value MultiRow The user may select multiple rows. +*/ + +/*! + \enum QTable::FocusStyle + + Specifies how the current cell (focus cell) is drawn. + + \value FollowStyle The current cell is drawn according to the + current style and the cell's background is also drawn selected, if + the current cell is within a selection + + \value SpreadSheet The current cell is drawn as in a spreadsheet. + This means, it is signified by a black rectangle around the cell, + and the background of the current cell is always drawn with the + widget's base color - even when selected. + +*/ + +/*! + \fn void QTable::clicked( int row, int col, int button, const QPoint &mousePos ) + + This signal is emitted when mouse button \a button is clicked. The + cell where the event took place is at \a row, \a col, and the + mouse's position is in \a mousePos. + + \sa Qt::ButtonState +*/ + +/*! + \fn void QTable::doubleClicked( int row, int col, int button, const QPoint &mousePos ) + + This signal is emitted when mouse button \a button is + double-clicked. The cell where the event took place is at \a row, + \a col, and the mouse's position is in \a mousePos. + + \sa Qt::ButtonState +*/ + +/*! + \fn void QTable::pressed( int row, int col, int button, const QPoint &mousePos ) + + This signal is emitted when mouse button \a button is pressed. The + cell where the event took place is at \a row, \a col, and the + mouse's position is in \a mousePos. + + \sa Qt::ButtonState +*/ + +/*! + \fn void QTable::selectionChanged() + + This signal is emitted whenever a selection changes. + + \sa QTableSelection +*/ + +/*! + \fn void QTable::contextMenuRequested( int row, int col, const QPoint & pos ) + + This signal is emitted when the user invokes a context menu with + the right mouse button (or with a system-specific keypress). The + cell where the event took place is at \a row, \a col. \a pos is + the position where the context menu will appear in the global + coordinate system. This signal is always emitted, even if the + contents of the cell are disabled. +*/ + +/*! + Creates an empty table object called \a name as a child of \a + parent. + + Call setNumRows() and setNumCols() to set the table size before + populating the table if you're using QTableItems. + + \sa QWidget::clearWFlags() Qt::WidgetFlags +*/ + +QTable::QTable( QWidget *parent, const char *name ) + : QScrollView( parent, name, WNoAutoErase | WStaticContents ), + leftHeader( 0 ), topHeader( 0 ), + currentSel( 0 ), lastSortCol( -1 ), sGrid( TRUE ), mRows( FALSE ), mCols( FALSE ), + asc( TRUE ), doSort( TRUE ), readOnly( FALSE ) +{ + init( 0, 0 ); +} + +/*! + Constructs an empty table called \a name with \a numRows rows and + \a numCols columns. The table is a child of \a parent. + + If you're using \l{QTableItem}s to populate the table's cells, you + can create QTableItem, QComboTableItem and QCheckTableItem items + and insert them into the table using setItem(). (See the notes on + large tables for an alternative to using QTableItems.) + + \sa QWidget::clearWFlags() Qt::WidgetFlags +*/ + +QTable::QTable( int numRows, int numCols, QWidget *parent, const char *name ) + : QScrollView( parent, name, WNoAutoErase | WStaticContents ), + leftHeader( 0 ), topHeader( 0 ), + currentSel( 0 ), lastSortCol( -1 ), sGrid( TRUE ), mRows( FALSE ), mCols( FALSE ), + asc( TRUE ), doSort( TRUE ), readOnly( FALSE ) +{ + init( numRows, numCols ); +} + +/*! \internal +*/ + +void QTable::init( int rows, int cols ) +{ +#ifndef QT_NO_DRAGANDDROP + setDragAutoScroll( FALSE ); +#endif + d = new QTablePrivate; + d->geomTimer = new QTimer( this ); + d->lastVisCol = 0; + d->lastVisRow = 0; + connect( d->geomTimer, SIGNAL( timeout() ), this, SLOT( updateGeometriesSlot() ) ); + shouldClearSelection = FALSE; + dEnabled = FALSE; + roRows.setAutoDelete( TRUE ); + roCols.setAutoDelete( TRUE ); + setSorting( FALSE ); + + unused = TRUE; // It's unused, ain't it? :) + + selMode = Multi; + + contents.setAutoDelete( TRUE ); + widgets.setAutoDelete( TRUE ); + + // Enable clipper and set background mode + enableClipper( qt_table_clipper_enabled ); + + viewport()->setFocusProxy( this ); + viewport()->setFocusPolicy( WheelFocus ); + + viewport()->setBackgroundMode( PaletteBase ); + setBackgroundMode( PaletteBackground, PaletteBase ); + setResizePolicy( Manual ); + selections.setAutoDelete( TRUE ); + + // Create headers + leftHeader = new QTableHeader( rows, this, this, "left table header" ); + leftHeader->setOrientation( Vertical ); + leftHeader->setTracking( TRUE ); + leftHeader->setMovingEnabled( TRUE ); + topHeader = new QTableHeader( cols, this, this, "right table header" ); + topHeader->setOrientation( Horizontal ); + topHeader->setTracking( TRUE ); + topHeader->setMovingEnabled( TRUE ); + if ( QApplication::reverseLayout() ) + setMargins( 0, fontMetrics().height() + 4, 30, 0 ); + else + setMargins( 30, fontMetrics().height() + 4, 0, 0 ); + + topHeader->setUpdatesEnabled( FALSE ); + leftHeader->setUpdatesEnabled( FALSE ); + // Initialize headers + int i = 0; + for ( i = 0; i < numCols(); ++i ) + topHeader->resizeSection( i, QMAX( 100, QApplication::globalStrut().height() ) ); + for ( i = 0; i < numRows(); ++i ) + leftHeader->resizeSection( i, QMAX( 20, QApplication::globalStrut().width() ) ); + topHeader->setUpdatesEnabled( TRUE ); + leftHeader->setUpdatesEnabled( TRUE ); + + // Prepare for contents + contents.setAutoDelete( FALSE ); + + // Connect header, table and scrollbars + connect( horizontalScrollBar(), SIGNAL( valueChanged(int) ), + topHeader, SLOT( setOffset(int) ) ); + connect( verticalScrollBar(), SIGNAL( valueChanged(int) ), + leftHeader, SLOT( setOffset(int) ) ); + connect( topHeader, SIGNAL( sectionSizeChanged(int) ), + this, SLOT( columnWidthChanged(int) ) ); + connect( topHeader, SIGNAL( indexChange(int,int,int) ), + this, SLOT( columnIndexChanged(int,int,int) ) ); + connect( topHeader, SIGNAL( sectionClicked(int) ), + this, SLOT( columnClicked(int) ) ); + connect( leftHeader, SIGNAL( sectionSizeChanged(int) ), + this, SLOT( rowHeightChanged(int) ) ); + connect( leftHeader, SIGNAL( indexChange(int,int,int) ), + this, SLOT( rowIndexChanged(int,int,int) ) ); + + // Initialize variables + autoScrollTimer = new QTimer( this ); + connect( autoScrollTimer, SIGNAL( timeout() ), + this, SLOT( doAutoScroll() ) ); + curRow = curCol = 0; + topHeader->setSectionState( curCol, QTableHeader::Bold ); + leftHeader->setSectionState( curRow, QTableHeader::Bold ); + edMode = NotEditing; + editRow = editCol = -1; + + drawActiveSelection = TRUE; + + installEventFilter( this ); + + focusStl = SpreadSheet; + + was_visible = FALSE; + + // initial size + resize( 640, 480 ); +} + +/*! + Releases all the resources used by the QTable object, + including all \l{QTableItem}s and their widgets. +*/ + +QTable::~QTable() +{ + setUpdatesEnabled( FALSE ); + contents.setAutoDelete( TRUE ); + contents.clear(); + widgets.clear(); + + delete d; +} + +void QTable::setReadOnly( bool b ) +{ + readOnly = b; + + QTableItem *i = item(curRow, curCol); + if (readOnly && isEditing()) { + endEdit(editRow, editCol, TRUE, FALSE); + } else if (!readOnly && i && (i->editType() == QTableItem::WhenCurrent + || i->editType() == QTableItem::Always)) { + editCell(curRow, curCol); + } +} + +/*! + If \a ro is TRUE, row \a row is set to be read-only; otherwise the + row is set to be editable. + + Whether a cell in this row is editable or read-only depends on the + cell's EditType, and this setting: + see \link qtableitem.html#wheneditable QTableItem::EditType\endlink. + + \sa isRowReadOnly() setColumnReadOnly() setReadOnly() +*/ + +void QTable::setRowReadOnly( int row, bool ro ) +{ + if ( ro ) + roRows.replace( row, new int( 0 ) ); + else + roRows.remove( row ); + + if (curRow == row) { + QTableItem *i = item(curRow, curCol); + if (ro && isEditing()) { + endEdit(editRow, editCol, TRUE, FALSE); + } else if (!ro && i && (i->editType() == QTableItem::WhenCurrent + || i->editType() == QTableItem::Always)) { + editCell(curRow, curCol); + } + } +} + +/*! + If \a ro is TRUE, column \a col is set to be read-only; otherwise + the column is set to be editable. + + Whether a cell in this column is editable or read-only depends on + the cell's EditType, and this setting: + see \link qtableitem.html#wheneditable QTableItem::EditType\endlink. + + \sa isColumnReadOnly() setRowReadOnly() setReadOnly() + +*/ + +void QTable::setColumnReadOnly( int col, bool ro ) +{ + if ( ro ) + roCols.replace( col, new int( 0 ) ); + else + roCols.remove( col ); + + if (curCol == col) { + QTableItem *i = item(curRow, curCol); + if (ro && isEditing()) { + endEdit(editRow, editCol, TRUE, FALSE); + } else if (!ro && i && (i->editType() == QTableItem::WhenCurrent + || i->editType() == QTableItem::Always)) { + editCell(curRow, curCol); + } + } +} + +/*! + \property QTable::readOnly + \brief whether the table is read-only + + Whether a cell in the table is editable or read-only depends on + the cell's \link QTableItem::EditType EditType\endlink, and this setting: + see \link qtableitem.html#wheneditable + QTableItem::EditType\endlink. + + \sa QWidget::enabled setColumnReadOnly() setRowReadOnly() +*/ + +bool QTable::isReadOnly() const +{ + return readOnly; +} + +/*! + Returns TRUE if row \a row is read-only; otherwise returns FALSE. + + Whether a cell in this row is editable or read-only depends on the + cell's \link QTableItem::EditType EditType\endlink, and this + setting: see \link qtableitem.html#wheneditable + QTableItem::EditType\endlink. + + \sa setRowReadOnly() isColumnReadOnly() +*/ + +bool QTable::isRowReadOnly( int row ) const +{ + return (roRows.find( row ) != 0); +} + +/*! + Returns TRUE if column \a col is read-only; otherwise returns + FALSE. + + Whether a cell in this column is editable or read-only depends on + the cell's EditType, and this setting: see \link + qtableitem.html#wheneditable QTableItem::EditType\endlink. + + \sa setColumnReadOnly() isRowReadOnly() +*/ + +bool QTable::isColumnReadOnly( int col ) const +{ + return (roCols.find( col ) != 0); +} + +void QTable::setSelectionMode( SelectionMode mode ) +{ + if ( mode == selMode ) + return; + selMode = mode; + clearSelection(); + if ( isRowSelection( selMode ) && numRows() > 0 && numCols() > 0 ) { + currentSel = new QTableSelection(); + selections.append( currentSel ); + currentSel->init( curRow, 0 ); + currentSel->expandTo( curRow, numCols() - 1 ); + repaintSelections( 0, currentSel ); + } +} + +/*! + \property QTable::selectionMode + \brief the current selection mode + + The default mode is \c Multi which allows the user to select + multiple ranges of cells. + + \sa SelectionMode setSelectionMode() +*/ + +QTable::SelectionMode QTable::selectionMode() const +{ + return selMode; +} + +/*! + \property QTable::focusStyle + \brief how the current (focus) cell is drawn + + The default style is \c SpreadSheet. + + \sa QTable::FocusStyle +*/ + +void QTable::setFocusStyle( FocusStyle fs ) +{ + focusStl = fs; + updateCell( curRow, curCol ); +} + +QTable::FocusStyle QTable::focusStyle() const +{ + return focusStl; +} + +/*! + This functions updates all the header states to be in sync with + the current selections. This should be called after + programatically changing, adding or removing selections, so that + the headers are updated. +*/ + +void QTable::updateHeaderStates() +{ + horizontalHeader()->setUpdatesEnabled( FALSE ); + verticalHeader()->setUpdatesEnabled( FALSE ); + + ( (QTableHeader*)verticalHeader() )->setSectionStateToAll( QTableHeader::Normal ); + ( (QTableHeader*)horizontalHeader() )->setSectionStateToAll( QTableHeader::Normal ); + + QPtrListIterator<QTableSelection> it( selections ); + QTableSelection *s; + while ( ( s = it.current() ) != 0 ) { + ++it; + if ( s->isActive() ) { + if ( s->leftCol() == 0 && + s->rightCol() == numCols() - 1 ) { + for ( int i = 0; i < s->bottomRow() - s->topRow() + 1; ++i ) + leftHeader->setSectionState( s->topRow() + i, QTableHeader::Selected ); + } + if ( s->topRow() == 0 && + s->bottomRow() == numRows() - 1 ) { + for ( int i = 0; i < s->rightCol() - s->leftCol() + 1; ++i ) + topHeader->setSectionState( s->leftCol() + i, QTableHeader::Selected ); + } + } + } + + horizontalHeader()->setUpdatesEnabled( TRUE ); + verticalHeader()->setUpdatesEnabled( TRUE ); + horizontalHeader()->repaint( FALSE ); + verticalHeader()->repaint( FALSE ); +} + +/*! + Returns the table's top QHeader. + + This header contains the column labels. + + To modify a column label use QHeader::setLabel(), e.g. + \quotefile table/statistics/statistics.cpp + \skipto horizontalHeader + \printline + + \sa verticalHeader() setTopMargin() QHeader +*/ + +QHeader *QTable::horizontalHeader() const +{ + return (QHeader*)topHeader; +} + +/*! + Returns the table's vertical QHeader. + + This header contains the row labels. + + \sa horizontalHeader() setLeftMargin() QHeader +*/ + +QHeader *QTable::verticalHeader() const +{ + return (QHeader*)leftHeader; +} + +void QTable::setShowGrid( bool b ) +{ + if ( sGrid == b ) + return; + sGrid = b; + updateContents(); +} + +/*! + \property QTable::showGrid + \brief whether the table's grid is displayed + + The grid is shown by default. +*/ + +bool QTable::showGrid() const +{ + return sGrid; +} + +/*! + \property QTable::columnMovingEnabled + \brief whether columns can be moved by the user + + The default is FALSE. Columns are moved by dragging whilst holding + down the Ctrl key. + + \warning If QTable is used to move header sections as a result of user + interaction, the mapping between header indexes and section exposed by + QHeader will not reflect the order of the headers in the table; i.e., + QTable does not call QHeader::moveSection() to move sections but handles + move operations internally. + + \sa rowMovingEnabled +*/ + +void QTable::setColumnMovingEnabled( bool b ) +{ + mCols = b; +} + +bool QTable::columnMovingEnabled() const +{ + return mCols; +} + +/*! + \property QTable::rowMovingEnabled + \brief whether rows can be moved by the user + + The default is FALSE. Rows are moved by dragging whilst holding + down the Ctrl key. + + \warning If QTable is used to move header sections as a result of user + interaction, the mapping between header indexes and section exposed by + QHeader will not reflect the order of the headers in the table; i.e., + QTable does not call QHeader::moveSection() to move sections but handles + move operations internally. + + \sa columnMovingEnabled +*/ + +void QTable::setRowMovingEnabled( bool b ) +{ + mRows = b; +} + +bool QTable::rowMovingEnabled() const +{ + return mRows; +} + +/*! + This is called when QTable's internal array needs to be resized to + \a len elements. + + If you don't use QTableItems you should reimplement this as an + empty method to avoid wasting memory. See the notes on large + tables for further details. +*/ + +void QTable::resizeData( int len ) +{ + contents.resize( len ); + widgets.resize( len ); +} + +/*! + Swaps the data in \a row1 and \a row2. + + This function is used to swap the positions of two rows. It is + called when the user changes the order of rows (see + setRowMovingEnabled()), and when rows are sorted. + + If you don't use \l{QTableItem}s and want your users to be able to + swap rows, e.g. for sorting, you will need to reimplement this + function. (See the notes on large tables.) + + If \a swapHeader is TRUE, the rows' header contents is also + swapped. + + This function will not update the QTable, you will have to do + this manually, e.g. by calling updateContents(). + + \sa swapColumns() swapCells() +*/ + +void QTable::swapRows( int row1, int row2, bool swapHeader ) +{ + if ( swapHeader ) + leftHeader->swapSections( row1, row2, FALSE ); + + QPtrVector<QTableItem> tmpContents; + tmpContents.resize( numCols() ); + QPtrVector<QWidget> tmpWidgets; + tmpWidgets.resize( numCols() ); + int i; + + contents.setAutoDelete( FALSE ); + widgets.setAutoDelete( FALSE ); + for ( i = 0; i < numCols(); ++i ) { + QTableItem *i1, *i2; + i1 = item( row1, i ); + i2 = item( row2, i ); + if ( i1 || i2 ) { + tmpContents.insert( i, i1 ); + contents.remove( indexOf( row1, i ) ); + contents.insert( indexOf( row1, i ), i2 ); + contents.remove( indexOf( row2, i ) ); + contents.insert( indexOf( row2, i ), tmpContents[ i ] ); + if ( contents[ indexOf( row1, i ) ] ) + contents[ indexOf( row1, i ) ]->setRow( row1 ); + if ( contents[ indexOf( row2, i ) ] ) + contents[ indexOf( row2, i ) ]->setRow( row2 ); + } + + QWidget *w1, *w2; + w1 = cellWidget( row1, i ); + w2 = cellWidget( row2, i ); + if ( w1 || w2 ) { + tmpWidgets.insert( i, w1 ); + widgets.remove( indexOf( row1, i ) ); + widgets.insert( indexOf( row1, i ), w2 ); + widgets.remove( indexOf( row2, i ) ); + widgets.insert( indexOf( row2, i ), tmpWidgets[ i ] ); + } + } + contents.setAutoDelete( FALSE ); + widgets.setAutoDelete( TRUE ); + + updateRowWidgets( row1 ); + updateRowWidgets( row2 ); + if ( curRow == row1 ) + curRow = row2; + else if ( curRow == row2 ) + curRow = row1; + if ( editRow == row1 ) + editRow = row2; + else if ( editRow == row2 ) + editRow = row1; +} + +/*! + Sets the left margin to be \a m pixels wide. + + The verticalHeader(), which displays row labels, occupies this + margin. + + In an Arabic or Hebrew localization, the verticalHeader() will + appear on the right side of the table, and this call will set the + right margin. + + \sa leftMargin() setTopMargin() verticalHeader() +*/ + +void QTable::setLeftMargin( int m ) +{ + if ( QApplication::reverseLayout() ) + setMargins( leftMargin(), topMargin(), m, bottomMargin() ); + else + setMargins( m, topMargin(), rightMargin(), bottomMargin() ); + updateGeometries(); +} + +/*! + Sets the top margin to be \a m pixels high. + + The horizontalHeader(), which displays column labels, occupies + this margin. + + \sa topMargin() setLeftMargin() +*/ + +void QTable::setTopMargin( int m ) +{ + setMargins( leftMargin(), m, rightMargin(), bottomMargin() ); + updateGeometries(); +} + +/*! + Swaps the data in \a col1 with \a col2. + + This function is used to swap the positions of two columns. It is + called when the user changes the order of columns (see + setColumnMovingEnabled(), and when columns are sorted. + + If you don't use \l{QTableItem}s and want your users to be able to + swap columns you will need to reimplement this function. (See the + notes on large tables.) + + If \a swapHeader is TRUE, the columns' header contents is also + swapped. + + \sa swapCells() +*/ + +void QTable::swapColumns( int col1, int col2, bool swapHeader ) +{ + if ( swapHeader ) + topHeader->swapSections( col1, col2, FALSE ); + + QPtrVector<QTableItem> tmpContents; + tmpContents.resize( numRows() ); + QPtrVector<QWidget> tmpWidgets; + tmpWidgets.resize( numRows() ); + int i; + + contents.setAutoDelete( FALSE ); + widgets.setAutoDelete( FALSE ); + for ( i = 0; i < numRows(); ++i ) { + QTableItem *i1, *i2; + i1 = item( i, col1 ); + i2 = item( i, col2 ); + if ( i1 || i2 ) { + tmpContents.insert( i, i1 ); + contents.remove( indexOf( i, col1 ) ); + contents.insert( indexOf( i, col1 ), i2 ); + contents.remove( indexOf( i, col2 ) ); + contents.insert( indexOf( i, col2 ), tmpContents[ i ] ); + if ( contents[ indexOf( i, col1 ) ] ) + contents[ indexOf( i, col1 ) ]->setCol( col1 ); + if ( contents[ indexOf( i, col2 ) ] ) + contents[ indexOf( i, col2 ) ]->setCol( col2 ); + } + + QWidget *w1, *w2; + w1 = cellWidget( i, col1 ); + w2 = cellWidget( i, col2 ); + if ( w1 || w2 ) { + tmpWidgets.insert( i, w1 ); + widgets.remove( indexOf( i, col1 ) ); + widgets.insert( indexOf( i, col1 ), w2 ); + widgets.remove( indexOf( i, col2 ) ); + widgets.insert( indexOf( i, col2 ), tmpWidgets[ i ] ); + } + } + contents.setAutoDelete( FALSE ); + widgets.setAutoDelete( TRUE ); + + columnWidthChanged( col1 ); + columnWidthChanged( col2 ); + if ( curCol == col1 ) + curCol = col2; + else if ( curCol == col2 ) + curCol = col1; + if ( editCol == col1 ) + editCol = col2; + else if ( editCol == col2 ) + editCol = col1; +} + +/*! + Swaps the contents of the cell at \a row1, \a col1 with the + contents of the cell at \a row2, \a col2. + + This function is also called when the table is sorted. + + If you don't use \l{QTableItem}s and want your users to be able to + swap cells, you will need to reimplement this function. (See the + notes on large tables.) + + \sa swapColumns() swapRows() +*/ + +void QTable::swapCells( int row1, int col1, int row2, int col2 ) +{ + contents.setAutoDelete( FALSE ); + widgets.setAutoDelete( FALSE ); + QTableItem *i1, *i2; + i1 = item( row1, col1 ); + i2 = item( row2, col2 ); + if ( i1 || i2 ) { + QTableItem *tmp = i1; + contents.remove( indexOf( row1, col1 ) ); + contents.insert( indexOf( row1, col1 ), i2 ); + contents.remove( indexOf( row2, col2 ) ); + contents.insert( indexOf( row2, col2 ), tmp ); + if ( contents[ indexOf( row1, col1 ) ] ) { + contents[ indexOf( row1, col1 ) ]->setRow( row1 ); + contents[ indexOf( row1, col1 ) ]->setCol( col1 ); + } + if ( contents[ indexOf( row2, col2 ) ] ) { + contents[ indexOf( row2, col2 ) ]->setRow( row2 ); + contents[ indexOf( row2, col2 ) ]->setCol( col2 ); + } + } + + QWidget *w1, *w2; + w1 = cellWidget( row1, col1 ); + w2 = cellWidget( row2, col2 ); + if ( w1 || w2 ) { + QWidget *tmp = w1; + widgets.remove( indexOf( row1, col1 ) ); + widgets.insert( indexOf( row1, col1 ), w2 ); + widgets.remove( indexOf( row2, col2 ) ); + widgets.insert( indexOf( row2, col2 ), tmp ); + } + + updateRowWidgets( row1 ); + updateRowWidgets( row2 ); + updateColWidgets( col1 ); + updateColWidgets( col2 ); + contents.setAutoDelete( FALSE ); + widgets.setAutoDelete( TRUE ); +} + +static bool is_child_of( QWidget *child, QWidget *parent ) +{ + while ( child ) { + if ( child == parent ) + return TRUE; + child = child->parentWidget(); + } + return FALSE; +} + +/*! + Draws the table contents on the painter \a p. This function is + optimized so that it only draws the cells inside the \a cw pixels + wide and \a ch pixels high clipping rectangle at position \a cx, + \a cy. + + Additionally, drawContents() highlights the current cell. +*/ + +void QTable::drawContents( QPainter *p, int cx, int cy, int cw, int ch ) +{ + int colfirst = columnAt( cx ); + int collast = columnAt( cx + cw ); + int rowfirst = rowAt( cy ); + int rowlast = rowAt( cy + ch ); + + if ( rowfirst == -1 || colfirst == -1 ) { + paintEmptyArea( p, cx, cy, cw, ch ); + return; + } + + drawActiveSelection = hasFocus() || viewport()->hasFocus() || d->inMenuMode + || is_child_of( qApp->focusWidget(), viewport() ) + || !style().styleHint( QStyle::SH_ItemView_ChangeHighlightOnFocus, this ); + if ( rowlast == -1 ) + rowlast = numRows() - 1; + if ( collast == -1 ) + collast = numCols() - 1; + + bool currentInSelection = FALSE; + + QPtrListIterator<QTableSelection> it( selections ); + QTableSelection *s; + while ( ( s = it.current() ) != 0 ) { + ++it; + if ( s->isActive() && + curRow >= s->topRow() && + curRow <= s->bottomRow() && + curCol >= s->leftCol() && + curCol <= s->rightCol() ) { + currentInSelection = s->topRow() != curRow || s->bottomRow() != curRow || s->leftCol() != curCol || s->rightCol() != curCol; + break; + } + } + + // Go through the rows + for ( int r = rowfirst; r <= rowlast; ++r ) { + // get row position and height + int rowp = rowPos( r ); + int rowh = rowHeight( r ); + + // Go through the columns in row r + // if we know from where to where, go through [colfirst, collast], + // else go through all of them + for ( int c = colfirst; c <= collast; ++c ) { + // get position and width of column c + int colp, colw; + colp = columnPos( c ); + colw = columnWidth( c ); + int oldrp = rowp; + int oldrh = rowh; + + QTableItem *itm = item( r, c ); + if ( itm && + ( itm->colSpan() > 1 || itm->rowSpan() > 1 ) ) { + bool goon = r == itm->row() && c == itm->col() || + r == rowfirst && c == itm->col() || + r == itm->row() && c == colfirst; + if ( !goon ) + continue; + rowp = rowPos( itm->row() ); + rowh = 0; + int i; + for ( i = 0; i < itm->rowSpan(); ++i ) + rowh += rowHeight( i + itm->row() ); + colp = columnPos( itm->col() ); + colw = 0; + for ( i = 0; i < itm->colSpan(); ++i ) + colw += columnWidth( i + itm->col() ); + } + + // Translate painter and draw the cell + p->translate( colp, rowp ); + bool selected = isSelected( r, c ); + if ( focusStl != FollowStyle && selected && !currentInSelection && + r == curRow && c == curCol ) + selected = FALSE; + paintCell( p, r, c, QRect( colp, rowp, colw, rowh ), selected ); + p->translate( -colp, -rowp ); + + rowp = oldrp; + rowh = oldrh; + + QWidget *w = cellWidget( r, c ); + QRect cg( cellGeometry( r, c ) ); + if ( w && w->geometry() != QRect( contentsToViewport( cg.topLeft() ), cg.size() - QSize( 1, 1 ) ) ) { + moveChild( w, colp, rowp ); + w->resize( cg.size() - QSize( 1, 1 ) ); + } + } + } + d->lastVisCol = collast; + d->lastVisRow = rowlast; + + // draw indication of current cell + QRect focusRect = cellGeometry( curRow, curCol ); + p->translate( focusRect.x(), focusRect.y() ); + paintFocus( p, focusRect ); + p->translate( -focusRect.x(), -focusRect.y() ); + + // Paint empty rects + paintEmptyArea( p, cx, cy, cw, ch ); + + drawActiveSelection = TRUE; +} + +/*! + \reimp + + (Implemented to get rid of a compiler warning.) +*/ + +void QTable::drawContents( QPainter * ) +{ +} + +/*! + Returns the geometry of cell \a row, \a col in the cell's + coordinate system. This is a convenience function useful in + paintCell(). It is equivalent to QRect( QPoint(0,0), cellGeometry( + row, col).size() ); + + \sa cellGeometry() +*/ + +QRect QTable::cellRect( int row, int col ) const +{ + return QRect( QPoint(0,0), cellGeometry( row, col ).size() ); +} + +/*! + \overload + + Use the other paintCell() function. This function is only included + for backwards compatibilty. +*/ + +void QTable::paintCell( QPainter* p, int row, int col, + const QRect &cr, bool selected ) +{ + if ( cr.width() == 0 || cr.height() == 0 ) + return; +#if defined(Q_WS_WIN) + const QColorGroup &cg = ( !drawActiveSelection && style().styleHint( QStyle::SH_ItemView_ChangeHighlightOnFocus ) ? palette().inactive() : colorGroup() ); +#else + const QColorGroup &cg = colorGroup(); +#endif + + QTableItem *itm = item( row, col ); + QColorGroup cg2( cg ); + if ( itm && !itm->isEnabled() ) + cg2 = palette().disabled(); + + paintCell( p, row, col, cr, selected, cg2 ); +} + +/*! + Paints the cell at \a row, \a col on the painter \a p. The painter + has already been translated to the cell's origin. \a cr describes + the cell coordinates in the content coordinate system. + + If \a selected is TRUE the cell is highlighted. + + \a cg is the colorgroup which should be used to draw the cell + content. + + If you want to draw custom cell content, for example right-aligned + text, you must either reimplement paintCell(), or subclass + QTableItem and reimplement QTableItem::paint() to do the custom + drawing. + + If you're using a QTableItem subclass, for example, to store a + data structure, then reimplementing QTableItem::paint() may be the + best approach. For data you want to draw immediately, e.g. data + retrieved from a database, it is probably best to reimplement + paintCell(). Note that if you reimplement paintCell(), i.e. don't + use \l{QTableItem}s, you must reimplement other functions: see the + notes on large tables. + + Note that the painter is not clipped by default in order to get + maximum efficiency. If you want clipping, use code like this: + + \code + p->setClipRect( cellRect(row, col), QPainter::CoordPainter ); + //... your drawing code + p->setClipping( FALSE ); + \endcode +*/ + +void QTable::paintCell( QPainter *p, int row, int col, + const QRect &cr, bool selected, const QColorGroup &cg ) +{ + if ( focusStl == SpreadSheet && selected && + row == curRow && + col == curCol && ( hasFocus() || viewport()->hasFocus() ) ) + selected = FALSE; + + int w = cr.width(); + int h = cr.height(); + int x2 = w - 1; + int y2 = h - 1; + + + QTableItem *itm = item( row, col ); + if ( itm ) { + p->save(); + itm->paint( p, cg, cr, selected ); + p->restore(); + } else { + p->fillRect( 0, 0, w, h, selected ? cg.brush( QColorGroup::Highlight ) : cg.brush( QColorGroup::Base ) ); + } + + if ( sGrid ) { + // Draw our lines + QPen pen( p->pen() ); + int gridColor = style().styleHint( QStyle::SH_Table_GridLineColor, this ); + if (gridColor != -1) { + const QPalette &pal = palette(); + if (cg != colorGroup() + && cg != pal.disabled() + && cg != pal.inactive()) + p->setPen(cg.mid()); + else + p->setPen((QRgb)gridColor); + } else { + p->setPen(cg.mid()); + } + p->drawLine( x2, 0, x2, y2 ); + p->drawLine( 0, y2, x2, y2 ); + p->setPen( pen ); + } +} + +/*! + Draws the focus rectangle of the current cell (see currentRow(), + currentColumn()). + + The painter \a p is already translated to the cell's origin, while + \a cr specifies the cell's geometry in content coordinates. +*/ + +void QTable::paintFocus( QPainter *p, const QRect &cr ) +{ + if ( !hasFocus() && !viewport()->hasFocus() ) + return; + QRect focusRect( 0, 0, cr.width(), cr.height() ); + if ( focusStyle() == SpreadSheet ) { + p->setPen( QPen( black, 1 ) ); + p->setBrush( NoBrush ); + p->drawRect( focusRect.x(), focusRect.y(), focusRect.width() - 1, focusRect.height() - 1 ); + p->drawRect( focusRect.x() - 1, focusRect.y() - 1, focusRect.width() + 1, focusRect.height() + 1 ); + } else { + QColor c = isSelected( curRow, curCol, FALSE ) ? + colorGroup().highlight() : colorGroup().base(); + style().drawPrimitive( QStyle::PE_FocusRect, p, focusRect, colorGroup(), + ( isSelected( curRow, curCol, FALSE ) ? + QStyle::Style_FocusAtBorder : + QStyle::Style_Default ), + QStyleOption(c) ); + } +} + +/*! + This function fills the \a cw pixels wide and \a ch pixels high + rectangle starting at position \a cx, \a cy with the background + color using the painter \a p. + + paintEmptyArea() is invoked by drawContents() to erase or fill + unused areas. +*/ + +void QTable::paintEmptyArea( QPainter *p, int cx, int cy, int cw, int ch ) +{ + // Regions work with shorts, so avoid an overflow and adjust the + // table size to the visible size + QSize ts( tableSize() ); + ts.setWidth( QMIN( ts.width(), visibleWidth() ) ); + ts.setHeight( QMIN( ts.height(), visibleHeight() ) ); + + // Region of the rect we should draw, calculated in viewport + // coordinates, as a region can't handle bigger coordinates + contentsToViewport2( cx, cy, cx, cy ); + QRegion reg( QRect( cx, cy, cw, ch ) ); + + // Subtract the table from it + reg = reg.subtract( QRect( QPoint( 0, 0 ), ts ) ); + + // And draw the rectangles (transformed inc contents coordinates as needed) + QMemArray<QRect> r = reg.rects(); + for ( int i = 0; i < (int)r.count(); ++i ) + p->fillRect( QRect(viewportToContents2(r[i].topLeft()),r[i].size()), viewport()->backgroundBrush() ); +} + +/*! + Returns the QTableItem representing the contents of the cell at \a + row, \a col. + + If \a row or \a col are out of range or no content has been set + for this cell, item() returns 0. + + If you don't use \l{QTableItem}s you may need to reimplement this + function: see the notes on large tables. + + \sa setItem() +*/ + +QTableItem *QTable::item( int row, int col ) const +{ + if ( row < 0 || col < 0 || row > numRows() - 1 || + col > numCols() - 1 || row * col >= (int)contents.size() ) + return 0; + + return contents[ indexOf( row, col ) ]; // contents array lookup +} + +/*! + Inserts the table item \a item into the table at row \a row, + column \a col, and repaints the cell. If a table item already + exists in this cell it is deleted and replaced with \a item. The + table takes ownership of the table item. + + If you don't use \l{QTableItem}s you may need to reimplement this + function: see the notes on large tables. + + \sa item() takeItem() +*/ + +void QTable::setItem( int row, int col, QTableItem *item ) +{ + if ( !item ) + return; + + if ( (int)contents.size() != numRows() * numCols() ) + resizeData( numRows() * numCols() ); + + int orow = item->row(); + int ocol = item->col(); + clearCell( row, col ); + + contents.insert( indexOf( row, col ), item ); + item->setRow( row ); + item->setCol( col ); + item->t = this; + updateCell( row, col ); + if ( qt_update_cell_widget ) + item->updateEditor( orow, ocol ); + + if ( row == curRow && col == curCol && item->editType() == QTableItem::WhenCurrent ) { + if ( beginEdit( row, col, FALSE ) ) + setEditMode( Editing, row, col ); + } +} + +/*! + Removes the QTableItem at \a row, \a col. + + If you don't use \l{QTableItem}s you may need to reimplement this + function: see the notes on large tables. +*/ + +void QTable::clearCell( int row, int col ) +{ + if ( (int)contents.size() != numRows() * numCols() ) + resizeData( numRows() * numCols() ); + clearCellWidget( row, col ); + contents.setAutoDelete( TRUE ); + contents.remove( indexOf( row, col ) ); + contents.setAutoDelete( FALSE ); +} + +/*! + Sets the text in the cell at \a row, \a col to \a text. + + If the cell does not contain a table item a QTableItem is created + with an \link QTableItem::EditType EditType\endlink of \c OnTyping, + otherwise the existing table item's text (if any) is replaced with + \a text. + + \sa text() setPixmap() setItem() QTableItem::setText() +*/ + +void QTable::setText( int row, int col, const QString &text ) +{ + QTableItem *itm = item( row, col ); + if ( itm ) { + itm->setText( text ); + itm->updateEditor( row, col ); + updateCell( row, col ); + } else { + QTableItem *i = new QTableItem( this, QTableItem::OnTyping, + text, QPixmap() ); + setItem( row, col, i ); + } +} + +/*! + Sets the pixmap in the cell at \a row, \a col to \a pix. + + If the cell does not contain a table item a QTableItem is created + with an \link QTableItem::EditType EditType\endlink of \c OnTyping, + otherwise the existing table item's pixmap (if any) is replaced + with \a pix. + + Note that \l{QComboTableItem}s and \l{QCheckTableItem}s don't show + pixmaps. + + \sa pixmap() setText() setItem() QTableItem::setPixmap() +*/ + +void QTable::setPixmap( int row, int col, const QPixmap &pix ) +{ + QTableItem *itm = item( row, col ); + if ( itm ) { + itm->setPixmap( pix ); + updateCell( row, col ); + } else { + QTableItem *i = new QTableItem( this, QTableItem::OnTyping, + QString::null, pix ); + setItem( row, col, i ); + } +} + +/*! + Returns the text in the cell at \a row, \a col, or QString::null + if the relevant item does not exist or has no text. + + \sa setText() setPixmap() +*/ + +QString QTable::text( int row, int col ) const +{ + QTableItem *itm = item( row, col ); + if ( itm ) + return itm->text(); + return QString::null; +} + +/*! + Returns the pixmap set for the cell at \a row, \a col, or a + null-pixmap if the cell contains no pixmap. + + \sa setPixmap() +*/ + +QPixmap QTable::pixmap( int row, int col ) const +{ + QTableItem *itm = item( row, col ); + if ( itm ) + return itm->pixmap(); + return QPixmap(); +} + +/*! + Moves the focus to the cell at \a row, \a col. + + \sa currentRow() currentColumn() +*/ + +void QTable::setCurrentCell( int row, int col ) +{ + setCurrentCell( row, col, TRUE, TRUE ); +} + +// need to use a define, as leftMargin() is protected +#define VERTICALMARGIN \ +( QApplication::reverseLayout() ? \ + rightMargin() \ + : \ + leftMargin() \ +) + +/*! \internal */ + +void QTable::setCurrentCell( int row, int col, bool updateSelections, bool ensureVisible ) +{ + QTableItem *oldItem = item( curRow, curCol ); + + if ( row > numRows() - 1 ) + row = numRows() - 1; + if ( col > numCols() - 1 ) + col = numCols() - 1; + + if ( curRow == row && curCol == col ) + return; + + + QTableItem *itm = oldItem; + if ( itm && itm->editType() != QTableItem::Always && itm->editType() != QTableItem::Never ) + endEdit( itm->row(), itm->col(), TRUE, FALSE ); + int oldRow = curRow; + int oldCol = curCol; + curRow = row; + curCol = col; + repaintCell( oldRow, oldCol ); + repaintCell( curRow, curCol ); + if ( ensureVisible ) + ensureCellVisible( curRow, curCol ); + emit currentChanged( row, col ); + + if ( oldCol != curCol ) { + if ( !isColumnSelected( oldCol ) ) + topHeader->setSectionState( oldCol, QTableHeader::Normal ); + else if ( isRowSelection( selectionMode() ) ) + topHeader->setSectionState( oldCol, QTableHeader::Selected ); + topHeader->setSectionState( curCol, isColumnSelected( curCol, TRUE ) ? + QTableHeader::Selected : QTableHeader::Bold ); + } + + if ( oldRow != curRow ) { + if ( !isRowSelected( oldRow ) ) + leftHeader->setSectionState( oldRow, QTableHeader::Normal ); + leftHeader->setSectionState( curRow, isRowSelected( curRow, TRUE ) ? + QTableHeader::Selected : QTableHeader::Bold ); + } + + itm = item( curRow, curCol ); + + QPoint cellPos( columnPos( curCol ) + leftMargin() - contentsX(), + rowPos( curRow ) + topMargin() - contentsY() ); + setMicroFocusHint( cellPos.x(), cellPos.y(), columnWidth( curCol ), + rowHeight( curRow ), ( itm && itm->editType() != QTableItem::Never ) ); + + if ( cellWidget( oldRow, oldCol ) && + cellWidget( oldRow, oldCol )->hasFocus() ) + viewport()->setFocus(); + + if ( itm && itm->editType() == QTableItem::WhenCurrent ) { + if ( beginEdit( itm->row(), itm->col(), FALSE ) ) + setEditMode( Editing, itm->row(), itm->col() ); + } else if ( itm && itm->editType() == QTableItem::Always ) { + if ( cellWidget( itm->row(), itm->col() ) ) + cellWidget( itm->row(), itm->col() )->setFocus(); + } + + if ( updateSelections && isRowSelection( selectionMode() ) && + !isSelected( curRow, curCol, FALSE ) ) { + if ( selectionMode() == QTable::SingleRow ) + clearSelection(); + currentSel = new QTableSelection(); + selections.append( currentSel ); + currentSel->init( curRow, 0 ); + currentSel->expandTo( curRow, numCols() - 1 ); + repaintSelections( 0, currentSel ); + } +} + +/*! + Scrolls the table until the cell at \a row, \a col becomes + visible. +*/ + +void QTable::ensureCellVisible( int row, int col ) +{ + if ( !isUpdatesEnabled() || !viewport()->isUpdatesEnabled() ) + return; + int cw = columnWidth( col ); + int rh = rowHeight( row ); + if ( cw < visibleWidth() ) + ensureVisible( columnPos( col ) + cw / 2, rowPos( row ) + rh / 2, cw / 2, rh / 2 ); + else + ensureVisible( columnPos( col ), rowPos( row ) + rh / 2, 0, rh / 2 ); +} + +/*! + Returns TRUE if the cell at \a row, \a col is selected; otherwise + returns FALSE. + + \sa isRowSelected() isColumnSelected() +*/ + +bool QTable::isSelected( int row, int col ) const +{ + return isSelected( row, col, TRUE ); +} + +/*! \internal */ + +bool QTable::isSelected( int row, int col, bool includeCurrent ) const +{ + QPtrListIterator<QTableSelection> it( selections ); + QTableSelection *s; + while ( ( s = it.current() ) != 0 ) { + ++it; + if ( s->isActive() && + row >= s->topRow() && + row <= s->bottomRow() && + col >= s->leftCol() && + col <= s->rightCol() ) + return TRUE; + if ( includeCurrent && row == currentRow() && col == currentColumn() ) + return TRUE; + } + return FALSE; +} + +/*! + Returns TRUE if row \a row is selected; otherwise returns FALSE. + + If \a full is FALSE (the default), 'row is selected' means that at + least one cell in the row is selected. If \a full is TRUE, then 'row + is selected' means every cell in the row is selected. + + \sa isColumnSelected() isSelected() +*/ + +bool QTable::isRowSelected( int row, bool full ) const +{ + if ( !full ) { + QPtrListIterator<QTableSelection> it( selections ); + QTableSelection *s; + while ( ( s = it.current() ) != 0 ) { + ++it; + if ( s->isActive() && + row >= s->topRow() && + row <= s->bottomRow() ) + return TRUE; + if ( row == currentRow() ) + return TRUE; + } + } else { + QPtrListIterator<QTableSelection> it( selections ); + QTableSelection *s; + while ( ( s = it.current() ) != 0 ) { + ++it; + if ( s->isActive() && + row >= s->topRow() && + row <= s->bottomRow() && + s->leftCol() == 0 && + s->rightCol() == numCols() - 1 ) + return TRUE; + } + } + return FALSE; +} + +/*! + Returns TRUE if column \a col is selected; otherwise returns FALSE. + + If \a full is FALSE (the default), 'column is selected' means that + at least one cell in the column is selected. If \a full is TRUE, + then 'column is selected' means every cell in the column is + selected. + + \sa isRowSelected() isSelected() +*/ + +bool QTable::isColumnSelected( int col, bool full ) const +{ + if ( !full ) { + QPtrListIterator<QTableSelection> it( selections ); + QTableSelection *s; + while ( ( s = it.current() ) != 0 ) { + ++it; + if ( s->isActive() && + col >= s->leftCol() && + col <= s->rightCol() ) + return TRUE; + if ( col == currentColumn() ) + return TRUE; + } + } else { + QPtrListIterator<QTableSelection> it( selections ); + QTableSelection *s; + while ( ( s = it.current() ) != 0 ) { + ++it; + if ( s->isActive() && + col >= s->leftCol() && + col <= s->rightCol() && + s->topRow() == 0 && + s->bottomRow() == numRows() - 1 ) + return TRUE; + } + } + return FALSE; +} + +/*! + \property QTable::numSelections + \brief The number of selections. + + \sa currentSelection() +*/ + +int QTable::numSelections() const +{ + return selections.count(); +} + +/*! + Returns selection number \a num, or an inactive QTableSelection if \a + num is out of range (see QTableSelection::isActive()). +*/ + +QTableSelection QTable::selection( int num ) const +{ + if ( num < 0 || num >= (int)selections.count() ) + return QTableSelection(); + + QTableSelection *s = ( (QTable*)this )->selections.at( num ); + return *s; +} + +/*! + Adds a selection described by \a s to the table and returns its + number or -1 if the selection is invalid. + + Remember to call QTableSelection::init() and + QTableSelection::expandTo() to make the selection valid (see also + QTableSelection::isActive(), or use the + QTableSelection(int,int,int,int) constructor). + + \sa numSelections() removeSelection() clearSelection() +*/ + +int QTable::addSelection( const QTableSelection &s ) +{ + if ( !s.isActive() ) + return -1; + + const int maxr = numRows()-1; + const int maxc = numCols()-1; + QTableSelection *sel = new QTableSelection( QMIN(s.anchorRow(), maxr), QMIN(s.anchorCol(), maxc), + QMIN(s.bottomRow(), maxr), QMIN(s.rightCol(), maxc) ); + + selections.append( sel ); + + repaintSelections( 0, sel, TRUE, TRUE ); + + emit selectionChanged(); + + return selections.count() - 1; +} + +/*! + If the table has a selection, \a s, this selection is removed from + the table. + + \sa addSelection() numSelections() +*/ + +void QTable::removeSelection( const QTableSelection &s ) +{ + selections.setAutoDelete( FALSE ); + for ( QTableSelection *sel = selections.first(); sel; sel = selections.next() ) { + if ( s == *sel ) { + selections.removeRef( sel ); + repaintSelections( sel, 0, TRUE, TRUE ); + if ( sel == currentSel ) + currentSel = 0; + delete sel; + } + } + selections.setAutoDelete( TRUE ); + emit selectionChanged(); +} + +/*! + \overload + + Removes selection number \a num from the table. + + \sa numSelections() addSelection() clearSelection() +*/ + +void QTable::removeSelection( int num ) +{ + if ( num < 0 || num >= (int)selections.count() ) + return; + + QTableSelection *s = selections.at( num ); + if ( s == currentSel ) + currentSel = 0; + selections.removeRef( s ); + repaintContents( FALSE ); +} + +/*! + Returns the number of the current selection or -1 if there is no + current selection. + + \sa numSelections() +*/ + +int QTable::currentSelection() const +{ + if ( !currentSel ) + return -1; + return ( (QTable*)this )->selections.findRef( currentSel ); +} + +/*! Selects the range starting at \a start_row and \a start_col and + ending at \a end_row and \a end_col. + + \sa QTableSelection +*/ + +void QTable::selectCells( int start_row, int start_col, int end_row, int end_col ) +{ + const int maxr = numRows()-1; + const int maxc = numCols()-1; + + start_row = QMIN( maxr, QMAX( 0, start_row ) ); + start_col = QMIN( maxc, QMAX( 0, start_col ) ); + end_row = QMIN( maxr, end_row ); + end_col = QMIN( maxc, end_col ); + QTableSelection sel( start_row, start_col, end_row, end_col ); + addSelection( sel ); +} + +/*! Selects the row \a row. + + \sa QTableSelection +*/ + +// ### Make this virtual in 4.0 and remove hack for QDataTable +void QTable::selectRow( int row ) +{ + row = QMIN(numRows()-1, row); + if ( row < 0 ) + return; + bool isDataTable = FALSE; +#ifndef QT_NO_SQL + isDataTable = ::qt_cast<QDataTable*>(this) != 0; +#endif + if ( isDataTable || selectionMode() == SingleRow ) { + setCurrentCell( row, currentColumn() ); + } else { + QTableSelection sel( row, 0, row, numCols() - 1 ); + addSelection( sel ); + } +} + +/*! Selects the column \a col. + + \sa QTableSelection +*/ + +// ### Make this virtual in 4.0 +void QTable::selectColumn( int col ) +{ + col = QMIN(numCols()-1, col); + QTableSelection sel( 0, col, numRows() - 1, col ); + addSelection( sel ); +} + +/*! \reimp +*/ +void QTable::contentsMousePressEvent( QMouseEvent* e ) +{ + contentsMousePressEventEx( e ); +} + +void QTable::contentsMousePressEventEx( QMouseEvent* e ) +{ + shouldClearSelection = FALSE; + if ( isEditing() ) { + if ( !cellGeometry( editRow, editCol ).contains( e->pos() ) ) { + endEdit( editRow, editCol, TRUE, edMode != Editing ); + } else { + e->ignore(); + return; + } + } + + d->redirectMouseEvent = FALSE; + + int tmpRow = rowAt( e->pos().y() ); + int tmpCol = columnAt( e->pos().x() ); + pressedRow = tmpRow; + pressedCol = tmpCol; + fixRow( tmpRow, e->pos().y() ); + fixCol( tmpCol, e->pos().x() ); + startDragCol = -1; + startDragRow = -1; + + if ( isSelected( tmpRow, tmpCol ) ) { + startDragCol = tmpCol; + startDragRow = tmpRow; + dragStartPos = e->pos(); + } + + QTableItem *itm = item( pressedRow, pressedCol ); + if ( itm && !itm->isEnabled() ) { + emit pressed( tmpRow, tmpCol, e->button(), e->pos() ); + return; + } + + if ( ( e->state() & ShiftButton ) == ShiftButton ) { + int oldRow = curRow; + int oldCol = curCol; + setCurrentCell( tmpRow, tmpCol, selMode == SingleRow, TRUE ); + if ( selMode != NoSelection && selMode != SingleRow ) { + if ( !currentSel ) { + currentSel = new QTableSelection(); + selections.append( currentSel ); + if ( !isRowSelection( selectionMode() ) ) + currentSel->init( oldRow, oldCol ); + else + currentSel->init( oldRow, 0 ); + } + QTableSelection oldSelection = *currentSel; + if ( !isRowSelection( selectionMode() ) ) + currentSel->expandTo( tmpRow, tmpCol ); + else + currentSel->expandTo( tmpRow, numCols() - 1 ); + repaintSelections( &oldSelection, currentSel ); + emit selectionChanged(); + } + } else if ( ( e->state() & ControlButton ) == ControlButton ) { + setCurrentCell( tmpRow, tmpCol, FALSE, TRUE ); + if ( selMode != NoSelection ) { + if ( selMode == Single || selMode == SingleRow && !isSelected( tmpRow, tmpCol, FALSE ) ) + clearSelection(); + if ( !(selMode == SingleRow && isSelected( tmpRow, tmpCol, FALSE )) ) { + currentSel = new QTableSelection(); + selections.append( currentSel ); + if ( !isRowSelection( selectionMode() ) ) { + currentSel->init( tmpRow, tmpCol ); + currentSel->expandTo( tmpRow, tmpCol ); + } else { + currentSel->init( tmpRow, 0 ); + currentSel->expandTo( tmpRow, numCols() - 1 ); + repaintSelections( 0, currentSel ); + } + emit selectionChanged(); + } + } + } else { + setCurrentCell( tmpRow, tmpCol, FALSE, TRUE ); + QTableItem *itm = item( tmpRow, tmpCol ); + if ( itm && itm->editType() == QTableItem::WhenCurrent ) { + QWidget *w = cellWidget( tmpRow, tmpCol ); + if ( ::qt_cast<QComboBox*>(w) || ::qt_cast<QButton*>(w) ) { + QMouseEvent ev( e->type(), w->mapFromGlobal( e->globalPos() ), + e->globalPos(), e->button(), e->state() ); + QApplication::sendPostedEvents( w, 0 ); + QApplication::sendEvent( w, &ev ); + d->redirectMouseEvent = TRUE; + } + } + if ( isSelected( tmpRow, tmpCol, FALSE ) ) { + shouldClearSelection = TRUE; + } else { + bool b = signalsBlocked(); + if ( selMode != NoSelection ) + blockSignals( TRUE ); + clearSelection(); + blockSignals( b ); + if ( selMode != NoSelection ) { + currentSel = new QTableSelection(); + selections.append( currentSel ); + if ( !isRowSelection( selectionMode() ) ) { + currentSel->init( tmpRow, tmpCol ); + currentSel->expandTo( tmpRow, tmpCol ); + } else { + currentSel->init( tmpRow, 0 ); + currentSel->expandTo( tmpRow, numCols() - 1 ); + repaintSelections( 0, currentSel ); + } + emit selectionChanged(); + } + } + } + + emit pressed( tmpRow, tmpCol, e->button(), e->pos() ); +} + +/*! \reimp +*/ + +void QTable::contentsMouseDoubleClickEvent( QMouseEvent *e ) +{ + if ( e->button() != LeftButton ) + return; + if ( !isRowSelection( selectionMode() ) ) + clearSelection(); + int tmpRow = rowAt( e->pos().y() ); + int tmpCol = columnAt( e->pos().x() ); + QTableItem *itm = item( tmpRow, tmpCol ); + if ( itm && !itm->isEnabled() ) + return; + if ( tmpRow != -1 && tmpCol != -1 ) { + if ( beginEdit( tmpRow, tmpCol, FALSE ) ) + setEditMode( Editing, tmpRow, tmpCol ); + } + + emit doubleClicked( tmpRow, tmpCol, e->button(), e->pos() ); +} + +/*! + Sets the current edit mode to \a mode, the current edit row to \a + row and the current edit column to \a col. + + \sa EditMode +*/ + +void QTable::setEditMode( EditMode mode, int row, int col ) +{ + edMode = mode; + editRow = row; + editCol = col; +} + + +/*! \reimp +*/ + +void QTable::contentsMouseMoveEvent( QMouseEvent *e ) +{ + if ( (e->state() & MouseButtonMask) == NoButton ) + return; + int tmpRow = rowAt( e->pos().y() ); + int tmpCol = columnAt( e->pos().x() ); + fixRow( tmpRow, e->pos().y() ); + fixCol( tmpCol, e->pos().x() ); + +#ifndef QT_NO_DRAGANDDROP + if ( dragEnabled() && startDragRow != -1 && startDragCol != -1 ) { + if (QPoint(dragStartPos - e->pos()).manhattanLength() > QApplication::startDragDistance()) + startDrag(); + return; + } +#endif + if ( selectionMode() == MultiRow && ( e->state() & ControlButton ) == ControlButton ) + shouldClearSelection = FALSE; + + if ( shouldClearSelection ) { + clearSelection(); + if ( selMode != NoSelection ) { + currentSel = new QTableSelection(); + selections.append( currentSel ); + if ( !isRowSelection( selectionMode() ) ) + currentSel->init( tmpRow, tmpCol ); + else + currentSel->init( tmpRow, 0 ); + emit selectionChanged(); + } + shouldClearSelection = FALSE; + } + + QPoint pos = mapFromGlobal( e->globalPos() ); + pos -= QPoint( leftHeader->width(), topHeader->height() ); + autoScrollTimer->stop(); + doAutoScroll(); + if ( pos.x() < 0 || pos.x() > visibleWidth() || pos.y() < 0 || pos.y() > visibleHeight() ) + autoScrollTimer->start( 100, TRUE ); +} + +/*! \internal + */ + +void QTable::doValueChanged() +{ + emit valueChanged( editRow, editCol ); +} + +/*! \internal +*/ + +void QTable::doAutoScroll() +{ + QPoint pos = QCursor::pos(); + pos = mapFromGlobal( pos ); + pos -= QPoint( leftHeader->width(), topHeader->height() ); + + int tmpRow = curRow; + int tmpCol = curCol; + if ( pos.y() < 0 ) + tmpRow--; + else if ( pos.y() > visibleHeight() ) + tmpRow++; + if ( pos.x() < 0 ) + tmpCol--; + else if ( pos.x() > visibleWidth() ) + tmpCol++; + + pos += QPoint( contentsX(), contentsY() ); + if ( tmpRow == curRow ) + tmpRow = rowAt( pos.y() ); + if ( tmpCol == curCol ) + tmpCol = columnAt( pos.x() ); + pos -= QPoint( contentsX(), contentsY() ); + + fixRow( tmpRow, pos.y() ); + fixCol( tmpCol, pos.x() ); + + if ( tmpRow < 0 || tmpRow > numRows() - 1 ) + tmpRow = currentRow(); + if ( tmpCol < 0 || tmpCol > numCols() - 1 ) + tmpCol = currentColumn(); + + ensureCellVisible( tmpRow, tmpCol ); + + if ( currentSel && selMode != NoSelection ) { + QTableSelection oldSelection = *currentSel; + bool useOld = TRUE; + if ( selMode != SingleRow ) { + if ( !isRowSelection( selectionMode() ) ) { + currentSel->expandTo( tmpRow, tmpCol ); + } else { + currentSel->expandTo( tmpRow, numCols() - 1 ); + } + } else { + bool currentInSelection = tmpRow == curRow && isSelected( tmpRow, tmpCol ); + if ( !currentInSelection ) { + useOld = FALSE; + clearSelection(); + currentSel = new QTableSelection(); + selections.append( currentSel ); + currentSel->init( tmpRow, 0 ); + currentSel->expandTo( tmpRow, numCols() - 1 ); + repaintSelections( 0, currentSel ); + } else { + currentSel->expandTo( tmpRow, numCols() - 1 ); + } + } + setCurrentCell( tmpRow, tmpCol, FALSE, TRUE ); + repaintSelections( useOld ? &oldSelection : 0, currentSel ); + if ( currentSel && oldSelection != *currentSel ) + emit selectionChanged(); + } else { + setCurrentCell( tmpRow, tmpCol, FALSE, TRUE ); + } + + if ( pos.x() < 0 || pos.x() > visibleWidth() || pos.y() < 0 || pos.y() > visibleHeight() ) + autoScrollTimer->start( 100, TRUE ); +} + +/*! \reimp +*/ + +void QTable::contentsMouseReleaseEvent( QMouseEvent *e ) +{ + if ( pressedRow == curRow && pressedCol == curCol ) + emit clicked( curRow, curCol, e->button(), e->pos() ); + + if ( e->button() != LeftButton ) + return; + if ( shouldClearSelection ) { + int tmpRow = rowAt( e->pos().y() ); + int tmpCol = columnAt( e->pos().x() ); + fixRow( tmpRow, e->pos().y() ); + fixCol( tmpCol, e->pos().x() ); + clearSelection(); + if ( selMode != NoSelection ) { + currentSel = new QTableSelection(); + selections.append( currentSel ); + if ( !isRowSelection( selectionMode() ) ) { + currentSel->init( tmpRow, tmpCol ); + } else { + currentSel->init( tmpRow, 0 ); + currentSel->expandTo( tmpRow, numCols() - 1 ); + repaintSelections( 0, currentSel ); + } + emit selectionChanged(); + } + shouldClearSelection = FALSE; + } + autoScrollTimer->stop(); + + if ( d->redirectMouseEvent && pressedRow == curRow && pressedCol == curCol && + item( pressedRow, pressedCol ) && item( pressedRow, pressedCol )->editType() == + QTableItem::WhenCurrent ) { + QWidget *w = cellWidget( pressedRow, pressedCol ); + if ( w ) { + QMouseEvent ev( e->type(), w->mapFromGlobal( e->globalPos() ), + e->globalPos(), e->button(), e->state() ); + QApplication::sendPostedEvents( w, 0 ); + QApplication::sendEvent( w, &ev ); + } + } +} + +/*! + \reimp +*/ + +void QTable::contentsContextMenuEvent( QContextMenuEvent *e ) +{ + if ( !receivers( SIGNAL(contextMenuRequested(int,int,const QPoint&)) ) ) { + e->ignore(); + return; + } + if ( e->reason() == QContextMenuEvent::Keyboard ) { + QRect r = cellGeometry( curRow, curCol ); + emit contextMenuRequested( curRow, curCol, viewport()->mapToGlobal( contentsToViewport( r.center() ) ) ); + } else { + int tmpRow = rowAt( e->pos().y() ); + int tmpCol = columnAt( e->pos().x() ); + emit contextMenuRequested( tmpRow, tmpCol, e->globalPos() ); + } +} + + +/*! \reimp +*/ + +bool QTable::eventFilter( QObject *o, QEvent *e ) +{ + QWidget *editorWidget = cellWidget( editRow, editCol ); + switch ( e->type() ) { + case QEvent::KeyPress: { + QTableItem *itm = item( curRow, curCol ); + int editRow = this->editRow; + int editCol = this->editCol; + if ((d->hasRowSpan || d->hasColSpan) && !editorWidget) { + if (QTableItem *eitm = item(editRow, editCol)) { + editRow = eitm->row(); + editCol = eitm->col(); + editorWidget = cellWidget(editRow, editCol); + } + } + if ( isEditing() && editorWidget && o == editorWidget ) { + itm = item( editRow, editCol ); + QKeyEvent *ke = (QKeyEvent*)e; + if ( ke->key() == Key_Escape ) { + if ( !itm || itm->editType() == QTableItem::OnTyping ) + endEdit( editRow, editCol, FALSE, edMode != Editing ); + return TRUE; + } + + if ( ( ke->state() == NoButton || ke->state() == Keypad ) + && ( ke->key() == Key_Return || ke->key() == Key_Enter ) ) { + if ( !itm || itm->editType() == QTableItem::OnTyping ) + endEdit( editRow, editCol, TRUE, edMode != Editing ); + activateNextCell(); + return TRUE; + } + + if ( ke->key() == Key_Tab || ke->key() == Key_BackTab ) { + if ( ke->state() & Qt::ControlButton ) + return FALSE; + if ( !itm || itm->editType() == QTableItem::OnTyping ) + endEdit( editRow, editCol, TRUE, edMode != Editing ); + if ( (ke->key() == Key_Tab) && !(ke->state() & ShiftButton) ) { + if ( currentColumn() >= numCols() - 1 ) + return TRUE; + int cc = QMIN( numCols() - 1, currentColumn() + 1 ); + while ( cc < numCols() ) { + QTableItem *i = item( currentRow(), cc ); + if ( !d->hiddenCols.find( cc ) && !isColumnReadOnly( cc ) && (!i || i->isEnabled()) ) + break; + ++cc; + } + setCurrentCell( currentRow(), cc ); + } else { // Key_BackTab + if ( currentColumn() == 0 ) + return TRUE; + int cc = QMAX( 0, currentColumn() - 1 ); + while ( cc >= 0 ) { + QTableItem *i = item( currentRow(), cc ); + if ( !d->hiddenCols.find( cc ) && !isColumnReadOnly( cc ) && (!i || i->isEnabled()) ) + break; + --cc; + } + setCurrentCell( currentRow(), cc ); + } + itm = item( curRow, curCol ); + if ( beginEdit( curRow, curCol, FALSE ) ) + setEditMode( Editing, curRow, curCol ); + return TRUE; + } + + if ( ( edMode == Replacing || + itm && itm->editType() == QTableItem::WhenCurrent ) && + ( ke->key() == Key_Up || ke->key() == Key_Prior || + ke->key() == Key_Home || ke->key() == Key_Down || + ke->key() == Key_Next || ke->key() == Key_End || + ke->key() == Key_Left || ke->key() == Key_Right ) ) { + if ( !itm || itm->editType() == QTableItem::OnTyping ) { + endEdit( editRow, editCol, TRUE, edMode != Editing ); + } + keyPressEvent( ke ); + return TRUE; + } + } else { + QObjectList *l = viewport()->queryList( "QWidget" ); + if ( l && l->find( o ) != -1 ) { + delete l; + QKeyEvent *ke = (QKeyEvent*)e; + if ( ( ke->state() & ControlButton ) == ControlButton || + ( ke->key() != Key_Left && ke->key() != Key_Right && + ke->key() != Key_Up && ke->key() != Key_Down && + ke->key() != Key_Prior && ke->key() != Key_Next && + ke->key() != Key_Home && ke->key() != Key_End ) ) + return FALSE; + keyPressEvent( (QKeyEvent*)e ); + return TRUE; + } + delete l; + } + + } break; + case QEvent::FocusOut: + if ( isEditing() && editorWidget && o == editorWidget && ( (QFocusEvent*)e )->reason() != QFocusEvent::Popup ) { + QTableItem *itm = item( editRow, editCol ); + if ( !itm || itm->editType() == QTableItem::OnTyping ) { + endEdit( editRow, editCol, TRUE, edMode != Editing ); + return TRUE; + } + } + break; +#ifndef QT_NO_WHEELEVENT + case QEvent::Wheel: + if ( o == this || o == viewport() ) { + QWheelEvent* we = (QWheelEvent*)e; + scrollBy( 0, -we->delta() ); + we->accept(); + return TRUE; + } +#endif + default: + break; + } + + return QScrollView::eventFilter( o, e ); +} + +void QTable::fixCell( int &row, int &col, int key ) +{ + if ( rowHeight( row ) > 0 && columnWidth( col ) > 0 ) + return; + if ( rowHeight( row ) <= 0 ) { + if ( key == Key_Down || + key == Key_Next || + key == Key_End ) { + while ( row < numRows() && rowHeight( row ) <= 0 ) + row++; + if ( rowHeight( row ) <= 0 ) + row = curRow; + } else if ( key == Key_Up || + key == Key_Prior || + key == Key_Home ) + while ( row >= 0 && rowHeight( row ) <= 0 ) + row--; + if ( rowHeight( row ) <= 0 ) + row = curRow; + } else if ( columnWidth( col ) <= 0 ) { + if ( key == Key_Left ) { + while ( col >= 0 && columnWidth( col ) <= 0 ) + col--; + if ( columnWidth( col ) <= 0 ) + col = curCol; + } else if ( key == Key_Right ) { + while ( col < numCols() && columnWidth( col ) <= 0 ) + col++; + if ( columnWidth( col ) <= 0 ) + col = curCol; + } + } +} + +/*! \reimp +*/ + +void QTable::keyPressEvent( QKeyEvent* e ) +{ + if ( isEditing() && item( editRow, editCol ) && + item( editRow, editCol )->editType() == QTableItem::OnTyping ) + return; + + int tmpRow = curRow; + int tmpCol = curCol; + int oldRow = tmpRow; + int oldCol = tmpCol; + + bool navigationKey = FALSE; + int r; + switch ( e->key() ) { + case Key_Left: + tmpCol = QMAX( 0, tmpCol - 1 ); + navigationKey = TRUE; + break; + case Key_Right: + tmpCol = QMIN( numCols() - 1, tmpCol + 1 ); + navigationKey = TRUE; + break; + case Key_Up: + tmpRow = QMAX( 0, tmpRow - 1 ); + navigationKey = TRUE; + break; + case Key_Down: + tmpRow = QMIN( numRows() - 1, tmpRow + 1 ); + navigationKey = TRUE; + break; + case Key_Prior: + r = QMAX( 0, rowAt( rowPos( tmpRow ) - visibleHeight() ) ); + if ( r < tmpRow || tmpRow < 0 ) + tmpRow = r; + navigationKey = TRUE; + break; + case Key_Next: + r = QMIN( numRows() - 1, rowAt( rowPos( tmpRow ) + visibleHeight() ) ); + if ( r > tmpRow ) + tmpRow = r; + else + tmpRow = numRows() - 1; + navigationKey = TRUE; + break; + case Key_Home: + tmpRow = 0; + navigationKey = TRUE; + break; + case Key_End: + tmpRow = numRows() - 1; + navigationKey = TRUE; + break; + case Key_F2: + if ( beginEdit( tmpRow, tmpCol, FALSE ) ) + setEditMode( Editing, tmpRow, tmpCol ); + break; + case Key_Enter: case Key_Return: + activateNextCell(); + return; + case Key_Tab: case Key_BackTab: + if ( (e->key() == Key_Tab) && !(e->state() & ShiftButton) ) { + if ( currentColumn() >= numCols() - 1 ) + return; + int cc = QMIN( numCols() - 1, currentColumn() + 1 ); + while ( cc < numCols() ) { + QTableItem *i = item( currentRow(), cc ); + if ( !d->hiddenCols.find( cc ) && !isColumnReadOnly( cc ) && (!i || i->isEnabled()) ) + break; + ++cc; + } + setCurrentCell( currentRow(), cc ); + } else { // Key_BackTab + if ( currentColumn() == 0 ) + return; + int cc = QMAX( 0, currentColumn() - 1 ); + while ( cc >= 0 ) { + QTableItem *i = item( currentRow(), cc ); + if ( !d->hiddenCols.find( cc ) && !isColumnReadOnly( cc ) && (!i || i->isEnabled()) ) + break; + --cc; + } + setCurrentCell( currentRow(), cc ); + } + return; + case Key_Escape: + e->ignore(); + return; + default: // ... or start in-place editing + if ( e->text()[ 0 ].isPrint() ) { + QTableItem *itm = item( tmpRow, tmpCol ); + if ( !itm || itm->editType() == QTableItem::OnTyping ) { + QWidget *w = beginEdit( tmpRow, tmpCol, + itm ? itm->isReplaceable() : TRUE ); + if ( w ) { + setEditMode( ( !itm || itm && itm->isReplaceable() + ? Replacing : Editing ), tmpRow, tmpCol ); + QApplication::sendEvent( w, e ); + return; + } + } + } + e->ignore(); + return; + } + + if ( navigationKey ) { + fixCell( tmpRow, tmpCol, e->key() ); + if ( ( e->state() & ShiftButton ) == ShiftButton && + selMode != NoSelection && selMode != SingleRow ) { + bool justCreated = FALSE; + setCurrentCell( tmpRow, tmpCol, FALSE, TRUE ); + if ( !currentSel ) { + justCreated = TRUE; + currentSel = new QTableSelection(); + selections.append( currentSel ); + if ( !isRowSelection( selectionMode() ) ) + currentSel->init( oldRow, oldCol ); + else + currentSel->init( oldRow < 0 ? 0 : oldRow, 0 ); + } + QTableSelection oldSelection = *currentSel; + if ( !isRowSelection( selectionMode() ) ) + currentSel->expandTo( tmpRow, tmpCol ); + else + currentSel->expandTo( tmpRow, numCols() - 1 ); + repaintSelections( justCreated ? 0 : &oldSelection, currentSel ); + emit selectionChanged(); + } else { + setCurrentCell( tmpRow, tmpCol, FALSE, TRUE ); + if ( !isRowSelection( selectionMode() ) ) { + clearSelection(); + } else { + bool currentInSelection = tmpRow == oldRow && isSelected( tmpRow, tmpCol, FALSE ); + if ( !currentInSelection ) { + bool hasOldSel = FALSE; + QTableSelection oldSelection; + if ( selectionMode() == MultiRow ) { + bool b = signalsBlocked(); + blockSignals( TRUE ); + clearSelection(); + blockSignals( b ); + } else { + if ( currentSel ) { + oldSelection = *currentSel; + hasOldSel = TRUE; + selections.removeRef( currentSel ); + leftHeader->setSectionState( oldSelection.topRow(), QTableHeader::Normal ); + } + } + currentSel = new QTableSelection(); + selections.append( currentSel ); + currentSel->init( tmpRow, 0 ); + currentSel->expandTo( tmpRow, numCols() - 1 ); + repaintSelections( hasOldSel ? &oldSelection : 0, currentSel, !hasOldSel ); + emit selectionChanged(); + } + } + } + } else { + setCurrentCell( tmpRow, tmpCol, FALSE, TRUE ); + } +} + +/*! \reimp +*/ + +void QTable::focusInEvent( QFocusEvent* ) +{ + d->inMenuMode = FALSE; + QWidget *editorWidget = cellWidget( editRow, editCol ); + updateCell( curRow, curCol ); + if ( style().styleHint( QStyle::SH_ItemView_ChangeHighlightOnFocus, this ) ) + repaintSelections(); + if ( isEditing() && editorWidget ) + editorWidget->setFocus(); + + QPoint cellPos( columnPos( curCol ) + leftMargin() - contentsX(), rowPos( curRow ) + topMargin() - contentsY() ); + QTableItem *itm = item( curRow, curCol ); + setMicroFocusHint( cellPos.x(), cellPos.y(), columnWidth( curCol ), rowHeight( curRow ), ( itm && itm->editType() != QTableItem::Never ) ); +} + + +/*! \reimp +*/ + +void QTable::focusOutEvent( QFocusEvent* ) +{ + updateCell( curRow, curCol ); + if (style().styleHint( QStyle::SH_ItemView_ChangeHighlightOnFocus, this )) { + d->inMenuMode = + QFocusEvent::reason() == QFocusEvent::Popup || + (qApp->focusWidget() && qApp->focusWidget()->inherits("QMenuBar")); + if ( !d->inMenuMode ) + repaintSelections(); + } +} + +/*! \reimp +*/ + +QSize QTable::sizeHint() const +{ + if ( cachedSizeHint().isValid() ) + return cachedSizeHint(); + + constPolish(); + + QSize s = tableSize(); + QSize sh; + if ( s.width() < 500 && s.height() < 500 ) { + sh = QSize( tableSize().width() + VERTICALMARGIN + 5, + tableSize().height() + topMargin() + 5 ); + } else { + sh = QScrollView::sizeHint(); + if ( !topHeader->isHidden() ) + sh.setHeight( sh.height() + topHeader->height() ); + if ( !leftHeader->isHidden() ) + sh.setWidth( sh.width() + leftHeader->width() ); + } + setCachedSizeHint( sh ); + return sh; +} + +/*! \reimp +*/ + +void QTable::viewportResizeEvent( QResizeEvent *e ) +{ + QScrollView::viewportResizeEvent( e ); + updateGeometries(); +} + +/*! \reimp +*/ + +void QTable::showEvent( QShowEvent *e ) +{ + QScrollView::showEvent( e ); + QRect r( cellGeometry( numRows() - 1, numCols() - 1 ) ); + resizeContents( r.right() + 1, r.bottom() + 1 ); + updateGeometries(); +} + +/*! \reimp +*/ + +void QTable::paintEvent( QPaintEvent *e ) +{ + QRect topLeftCorner = QStyle::visualRect( QRect(frameWidth(), frameWidth(), VERTICALMARGIN, topMargin() ), rect() ); + erase( topLeftCorner ); // erase instead of widget on top + QScrollView::paintEvent( e ); + +#ifdef Q_OS_TEMP + QPainter p( this ); + p.drawLine( topLeftCorner.bottomLeft(), topLeftCorner.bottomRight() ); + p.drawLine( topLeftCorner.bottomRight(), topLeftCorner.topRight() ); +#endif +} + +static bool inUpdateCell = FALSE; + +/*! + Repaints the cell at \a row, \a col. +*/ + +void QTable::updateCell( int row, int col ) +{ + if ( inUpdateCell || row < 0 || col < 0 ) + return; + inUpdateCell = TRUE; + QRect cg = cellGeometry( row, col ); + QRect r( contentsToViewport( QPoint( cg.x() - 2, cg.y() - 2 ) ), + QSize( cg.width() + 4, cg.height() + 4 ) ); + if (viewport()->rect().intersects(r)) + QApplication::postEvent( viewport(), new QPaintEvent( r, FALSE ) ); + inUpdateCell = FALSE; +} + +void QTable::repaintCell( int row, int col ) +{ + if ( row == -1 || col == -1 ) + return; + QRect cg = cellGeometry( row, col ); + QRect r( QPoint( cg.x() - 2, cg.y() - 2 ), + QSize( cg.width() + 4, cg.height() + 4 ) ); + + QRect v = viewport()->rect(); + v.moveBy(contentsX(), contentsY()); + if (v.intersects(r)) + repaintContents( r, FALSE ); +} + +void QTable::contentsToViewport2( int x, int y, int& vx, int& vy ) +{ + const QPoint v = contentsToViewport2( QPoint( x, y ) ); + vx = v.x(); + vy = v.y(); +} + +QPoint QTable::contentsToViewport2( const QPoint &p ) +{ + return QPoint( p.x() - contentsX(), + p.y() - contentsY() ); +} + +QPoint QTable::viewportToContents2( const QPoint& vp ) +{ + return QPoint( vp.x() + contentsX(), + vp.y() + contentsY() ); +} + +void QTable::viewportToContents2( int vx, int vy, int& x, int& y ) +{ + const QPoint c = viewportToContents2( QPoint( vx, vy ) ); + x = c.x(); + y = c.y(); +} + +/*! + This function should be called whenever the column width of \a col + has been changed. It updates the geometry of any affected columns + and repaints the table to reflect the changes it has made. +*/ + +void QTable::columnWidthChanged( int col ) +{ + int p = columnPos( col ); + if ( d->hasColSpan ) + p = contentsX(); + updateContents( p, contentsY(), contentsWidth(), visibleHeight() ); + QSize s( tableSize() ); + int w = contentsWidth(); + resizeContents( s.width(), s.height() ); + if ( contentsWidth() < w ) + repaintContents( s.width(), contentsY(), + w - s.width() + 1, visibleHeight(), TRUE ); + else + repaintContents( w, contentsY(), + s.width() - w + 1, visibleHeight(), FALSE ); + + // update widgets that are affected by this change + if ( widgets.size() ) { + int last = isHidden() ? numCols() - 1 : d->lastVisCol; + for ( int c = col; c <= last; ++c ) + updateColWidgets( c ); + } + delayedUpdateGeometries(); +} + +/*! + This function should be called whenever the row height of \a row + has been changed. It updates the geometry of any affected rows and + repaints the table to reflect the changes it has made. +*/ + +void QTable::rowHeightChanged( int row ) +{ + int p = rowPos( row ); + if ( d->hasRowSpan ) + p = contentsY(); + updateContents( contentsX(), p, visibleWidth(), contentsHeight() ); + QSize s( tableSize() ); + int h = contentsHeight(); + resizeContents( s.width(), s.height() ); + if ( contentsHeight() < h ) { + repaintContents( contentsX(), contentsHeight(), + visibleWidth(), h - s.height() + 1, TRUE ); + } else { + repaintContents( contentsX(), h, + visibleWidth(), s.height() - h + 1, FALSE ); + } + + // update widgets that are affected by this change + if ( widgets.size() ) { + d->lastVisRow = rowAt( contentsY() + visibleHeight() + ( s.height() - h + 1 ) ); + int last = isHidden() ? numRows() - 1 : d->lastVisRow; + for ( int r = row; r <= last; ++r ) + updateRowWidgets( r ); + } + delayedUpdateGeometries(); +} + +/*! \internal */ + +void QTable::updateRowWidgets( int row ) +{ + for ( int i = 0; i < numCols(); ++i ) { + QWidget *w = cellWidget( row, i ); + if ( !w ) + continue; + moveChild( w, columnPos( i ), rowPos( row ) ); + w->resize( columnWidth( i ) - 1, rowHeight( row ) - 1 ); + } +} + +/*! \internal */ + +void QTable::updateColWidgets( int col ) +{ + for ( int i = 0; i < numRows(); ++i ) { + QWidget *w = cellWidget( i, col ); + if ( !w ) + continue; + moveChild( w, columnPos( col ), rowPos( i ) ); + w->resize( columnWidth( col ) - 1, rowHeight( i ) - 1 ); + } +} + +/*! + This function is called when column order is to be changed, i.e. + when the user moved the column header \a section from \a fromIndex + to \a toIndex. + + If you want to change the column order programmatically, call + swapRows() or swapColumns(); + + \sa QHeader::indexChange() rowIndexChanged() +*/ + +void QTable::columnIndexChanged( int, int fromIndex, int toIndex ) +{ + if ( doSort && lastSortCol == fromIndex && topHeader ) + topHeader->setSortIndicator( toIndex, topHeader->sortIndicatorOrder() ); + repaintContents( contentsX(), contentsY(), + visibleWidth(), visibleHeight(), FALSE ); +} + +/*! + This function is called when the order of the rows is to be + changed, i.e. the user moved the row header section \a section + from \a fromIndex to \a toIndex. + + If you want to change the order programmatically, call swapRows() + or swapColumns(); + + \sa QHeader::indexChange() columnIndexChanged() +*/ + +void QTable::rowIndexChanged( int, int, int ) +{ + repaintContents( contentsX(), contentsY(), + visibleWidth(), visibleHeight(), FALSE ); +} + +/*! + This function is called when the column \a col has been clicked. + The default implementation sorts this column if sorting() is TRUE. +*/ + +void QTable::columnClicked( int col ) +{ + if ( !sorting() ) + return; + + if ( col == lastSortCol ) { + asc = !asc; + } else { + lastSortCol = col; + asc = TRUE; + } + sortColumn( lastSortCol, asc ); +} + +/*! + \property QTable::sorting + \brief whether a click on the header of a column sorts that column + + \sa sortColumn() +*/ + +void QTable::setSorting( bool b ) +{ + doSort = b; + if ( topHeader ) + topHeader->setSortIndicator( b ? lastSortCol : -1 ); +} + +bool QTable::sorting() const +{ + return doSort; +} + +static bool inUpdateGeometries = FALSE; + +void QTable::delayedUpdateGeometries() +{ + d->geomTimer->start( 0, TRUE ); +} + +void QTable::updateGeometriesSlot() +{ + updateGeometries(); +} + +/*! + This function updates the geometries of the left and top header. + You do not normally need to call this function. +*/ + +void QTable::updateGeometries() +{ + if ( inUpdateGeometries ) + return; + inUpdateGeometries = TRUE; + QSize ts = tableSize(); + if ( topHeader->offset() && + ts.width() < topHeader->offset() + topHeader->width() ) + horizontalScrollBar()->setValue( ts.width() - topHeader->width() ); + if ( leftHeader->offset() && + ts.height() < leftHeader->offset() + leftHeader->height() ) + verticalScrollBar()->setValue( ts.height() - leftHeader->height() ); + + leftHeader->setGeometry( QStyle::visualRect( QRect( frameWidth(), topMargin() + frameWidth(), + VERTICALMARGIN, visibleHeight() ), rect() ) ); + topHeader->setGeometry( QStyle::visualRect( QRect(VERTICALMARGIN + frameWidth(), frameWidth(), + visibleWidth(), topMargin() ), rect() ) ); + horizontalScrollBar()->raise(); + verticalScrollBar()->raise(); + topHeader->updateStretches(); + leftHeader->updateStretches(); + inUpdateGeometries = FALSE; +} + +/*! + Returns the width of column \a col. + + \sa setColumnWidth() rowHeight() +*/ + +int QTable::columnWidth( int col ) const +{ + return topHeader->sectionSize( col ); +} + +/*! + Returns the height of row \a row. + + \sa setRowHeight() columnWidth() +*/ + +int QTable::rowHeight( int row ) const +{ + return leftHeader->sectionSize( row ); +} + +/*! + Returns the x-coordinate of the column \a col in content + coordinates. + + \sa columnAt() rowPos() +*/ + +int QTable::columnPos( int col ) const +{ + return topHeader->sectionPos( col ); +} + +/*! + Returns the y-coordinate of the row \a row in content coordinates. + + \sa rowAt() columnPos() +*/ + +int QTable::rowPos( int row ) const +{ + return leftHeader->sectionPos( row ); +} + +/*! + Returns the number of the column at position \a x. \a x must be + given in content coordinates. + + \sa columnPos() rowAt() +*/ + +int QTable::columnAt( int x ) const +{ + return topHeader->sectionAt( x ); +} + +/*! + Returns the number of the row at position \a y. \a y must be given + in content coordinates. + + \sa rowPos() columnAt() +*/ + +int QTable::rowAt( int y ) const +{ + return leftHeader->sectionAt( y ); +} + +/*! + Returns the bounding rectangle of the cell at \a row, \a col in + content coordinates. +*/ + +QRect QTable::cellGeometry( int row, int col ) const +{ + QTableItem *itm = item( row, col ); + + if ( !itm || itm->rowSpan() == 1 && itm->colSpan() == 1 ) + return QRect( columnPos( col ), rowPos( row ), + columnWidth( col ), rowHeight( row ) ); + + while ( row != itm->row() ) + row--; + while ( col != itm->col() ) + col--; + + QRect rect( columnPos( col ), rowPos( row ), + columnWidth( col ), rowHeight( row ) ); + + for ( int r = 1; r < itm->rowSpan(); ++r ) + rect.setHeight( rect.height() + rowHeight( r + row ) ); + + for ( int c = 1; c < itm->colSpan(); ++c ) + rect.setWidth( rect.width() + columnWidth( c + col ) ); + + return rect; +} + +/*! + Returns the size of the table. + + This is the same as the coordinates of the bottom-right edge of + the last table cell. +*/ + +QSize QTable::tableSize() const +{ + return QSize( columnPos( numCols() - 1 ) + columnWidth( numCols() - 1 ), + rowPos( numRows() - 1 ) + rowHeight( numRows() - 1 ) ); +} + +/*! + \property QTable::numRows + \brief The number of rows in the table + + \sa numCols +*/ + +int QTable::numRows() const +{ + return leftHeader->count(); +} + +/*! + \property QTable::numCols + \brief The number of columns in the table + + \sa numRows +*/ + +int QTable::numCols() const +{ + return topHeader->count(); +} + +void QTable::saveContents( QPtrVector<QTableItem> &tmp, + QPtrVector<QTable::TableWidget> &tmp2) +{ + int nCols = numCols(); + if ( editRow != -1 && editCol != -1 ) + endEdit( editRow, editCol, FALSE, edMode != Editing ); + tmp.resize( contents.size() ); + tmp2.resize( widgets.size() ); + int i; + for ( i = 0; i < (int)tmp.size(); ++i ) { + QTableItem *item = contents[ i ]; + if ( item && ( item->row() * nCols) + item->col() == i ) + tmp.insert( i, item ); + else + tmp.insert( i, 0 ); + } + for ( i = 0; i < (int)tmp2.size(); ++i ) { + QWidget *w = widgets[ i ]; + if ( w ) + tmp2.insert( i, new TableWidget( w, i / nCols, i % nCols ) ); + else + tmp2.insert( i, 0 ); + } +} + +void QTable::updateHeaderAndResizeContents( QTableHeader *header, + int num, int rowCol, + int width, bool &updateBefore ) +{ + updateBefore = rowCol < num; + if ( rowCol > num ) { + header->QHeader::resizeArrays( rowCol ); + header->QTableHeader::resizeArrays( rowCol ); + int old = num; + clearSelection( FALSE ); + int i = 0; + for ( i = old; i < rowCol; ++i ) + header->addLabel( QString::null, width ); + } else { + clearSelection( FALSE ); + if ( header == leftHeader ) { + while ( numRows() > rowCol ) + header->removeLabel( numRows() - 1 ); + } else { + while ( numCols() > rowCol ) + header->removeLabel( numCols() - 1 ); + } + } + + contents.setAutoDelete( FALSE ); + contents.clear(); + contents.setAutoDelete( TRUE ); + widgets.setAutoDelete( FALSE ); + widgets.clear(); + widgets.setAutoDelete( TRUE ); + resizeData( numRows() * numCols() ); + + // keep numStretches in sync + int n = 0; + for ( uint i = 0; i < header->stretchable.size(); i++ ) + n += ( header->stretchable.at(i) & 1 ); // avoid cmp + header->numStretches = n; +} + +void QTable::restoreContents( QPtrVector<QTableItem> &tmp, + QPtrVector<QTable::TableWidget> &tmp2 ) +{ + int i; + int nCols = numCols(); + for ( i = 0; i < (int)tmp.size(); ++i ) { + QTableItem *it = tmp[ i ]; + if ( it ) { + int idx = ( it->row() * nCols ) + it->col(); + if ( (uint)idx < contents.size() && + it->row() == idx / nCols && it->col() == idx % nCols ) { + contents.insert( idx, it ); + if ( it->rowSpan() > 1 || it->colSpan() > 1 ) { + int ridx, iidx; + for ( int irow = 0; irow < it->rowSpan(); irow++ ) { + ridx = idx + irow * nCols; + for ( int icol = 0; icol < it->colSpan(); icol++ ) { + iidx = ridx + icol; + if ( idx != iidx && (uint)iidx < contents.size() ) + contents.insert( iidx, it ); + } + } + + } + } else { + delete it; + } + } + } + for ( i = 0; i < (int)tmp2.size(); ++i ) { + TableWidget *w = tmp2[ i ]; + if ( w ) { + int idx = ( w->row * nCols ) + w->col; + if ( (uint)idx < widgets.size() && + w->row == idx / nCols && w->col == idx % nCols ) + widgets.insert( idx, w->wid ); + else + delete w->wid; + delete w; + } + } +} + +void QTable::finishContentsResze( bool updateBefore ) +{ + QRect r( cellGeometry( numRows() - 1, numCols() - 1 ) ); + resizeContents( r.right() + 1, r.bottom() + 1 ); + updateGeometries(); + if ( updateBefore ) + repaintContents( contentsX(), contentsY(), + visibleWidth(), visibleHeight(), TRUE ); + else + repaintContents( contentsX(), contentsY(), + visibleWidth(), visibleHeight(), FALSE ); + + if ( isRowSelection( selectionMode() ) ) { + int r = curRow; + curRow = -1; + setCurrentCell( r, curCol ); + } +} + +void QTable::setNumRows( int r ) +{ + if ( r < 0 ) + return; + + if (r < numRows()) { + // Removed rows are no longer hidden, and should thus be removed from "hiddenRows" + for (int rr = numRows()-1; rr >= r; --rr) { + if (d->hiddenRows.find(rr)) + d->hiddenRows.remove(rr); + } + } + + fontChange(font()); // invalidate the sizeHintCache + + QPtrVector<QTableItem> tmp; + QPtrVector<TableWidget> tmp2; + saveContents( tmp, tmp2 ); + + bool isUpdatesEnabled = leftHeader->isUpdatesEnabled(); + leftHeader->setUpdatesEnabled( FALSE ); + + bool updateBefore; + updateHeaderAndResizeContents( leftHeader, numRows(), r, 20, updateBefore ); + + int w = fontMetrics().width( QString::number( r ) + "W" ); + if ( VERTICALMARGIN > 0 && w > VERTICALMARGIN ) + setLeftMargin( w ); + + restoreContents( tmp, tmp2 ); + + leftHeader->calculatePositions(); + finishContentsResze( updateBefore ); + leftHeader->setUpdatesEnabled( isUpdatesEnabled ); + if ( isUpdatesEnabled ) + leftHeader->update(); + leftHeader->updateCache(); + if ( curRow >= numRows() ) { + curRow = numRows() - 1; + if ( curRow < 0 ) + curCol = -1; + else + repaintCell( curRow, curCol ); + } + + if ( curRow > numRows() ) + curRow = numRows(); +} + +void QTable::setNumCols( int c ) +{ + if ( c < 0 ) + return; + + if (c < numCols()) { + // Removed columns are no longer hidden, and should thus be removed from "hiddenCols" + for (int cc = numCols()-1; cc >= c; --cc) { + if (d->hiddenCols.find(cc)) + d->hiddenCols.remove(cc); + } + } + + fontChange(font()); // invalidate the sizeHintCache + + QPtrVector<QTableItem> tmp; + QPtrVector<TableWidget> tmp2; + saveContents( tmp, tmp2 ); + + bool isUpdatesEnabled = topHeader->isUpdatesEnabled(); + topHeader->setUpdatesEnabled( FALSE ); + + bool updateBefore; + updateHeaderAndResizeContents( topHeader, numCols(), c, 100, updateBefore ); + + restoreContents( tmp, tmp2 ); + + topHeader->calculatePositions(); + finishContentsResze( updateBefore ); + topHeader->setUpdatesEnabled( isUpdatesEnabled ); + if ( isUpdatesEnabled ) + topHeader->update(); + topHeader->updateCache(); + if ( curCol >= numCols() ) { + curCol = numCols() - 1; + if ( curCol < 0 ) + curRow = -1; + else + repaintCell( curRow, curCol ); + } +} + +/*! Sets the section labels of the verticalHeader() to \a labels */ + +void QTable::setRowLabels( const QStringList &labels ) +{ + leftHeader->setLabels(labels); +} + +/*! Sets the section labels of the horizontalHeader() to \a labels */ + +void QTable::setColumnLabels( const QStringList &labels ) +{ + topHeader->setLabels(labels); +} + +/*! + This function returns the widget which should be used as an editor + for the contents of the cell at \a row, \a col. + + If \a initFromCell is TRUE, the editor is used to edit the current + contents of the cell (so the editor widget should be initialized + with this content). If \a initFromCell is FALSE, the content of + the cell is replaced with the new content which the user entered + into the widget created by this function. + + The default functionality is as follows: if \a initFromCell is + TRUE or the cell has a QTableItem and the table item's + QTableItem::isReplaceable() is FALSE then the cell is asked to + create an appropriate editor (using QTableItem::createEditor()). + Otherwise a QLineEdit is used as the editor. + + If you want to create your own editor for certain cells, implement + a custom QTableItem subclass and reimplement + QTableItem::createEditor(). + + If you are not using \l{QTableItem}s and you don't want to use a + QLineEdit as the default editor, subclass QTable and reimplement + this function with code like this: + \code + QTableItem *i = item( row, col ); + if ( initFromCell || ( i && !i->isReplaceable() ) ) + // If we had a QTableItem ask the base class to create the editor + return QTable::createEditor( row, col, initFromCell ); + else + return ...(create your own editor) + \endcode + Ownership of the editor widget is transferred to the caller. + + If you reimplement this function return 0 for read-only cells. You + will need to reimplement setCellContentFromEditor() to retrieve + the data the user entered. + + \sa QTableItem::createEditor() +*/ + +QWidget *QTable::createEditor( int row, int col, bool initFromCell ) const +{ + if ( isReadOnly() || isRowReadOnly( row ) || isColumnReadOnly( col ) ) + return 0; + + QWidget *e = 0; + + // the current item in the cell should be edited if possible + QTableItem *i = item( row, col ); + if ( initFromCell || ( i && !i->isReplaceable() ) ) { + if ( i ) { + if ( i->editType() == QTableItem::Never ) + return 0; + + e = i->createEditor(); + if ( !e ) + return 0; + } + } + + // no contents in the cell yet, so open the default editor + if ( !e ) { + e = new QLineEdit( viewport(), "qt_lineeditor" ); + ( (QLineEdit*)e )->setFrame( FALSE ); + } + + return e; +} + +/*! + This function is called to start in-place editing of the cell at + \a row, \a col. Editing is achieved by creating an editor + (createEditor() is called) and setting the cell's editor with + setCellWidget() to the newly created editor. (After editing is + complete endEdit() will be called to replace the cell's content + with the editor's content.) If \a replace is TRUE the editor will + start empty; otherwise it will be initialized with the cell's + content (if any), i.e. the user will be modifying the original + cell content. + + \sa endEdit() +*/ + +QWidget *QTable::beginEdit( int row, int col, bool replace ) +{ + if ( isReadOnly() || isRowReadOnly( row ) || isColumnReadOnly( col ) ) + return 0; + if ( row < 0 || row >= numRows() || col < 0 || col >= numCols() ) + return 0; + + QTableItem *itm = item( row, col ); + if ( itm && !itm->isEnabled() ) + return 0; + if ( cellWidget( row, col ) ) + return 0; + ensureCellVisible( row, col ); + QWidget *e = createEditor( row, col, !replace ); + if ( !e ) + return 0; + setCellWidget( row, col, e ); + e->setActiveWindow(); + e->setFocus(); + updateCell( row, col ); + return e; +} + +/*! + This function is called when in-place editing of the cell at \a + row, \a col is requested to stop. + + If the cell is not being edited or \a accept is FALSE the function + returns and the cell's contents are left unchanged. + + If \a accept is TRUE the content of the editor must be transferred + to the relevant cell. If \a replace is TRUE the current content of + this cell should be replaced by the content of the editor (this + means removing the current QTableItem of the cell and creating a + new one for the cell). Otherwise (if possible) the content of the + editor should just be set to the existing QTableItem of this cell. + + setCellContentFromEditor() is called to replace the contents of + the cell with the contents of the cell's editor. + + Finally clearCellWidget() is called to remove the editor widget. + + \sa setCellContentFromEditor(), beginEdit() +*/ + +void QTable::endEdit( int row, int col, bool accept, bool replace ) +{ + QWidget *editor = cellWidget( row, col ); + if ( !editor ) + return; + + if ( !accept ) { + if ( row == editRow && col == editCol ) + setEditMode( NotEditing, -1, -1 ); + clearCellWidget( row, col ); + updateCell( row, col ); + viewport()->setFocus(); + updateCell( row, col ); + return; + } + + QTableItem *i = item( row, col ); + QString oldContent; + if ( i ) + oldContent = i->text(); + + if ( !i || replace ) { + setCellContentFromEditor( row, col ); + i = item( row, col ); + } else { + i->setContentFromEditor( editor ); + } + + if ( row == editRow && col == editCol ) + setEditMode( NotEditing, -1, -1 ); + + viewport()->setFocus(); + updateCell( row, col ); + + if (!i || (oldContent != i->text())) + emit valueChanged( row, col ); + + clearCellWidget( row, col ); +} + +/*! + This function is called to replace the contents of the cell at \a + row, \a col with the contents of the cell's editor. + + If there already exists a QTableItem for the cell, + it calls QTableItem::setContentFromEditor() on this QTableItem. + + If, for example, you want to create different \l{QTableItem}s + depending on the contents of the editor, you might reimplement + this function. + + If you want to work without \l{QTableItem}s, you will need to + reimplement this function to save the data the user entered into + your data structure. (See the notes on large tables.) + + \sa QTableItem::setContentFromEditor() createEditor() +*/ + +void QTable::setCellContentFromEditor( int row, int col ) +{ + QWidget *editor = cellWidget( row, col ); + if ( !editor ) + return; + + QTableItem *i = item( row, col ); + if ( i ) { + i->setContentFromEditor( editor ); + } else { + QLineEdit *le = ::qt_cast<QLineEdit*>(editor); + if ( le ) + setText( row, col, le->text() ); + } +} + +/*! + Returns TRUE if the \l EditMode is \c Editing or \c Replacing; + otherwise (i.e. the \l EditMode is \c NotEditing) returns FALSE. + + \sa QTable::EditMode +*/ + +bool QTable::isEditing() const +{ + return edMode != NotEditing; +} + +/*! + Returns the current edit mode + + \sa QTable::EditMode +*/ + +QTable::EditMode QTable::editMode() const +{ + return edMode; +} + +/*! + Returns the current edited row +*/ + +int QTable::currEditRow() const +{ + return editRow; +} + +/*! + Returns the current edited column +*/ + +int QTable::currEditCol() const +{ + return editCol; +} + +/*! + Returns a single integer which identifies a particular \a row and \a + col by mapping the 2D table to a 1D array. + + This is useful, for example, if you have a sparse table and want to + use a QIntDict to map integers to the cells that are used. +*/ + +int QTable::indexOf( int row, int col ) const +{ + return ( row * numCols() ) + col; +} + +/*! \internal +*/ + +void QTable::repaintSelections( QTableSelection *oldSelection, + QTableSelection *newSelection, + bool updateVertical, bool updateHorizontal ) +{ + if ( !oldSelection && !newSelection ) + return; + if ( oldSelection && newSelection && *oldSelection == *newSelection ) + return; + if ( oldSelection && !oldSelection->isActive() ) + oldSelection = 0; + + bool optimizeOld = FALSE; + bool optimizeNew = FALSE; + + QRect old; + if ( oldSelection ) + old = rangeGeometry( oldSelection->topRow(), + oldSelection->leftCol(), + oldSelection->bottomRow(), + oldSelection->rightCol(), + optimizeOld ); + else + old = QRect( 0, 0, 0, 0 ); + + QRect cur; + if ( newSelection ) + cur = rangeGeometry( newSelection->topRow(), + newSelection->leftCol(), + newSelection->bottomRow(), + newSelection->rightCol(), + optimizeNew ); + else + cur = QRect( 0, 0, 0, 0 ); + int i; + + if ( !optimizeOld || !optimizeNew || + old.width() > SHRT_MAX || old.height() > SHRT_MAX || + cur.width() > SHRT_MAX || cur.height() > SHRT_MAX ) { + QRect rr = cur.unite( old ); + repaintContents( rr, FALSE ); + } else { + old = QRect( contentsToViewport2( old.topLeft() ), old.size() ); + cur = QRect( contentsToViewport2( cur.topLeft() ), cur.size() ); + QRegion r1( old ); + QRegion r2( cur ); + QRegion r3 = r1.subtract( r2 ); + QRegion r4 = r2.subtract( r1 ); + + for ( i = 0; i < (int)r3.rects().count(); ++i ) { + QRect r( r3.rects()[ i ] ); + r = QRect( viewportToContents2( r.topLeft() ), r.size() ); + repaintContents( r, FALSE ); + } + for ( i = 0; i < (int)r4.rects().count(); ++i ) { + QRect r( r4.rects()[ i ] ); + r = QRect( viewportToContents2( r.topLeft() ), r.size() ); + repaintContents( r, FALSE ); + } + } + + int top, left, bottom, right; + { + int oldTopRow = oldSelection ? oldSelection->topRow() : numRows() - 1; + int newTopRow = newSelection ? newSelection->topRow() : numRows() - 1; + top = QMIN(oldTopRow, newTopRow); + } + + { + int oldLeftCol = oldSelection ? oldSelection->leftCol() : numCols() - 1; + int newLeftCol = newSelection ? newSelection->leftCol() : numCols() - 1; + left = QMIN(oldLeftCol, newLeftCol); + } + + { + int oldBottomRow = oldSelection ? oldSelection->bottomRow() : 0; + int newBottomRow = newSelection ? newSelection->bottomRow() : 0; + bottom = QMAX(oldBottomRow, newBottomRow); + } + + { + int oldRightCol = oldSelection ? oldSelection->rightCol() : 0; + int newRightCol = newSelection ? newSelection->rightCol() : 0; + right = QMAX(oldRightCol, newRightCol); + } + + if ( updateHorizontal && numCols() > 0 && left >= 0 && !isRowSelection( selectionMode() ) ) { + register int *s = &topHeader->states.data()[left]; + for ( i = left; i <= right; ++i ) { + if ( !isColumnSelected( i ) ) + *s = QTableHeader::Normal; + else if ( isColumnSelected( i, TRUE ) ) + *s = QTableHeader::Selected; + else + *s = QTableHeader::Bold; + ++s; + } + topHeader->repaint( FALSE ); + } + + if ( updateVertical && numRows() > 0 && top >= 0 ) { + register int *s = &leftHeader->states.data()[top]; + for ( i = top; i <= bottom; ++i ) { + if ( !isRowSelected( i ) ) + *s = QTableHeader::Normal; + else if ( isRowSelected( i, TRUE ) ) + *s = QTableHeader::Selected; + else + *s = QTableHeader::Bold; + ++s; + } + leftHeader->repaint( FALSE ); + } +} + +/*! + Repaints all selections +*/ + +void QTable::repaintSelections() +{ + if ( selections.isEmpty() ) + return; + + QRect r; + for ( QTableSelection *s = selections.first(); s; s = selections.next() ) { + bool b; + r = r.unite( rangeGeometry( s->topRow(), + s->leftCol(), + s->bottomRow(), + s->rightCol(), b ) ); + } + + repaintContents( r, FALSE ); +} + +/*! + Clears all selections and repaints the appropriate regions if \a + repaint is TRUE. + + \sa removeSelection() +*/ + +void QTable::clearSelection( bool repaint ) +{ + if ( selections.isEmpty() ) + return; + bool needRepaint = !selections.isEmpty(); + + QRect r; + for ( QTableSelection *s = selections.first(); s; s = selections.next() ) { + bool b; + r = r.unite( rangeGeometry( s->topRow(), + s->leftCol(), + s->bottomRow(), + s->rightCol(), b ) ); + } + + currentSel = 0; + selections.clear(); + + if ( needRepaint && repaint ) + repaintContents( r, FALSE ); + + leftHeader->setSectionStateToAll( QTableHeader::Normal ); + leftHeader->repaint( FALSE ); + if ( !isRowSelection( selectionMode() ) ) { + topHeader->setSectionStateToAll( QTableHeader::Normal ); + topHeader->repaint( FALSE ); + } + topHeader->setSectionState( curCol, QTableHeader::Bold ); + leftHeader->setSectionState( curRow, QTableHeader::Bold ); + emit selectionChanged(); +} + +/*! \internal +*/ + +QRect QTable::rangeGeometry( int topRow, int leftCol, + int bottomRow, int rightCol, bool &optimize ) +{ + topRow = QMAX( topRow, rowAt( contentsY() ) ); + leftCol = QMAX( leftCol, columnAt( contentsX() ) ); + int ra = rowAt( contentsY() + visibleHeight() ); + if ( ra != -1 ) + bottomRow = QMIN( bottomRow, ra ); + int ca = columnAt( contentsX() + visibleWidth() ); + if ( ca != -1 ) + rightCol = QMIN( rightCol, ca ); + optimize = TRUE; + QRect rect; + for ( int r = topRow; r <= bottomRow; ++r ) { + for ( int c = leftCol; c <= rightCol; ++c ) { + rect = rect.unite( cellGeometry( r, c ) ); + QTableItem *i = item( r, c ); + if ( i && ( i->rowSpan() > 1 || i->colSpan() > 1 ) ) + optimize = FALSE; + } + } + return rect; +} + +/*! + This function is called to activate the next cell if in-place + editing was finished by pressing the Enter key. + + The default behaviour is to move from top to bottom, i.e. move to + the cell beneath the cell being edited. Reimplement this function + if you want different behaviour, e.g. moving from left to right. +*/ + +void QTable::activateNextCell() +{ + int firstRow = 0; + while ( d->hiddenRows.find( firstRow ) ) + firstRow++; + int firstCol = 0; + while ( d->hiddenCols.find( firstCol ) ) + firstCol++; + int nextRow = curRow; + int nextCol = curCol; + while ( d->hiddenRows.find( ++nextRow ) ); + if ( nextRow >= numRows() ) { + nextRow = firstRow; + while ( d->hiddenCols.find( ++nextCol ) ); + if ( nextCol >= numCols() ) + nextCol = firstCol; + } + + if ( !currentSel || !currentSel->isActive() || + ( currentSel->leftCol() == currentSel->rightCol() && + currentSel->topRow() == currentSel->bottomRow() ) ) { + clearSelection(); + setCurrentCell( nextRow, nextCol ); + } else { + if ( curRow < currentSel->bottomRow() ) + setCurrentCell( nextRow, curCol ); + else if ( curCol < currentSel->rightCol() ) + setCurrentCell( currentSel->topRow(), nextCol ); + else + setCurrentCell( currentSel->topRow(), currentSel->leftCol() ); + } + +} + +/*! \internal +*/ + +void QTable::fixRow( int &row, int y ) +{ + if ( row == -1 ) { + if ( y < 0 ) + row = 0; + else + row = numRows() - 1; + } +} + +/*! \internal +*/ + +void QTable::fixCol( int &col, int x ) +{ + if ( col == -1 ) { + if ( x < 0 ) + col = 0; + else + col = numCols() - 1; + } +} + +struct SortableTableItem +{ + QTableItem *item; +}; + +#if defined(Q_C_CALLBACKS) +extern "C" { +#endif + +#ifdef Q_OS_TEMP +static int _cdecl cmpTableItems( const void *n1, const void *n2 ) +#else +static int cmpTableItems( const void *n1, const void *n2 ) +#endif +{ + if ( !n1 || !n2 ) + return 0; + + SortableTableItem *i1 = (SortableTableItem *)n1; + SortableTableItem *i2 = (SortableTableItem *)n2; + + return i1->item->key().localeAwareCompare( i2->item->key() ); +} + +#if defined(Q_C_CALLBACKS) +} +#endif + +/*! + Sorts column \a col. If \a ascending is TRUE the sort is in + ascending order, otherwise the sort is in descending order. + + If \a wholeRows is TRUE, entire rows are sorted using swapRows(); + otherwise only cells in the column are sorted using swapCells(). + + Note that if you are not using QTableItems you will need to + reimplement swapRows() and swapCells(). (See the notes on large + tables.) + + \sa swapRows() +*/ + +void QTable::sortColumn( int col, bool ascending, bool wholeRows ) +{ + int filledRows = 0, i; + for ( i = 0; i < numRows(); ++i ) { + QTableItem *itm = item( i, col ); + if ( itm ) + filledRows++; + } + + if ( !filledRows ) + return; + + SortableTableItem *items = new SortableTableItem[ filledRows ]; + int j = 0; + for ( i = 0; i < numRows(); ++i ) { + QTableItem *itm = item( i, col ); + if ( !itm ) + continue; + items[ j++ ].item = itm; + } + + qsort( items, filledRows, sizeof( SortableTableItem ), cmpTableItems ); + + bool updatesEnabled = isUpdatesEnabled(); + setUpdatesEnabled( FALSE ); + for ( i = 0; i < numRows(); ++i ) { + if ( i < filledRows ) { + if ( ascending ) { + if ( items[ i ].item->row() == i ) + continue; + if ( wholeRows ) + swapRows( items[ i ].item->row(), i ); + else + swapCells( items[ i ].item->row(), col, i, col ); + } else { + if ( items[ i ].item->row() == filledRows - i - 1 ) + continue; + if ( wholeRows ) + swapRows( items[ i ].item->row(), filledRows - i - 1 ); + else + swapCells( items[ i ].item->row(), col, + filledRows - i - 1, col ); + } + } + } + setUpdatesEnabled( updatesEnabled ); + if ( topHeader ) + topHeader->setSortIndicator( col, ascending ? Qt::Ascending : Qt::Descending ); + + if ( !wholeRows ) + repaintContents( columnPos( col ), contentsY(), + columnWidth( col ), visibleHeight(), FALSE ); + else + repaintContents( contentsX(), contentsY(), + visibleWidth(), visibleHeight(), FALSE ); + + delete [] items; +} + +/*! + Hides row \a row. + + \sa showRow() hideColumn() +*/ + +void QTable::hideRow( int row ) +{ + if ( d->hiddenRows.find( row ) ) + return; + d->hiddenRows.replace( row, new int( leftHeader->sectionSize( row ) ) ); + leftHeader->resizeSection( row, 0 ); + leftHeader->setResizeEnabled( FALSE, row ); + if ( isRowStretchable(row) ) + leftHeader->numStretches--; + rowHeightChanged( row ); + if ( curRow == row ) { + int r = curRow; + int c = curCol; + int k = ( r >= numRows() - 1 ? Key_Up : Key_Down ); + fixCell( r, c, k ); + if ( numRows() > 0 ) + setCurrentCell( r, c ); + } +} + +/*! + Hides column \a col. + + \sa showColumn() hideRow() +*/ + +void QTable::hideColumn( int col ) +{ + if ( !numCols() || d->hiddenCols.find( col ) ) + return; + d->hiddenCols.replace( col, new int( topHeader->sectionSize( col ) ) ); + topHeader->resizeSection( col, 0 ); + topHeader->setResizeEnabled( FALSE, col ); + if ( isColumnStretchable(col) ) + topHeader->numStretches--; + columnWidthChanged( col ); + if ( curCol == col ) { + int r = curRow; + int c = curCol; + int k = ( c >= numCols() - 1 ? Key_Left : Key_Right ); + fixCell( r, c, k ); + if ( numCols() > 0 ) + setCurrentCell( r, c ); + } +} + +/*! + Shows row \a row. + + \sa hideRow() showColumn() +*/ + +void QTable::showRow( int row ) +{ + int *h = d->hiddenRows.find( row ); + if ( h ) { + int rh = *h; + d->hiddenRows.remove( row ); + setRowHeight( row, rh ); + if ( isRowStretchable(row) ) + leftHeader->numStretches++; + } else if ( rowHeight( row ) == 0 ) { + setRowHeight( row, 20 ); + } + leftHeader->setResizeEnabled( TRUE, row ); +} + +/*! + Shows column \a col. + + \sa hideColumn() showRow() +*/ + +void QTable::showColumn( int col ) +{ + int *w = d->hiddenCols.find( col ); + if ( w ) { + int cw = *w; + d->hiddenCols.remove( col ); + setColumnWidth( col, cw ); + if ( isColumnStretchable( col ) ) + topHeader->numStretches++; + } else if ( columnWidth( col ) == 0 ) { + setColumnWidth( col, 20 ); + } + topHeader->setResizeEnabled( TRUE, col ); +} + +/*! + Returns TRUE if row \a row is hidden; otherwise returns + FALSE. + + \sa hideRow(), isColumnHidden() +*/ +bool QTable::isRowHidden( int row ) const +{ + return d->hiddenRows.find( row ); +} + +/*! + Returns TRUE if column \a col is hidden; otherwise returns + FALSE. + + \sa hideColumn(), isRowHidden() +*/ +bool QTable::isColumnHidden( int col ) const +{ + return d->hiddenCols.find( col ); +} + +/*! + Resizes column \a col to be \a w pixels wide. + + \sa columnWidth() setRowHeight() +*/ + +void QTable::setColumnWidth( int col, int w ) +{ + int *ow = d->hiddenCols.find( col ); + if ( ow ) { + d->hiddenCols.replace( col, new int( w ) ); + } else { + topHeader->resizeSection( col, w ); + columnWidthChanged( col ); + } +} + +/*! + Resizes row \a row to be \a h pixels high. + + \sa rowHeight() setColumnWidth() +*/ + +void QTable::setRowHeight( int row, int h ) +{ + int *oh = d->hiddenRows.find( row ); + if ( oh ) { + d->hiddenRows.replace( row, new int( h ) ); + } else { + leftHeader->resizeSection( row, h ); + rowHeightChanged( row ); + } +} + +/*! + Resizes column \a col so that the column width is wide enough to + display the widest item the column contains. + + \sa adjustRow() +*/ + +void QTable::adjustColumn( int col ) +{ + int w; + if ( currentColumn() == col ) { + QFont f = font(); + f.setBold(true); + w = topHeader->sectionSizeHint( col, QFontMetrics(f) ).width(); + } else { + w = topHeader->sectionSizeHint( col, fontMetrics() ).width(); + } + if ( topHeader->iconSet( col ) ) + w += topHeader->iconSet( col )->pixmap().width(); + w = QMAX( w, 20 ); + for ( int i = 0; i < numRows(); ++i ) { + QTableItem *itm = item( i, col ); + if ( !itm ) { + QWidget *widget = cellWidget( i, col ); + if ( widget ) + w = QMAX( w, widget->sizeHint().width() ); + } else { + if ( itm->colSpan() > 1 ) + w = QMAX( w, itm->sizeHint().width() / itm->colSpan() ); + else + w = QMAX( w, itm->sizeHint().width() ); + } + } + w = QMAX( w, QApplication::globalStrut().width() ); + setColumnWidth( col, w ); +} + +/*! + Resizes row \a row so that the row height is tall enough to + display the tallest item the row contains. + + \sa adjustColumn() +*/ + +void QTable::adjustRow( int row ) +{ + int h = 20; + h = QMAX( h, leftHeader->sectionSizeHint( row, leftHeader->fontMetrics() ).height() ); + if ( leftHeader->iconSet( row ) ) + h = QMAX( h, leftHeader->iconSet( row )->pixmap().height() ); + for ( int i = 0; i < numCols(); ++i ) { + QTableItem *itm = item( row, i ); + if ( !itm ) { + QWidget *widget = cellWidget( row, i ); + if ( widget ) + h = QMAX( h, widget->sizeHint().height() ); + } else { + if ( itm->rowSpan() > 1 ) + h = QMAX( h, itm->sizeHint().height() / itm->rowSpan() ); + else + h = QMAX( h, itm->sizeHint().height() ); + } + } + h = QMAX( h, QApplication::globalStrut().height() ); + setRowHeight( row, h ); +} + +/*! + If \a stretch is TRUE, column \a col is set to be stretchable; + otherwise column \a col is set to be unstretchable. + + If the table widget's width decreases or increases stretchable + columns will grow narrower or wider to fit the space available as + completely as possible. The user cannot manually resize stretchable + columns. + + \sa isColumnStretchable() setRowStretchable() adjustColumn() +*/ + +void QTable::setColumnStretchable( int col, bool stretch ) +{ + topHeader->setSectionStretchable( col, stretch ); + + if ( stretch && d->hiddenCols.find(col) ) + topHeader->numStretches--; +} + +/*! + If \a stretch is TRUE, row \a row is set to be stretchable; + otherwise row \a row is set to be unstretchable. + + If the table widget's height decreases or increases stretchable + rows will grow shorter or taller to fit the space available as + completely as possible. The user cannot manually resize + stretchable rows. + + \sa isRowStretchable() setColumnStretchable() +*/ + +void QTable::setRowStretchable( int row, bool stretch ) +{ + leftHeader->setSectionStretchable( row, stretch ); + + if ( stretch && d->hiddenRows.find(row) ) + leftHeader->numStretches--; +} + +/*! + Returns TRUE if column \a col is stretchable; otherwise returns + FALSE. + + \sa setColumnStretchable() isRowStretchable() +*/ + +bool QTable::isColumnStretchable( int col ) const +{ + return topHeader->isSectionStretchable( col ); +} + +/*! + Returns TRUE if row \a row is stretchable; otherwise returns + FALSE. + + \sa setRowStretchable() isColumnStretchable() +*/ + +bool QTable::isRowStretchable( int row ) const +{ + return leftHeader->isSectionStretchable( row ); +} + +/*! + Takes the table item \a i out of the table. This function does \e + not delete the table item. You must either delete the table item + yourself or put it into a table (using setItem()) which will then + take ownership of it. + + Use this function if you want to move an item from one cell in a + table to another, or to move an item from one table to another, + reinserting the item with setItem(). + + If you want to exchange two cells use swapCells(). +*/ + +void QTable::takeItem( QTableItem *i ) +{ + if ( !i ) + return; + QRect rect = cellGeometry( i->row(), i->col() ); + contents.setAutoDelete( FALSE ); + int bottom = i->row() + i->rowSpan(); + if ( bottom > numRows() ) + bottom = numRows(); + int right = i->col() + i->colSpan(); + if ( right > numCols() ) + right = numCols(); + for ( int r = i->row(); r < bottom; ++r ) { + for ( int c = i->col(); c < right; ++c ) + contents.remove( indexOf( r, c ) ); + } + contents.setAutoDelete( TRUE ); + repaintContents( rect, FALSE ); + int orow = i->row(); + int ocol = i->col(); + i->setRow( -1 ); + i->setCol( -1 ); + i->updateEditor( orow, ocol ); + i->t = 0; +} + +/*! + Sets the widget \a e to the cell at \a row, \a col and takes care of + placing and resizing the widget when the cell geometry changes. + + By default widgets are inserted into a vector with numRows() * + numCols() elements. In very large tables you will probably want to + store the widgets in a data structure that consumes less memory (see + the notes on large tables). To support the use of your own data + structure this function calls insertWidget() to add the widget to + the internal data structure. To use your own data structure + reimplement insertWidget(), cellWidget() and clearCellWidget(). + + Cell widgets are created dynamically with the \c new operator. The + cell widgets are destroyed automatically once the table is + destroyed; the table takes ownership of the widget when using + setCellWidget. + +*/ + +void QTable::setCellWidget( int row, int col, QWidget *e ) +{ + if ( !e || row >= numRows() || col >= numCols() ) + return; + + QWidget *w = cellWidget( row, col ); + if ( w && row == editRow && col == editCol ) + endEdit( editRow, editCol, FALSE, edMode != Editing ); + + e->installEventFilter( this ); + clearCellWidget( row, col ); + if ( e->parent() != viewport() ) + e->reparent( viewport(), QPoint( 0,0 ) ); + QTableItem *itm = item(row, col); + if (itm && itm->row() >= 0 && itm->col() >= 0) { // get the correct row and col if the item is spanning + row = itm->row(); + col = itm->col(); + } + insertWidget( row, col, e ); + QRect cr = cellGeometry( row, col ); + e->resize( cr.size() ); + moveChild( e, cr.x(), cr.y() ); + e->show(); +} + +/*! + Inserts widget \a w at \a row, \a col into the internal + data structure. See the documentation of setCellWidget() for + further details. + + If you don't use \l{QTableItem}s you may need to reimplement this + function: see the notes on large tables. +*/ + +void QTable::insertWidget( int row, int col, QWidget *w ) +{ + if ( row < 0 || col < 0 || row > numRows() - 1 || col > numCols() - 1 ) + return; + + if ( (int)widgets.size() != numRows() * numCols() ) + widgets.resize( numRows() * numCols() ); + + widgets.insert( indexOf( row, col ), w ); +} + +/*! + Returns the widget that has been set for the cell at \a row, \a + col, or 0 if no widget has been set. + + If you don't use \l{QTableItem}s you may need to reimplement this + function: see the notes on large tables. + + \sa clearCellWidget() setCellWidget() +*/ + +QWidget *QTable::cellWidget( int row, int col ) const +{ + if ( row < 0 || col < 0 || row > numRows() - 1 || col > numCols() - 1 ) + return 0; + + if ( (int)widgets.size() != numRows() * numCols() ) + ( (QTable*)this )->widgets.resize( numRows() * numCols() ); + + return widgets[ indexOf( row, col ) ]; +} + +/*! + Removes the widget (if there is one) set for the cell at \a row, + \a col. + + If you don't use \l{QTableItem}s you may need to reimplement this + function: see the notes on large tables. + + This function deletes the widget at \a row, \a col. Note that the + widget is not deleted immediately; instead QObject::deleteLater() + is called on the widget to avoid problems with timing issues. + + \sa cellWidget() setCellWidget() +*/ + +void QTable::clearCellWidget( int row, int col ) +{ + if ( row < 0 || col < 0 || row > numRows() - 1 || col > numCols() - 1 ) + return; + + if ( (int)widgets.size() != numRows() * numCols() ) + widgets.resize( numRows() * numCols() ); + + QWidget *w = cellWidget( row, col ); + if ( w ) { + w->removeEventFilter( this ); + w->deleteLater(); + } + widgets.setAutoDelete( FALSE ); + widgets.remove( indexOf( row, col ) ); + widgets.setAutoDelete( TRUE ); +} + +/*! + \fn void QTable::dropped ( QDropEvent * e ) + + This signal is emitted when a drop event occurred on the table. + + \a e contains information about the drop. +*/ + +/*! + If \a b is TRUE, the table starts a drag (see dragObject()) when + the user presses and moves the mouse on a selected cell. +*/ + +void QTable::setDragEnabled( bool b ) +{ + dEnabled = b; +} + +/*! + If this function returns TRUE, the table supports dragging. + + \sa setDragEnabled(); +*/ + +bool QTable::dragEnabled() const +{ + return dEnabled; +} + +/*! + Inserts \a count empty rows at row \a row. Also clears the selection(s). + + \sa insertColumns() removeRow() +*/ + +void QTable::insertRows( int row, int count ) +{ + // special case, so a call like insertRow( currentRow(), 1 ) also + // works, when we have 0 rows and currentRow() is -1 + if ( row == -1 && curRow == -1 ) + row = 0; + if ( row < 0 || count <= 0 ) + return; + + if ( curRow >= row && curRow < row + count ) + curRow = row + count; + + --row; + if ( row >= numRows() ) + return; + + bool updatesEnabled = isUpdatesEnabled(); + setUpdatesEnabled( FALSE ); + bool leftHeaderUpdatesEnabled = leftHeader->isUpdatesEnabled(); + leftHeader->setUpdatesEnabled( FALSE ); + int oldLeftMargin = leftMargin(); + + setNumRows( numRows() + count ); + + for ( int i = numRows() - count - 1; i > row; --i ) + leftHeader->swapSections( i, i + count ); + + leftHeader->setUpdatesEnabled( leftHeaderUpdatesEnabled ); + setUpdatesEnabled( updatesEnabled ); + + int cr = QMAX( 0, currentRow() ); + int cc = QMAX( 0, currentColumn() ); + if ( curRow > row ) + curRow -= count; // this is where curRow was + setCurrentCell( cr, cc, TRUE, FALSE ); // without ensureCellVisible + + // Repaint the header + if ( leftHeaderUpdatesEnabled ) { + int y = rowPos( row ) - contentsY(); + if ( leftMargin() != oldLeftMargin || d->hasRowSpan ) + y = 0; // full repaint + QRect rect( 0, y, leftHeader->width(), contentsHeight() ); + leftHeader->update( rect ); + } + + if ( updatesEnabled ) { + int p = rowPos( row ); + if ( d->hasRowSpan ) + p = contentsY(); + updateContents( contentsX(), p, visibleWidth(), contentsHeight() + 1 ); + } +} + +/*! + Inserts \a count empty columns at column \a col. Also clears the selection(s). + + \sa insertRows() removeColumn() +*/ + +void QTable::insertColumns( int col, int count ) +{ + // see comment in insertRows() + if ( col == -1 && curCol == -1 ) + col = 0; + if ( col < 0 || count <= 0 ) + return; + + if ( curCol >= col && curCol < col + count ) + curCol = col + count; + + --col; + if ( col >= numCols() ) + return; + + bool updatesEnabled = isUpdatesEnabled(); + setUpdatesEnabled( FALSE ); + bool topHeaderUpdatesEnabled = topHeader->isUpdatesEnabled(); + topHeader->setUpdatesEnabled( FALSE ); + int oldTopMargin = topMargin(); + + setNumCols( numCols() + count ); + + for ( int i = numCols() - count - 1; i > col; --i ) + topHeader->swapSections( i, i + count ); + + topHeader->setUpdatesEnabled( topHeaderUpdatesEnabled ); + setUpdatesEnabled( updatesEnabled ); + + int cr = QMAX( 0, currentRow() ); + int cc = QMAX( 0, currentColumn() ); + if ( curCol > col ) + curCol -= count; // this is where curCol was + setCurrentCell( cr, cc, TRUE, FALSE ); // without ensureCellVisible + + // Repaint the header + if ( topHeaderUpdatesEnabled ) { + int x = columnPos( col ) - contentsX(); + if ( topMargin() != oldTopMargin || d->hasColSpan ) + x = 0; // full repaint + QRect rect( x, 0, contentsWidth(), topHeader->height() ); + topHeader->update( rect ); + } + + if ( updatesEnabled ) { + int p = columnPos( col ); + if ( d->hasColSpan ) + p = contentsX(); + updateContents( p, contentsY(), contentsWidth() + 1, visibleHeight() ); + } +} + +/*! + Removes row \a row, and deletes all its cells including any table + items and widgets the cells may contain. Also clears the selection(s). + + \sa hideRow() insertRows() removeColumn() removeRows() +*/ + +void QTable::removeRow( int row ) +{ + if ( row < 0 || row >= numRows() ) + return; + if ( row < numRows() - 1 ) { + if (d->hiddenRows.find(row)) + d->hiddenRows.remove(row); + + for ( int i = row; i < numRows() - 1; ++i ) + ( (QTableHeader*)verticalHeader() )->swapSections( i, i + 1 ); + } + setNumRows( numRows() - 1 ); +} + +/*! + Removes the rows listed in the array \a rows, and deletes all their + cells including any table items and widgets the cells may contain. + + The array passed in must only contain valid rows (in the range + from 0 to numRows() - 1) with no duplicates, and must be sorted in + ascending order. Also clears the selection(s). + + \sa removeRow() insertRows() removeColumns() +*/ + +void QTable::removeRows( const QMemArray<int> &rows ) +{ + if ( rows.count() == 0 ) + return; + int i; + for ( i = 0; i < (int)rows.count() - 1; ++i ) { + for ( int j = rows[i] - i; j < rows[i + 1] - i - 1; j++ ) { + ( (QTableHeader*)verticalHeader() )->swapSections( j, j + i + 1 ); + } + } + + for ( int j = rows[i] - i; j < numRows() - (int)rows.size(); j++) + ( (QTableHeader*)verticalHeader() )->swapSections( j, j + rows.count() ); + + setNumRows( numRows() - rows.count() ); +} + +/*! + Removes column \a col, and deletes all its cells including any + table items and widgets the cells may contain. Also clears the + selection(s). + + \sa removeColumns() hideColumn() insertColumns() removeRow() +*/ + +void QTable::removeColumn( int col ) +{ + if ( col < 0 || col >= numCols() ) + return; + if ( col < numCols() - 1 ) { + if (d->hiddenCols.find(col)) + d->hiddenCols.remove(col); + + for ( int i = col; i < numCols() - 1; ++i ) + ( (QTableHeader*)horizontalHeader() )->swapSections( i, i + 1 ); + } + setNumCols( numCols() - 1 ); +} + +/*! + Removes the columns listed in the array \a cols, and deletes all + their cells including any table items and widgets the cells may + contain. + + The array passed in must only contain valid columns (in the range + from 0 to numCols() - 1) with no duplicates, and must be sorted in + ascending order. Also clears the selection(s). + + \sa removeColumn() insertColumns() removeRows() +*/ + +void QTable::removeColumns( const QMemArray<int> &cols ) +{ + if ( cols.count() == 0 ) + return; + int i; + for ( i = 0; i < (int)cols.count() - 1; ++i ) { + for ( int j = cols[i] - i; j < cols[i + 1] - i - 1; j++ ) { + ( (QTableHeader*)horizontalHeader() )->swapSections( j, j + i + 1 ); + } + } + + for ( int j = cols[i] - i; j < numCols() - (int)cols.size(); j++) + ( (QTableHeader*)horizontalHeader() )->swapSections( j, j + cols.count() ); + + setNumCols( numCols() - cols.count() ); +} + +/*! + Starts editing the cell at \a row, \a col. + + If \a replace is TRUE the content of this cell will be replaced by + the content of the editor when editing is finished, i.e. the user + will be entering new data; otherwise the current content of the + cell (if any) will be modified in the editor. + + \sa beginEdit() +*/ + +void QTable::editCell( int row, int col, bool replace ) +{ + if ( row < 0 || col < 0 || row > numRows() - 1 || col > numCols() - 1 ) + return; + + if ( beginEdit( row, col, replace ) ) { + edMode = Editing; + editRow = row; + editCol = col; + } +} + +#ifndef QT_NO_DRAGANDDROP + +/*! + This event handler is called whenever a QTable object receives a + \l QDragEnterEvent \a e, i.e. when the user pressed the mouse + button to drag something. + + The focus is moved to the cell where the QDragEnterEvent occurred. +*/ + +void QTable::contentsDragEnterEvent( QDragEnterEvent *e ) +{ + oldCurrentRow = curRow; + oldCurrentCol = curCol; + int tmpRow = rowAt( e->pos().y() ); + int tmpCol = columnAt( e->pos().x() ); + fixRow( tmpRow, e->pos().y() ); + fixCol( tmpCol, e->pos().x() ); + if (e->source() != (QObject*)cellWidget( currentRow(), currentColumn() ) ) + setCurrentCell( tmpRow, tmpCol, FALSE, TRUE ); + e->accept(); +} + +/*! + This event handler is called whenever a QTable object receives a + \l QDragMoveEvent \a e, i.e. when the user actually drags the + mouse. + + The focus is moved to the cell where the QDragMoveEvent occurred. +*/ + +void QTable::contentsDragMoveEvent( QDragMoveEvent *e ) +{ + int tmpRow = rowAt( e->pos().y() ); + int tmpCol = columnAt( e->pos().x() ); + fixRow( tmpRow, e->pos().y() ); + fixCol( tmpCol, e->pos().x() ); + if (e->source() != (QObject*)cellWidget( currentRow(), currentColumn() ) ) + setCurrentCell( tmpRow, tmpCol, FALSE, TRUE ); + e->accept(); +} + +/*! + This event handler is called when a drag activity leaves \e this + QTable object with event \a e. +*/ + +void QTable::contentsDragLeaveEvent( QDragLeaveEvent * ) +{ + setCurrentCell( oldCurrentRow, oldCurrentCol, FALSE, TRUE ); +} + +/*! + This event handler is called when the user ends a drag and drop by + dropping something onto \e this QTable and thus triggers the drop + event, \a e. +*/ + +void QTable::contentsDropEvent( QDropEvent *e ) +{ + setCurrentCell( oldCurrentRow, oldCurrentCol, FALSE, TRUE ); + emit dropped( e ); +} + +/*! + If the user presses the mouse on a selected cell, starts moving + (i.e. dragging), and dragEnabled() is TRUE, this function is + called to obtain a drag object. A drag using this object begins + immediately unless dragObject() returns 0. + + By default this function returns 0. You might reimplement it and + create a QDragObject depending on the selected items. + + \sa dropped() +*/ + +QDragObject *QTable::dragObject() +{ + return 0; +} + +/*! + Starts a drag. + + Usually you don't need to call or reimplement this function yourself. + + \sa dragObject(); +*/ + +void QTable::startDrag() +{ + if ( startDragRow == -1 || startDragCol == -1 ) + return; + + startDragRow = startDragCol = -1; + + QDragObject *drag = dragObject(); + if ( !drag ) + return; + + drag->drag(); +} + +#endif + +/*! \reimp */ +void QTable::windowActivationChange( bool oldActive ) +{ + if ( oldActive && autoScrollTimer ) + autoScrollTimer->stop(); + + if ( !isVisible() ) + return; + + if ( palette().active() != palette().inactive() ) + updateContents(); +} + +/*! \reimp */ +void QTable::setEnabled( bool b ) +{ + if ( !b ) { + // editor will lose focus, causing a crash deep in setEnabled(), + // so we'll end the edit early. + endEdit( editRow, editCol, TRUE, edMode != Editing ); + } + QScrollView::setEnabled(b); +} + + +/* + \class QTableHeader + \brief The QTableHeader class allows for creation and manipulation + of table headers. +\if defined(commercial) + It is part of the <a href="commercialeditions.html">Qt Enterprise Edition</a>. +\endif + + \ingroup advanced + \module table + + + QTable uses this subclass of QHeader for its headers. QTable has a + horizontalHeader() for displaying column labels, and a + verticalHeader() for displaying row labels. + +*/ + +/* + \enum QTableHeader::SectionState + + This enum type denotes the state of the header's text + + \value Normal the default + \value Bold + \value Selected typically represented by showing the section "sunken" + or "pressed in" +*/ + +/*! + Creates a new table header called \a name with \a i sections. It + is a child of widget \a parent and attached to table \a t. +*/ + +QTableHeader::QTableHeader( int i, QTable *t, + QWidget *parent, const char *name ) + : QHeader( i, parent, name ), mousePressed(FALSE), startPos(-1), + table( t ), caching( FALSE ), resizedSection(-1), + numStretches( 0 ) +{ + setIsATableHeader( TRUE ); + d = 0; + states.resize( i ); + stretchable.resize( i ); + states.fill( Normal, -1 ); + stretchable.fill( FALSE, -1 ); + autoScrollTimer = new QTimer( this ); + connect( autoScrollTimer, SIGNAL( timeout() ), + this, SLOT( doAutoScroll() ) ); +#ifndef NO_LINE_WIDGET + line1 = new QWidget( table->viewport(), "qt_line1" ); + line1->hide(); + line1->setBackgroundMode( PaletteText ); + table->addChild( line1 ); + line2 = new QWidget( table->viewport(), "qt_line2" ); + line2->hide(); + line2->setBackgroundMode( PaletteText ); + table->addChild( line2 ); +#else + d = new QTableHeaderPrivate; + d->oldLinePos = -1; //outside, in contents coords +#endif + connect( this, SIGNAL( sizeChange(int,int,int) ), + this, SLOT( sectionWidthChanged(int,int,int) ) ); + connect( this, SIGNAL( indexChange(int,int,int) ), + this, SLOT( indexChanged(int,int,int) ) ); + + stretchTimer = new QTimer( this ); + widgetStretchTimer = new QTimer( this ); + connect( stretchTimer, SIGNAL( timeout() ), + this, SLOT( updateStretches() ) ); + connect( widgetStretchTimer, SIGNAL( timeout() ), + this, SLOT( updateWidgetStretches() ) ); + startPos = -1; +} + +/*! + Adds a new section, \a size pixels wide (or high for vertical + headers) with the label \a s. If \a size is negative the section's + size is calculated based on the width (or height) of the label's + text. +*/ + +void QTableHeader::addLabel( const QString &s , int size ) +{ + QHeader::addLabel( s, size ); + if ( count() > (int)states.size() ) { + int s = states.size(); + states.resize( count() ); + stretchable.resize( count() ); + for ( ; s < count(); ++s ) { + states[ s ] = Normal; + stretchable[ s ] = FALSE; + } + } +} + +void QTableHeader::removeLabel( int section ) +{ + QHeader::removeLabel( section ); + if ( section == (int)states.size() - 1 ) { + states.resize( states.size() - 1 ); + stretchable.resize( stretchable.size() - 1 ); + } +} + +void QTableHeader::resizeArrays( int n ) +{ + int old = states.size(); + states.resize( n ); + stretchable.resize( n ); + if ( n > old ) { + for ( int i = old; i < n; ++i ) { + stretchable[ i ] = FALSE; + states[ i ] = Normal; + } + } +} + +void QTableHeader::setLabel( int section, const QString & s, int size ) +{ + QHeader::setLabel( section, s, size ); + sectionLabelChanged( section ); +} + +void QTableHeader::setLabel( int section, const QIconSet & iconset, + const QString & s, int size ) +{ + QHeader::setLabel( section, iconset, s, size ); + sectionLabelChanged( section ); +} + +/*! + Sets the SectionState of section \a s to \a astate. + + \sa sectionState() +*/ + +void QTableHeader::setSectionState( int s, SectionState astate ) +{ + if ( s < 0 || s >= (int)states.count() ) + return; + if ( states.data()[ s ] == astate ) + return; + if ( isRowSelection( table->selectionMode() ) && orientation() == Horizontal ) + return; + + states.data()[ s ] = astate; + if ( isUpdatesEnabled() ) { + if ( orientation() == Horizontal ) + repaint( sectionPos( s ) - offset(), 0, sectionSize( s ), height(), FALSE ); + else + repaint( 0, sectionPos( s ) - offset(), width(), sectionSize( s ), FALSE ); + } +} + +void QTableHeader::setSectionStateToAll( SectionState state ) +{ + if ( isRowSelection( table->selectionMode() ) && orientation() == Horizontal ) + return; + + register int *d = (int *) states.data(); + int n = count(); + + while (n >= 4) { + d[0] = state; + d[1] = state; + d[2] = state; + d[3] = state; + d += 4; + n -= 4; + } + + if (n > 0) { + d[0] = state; + if (n > 1) { + d[1] = state; + if (n > 2) { + d[2] = state; + } + } + } +} + +/*! + Returns the SectionState of section \a s. + + \sa setSectionState() +*/ + +QTableHeader::SectionState QTableHeader::sectionState( int s ) const +{ + return (s < 0 || s >= (int)states.count() ? Normal : (QTableHeader::SectionState)states[s]); +} + +/*! \reimp +*/ + +void QTableHeader::paintEvent( QPaintEvent *e ) +{ + QPainter p( this ); + p.setPen( colorGroup().buttonText() ); + int pos = orientation() == Horizontal + ? e->rect().left() + : e->rect().top(); + int id = mapToIndex( sectionAt( pos + offset() ) ); + if ( id < 0 ) { + if ( pos > 0 ) + return; + else + id = 0; + } + + QRegion reg = e->region(); + for ( int i = id; i < count(); i++ ) { + QRect r = sRect( i ); + reg -= r; + p.save(); + if ( !( orientation() == Horizontal && isRowSelection( table->selectionMode() ) ) && + ( sectionState( i ) == Bold || sectionState( i ) == Selected ) ) { + QFont f( font() ); + f.setBold( TRUE ); + p.setFont( f ); + } + paintSection( &p, i, r ); + p.restore(); + if ( orientation() == Horizontal && r. right() >= e->rect().right() || + orientation() == Vertical && r. bottom() >= e->rect().bottom() ) + return; + } + if ( !reg.isEmpty() ) + erase( reg ); +} + +/*! + \reimp + + Paints the header section with index \a index into the rectangular + region \a fr on the painter \a p. +*/ + +void QTableHeader::paintSection( QPainter *p, int index, const QRect& fr ) +{ + int section = mapToSection( index ); + if ( section < 0 || cellSize( section ) <= 0 ) + return; + + if ( sectionState( index ) != Selected || + orientation() == Horizontal && isRowSelection( table->selectionMode() ) ) { + QHeader::paintSection( p, index, fr ); + } else { + QStyle::SFlags flags = QStyle::Style_Off | ( orient == Horizontal ? QStyle::Style_Horizontal : 0 ); + if(isEnabled()) + flags |= QStyle::Style_Enabled; + if(isClickEnabled()) { + if(sectionState(index) == Selected) { + flags |= QStyle::Style_Down; + if(!mousePressed) + flags |= QStyle::Style_Sunken; + } + } + if(!(flags & QStyle::Style_Down)) + flags |= QStyle::Style_Raised; + style().drawPrimitive( QStyle::PE_HeaderSection, p, QRect(fr.x(), fr.y(), fr.width(), fr.height()), + colorGroup(), flags ); + paintSectionLabel( p, index, fr ); + } +} + +static int real_pos( const QPoint &p, Qt::Orientation o ) +{ + if ( o == Qt::Horizontal ) + return p.x(); + return p.y(); +} + +/*! \reimp +*/ + +void QTableHeader::mousePressEvent( QMouseEvent *e ) +{ + if ( e->button() != LeftButton ) + return; + QHeader::mousePressEvent( e ); + mousePressed = TRUE; + pressPos = real_pos( e->pos(), orientation() ); + if ( !table->currentSel || ( e->state() & ShiftButton ) != ShiftButton ) + startPos = -1; + setCaching( TRUE ); + resizedSection = -1; +#ifdef QT_NO_CURSOR + isResizing = FALSE; +#else + isResizing = cursor().shape() != ArrowCursor; + if ( !isResizing && sectionAt( pressPos ) != -1 ) + doSelection( e ); +#endif +} + +/*! \reimp +*/ + +void QTableHeader::mouseMoveEvent( QMouseEvent *e ) +{ + if ( (e->state() & MouseButtonMask) != LeftButton // Using LeftButton simulates old behavior. +#ifndef QT_NO_CURSOR + || cursor().shape() != ArrowCursor +#endif + || ( ( e->state() & ControlButton ) == ControlButton && + ( orientation() == Horizontal + ? table->columnMovingEnabled() : table->rowMovingEnabled() ) ) ) { + QHeader::mouseMoveEvent( e ); + return; + } + + if ( !doSelection( e ) ) + QHeader::mouseMoveEvent( e ); +} + +bool QTableHeader::doSelection( QMouseEvent *e ) +{ + int p = real_pos( e->pos(), orientation() ) + offset(); + + if ( isRowSelection( table->selectionMode() ) ) { + if ( orientation() == Horizontal ) + return TRUE; + if ( table->selectionMode() == QTable::SingleRow ) { + int secAt = sectionAt( p ); + if ( secAt == -1 ) + return TRUE; + table->setCurrentCell( secAt, table->currentColumn() ); + return TRUE; + } + } + + if ( startPos == -1 ) { + int secAt = sectionAt( p ); + if ( ( e->state() & ControlButton ) != ControlButton && + ( e->state() & ShiftButton ) != ShiftButton || + table->selectionMode() == QTable::Single || + table->selectionMode() == QTable::SingleRow ) { + startPos = p; + bool b = table->signalsBlocked(); + table->blockSignals( TRUE ); + table->clearSelection(); + table->blockSignals( b ); + } + saveStates(); + + if ( table->selectionMode() != QTable::NoSelection ) { + startPos = p; + QTableSelection *oldSelection = table->currentSel; + + if ( orientation() == Vertical ) { + if ( !table->isRowSelected( secAt, TRUE ) ) { + table->currentSel = new QTableSelection(); + table->selections.append( table->currentSel ); + table->currentSel->init( secAt, 0 ); + table->currentSel->expandTo( secAt, table->numCols() - 1 ); + emit table->selectionChanged(); + } + table->setCurrentCell( secAt, 0 ); + } else { // orientation == Horizontal + if ( !table->isColumnSelected( secAt, TRUE ) ) { + table->currentSel = new QTableSelection(); + table->selections.append( table->currentSel ); + table->currentSel->init( 0, secAt ); + table->currentSel->expandTo( table->numRows() - 1, secAt ); + emit table->selectionChanged(); + } + table->setCurrentCell( 0, secAt ); + } + + if ( orientation() == Horizontal && table->isColumnSelected(secAt) || + orientation() == Vertical && table->isRowSelected(secAt)) { + setSectionState( secAt, Selected ); + } + + table->repaintSelections( oldSelection, table->currentSel, + orientation() == Horizontal, + orientation() == Vertical ); + if ( sectionAt( p ) != -1 ) + endPos = p; + + return TRUE; + } + } + + if ( sectionAt( p ) != -1 ) + endPos = p; + if ( startPos != -1 ) { + updateSelections(); + p -= offset(); + if ( orientation() == Horizontal && ( p < 0 || p > width() ) ) { + doAutoScroll(); + autoScrollTimer->start( 100, TRUE ); + } else if ( orientation() == Vertical && ( p < 0 || p > height() ) ) { + doAutoScroll(); + autoScrollTimer->start( 100, TRUE ); + } + return TRUE; + } + return table->selectionMode() == QTable::NoSelection; +} + +static inline bool mayOverwriteMargin( int before, int after ) +{ + /* + 0 is the only user value that we always respect. We also never + shrink a margin, in case the user wanted it that way. + */ + return before != 0 && before < after; +} + +void QTableHeader::sectionLabelChanged( int section ) +{ + emit sectionSizeChanged( section ); + + // this does not really belong here + if ( orientation() == Horizontal ) { + int h = sizeHint().height(); + if ( h != height() && mayOverwriteMargin(table->topMargin(), h) ) + table->setTopMargin( h ); + } else { + int w = sizeHint().width(); + if ( w != width() && mayOverwriteMargin( ( QApplication::reverseLayout() ? table->rightMargin() : table->leftMargin() ), w) ) + table->setLeftMargin( w ); + } +} + +/*! \reimp */ +void QTableHeader::mouseReleaseEvent( QMouseEvent *e ) +{ + if ( e->button() != LeftButton ) + return; + autoScrollTimer->stop(); + mousePressed = FALSE; + setCaching( FALSE ); + QHeader::mouseReleaseEvent( e ); +#ifndef NO_LINE_WIDGET + line1->hide(); + line2->hide(); +#else + if ( d->oldLinePos >= 0 ) + if ( orientation() == Horizontal ) + table->updateContents( d->oldLinePos, table->contentsY(), + 1, table->visibleHeight() ); + else + table->updateContents( table->contentsX(), d->oldLinePos, + table->visibleWidth(), 1 ); + d->oldLinePos = -1; +#endif + if ( resizedSection != -1 ) { + emit sectionSizeChanged( resizedSection ); + updateStretches(); + } + + //Make sure all newly selected sections are painted one last time + QRect selectedRects; + for ( int i = 0; i < count(); i++ ) { + if(sectionState( i ) == Selected) + selectedRects |= sRect( i ); + } + if(!selectedRects.isNull()) + repaint(selectedRects); +} + +/*! \reimp +*/ + +void QTableHeader::mouseDoubleClickEvent( QMouseEvent *e ) +{ + if ( e->button() != LeftButton ) + return; + if ( isResizing ) { + int p = real_pos( e->pos(), orientation() ) + offset(); + int section = sectionAt( p ); + if ( section == -1 ) + return; + section--; + if ( p >= sectionPos( count() - 1 ) + sectionSize( count() - 1 ) ) + ++section; + while ( sectionSize( section ) == 0 ) + section--; + if ( section < 0 ) + return; + int oldSize = sectionSize( section ); + if ( orientation() == Horizontal ) { + table->adjustColumn( section ); + int newSize = sectionSize( section ); + if ( oldSize != newSize ) + emit sizeChange( section, oldSize, newSize ); + for ( int i = 0; i < table->numCols(); ++i ) { + if ( table->isColumnSelected( i ) && sectionSize( i ) != 0 ) + table->adjustColumn( i ); + } + } else { + table->adjustRow( section ); + int newSize = sectionSize( section ); + if ( oldSize != newSize ) + emit sizeChange( section, oldSize, newSize ); + for ( int i = 0; i < table->numRows(); ++i ) { + if ( table->isRowSelected( i ) && sectionSize( i ) != 0 ) + table->adjustRow( i ); + } + } + } +} + +/*! \reimp +*/ + +void QTableHeader::resizeEvent( QResizeEvent *e ) +{ + stretchTimer->stop(); + widgetStretchTimer->stop(); + QHeader::resizeEvent( e ); + if ( numStretches == 0 ) + return; + stretchTimer->start( 0, TRUE ); +} + +void QTableHeader::updateStretches() +{ + if ( numStretches == 0 ) + return; + + int dim = orientation() == Horizontal ? width() : height(); + if ( sectionPos(count() - 1) + sectionSize(count() - 1) == dim ) + return; + int i; + int pd = dim - ( sectionPos(count() - 1) + + sectionSize(count() - 1) ); + bool block = signalsBlocked(); + blockSignals( TRUE ); + for ( i = 0; i < (int)stretchable.count(); ++i ) { + if ( !stretchable[i] || + ( stretchable[i] && table->d->hiddenCols[i] ) ) + continue; + pd += sectionSize( i ); + } + pd /= numStretches; + for ( i = 0; i < (int)stretchable.count(); ++i ) { + if ( !stretchable[i] || + ( stretchable[i] && table->d->hiddenCols[i] ) ) + continue; + if ( i == (int)stretchable.count() - 1 && + sectionPos( i ) + pd < dim ) + pd = dim - sectionPos( i ); + resizeSection( i, QMAX( 20, pd ) ); + } + blockSignals( block ); + table->repaintContents( FALSE ); + widgetStretchTimer->start( 100, TRUE ); +} + +void QTableHeader::updateWidgetStretches() +{ + QSize s = table->tableSize(); + table->resizeContents( s.width(), s.height() ); + for ( int i = 0; i < table->numCols(); ++i ) + table->updateColWidgets( i ); +} + +void QTableHeader::updateSelections() +{ + if ( table->selectionMode() == QTable::NoSelection || + (isRowSelection( table->selectionMode() ) && orientation() != Vertical ) ) + return; + int a = sectionAt( startPos ); + int b = sectionAt( endPos ); + int start = QMIN( a, b ); + int end = QMAX( a, b ); + register int *s = states.data(); + for ( int i = 0; i < count(); ++i ) { + if ( i < start || i > end ) + *s = oldStates.data()[ i ]; + else + *s = Selected; + ++s; + } + repaint( FALSE ); + + if (table->currentSel) { + QTableSelection oldSelection = *table->currentSel; + if ( orientation() == Vertical ) + table->currentSel->expandTo( b, table->horizontalHeader()->count() - 1 ); + else + table->currentSel->expandTo( table->verticalHeader()->count() - 1, b ); + table->repaintSelections( &oldSelection, table->currentSel, + orientation() == Horizontal, + orientation() == Vertical ); + } + emit table->selectionChanged(); +} + +void QTableHeader::saveStates() +{ + oldStates.resize( count() ); + register int *s = states.data(); + register int *s2 = oldStates.data(); + for ( int i = 0; i < count(); ++i ) { + *s2 = *s; + ++s2; + ++s; + } +} + +void QTableHeader::doAutoScroll() +{ + QPoint pos = mapFromGlobal( QCursor::pos() ); + int p = real_pos( pos, orientation() ) + offset(); + if ( sectionAt( p ) != -1 ) + endPos = p; + if ( orientation() == Horizontal ) + table->ensureVisible( endPos, table->contentsY() ); + else + table->ensureVisible( table->contentsX(), endPos ); + updateSelections(); + autoScrollTimer->start( 100, TRUE ); +} + +void QTableHeader::sectionWidthChanged( int col, int, int ) +{ + resizedSection = col; + if ( orientation() == Horizontal ) { +#ifndef NO_LINE_WIDGET + table->moveChild( line1, QHeader::sectionPos( col ) - 1, + table->contentsY() ); + line1->resize( 1, table->visibleHeight() ); + line1->show(); + line1->raise(); + table->moveChild( line2, + QHeader::sectionPos( col ) + QHeader::sectionSize( col ) - 1, + table->contentsY() ); + line2->resize( 1, table->visibleHeight() ); + line2->show(); + line2->raise(); +#else + QPainter p( table->viewport() ); + int lx = QHeader::sectionPos( col ) + QHeader::sectionSize( col ) - 1; + int ly = table->contentsY(); + + if ( lx != d->oldLinePos ) { + QPoint pt = table->contentsToViewport( QPoint( lx, ly ) ); + p.drawLine( pt.x(), pt.y()+1, + pt.x(), pt.y()+ table->visibleHeight() ); + if ( d->oldLinePos >= 0 ) + table->repaintContents( d->oldLinePos, table->contentsY(), + 1, table->visibleHeight() ); + + d->oldLinePos = lx; + } +#endif + } else { +#ifndef NO_LINE_WIDGET + table->moveChild( line1, table->contentsX(), + QHeader::sectionPos( col ) - 1 ); + line1->resize( table->visibleWidth(), 1 ); + line1->show(); + line1->raise(); + table->moveChild( line2, table->contentsX(), + QHeader::sectionPos( col ) + QHeader::sectionSize( col ) - 1 ); + line2->resize( table->visibleWidth(), 1 ); + line2->show(); + line2->raise(); + +#else + QPainter p( table->viewport() ); + int lx = table->contentsX(); + int ly = QHeader::sectionPos( col ) + QHeader::sectionSize( col ) - 1; + + if ( ly != d->oldLinePos ) { + QPoint pt = table->contentsToViewport( QPoint( lx, ly ) ); + p.drawLine( pt.x()+1, pt.y(), + pt.x() + table->visibleWidth(), pt.y() ); + if ( d->oldLinePos >= 0 ) + table->repaintContents( table->contentsX(), d->oldLinePos, + table->visibleWidth(), 1 ); + d->oldLinePos = ly; + } + +#endif + } +} + +/*! + \reimp + + Returns the size of section \a section in pixels or -1 if \a + section is out of range. +*/ + +int QTableHeader::sectionSize( int section ) const +{ + if ( count() <= 0 || section < 0 || section >= count() ) + return -1; + if ( caching && section < (int)sectionSizes.count() ) + return sectionSizes[ section ]; + return QHeader::sectionSize( section ); +} + +/*! + \reimp + + Returns the start position of section \a section in pixels or -1 + if \a section is out of range. + + \sa sectionAt() +*/ + +int QTableHeader::sectionPos( int section ) const +{ + if ( count() <= 0 || section < 0 || section >= count() ) + return -1; + if ( caching && section < (int)sectionPoses.count() ) + return sectionPoses[ section ]; + return QHeader::sectionPos( section ); +} + +/*! + \reimp + + Returns the number of the section at index position \a pos or -1 + if there is no section at the position given. + + \sa sectionPos() +*/ + +int QTableHeader::sectionAt( int pos ) const +{ + if ( !caching || sectionSizes.count() <= 0 || sectionPoses.count() <= 0 ) + return QHeader::sectionAt( pos ); + if ( count() <= 0 || pos > sectionPoses[ count() - 1 ] + sectionSizes[ count() - 1 ] ) + return -1; + int l = 0; + int r = count() - 1; + int i = ( (l+r+1) / 2 ); + while ( r - l ) { + if ( sectionPoses[i] > pos ) + r = i -1; + else + l = i; + i = ( (l+r+1) / 2 ); + } + if ( sectionPoses[i] <= pos && + pos <= sectionPoses[i] + sectionSizes[ mapToSection( i ) ] ) + return mapToSection( i ); + return -1; +} + +void QTableHeader::updateCache() +{ + sectionPoses.resize( count() ); + sectionSizes.resize( count() ); + if ( !caching ) + return; + for ( int i = 0; i < count(); ++i ) { + sectionSizes[ i ] = QHeader::sectionSize( i ); + sectionPoses[ i ] = QHeader::sectionPos( i ); + } +} + +void QTableHeader::setCaching( bool b ) +{ + if ( caching == b ) + return; + caching = b; + sectionPoses.resize( count() ); + sectionSizes.resize( count() ); + if ( b ) { + for ( int i = 0; i < count(); ++i ) { + sectionSizes[ i ] = QHeader::sectionSize( i ); + sectionPoses[ i ] = QHeader::sectionPos( i ); + } + } +} + +/*! + If \a b is TRUE, section \a s is stretchable; otherwise the + section is not stretchable. + + \sa isSectionStretchable() +*/ + +void QTableHeader::setSectionStretchable( int s, bool b ) +{ + if ( stretchable[ s ] == b ) + return; + stretchable[ s ] = b; + if ( b ) + numStretches++; + else + numStretches--; +} + +/*! + Returns TRUE if section \a s is stretcheable; otherwise returns + FALSE. + + \sa setSectionStretchable() +*/ + +bool QTableHeader::isSectionStretchable( int s ) const +{ + return stretchable[ s ]; +} + +void QTableHeader::swapSections( int oldIdx, int newIdx, bool swapTable ) +{ + extern bool qt_qheader_label_return_null_strings; // qheader.cpp + qt_qheader_label_return_null_strings = TRUE; + + QIconSet oldIconSet, newIconSet; + if ( iconSet( oldIdx ) ) + oldIconSet = *iconSet( oldIdx ); + if ( iconSet( newIdx ) ) + newIconSet = *iconSet( newIdx ); + QString oldLabel = label( oldIdx ); + QString newLabel = label( newIdx ); + bool sectionsHasContent = !(oldIconSet.isNull() && newIconSet.isNull() + && oldLabel.isNull() && newLabel.isNull()); + if (sectionsHasContent) { + setLabel( oldIdx, newIconSet, newLabel ); + setLabel( newIdx, oldIconSet, oldLabel ); + } + + qt_qheader_label_return_null_strings = FALSE; + + int w1 = sectionSize( oldIdx ); + int w2 = sectionSize( newIdx ); + if ( w1 != w2 ) { + resizeSection( oldIdx, w2 ); + resizeSection( newIdx, w1 ); + } + + if ( !swapTable ) + return; + if ( orientation() == Horizontal ) + table->swapColumns( oldIdx, newIdx ); + else + table->swapRows( oldIdx, newIdx ); +} + +void QTableHeader::indexChanged( int sec, int oldIdx, int newIdx ) +{ + newIdx = mapToIndex( sec ); + if ( oldIdx > newIdx ) + moveSection( sec, oldIdx + 1 ); + else + moveSection( sec, oldIdx ); + + if ( oldIdx < newIdx ) { + while ( oldIdx < newIdx ) { + swapSections( oldIdx, oldIdx + 1 ); + oldIdx++; + } + } else { + while ( oldIdx > newIdx ) { + swapSections( oldIdx - 1, oldIdx ); + oldIdx--; + } + } + + table->repaintContents( table->contentsX(), table->contentsY(), + table->visibleWidth(), table->visibleHeight() ); +} + +void QTableHeader::setLabels(const QStringList & labels) +{ + int i = 0; + bool updates = isUpdatesEnabled(); + const int c = QMIN(count(), (int)labels.count()); + setUpdatesEnabled(FALSE); + for ( QStringList::ConstIterator it = labels.begin(); i < c; ++i, ++it ) { + if (i == c - 1) { + setUpdatesEnabled(updates); + setLabel( i, *it ); + } else { + QHeader::setLabel( i, *it ); + emit sectionSizeChanged( i ); + } + } +} + +#include "qtable.moc" + +#endif // QT_NO_TABLE |