Kexi API Documentation (2.0 alpha)

kexicomboboxbase.cpp

Go to the documentation of this file.
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     if (-1 == visibleColumn)
00176         return 0;
00177     KexiTableViewData::Iterator it(lookupData->iterator());
00178     int row;
00179     for (row = 0;it.current();++it, row++) {
00180         if (valueIsText) {
00181             if (it.current()->at(visibleColumn).toString().stripWhiteSpace().lower() == txt)
00182                 break;
00183         }
00184         else {
00185             if (it.current()->at(visibleColumn) == v)
00186                 break;
00187         }
00188     }
00189 
00190     m_setValueOrTextInInternalEditor_enabled = false; // <-- this is the entered value, 
00191                                                       //     so do not change the internal editor's contents
00192     if (it.current())
00193         popup()->tableView()->selectRow(row);
00194     else
00195         popup()->tableView()->clearSelection();
00196 
00197     m_setValueOrTextInInternalEditor_enabled = true;
00198 
00199     return it.current();
00200 }
00201 
00202 QString KexiComboBoxBase::valueForString(const QString& str, int* row, 
00203     uint lookInColumn, uint returnFromColumn, bool allowNulls)
00204 {
00205     KexiTableViewData *relData = column() ? column()->relatedData() : 0;
00206     if (!relData)
00207         return QString::null; //safety
00208     //use 'related table data' model
00209     //-not effective for large sets: please cache it!
00210     //.stripWhiteSpace() is not generic!
00211 
00212     const QString txt = str.stripWhiteSpace().lower();
00213     KexiTableViewData::Iterator it( relData->iterator() );
00214     for (*row = 0;it.current();++it, (*row)++) {
00215         if (it.current()->at(lookInColumn).toString().stripWhiteSpace().lower()==txt)
00216             break;
00217     }
00218     if (it.current())
00219         return it.current()->at(returnFromColumn).toString();
00220 
00221     *row = -1;
00222 
00223     if (column() && column()->relatedDataEditable())
00224         return str; //new value entered and that's allowed
00225 
00226     kexiwarn << "KexiComboBoxBase::valueForString(): no related row found, ID will be painted!" << endl;
00227     if (allowNulls)
00228         return QString::null;
00229     return str; //for sanity but it's weird to show id to the user
00230 }
00231 
00232 QVariant KexiComboBoxBase::value()
00233 {
00234     KexiTableViewData *relData = column() ? column()->relatedData() : 0;
00235     KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema();
00236     if (relData) {
00237         if (m_internalEditorValueChanged) {
00238             //we've user-entered text: look for id
00239 //TODO: make error if matching text not found?
00240             int rowToHighlight;
00241             return valueForString(m_userEnteredValue.toString(), &rowToHighlight, 1, 0, true/*allowNulls*/);
00242         }
00243         else {
00244             //use 'related table data' model
00245             KexiTableItem *it = popup() ? popup()->tableView()->selectedItem() : 0;
00246             return it ? it->at(0) : origValue();//QVariant();
00247         }
00248     }
00249     else if (lookupFieldSchema)
00250     {
00251         if (lookupFieldSchema->boundColumn()==-1)
00252             return origValue();
00253         KexiTableItem *it = popup() ? popup()->tableView()->selectedItem() : 0;
00254         if ( m_internalEditorValueChanged && !m_userEnteredValue.toString().isEmpty()) { //
00255             //try to select a row using the user-entered text
00256             if (!popup())
00257                 createPopup(false);
00258             it = selectItemForEnteredValueInLookupTable( m_userEnteredValue );
00259         }
00260         return it ? it->at( lookupFieldSchema->boundColumn() ) : QVariant();
00261     }
00262     else if (popup()) {
00263         //use 'enum hints' model
00264         const int row = popup()->tableView()->currentRow();
00265         if (row>=0)
00266             return QVariant( row );
00267     }
00268 
00269     if (valueFromInternalEditor().toString().isEmpty())
00270         return QVariant();
00273     return origValue(); //unchanged
00274 }
00275 
00276 QVariant KexiComboBoxBase::visibleValueForLookupField()
00277 {
00278     KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema();
00279     if (!popup() || !lookupFieldSchema || -1 == lookupFieldSchema->visibleColumn())
00280         return QVariant();
00281     KexiTableItem *it = popup()->tableView()->selectedItem();
00282     return it ? it->at( lookupFieldSchema->visibleColumn() ) : QVariant();
00283 }
00284 
00285 QVariant KexiComboBoxBase::visibleValue()
00286 {
00287     return m_visibleValue;
00288 }
00289 
00290 void KexiComboBoxBase::clear()
00291 {
00292     if (popup())
00293         popup()->hide();
00294     slotInternalEditorValueChanged(QVariant());
00295 }
00296 
00297 tristate KexiComboBoxBase::valueChangedInternal()
00298 {
00299     //avoid comparing values:
00300     KexiTableViewData *relData = column() ? column()->relatedData() : 0;
00301     KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema();
00302     if (relData || lookupFieldSchema) {
00303         if (m_internalEditorValueChanged)
00304             return true;
00305 
00306         //use 'related table data' model
00307         KexiTableItem *it = popup() ? popup()->tableView()->selectedItem() : 0;
00308         if (!it)
00309             return false;
00310     }
00311     else {
00312         //use 'enum hints' model
00313         const int row = popup() ? popup()->tableView()->currentRow() : -1;
00314         if (row<0 && !m_internalEditorValueChanged/*true if text box is cleared*/)
00315             return false;
00316     }
00317 
00318     return cancelled;
00319 }
00320 
00321 bool KexiComboBoxBase::valueIsNull()
00322 {
00323 //  bool ok;
00324     QVariant v( value() );
00325     return v.isNull();
00326 //  return !ok || v.isNull();
00327 }
00328 
00329 bool KexiComboBoxBase::valueIsEmpty()
00330 {
00331     return valueIsNull();
00332 }
00333 
00334 void KexiComboBoxBase::showPopup()
00335 {
00336     createPopup(true);
00337 }
00338 
00339 void KexiComboBoxBase::createPopup(bool show)
00340 {
00341     if (!field())
00342         return;
00343     m_insideCreatePopup = true;
00344     QWidget* thisWidget = dynamic_cast<QWidget*>(this);
00345     QWidget *widgetToFocus = internalEditor() ? internalEditor() : thisWidget;
00346     if (!popup()) {
00347         setPopup( column() ? new KexiComboBoxPopup(thisWidget, *column()) 
00348             : new KexiComboBoxPopup(thisWidget, *field()) );
00349         QObject::connect(popup(), SIGNAL(rowAccepted(KexiTableItem*,int)), 
00350             thisWidget, SLOT(slotRowAccepted(KexiTableItem*,int)));
00351         QObject::connect(popup()->tableView(), SIGNAL(itemSelected(KexiTableItem*)),
00352             thisWidget, SLOT(slotItemSelected(KexiTableItem*)));
00353 
00354         popup()->setFocusProxy( widgetToFocus );    
00355         popup()->tableView()->setFocusProxy( widgetToFocus );
00356         popup()->installEventFilter(thisWidget);
00357 
00358         if (origValue().isNull())
00359             popup()->tableView()->clearSelection();
00360         else {
00361             popup()->tableView()->selectRow( 0 );
00362             popup()->tableView()->setHighlightedRow( 0 );
00363         }
00364     }
00365     if (show && internalEditor() && !internalEditor()->isVisible())
00366         /*emit*/editRequested();
00367 
00368     QPoint posMappedToGlobal = mapFromParentToGlobal(thisWidget->pos());
00369     if (posMappedToGlobal != QPoint(-1,-1)) {
00371         popup()->move( posMappedToGlobal + QPoint(0, thisWidget->height()) );
00372         //to avoid flickering: first resize to 0-height, then show and resize back to prev. height
00373         const int w = popupWidthHint();
00374         popup()->resize(w, 0);
00375         if (show)
00376             popup()->show();
00377         popup()->updateSize(w);
00378         if (m_updatePopupSelectionOnShow) {
00379             int rowToHighlight = -1;
00380             KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema();
00381             KexiTableViewData *relData = column() ? column()->relatedData() : 0;
00382             if (lookupFieldSchema) {
00383                 rowToHighlight = rowToHighlightForLookupTable();
00384             }
00385             else if (relData) {
00386                 (void)valueForString(origValue().toString(), &rowToHighlight, 0, 1);
00387             }
00388             else //enum hint
00389                 rowToHighlight = origValue().toInt();
00390 
00391 /*-->*/ m_moveCursorToEndInInternalEditor_enabled = show;
00392             m_selectAllInInternalEditor_enabled = show;
00393             m_setValueInInternalEditor_enabled = show;
00394             if (rowToHighlight==-1) {
00395                 rowToHighlight = QMAX( popup()->tableView()->highlightedRow(), 0);
00396                 setValueInInternalEditor(QVariant());
00397             }
00398             popup()->tableView()->selectRow( rowToHighlight );
00399             popup()->tableView()->setHighlightedRow( rowToHighlight );
00400             if (rowToHighlight < popup()->tableView()->rowsPerPage())
00401                 popup()->tableView()->ensureCellVisible( 0, -1 );
00402 
00403 /*-->*/ m_moveCursorToEndInInternalEditor_enabled = true;
00404             m_selectAllInInternalEditor_enabled = true;
00405             m_setValueInInternalEditor_enabled = true;
00406         }
00407     }
00408 
00409     if (show) {
00410         moveCursorToEndInInternalEditor();
00411         selectAllInInternalEditor();
00412         widgetToFocus->setFocus();
00413     }
00414     m_insideCreatePopup = false;
00415 }
00416 
00417 void KexiComboBoxBase::hide()
00418 {
00419     if (popup())
00420         popup()->hide();
00421 }
00422 
00423 void KexiComboBoxBase::slotRowAccepted(KexiTableItem * item, int row)
00424 {
00425     Q_UNUSED(row);
00426     //update our value
00427     //..nothing to do?
00428     updateButton();
00429     slotItemSelected(item);
00430     /*emit*/acceptRequested();
00431 }
00432 
00433 void KexiComboBoxBase::acceptPopupSelection()
00434 {
00435     if (!popup())
00436         return;
00437     KexiTableItem *item = popup()->tableView()->highlightedItem();
00438     if (item) {
00439         popup()->tableView()->selectRow( popup()->tableView()->highlightedRow() );
00440         slotRowAccepted(item, -1);
00441     }
00442     popup()->hide();
00443 }
00444 
00445 void KexiComboBoxBase::slotItemSelected(KexiTableItem*)
00446 {
00447     kexidbg << "KexiComboBoxBase::slotItemSelected(): m_visibleValue = " << m_visibleValue << endl;
00448 
00449     QVariant valueToSet;
00450     KexiTableViewData *relData = column() ? column()->relatedData() : 0;
00451     KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema();
00452 
00453     m_visibleValue = lookupFieldSchema ? visibleValueForLookupField() : QVariant();
00454 
00455     if (relData) {
00456         //use 'related table data' model
00457         KexiTableItem *item = popup()->tableView()->selectedItem();
00458         if (item)
00459             valueToSet = item->at(1);
00460     }
00461     else if (lookupFieldSchema) {
00462         KexiTableItem *item = popup()->tableView()->selectedItem();
00463         if (item && lookupFieldSchema->visibleColumn()!=-1 && (int)item->size() >= lookupFieldSchema->visibleColumn()) {
00464             valueToSet = item->at( lookupFieldSchema->visibleColumn() );
00465         }
00466     }
00467     else {
00468         //use 'enum hints' model
00469         valueToSet = field()->enumHint( popup()->tableView()->currentRow() );
00470         if (valueToSet.toString().isEmpty() && !m_insideCreatePopup) {
00471             clear();
00472             QWidget* thisWidget = dynamic_cast<QWidget*>(this);
00473             thisWidget->parentWidget()->setFocus();
00474             return;
00475         }
00476     }
00477     setValueOrTextInInternalEditor( valueToSet );
00478     if (m_setValueOrTextInInternalEditor_enabled) {
00479         moveCursorToEndInInternalEditor();
00480         selectAllInInternalEditor();
00481     }
00482     // a new (temp) popup table index is selected: do not update selection next time:
00483     m_updatePopupSelectionOnShow = false;
00484 }
00485 
00486 void KexiComboBoxBase::slotInternalEditorValueChanged(const QVariant& v)
00487 {
00488     if (!m_slotInternalEditorValueChanged_enabled)
00489         return;
00490     m_userEnteredValue = v;
00491     m_internalEditorValueChanged = true;
00492     if (v.toString().isEmpty()) {
00493         if (popup()) {
00494             popup()->tableView()->clearSelection();
00495         }
00496         return;
00497     }
00498 }
00499 
00500 void KexiComboBoxBase::setValueOrTextInInternalEditor(const QVariant& value)
00501 {
00502     if (!m_setValueOrTextInInternalEditor_enabled)
00503         return;
00504     setValueInInternalEditor( value );
00505     //this text is not entered by hand:
00506     m_userEnteredValue = QVariant();
00507     m_internalEditorValueChanged = false;
00508 }
00509 
00510 bool KexiComboBoxBase::handleKeyPressForPopup( QKeyEvent *ke )
00511 {
00512     const int k = ke->key();
00513     int highlightedOrSelectedRow = popup() ? popup()->tableView()->highlightedRow() : -1;
00514     if (popup() && highlightedOrSelectedRow < 0)
00515         highlightedOrSelectedRow = popup()->tableView()->currentRow();
00516 
00517     const bool enterPressed = k==Qt::Key_Enter || k==Qt::Key_Return;
00518 
00519     // The editor may be active but the pull down menu not existant/visible,
00520     // e.g. when the user has pressed a normal button to activate the editor
00521     // Don't handle the event here in that case.
00522     if (!popup() || (!enterPressed && !popup()->isVisible())) {
00523         return false;
00524     }
00525 
00526     switch (k) {
00527     case Qt::Key_Up:
00528             popup()->tableView()->setHighlightedRow( 
00529                 QMAX(highlightedOrSelectedRow-1, 0) );
00530             updateTextForHighlightedRow();
00531             return true;
00532     case Qt::Key_Down:
00533             popup()->tableView()->setHighlightedRow( 
00534                 QMIN(highlightedOrSelectedRow+1, popup()->tableView()->rows()-1) );
00535             updateTextForHighlightedRow();
00536             return true;
00537     case Qt::Key_PageUp:
00538             popup()->tableView()->setHighlightedRow( 
00539                 QMAX(highlightedOrSelectedRow-popup()->tableView()->rowsPerPage(), 0) );
00540             updateTextForHighlightedRow();
00541             return true;
00542     case Qt::Key_PageDown:
00543             popup()->tableView()->setHighlightedRow( 
00544                 QMIN(highlightedOrSelectedRow+popup()->tableView()->rowsPerPage(), 
00545                  popup()->tableView()->rows()-1) );
00546             updateTextForHighlightedRow();
00547             return true;
00548     case Qt::Key_Home:
00549             popup()->tableView()->setHighlightedRow( 0 );
00550             updateTextForHighlightedRow();
00551             return true;
00552     case Qt::Key_End:
00553             popup()->tableView()->setHighlightedRow( popup()->tableView()->rows()-1 );
00554             updateTextForHighlightedRow();
00555             return true;
00556     case Qt::Key_Enter:
00557     case Qt::Key_Return: //accept
00558             //select row that is highlighted
00559             if (popup()->tableView()->highlightedRow()>=0)
00560                 popup()->tableView()->selectRow( popup()->tableView()->highlightedRow() );
00561             //do not return true: allow to process event
00562     default: ;
00563     }
00564     return false;
00565 }
00566 
00567 void KexiComboBoxBase::updateTextForHighlightedRow()
00568 {
00569     KexiTableItem *item = popup() ? popup()->tableView()->highlightedItem() : 0;
00570     if (item)
00571         slotItemSelected(item);
00572 }
00573 
00574 void KexiComboBoxBase::undoChanges()
00575 {
00576     KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema();
00577     if (lookupFieldSchema) {
00578 //      kexidbg << "KexiComboBoxBase::undoChanges(): m_visibleValue BEFORE = " << m_visibleValue << endl;
00579         if (popup())
00580             popup()->tableView()->selectRow( popup()->tableView()->highlightedRow() );
00581         m_visibleValue = visibleValueForLookupField();
00582 //      kexidbg << "KexiComboBoxBase::undoChanges(): m_visibleValue AFTER = " << m_visibleValue << endl;
00583         setValueOrTextInInternalEditor( m_visibleValue );
00584     }
00585 }
KDE Logo
This file is part of the documentation for Kexi 2.0 alpha.
Documentation copyright © 2002-2007 the Kexi Team.
Generated on Tue Apr 1 20:48:07 2008 by doxygen 1.4.2 written by Dimitri van Heesch, © 1997-2003