kexicomboboxbase.cpp
1/* This file is part of the KDE project
2 Copyright (C) 2002 Peter Simonsson <psn@linux.se>
3 Copyright (C) 2003-2007 Jaroslaw Staniek <js@iidea.pl>
4
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Library General Public
7 License as published by the Free Software Foundation; either
8 version 2 of the License, or (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Library General Public License for more details.
14
15 You should have received a copy of the GNU Library General Public License
16 along with this program; see the file COPYING. If not, write to
17 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 */
20
21#include <tqlayout.h>
22#include <tqstyle.h>
23#include <tqwindowsstyle.h>
24#include <tqpainter.h>
25
26#include "kexicomboboxbase.h"
27#include <widget/utils/kexicomboboxdropdownbutton.h>
28#include "kexicomboboxpopup.h"
29#include "kexitableview.h"
30#include "kexitableitem.h"
31#include "kexi.h"
32
33#include <klineedit.h>
34
35KexiComboBoxBase::KexiComboBoxBase()
36{
37 m_internalEditorValueChanged = false; //user has text or other value inside editor
40 m_insideCreatePopup = false;
43 m_moveCursorToEndInInternalEditor_enabled = true;
44 m_selectAllInInternalEditor_enabled = true;
45 m_setValueInInternalEditor_enabled = true;
47}
48
49KexiComboBoxBase::~KexiComboBoxBase()
50{
51}
52
62
64{
65 if (!popup())
66 return -1;//err
68 if (!lookupFieldSchema)
69 return -1;
71 return -1; //err
72 bool ok;
73 const int rowUid = origValue().toInt();
75 KexiTableViewData *tvData = popup()->tableView()->KexiDataAwareObjectInterface::data();
76 const int boundColumn = lookupFieldSchema->boundColumn();
77 KexiTableViewData::Iterator it(tvData->iterator());
78 int row=0;
79 for (;it.current();++it, row++)
80 {
81 if (it.current()->at(boundColumn).toInt(&ok) == rowUid && ok || !ok)
82 break;
83 }
84 if (!ok || !it.current()) //item not found: highlight 1st row, if available
85 return -1;
86 return row;
87}
88
89void KexiComboBoxBase::setValueInternal(const TQVariant& add_, bool removeOld)
90{
91 Q_UNUSED(removeOld);
94 TQString add(add_.toString());
95 if (add.isEmpty()) {
96 KexiTableViewData *relData = column() ? column()->relatedData() : 0;
97 TQVariant valueToSet;
98 bool hasValueToSet = true;
99 int rowToHighlight = -1;
101 if (lookupFieldSchema) {
102 //use 'lookup field' model
106 return;
108 //only for table views
109 if (!popup())
110 createPopup(false);
111 }
112 if (popup()) {
113 const int rowToHighlight = rowToHighlightForLookupTable();
114 popup()->tableView()->setHighlightedRow(rowToHighlight);
115
116 const int visibleColumn = lookupFieldSchema->visibleColumn( popup()->tableView()->KexiDataAwareObjectInterface::data()->columnsCount() );
117 if (m_setVisibleValueOnSetValueInternal && -1!=visibleColumn) {
118 //only for table views
119 KexiTableItem *it = popup()->tableView()->highlightedItem();
120 if (it)
121 valueToSet = it->at( visibleColumn );
122 }
123 else {
124 hasValueToSet = false;
125 }
126 }
127 }
128 else if (relData) {
129 //use 'related table data' model
130 valueToSet = valueForString(origValue().toString(), &rowToHighlight, 0, 1);
131 }
132 else {
133 //use 'enum hints' model
134 const int row = origValue().toInt();
135 valueToSet = field()->enumHint(row).stripWhiteSpace();
136 }
137 if (hasValueToSet)
138 setValueOrTextInInternalEditor( valueToSet );
140 /*impl.*/selectAllInInternalEditor();
141
142 if (popup()) {
143 if (origValue().isNull()) {
144 popup()->tableView()->clearSelection();
145 popup()->tableView()->setHighlightedRow(0);
146 } else {
147 if (relData) {
148 if (rowToHighlight!=-1)
149 popup()->tableView()->setHighlightedRow(rowToHighlight);
150 }
151 else if (!lookupFieldSchema) {
152 //popup()->tableView()->selectRow(origValue().toInt());
153 popup()->tableView()->setHighlightedRow(origValue().toInt());
154 }
155 }
156 }
157 }
158 else {
159 //todo: autocompl.?
160 if (popup())
161 popup()->tableView()->clearSelection();
162 /*impl.*/setValueInInternalEditor(add); //not setLineEditText(), because 'add' is entered by user!
163 //setLineEditText( add );
165 }
166}
167
169{
171 if (!popup() || !lookupFieldSchema)
172 return 0; //safety
173//-not effective for large sets: please cache it!
174//.stripWhiteSpace() is not generic!
175
176 const bool valueIsText = v.type()==TQVariant::String || v.type()==TQVariant::CString; //most common case
177 const TQString txt( valueIsText ? v.toString().stripWhiteSpace().lower() : TQString() );
178 KexiTableViewData *lookupData = popup()->tableView()->KexiDataAwareObjectInterface::data();
179 const int visibleColumn = lookupFieldSchema->visibleColumn( lookupData->columnsCount() );
180 if (-1 == visibleColumn)
181 return 0;
182 KexiTableViewData::Iterator it(lookupData->iterator());
183 int row;
184 for (row = 0;it.current();++it, row++) {
185 if (valueIsText) {
186 if (it.current()->at(visibleColumn).toString().stripWhiteSpace().lower() == txt)
187 break;
188 }
189 else {
190 if (it.current()->at(visibleColumn) == v)
191 break;
192 }
193 }
194
195 m_setValueOrTextInInternalEditor_enabled = false; // <-- this is the entered value,
196 // so do not change the internal editor's contents
197 if (it.current())
198 popup()->tableView()->selectRow(row);
199 else
200 popup()->tableView()->clearSelection();
201
203
204 return it.current();
205}
206
207TQString KexiComboBoxBase::valueForString(const TQString& str, int* row,
208 uint lookInColumn, uint returnFromColumn, bool allowNulls)
209{
210 KexiTableViewData *relData = column() ? column()->relatedData() : 0;
211 if (!relData)
212 return TQString(); //safety
213 //use 'related table data' model
214 //-not effective for large sets: please cache it!
215 //.stripWhiteSpace() is not generic!
216
217 const TQString txt = str.stripWhiteSpace().lower();
218 KexiTableViewData::Iterator it( relData->iterator() );
219 for (*row = 0;it.current();++it, (*row)++) {
220 if (it.current()->at(lookInColumn).toString().stripWhiteSpace().lower()==txt)
221 break;
222 }
223 if (it.current())
224 return it.current()->at(returnFromColumn).toString();
225
226 *row = -1;
227
228 if (column() && column()->relatedDataEditable())
229 return str; //new value entered and that's allowed
230
231 kexiwarn << "KexiComboBoxBase::valueForString(): no related row found, ID will be painted!" << endl;
232 if (allowNulls)
233 return TQString();
234 return str; //for sanity but it's weird to show id to the user
235}
236
238{
239 KexiTableViewData *relData = column() ? column()->relatedData() : 0;
241 if (relData) {
243 //we've user-entered text: look for id
244//TODO: make error if matching text not found?
245 int rowToHighlight;
246 return valueForString(m_userEnteredValue.toString(), &rowToHighlight, 1, 0, true/*allowNulls*/);
247 }
248 else {
249 //use 'related table data' model
250 KexiTableItem *it = popup() ? popup()->tableView()->selectedItem() : 0;
251 return it ? it->at(0) : origValue();//TQVariant();
252 }
253 }
254 else if ((lookupFieldSchema = this->lookupFieldSchema()))
255 {
256 if (lookupFieldSchema->boundColumn()==-1)
257 return origValue();
258 KexiTableItem *it = popup() ? popup()->tableView()->selectedItem() : 0;
259 if ( m_internalEditorValueChanged && !m_userEnteredValue.toString().isEmpty()) { //
260 //try to select a row using the user-entered text
261 if (!popup()) {
262 TQVariant prevUserEnteredValue = m_userEnteredValue;
263 createPopup(false);
264 m_userEnteredValue = prevUserEnteredValue;
265 }
267 }
268 return it ? it->at( lookupFieldSchema->boundColumn() ) : TQVariant();
269 }
270 else if (popup()) {
271 //use 'enum hints' model
272 const int row = popup()->tableView()->currentRow();
273 if (row>=0)
274 return TQVariant( row );
275 }
276
277 if (valueFromInternalEditor().toString().isEmpty())
278 return TQVariant();
281 return origValue(); //unchanged
282}
283
284TQVariant KexiComboBoxBase::visibleValueForLookupField()
285{
287 if (!popup() || !lookupFieldSchema)
288 return TQVariant();
289 const int visibleColumn = lookupFieldSchema->visibleColumn( popup()->tableView()->KexiDataAwareObjectInterface::data()->columnsCount() );
290 if (-1 == visibleColumn)
291 return TQVariant();
292 KexiTableItem *it = popup()->tableView()->selectedItem();
293 return it ? it->at( TQMIN( (uint)visibleColumn, it->count()-1)/*sanity*/ ) : TQVariant();
294}
295
296TQVariant KexiComboBoxBase::visibleValue()
297{
298 return m_visibleValue;
299}
300
302{
303 if (popup())
304 popup()->hide();
306}
307
308tristate KexiComboBoxBase::valueChangedInternal()
309{
310 //avoid comparing values:
311 KexiTableViewData *relData = column() ? column()->relatedData() : 0;
313 if (relData || lookupFieldSchema) {
315 return true;
316
317 //use 'related table data' model
318 KexiTableItem *it = popup() ? popup()->tableView()->selectedItem() : 0;
319 if (!it)
320 return false;
321 }
322 else {
323 //use 'enum hints' model
324 const int row = popup() ? popup()->tableView()->currentRow() : -1;
325 if (row<0 && !m_internalEditorValueChanged/*true if text box is cleared*/)
326 return false;
327 }
328
329 return cancelled;
330}
331
332bool KexiComboBoxBase::valueIsNull()
333{
334// bool ok;
335 TQVariant v( value() );
336 return v.isNull();
337// return !ok || v.isNull();
338}
339
340bool KexiComboBoxBase::valueIsEmpty()
341{
342 return valueIsNull();
343}
344
345void KexiComboBoxBase::showPopup()
346{
347 createPopup(true);
348}
349
351{
352 if (!field())
353 return;
354 m_insideCreatePopup = true;
355 TQWidget* thisWidget = dynamic_cast<TQWidget*>(this);
356 TQWidget *widgetToFocus = internalEditor() ? internalEditor() : thisWidget;
357 if (!popup()) {
358 setPopup( column() ? new KexiComboBoxPopup(thisWidget, *column())
359 : new KexiComboBoxPopup(thisWidget, *field()) );
360 TQObject::connect(popup(), TQ_SIGNAL(rowAccepted(KexiTableItem*,int)),
361 thisWidget, TQ_SLOT(slotRowAccepted(KexiTableItem*,int)));
362 TQObject::connect(popup()->tableView(), TQ_SIGNAL(itemSelected(KexiTableItem*)),
363 thisWidget, TQ_SLOT(slotItemSelected(KexiTableItem*)));
364
365 popup()->setFocusProxy( widgetToFocus );
366 popup()->tableView()->setFocusProxy( widgetToFocus );
367 popup()->installEventFilter(thisWidget);
368
369 if (origValue().isNull())
370 popup()->tableView()->clearSelection();
371 else {
372 popup()->tableView()->selectRow( 0 );
373 popup()->tableView()->setHighlightedRow( 0 );
374 }
375 }
376 if (show && internalEditor() && !internalEditor()->isVisible())
377 /*emit*/editRequested();
378
379 TQPoint posMappedToGlobal = mapFromParentToGlobal(thisWidget->pos());
380 if (posMappedToGlobal != TQPoint(-1,-1)) {
382 popup()->move( posMappedToGlobal + TQPoint(0, thisWidget->height()) );
383 //to avoid flickering: first resize to 0-height, then show and resize back to prev. height
384 const int w = popupWidthHint();
385 popup()->resize(w, 0);
386 if (show)
387 popup()->show();
388 popup()->updateSize(w);
390 int rowToHighlight = -1;
392 KexiTableViewData *relData = column() ? column()->relatedData() : 0;
393 if (lookupFieldSchema) {
394 rowToHighlight = rowToHighlightForLookupTable();
395 }
396 else if (relData) {
397 (void)valueForString(origValue().toString(), &rowToHighlight, 0, 1);
398 }
399 else //enum hint
400 rowToHighlight = origValue().toInt();
401
402/*-->*/ m_moveCursorToEndInInternalEditor_enabled = show;
403 m_selectAllInInternalEditor_enabled = show;
404 m_setValueInInternalEditor_enabled = show;
405 if (rowToHighlight==-1) {
406 rowToHighlight = TQMAX( popup()->tableView()->highlightedRow(), 0);
407 setValueInInternalEditor(TQVariant());
408 }
409 popup()->tableView()->selectRow( rowToHighlight );
410 popup()->tableView()->setHighlightedRow( rowToHighlight );
411 if (rowToHighlight < popup()->tableView()->rowsPerPage())
412 popup()->tableView()->ensureCellVisible( 0, -1 );
413
414/*-->*/ m_moveCursorToEndInInternalEditor_enabled = true;
415 m_selectAllInInternalEditor_enabled = true;
416 m_setValueInInternalEditor_enabled = true;
417 }
418 }
419
420 if (show) {
423 widgetToFocus->setFocus();
424 }
425 m_insideCreatePopup = false;
426}
427
428void KexiComboBoxBase::hide()
429{
430 if (popup())
431 popup()->hide();
432}
433
434void KexiComboBoxBase::slotRowAccepted(KexiTableItem * item, int row)
435{
436 Q_UNUSED(row);
437 //update our value
438 //..nothing to do?
439 updateButton();
440 slotItemSelected(item);
441 /*emit*/acceptRequested();
442}
443
444void KexiComboBoxBase::acceptPopupSelection()
445{
446 if (!popup())
447 return;
448 KexiTableItem *item = popup()->tableView()->highlightedItem();
449 if (item) {
450 popup()->tableView()->selectRow( popup()->tableView()->highlightedRow() );
451 slotRowAccepted(item, -1);
452 }
453 popup()->hide();
454}
455
457{
458 kexidbg << "KexiComboBoxBase::slotItemSelected(): m_visibleValue = " << m_visibleValue << endl;
459
460 TQVariant valueToSet;
461 KexiTableViewData *relData = column() ? column()->relatedData() : 0;
463
464 m_visibleValue = lookupFieldSchema ? visibleValueForLookupField() : TQVariant();
465
466 if (relData) {
467 //use 'related table data' model
468 KexiTableItem *item = popup()->tableView()->selectedItem();
469 if (item)
470 valueToSet = item->at(1);
471 }
472 else if (lookupFieldSchema) {
473 KexiTableItem *item = popup()->tableView()->selectedItem();
474 const int visibleColumn = lookupFieldSchema->visibleColumn( popup()->tableView()->KexiDataAwareObjectInterface::data()->columnsCount() );
475 if (item && visibleColumn!=-1 /* && (int)item->size() >= visibleColumn --already checked*/) {
476 valueToSet = item->at( TQMIN( (uint)visibleColumn, item->count()-1)/*sanity*/ );
477 }
478 }
479 else {
480 //use 'enum hints' model
481 valueToSet = field()->enumHint( popup()->tableView()->currentRow() );
482 if (valueToSet.toString().isEmpty() && !m_insideCreatePopup) {
483 clear();
484 TQWidget* thisWidget = dynamic_cast<TQWidget*>(this);
485 thisWidget->parentWidget()->setFocus();
486 return;
487 }
488 }
489 setValueOrTextInInternalEditor( valueToSet );
493 }
494 // a new (temp) popup table index is selected: do not update selection next time:
496}
497
499{
501 return;
504 if (v.toString().isEmpty()) {
505 if (popup()) {
506 popup()->tableView()->clearSelection();
507 }
508 return;
509 }
510}
511
513{
515 return;
517 //this text is not entered by hand:
518 m_userEnteredValue = TQVariant();
520}
521
522bool KexiComboBoxBase::handleKeyPressForPopup( TQKeyEvent *ke )
523{
524 const int k = ke->key();
525 int highlightedOrSelectedRow = popup() ? popup()->tableView()->highlightedRow() : -1;
526 if (popup() && highlightedOrSelectedRow < 0)
527 highlightedOrSelectedRow = popup()->tableView()->currentRow();
528
529 const bool enterPressed = k==TQt::Key_Enter || k==TQt::Key_Return;
530
531 // The editor may be active but the pull down menu not existant/visible,
532 // e.g. when the user has pressed a normal button to activate the editor
533 // Don't handle the event here in that case.
534 if (!popup() || (!enterPressed && !popup()->isVisible())) {
535 return false;
536 }
537
538 switch (k) {
539 case TQt::Key_Up:
540 popup()->tableView()->setHighlightedRow(
541 TQMAX(highlightedOrSelectedRow-1, 0) );
542 updateTextForHighlightedRow();
543 return true;
544 case TQt::Key_Down:
545 popup()->tableView()->setHighlightedRow(
546 TQMIN(highlightedOrSelectedRow+1, popup()->tableView()->rows()-1) );
547 updateTextForHighlightedRow();
548 return true;
549 case TQt::Key_PageUp:
550 popup()->tableView()->setHighlightedRow(
551 TQMAX(highlightedOrSelectedRow-popup()->tableView()->rowsPerPage(), 0) );
552 updateTextForHighlightedRow();
553 return true;
554 case TQt::Key_PageDown:
555 popup()->tableView()->setHighlightedRow(
556 TQMIN(highlightedOrSelectedRow+popup()->tableView()->rowsPerPage(),
557 popup()->tableView()->rows()-1) );
558 updateTextForHighlightedRow();
559 return true;
560 case TQt::Key_Home:
561 popup()->tableView()->setHighlightedRow( 0 );
562 updateTextForHighlightedRow();
563 return true;
564 case TQt::Key_End:
565 popup()->tableView()->setHighlightedRow( popup()->tableView()->rows()-1 );
566 updateTextForHighlightedRow();
567 return true;
568 case TQt::Key_Enter:
569 case TQt::Key_Return: //accept
570 //select row that is highlighted
571 if (popup()->tableView()->highlightedRow()>=0)
572 popup()->tableView()->selectRow( popup()->tableView()->highlightedRow() );
573 //do not return true: allow to process event
574 default: ;
575 }
576 return false;
577}
578
579void KexiComboBoxBase::updateTextForHighlightedRow()
580{
581 KexiTableItem *item = popup() ? popup()->tableView()->highlightedItem() : 0;
582 if (item)
583 slotItemSelected(item);
584}
585
587{
589 if (lookupFieldSchema) {
590// kexidbg << "KexiComboBoxBase::undoChanges(): m_visibleValue BEFORE = " << m_visibleValue << endl;
591 if (popup())
592 popup()->tableView()->selectRow( popup()->tableView()->highlightedRow() );
593 m_visibleValue = visibleValueForLookupField();
594// kexidbg << "KexiComboBoxBase::undoChanges(): m_visibleValue AFTER = " << m_visibleValue << endl;
595 setValueOrTextInInternalEditor( m_visibleValue );
596 }
597}
bool m_mouseBtnPressedWhenPopupVisible
Used only by KexiComboBoxTableEdit.
TQVariant m_userEnteredValue
value (usually a text) entered by hand (by the user)
virtual TQVariant valueFromInternalEditor()=0
Implement this to return value from the internal editor.
virtual void moveCursorToEndInInternalEditor()=0
Implement this to perform "move cursor to end" in the internal editor.
virtual void editRequested()=0
Implement this as signal.
virtual void slotItemSelected(KexiTableItem *)
Call this from slot.
virtual KexiTableViewColumn * column() const =0
virtual TQWidget * internalEditor() const =0
Implement this to return the internal editor.
virtual TQVariant value()
Note: Generally in current implementation this is integer > 0; may be null if no value is set.
bool m_setVisibleValueOnSetValueInternal
Used in setValueInternal() to control whether we want to set visible value on setValueInternal()
KexiTableItem * selectItemForEnteredValueInLookupTable(const TQVariant &v)
Used to select row item for an user-entered value v.
virtual void setValueInInternalEditor(const TQVariant &value)=0
Implement this to perform "set value" in the internal editor.
void setValueOrTextInInternalEditor(const TQVariant &value)
sets value for the line edit without setting a flag (m_userEnteredValue) that indicates that the text...
void createPopup(bool show)
virtual TQPoint mapFromParentToGlobal(const TQPoint &pos) const =0
Implement this to return a position pos mapped from parent (e.g.
virtual TQVariant origValue() const =0
virtual void slotRowAccepted(KexiTableItem *item, int row)
Call this from slot.
virtual void clear()
Reimplement this and call this impl.: used to clear internal editor.
virtual int popupWidthHint() const =0
Implement this to return a hint for popup width.
bool m_setValueOrTextInInternalEditor_enabled
Used in setValueOrTextInInternalEditor() and slotItemSelected()
KexiDB::LookupFieldSchema * lookupFieldSchema() const
virtual void acceptRequested()=0
Implement this as signal.
bool m_updatePopupSelectionOnShow
Set to false as soon as the item corresponding with the current value is selected in the popup table.
TQString valueForString(const TQString &str, int *row, uint lookInColumn, uint returnFromColumn, bool allowNulls=false)
bool m_internalEditorValueChanged
true if user has text or other value inside editor
virtual void setValueInternal(const TQVariant &add, bool removeOld)
virtual void updateButton()
Implement this to update button state.
virtual KexiDB::Field * field() const =0
void slotInternalEditorValueChanged(const TQVariant &v)
Call this from slot.
bool m_slotInternalEditorValueChanged_enabled
Used in slotInternalEditorValueChanged()
void undoChanges()
Used by KexiDBComboBox.
bool m_insideCreatePopup
true if we're inside createPopup(); used in slotItemSelected()
virtual void selectAllInInternalEditor()=0
Implement this to perform "select all" in the internal editor.
int rowToHighlightForLookupTable() const
Internal class for displaying popup table view.
virtual TableSchema * table() const
Definition field.cpp:300
Provides information about lookup field's setup.
int visibleColumn(uint fieldsCount) const
LookupFieldSchema * lookupFieldSchema(const Field &field) const
KexiTableViewData * data() const
KexiTableViewData * relatedData() const
virtual void ensureCellVisible(int row, int col)
int highlightedRow() const
void setHighlightedRow(int row)
3-state logical type with three values: true, false and cancelled and convenient operators.
Definition tristate.h:100