|
@@ -0,0 +1,451 @@
|
|
|
|
+/*=========================================================================
|
|
|
|
+
|
|
|
|
+ Library: CTK
|
|
|
|
+
|
|
|
|
+ Copyright (c) Kitware Inc.
|
|
|
|
+
|
|
|
|
+ Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
+ you may not use this file except in compliance with the License.
|
|
|
|
+ You may obtain a copy of the License at
|
|
|
|
+
|
|
|
|
+ http://www.apache.org/licenses/LICENSE-2.0.txt
|
|
|
|
+
|
|
|
|
+ Unless required by applicable law or agreed to in writing, software
|
|
|
|
+ distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
+ See the License for the specific language governing permissions and
|
|
|
|
+ limitations under the License.
|
|
|
|
+
|
|
|
|
+=========================================================================*/
|
|
|
|
+
|
|
|
|
+// Qt includes
|
|
|
|
+#include <QActionEvent>
|
|
|
|
+#include <QComboBox>
|
|
|
|
+#include <QCompleter>
|
|
|
|
+#include <QDebug>
|
|
|
|
+#include <QEvent>
|
|
|
|
+#include <QHBoxLayout>
|
|
|
|
+#include <QLineEdit>
|
|
|
|
+#include <QStringList>
|
|
|
|
+#include <QStringListModel>
|
|
|
|
+
|
|
|
|
+// CTK includes
|
|
|
|
+#include "ctkSearchBox.h"
|
|
|
|
+#include "ctkMenuComboBox_p.h"
|
|
|
|
+
|
|
|
|
+// -------------------------------------------------------------------------
|
|
|
|
+ctkMenuComboBoxInternal::ctkMenuComboBoxInternal()
|
|
|
|
+{
|
|
|
|
+}
|
|
|
|
+// -------------------------------------------------------------------------
|
|
|
|
+ctkMenuComboBoxInternal::~ctkMenuComboBoxInternal()
|
|
|
|
+{
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// -------------------------------------------------------------------------
|
|
|
|
+void ctkMenuComboBoxInternal::showPopup()
|
|
|
|
+{
|
|
|
|
+ QMenu* menu = this->Menu.data();
|
|
|
|
+ if (!menu)
|
|
|
|
+ {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ menu->popup(this->mapToGlobal(this->rect().bottomLeft()));
|
|
|
|
+ static int minWidth = menu->sizeHint().width();
|
|
|
|
+ menu->setFixedWidth(qMax(this->width(), minWidth));
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// -------------------------------------------------------------------------
|
|
|
|
+ctkMenuComboBoxPrivate::ctkMenuComboBoxPrivate(ctkMenuComboBox& object)
|
|
|
|
+ :q_ptr(&object)
|
|
|
|
+{
|
|
|
|
+ this->MenuComboBox = 0;
|
|
|
|
+ this->SearchCompleter = 0;
|
|
|
|
+ this->EditBehavior = ctkMenuComboBox::EditableOnFocus;
|
|
|
|
+ this->IsDefaultTextCurrent = true;
|
|
|
|
+ this->IsDefaultIconCurrent = true;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// -------------------------------------------------------------------------
|
|
|
|
+void ctkMenuComboBoxPrivate::init()
|
|
|
|
+{
|
|
|
|
+ Q_Q(ctkMenuComboBox);
|
|
|
|
+ QHBoxLayout* layout = new QHBoxLayout(q);
|
|
|
|
+ layout->setContentsMargins(0,0,0,0);
|
|
|
|
+ layout->setSizeConstraint(QLayout::SetMinimumSize);
|
|
|
|
+
|
|
|
|
+ this->MenuComboBox = new ctkMenuComboBoxInternal();
|
|
|
|
+ this->MenuComboBox->setMinimumContentsLength(12);
|
|
|
|
+ layout->addWidget(this->MenuComboBox);
|
|
|
|
+ this->MenuComboBox->installEventFilter(q);
|
|
|
|
+ this->MenuComboBox->setSizeAdjustPolicy(QComboBox::AdjustToContents);
|
|
|
|
+ this->MenuComboBox->addItem(this->DefaultIcon, this->DefaultText);
|
|
|
|
+
|
|
|
|
+ this->SearchCompleter = new QCompleter(QStringList(), q);
|
|
|
|
+ this->SearchCompleter->setCaseSensitivity(Qt::CaseInsensitive);
|
|
|
|
+
|
|
|
|
+ //q->setSizePolicy(this->MenuComboBox->sizePolicy());
|
|
|
|
+ q->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
|
|
|
|
+ q->setDefaultText(ctkMenuComboBox::tr("Search..."));
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// ------------------------------------------------------------------------
|
|
|
|
+QAction* ctkMenuComboBoxPrivate::actionByTitle(const QString& text, const QMenu* parentMenu)
|
|
|
|
+{
|
|
|
|
+ if (parentMenu->title() == text)
|
|
|
|
+ {
|
|
|
|
+ return parentMenu->menuAction();
|
|
|
|
+ }
|
|
|
|
+ foreach(QAction* action, parentMenu->actions())
|
|
|
|
+ {
|
|
|
|
+ if (action->text().toLower() == text.toLower())
|
|
|
|
+ {
|
|
|
|
+ return action;
|
|
|
|
+ }
|
|
|
|
+ if (action->menu())
|
|
|
|
+ {
|
|
|
|
+ QAction* subAction = this->actionByTitle(text, action->menu());
|
|
|
|
+ if(subAction)
|
|
|
|
+ {
|
|
|
|
+ return subAction;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// ------------------------------------------------------------------------
|
|
|
|
+void ctkMenuComboBoxPrivate::setCurrentText(const QString& newCurrentText)
|
|
|
|
+{
|
|
|
|
+ if (this->MenuComboBox->lineEdit())
|
|
|
|
+ {
|
|
|
|
+ static_cast<ctkSearchBox*>(this->MenuComboBox->lineEdit())
|
|
|
|
+ ->setPlaceholderText(newCurrentText);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ this->MenuComboBox->setItemText(this->MenuComboBox->currentIndex(),
|
|
|
|
+ newCurrentText);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// ------------------------------------------------------------------------
|
|
|
|
+QString ctkMenuComboBoxPrivate::currentText()const
|
|
|
|
+{
|
|
|
|
+ return this->MenuComboBox->itemText(this->MenuComboBox->currentIndex());
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// ------------------------------------------------------------------------
|
|
|
|
+QIcon ctkMenuComboBoxPrivate::currentIcon()const
|
|
|
|
+{
|
|
|
|
+ return this->MenuComboBox->itemIcon(this->MenuComboBox->currentIndex());
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// ------------------------------------------------------------------------
|
|
|
|
+void ctkMenuComboBoxPrivate::setCurrentIcon(const QIcon& newCurrentIcon)
|
|
|
|
+{
|
|
|
|
+ this->MenuComboBox->setItemIcon(this->MenuComboBox->currentIndex(),
|
|
|
|
+ newCurrentIcon);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// -------------------------------------------------------------------------
|
|
|
|
+void ctkMenuComboBoxPrivate::setComboBoxEditable(bool edit)
|
|
|
|
+{
|
|
|
|
+ Q_Q(ctkMenuComboBox);
|
|
|
|
+ if(edit)
|
|
|
|
+ {
|
|
|
|
+ if (!this->MenuComboBox->lineEdit())
|
|
|
|
+ {
|
|
|
|
+ ctkSearchBox* line = new ctkSearchBox();
|
|
|
|
+ this->MenuComboBox->setLineEdit(line);
|
|
|
|
+ q->connect(line, SIGNAL(returnPressed()),
|
|
|
|
+ q,SLOT(onReturnPressed()));
|
|
|
|
+ }
|
|
|
|
+ this->MenuComboBox->setCompleter(this->SearchCompleter);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ this->MenuComboBox->setEditable(edit);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// -------------------------------------------------------------------------
|
|
|
|
+void ctkMenuComboBoxPrivate::addAction(QAction *action)
|
|
|
|
+{
|
|
|
|
+ if (action->menu())
|
|
|
|
+ {
|
|
|
|
+ this->addMenuToCompleter(action->menu());
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ this->addActionToCompleter(action);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// -------------------------------------------------------------------------
|
|
|
|
+void ctkMenuComboBoxPrivate::addMenuToCompleter(QMenu* menu)
|
|
|
|
+{
|
|
|
|
+ Q_Q(ctkMenuComboBox);
|
|
|
|
+ menu->installEventFilter(q);
|
|
|
|
+
|
|
|
|
+ // Bug QT : see this link for more details
|
|
|
|
+ // https://bugreports.qt.nokia.com/browse/QTBUG-20929?focusedCommentId=161370#comment-161370
|
|
|
|
+ // if the submenu doesn't have a parent, the submenu triggered(QAction*)
|
|
|
|
+ // signal is not propagated. So we listened this submenu to fix the bug.
|
|
|
|
+ QObject* emptyObject = 0;
|
|
|
|
+ if(menu->parent() == emptyObject)
|
|
|
|
+ {
|
|
|
|
+ q->connect(menu, SIGNAL(triggered(QAction*)),
|
|
|
|
+ q, SLOT(onActionSelected(QAction*)), Qt::UniqueConnection);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ foreach (QAction* action, menu->actions())
|
|
|
|
+ {
|
|
|
|
+ this->addAction(action);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// -------------------------------------------------------------------------
|
|
|
|
+void ctkMenuComboBoxPrivate::addActionToCompleter(QAction *action)
|
|
|
|
+{
|
|
|
|
+ QStringListModel* model = qobject_cast<QStringListModel* >(this->SearchCompleter->model());
|
|
|
|
+ Q_ASSERT(model);
|
|
|
|
+ QModelIndex start = this->SearchCompleter->model()->index(0,0);
|
|
|
|
+ QModelIndexList indexList = this->SearchCompleter->model()->match(
|
|
|
|
+ start, 0, action->text());
|
|
|
|
+ if (indexList.count())
|
|
|
|
+ {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ int actionCount = this->SearchCompleter->model()->rowCount();
|
|
|
|
+ this->SearchCompleter->model()->insertRow(actionCount);
|
|
|
|
+ QModelIndex index = this->SearchCompleter->model()->index(actionCount, 0);
|
|
|
|
+ this->SearchCompleter->model()->setData(index, action->text());
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// ------------------------------------------------------------------------
|
|
|
|
+void ctkMenuComboBoxPrivate::removeActionToCompleter(QAction *action)
|
|
|
|
+{
|
|
|
|
+ QStringListModel* model = qobject_cast<QStringListModel* >(this->SearchCompleter->model());
|
|
|
|
+ Q_ASSERT(model);
|
|
|
|
+ if (!model->stringList().contains(action->text()) )
|
|
|
|
+ {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Maybe the action is present multiple times in different submenus
|
|
|
|
+ // Don't remove its entry from the completer model if there are still some action instances
|
|
|
|
+ // in the menus.
|
|
|
|
+ if (this->actionByTitle(action->text(), this->Menu.data()))
|
|
|
|
+ {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ QModelIndex start = this->SearchCompleter->model()->index(0,0);
|
|
|
|
+ QModelIndexList indexList = this->SearchCompleter->model()->match(
|
|
|
|
+ start, 0, action->text());
|
|
|
|
+ Q_ASSERT(indexList.count() == 1);
|
|
|
|
+ foreach (QModelIndex index, indexList)
|
|
|
|
+ {
|
|
|
|
+ // Search completer model is a flat list
|
|
|
|
+ this->SearchCompleter->model()->removeRow(index.row());
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// ------------------------------------------------------------------------
|
|
|
|
+ctkMenuComboBox::ctkMenuComboBox(QWidget* _parent)
|
|
|
|
+ :QWidget(_parent)
|
|
|
|
+ , d_ptr(new ctkMenuComboBoxPrivate(*this))
|
|
|
|
+{
|
|
|
|
+ Q_D(ctkMenuComboBox);
|
|
|
|
+ d->init();
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// ------------------------------------------------------------------------
|
|
|
|
+ctkMenuComboBox::~ctkMenuComboBox()
|
|
|
|
+{
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// ------------------------------------------------------------------------
|
|
|
|
+void ctkMenuComboBox::setMenu(QMenu* menu)
|
|
|
|
+{
|
|
|
|
+ Q_D(ctkMenuComboBox);
|
|
|
|
+ if (d->Menu.data() == menu)
|
|
|
|
+ {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (d->Menu)
|
|
|
|
+ {
|
|
|
|
+ this->removeAction(d->Menu.data()->menuAction());
|
|
|
|
+ QObject::disconnect(d->Menu.data(),SIGNAL(triggered(QAction*)),
|
|
|
|
+ this,SLOT(onActionSelected(QAction*)));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ d->Menu = menu;
|
|
|
|
+ d->MenuComboBox->Menu = menu;
|
|
|
|
+ d->addMenuToCompleter(menu);
|
|
|
|
+
|
|
|
|
+ if (d->Menu)
|
|
|
|
+ {
|
|
|
|
+ this->addAction(d->Menu.data()->menuAction());
|
|
|
|
+ QObject::connect(d->Menu.data(),SIGNAL(triggered(QAction*)),
|
|
|
|
+ this,SLOT(onActionSelected(QAction*)), Qt::UniqueConnection);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// -------------------------------------------------------------------------
|
|
|
|
+QMenu* ctkMenuComboBox::menu()const
|
|
|
|
+{
|
|
|
|
+ Q_D(const ctkMenuComboBox);
|
|
|
|
+ return d->Menu.data();
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// -------------------------------------------------------------------------
|
|
|
|
+void ctkMenuComboBox::setDefaultText(const QString& newDefaultText)
|
|
|
|
+{
|
|
|
|
+ Q_D(ctkMenuComboBox);
|
|
|
|
+ d->DefaultText = newDefaultText;
|
|
|
|
+ if (d->IsDefaultTextCurrent)
|
|
|
|
+ {
|
|
|
|
+ d->setCurrentText(d->DefaultText);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// -------------------------------------------------------------------------
|
|
|
|
+QString ctkMenuComboBox::defaultText()const
|
|
|
|
+{
|
|
|
|
+ Q_D(const ctkMenuComboBox);
|
|
|
|
+ return d->DefaultText;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// -------------------------------------------------------------------------
|
|
|
|
+void ctkMenuComboBox::setDefaultIcon(const QIcon& newIcon)
|
|
|
|
+{
|
|
|
|
+ Q_D(ctkMenuComboBox);
|
|
|
|
+ d->DefaultIcon = newIcon;
|
|
|
|
+ if (d->IsDefaultIconCurrent)
|
|
|
|
+ {
|
|
|
|
+ d->setCurrentIcon(d->DefaultIcon);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// -------------------------------------------------------------------------
|
|
|
|
+QIcon ctkMenuComboBox::defaultIcon()const
|
|
|
|
+{
|
|
|
|
+ Q_D(const ctkMenuComboBox);
|
|
|
|
+ return d->DefaultIcon;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// -------------------------------------------------------------------------
|
|
|
|
+void ctkMenuComboBox::setEditableBehavior(ctkMenuComboBox::EditableBehavior edit)
|
|
|
|
+{
|
|
|
|
+ Q_D(ctkMenuComboBox);
|
|
|
|
+ d->EditBehavior = edit;
|
|
|
|
+ switch (edit)
|
|
|
|
+ {
|
|
|
|
+ case ctkMenuComboBox::Editable:
|
|
|
|
+ d->setComboBoxEditable(true);
|
|
|
|
+ break;
|
|
|
|
+ case ctkMenuComboBox::NotEditable:
|
|
|
|
+ d->setComboBoxEditable(false);
|
|
|
|
+ break;
|
|
|
|
+ case ctkMenuComboBox::EditableOnFocus:
|
|
|
|
+ d->setComboBoxEditable(this->hasFocus());
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// -------------------------------------------------------------------------
|
|
|
|
+ctkMenuComboBox::EditableBehavior ctkMenuComboBox::editableBehavior()const
|
|
|
|
+{
|
|
|
|
+ Q_D(const ctkMenuComboBox);
|
|
|
|
+ return d->EditBehavior;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// -------------------------------------------------------------------------
|
|
|
|
+void ctkMenuComboBox::setMinimumContentsLength(int characters)
|
|
|
|
+{
|
|
|
|
+ Q_D(ctkMenuComboBox);
|
|
|
|
+ d->MenuComboBox->setMinimumContentsLength(characters);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// -------------------------------------------------------------------------
|
|
|
|
+void ctkMenuComboBox::onActionSelected(QAction* action)
|
|
|
|
+{
|
|
|
|
+ Q_D(ctkMenuComboBox);
|
|
|
|
+ /// Set the action selected in the combobox.
|
|
|
|
+
|
|
|
|
+ d->IsDefaultTextCurrent = true;
|
|
|
|
+ QString newText = d->DefaultText;
|
|
|
|
+ if (action && !action->text().isEmpty())
|
|
|
|
+ {
|
|
|
|
+ newText = action->text();
|
|
|
|
+ d->IsDefaultTextCurrent = false;
|
|
|
|
+ }
|
|
|
|
+ d->setCurrentText(newText);
|
|
|
|
+
|
|
|
|
+ d->IsDefaultIconCurrent = true;
|
|
|
|
+ QIcon newIcon = d->DefaultIcon;
|
|
|
|
+ if (action && !action->icon().isNull())
|
|
|
|
+ {
|
|
|
|
+ d->IsDefaultIconCurrent = false;
|
|
|
|
+ newIcon = action->icon();
|
|
|
|
+ }
|
|
|
|
+ d->setCurrentIcon(newIcon);
|
|
|
|
+
|
|
|
|
+ d->MenuComboBox->clearFocus();
|
|
|
|
+
|
|
|
|
+ qDebug() << action->text();
|
|
|
|
+ emit ctkMenuComboBox::actionChanged(action);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// -------------------------------------------------------------------------
|
|
|
|
+void ctkMenuComboBox::clearActiveAction()
|
|
|
|
+{
|
|
|
|
+ this->onActionSelected(0);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// -------------------------------------------------------------------------
|
|
|
|
+void ctkMenuComboBox::onReturnPressed()
|
|
|
|
+{
|
|
|
|
+ Q_D(ctkMenuComboBox);
|
|
|
|
+ QAction* action = d->actionByTitle(d->MenuComboBox->lineEdit()->text(), d->Menu.data());
|
|
|
|
+ if (!action)
|
|
|
|
+ {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ action->trigger();
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// -------------------------------------------------------------------------
|
|
|
|
+bool ctkMenuComboBox::eventFilter(QObject* target, QEvent* event)
|
|
|
|
+{
|
|
|
|
+ Q_D(ctkMenuComboBox);
|
|
|
|
+ if (target == d->MenuComboBox)
|
|
|
|
+ {
|
|
|
|
+ if (event->type() == QEvent::Resize)
|
|
|
|
+ {
|
|
|
|
+ this->layout()->invalidate();
|
|
|
|
+ }
|
|
|
|
+ if(d->EditBehavior == ctkMenuComboBox::EditableOnFocus)
|
|
|
|
+ {
|
|
|
|
+ if (event->type() == QEvent::FocusIn)
|
|
|
|
+ {
|
|
|
|
+ d->setComboBoxEditable(true);
|
|
|
|
+ }
|
|
|
|
+ if (event->type() == QEvent::FocusOut)
|
|
|
|
+ {
|
|
|
|
+ d->setComboBoxEditable(false);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else if (event->type() == QEvent::ActionAdded)
|
|
|
|
+ {
|
|
|
|
+ QActionEvent* actionEvent = static_cast<QActionEvent *>(event);
|
|
|
|
+ d->addAction(actionEvent->action());
|
|
|
|
+ }
|
|
|
|
+ else if (event->type() == QEvent::ActionRemoved)
|
|
|
|
+ {
|
|
|
|
+ QActionEvent* actionEvent = static_cast<QActionEvent *>(event);
|
|
|
|
+ d->removeActionToCompleter(actionEvent->action());
|
|
|
|
+ }
|
|
|
|
+ return this->Superclass::eventFilter(target, event);
|
|
|
|
+}
|
|
|
|
+
|