/* This file is part of the KDE project Copyright (C) 2005-2007 Jaroslaw Staniek Based on KexiTableView code. Copyright (C) 2002 Till Busch Copyright (C) 2003 Lucijan Busch Copyright (C) 2003 Daniel Molkentin Copyright (C) 2003 Joseph Wenninger 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 #include #include #include #include #include #include #include #include #include #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 TQLabel("",0, "vScrollBarToolTip", TQt::WStyle_Customize |TQt::WStyle_NoBorder|TQt::WX11BypassWM|TQt::WStyle_StaysOnTop|TQt::WStyle_Tool); m_scrollBarTip->setPalette(TQToolTip::palette()); m_scrollBarTip->setMargin(2); m_scrollBarTip->setIndent(0); m_scrollBarTip->tqsetAlignment(TQt::AlignCenter); m_scrollBarTip->setFrameStyle( TQFrame::Plain | TQFrame::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() : TQString(), 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, TQT_SIGNAL(refreshRequested()), this, TQT_SLOT(slotRefreshRequested())); connectToReloadDataSlot(m_data, TQT_SIGNAL(reloadRequested())); TQObject* thisObject = dynamic_cast(this); if (thisObject) { TQObject::connect(m_data, TQT_SIGNAL(destroying()), thisObject, TQT_SLOT(slotDataDestroying())); TQObject::connect(m_data, TQT_SIGNAL(rowsDeleted( const TQValueList & )), thisObject, TQT_SLOT(slotRowsDeleted( const TQValueList & ))); TQObject::connect(m_data, TQT_SIGNAL(aboutToDeleteRow(KexiTableItem&,KexiDB::ResultInfo*,bool)), thisObject, TQT_SLOT(slotAboutToDeleteRow(KexiTableItem&,KexiDB::ResultInfo*,bool))); TQObject::connect(m_data, TQT_SIGNAL(rowDeleted()), thisObject, TQT_SLOT(slotRowDeleted())); TQObject::connect(m_data, TQT_SIGNAL(rowInserted(KexiTableItem*,bool)), thisObject, TQT_SLOT(slotRowInserted(KexiTableItem*,bool))); TQObject::connect(m_data, TQT_SIGNAL(rowInserted(KexiTableItem*,uint,bool)), thisObject, TQT_SLOT(slotRowInserted(KexiTableItem*,uint,bool))); //not db-aware TQObject::connect(m_data, TQT_SIGNAL(rowRepaintRequested(KexiTableItem&)), thisObject, TQT_SLOT(slotRowRepaintRequested(KexiTableItem&))); // setup scrollbar's tooltip TQObject::connect(verticalScrollBar(),TQT_SIGNAL(sliderReleased()), thisObject,TQT_SLOT(vScrollBarSliderReleased())); TQObject::connect(verticalScrollBar(),TQT_SIGNAL(valueChanged(int)), thisObject,TQT_SLOT(vScrollBarValueChanged(int))); TQObject::connect(&m_scrollBarTipTimer,TQT_SIGNAL(timeout()), thisObject,TQT_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; // TQSize 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( TQMIN( rows() - 1 +(isInsertingEnabled()?1:0), m_curRow + 1 ) ); } void KexiDataAwareObjectInterface::selectPrevPage() { selectRow( TQMAX( 0, m_curRow - rowsPerPage() ) ); } void KexiDataAwareObjectInterface::selectNextPage() { selectRow( TQMIN( 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( TQMAX( 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 = TQMAX(0, col); newcol = TQMIN(columns() - 1, newcol); } else { newcol = m_curCol; //no changes newcol = TQMAX(0, newcol); //may not be < 0 ! } newrow = TQMAX(0, row); newrow = TQMIN(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(): " <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 = TQMIN( 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 && oldColhideFocus(); } } // 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); // TQPoint pcenter = TQRect( 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) << TQString("NOW item at %1 (%2) is current") .tqarg(m_curRow).tqarg((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; // TQString 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 tqrepaint properly!! // TQSize 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 TQToolTip { public: KexiDataAwareObjectInterfaceToolTip( const TQString & text, const TQPoint & pos, TQWidget * widget ) : TQToolTip(widget), m_text(text) { tip( TQRect(pos, TQSize(100, 100)), text ); } virtual void maybeTip(const TQPoint & p) { tip( TQRect(p, TQSize(100, 100)), m_text); } TQString m_text; }; bool KexiDataAwareObjectInterface::acceptEditor() { if (!hasData()) return true; if (!m_editor || m_inside_acceptEditor) return true; m_inside_acceptEditor = true;//avoid recursion TQVariant newval; Validator::Result res = Validator::Ok; TQString msg, desc; bool setNull = false; // bool allow = true; // static const TQString 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; TQWidget *par = dynamic_cast(this) ? dynamic_cast(this)->viewport() : dynamic_cast(this); TQWidget *edit = dynamic_cast(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").tqarg(m_editor->columnInfo()->field->typeName())+"?", dynamic_cast(this)); m_errorMessagePopup->move( par->mapToGlobal(edit->pos()) + TQPoint(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().tqarg(m_editor->columnInfo()->field->captionOrName()) msg = Validator::msgColumnNotEmpty().tqarg(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().tqarg(m_editor->columnInfo()->field->captionOrName()) msg = Validator::msgColumnNotEmpty().tqarg(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().tqarg(m_editor->columnInfo()->field->captionOrName()) msg = Validator::msgColumnNotEmpty().tqarg(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(this), msg); else KMessageBox::detailedSorry(dynamic_cast(this), msg, desc); } editCurrentCellAgain = true; // allow = false; } else if (res == Validator::Warning) { //js: todo: message!!! KMessageBox::messageBox(dynamic_cast(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 TQVariant 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()->columnresult()->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()==TQVariant::String ? newval.toString() : TQString()); // m_editor->setFocus(); } return false; } void KexiDataAwareObjectInterface::startEditCurrentCell(const TQString &setText) { kdDebug() << "** KexiDataAwareObjectInterface::startEditCurrentCell("<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, TQString(), 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(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 /*tqrepaint*/); if (changeCurrentRow) { //update iter... m_itemIterator->toFirst(); (*m_itemIterator)+=m_curRow; } /* TQSize 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 tqrepaint) { int row = m_data->findRef(item); slotRowInserted( item, row, tqrepaint ); } void KexiDataAwareObjectInterface::slotRowInserted(KexiTableItem * /*item*/, uint row, bool tqrepaint) { if (tqrepaint && (int)rowpTopHeader->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 tqrepaint) { if (!hasData()) return true; if (m_data->count()<1) return true; if (ask) { TQString tableName = m_data->dbTableName(); if (!tableName.isEmpty()) { tableName.prepend(" \""); tableName.append("\""); } if (KMessageBox::Cancel == KMessageBox::warningContinueCancel(dynamic_cast(this), i18n("Do you want to clear the contents of table %1?").tqarg(tableName), 0, KGuiItem(i18n("&Clear Contents")) )) return cancelled; } cancelRowEdit(); // acceptRowEdit(); // m_verticalHeader->clear(); const bool repaintLater = tqrepaint && m_spreadSheetMode; const int oldRows = rows(); bool res = m_data->deleteAllRows(tqrepaint && !repaintLater); if (res) { if (m_spreadSheetMode) { // const uint columns = m_data->columns.count(); for (int i=0; iappend(m_data->createItem());//new KexiTableItem(columns)); } } } if (repaintLater) m_data->reload(); // d->clearVariables(); // m_verticalHeader->setCurrentRow(-1); // d->pUpdateTimer->start(1,true); // if (tqrepaint) // viewport()->tqrepaint(); return res; } void KexiDataAwareObjectInterface::clearColumns(bool tqrepaint) { cancelRowEdit(); m_data->clearInternal(); clearColumnsInternal(tqrepaint); updateIndicesForVisibleValues(); if (tqrepaint) // viewport()->tqrepaint(); //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()->tqrepaint(); // 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_curColhideFocus(); } } // setCursorPosition(-1, -1, true); clearVariables(); if (m_verticalHeader) m_verticalHeader->setCurrentRow(-1); if (dynamic_cast(this) && dynamic_cast(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(); } TQVariant KexiDataAwareObjectInterface::columnDefaultValue(int /*col*/) const { return TQVariant(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 tqrepaint) { if (tqrepaint) { 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; TQString 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 /*tqrepaint*/)) { /*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 TQVariant* 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; } TQVariant *storedValue = &m_currentItem->at( realFieldNumber ); //db-aware data: now, try to find a buffered value (or default one) const TQVariant *cv = m_data->rowEditBuffer()->at( *tvcol->columnInfo, storedValue->isNull() && useDefaultValueIfPossible); if (cv) return cv; return storedValue; } //not db-aware data: const TQVariant *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(); TQVariant oldValue=m_currentItem->at(m_curCol); (*m_currentItem)[m_curCol] = TQVariant(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(TQKeyEvent *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 == TQt::Key_Up && nobtn) || (k == TQt::Key_PageUp && e->state()==TQt::ControlButton)) { selectPrevRow(); e->accept(); } else if ((k == TQt::Key_Down && nobtn) || (k == TQt::Key_PageDown && e->state()==TQt::ControlButton)) { selectNextRow(); e->accept(); } else if (k == TQt::Key_PageUp && nobtn) { selectPrevPage(); e->accept(); } else if (k == TQt::Key_PageDown && nobtn) { selectNextPage(); e->accept(); } else if (k == TQt::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()==TQt::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 == TQt::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()==TQt::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()==TQt::ControlButton && k == TQt::Key_Equal || e->state()==(TQt::ControlButton|TQt::ShiftButton) && k == TQt::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 TQRect r( verticalScrollBar()->sliderRect() ); const int row = lastVisibleRow()+1; if (row<=0) { m_scrollBarTipTimer.stop(); m_scrollBarTip->hide(); return; } m_scrollBarTip->setText( i18n("Row: ") + TQString::number(row) ); m_scrollBarTip->adjustSize(); TQWidget* thisWidget = dynamic_cast(this); m_scrollBarTip->move( thisWidget->mapToGlobal( r.topLeft() + verticalScrollBar()->pos() ) + TQPoint( - 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()) <= TQMAX(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(TQFocusEvent* e) { Q_UNUSED(e); m_scrollBarTipTimer.stop(); m_scrollBarTip->hide(); updateCell(m_curRow, m_curCol); } int KexiDataAwareObjectInterface::showErrorMessageForResult(KexiDB::ResultInfo* resultInfo) { TQWidget *thisWidget = dynamic_cast(this); if (resultInfo->allowToDiscardChanges) { return KMessageBox::questionYesNo(thisWidget, resultInfo->msg + (resultInfo->desc.isEmpty() ? TQString() : ("\n"+resultInfo->desc)), TQString(), KGuiItem(i18n("Correct Changes", "Correct"), TQString(), 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 TQString& stringValue, int stringLength, const TQString& 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.tqstartsWith(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 TQVariant& valueToFind, const KexiSearchAndReplaceViewInterface::Options& options, bool next) { if (!hasData()) return cancelled; const TQVariant 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 TQString 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 TQVariant 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 TQVariant& valueToFind, const TQVariant& 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; }