kexi

kexicomboboxbase.cpp

00001 /* This file is part of the KDE project
00002    Copyright (C) 2002 Peter Simonsson <psn@linux.se>
00003    Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl>
00004 
00005    This program is free software; you can redistribute it and/or
00006    modify it under the terms of the GNU Library General Public
00007    License as published by the Free Software Foundation; either
00008    version 2 of the License, or (at your option) any later version.
00009 
00010    This program is distributed in the hope that it will be useful,
00011    but WITHOUT ANY WARRANTY; without even the implied warranty of
00012    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013    Library General Public License for more details.
00014 
00015    You should have received a copy of the GNU Library General Public License
00016    along with this program; see the file COPYING.  If not, write to
00017    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00018  * Boston, MA 02110-1301, USA.
00019  */
00020 
00021 #include <qlayout.h>
00022 #include <qstyle.h>
00023 #include <qwindowsstyle.h>
00024 #include <qpainter.h>
00025 
00026 #include "kexicomboboxbase.h"
00027 #include <widget/utils/kexicomboboxdropdownbutton.h>
00028 #include "kexicomboboxpopup.h"
00029 #include "kexitableview.h"
00030 #include "kexitableitem.h"
00031 #include "kexi.h"
00032 
00033 #include <klineedit.h>
00034 
00035 KexiComboBoxBase::KexiComboBoxBase()
00036 {
00037     m_internalEditorValueChanged = false; //user has text or other value inside editor
00038     m_slotInternalEditorValueChanged_enabled = true;
00039     m_mouseBtnPressedWhenPopupVisible = false;
00040     m_insideCreatePopup = false;
00041     m_setValueOrTextInInternalEditor_enabled = true;
00042     m_updatePopupSelectionOnShow = true;
00043     m_moveCursorToEndInInternalEditor_enabled = true;
00044     m_selectAllInInternalEditor_enabled = true;
00045     m_setValueInInternalEditor_enabled = true;
00046     m_setVisibleValueOnSetValueInternal = false;
00047 }
00048 
00049 KexiComboBoxBase::~KexiComboBoxBase()
00050 {
00051 }
00052 
00053 KexiDB::LookupFieldSchema *KexiComboBoxBase::lookupFieldSchema() const
00054 {
00055     if (field() && field()->table())
00056         return field()->table()->lookupFieldSchema( *field() );
00057     return 0;
00058 }
00059 
00060 int KexiComboBoxBase::rowToHighlightForLookupTable() const
00061 {
00062     if (!popup())
00063         return -1;//err
00064     KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema();
00065     if (!lookupFieldSchema)
00066         return -1;
00067     if (lookupFieldSchema->boundColumn()==-1)
00068         return -1; //err
00069     bool ok;
00070     const int rowUid = origValue().toInt();
00072     KexiTableViewData *tvData = popup()->tableView()->data();
00073     const int boundColumn = lookupFieldSchema->boundColumn();
00074     KexiTableViewData::Iterator it(tvData->iterator());
00075     int row=0;
00076     for (;it.current();++it, row++)
00077     {
00078         if (it.current()->at(boundColumn).toInt(&ok) == rowUid && ok || !ok)
00079             break;
00080     }
00081     if (!ok || !it.current()) //item not found: highlight 1st row, if available
00082         return -1;
00083     return row;
00084 }
00085 
00086 void KexiComboBoxBase::setValueInternal(const QVariant& add_, bool removeOld)
00087 {
00088     Q_UNUSED(removeOld);
00089     m_mouseBtnPressedWhenPopupVisible = false;
00090     m_updatePopupSelectionOnShow = true;
00091     QString add(add_.toString());
00092     if (add.isEmpty()) {
00093         KexiTableViewData *relData = column() ? column()->relatedData() : 0;
00094         QVariant valueToSet;
00095         bool hasValueToSet = true;
00096         int rowToHighlight = -1;
00097         KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema();
00098         if (lookupFieldSchema) {
00099             //use 'lookup field' model
00101             if (lookupFieldSchema->boundColumn()==-1)
00103                 return;
00104             if (m_setVisibleValueOnSetValueInternal) {
00105                 //only for table views
00106                 if (!popup())
00107                     createPopup(false);
00108             }
00109             if (popup()) {
00110                 const int rowToHighlight = rowToHighlightForLookupTable();
00111                 popup()->tableView()->setHighlightedRow(rowToHighlight);
00112             }
00113             if (m_setVisibleValueOnSetValueInternal && -1!=lookupFieldSchema->visibleColumn()) {
00114                 //only for table views
00115                 KexiTableItem *it = popup()->tableView()->highlightedItem();
00116                 if (it)
00117                     valueToSet = it->at( lookupFieldSchema->visibleColumn() );
00118             }
00119             else {
00120                 hasValueToSet = false;
00121             }
00122         }
00123         else if (relData) {
00124             //use 'related table data' model
00125             valueToSet = valueForString(origValue().toString(), &rowToHighlight, 0, 1);
00126         }
00127         else {
00128             //use 'enum hints' model
00129             const int row = origValue().toInt();
00130             valueToSet = field()->enumHint(row).stripWhiteSpace();
00131         }
00132         if (hasValueToSet)
00133             setValueOrTextInInternalEditor( valueToSet );
00134         /*impl.*/moveCursorToEndInInternalEditor();
00135         /*impl.*/selectAllInInternalEditor();
00136         
00137         if (popup()) {
00138             if (origValue().isNull()) {
00139                 popup()->tableView()->clearSelection();
00140                 popup()->tableView()->setHighlightedRow(0);
00141             } else {
00142                 if (relData) {
00143                     if (rowToHighlight!=-1)
00144                         popup()->tableView()->setHighlightedRow(rowToHighlight);
00145                 }
00146                 else if (!lookupFieldSchema) {
00147                     //popup()->tableView()->selectRow(origValue().toInt());
00148                     popup()->tableView()->setHighlightedRow(origValue().toInt());
00149                 }
00150             }
00151         }
00152     }
00153     else {
00154         //todo: autocompl.?
00155         if (popup())
00156             popup()->tableView()->clearSelection();
00157         /*impl.*/setValueInInternalEditor(add); //not setLineEditText(), because 'add' is entered by user!
00158         //setLineEditText( add );
00159         /*impl.*/moveCursorToEndInInternalEditor();
00160     }
00161 }
00162 
00163 KexiTableItem* KexiComboBoxBase::selectItemForEnteredValueInLookupTable(const QVariant& v)
00164 {
00165     KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema();
00166     if (!popup() || !lookupFieldSchema)
00167         return 0; //safety
00168 //-not effective for large sets: please cache it!
00169 //.stripWhiteSpace() is not generic!
00170 
00171     const bool valueIsText = v.type()==QVariant::String || v.type()==QVariant::CString; //most common case
00172     const QString txt( valueIsText ? v.toString().stripWhiteSpace().lower() : QString::null );
00173     KexiTableViewData *lookupData = popup()->tableView()->data();
00174     const int visibleColumn = lookupFieldSchema->visibleColumn();
00175     KexiTableViewData::Iterator it(lookupData->iterator());
00176     int row;
00177     for (row = 0;it.current();++it, row++) {
00178         if (valueIsText) {
00179             if (it.current()->at(visibleColumn).toString().stripWhiteSpace().lower() == txt)
00180                 break;
00181         }
00182         else {
00183             if (it.current()->at(visibleColumn) == v)
00184                 break;
00185         }
00186     }
00187 
00188     m_setValueOrTextInInternalEditor_enabled = false; // <-- this is the entered value, 
00189                                                       //     so do not change the internal editor's contents
00190     if (it.current())
00191         popup()->tableView()->selectRow(row);
00192     else
00193         popup()->tableView()->clearSelection();
00194 
00195     m_setValueOrTextInInternalEditor_enabled = true;
00196 
00197     return it.current();
00198 }
00199 
00200 QString KexiComboBoxBase::valueForString(const QString& str, int* row, 
00201     uint lookInColumn, uint returnFromColumn, bool allowNulls)
00202 {
00203     KexiTableViewData *relData = column() ? column()->relatedData() : 0;
00204     if (!relData)
00205         return QString::null; //safety
00206     //use 'related table data' model
00207     //-not effective for large sets: please cache it!
00208     //.stripWhiteSpace() is not generic!
00209 
00210     const QString txt = str.stripWhiteSpace().lower();
00211     KexiTableViewData::Iterator it( relData->iterator() );
00212     for (*row = 0;it.current();++it, (*row)++) {
00213         if (it.current()->at(lookInColumn).toString().stripWhiteSpace().lower()==txt)
00214             break;
00215     }
00216     if (it.current())
00217         return it.current()->at(returnFromColumn).toString();
00218 
00219     *row = -1;
00220 
00221     if (column() && column()->relatedDataEditable())
00222         return str; //new value entered and that's allowed
00223 
00224     kexiwarn << "KexiComboBoxBase::valueForString(): no related row found, ID will be painted!" << endl;
00225     if (allowNulls)
00226         return QString::null;
00227     return str; //for sanity but it's weird to show id to the user
00228 }
00229 
00230 QVariant KexiComboBoxBase::value()
00231 {
00232     KexiTableViewData *relData = column() ? column()->relatedData() : 0;
00233     KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema();
00234     if (relData) {
00235         if (m_internalEditorValueChanged) {
00236             //we've user-entered text: look for id
00237 //TODO: make error if matching text not found?
00238             int rowToHighlight;
00239             return valueForString(m_userEnteredValue.toString(), &rowToHighlight, 1, 0, true/*allowNulls*/);
00240         }
00241         else {
00242             //use 'related table data' model
00243             KexiTableItem *it = popup() ? popup()->tableView()->selectedItem() : 0;
00244             return it ? it->at(0) : origValue();//QVariant();
00245         }
00246     }
00247     else if (lookupFieldSchema)
00248     {
00249         if (lookupFieldSchema->boundColumn()==-1)
00250             return origValue();
00251         KexiTableItem *it = popup() ? popup()->tableView()->selectedItem() : 0;
00252         if ( m_internalEditorValueChanged && !m_userEnteredValue.toString().isEmpty()) { //
00253             //try to select a row using the user-entered text
00254             if (!popup())
00255                 createPopup(false);
00256             it = selectItemForEnteredValueInLookupTable( m_userEnteredValue );
00257         }
00258         return it ? it->at( lookupFieldSchema->boundColumn() ) : QVariant();
00259     }
00260     else if (popup()) {
00261         //use 'enum hints' model
00262         const int row = popup()->tableView()->currentRow();
00263         if (row>=0)
00264             return QVariant( row );
00265     }
00266 
00267     if (valueFromInternalEditor().toString().isEmpty())
00268         return QVariant();
00271     return origValue(); //unchanged
00272 }
00273 
00274 QVariant KexiComboBoxBase::visibleValueForLookupField()
00275 {
00276     KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema();
00277     if (!popup() || !lookupFieldSchema)
00278         return QVariant();
00279     KexiTableItem *it = popup()->tableView()->selectedItem();
00280     return it ? it->at( lookupFieldSchema->visibleColumn() ) : QVariant();
00281 }
00282 
00283 QVariant KexiComboBoxBase::visibleValue()
00284 {
00285     return m_visibleValue;
00286 }
00287 
00288 void KexiComboBoxBase::clear()
00289 {
00290     if (popup())
00291         popup()->hide();
00292     slotInternalEditorValueChanged(QVariant());
00293 }
00294 
00295 tristate KexiComboBoxBase::valueChangedInternal()
00296 {
00297     //avoid comparing values:
00298     KexiTableViewData *relData = column() ? column()->relatedData() : 0;
00299     KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema();
00300     if (relData || lookupFieldSchema) {
00301         if (m_internalEditorValueChanged)
00302             return true;
00303 
00304         //use 'related table data' model
00305         KexiTableItem *it = popup() ? popup()->tableView()->selectedItem() : 0;
00306         if (!it)
00307             return false;
00308     }
00309     else {
00310         //use 'enum hints' model
00311         const int row = popup() ? popup()->tableView()->currentRow() : -1;
00312         if (row<0 && !m_internalEditorValueChanged/*true if text box is cleared*/)
00313             return false;
00314     }
00315 
00316     return cancelled;
00317 }
00318 
00319 bool KexiComboBoxBase::valueIsNull()
00320 {
00321 //  bool ok;
00322     QVariant v( value() );
00323     return v.isNull();
00324 //  return !ok || v.isNull();
00325 }
00326 
00327 bool KexiComboBoxBase::valueIsEmpty()
00328 {
00329     return valueIsNull();
00330 }
00331 
00332 void KexiComboBoxBase::showPopup()
00333 {
00334     createPopup(true);
00335 }
00336 
00337 void KexiComboBoxBase::createPopup(bool show)
00338 {
00339     if (!field())
00340         return;
00341     m_insideCreatePopup = true;
00342     QWidget* thisWidget = dynamic_cast<QWidget*>(this);
00343     QWidget *widgetToFocus = internalEditor() ? internalEditor() : thisWidget;
00344     if (!popup()) {
00345         setPopup( column() ? new KexiComboBoxPopup(thisWidget, *column()) 
00346             : new KexiComboBoxPopup(thisWidget, *field()) );
00347         QObject::connect(popup(), SIGNAL(rowAccepted(KexiTableItem*,int)), 
00348             thisWidget, SLOT(slotRowAccepted(KexiTableItem*,int)));
00349         QObject::connect(popup()->tableView(), SIGNAL(itemSelected(KexiTableItem*)),
00350             thisWidget, SLOT(slotItemSelected(KexiTableItem*)));
00351 
00352         popup()->setFocusProxy( widgetToFocus );    
00353         popup()->tableView()->setFocusProxy( widgetToFocus );
00354         popup()->installEventFilter(thisWidget);
00355 
00356         if (origValue().isNull())
00357             popup()->tableView()->clearSelection();
00358         else {
00359             popup()->tableView()->selectRow( 0 );
00360             popup()->tableView()->setHighlightedRow( 0 );
00361         }
00362     }
00363     if (show && internalEditor() && !internalEditor()->isVisible())
00364         /*emit*/editRequested();
00365 
00366     QPoint posMappedToGlobal = mapFromParentToGlobal(thisWidget->pos());
00367     if (posMappedToGlobal != QPoint(-1,-1)) {
00369         popup()->move( posMappedToGlobal + QPoint(0, thisWidget->height()) );
00370         //to avoid flickering: first resize to 0-height, then show and resize back to prev. height
00371         const int w = popupWidthHint();
00372         popup()->resize(w, 0);
00373         if (show)
00374             popup()->show();
00375         popup()->updateSize(w);
00376         if (m_updatePopupSelectionOnShow) {
00377             int rowToHighlight = -1;
00378             KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema();
00379             KexiTableViewData *relData = column() ? column()->relatedData() : 0;
00380             if (lookupFieldSchema) {
00381                 rowToHighlight = rowToHighlightForLookupTable();
00382             }
00383             else if (relData) {
00384                 (void)valueForString(origValue().toString(), &rowToHighlight, 0, 1);
00385             }
00386             else //enum hint
00387                 rowToHighlight = origValue().toInt();
00388 
00389 /*-->*/ m_moveCursorToEndInInternalEditor_enabled = show;
00390             m_selectAllInInternalEditor_enabled = show;
00391             m_setValueInInternalEditor_enabled = show;
00392             if (rowToHighlight==-1) {
00393                 rowToHighlight = QMAX( popup()->tableView()->highlightedRow(), 0);
00394                 setValueInInternalEditor(QVariant());
00395             }
00396             popup()->tableView()->selectRow( rowToHighlight );
00397             popup()->tableView()->setHighlightedRow( rowToHighlight );
00398             if (rowToHighlight < popup()->tableView()->rowsPerPage())
00399                 popup()->tableView()->ensureCellVisible( 0, -1 );
00400 
00401 /*-->*/ m_moveCursorToEndInInternalEditor_enabled = true;
00402             m_selectAllInInternalEditor_enabled = true;
00403             m_setValueInInternalEditor_enabled = true;
00404         }
00405     }
00406 
00407     if (show) {
00408         moveCursorToEndInInternalEditor();
00409         selectAllInInternalEditor();
00410         widgetToFocus->setFocus();
00411     }
00412     m_insideCreatePopup = false;
00413 }
00414 
00415 void KexiComboBoxBase::hide()
00416 {
00417     if (popup())
00418         popup()->hide();
00419 }
00420 
00421 void KexiComboBoxBase::slotRowAccepted(KexiTableItem * item, int row)
00422 {
00423     Q_UNUSED(row);
00424     //update our value
00425     //..nothing to do?
00426     updateButton();
00427     slotItemSelected(item);
00428     /*emit*/acceptRequested();
00429 }
00430 
00431 void KexiComboBoxBase::acceptPopupSelection()
00432 {
00433     if (!popup())
00434         return;
00435     KexiTableItem *item = popup()->tableView()->highlightedItem();
00436     if (item) {
00437         popup()->tableView()->selectRow( popup()->tableView()->highlightedRow() );
00438         slotRowAccepted(item, -1);
00439     }
00440     popup()->hide();
00441 }
00442 
00443 void KexiComboBoxBase::slotItemSelected(KexiTableItem*)
00444 {
00445     kexidbg << "KexiComboBoxBase::slotItemSelected(): m_visibleValue = " << m_visibleValue << endl;
00446 
00447     QVariant valueToSet;
00448     KexiTableViewData *relData = column() ? column()->relatedData() : 0;
00449     KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema();
00450 
00451     m_visibleValue = lookupFieldSchema ? visibleValueForLookupField() : QVariant();
00452 
00453     if (relData) {
00454         //use 'related table data' model
00455         KexiTableItem *item = popup()->tableView()->selectedItem();
00456         if (item)
00457             valueToSet = item->at(1);
00458     }
00459     else if (lookupFieldSchema) {
00460         KexiTableItem *item = popup()->tableView()->selectedItem();
00461         if (item && lookupFieldSchema->visibleColumn()!=-1 && (int)item->size() >= lookupFieldSchema->visibleColumn()) {
00462             valueToSet = item->at( lookupFieldSchema->visibleColumn() );
00463         }
00464     }
00465     else {
00466         //use 'enum hints' model
00467         valueToSet = field()->enumHint( popup()->tableView()->currentRow() );
00468         if (valueToSet.toString().isEmpty() && !m_insideCreatePopup) {
00469             clear();
00470             QWidget* thisWidget = dynamic_cast<QWidget*>(this);
00471             thisWidget->parentWidget()->setFocus();
00472             return;
00473         }
00474     }
00475     setValueOrTextInInternalEditor( valueToSet );
00476     if (m_setValueOrTextInInternalEditor_enabled) {
00477         moveCursorToEndInInternalEditor();
00478         selectAllInInternalEditor();
00479     }
00480     // a new (temp) popup table index is selected: do not update selection next time:
00481     m_updatePopupSelectionOnShow = false;
00482 }
00483 
00484 void KexiComboBoxBase::slotInternalEditorValueChanged(const QVariant& v)
00485 {
00486     if (!m_slotInternalEditorValueChanged_enabled)
00487         return;
00488     m_userEnteredValue = v;
00489     m_internalEditorValueChanged = true;
00490     if (v.toString().isEmpty()) {
00491         if (popup()) {
00492             popup()->tableView()->clearSelection();
00493         }
00494         return;
00495     }
00496 }
00497 
00498 void KexiComboBoxBase::setValueOrTextInInternalEditor(const QVariant& value)
00499 {
00500     if (!m_setValueOrTextInInternalEditor_enabled)
00501         return;
00502     setValueInInternalEditor( value );
00503     //this text is not entered by hand:
00504     m_userEnteredValue = QVariant();
00505     m_internalEditorValueChanged = false;
00506 }
00507 
00508 bool KexiComboBoxBase::handleKeyPressForPopup( QKeyEvent *ke )
00509 {
00510     const int k = ke->key();
00511     int highlightedOrSelectedRow = popup() ? popup()->tableView()->highlightedRow() : -1;
00512     if (popup() && highlightedOrSelectedRow < 0)
00513         highlightedOrSelectedRow = popup()->tableView()->currentRow();
00514 
00515     const bool enterPressed = k==Qt::Key_Enter || k==Qt::Key_Return;
00516 
00517     // The editor may be active but the pull down menu not existant/visible,
00518     // e.g. when the user has pressed a normal button to activate the editor
00519     // Don't handle the event here in that case.
00520     if (!popup() || (!enterPressed && !popup()->isVisible())) {
00521         return false;
00522     }
00523 
00524     switch (k) {
00525     case Qt::Key_Up:
00526             popup()->tableView()->setHighlightedRow( 
00527                 QMAX(highlightedOrSelectedRow-1, 0) );
00528             updateTextForHighlightedRow();
00529             return true;
00530     case Qt::Key_Down:
00531             popup()->tableView()->setHighlightedRow( 
00532                 QMIN(highlightedOrSelectedRow+1, popup()->tableView()->rows()-1) );
00533             updateTextForHighlightedRow();
00534             return true;
00535     case Qt::Key_PageUp:
00536             popup()->tableView()->setHighlightedRow( 
00537                 QMAX(highlightedOrSelectedRow-popup()->tableView()->rowsPerPage(), 0) );
00538             updateTextForHighlightedRow();
00539             return true;
00540     case Qt::Key_PageDown:
00541             popup()->tableView()->setHighlightedRow( 
00542                 QMIN(highlightedOrSelectedRow+popup()->tableView()->rowsPerPage(), 
00543                  popup()->tableView()->rows()-1) );
00544             updateTextForHighlightedRow();
00545             return true;
00546     case Qt::Key_Home:
00547             popup()->tableView()->setHighlightedRow( 0 );
00548             updateTextForHighlightedRow();
00549             return true;
00550     case Qt::Key_End:
00551             popup()->tableView()->setHighlightedRow( popup()->tableView()->rows()-1 );
00552             updateTextForHighlightedRow();
00553             return true;
00554     case Qt::Key_Enter:
00555     case Qt::Key_Return: //accept
00556             //select row that is highlighted
00557             if (popup()->tableView()->highlightedRow()>=0)
00558                 popup()->tableView()->selectRow( popup()->tableView()->highlightedRow() );
00559             //do not return true: allow to process event
00560     default: ;
00561     }
00562     return false;
00563 }
00564 
00565 void KexiComboBoxBase::updateTextForHighlightedRow()
00566 {
00567     KexiTableItem *item = popup() ? popup()->tableView()->highlightedItem() : 0;
00568     if (item)
00569         slotItemSelected(item);
00570 }
00571 
00572 void KexiComboBoxBase::undoChanges()
00573 {
00574     KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema();
00575     if (lookupFieldSchema) {
00576 //      kexidbg << "KexiComboBoxBase::undoChanges(): m_visibleValue BEFORE = " << m_visibleValue << endl;
00577         if (popup())
00578             popup()->tableView()->selectRow( popup()->tableView()->highlightedRow() );
00579         m_visibleValue = visibleValueForLookupField();
00580 //      kexidbg << "KexiComboBoxBase::undoChanges(): m_visibleValue AFTER = " << m_visibleValue << endl;
00581         setValueOrTextInInternalEditor( m_visibleValue );
00582     }
00583 }
KDE Home | KDE Accessibility Home | Description of Access Keys