diff options
author | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-01-20 01:29:50 +0000 |
---|---|---|
committer | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-01-20 01:29:50 +0000 |
commit | 8362bf63dea22bbf6736609b0f49c152f975eb63 (patch) | |
tree | 0eea3928e39e50fae91d4e68b21b1e6cbae25604 /kexi/widget/tableview/kexidataawareobjectiface.cpp | |
download | koffice-8362bf63dea22bbf6736609b0f49c152f975eb63.tar.gz koffice-8362bf63dea22bbf6736609b0f49c152f975eb63.zip |
Added old abandoned KDE3 version of koffice
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/koffice@1077364 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'kexi/widget/tableview/kexidataawareobjectiface.cpp')
-rw-r--r-- | kexi/widget/tableview/kexidataawareobjectiface.cpp | 2108 |
1 files changed, 2108 insertions, 0 deletions
diff --git a/kexi/widget/tableview/kexidataawareobjectiface.cpp b/kexi/widget/tableview/kexidataawareobjectiface.cpp new file mode 100644 index 00000000..59edbed3 --- /dev/null +++ b/kexi/widget/tableview/kexidataawareobjectiface.cpp @@ -0,0 +1,2108 @@ +/* This file is part of the KDE project + Copyright (C) 2005-2007 Jaroslaw Staniek <js@iidea.pl> + + Based on KexiTableView code. + Copyright (C) 2002 Till Busch <till@bux.at> + Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2003 Daniel Molkentin <molkentin@kde.org> + Copyright (C) 2003 Joseph Wenninger <jowenn@kde.org> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "kexidataawareobjectiface.h" + +#include <qscrollview.h> +#include <qlabel.h> +#include <qtooltip.h> + +#include <kmessagebox.h> + +#include <kexi.h> +#include <kexiutils/validator.h> +#include <widget/utils/kexirecordnavigator.h> +#include <widget/utils/kexirecordmarker.h> +#include <kexidb/roweditbuffer.h> +#include <kexidataiteminterface.h> + +#include "kexitableviewheader.h" + +using namespace KexiUtils; + +KexiDataAwareObjectInterface::KexiDataAwareObjectInterface() +{ + m_data = 0; + m_itemIterator = 0; + m_readOnly = -1; //don't know + m_insertingEnabled = -1; //don't know + m_isSortingEnabled = true; + m_isFilteringEnabled = true; + m_deletionPolicy = AskDelete; + m_inside_acceptEditor = false; + m_acceptsRowEditAfterCellAccepting = false; + m_internal_acceptsRowEditAfterCellAccepting = false; + m_contentsMousePressEvent_dblClick = false; + m_navPanel = 0; + m_initDataContentsOnShow = false; + m_cursorPositionSetExplicityBeforeShow = false; + m_verticalHeader = 0; + m_horizontalHeader = 0; + m_insertItem = 0; +// m_rowEditBuffer = 0; + m_spreadSheetMode = false; + m_dropsAtRowEnabled = false; + m_updateEntireRowWhenMovingToOtherRow = false; + m_dragIndicatorLine = -1; + m_emptyRowInsertingEnabled = false; + m_popupMenu = 0; + m_contextMenuEnabled = true; + m_rowWillBeDeleted = -1; + m_alsoUpdateNextRow = false; + m_verticalHeaderAlreadyAdded = false; + m_vScrollBarValueChanged_enabled = true; + m_scrollbarToolTipsEnabled = true; + m_scrollBarTipTimerCnt = 0; + m_scrollBarTip = 0; + m_recentSearchDirection = KexiSearchAndReplaceViewInterface::Options::DefaultSearchDirection; + + // setup scrollbar tooltip and related members + m_scrollBarTip = new QLabel("",0, "vScrollBarToolTip", + Qt::WStyle_Customize |Qt::WStyle_NoBorder|Qt::WX11BypassWM|Qt::WStyle_StaysOnTop|Qt::WStyle_Tool); + m_scrollBarTip->setPalette(QToolTip::palette()); + m_scrollBarTip->setMargin(2); + m_scrollBarTip->setIndent(0); + m_scrollBarTip->setAlignment(Qt::AlignCenter); + m_scrollBarTip->setFrameStyle( QFrame::Plain | QFrame::Box ); + m_scrollBarTip->setLineWidth(1); + + clearVariables(); +} + +KexiDataAwareObjectInterface::~KexiDataAwareObjectInterface() +{ + delete m_insertItem; +// delete m_rowEditBuffer; + delete m_itemIterator; + delete m_scrollBarTip; + //we cannot delete m_data here... subclasses should do this +} + +void KexiDataAwareObjectInterface::clearVariables() +{ + m_editor = 0; +// m_rowEditBuffer = 0; + m_rowEditing = false; + m_newRowEditing = false; + m_curRow = -1; + m_curCol = -1; + m_currentItem = 0; +} + +void KexiDataAwareObjectInterface::setData( KexiTableViewData *data, bool owner ) +{ + const bool theSameData = m_data && m_data==data; + if (m_owner && m_data && m_data!=data/*don't destroy if it's the same*/) { + kexidbg << "KexiDataAwareObjectInterface::setData(): destroying old data (owned)" << endl; + delete m_itemIterator; + delete m_data; //destroy old data + m_data = 0; + m_itemIterator = 0; + } + m_owner = owner; + m_data = data; + if (m_data) + m_itemIterator = m_data->createIterator(); + + kdDebug(44021) << "KexiDataAwareObjectInterface::setData(): using shared data" << endl; + //add columns +//OK? + clearColumnsInternal(false); + if (m_data) { + int i = 0; + for (KexiTableViewColumn::ListIterator it(m_data->columns); + it.current(); ++it, i++) + { + KexiDB::Field *f = it.current()->field(); + if (it.current()->visible()) { + int wid = f->width(); + if (wid==0) + wid=KEXI_DEFAULT_DATA_COLUMN_WIDTH;//default col width in pixels +//! @todo add col width configuration and storage + addHeaderColumn(it.current()->isHeaderTextVisible() + ? it.current()->captionAliasOrName() : QString::null, + f->description(), it.current()->icon(), wid); + } + } + } + if (m_verticalHeader) { + m_verticalHeader->clear(); + if (m_data) + m_verticalHeader->addLabels(m_data->count()); + } + if (m_data && m_data->count()==0) + m_navPanel->setCurrentRecordNumber(0+1); + + if (m_data && !theSameData) { +//! @todo: store sorting settings? + setSorting(-1); +// connect(m_data, SIGNAL(refreshRequested()), this, SLOT(slotRefreshRequested())); + connectToReloadDataSlot(m_data, SIGNAL(reloadRequested())); + QObject* thisObject = dynamic_cast<QObject*>(this); + if (thisObject) { + QObject::connect(m_data, SIGNAL(destroying()), thisObject, SLOT(slotDataDestroying())); + QObject::connect(m_data, SIGNAL(rowsDeleted( const QValueList<int> & )), + thisObject, SLOT(slotRowsDeleted( const QValueList<int> & ))); + QObject::connect(m_data, SIGNAL(aboutToDeleteRow(KexiTableItem&,KexiDB::ResultInfo*,bool)), + thisObject, SLOT(slotAboutToDeleteRow(KexiTableItem&,KexiDB::ResultInfo*,bool))); + QObject::connect(m_data, SIGNAL(rowDeleted()), thisObject, SLOT(slotRowDeleted())); + QObject::connect(m_data, SIGNAL(rowInserted(KexiTableItem*,bool)), + thisObject, SLOT(slotRowInserted(KexiTableItem*,bool))); + QObject::connect(m_data, SIGNAL(rowInserted(KexiTableItem*,uint,bool)), + thisObject, SLOT(slotRowInserted(KexiTableItem*,uint,bool))); //not db-aware + QObject::connect(m_data, SIGNAL(rowRepaintRequested(KexiTableItem&)), + thisObject, SLOT(slotRowRepaintRequested(KexiTableItem&))); + // setup scrollbar's tooltip + QObject::connect(verticalScrollBar(),SIGNAL(sliderReleased()), + thisObject,SLOT(vScrollBarSliderReleased())); + QObject::connect(verticalScrollBar(),SIGNAL(valueChanged(int)), + thisObject,SLOT(vScrollBarValueChanged(int))); + QObject::connect(&m_scrollBarTipTimer,SIGNAL(timeout()), + thisObject,SLOT(scrollBarTipTimeout())); + } + } + + if (!m_data) { +// clearData(); + cancelRowEdit(); + //m_data->clearInternal(); + clearVariables(); + } + else { + if (!m_insertItem) {//first setData() call - add 'insert' item + m_insertItem = m_data->createItem(); //new KexiTableItem(m_data->columns.count()); + } + else {//just reinit + m_insertItem->init(m_data->columns.count()); + } + } + + //update gui mode + m_navPanel->setInsertingEnabled(m_data && isInsertingEnabled()); + if (m_verticalHeader) + m_verticalHeader->showInsertRow(m_data && isInsertingEnabled()); + + initDataContents(); + updateIndicesForVisibleValues(); + + if (m_data) + /*emit*/ dataSet( m_data ); +} + +void KexiDataAwareObjectInterface::initDataContents() +{ + m_editor = 0; +// QSize s(tableSize()); +// resizeContents(s.width(),s.height()); + + m_navPanel->setRecordCount(rows()); + + if (m_data && !m_cursorPositionSetExplicityBeforeShow) { + //set current row: + m_currentItem = 0; + int curRow = -1, curCol = -1; + if (m_data->columnsCount()>0) { + if (rows()>0) { + m_itemIterator->toFirst(); + m_currentItem = **m_itemIterator; + curRow = 0; + curCol = 0; + } + else {//no data + if (isInsertingEnabled()) { + m_currentItem = m_insertItem; + curRow = 0; + curCol = 0; + } + } + } + setCursorPosition(curRow, curCol, true/*force*/); + } + ensureCellVisible(m_curRow, m_curCol); +// updateRowCountInfo(); +// setNavRowCount(rows()); + +//OK? +// updateContents(); + updateWidgetContents(); + + m_cursorPositionSetExplicityBeforeShow = false; + + /*emit*/ dataRefreshed(); +} + +void KexiDataAwareObjectInterface::setSortingEnabled(bool set) +{ + if (m_isSortingEnabled && !set) + setSorting(-1); + m_isSortingEnabled = set; + /*emit*/ reloadActions(); +} + +void KexiDataAwareObjectInterface::setSorting(int col, bool ascending) +{ + if (!m_data || !m_isSortingEnabled) + return; +// d->pTopHeader->setSortIndicator(col, ascending ? Ascending : Descending); + setLocalSortingOrder(col, ascending ? 1 : -1); + m_data->setSorting(col, ascending); +} + +int KexiDataAwareObjectInterface::dataSortedColumn() const +{ + if (m_data && m_isSortingEnabled) + return m_data->sortedColumn(); + return -1; +} + +int KexiDataAwareObjectInterface::dataSortingOrder() const +{ + return m_data ? m_data->sortingOrder() : 0; +} + +bool KexiDataAwareObjectInterface::sort() +{ + if (!m_data || !m_isSortingEnabled) + return false; + + if (rows() < 2) + return true; + + if (!acceptRowEdit()) + return false; + + const int oldRow = m_curRow; + if (m_data->sortedColumn()!=-1) + m_data->sort(); + + //locate current record + if (!m_currentItem) { + m_itemIterator->toFirst(); + m_currentItem = **m_itemIterator; //m_data->first(); + m_curRow = 0; + if (!m_currentItem) + return true; + } + if (m_currentItem != m_insertItem) { + m_curRow = m_data->findRef(m_currentItem); + int jump = m_curRow - oldRow; + if (jump<0) + (*m_itemIterator) -= -jump; + else + (*m_itemIterator) += jump; + } + + updateGUIAfterSorting(); + editorShowFocus( m_curRow, m_curCol ); + if (m_verticalHeader) + m_verticalHeader->setCurrentRow(m_curRow); + if (m_horizontalHeader) + m_horizontalHeader->setSelectedSection(m_curCol); + if (m_navPanel) + m_navPanel->setCurrentRecordNumber(m_curRow+1); + return true; +} + +void KexiDataAwareObjectInterface::sortAscending() +{ + if (currentColumn()<0) + return; + sortColumnInternal( currentColumn(), 1 ); +} + +void KexiDataAwareObjectInterface::sortDescending() +{ + if (currentColumn()<0) + return; + sortColumnInternal( currentColumn(), -1 ); +} + +void KexiDataAwareObjectInterface::sortColumnInternal(int col, int order) +{ + //-select sorting + bool asc; + if (order == 0) {// invert + if (col==dataSortedColumn() && dataSortingOrder()==1) + asc = dataSortingOrder()==-1; //inverse sorting for this column -> descending order + else + asc = true; + } + else + asc = (order==1); + + int prevSortOrder = currentLocalSortingOrder(); + const int prevSortColumn = currentLocalSortingOrder(); + setSorting( col, asc ); + //-perform sorting + if (!sort()) + setLocalSortingOrder(prevSortColumn, prevSortOrder); //this will also remove indicator + //if prevSortColumn==-1 + if (col != prevSortColumn) + /*emit*/ sortedColumnChanged(col); +} + +bool KexiDataAwareObjectInterface::isInsertingEnabled() const +{ + if (isReadOnly()) + return false; + if (m_insertingEnabled == 1 || m_insertingEnabled == 0) + return (bool)m_insertingEnabled; + if (!hasData()) + return true; + return m_data->isInsertingEnabled(); +} + +void KexiDataAwareObjectInterface::setFilteringEnabled(bool set) +{ + m_isFilteringEnabled = set; +} + +bool KexiDataAwareObjectInterface::isDeleteEnabled() const +{ + return (m_deletionPolicy != NoDelete) && !isReadOnly(); +} + +void KexiDataAwareObjectInterface::setDeletionPolicy(DeletionPolicy policy) +{ + m_deletionPolicy = policy; +// updateContextMenu(); +} + +void KexiDataAwareObjectInterface::setReadOnly(bool set) +{ + if (isReadOnly() == set || (m_data && m_data->isReadOnly() && !set)) + return; //not allowed! + m_readOnly = (set ? 1 : 0); + if (set) + setInsertingEnabled(false); + updateWidgetContents(); + /*emit*/ reloadActions(); +} + +bool KexiDataAwareObjectInterface::isReadOnly() const +{ + if (!hasData()) + return true; + if (m_readOnly == 1 || m_readOnly == 0) + return (bool)m_readOnly; + if (!hasData()) + return true; + return m_data->isReadOnly(); +} + +void KexiDataAwareObjectInterface::setInsertingEnabled(bool set) +{ + if (isInsertingEnabled() == set || (m_data && !m_data->isInsertingEnabled() && set)) + return; //not allowed! + m_insertingEnabled = (set ? 1 : 0); + m_navPanel->setInsertingEnabled(set); + if (m_verticalHeader) + m_verticalHeader->showInsertRow(set); + if (set) + setReadOnly(false); +// update(); + updateWidgetContents(); + /*emit*/ reloadActions(); +} + +void KexiDataAwareObjectInterface::setSpreadSheetMode() +{ + m_spreadSheetMode = true; + setSortingEnabled( false ); + setInsertingEnabled( false ); + setAcceptsRowEditAfterCellAccepting( true ); + setFilteringEnabled( false ); + setEmptyRowInsertingEnabled( true ); + m_navPanelEnabled = false; +} + +void KexiDataAwareObjectInterface::selectNextRow() +{ + selectRow( QMIN( rows() - 1 +(isInsertingEnabled()?1:0), m_curRow + 1 ) ); +} + +void KexiDataAwareObjectInterface::selectPrevPage() +{ + selectRow( + QMAX( 0, m_curRow - rowsPerPage() ) + ); +} + +void KexiDataAwareObjectInterface::selectNextPage() +{ + selectRow( + QMIN( + rows() - 1 + (isInsertingEnabled()?1:0), + m_curRow + rowsPerPage() + ) + ); +} + +void KexiDataAwareObjectInterface::selectFirstRow() +{ + selectRow(0); +} + +void KexiDataAwareObjectInterface::selectLastRow() +{ +// selectRow(rows() - 1 + (isInsertingEnabled()?1:0)); + selectRow(rows() - 1); +} + +void KexiDataAwareObjectInterface::selectRow(int row) +{ + m_vScrollBarValueChanged_enabled = false; //disable tooltip + setCursorPosition(row, -1); + m_vScrollBarValueChanged_enabled = true; +} + +void KexiDataAwareObjectInterface::selectPrevRow() +{ + selectRow( QMAX( 0, m_curRow - 1 ) ); +} + +void KexiDataAwareObjectInterface::clearSelection() +{ +// selectRow( -1 ); + int oldRow = m_curRow; +// int oldCol = m_curCol; + m_curRow = -1; + m_curCol = -1; + m_currentItem = 0; + updateRow( oldRow ); + m_navPanel->setCurrentRecordNumber(0); +// setNavRowNumber(-1); +} + +void KexiDataAwareObjectInterface::setCursorPosition(int row, int col/*=-1*/, bool forceSet) +{ + int newrow = row; + int newcol = col; + + if(rows() <= 0) { + if (m_verticalHeader) + m_verticalHeader->setCurrentRow(-1); + if (m_horizontalHeader) + m_horizontalHeader->setSelectedSection(-1); + if (isInsertingEnabled()) { + m_currentItem=m_insertItem; + newrow=0; + if (col>=0) + newcol=col; + else + newcol=0; + } + else { + m_currentItem=0; + m_curRow=-1; + m_curCol=-1; + return; + } + } + + if(col>=0) + { + newcol = QMAX(0, col); + newcol = QMIN(columns() - 1, newcol); + } + else { + newcol = m_curCol; //no changes + newcol = QMAX(0, newcol); //may not be < 0 ! + } + newrow = QMAX(0, row); + newrow = QMIN(rows() - 1 + (isInsertingEnabled()?1:0), newrow); + +// d->pCurrentItem = itemAt(d->curRow); +// kdDebug(44021) << "setCursorPosition(): d->curRow=" << d->curRow << " oldRow=" << oldRow << " d->curCol=" << d->curCol << " oldCol=" << oldCol << endl; + + if ( forceSet || m_curRow != newrow || m_curCol != newcol ) + { + kexidbg << "setCursorPosition(): " <<QString("old:%1,%2 new:%3,%4").arg(m_curCol) + .arg(m_curRow).arg(newcol).arg(newrow) << endl; + + // cursor moved: get rid of editor + if (m_editor) { + if (!m_contentsMousePressEvent_dblClick) { + if (!acceptEditor()) { + return; + } + //update row num. again + newrow = QMIN( rows() - 1 + (isInsertingEnabled()?1:0), newrow); + } + } + if (m_errorMessagePopup) { + m_errorMessagePopup->close(); + } + + if (m_curRow != newrow || forceSet) {//update current row info + m_navPanel->setCurrentRecordNumber(newrow+1); +// setNavRowNumber(newrow); +// d->navBtnPrev->setEnabled(newrow>0); +// d->navBtnFirst->setEnabled(newrow>0); +// d->navBtnNext->setEnabled(newrow<(rows()-1+(isInsertingEnabled()?1:0))); +// d->navBtnLast->setEnabled(newrow!=(rows()-1)); + } + + // cursor moved to other row: end of row editing + bool newRowInserted = false; + if (m_rowEditing && m_curRow != newrow) { + newRowInserted = m_newRowEditing; + if (!acceptRowEdit()) { + //accepting failed: cancel setting the cursor + return; + } + //update row number, because number of rows changed + newrow = QMIN( rows() - 1 + (isInsertingEnabled()?1:0), newrow); + + m_navPanel->setCurrentRecordNumber(newrow+1); //refresh + } + + //change position + int oldRow = m_curRow; + int oldCol = m_curCol; + m_curRow = newrow; + m_curCol = newcol; + +// int cw = columnWidth( d->curCol ); +// int rh = rowHeight(); +// ensureVisible( columnPos( d->curCol ) + cw / 2, rowPos( d->curRow ) + rh / 2, cw / 2, rh / 2 ); +// center(columnPos(d->curCol) + cw / 2, rowPos(d->curRow) + rh / 2, cw / 2, rh / 2); +// kdDebug(44021) << " contentsY() = "<< contentsY() << endl; + +//js if (oldRow > d->curRow) +//js ensureVisible(columnPos(d->curCol), rowPos(d->curRow) + rh, columnWidth(d->curCol), rh); +//js else// if (oldRow <= d->curRow) +//js ensureVisible(columnPos(d->curCol), rowPos(d->curRow), columnWidth(d->curCol), rh); + + + //show editor-dependent focus, if we're changing the current column + if (oldCol>=0 && oldCol<columns() && m_curCol!=oldCol) { + //find the editor for this column + KexiDataItemInterface *edit = editor( oldCol ); + if (edit) { + edit->hideFocus(); + } + } + + // position changed, so subsequent searching should be started from scratch + // (e.g. from the current cell or the top-left cell) + m_positionOfRecentlyFoundValue.exists = false; + + //show editor-dependent focus, if needed + editorShowFocus( m_curRow, m_curCol ); + + if (m_updateEntireRowWhenMovingToOtherRow) + updateRow( oldRow ); + else + updateCell( oldRow, oldCol ); + +// //quite clever: ensure the cell is visible: +// ensureCellVisible(m_curRow, m_curCol); + +// QPoint pcenter = QRect( columnPos(d->curCol), rowPos(d->curRow), columnWidth(d->curCol), rh).center(); +// ensureVisible(pcenter.x(), pcenter.y(), columnWidth(d->curCol)/2, rh/2); + +// ensureVisible(columnPos(d->curCol), rowPos(d->curRow) - contentsY(), columnWidth(d->curCol), rh); + if (m_verticalHeader && (oldRow != m_curRow || forceSet)) + m_verticalHeader->setCurrentRow(m_curRow); + + if (m_updateEntireRowWhenMovingToOtherRow) + updateRow( m_curRow ); + else + updateCell( m_curRow, m_curCol ); + + if (m_curCol != oldCol || m_curRow != oldRow || forceSet) {//ensure this is also refreshed + if (!m_updateEntireRowWhenMovingToOtherRow) //only if entire row has not been updated + updateCell( oldRow, m_curCol ); + } + //update row + if (forceSet || m_curRow != oldRow) { + if (isInsertingEnabled() && m_curRow == rows()) { + kdDebug(44021) << "NOW insert item is current" << endl; + m_currentItem = m_insertItem; + } + else { + kdDebug(44021) << QString("NOW item at %1 (%2) is current") + .arg(m_curRow).arg((ulong)itemAt(m_curRow)) << endl; + //NOT EFFECTIVE!!!!!!!!!!! + //set item iterator + if (!newRowInserted && isInsertingEnabled() && m_currentItem == m_insertItem && m_curRow == (rows()-1)) { + //moving from 'insert item' to last item + m_itemIterator->toLast(); + } + else if (!newRowInserted && !forceSet && m_currentItem != m_insertItem && 0==m_curRow) + m_itemIterator->toFirst(); + else if (!newRowInserted && !forceSet && m_currentItem != m_insertItem && oldRow>=0 && (oldRow+1)==m_curRow) //just move next + ++(*m_itemIterator); + else if (!newRowInserted && !forceSet && m_currentItem != m_insertItem && oldRow>=0 && (oldRow-1)==m_curRow) //just move back + --(*m_itemIterator); + else { //move at: + m_itemIterator->toFirst(); + (*m_itemIterator)+=m_curRow; + } + if (!**m_itemIterator) { //sanity + m_itemIterator->toFirst(); + (*m_itemIterator)+=m_curRow; + } + m_currentItem = **m_itemIterator; + //itemAt(m_curRow); + } + } + + //quite clever: ensure the cell is visible: + ensureCellVisible(m_curRow, m_curCol); + + if (m_horizontalHeader && (oldCol != m_curCol || forceSet)) + m_horizontalHeader->setSelectedSection(m_curCol); + + /*emit*/ itemSelected(m_currentItem); + /*emit*/ cellSelected(m_curCol, m_curRow); + /* only needed for forms */ + selectCellInternal(); + } + else { + kexidbg << "setCursorPosition(): NO CHANGE" << endl; + } + + if(m_initDataContentsOnShow) { + m_cursorPositionSetExplicityBeforeShow = true; + } +} + +bool KexiDataAwareObjectInterface::acceptRowEdit() +{ + if (!m_rowEditing || /*sanity*/!m_data->rowEditBuffer()) + return true; + if (m_inside_acceptEditor) { + m_internal_acceptsRowEditAfterCellAccepting = true; + return true; + } + m_internal_acceptsRowEditAfterCellAccepting = false; + + const int columnEditedBeforeAccepting = m_editor ? currentColumn() : -1; + if (!acceptEditor()) + return false; + kdDebug() << "EDIT ROW ACCEPTING..." << endl; + + bool success = true; +// bool allow = true; +// int faultyColumn = -1; // will be !=-1 if cursor has to be moved to that column + const bool inserting = m_newRowEditing; +// QString msg, desc; +// bool inserting = d->pInsertItem && d->pInsertItem==d->pCurrentItem; + + if (m_data->rowEditBuffer()->isEmpty() && !m_newRowEditing) { +/* if (d->newRowEditing) { + cancelRowEdit(); + kdDebug() << "-- NOTHING TO INSERT!!!" << endl; + return true; + } + else {*/ + kdDebug() << "-- NOTHING TO ACCEPT!!!" << endl; +// } + } + else {//not empty edit buffer or new row to insert: + if (m_newRowEditing) { +// emit aboutToInsertRow(d->pCurrentItem, m_data->rowEditBuffer(), success, &faultyColumn); +// if (success) { + kdDebug() << "-- INSERTING: " << endl; + m_data->rowEditBuffer()->debug(); + success = m_data->saveNewRow(*m_currentItem); +// if (!success) { +// } +// } + } + else { +// emit aboutToUpdateRow(d->pCurrentItem, m_data->rowEditBuffer(), success, &faultyColumn); + if (success) { + //accept changes for this row: + kdDebug() << "-- UPDATING: " << endl; + m_data->rowEditBuffer()->debug(); + kdDebug() << "-- BEFORE: " << endl; + m_currentItem->debug(); + success = m_data->saveRowChanges(*m_currentItem);//, &msg, &desc, &faultyColumn); + kdDebug() << "-- AFTER: " << endl; + m_currentItem->debug(); + +// if (!success) { +// } + } + } + } + + if (success) { + //editing is finished: + if (m_newRowEditing) { + //update current-item-iterator + m_itemIterator->toLast(); + m_currentItem = **m_itemIterator; + } + m_rowEditing = false; + m_newRowEditing = false; + //indicate on the vheader that we are not editing + if (m_verticalHeader) + m_verticalHeader->setEditRow(-1); + + updateAfterAcceptRowEdit(); + + kdDebug() << "EDIT ROW ACCEPTED:" << endl; +// /*debug*/itemAt(m_curRow); + + if (inserting) { +// emit rowInserted(d->pCurrentItem); + //update navigator's data + m_navPanel->setRecordCount(rows()); + } + else { +// emit rowUpdated(d->pCurrentItem); + } + + /*emit*/ rowEditTerminated(m_curRow); + } + else { +// if (!allow) { +// kdDebug() << "INSERT/EDIT ROW - DISALLOWED by signal!" << endl; +// } +// else { +// kdDebug() << "EDIT ROW - ERROR!" << endl; +// } + int faultyColumn = -1; + if (m_data->result()->column >= 0 && m_data->result()->column < columns()) + faultyColumn = m_data->result()->column; + else if (columnEditedBeforeAccepting >= 0) + faultyColumn = columnEditedBeforeAccepting; + if (faultyColumn >= 0) { + setCursorPosition(m_curRow, faultyColumn); + } + + const int button = showErrorMessageForResult( m_data->result() ); + if (KMessageBox::No == button) { + //discard changes + cancelRowEdit(); + } + else { + if (faultyColumn >= 0) { + //edit this cell + startEditCurrentCell(); + } + } + } + + return success; +} + +bool KexiDataAwareObjectInterface::cancelRowEdit() +{ + if (!hasData()) + return false; + if (!m_rowEditing) + return false; + cancelEditor(); + m_rowEditing = false; + //indicate on the vheader that we are not editing + if (m_verticalHeader) + m_verticalHeader->setEditRow(-1); + m_alsoUpdateNextRow = m_newRowEditing; + if (m_newRowEditing) { + m_newRowEditing = false; + //remove current edited row (it is @ the end of list) + m_data->removeLast(); + //current item is now empty, last row + m_currentItem = m_insertItem; + //update visibility + if (m_verticalHeader) + m_verticalHeader->removeLabel(false); //-1 label +// updateContents(columnPos(0), rowPos(rows()), +// viewport()->width(), d->rowHeight*3 + (m_navPanel ? m_navPanel->height() : 0)*3 ); +// updateContents(); //js: above did not work well so we do that dirty + updateWidgetContents(); +//TODO: still doesn't repaint properly!! +// QSize s(tableSize()); +// resizeContents(s.width(), s.height()); + updateWidgetContentsSize(); +// m_verticalHeader->update(); + //--no cancel action is needed for datasource, + // because the row was not yet stored. + } + + m_data->clearRowEditBuffer(); + updateAfterCancelRowEdit(); + +//! \todo (js): cancel changes for this row! + kexidbg << "EDIT ROW CANCELLED." << endl; + + /*emit*/ rowEditTerminated(m_curRow); + return true; +} + +void KexiDataAwareObjectInterface::updateAfterCancelRowEdit() +{ + updateRow(m_curRow); + if (m_alsoUpdateNextRow) + updateRow(m_curRow+1); + m_alsoUpdateNextRow = false; +} + +void KexiDataAwareObjectInterface::updateAfterAcceptRowEdit() +{ + updateRow(m_curRow); +} + +void KexiDataAwareObjectInterface::removeEditor() +{ + if (!m_editor) + return; + m_editor->hideWidget(); + m_editor = 0; +} + +bool KexiDataAwareObjectInterface::cancelEditor() +{ + if (m_errorMessagePopup) { + m_errorMessagePopup->close(); + } + if (!m_editor) + return false; + removeEditor(); + return true; +} + +//! @internal +class KexiDataAwareObjectInterfaceToolTip : public QToolTip { + public: + KexiDataAwareObjectInterfaceToolTip( const QString & text, const QPoint & pos, QWidget * widget ) + : QToolTip(widget), m_text(text) + { + tip( QRect(pos, QSize(100, 100)), text ); + } + virtual void maybeTip(const QPoint & p) { + tip( QRect(p, QSize(100, 100)), m_text); + } + QString m_text; +}; + +bool KexiDataAwareObjectInterface::acceptEditor() +{ + if (!hasData()) + return true; + if (!m_editor || m_inside_acceptEditor) + return true; + + m_inside_acceptEditor = true;//avoid recursion + + QVariant newval; + Validator::Result res = Validator::Ok; + QString msg, desc; + bool setNull = false; +// bool allow = true; +// static const QString msg_NOT_NULL = i18n("\"%1\" column requires a value to be entered."); + + //autoincremented field can be omitted (left as null or empty) if we're inserting a new row + const bool autoIncColumnCanBeOmitted = m_newRowEditing && m_editor->field()->isAutoIncrement(); +// const bool autoIncColumnCanBeOmitted = m_newRowEditing && m_editor->columnInfo()->field->isAutoIncrement(); + + bool valueChanged = m_editor->valueChanged(); + bool editCurrentCellAgain = false; + + if (valueChanged) { + if (!m_editor->valueIsValid()) { + //used e.g. for date or time values - the value can be null but not necessary invalid + res = Validator::Error; + editCurrentCellAgain = true; + QWidget *par = dynamic_cast<QScrollView*>(this) ? dynamic_cast<QScrollView*>(this)->viewport() : + dynamic_cast<QWidget*>(this); + QWidget *edit = dynamic_cast<QWidget*>(m_editor); + if (par && edit) { +//! @todo allow displaying user-defined warning +//! @todo also use for other error messages + if (!m_errorMessagePopup) { +// m_errorMessagePopup->close(); + m_errorMessagePopup = new KexiArrowTip( + i18n("Error: %1").arg(m_editor->columnInfo()->field->typeName())+"?", + dynamic_cast<QWidget*>(this)); + m_errorMessagePopup->move( + par->mapToGlobal(edit->pos()) + QPoint(6, edit->height() + 0) ); + m_errorMessagePopup->show(); + } + m_editor->setFocus(); + } + } + else if (m_editor->valueIsNull()) {//null value entered +// if (m_editor->columnInfo()->field->isNotNull() && !autoIncColumnCanBeOmitted) { + if (m_editor->field()->isNotNull() && !autoIncColumnCanBeOmitted) { + kdDebug() << "KexiDataAwareObjectInterface::acceptEditor(): NULL NOT ALLOWED!" << endl; + res = Validator::Error; +// msg = Validator::msgColumnNotEmpty().arg(m_editor->columnInfo()->field->captionOrName()) + msg = Validator::msgColumnNotEmpty().arg(m_editor->field()->captionOrName()) + + "\n\n" + Kexi::msgYouCanImproveData(); + desc = i18n("The column's constraint is declared as NOT NULL."); + editCurrentCellAgain = true; + // allow = false; + // removeEditor(); + // return true; + } + else { + kdDebug() << "KexiDataAwareObjectInterface::acceptEditor(): NULL VALUE WILL BE SET" << endl; + //ok, just leave newval as NULL + setNull = true; + } + } + else if (m_editor->valueIsEmpty()) {//empty value entered +// if (m_editor->columnInfo()->field->hasEmptyProperty()) { + if (m_editor->field()->hasEmptyProperty()) { +// if (m_editor->columnInfo()->field->isNotEmpty() && !autoIncColumnCanBeOmitted) { + if (m_editor->field()->isNotEmpty() && !autoIncColumnCanBeOmitted) { + kdDebug() << "KexiDataAwareObjectInterface::acceptEditor(): EMPTY NOT ALLOWED!" << endl; + res = Validator::Error; +// msg = Validator::msgColumnNotEmpty().arg(m_editor->columnInfo()->field->captionOrName()) + msg = Validator::msgColumnNotEmpty().arg(m_editor->field()->captionOrName()) + + "\n\n" + Kexi::msgYouCanImproveData(); + desc = i18n("The column's constraint is declared as NOT EMPTY."); + editCurrentCellAgain = true; + // allow = false; + // removeEditor(); + // return true; + } + else { + kdDebug() << "KexiDataAwareObjectInterface::acceptEditor(): EMPTY VALUE WILL BE SET" << endl; + } + } + else { +// if (m_editor->columnInfo()->field->isNotNull() && !autoIncColumnCanBeOmitted) { + if (m_editor->field()->isNotNull() && !autoIncColumnCanBeOmitted) { + kdDebug() << "KexiDataAwareObjectInterface::acceptEditor(): NEITHER NULL NOR EMPTY VALUE CAN BE SET!" << endl; + res = Validator::Error; +// msg = Validator::msgColumnNotEmpty().arg(m_editor->columnInfo()->field->captionOrName()) + msg = Validator::msgColumnNotEmpty().arg(m_editor->field()->captionOrName()) + + "\n\n" + Kexi::msgYouCanImproveData(); + desc = i18n("The column's constraint is declared as NOT EMPTY and NOT NULL."); + editCurrentCellAgain = true; +// allow = false; + // removeEditor(); + // return true; + } + else { + kdDebug() << "KexiDataAwareObjectInterface::acceptEditor(): NULL VALUE WILL BE SET BECAUSE EMPTY IS NOT ALLOWED" << endl; + //ok, just leave newval as NULL + setNull = true; + } + } + } + }//changed + + const int realFieldNumber = fieldNumberForColumn(m_curCol); + if (realFieldNumber < 0) { + kdWarning() << "KexiDataAwareObjectInterface::acceptEditor(): fieldNumberForColumn(m_curCol) < 0" << endl; + m_inside_acceptEditor = false; + return false; + } + + KexiTableViewColumn *currentTVColumn = column(m_curCol); + + //try to get the value entered: + if (res == Validator::Ok) { + if ((!setNull && !valueChanged) + || (m_editor->field()->type()!=KexiDB::Field::Boolean && setNull && m_currentItem->at( realFieldNumber ).isNull())) { + kdDebug() << "KexiDataAwareObjectInterface::acceptEditor(): VALUE NOT CHANGED." << endl; + removeEditor(); + if (m_acceptsRowEditAfterCellAccepting || m_internal_acceptsRowEditAfterCellAccepting) + acceptRowEdit(); + m_inside_acceptEditor = false; + return true; + } + if (!setNull) {//get the new value +// bool ok; + newval = m_editor->value(); +//! @todo validation rules for this value? +/* + if (!ok) { + kdDebug() << "KexiDataAwareObjectInterface::acceptEditor(): INVALID VALUE - NOT CHANGED." << endl; + res = KexiValidator::Error; +//js: TODO get detailed info on why m_editor->value() failed + msg = i18n("Entered value is invalid.") + + "\n\n" + KexiValidator::msgYouCanImproveData(); + editCurrentCellAgain = true; +// removeEditor(); +// return true; + }*/ + } + + //Check other validation rules: + //1. check using validator +// KexiValidator *validator = m_data->column(m_curCol)->validator(); + Validator *validator = currentTVColumn->validator(); + if (validator) { +// res = validator->check(m_data->column(m_curCol)->field()->captionOrName(), + res = validator->check(currentTVColumn->field()->captionOrName(), + newval, msg, desc); + } + } + + //show the validation result if not OK: + if (res == Validator::Error) { + if (!msg.isEmpty()) { + if (desc.isEmpty()) + KMessageBox::sorry(dynamic_cast<QWidget*>(this), msg); + else + KMessageBox::detailedSorry(dynamic_cast<QWidget*>(this), msg, desc); + } + editCurrentCellAgain = true; +// allow = false; + } + else if (res == Validator::Warning) { + //js: todo: message!!! + KMessageBox::messageBox(dynamic_cast<QWidget*>(this), KMessageBox::Sorry, msg + "\n" + desc); + editCurrentCellAgain = true; + } + + if (res == Validator::Ok) { + //2. check using signal + //bool allow = true; +// emit aboutToChangeCell(d->pCurrentItem, newval, allow); +// if (allow) { + //send changes to the backend + QVariant visibleValue; + if (!newval.isNull()/* visible value should be null if value is null */ + && currentTVColumn->visibleLookupColumnInfo) + { + visibleValue = m_editor->visibleValue(); //visible value for lookup field + } + //should be also added to the buffer + if (m_data->updateRowEditBufferRef(m_currentItem, m_curCol, currentTVColumn, + newval, /*allowSignals*/true, currentTVColumn->visibleLookupColumnInfo ? &visibleValue : 0)) + { + kdDebug() << "KexiDataAwareObjectInterface::acceptEditor(): ------ EDIT BUFFER CHANGED TO:" << endl; + m_data->rowEditBuffer()->debug(); + } else { + kdDebug() << "KexiDataAwareObjectInterface::acceptEditor(): ------ CHANGE FAILED in KexiDataAwareObjectInterface::updateRowEditBuffer()" << endl; + res = Validator::Error; + + //now: there might be called cancelEditor() in updateRowEditBuffer() handler, + //if this is true, d->pEditor is NULL. + + if (m_editor && m_data->result()->column>=0 && m_data->result()->column<columns()) { + //move to faulty column (if m_editor is not cleared) + setCursorPosition(m_curRow, m_data->result()->column); + } + if (!m_data->result()->msg.isEmpty()) { + const int button = showErrorMessageForResult( m_data->result() ); + if (KMessageBox::No == button) { + //discard changes + cancelEditor(); + if (m_acceptsRowEditAfterCellAccepting) + cancelRowEdit(); + m_inside_acceptEditor = false; + return false; + } + } + } + } + + if (res == Validator::Ok) { + removeEditor(); + /*emit*/ itemChanged(m_currentItem, m_curRow, m_curCol, + m_currentItem->at( realFieldNumber )); + /*emit*/ itemChanged(m_currentItem, m_curRow, m_curCol); + } + m_inside_acceptEditor = false; + if (res == Validator::Ok) { + if (m_acceptsRowEditAfterCellAccepting || m_internal_acceptsRowEditAfterCellAccepting) + acceptRowEdit(); + return true; + } + if (m_editor) { + //allow to edit the cell again, (if m_pEditor is not cleared) + + if (m_editor->hasFocusableWidget()) { + m_editor->showWidget(); + m_editor->setFocus(); + } +// startEditCurrentCell(newval.type()==QVariant::String ? newval.toString() : QString::null); +// m_editor->setFocus(); + } + return false; +} + +void KexiDataAwareObjectInterface::startEditCurrentCell(const QString &setText) +{ + kdDebug() << "** KexiDataAwareObjectInterface::startEditCurrentCell("<<setText<<")"<<endl; +// if (columnType(d->curCol) == KexiDB::Field::Boolean) +// return; + if (isReadOnly() || !columnEditable(m_curCol)) + return; + if (m_editor) { + if (m_editor->hasFocusableWidget()) { + m_editor->showWidget(); + m_editor->setFocus(); + } + } +// ensureVisible(columnPos(m_curCol), rowPos(m_curRow)+rowHeight(), +// columnWidth(m_curCol), rowHeight()); +//OK? + //ensureCellVisible(m_curRow+1, m_curCol); + if (!m_editor) + createEditor(m_curRow, m_curCol, setText, !setText.isEmpty()); +} + +void KexiDataAwareObjectInterface::deleteAndStartEditCurrentCell() +{ + if (isReadOnly() || !columnEditable(m_curCol)) + return; + if (m_editor) {//if we've editor - just clear it + m_editor->clear(); + return; + } +//js if (columnType(m_curCol) == KexiDB::Field::Boolean) +//js return; +// ensureVisible(columnPos(m_curCol), rowPos(m_curRow) + rowHeight(), +// columnWidth(m_curCol), rowHeight()); +//OK? + ensureCellVisible(m_curRow+1, m_curCol); + createEditor(m_curRow, m_curCol, QString::null, false/*removeOld*/); + if (!m_editor) + return; + m_editor->clear(); + if (m_editor->acceptEditorAfterDeleteContents()) + acceptEditor(); + if (!m_editor || !m_editor->hasFocusableWidget()) + updateCell(m_curRow, m_curCol); +} + +void KexiDataAwareObjectInterface::deleteCurrentRow() +{ + if (m_newRowEditing) {//we're editing fresh new row: just cancel this! + cancelRowEdit(); + return; + } + + if (!acceptRowEdit()) + return; + + if (!isDeleteEnabled() || !m_currentItem || m_currentItem == m_insertItem) + return; + switch (m_deletionPolicy) { + case NoDelete: + return; + case ImmediateDelete: + break; + case AskDelete: + if (KMessageBox::Cancel == KMessageBox::warningContinueCancel(dynamic_cast<QWidget*>(this), + i18n("Do you want to delete selected row?"), 0, + KGuiItem(i18n("&Delete Row"),"editdelete"), + "dontAskBeforeDeleteRow"/*config entry*/, + KMessageBox::Notify|KMessageBox::Dangerous)) + return; + break; + case SignalDelete: + /*emit*/ itemDeleteRequest(m_currentItem, m_curRow, m_curCol); + /*emit*/ currentItemDeleteRequest(); + return; + default: + return; + } + + if (!deleteItem(m_currentItem)) {//nothing + } +} + +KexiTableItem *KexiDataAwareObjectInterface::insertEmptyRow(int row) +{ + if ( !acceptRowEdit() || !isEmptyRowInsertingEnabled() + || (row!=-1 && row >= ((int)rows()+(isInsertingEnabled()?1:0) ) ) ) + return 0; + + KexiTableItem *newItem = m_data->createItem(); //new KexiTableItem(m_data->columns.count()); + insertItem(newItem, row); + return newItem; +} + +void KexiDataAwareObjectInterface::insertItem(KexiTableItem *newItem, int row) +{ + const bool changeCurrentRow = row==-1 || row==m_curRow; + if (changeCurrentRow) { + //change current row + row = (m_curRow >= 0 ? m_curRow : 0); + m_currentItem = newItem; + m_curRow = row; + } + else if (m_curRow >= row) { + m_curRow++; + } + + m_data->insertRow(*newItem, row, true /*repaint*/); + + if (changeCurrentRow) { + //update iter... + m_itemIterator->toFirst(); + (*m_itemIterator)+=m_curRow; + } +/* + QSize s(tableSize()); + resizeContents(s.width(),s.height()); + + //redraw only this row and below: + int leftcol = d->pTopHeader->sectionAt( d->pTopHeader->offset() ); +// updateContents( columnPos( leftcol ), rowPos(d->curRow), +// clipper()->width(), clipper()->height() - (rowPos(d->curRow) - contentsY()) ); + updateContents( columnPos( leftcol ), rowPos(row), + clipper()->width(), clipper()->height() - (rowPos(row) - contentsY()) ); + + m_verticalHeader->addLabel(); + + //update navigator's data + setNavRowCount(rows()); + + if (d->curRow >= row) { + //update + editorShowFocus( d->curRow, d->curCol ); + } + */ +} + +void KexiDataAwareObjectInterface::slotRowInserted(KexiTableItem *item, bool repaint) +{ + int row = m_data->findRef(item); + slotRowInserted( item, row, repaint ); +} + +void KexiDataAwareObjectInterface::slotRowInserted(KexiTableItem * /*item*/, uint row, bool repaint) +{ + if (repaint && (int)row<rows()) { + updateWidgetContentsSize(); + +/* updateAllVisibleRowsBelow() used instead + //redraw only this row and below: + int leftcol = d->pTopHeader->sectionAt( d->pTopHeader->offset() ); + updateContents( columnPos( leftcol ), rowPos(row), + clipper()->width(), clipper()->height() - (rowPos(row) - contentsY()) ); +*/ + updateAllVisibleRowsBelow(row); + + if (!m_verticalHeaderAlreadyAdded) { + if (m_verticalHeader) + m_verticalHeader->addLabel(); + } + else //it was added because this inserting was interactive + m_verticalHeaderAlreadyAdded = false; + + //update navigator's data + m_navPanel->setRecordCount(rows()); + + if (m_curRow >= (int)row) { + //update + editorShowFocus( m_curRow, m_curCol ); + } + } +} + +tristate KexiDataAwareObjectInterface::deleteAllRows(bool ask, bool repaint) +{ + if (!hasData()) + return true; + if (m_data->count()<1) + return true; + + if (ask) { + QString tableName = m_data->dbTableName(); + if (!tableName.isEmpty()) { + tableName.prepend(" \""); + tableName.append("\""); + } + if (KMessageBox::Cancel == KMessageBox::warningContinueCancel(dynamic_cast<QWidget*>(this), + i18n("Do you want to clear the contents of table %1?").arg(tableName), + 0, KGuiItem(i18n("&Clear Contents")) )) + return cancelled; + } + + cancelRowEdit(); +// acceptRowEdit(); +// m_verticalHeader->clear(); + const bool repaintLater = repaint && m_spreadSheetMode; + const int oldRows = rows(); + + bool res = m_data->deleteAllRows(repaint && !repaintLater); + + if (res) { + if (m_spreadSheetMode) { +// const uint columns = m_data->columns.count(); + for (int i=0; i<oldRows; i++) { + m_data->append(m_data->createItem());//new KexiTableItem(columns)); + } + } + } + if (repaintLater) + m_data->reload(); + +// d->clearVariables(); +// m_verticalHeader->setCurrentRow(-1); + +// d->pUpdateTimer->start(1,true); +// if (repaint) +// viewport()->repaint(); + return res; +} + +void KexiDataAwareObjectInterface::clearColumns(bool repaint) +{ + cancelRowEdit(); + m_data->clearInternal(); + + clearColumnsInternal(repaint); + updateIndicesForVisibleValues(); + + if (repaint) +// viewport()->repaint(); +//OK? + updateWidgetContents(); + +/* for(int i=0; i < rows(); i++) + { + m_verticalHeader->removeLabel(); + } + + editorCancel(); + m_contents->clear(); + + d->clearVariables(); + d->numCols = 0; + + while(d->pTopHeader->count()>0) + d->pTopHeader->removeLabel(0); + + m_verticalHeader->setCurrentRow(-1); + + viewport()->repaint(); + +// d->pColumnTypes.resize(0); +// d->pColumnModes.resize(0); +// d->pColumnDefaults.clear();*/ +} + +void KexiDataAwareObjectInterface::reloadData() +{ +// cancelRowEdit(); + acceptRowEdit(); + if (m_verticalHeader) + m_verticalHeader->clear(); + + if (m_curCol>=0 && m_curCol<columns()) { + //find the editor for this column + KexiDataItemInterface *edit = editor( m_curCol ); + if (edit) { + edit->hideFocus(); + } + } +// setCursorPosition(-1, -1, true); + clearVariables(); + if (m_verticalHeader) + m_verticalHeader->setCurrentRow(-1); + + if (dynamic_cast<QWidget*>(this) && dynamic_cast<QWidget*>(this)->isVisible()) + initDataContents(); + else + m_initDataContentsOnShow = true; + + if (m_verticalHeader) + m_verticalHeader->addLabels(m_data->count()); + + updateWidgetScrollBars(); +} + +int KexiDataAwareObjectInterface::columnType(int col) +{ + KexiTableViewColumn* c = m_data ? column(col) : 0; + return c ? c->field()->type() : KexiDB::Field::InvalidType; +} + +bool KexiDataAwareObjectInterface::columnEditable(int col) +{ + KexiTableViewColumn* c = m_data ? column(col) : 0; + return c ? (! c->isReadOnly()) : false; +} + +int KexiDataAwareObjectInterface::rows() const +{ + if (!hasData()) + return 0; + return m_data->count(); +} + +int KexiDataAwareObjectInterface::dataColumns() const +{ + if (!hasData()) + return 0; + return m_data->columns.count(); +} + +QVariant KexiDataAwareObjectInterface::columnDefaultValue(int /*col*/) const +{ + return QVariant(0); +//TODO(js) +// return m_data->columns[col].defaultValue; +} + +void KexiDataAwareObjectInterface::setAcceptsRowEditAfterCellAccepting(bool set) +{ + m_acceptsRowEditAfterCellAccepting = set; +} + +void KexiDataAwareObjectInterface::setDropsAtRowEnabled(bool set) +{ +// const bool old = d->dropsAtRowEnabled; + if (!set) + m_dragIndicatorLine = -1; + if (m_dropsAtRowEnabled && !set) { + m_dropsAtRowEnabled = false; +// update(); + updateWidgetContents(); + } + else { + m_dropsAtRowEnabled = set; + } +} + +void KexiDataAwareObjectInterface::setEmptyRowInsertingEnabled(bool set) +{ + m_emptyRowInsertingEnabled = set; + /*emit*/ reloadActions(); +} + +void KexiDataAwareObjectInterface::slotAboutToDeleteRow(KexiTableItem& item, + KexiDB::ResultInfo* /*result*/, bool repaint) +{ + if (repaint) { + m_rowWillBeDeleted = m_data->findRef(&item); + } +} + +void KexiDataAwareObjectInterface::slotRowDeleted() +{ + if (m_rowWillBeDeleted >= 0) { + if (m_rowWillBeDeleted > 0 && m_rowWillBeDeleted >= (rows()-1) && !m_spreadSheetMode) + m_rowWillBeDeleted = rows()-1; //move up if it's the last row + updateWidgetContentsSize(); + + if (! (m_spreadSheetMode && m_rowWillBeDeleted>=(rows()-1))) + setCursorPosition(m_rowWillBeDeleted, m_curCol, true/*forceSet*/); + if (m_verticalHeader) + m_verticalHeader->removeLabel(); + + updateAllVisibleRowsBelow(m_curRow); //needed for KexiTableView + + //update navigator's data + m_navPanel->setRecordCount(rows()); + + m_rowWillBeDeleted = -1; + } +} + +bool KexiDataAwareObjectInterface::beforeDeleteItem(KexiTableItem *) +{ + //always return + return true; +} + +bool KexiDataAwareObjectInterface::deleteItem(KexiTableItem *item)/*, bool moveCursor)*/ +{ + if (!item || !beforeDeleteItem(item)) + return false; + + QString msg, desc; +// bool current = (item == d->pCurrentItem); + const bool lastRowDeleted = m_spreadSheetMode && m_data->last() == item; //we need to know this so we + //can return to the last row + //after reinserting it + if (!m_data->deleteRow(*item, true /*repaint*/)) { + /*const int button =*/ + showErrorMessageForResult( m_data->result() ); +// if (KMessageBox::No == button) { + //discard changes + // } + return false; + } + else { +//setCursorPosition() wil lset this! if (current) + //d->pCurrentItem = m_data->current(); + } + +// repaintAfterDelete(); + if (m_spreadSheetMode) { //append empty row for spreadsheet mode + m_data->append(m_data->createItem());//new KexiTableItem(m_data->columns.count())); + if (m_verticalHeader) + m_verticalHeader->addLabels(1); + if (lastRowDeleted) //back to the last row + setCursorPosition(rows()-1, m_curCol, true/*forceSet*/); + /*emit*/ newItemAppendedForAfterDeletingInSpreadSheetMode(); + } + return true; +} + +KexiTableViewColumn* KexiDataAwareObjectInterface::column(int col) +{ + return m_data->column(col); +} + +bool KexiDataAwareObjectInterface::hasDefaultValueAt(const KexiTableViewColumn& tvcol) +{ + if (m_rowEditing && m_data->rowEditBuffer() && m_data->rowEditBuffer()->isDBAware()) { + return m_data->rowEditBuffer()->hasDefaultValueAt( *tvcol.columnInfo ); + } + return false; +} + +const QVariant* KexiDataAwareObjectInterface::bufferedValueAt(int col, bool useDefaultValueIfPossible) +{ + if (m_rowEditing && m_data->rowEditBuffer()) + { + KexiTableViewColumn* tvcol = column(col); + if (tvcol->isDBAware) { + //get the stored value + const int realFieldNumber = fieldNumberForColumn(col); + if (realFieldNumber < 0) { + kdWarning() << "KexiDataAwareObjectInterface::bufferedValueAt(): " + "fieldNumberForColumn(m_curCol) < 0" << endl; + return 0; + } + QVariant *storedValue = &m_currentItem->at( realFieldNumber ); + + //db-aware data: now, try to find a buffered value (or default one) + const QVariant *cv = m_data->rowEditBuffer()->at( *tvcol->columnInfo, + storedValue->isNull() && useDefaultValueIfPossible); + if (cv) + return cv; + return storedValue; + } + //not db-aware data: + const QVariant *cv = m_data->rowEditBuffer()->at( tvcol->field()->name() ); + if (cv) + return cv; + } + //not db-aware data: + const int realFieldNumber = fieldNumberForColumn(col); + if (realFieldNumber < 0) { + kdWarning() << "KexiDataAwareObjectInterface::bufferedValueAt(): " + "fieldNumberForColumn(m_curCol) < 0" << endl; + return 0; + } + return &m_currentItem->at( realFieldNumber ); +} + +void KexiDataAwareObjectInterface::startEditOrToggleValue() +{ + if ( !isReadOnly() && columnEditable(m_curCol) ) { + if (columnType(m_curCol) == KexiDB::Field::Boolean) { + boolToggled(); + } + else { + startEditCurrentCell(); + return; + } + } +} + +void KexiDataAwareObjectInterface::boolToggled() +{ + startEditCurrentCell(); + if (m_editor) { + m_editor->clickedOnContents(); + } + acceptEditor(); + updateCell(m_curRow, m_curCol); + +/* int s = m_currentItem->at(m_curCol).toInt(); + QVariant oldValue=m_currentItem->at(m_curCol); + (*m_currentItem)[m_curCol] = QVariant(s ? 0 : 1); + updateCell(m_curRow, m_curCol); +// emit itemChanged(m_currentItem, m_curRow, m_curCol, oldValue); +// emit itemChanged(m_currentItem, m_curRow, m_curCol);*/ +} + +void KexiDataAwareObjectInterface::slotDataDestroying() +{ + m_data = 0; + m_itemIterator = 0; +} + +void KexiDataAwareObjectInterface::addNewRecordRequested() +{ + if (!isInsertingEnabled()) + return; + if (m_rowEditing) { + if (!acceptRowEdit()) + return; + } +// setFocus(); + selectRow(rows()); + startEditCurrentCell(); + if (m_editor) + m_editor->setFocus(); +} + +bool KexiDataAwareObjectInterface::handleKeyPress(QKeyEvent *e, int &curRow, int &curCol, + bool fullRowSelection, bool *moveToFirstField, bool *moveToLastField) +{ + if (moveToFirstField) + *moveToFirstField = false; + if (moveToLastField) + *moveToLastField = false; + + const bool nobtn = e->state()==Qt::NoButton; + const int k = e->key(); + //kdDebug() << "-----------" << e->state() << " " << k << endl; + + if ((k == Qt::Key_Up && nobtn) || (k == Qt::Key_PageUp && e->state()==Qt::ControlButton)) { + selectPrevRow(); + e->accept(); + } + else if ((k == Qt::Key_Down && nobtn) || (k == Qt::Key_PageDown && e->state()==Qt::ControlButton)) { + selectNextRow(); + e->accept(); + } + else if (k == Qt::Key_PageUp && nobtn) { + selectPrevPage(); + e->accept(); + } + else if (k == Qt::Key_PageDown && nobtn) { + selectNextPage(); + e->accept(); + } + else if (k == Qt::Key_Home) { + if (fullRowSelection) { + //we're in row-selection mode: home key always moves to 1st row + curRow = 0;//to 1st row + } + else {//cell selection mode: different actions depending on ctrl and shift keys state + if (nobtn) { + curCol = 0;//to 1st col + } + else if (e->state()==Qt::ControlButton) { + curRow = 0;//to 1st row and col + curCol = 0; + } + else + return false; + } + if (moveToFirstField) + *moveToFirstField = true; + //do not accept yet + e->ignore(); + } + else if (k == Qt::Key_End) { + if (fullRowSelection) { + //we're in row-selection mode: home key always moves to last row + curRow = m_data->count()-1+(isInsertingEnabled()?1:0);//to last row + } + else {//cell selection mode: different actions depending on ctrl and shift keys state + if (nobtn) { + curCol = columns()-1;//to last col + } + else if (e->state()==Qt::ControlButton) { + curRow = m_data->count()-1 /*+(isInsertingEnabled()?1:0)*/; //to last row and col + curCol = columns()-1;//to last col + } + else + return false; + } + if (moveToLastField) + *moveToLastField = true; + //do not accept yet + e->ignore(); + } + else if (isInsertingEnabled() && (e->state()==Qt::ControlButton && k == Qt::Key_Equal + || e->state()==(Qt::ControlButton|Qt::ShiftButton) && k == Qt::Key_Equal)) { + curRow = m_data->count(); //to the new row + curCol = 0;//to first col + if (moveToFirstField) + *moveToFirstField = true; + //do not accept yet + e->ignore(); + } + else + return false; + + return true; +} + +void KexiDataAwareObjectInterface::vScrollBarValueChanged(int v) +{ + Q_UNUSED(v); + if (!m_vScrollBarValueChanged_enabled) + return; + + if (m_scrollbarToolTipsEnabled) { + const QRect r( verticalScrollBar()->sliderRect() ); + const int row = lastVisibleRow()+1; + if (row<=0) { + m_scrollBarTipTimer.stop(); + m_scrollBarTip->hide(); + return; + } + m_scrollBarTip->setText( i18n("Row: ") + QString::number(row) ); + m_scrollBarTip->adjustSize(); + QWidget* thisWidget = dynamic_cast<QWidget*>(this); + m_scrollBarTip->move( + thisWidget->mapToGlobal( r.topLeft() + verticalScrollBar()->pos() ) + + QPoint( - m_scrollBarTip->width()-5, + r.height()/2 - m_scrollBarTip->height()/2) ); + if (verticalScrollBar()->draggingSlider()) { + kdDebug(44021) << " draggingSlider() " << endl; + m_scrollBarTipTimer.stop(); + m_scrollBarTip->show(); + m_scrollBarTip->raise(); + } + else { + m_scrollBarTipTimerCnt++; + if (m_scrollBarTipTimerCnt>4) { + m_scrollBarTipTimerCnt=0; + m_scrollBarTip->show(); + m_scrollBarTip->raise(); + m_scrollBarTipTimer.start(500, true); + } + } + } + //update bottom view region +/* if (m_navPanel && (contentsHeight() - contentsY() - clipper()->height()) <= QMAX(d->rowHeight,m_navPanel->height())) { + slotUpdate(); + triggerUpdate(); + }*/ +} + +bool KexiDataAwareObjectInterface::scrollbarToolTipsEnabled() const +{ + return m_scrollbarToolTipsEnabled; +} + +void KexiDataAwareObjectInterface::setScrollbarToolTipsEnabled(bool set) +{ + m_scrollbarToolTipsEnabled = set; +} + +void KexiDataAwareObjectInterface::vScrollBarSliderReleased() +{ + kdDebug(44021) << "vScrollBarSliderReleased()" << endl; + m_scrollBarTip->hide(); +} + +void KexiDataAwareObjectInterface::scrollBarTipTimeout() +{ + if (m_scrollBarTip->isVisible()) { +// kdDebug(44021) << "TIMEOUT! - hide" << endl; + if (m_scrollBarTipTimerCnt>0) { + m_scrollBarTipTimerCnt=0; + m_scrollBarTipTimer.start(500, true); + return; + } + m_scrollBarTip->hide(); + } + m_scrollBarTipTimerCnt=0; +} + +void KexiDataAwareObjectInterface::focusOutEvent(QFocusEvent* e) +{ + Q_UNUSED(e); + m_scrollBarTipTimer.stop(); + m_scrollBarTip->hide(); + + updateCell(m_curRow, m_curCol); +} + +int KexiDataAwareObjectInterface::showErrorMessageForResult(KexiDB::ResultInfo* resultInfo) +{ + QWidget *thisWidget = dynamic_cast<QWidget*>(this); + if (resultInfo->allowToDiscardChanges) { + return KMessageBox::questionYesNo(thisWidget, resultInfo->msg + + (resultInfo->desc.isEmpty() ? QString::null : ("\n"+resultInfo->desc)), + QString::null, + KGuiItem(i18n("Correct Changes", "Correct"), QString::null, i18n("Correct changes")), + KGuiItem(i18n("Discard Changes")) ); + } + + if (resultInfo->desc.isEmpty()) + KMessageBox::sorry(thisWidget, resultInfo->msg); + else + KMessageBox::detailedSorry(thisWidget, resultInfo->msg, resultInfo->desc); + + return KMessageBox::Ok; +} + +void KexiDataAwareObjectInterface::updateIndicesForVisibleValues() +{ + m_indicesForVisibleValues.resize( m_data ? m_data->columnsCount() : 0 ); + if (!m_data) + return; + for (uint i=0; i < m_data->columnsCount(); i++) { + KexiTableViewColumn* tvCol = m_data->column(i); + if (tvCol->columnInfo && tvCol->columnInfo->indexForVisibleLookupValue()!=-1) + // retrieve visible value from lookup field + m_indicesForVisibleValues[ i ] = tvCol->columnInfo->indexForVisibleLookupValue(); + else + m_indicesForVisibleValues[ i ] = i; + } +} + +/*! Performs searching \a stringValue in \a where string. + \a matchAnyPartOfField, \a matchWholeField, \a wholeWordsOnly options are used to control how to search. + + If \a matchWholeField is true, \a wholeWordsOnly is not checked. + \a firstCharacter is in/out parameter. If \a matchAnyPartOfField is true and \a matchWholeField is false, + \a firstCharacter >= 0, the search will be performed after skipping first \a firstCharacter characters. + + If \a forward is false, we are searching backwart from \a firstCharacter position. \a firstCharacter == -1 + means then the last character. \a firstCharacter == INT_MAX means "before first" place, so searching fails + immediately. + On success, true is returned and \a firstCharacter is set to position of the matched string. */ +static inline bool findInString(const QString& stringValue, int stringLength, const QString& where, + int& firstCharacter, bool matchAnyPartOfField, bool matchWholeField, + bool caseSensitive, bool wholeWordsOnly, bool forward) +{ + if (where.isEmpty()) { + firstCharacter = -1; + return false; + } + + if (matchAnyPartOfField) { + if (forward) { + int pos = firstCharacter == -1 ? 0 : firstCharacter; + if (wholeWordsOnly) { + const int whereLength = where.length(); + while (true) { + pos = where.find( stringValue, pos, caseSensitive ); + if (pos == -1) + break; + if ((pos > 0 && where.at(pos-1).isLetterOrNumber()) + ||((pos+stringLength-1) < (whereLength-1) && where.at(pos+stringLength-1+1).isLetterOrNumber())) + { + pos++; // invalid match because before or after the string there is non-white space + } + else + break; + }//while + firstCharacter = pos; + } + else {// !wholeWordsOnly + firstCharacter = where.find( stringValue, pos, caseSensitive ); + } + return firstCharacter != -1; + } + else { // !matchAnyPartOfField + if (firstCharacter == INT_MAX) { + firstCharacter = -1; //next time we'll be looking at different cell + return false; + } + int pos = firstCharacter; + if (wholeWordsOnly) { + const int whereLength = where.length(); + while (true) { + pos = where.findRev( stringValue, pos, caseSensitive ); + if (pos == -1) + break; + if ((pos > 0 && where.at(pos-1).isLetterOrNumber()) + ||((pos+stringLength-1) < (whereLength-1) && where.at(pos+stringLength-1+1).isLetterOrNumber())) + { + // invalid match because before or after the string there is non-white space + pos--; + if (pos < 0) // it can make pos < 0 + break; + } + else + break; + }//while + firstCharacter = pos; + } + else {// !wholeWordsOnly + firstCharacter = where.findRev( stringValue, pos, caseSensitive ); + } + return firstCharacter != -1; + } + } + else if (matchWholeField) { + if (firstCharacter != -1 && firstCharacter != 0) { //we're not at 0-th char + firstCharacter = -1; + } + else if ( (caseSensitive ? where : where.lower()) == stringValue) { + firstCharacter = 0; + return true; + } + } + else {// matchStartOfField + if (firstCharacter != -1 && firstCharacter != 0) { //we're not at 0-th char + firstCharacter = -1; + } + else if (where.startsWith(stringValue, caseSensitive)) { + if (wholeWordsOnly) { + // If where.length() < stringValue.length(), true will be returned too - fine. + return !where.at( stringValue.length() ).isLetterOrNumber(); + } + firstCharacter = 0; + return true; + } + } + return false; +} + +tristate KexiDataAwareObjectInterface::find(const QVariant& valueToFind, + const KexiSearchAndReplaceViewInterface::Options& options, bool next) +{ + if (!hasData()) + return cancelled; + const QVariant prevSearchedValue( m_recentlySearchedValue ); + m_recentlySearchedValue = valueToFind; + const KexiSearchAndReplaceViewInterface::Options::SearchDirection prevSearchDirection = m_recentSearchDirection; + m_recentSearchDirection = options.searchDirection; + if (valueToFind.isNull() || valueToFind.toString().isEmpty()) + return cancelled; + + const bool forward = (options.searchDirection == KexiSearchAndReplaceViewInterface::Options::SearchUp) + ? !next : next; //direction can be reversed + + if ((!prevSearchedValue.isNull() && prevSearchedValue!=valueToFind) + || (prevSearchDirection!=options.searchDirection && options.searchDirection==KexiSearchAndReplaceViewInterface::Options::SearchAllRows)) + { + // restart searching when value has been changed or new direction is SearchAllRows + m_positionOfRecentlyFoundValue.exists = false; + } + + const bool startFrom1stRowAndCol = !m_positionOfRecentlyFoundValue.exists && next + && options.searchDirection == KexiSearchAndReplaceViewInterface::Options::SearchAllRows; + const bool startFromLastRowAndCol = + (!m_positionOfRecentlyFoundValue.exists && !next && options.searchDirection == KexiSearchAndReplaceViewInterface::Options::SearchAllRows) + ||(m_curRow >= rows() && !forward); //we're at "insert" row, and searching backwards: move to the last cell + + if (!startFrom1stRowAndCol && !startFromLastRowAndCol && m_curRow >= rows()) { + //we're at "insert" row, and searching forward: no chances to find something + return false; + } + KexiTableViewData::Iterator it( (startFrom1stRowAndCol || startFromLastRowAndCol) + ? m_data->iterator() : *m_itemIterator /*start from the current cell*/ ); + if (startFromLastRowAndCol) + it.toLast(); + int firstCharacter; + if (m_positionOfRecentlyFoundValue.exists) {// start after the next/prev char position + if (forward) + firstCharacter = m_positionOfRecentlyFoundValue.lastCharacter + 1; + else { + firstCharacter = (m_positionOfRecentlyFoundValue.firstCharacter > 0) ? + (m_positionOfRecentlyFoundValue.firstCharacter - 1) : INT_MAX /* this means 'before first'*/; + } + } + else { + firstCharacter = -1; //forward ? -1 : INT_MAX; + } + + const int columnsCount = m_data->columnsCount(); + int row, col; + if (startFrom1stRowAndCol) { + row = 0; + col = 0; + } + else if (startFromLastRowAndCol) { + row = rows()-1; + col = columnsCount-1; + } + else { + row = m_curRow; + col = m_curCol; + } + + //sache some flags for efficiency + const bool matchAnyPartOfField + = options.textMatching == KexiSearchAndReplaceViewInterface::Options::MatchAnyPartOfField; + const bool matchWholeField + = options.textMatching == KexiSearchAndReplaceViewInterface::Options::MatchWholeField; + const bool caseSensitive = options.caseSensitive; + const bool wholeWordsOnly = options.wholeWordsOnly; +//unused const bool promptOnReplace = options.promptOnReplace; + int columnNumber = (options.columnNumber == KexiSearchAndReplaceViewInterface::Options::CurrentColumn) + ? m_curCol : options.columnNumber; + if (columnNumber>=0) + col = columnNumber; + const bool lookInAllColumns = columnNumber == KexiSearchAndReplaceViewInterface::Options::AllColumns; + int firstColumn; // real number of the first column, can be smaller than lastColumn if forward==true + int lastColumn; // real number of the last column + if (lookInAllColumns) { + firstColumn = forward ? 0 : columnsCount-1; + lastColumn = forward ? columnsCount-1 : 0; + } + else { + firstColumn = columnNumber; + lastColumn = columnNumber; + } + const QString stringValue( caseSensitive ? valueToFind.toString() : valueToFind.toString().lower() ); + const int stringLength = stringValue.length(); + + // search + const int prevRow = m_curRow; + KexiTableItem *item; + while ( (item = it.current()) ) { + for (; forward ? col <= lastColumn : col >= lastColumn; + col = forward ? (col+1) : (col-1)) + { + const QVariant cell( item->at( m_indicesForVisibleValues[ col ] ) ); + if (findInString(stringValue, stringLength, cell.toString(), firstCharacter, + matchAnyPartOfField, matchWholeField, caseSensitive, wholeWordsOnly, forward)) + { + //*m_itemIterator = it; + //m_currentItem = *it; + //m_curRow = row; + //m_curCol = col; + setCursorPosition(row, col, true/*forceSet*/); + if (prevRow != m_curRow) + updateRow(prevRow); + // remember the exact position for the found value + m_positionOfRecentlyFoundValue.exists = true; + m_positionOfRecentlyFoundValue.firstCharacter = firstCharacter; +//! @todo for regexp lastCharacter should be computed + m_positionOfRecentlyFoundValue.lastCharacter = firstCharacter + stringLength - 1; + return true; + } + }//for + if (forward) { + ++it; + ++row; + } + else { + --it; + --row; + } + col = firstColumn; + }//while + return false; +} + +tristate KexiDataAwareObjectInterface::findNextAndReplace( + const QVariant& valueToFind, const QVariant& replacement, + const KexiSearchAndReplaceViewInterface::Options& options, bool replaceAll) +{ + Q_UNUSED(replacement); + Q_UNUSED(options); + Q_UNUSED(replaceAll); + + if (isReadOnly()) + return cancelled; + if (valueToFind.isNull() || valueToFind.toString().isEmpty()) + return cancelled; + //! @todo implement KexiDataAwareObjectInterface::findAndReplace() + return false; +} |