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/kexicomboboxbase.cpp

598 lines
19 KiB

/* This file is part of the KDE project
Copyright (C) 2002 Peter Simonsson <psn@linux.se>
Copyright (C) 2003-2007 Jaroslaw Staniek <js@iidea.pl>
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 <layout.h>
#include <tqstyle.h>
#include <tqwindowsstyle.h>
#include <tqpainter.h>
#include "kexicomboboxbase.h"
#include <widget/utils/kexicomboboxdropdownbutton.h>
#include "kexicomboboxpopup.h"
#include "kexitableview.h"
#include "kexitableitem.h"
#include "kexi.h"
#include <klineedit.h>
KexiComboBoxBase::KexiComboBoxBase()
{
m_internalEditorValueChanged = false; //user has text or other value inside editor
m_slotInternalEditorValueChanged_enabled = true;
m_mouseBtnPressedWhenPopupVisible = false;
m_insideCreatePopup = false;
m_setValueOrTextInInternalEditor_enabled = true;
m_updatePopupSelectionOnShow = true;
m_moveCursorToEndInInternalEditor_enabled = true;
m_selectAllInInternalEditor_enabled = true;
m_setValueInInternalEditor_enabled = true;
m_setVisibleValueOnSetValueInternal = false;
}
KexiComboBoxBase::~KexiComboBoxBase()
{
}
KexiDB::LookupFieldSchema *KexiComboBoxBase::lookupFieldSchema() const
{
if (field() && field()->table()) {
KexiDB::LookupFieldSchema *lookupFieldSchema = field()->table()->lookupFieldSchema( *field() );
if (lookupFieldSchema && !lookupFieldSchema->rowSource().name().isEmpty())
return lookupFieldSchema;
}
return 0;
}
int KexiComboBoxBase::rowToHighlightForLookupTable() const
{
if (!popup())
return -1;//err
KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema();
if (!lookupFieldSchema)
return -1;
if (lookupFieldSchema->boundColumn()==-1)
return -1; //err
bool ok;
const int rowUid = origValue().toInt();
//! @todo for now we're assuming the id is INTEGER
KexiTableViewData *tvData = popup()->tableView()->KexiDataAwareObjectInterface::data();
const int boundColumn = lookupFieldSchema->boundColumn();
KexiTableViewData::Iterator it(tvData->iterator());
int row=0;
for (;it.current();++it, row++)
{
if (it.current()->at(boundColumn).toInt(&ok) == rowUid && ok || !ok)
break;
}
if (!ok || !it.current()) //item not found: highlight 1st row, if available
return -1;
return row;
}
void KexiComboBoxBase::setValueInternal(const TQVariant& add_, bool removeOld)
{
Q_UNUSED(removeOld);
m_mouseBtnPressedWhenPopupVisible = false;
m_updatePopupSelectionOnShow = true;
TQString add(add_.toString());
if (add.isEmpty()) {
KexiTableViewData *relData = column() ? column()->relatedData() : 0;
TQVariant valueToSet;
bool hasValueToSet = true;
int rowToHighlight = -1;
KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema();
if (lookupFieldSchema) {
//use 'lookup field' model
//! @todo support more RowSourceType's, not only table
if (lookupFieldSchema->boundColumn()==-1)
//! @todo errmsg
return;
if (m_setVisibleValueOnSetValueInternal) {
//only for table views
if (!popup())
createPopup(false/*!show*/);
}
if (popup()) {
const int rowToHighlight = rowToHighlightForLookupTable();
popup()->tableView()->setHighlightedRow(rowToHighlight);
const int visibleColumn = lookupFieldSchema->visibleColumn( popup()->tableView()->KexiDataAwareObjectInterface::data()->columnsCount() );
if (m_setVisibleValueOnSetValueInternal && -1!=visibleColumn) {
//only for table views
KexiTableItem *it = popup()->tableView()->highlightedItem();
if (it)
valueToSet = it->at( visibleColumn );
}
else {
hasValueToSet = false;
}
}
}
else if (relData) {
//use 'related table data' model
valueToSet = valueForString(origValue().toString(), &rowToHighlight, 0, 1);
}
else {
//use 'enum hints' model
const int row = origValue().toInt();
valueToSet = field()->enumHint(row).stripWhiteSpace();
}
if (hasValueToSet)
setValueOrTextInInternalEditor( valueToSet );
/*impl.*/moveCursorToEndInInternalEditor();
/*impl.*/selectAllInInternalEditor();
if (popup()) {
if (origValue().isNull()) {
popup()->tableView()->clearSelection();
popup()->tableView()->setHighlightedRow(0);
} else {
if (relData) {
if (rowToHighlight!=-1)
popup()->tableView()->setHighlightedRow(rowToHighlight);
}
else if (!lookupFieldSchema) {
//popup()->tableView()->selectRow(origValue().toInt());
popup()->tableView()->setHighlightedRow(origValue().toInt());
}
}
}
}
else {
//todo: autocompl.?
if (popup())
popup()->tableView()->clearSelection();
/*impl.*/setValueInInternalEditor(add); //not setLineEditText(), because 'add' is entered by user!
//setLineEditText( add );
/*impl.*/moveCursorToEndInInternalEditor();
}
}
KexiTableItem* KexiComboBoxBase::selectItemForEnteredValueInLookupTable(const TQVariant& v)
{
KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema();
if (!popup() || !lookupFieldSchema)
return 0; //safety
//-not effective for large sets: please cache it!
//.stripWhiteSpace() is not generic!
const bool valueIsText = v.type()==TQVariant::String || v.type()==TQVariant::CString; //most common case
const TQString txt( valueIsText ? v.toString().stripWhiteSpace().lower() : TQString() );
KexiTableViewData *lookupData = popup()->tableView()->KexiDataAwareObjectInterface::data();
const int visibleColumn = lookupFieldSchema->visibleColumn( lookupData->columnsCount() );
if (-1 == visibleColumn)
return 0;
KexiTableViewData::Iterator it(lookupData->iterator());
int row;
for (row = 0;it.current();++it, row++) {
if (valueIsText) {
if (it.current()->at(visibleColumn).toString().stripWhiteSpace().lower() == txt)
break;
}
else {
if (it.current()->at(visibleColumn) == v)
break;
}
}
m_setValueOrTextInInternalEditor_enabled = false; // <-- this is the entered value,
// so do not change the internal editor's contents
if (it.current())
popup()->tableView()->selectRow(row);
else
popup()->tableView()->clearSelection();
m_setValueOrTextInInternalEditor_enabled = true;
return it.current();
}
TQString KexiComboBoxBase::valueForString(const TQString& str, int* row,
uint lookInColumn, uint returnFromColumn, bool allowNulls)
{
KexiTableViewData *relData = column() ? column()->relatedData() : 0;
if (!relData)
return TQString(); //safety
//use 'related table data' model
//-not effective for large sets: please cache it!
//.stripWhiteSpace() is not generic!
const TQString txt = str.stripWhiteSpace().lower();
KexiTableViewData::Iterator it( relData->iterator() );
for (*row = 0;it.current();++it, (*row)++) {
if (it.current()->at(lookInColumn).toString().stripWhiteSpace().lower()==txt)
break;
}
if (it.current())
return it.current()->at(returnFromColumn).toString();
*row = -1;
if (column() && column()->relatedDataEditable())
return str; //new value entered and that's allowed
kexiwarn << "KexiComboBoxBase::valueForString(): no related row found, ID will be painted!" << endl;
if (allowNulls)
return TQString();
return str; //for sanity but it's weird to show id to the user
}
TQVariant KexiComboBoxBase::value()
{
KexiTableViewData *relData = column() ? column()->relatedData() : 0;
KexiDB::LookupFieldSchema *lookupFieldSchema = 0;
if (relData) {
if (m_internalEditorValueChanged) {
//we've user-entered text: look for id
//TODO: make error if matching text not found?
int rowToHighlight;
return valueForString(m_userEnteredValue.toString(), &rowToHighlight, 1, 0, true/*allowNulls*/);
}
else {
//use 'related table data' model
KexiTableItem *it = popup() ? popup()->tableView()->selectedItem() : 0;
return it ? it->at(0) : origValue();//TQVariant();
}
}
else if ((lookupFieldSchema = this->lookupFieldSchema()))
{
if (lookupFieldSchema->boundColumn()==-1)
return origValue();
KexiTableItem *it = popup() ? popup()->tableView()->selectedItem() : 0;
if (/*!it &&*/ m_internalEditorValueChanged && !m_userEnteredValue.toString().isEmpty()) { //
//try to select a row using the user-entered text
if (!popup()) {
TQVariant prevUserEnteredValue = m_userEnteredValue;
createPopup(false);
m_userEnteredValue = prevUserEnteredValue;
}
it = selectItemForEnteredValueInLookupTable( m_userEnteredValue );
}
return it ? it->at( lookupFieldSchema->boundColumn() ) : TQVariant();
}
else if (popup()) {
//use 'enum hints' model
const int row = popup()->tableView()->currentRow();
if (row>=0)
return TQVariant( row );
}
if (valueFromInternalEditor().toString().isEmpty())
return TQVariant();
/*! \todo don't return just 1st row, but use autocompletion feature
and: show message box if entered text does not match! */
return origValue(); //unchanged
}
TQVariant KexiComboBoxBase::visibleValueForLookupField()
{
KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema();
if (!popup() || !lookupFieldSchema)
return TQVariant();
const int visibleColumn = lookupFieldSchema->visibleColumn( popup()->tableView()->KexiDataAwareObjectInterface::data()->columnsCount() );
if (-1 == visibleColumn)
return TQVariant();
KexiTableItem *it = popup()->tableView()->selectedItem();
return it ? it->at( TQMIN( (uint)visibleColumn, it->count()-1)/*sanity*/ ) : TQVariant();
}
TQVariant KexiComboBoxBase::visibleValue()
{
return m_visibleValue;
}
void KexiComboBoxBase::clear()
{
if (popup())
popup()->hide();
slotInternalEditorValueChanged(TQVariant());
}
tristate KexiComboBoxBase::valueChangedInternal()
{
//avoid comparing values:
KexiTableViewData *relData = column() ? column()->relatedData() : 0;
KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema();
if (relData || lookupFieldSchema) {
if (m_internalEditorValueChanged)
return true;
//use 'related table data' model
KexiTableItem *it = popup() ? popup()->tableView()->selectedItem() : 0;
if (!it)
return false;
}
else {
//use 'enum hints' model
const int row = popup() ? popup()->tableView()->currentRow() : -1;
if (row<0 && !m_internalEditorValueChanged/*true if text box is cleared*/)
return false;
}
return cancelled;
}
bool KexiComboBoxBase::valueIsNull()
{
// bool ok;
TQVariant v( value() );
return v.isNull();
// return !ok || v.isNull();
}
bool KexiComboBoxBase::valueIsEmpty()
{
return valueIsNull();
}
void KexiComboBoxBase::showPopup()
{
createPopup(true);
}
void KexiComboBoxBase::createPopup(bool show)
{
if (!field())
return;
m_insideCreatePopup = true;
TQWidget* thisWidget = dynamic_cast<TQWidget*>(this);
TQWidget *widgetToFocus = internalEditor() ? internalEditor() : thisWidget;
if (!popup()) {
setPopup( column() ? new KexiComboBoxPopup(thisWidget, *column())
: new KexiComboBoxPopup(thisWidget, *field()) );
TQObject::connect(popup(), TQT_SIGNAL(rowAccepted(KexiTableItem*,int)),
thisWidget, TQT_SLOT(slotRowAccepted(KexiTableItem*,int)));
TQObject::connect(popup()->tableView(), TQT_SIGNAL(itemSelected(KexiTableItem*)),
thisWidget, TQT_SLOT(slotItemSelected(KexiTableItem*)));
popup()->setFocusProxy( widgetToFocus );
popup()->tableView()->setFocusProxy( widgetToFocus );
popup()->installEventFilter(thisWidget);
if (origValue().isNull())
popup()->tableView()->clearSelection();
else {
popup()->tableView()->selectRow( 0 );
popup()->tableView()->setHighlightedRow( 0 );
}
}
if (show && internalEditor() && !internalEditor()->isVisible())
/*emit*/editRequested();
TQPoint posMappedToGlobal = mapFromParentToGlobal(thisWidget->pos());
if (posMappedToGlobal != TQPoint(-1,-1)) {
//! todo alter the position to fit the popup within screen boundaries
popup()->move( posMappedToGlobal + TQPoint(0, thisWidget->height()) );
//to avoid flickering: first resize to 0-height, then show and resize back to prev. height
const int w = popupWidthHint();
popup()->resize(w, 0);
if (show)
popup()->show();
popup()->updateSize(w);
if (m_updatePopupSelectionOnShow) {
int rowToHighlight = -1;
KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema();
KexiTableViewData *relData = column() ? column()->relatedData() : 0;
if (lookupFieldSchema) {
rowToHighlight = rowToHighlightForLookupTable();
}
else if (relData) {
(void)valueForString(origValue().toString(), &rowToHighlight, 0, 1);
}
else //enum hint
rowToHighlight = origValue().toInt();
/*-->*/ m_moveCursorToEndInInternalEditor_enabled = show;
m_selectAllInInternalEditor_enabled = show;
m_setValueInInternalEditor_enabled = show;
if (rowToHighlight==-1) {
rowToHighlight = TQMAX( popup()->tableView()->highlightedRow(), 0);
setValueInInternalEditor(TQVariant());
}
popup()->tableView()->selectRow( rowToHighlight );
popup()->tableView()->setHighlightedRow( rowToHighlight );
if (rowToHighlight < popup()->tableView()->rowsPerPage())
popup()->tableView()->ensureCellVisible( 0, -1 );
/*-->*/ m_moveCursorToEndInInternalEditor_enabled = true;
m_selectAllInInternalEditor_enabled = true;
m_setValueInInternalEditor_enabled = true;
}
}
if (show) {
moveCursorToEndInInternalEditor();
selectAllInInternalEditor();
widgetToFocus->setFocus();
}
m_insideCreatePopup = false;
}
void KexiComboBoxBase::hide()
{
if (popup())
popup()->hide();
}
void KexiComboBoxBase::slotRowAccepted(KexiTableItem * item, int row)
{
Q_UNUSED(row);
//update our value
//..nothing to do?
updateButton();
slotItemSelected(item);
/*emit*/acceptRequested();
}
void KexiComboBoxBase::acceptPopupSelection()
{
if (!popup())
return;
KexiTableItem *item = popup()->tableView()->highlightedItem();
if (item) {
popup()->tableView()->selectRow( popup()->tableView()->highlightedRow() );
slotRowAccepted(item, -1);
}
popup()->hide();
}
void KexiComboBoxBase::slotItemSelected(KexiTableItem*)
{
kexidbg << "KexiComboBoxBase::slotItemSelected(): m_visibleValue = " << m_visibleValue << endl;
TQVariant valueToSet;
KexiTableViewData *relData = column() ? column()->relatedData() : 0;
KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema();
m_visibleValue = lookupFieldSchema ? visibleValueForLookupField() : TQVariant();
if (relData) {
//use 'related table data' model
KexiTableItem *item = popup()->tableView()->selectedItem();
if (item)
valueToSet = item->at(1);
}
else if (lookupFieldSchema) {
KexiTableItem *item = popup()->tableView()->selectedItem();
const int visibleColumn = lookupFieldSchema->visibleColumn( popup()->tableView()->KexiDataAwareObjectInterface::data()->columnsCount() );
if (item && visibleColumn!=-1 /* && (int)item->size() >= visibleColumn --already checked*/) {
valueToSet = item->at( TQMIN( (uint)visibleColumn, item->count()-1)/*sanity*/ );
}
}
else {
//use 'enum hints' model
valueToSet = field()->enumHint( popup()->tableView()->currentRow() );
if (valueToSet.toString().isEmpty() && !m_insideCreatePopup) {
clear();
TQWidget* thisWidget = dynamic_cast<TQWidget*>(this);
thisWidget->parentWidget()->setFocus();
return;
}
}
setValueOrTextInInternalEditor( valueToSet );
if (m_setValueOrTextInInternalEditor_enabled) {
moveCursorToEndInInternalEditor();
selectAllInInternalEditor();
}
// a new (temp) popup table index is selected: do not update selection next time:
m_updatePopupSelectionOnShow = false;
}
void KexiComboBoxBase::slotInternalEditorValueChanged(const TQVariant& v)
{
if (!m_slotInternalEditorValueChanged_enabled)
return;
m_userEnteredValue = v;
m_internalEditorValueChanged = true;
if (v.toString().isEmpty()) {
if (popup()) {
popup()->tableView()->clearSelection();
}
return;
}
}
void KexiComboBoxBase::setValueOrTextInInternalEditor(const TQVariant& value)
{
if (!m_setValueOrTextInInternalEditor_enabled)
return;
setValueInInternalEditor( value );
//this text is not entered by hand:
m_userEnteredValue = TQVariant();
m_internalEditorValueChanged = false;
}
bool KexiComboBoxBase::handleKeyPressForPopup( TQKeyEvent *ke )
{
const int k = ke->key();
int highlightedOrSelectedRow = popup() ? popup()->tableView()->highlightedRow() : -1;
if (popup() && highlightedOrSelectedRow < 0)
highlightedOrSelectedRow = popup()->tableView()->currentRow();
const bool enterPressed = k==TQt::Key_Enter || k==TQt::Key_Return;
// The editor may be active but the pull down menu not existant/visible,
// e.g. when the user has pressed a normal button to activate the editor
// Don't handle the event here in that case.
if (!popup() || (!enterPressed && !popup()->isVisible())) {
return false;
}
switch (k) {
case TQt::Key_Up:
popup()->tableView()->setHighlightedRow(
TQMAX(highlightedOrSelectedRow-1, 0) );
updateTextForHighlightedRow();
return true;
case TQt::Key_Down:
popup()->tableView()->setHighlightedRow(
TQMIN(highlightedOrSelectedRow+1, popup()->tableView()->rows()-1) );
updateTextForHighlightedRow();
return true;
case TQt::Key_PageUp:
popup()->tableView()->setHighlightedRow(
TQMAX(highlightedOrSelectedRow-popup()->tableView()->rowsPerPage(), 0) );
updateTextForHighlightedRow();
return true;
case TQt::Key_PageDown:
popup()->tableView()->setHighlightedRow(
TQMIN(highlightedOrSelectedRow+popup()->tableView()->rowsPerPage(),
popup()->tableView()->rows()-1) );
updateTextForHighlightedRow();
return true;
case TQt::Key_Home:
popup()->tableView()->setHighlightedRow( 0 );
updateTextForHighlightedRow();
return true;
case TQt::Key_End:
popup()->tableView()->setHighlightedRow( popup()->tableView()->rows()-1 );
updateTextForHighlightedRow();
return true;
case TQt::Key_Enter:
case TQt::Key_Return: //accept
//select row that is highlighted
if (popup()->tableView()->highlightedRow()>=0)
popup()->tableView()->selectRow( popup()->tableView()->highlightedRow() );
//do not return true: allow to process event
default: ;
}
return false;
}
void KexiComboBoxBase::updateTextForHighlightedRow()
{
KexiTableItem *item = popup() ? popup()->tableView()->highlightedItem() : 0;
if (item)
slotItemSelected(item);
}
void KexiComboBoxBase::undoChanges()
{
KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema();
if (lookupFieldSchema) {
// kexidbg << "KexiComboBoxBase::undoChanges(): m_visibleValue BEFORE = " << m_visibleValue << endl;
if (popup())
popup()->tableView()->selectRow( popup()->tableView()->highlightedRow() );
m_visibleValue = visibleValueForLookupField();
// kexidbg << "KexiComboBoxBase::undoChanges(): m_visibleValue AFTER = " << m_visibleValue << endl;
setValueOrTextInInternalEditor( m_visibleValue );
}
}