PDA

View Full Version : Custom QComboBox or how to use Qt icons in widgets



ScabrusMantra
22nd March 2011, 14:14
I am trying to create a custom widget, which will behave similar to QComboBox with few modifications. I would like it to have search as you type feature, so that while user types in popup view would remain open and first match would get selected. This is similar to font selection widget in OpenOffice writer. I've come really close to acomplishing my goal by using this example:

https://idlebox.net/2010/apidocs/qt-everywhere-opensource-4.7.0.zip/network-googlesuggest.html

where I replaced google search with the search against a set dictionary. Then I added button to the lineEdit as described here:

http://labs.qt.nokia.com/2007/06/06/lineedit-with-a-clear-button/

While I've achieved desied behavior I can not get the button to look exactly like down arrow as appears in the native QComboBox. So, after this long introduction my question is this: How can I use some Qt icon that is used in one of the standard widgets in my custom widget. In my particular case I need to have downarrow icon in a QToolButton that I've added to my QLineEdit. Also, I've tried to use 1downarrow.png icon that is found in /usr/share/icons/crystalsvg/16x16/actions/ folder, but I had to add to my resource file. I suspect that there is a better way to do it, but I could nto figure it out.

Any help would be greatly appreciated. Thanks in advance for any suggestions.

ScabrusMantra
23rd March 2011, 18:34
Ok, since I could not get my button icon to look like the one in the QComboBox I've tried an alternative approach based on QComboBox. Here are the features that I want to have:

1. Items displayed in combo box are predetermined and do not change.
2. have combo box's line edit editable and when text is entered there, have list view popup (with some short delay) and select first item matching text entered
3. have event filter on the list view to behave like regular comboBox (pressing enter - selects item etc)

I have two classes: ChmComboBox and ChmCompleter, where the later is being derived from QListView:



class ChmCompleter : public QListView
{
Q_OBJECT

public:
ChmCompleter(QLineEdit *a_editor, QWidget *a_parent = 0);
~ChmCompleter();
bool eventFilter(QObject *a_obj, QEvent *a_event);

public slots:
void doneCompletion();
void preventSuggest();
void autoSuggest();
void showPopup();

private:
QLineEdit *m_editor;
QTimer *m_timer;

};

class ChmComboBox : public QComboBox
{
Q_OBJECT

public:
ChmComboBox(QWidget *a_parent = 0);

protected slots:
void doSearch();

signals:
void textEntered(const QString &a_s);

private:
ChmCompleter *m_completer;

};



Here is my implementation:



// ************* ChmCompleter *****************************

ChmCompleter::ChmCompleter(QLineEdit *a_editor, QWidget *a_parent)
: QListView(a_parent),m_editor(a_editor)
{
setWindowFlags(Qt::Popup);
setFocusPolicy(Qt::NoFocus);
setFocusProxy(a_editor);
setMouseTracking(true);
installEventFilter(this);

m_timer = new QTimer(this);
m_timer->setSingleShot(true);
m_timer->setInterval(500);
connect(m_timer, SIGNAL(timeout()), this, SLOT(autoSuggest()) );
connect(m_editor, SIGNAL(textEdited(QString)), m_timer, SLOT(start()) );
}

ChmCompleter::~ChmCompleter()
{
if(m_timer)
delete(m_timer);
}

bool ChmCompleter::eventFilter(QObject *a_obj, QEvent *a_event)
{
if(a_obj != this)
return false;

if(a_event->type() == QEvent::MouseButtonPress)
{
hide();
m_editor->setFocus();
return true;
}

if(a_event->type() == QEvent::KeyPress)
{
bool consumed = false;
int key = static_cast<QKeyEvent*>(a_event)->key();
switch(key)
{
case Qt::Key_Enter:
case Qt::Key_Return:
doneCompletion();
consumed = true;

case Qt::Key_Escape:
m_editor->setFocus();
hide();
consumed = true;

case Qt::Key_Up:
case Qt::Key_Down:
case Qt::Key_Home:
case Qt::Key_End:
case Qt::Key_PageUp:
case Qt::Key_PageDown:
break;

default:
m_editor->setFocus();
m_editor->event(a_event);
hide();
break;
}

return consumed;
}

return false;
}

void ChmCompleter::showPopup()
{
QModelIndex start = model()->index(0, 0);
QModelIndexList mi = model()->match(start, Qt::DisplayRole, m_editor->text(), 1, Qt::MatchStartsWith);
if( mi.count() > 0)
selectionModel()->setCurrentIndex(mi.at(0), QItemSelectionModel::Select);
setFocus();
show();
}

void ChmCompleter::doneCompletion()
{
m_timer->stop();
hide();
m_editor->setFocus();
QModelIndex index = selectionModel()->currentIndex();
if(index.isValid())
{
m_editor->setText(model()->data(index).toString());
QMetaObject::invokeMethod(m_editor, "returnPressed");
}
}

void ChmCompleter::preventSuggest()
{
m_timer->stop();
}

void ChmCompleter::autoSuggest()
{
QString str = m_editor->text();
if(!str.isEmpty())
showPopup();
}

// ************* ChmComboBox **************
ChmComboBox::ChmComboBox(QWidget *a_parent)
: QComboBox(a_parent)
{
setEditable(true);
setCompleter(0);
setInsertPolicy(QComboBox::NoInsert);
m_completer = new ChmCompleter(this->lineEdit(), this);
connect(lineEdit(), SIGNAL(returnPressed()), this, SLOT(doSearch()) );
setFocus();
lineEdit()->setPlaceholderText("Type or select Y-variable");

QStringListModel *model = new QStringListModel(this);
setModel(model);
setView(m_completer);
m_completer->installEventFilter(m_completer);
m_completer->viewport()->installEventFilter(m_completer);
}

void ChmComboBox::doSearch()
{
m_completer->preventSuggest();
if(lineEdit()->text() == lineEdit()->placeholderText())
return;
else if(findText(lineEdit()->text()) >= 0)
emit(currentIndexChanged(lineEdit()->text()));
else
// ATTENTION!!! add popup dialog here
}



Now I am having some problems that I can not resolve:

1. List view does not show when I type in line edit. I did verify that ChmCompleter::showPopup() function is called, but calling show() there has no effect whatsoever.

2. Pressing keys when listView is open has no effect and eventFilter is not catching any events.

I would greatly appreciate if anyone could suggest what I need to do to get this code to work correctly. Thanks in advance for any help.