You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
koffice/kexi/widget/tableview/kexidataawareobjectiface.cpp

2109 lines
61 KiB

/* 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 <tqscrollview.h>
#include <tqlabel.h>
#include <tqtooltip.h>
#include <tdemessagebox.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 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->setAlignment(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, TQ_SIGNAL(refreshRequested()), this, TQ_SLOT(slotRefreshRequested()));
connectToReloadDataSlot(m_data, TQ_SIGNAL(reloadRequested()));
TQObject* thisObject = dynamic_cast<TQObject*>(this);
if (thisObject) {
TQObject::connect(m_data, TQ_SIGNAL(destroying()), thisObject, TQ_SLOT(slotDataDestroying()));
TQObject::connect(m_data, TQ_SIGNAL(rowsDeleted( const TQValueList<int> & )),
thisObject, TQ_SLOT(slotRowsDeleted( const TQValueList<int> & )));
TQObject::connect(m_data, TQ_SIGNAL(aboutToDeleteRow(KexiTableItem&,KexiDB::ResultInfo*,bool)),
thisObject, TQ_SLOT(slotAboutToDeleteRow(KexiTableItem&,KexiDB::ResultInfo*,bool)));
TQObject::connect(m_data, TQ_SIGNAL(rowDeleted()), thisObject, TQ_SLOT(slotRowDeleted()));
TQObject::connect(m_data, TQ_SIGNAL(rowInserted(KexiTableItem*,bool)),
thisObject, TQ_SLOT(slotRowInserted(KexiTableItem*,bool)));
TQObject::connect(m_data, TQ_SIGNAL(rowInserted(KexiTableItem*,uint,bool)),
thisObject, TQ_SLOT(slotRowInserted(KexiTableItem*,uint,bool))); //not db-aware
TQObject::connect(m_data, TQ_SIGNAL(rowRepaintRequested(KexiTableItem&)),
thisObject, TQ_SLOT(slotRowRepaintRequested(KexiTableItem&)));
// setup scrollbar's tooltip
TQObject::connect(verticalScrollBar(),TQ_SIGNAL(sliderReleased()),
thisObject,TQ_SLOT(vScrollBarSliderReleased()));
TQObject::connect(verticalScrollBar(),TQ_SIGNAL(valueChanged(int)),
thisObject,TQ_SLOT(vScrollBarValueChanged(int)));
TQObject::connect(&m_scrollBarTipTimer,TQ_SIGNAL(timeout()),
thisObject,TQ_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(): " <<TQString("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 = TQMIN( 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 = 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 && 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);
// 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")
.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;
// 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 repaint 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<TQScrollView*>(this) ? dynamic_cast<TQScrollView*>(this)->viewport() :
dynamic_cast<TQWidget*>(this);
TQWidget *edit = dynamic_cast<TQWidget*>(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<TQWidget*>(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().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<TQWidget*>(this), msg);
else
KMessageBox::detailedSorry(dynamic_cast<TQWidget*>(this), msg, desc);
}
editCurrentCellAgain = true;
// allow = false;
}
else if (res == Validator::Warning) {
//js: todo: message!!!
KMessageBox::messageBox(dynamic_cast<TQWidget*>(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()->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()==TQVariant::String ? newval.toString() : TQString());
// m_editor->setFocus();
}
return false;
}
void KexiDataAwareObjectInterface::startEditCurrentCell(const TQString &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, 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<TQWidget*>(this),
i18n("Do you want to delete selected row?"), 0,
KGuiItem(i18n("&Delete Row"),"edit-delete"),
"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;
}
/*
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 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) {
TQString tableName = m_data->dbTableName();
if (!tableName.isEmpty()) {
tableName.prepend(" \"");
tableName.append("\"");
}
if (KMessageBox::Cancel == KMessageBox::warningContinueCancel(dynamic_cast<TQWidget*>(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<TQWidget*>(this) && dynamic_cast<TQWidget*>(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 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;
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 /*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 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()==TQt::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<TQWidget*>(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<TQWidget*>(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.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 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;
}