소스 검색

Create new widget ctkMenuComboBox:

Widget compose by a comboBox linked with a menu.
it can be editable, editable on focus or disable.
Purpose : to show or to filter a menu.
Benjamin Long 14 년 전
부모
커밋
cfde4659d8

+ 3 - 0
Libs/Widgets/CMakeLists.txt

@@ -78,6 +78,8 @@ SET(KIT_SRCS
   ctkMatrixWidget.h
   ctkMenuButton.cpp
   ctkMenuButton.h
+  ctkMenuComboBox.cpp
+  ctkMenuComboBox.h
   ctkModalityWidget.cpp
   ctkModalityWidget.h
   ctkPathLineEdit.cpp
@@ -199,6 +201,7 @@ SET(KIT_MOC_SRCS
   ctkMaterialPropertyWidget.h
   ctkMatrixWidget.h
   ctkMenuButton.h
+  ctkMenuComboBox.h
   ctkModalityWidget.h
   ctkPathLineEdit.h
   ctkPopupWidget.h

+ 6 - 0
Libs/Widgets/Testing/Cpp/CMakeLists.txt

@@ -41,6 +41,9 @@ SET(TEST_SOURCES
   ctkMatrixWidgetTest1.cpp
   ctkMatrixWidgetTest2.cpp
   ctkMenuButtonTest1.cpp
+  ctkMenuComboBoxTest1.cpp
+  ctkMenuComboBoxTest2.cpp
+  ctkMenuComboBoxTest3.cpp
   ctkModalityWidgetTest1.cpp
   ctkPathLineEditTest1.cpp
   ctkPopupWidgetTest1.cpp
@@ -164,6 +167,9 @@ SIMPLE_TEST( ctkMaterialPropertyWidgetTest2 )
 SIMPLE_TEST( ctkMatrixWidgetTest1 )
 SIMPLE_TEST( ctkMatrixWidgetTest2 )
 SIMPLE_TEST( ctkMenuButtonTest1 )
+SIMPLE_TEST( ctkMenuComboBoxTest1 )
+SIMPLE_TEST( ctkMenuComboBoxTest2 )
+SIMPLE_TEST( ctkMenuComboBoxTest3 )
 SIMPLE_TEST( ctkModalityWidgetTest1 )
 SIMPLE_TEST( ctkPathLineEditTest1 )
 SIMPLE_TEST( ctkPopupWidgetTest1 )

+ 109 - 0
Libs/Widgets/Testing/Cpp/ctkMenuComboBoxTest1.cpp

@@ -0,0 +1,109 @@
+/*=========================================================================
+
+  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 <QApplication>
+#include <QComboBox>
+#include <QDebug>
+#include <QIcon>
+#include <QLineEdit>
+#include <QMenu>
+#include <QTimer>
+#include <QToolBar>
+#include <QVBoxLayout>
+
+// CTK includes
+#include "ctkMenuComboBox.h"
+
+// STD includes
+#include <iostream>
+
+//-----------------------------------------------------------------------------
+int ctkMenuComboBoxTest1(int argc, char * argv [] )
+{
+  QApplication app(argc, argv);
+  QIcon plus(":Icons/plus.png");
+
+  QMenu* file = new QMenu("File");
+  file->addAction("first");
+  QMenu* wizards = new QMenu("Wizards");
+  file->addMenu(wizards);
+
+  QMenu*informatics = new QMenu("Informatics");
+  file->addMenu(informatics);
+
+  QWidget topLevelWidget(0);
+
+  /*ctkMenuComboBox* Menu = new ctkMenuComboBox();
+  Menu->setMenu(file);
+  Menu->setMinimumWidth(200);
+  //Menu->setMinimumWidthComboBox(150);
+  //Menu->setMinimumWidth(150);
+  Menu->setEditableBehavior(ctkMenuComboBox::EditableOnFocus);
+  Menu->show();*/
+
+  ctkMenuComboBox* Menu2 = new ctkMenuComboBox(0);
+  Menu2->setMenu(file);
+  Menu2->setDefaultText("Search");
+  Menu2->setAutoFillBackground(true);
+  Menu2->setMinimumContentsLength(25);
+  Menu2->setEditableBehavior(ctkMenuComboBox::EditableOnFocus);
+  //Menu2->show();
+
+/*
+  ctkMenuComboBox* Menu3 = new ctkMenuComboBox();
+  Menu3->setMenu(file);
+  //Menu3->setMinimumWidth(150);
+  //Menu3->setMinimumWidthComboBox(150);
+  Menu3->setEditableBehavior(ctkMenuComboBox::NotEditable);
+*/
+  QVBoxLayout* layout = new QVBoxLayout;
+  QToolBar bar;
+  //QWidget bar;
+  //QHBoxLayout* barLayout = new QHBoxLayout(&bar);
+
+  layout->addWidget(&bar);
+  bar.addWidget(Menu2);
+  //bar.addWidget(Menu);
+  //layout->addWidget(Menu3);
+
+  file->addAction(plus, "Add ...");
+  file->addAction("Saveeeeeeeeeeeeeeeeeeeeeee ...");
+  wizards->addAction("tutu");
+  wizards->addMenu(informatics);
+  informatics->addAction("ddd");
+
+  QAction* actionEnd = wizards->addAction("Quit");
+
+  actionEnd->setShortcut(QKeySequence("Ctrl+Q"));
+
+  QObject::connect(actionEnd, SIGNAL(triggered()), qApp, SLOT(quit()));
+
+  topLevelWidget.setLayout(layout);
+  topLevelWidget.show();
+
+  if (argc < 2 || QString(argv[1]) != "-I" )
+    {
+    QTimer::singleShot(200, &app, SLOT(quit()));
+    }
+
+  return app.exec();
+}
+

+ 135 - 0
Libs/Widgets/Testing/Cpp/ctkMenuComboBoxTest2.cpp

@@ -0,0 +1,135 @@
+/*=========================================================================
+
+  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 <QApplication>
+#include <QComboBox>
+#include <QCompleter>
+#include <QDebug>
+#include <QIcon>
+#include <QLineEdit>
+#include <QListWidget>
+#include <QMenu>
+#include <QTimer>
+#include <QToolBar>
+#include <QVBoxLayout>
+
+// CTK includes
+#include "ctkMenuComboBox.h"
+
+// STD includes
+#include <iostream>
+
+//-----------------------------------------------------------------------------
+int ctkMenuComboBoxTest2(int argc, char * argv [] )
+{
+  QApplication app(argc, argv);
+
+  ctkMenuComboBox* menu = new ctkMenuComboBox();
+
+  /// ------ Test setMenu ------
+  menu->setMenu(new QMenu(0));
+  if(!menu->menu()->isEmpty())
+    {
+    qDebug() << "Line : " << __LINE__
+        << "ctkMenuComboBox::setMenu failed";
+    return EXIT_FAILURE;
+    }
+
+  QMenu* file = new QMenu("File");
+  file->addAction("first");
+  menu->setMenu(file);
+  QList<QCompleter* > completer = menu->findChildren<QCompleter *>();
+  if(menu->menu()->isEmpty()
+    && completer[0]->model()->rowCount() != 1)
+    {
+    qDebug() << "Line : " << __LINE__
+        << "ctkMenuComboBox::setMenu failed";
+    return EXIT_FAILURE;
+    }
+
+  /// ------- Test delete Menu -----
+  //delete menu->menu();
+  //file = new QMenu("File");
+  //menu->show();
+  //return app.exec();
+
+  /// ------- Test addAction -------
+  menu->setMenu(file);
+  QMenu* informatics = new QMenu("Informatics");
+  QAction* titi = informatics->addAction("titi");
+  file->addMenu(informatics);
+  if(menu->menu()->isEmpty()
+    && completer[0]->model()->rowCount() != 2)
+    {
+    qDebug() << "Line : " << __LINE__
+        << "ctkMenuComboBox::addAction failed";
+    return EXIT_FAILURE;
+    }
+
+  /// ------- Test removeAction -------
+  informatics->removeAction(titi);
+  if(completer[0]->model()->rowCount() != 1)
+    {
+    qDebug() << "Line : " << __LINE__
+        << "ctkMenuComboBox::removeAction failed";
+    return EXIT_FAILURE;
+    }
+
+  /// ------- Test add 2 same actions -> only 1 add on the completer --------
+  informatics->addAction(titi);
+  file->addAction(titi);
+  if (completer[0]->model()->rowCount() != 2)
+    {
+    qDebug() << "Line : " << __LINE__
+        << "ctkMenuComboBox::addAction failed";
+    return EXIT_FAILURE;
+    }
+
+  /// ------- Test remove one of the two --------
+  informatics->removeAction(titi);
+  //file->removeAction(first);
+  if (completer[0]->model()->rowCount() != 2)
+    {
+    qDebug() << "Line : " << __LINE__
+        << "ctkMenuComboBox::addAction failed"
+        << completer[0]->model()->rowCount();
+    return EXIT_FAILURE;
+    }
+
+  /// ------- Test remove the second one -------
+  file->removeAction(titi);
+  if (completer[0]->model()->rowCount() != 1)
+    {
+    qDebug() << "Line : " << __LINE__
+        << "ctkMenuComboBox::addAction failed"
+        << completer[0]->model()->rowCount();
+    return EXIT_FAILURE;
+    }
+
+  menu->show();
+  if (argc < 2 || QString(argv[1]) != "-I" )
+    {
+    QTimer::singleShot(200, &app, SLOT(quit()));
+    }
+
+  return app.exec();
+}
+

+ 81 - 0
Libs/Widgets/Testing/Cpp/ctkMenuComboBoxTest3.cpp

@@ -0,0 +1,81 @@
+/*=========================================================================
+
+  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 <QApplication>
+#include <QComboBox>
+#include <QDebug>
+#include <QList>
+#include <QMenu>
+#include <QTimer>
+
+// CTK includes
+#include "ctkMenuComboBox.h"
+
+// STD includes
+#include <iostream>
+
+//-----------------------------------------------------------------------------
+int ctkMenuComboBoxTest3(int argc, char * argv [] )
+{
+  QApplication app(argc, argv);
+
+  ctkMenuComboBox menu;
+  if (menu.menu())
+    {
+    qDebug()  << "Line : " << __LINE__
+              << " ctkMenuComboBox::ctkmenuComboBox failed"
+              << menu.menu();
+    return EXIT_FAILURE;
+    }
+
+  menu.setEditableBehavior(ctkMenuComboBox::Editable);
+  QList<QComboBox* > comboBox = menu.findChildren<QComboBox *>();
+  if (!comboBox[0]->isEditable())
+    {
+    qDebug()  << "Line : " << __LINE__
+              << "ctkMenuComboBox::setEditableBehavior failed";
+    return EXIT_FAILURE;
+    }
+  menu.setEditableBehavior(ctkMenuComboBox::EditableOnFocus);
+  if (comboBox[0]->isEditable())
+    {
+    qDebug()  << "Line : " << __LINE__
+              << "ctkMenuComboBox::setEditableBehavior failed";
+    return EXIT_FAILURE;
+    }
+  menu.setEditableBehavior(ctkMenuComboBox::Editable);
+  menu.setEditableBehavior(ctkMenuComboBox::NotEditable);
+  if (comboBox[0]->isEditable())
+    {
+    qDebug()  << "Line : " << __LINE__
+              << "ctkMenuComboBox::setEditableBehavior failed";
+    return EXIT_FAILURE;
+    }
+
+  menu.show();
+  if (argc < 2 || QString(argv[1]) != "-I" )
+    {
+    QTimer::singleShot(200, &app, SLOT(quit()));
+    }
+
+  return app.exec();
+}
+

+ 1 - 0
Libs/Widgets/Testing/Cpp/ctkSearchBoxTest1.cpp

@@ -17,6 +17,7 @@ int ctkSearchBoxTest1(int argc, char* argv[])
   QApplication app(argc, argv);
 
   ctkSearchBox search;
+  search.setShowSearchIcon(true);
   search.show();
 
   QWidget topLevel;

+ 451 - 0
Libs/Widgets/ctkMenuComboBox.cpp

@@ -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);
+}
+

+ 117 - 0
Libs/Widgets/ctkMenuComboBox.h

@@ -0,0 +1,117 @@
+/*=========================================================================
+
+  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.
+
+=========================================================================*/
+
+#ifndef __ctkMenuComboBox_h
+#define __ctkMenuComboBox_h
+
+// Qt includes
+#include <QMenu>
+#include <QMetaType>
+#include <QWidget>
+
+// CTK includes
+#include "ctkWidgetsExport.h"
+
+class ctkMenuComboBoxPrivate;
+
+/// QComboBox linked with a QMenu. See ctkMenuComboBox::setMenu()
+/// ctkMenuComboBox can be editable, editable on focus or disable.
+///   if it is editable :
+/// the comboBox is always editable, you can filter the Menu or show it.
+///   if it is editable on focus:
+/// the combobox become editable when it has the focus in.
+/// So ctkMenuComboBox's purpose is to filter a menu, if you edit the current text
+/// or show the menu, if you click on the arrow.
+///   if it is disabled :
+/// the ctkMenuComboBox has the same behavior as a QPushButton. You can't filter the menu.
+
+/// By default ctkMenuComboBox is editable on focus.
+/// See ctkmenuComboBox::editableType() to change the default behavior.
+
+class CTK_WIDGETS_EXPORT ctkMenuComboBox : public QWidget
+{
+  Q_OBJECT
+  Q_ENUMS(EditableBehavior)
+  Q_PROPERTY(QString defaultText READ defaultText WRITE setDefaultText)
+  Q_PROPERTY(QIcon defaultIcon READ defaultIcon WRITE setDefaultIcon)
+  Q_PROPERTY(EditableBehavior editBehavior READ editableBehavior WRITE setEditableBehavior)
+
+public:
+  enum EditableBehavior{
+    NotEditable = 0,
+    Editable,
+    EditableOnFocus
+  };
+
+  /// Superclass typedef
+  typedef QWidget Superclass;
+
+  ///
+  ctkMenuComboBox(QWidget* parent = 0);
+  virtual ~ctkMenuComboBox();
+
+  /// Add a menu to the QcomboBox and set a QCompleter
+  void setMenu(QMenu* menu);
+  QMenu* menu()const;
+
+  /// Empty by default
+  /// set the first default text.
+  void setDefaultText(const QString&);
+  QString defaultText()const;
+
+  /// Empty by default
+  /// if a QAction doesn't have icon in the menu, the comboBox takes the defaultIcon.
+  void setDefaultIcon(const QIcon&);
+  QIcon defaultIcon()const;
+
+  /// set/get the editableType; See enum EditableType for more details.
+  void setEditableBehavior(EditableBehavior editBehavior);
+  EditableBehavior editableBehavior()const;
+
+  /// See QComboBox::setMinimumContentsLength()
+  void setMinimumContentsLength(int characters);
+
+  virtual bool eventFilter(QObject* target, QEvent* event);
+
+public slots:
+  void clearActiveAction();
+
+signals:
+  void actionChanged(QAction* action);
+
+protected slots:
+  /// Change the current text/icon on the QComboBox
+  /// And trigger the action.
+  /// action selected from the menu.
+  void onActionSelected(QAction* action);
+  /// action selected from the line edit or the completer.
+  void onReturnPressed();
+
+protected:
+  QScopedPointer<ctkMenuComboBoxPrivate> d_ptr;
+
+private:
+  Q_DECLARE_PRIVATE(ctkMenuComboBox);
+  Q_DISABLE_COPY(ctkMenuComboBox);
+};
+
+Q_DECLARE_METATYPE(ctkMenuComboBox::EditableBehavior)
+
+#endif

+ 82 - 0
Libs/Widgets/ctkMenuComboBox_p.h

@@ -0,0 +1,82 @@
+/*=========================================================================
+
+  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.
+
+=========================================================================*/
+
+#ifndef __ctkMenuComboBox_p_h
+#define __ctkMenuComboBox_p_h
+
+// Qt includes
+#include <QWeakPointer>
+
+// CTK includes
+#include "ctkMenuComboBox.h"
+#include "ctkSearchBox.h"
+
+class ctkMenuComboBoxInternal: public QComboBox
+{
+public:
+  /// Superclass typedef
+  typedef QComboBox Superclass;
+
+  ctkMenuComboBoxInternal();
+  virtual ~ctkMenuComboBoxInternal();
+  virtual void showPopup();
+
+  QWeakPointer<QMenu>  Menu;
+};
+
+// -------------------------------------------------------------------------
+class ctkMenuComboBoxPrivate
+{
+  Q_DECLARE_PUBLIC(ctkMenuComboBox);
+
+protected:
+  ctkMenuComboBox* const q_ptr;
+public:
+  ctkMenuComboBoxPrivate(ctkMenuComboBox& object);
+  void init();
+  QAction* actionByTitle(const QString& text, const QMenu* parentMenu);
+  void setCurrentText(const QString& newCurrentText);
+  QString currentText()const;
+
+  void setCurrentIcon(const QIcon& newCurrentIcon);
+  QIcon currentIcon()const;
+
+  void setComboBoxEditable(bool);
+
+  void addAction(QAction* action);
+  void addMenuToCompleter(QMenu* menu);
+  void addActionToCompleter(QAction* action);
+
+  void removeActionToCompleter(QAction* action);
+
+  QIcon         DefaultIcon;
+  QString       DefaultText;
+  bool          IsDefaultTextCurrent;
+  bool          IsDefaultIconCurrent;
+
+  ctkMenuComboBox::EditableBehavior EditBehavior;
+
+  ctkMenuComboBoxInternal*    MenuComboBox;
+  QCompleter*                 SearchCompleter;
+  QWeakPointer<QMenu>         Menu;
+};
+
+#endif
+

+ 61 - 12
Libs/Widgets/ctkSearchBox.cpp

@@ -47,6 +47,9 @@ public:
 
   QIcon clearIcon;
   QIcon searchIcon;
+  bool showSearchIcon;
+  bool alwaysShowClearIcon;
+  bool hideClearIcon;
 
   QIcon::Mode clearIconMode;
 #if QT_VERSION < 0x040700
@@ -61,6 +64,9 @@ ctkSearchBoxPrivate::ctkSearchBoxPrivate(ctkSearchBox &object)
   this->clearIcon = QIcon(":Icons/clear.svg");
   this->searchIcon = QIcon(":Icons/search.svg");
   this->clearIconMode = QIcon::Disabled;
+  this->showSearchIcon = false;
+  this->alwaysShowClearIcon = false;
+  this->hideClearIcon = true;
 }
 
 // --------------------------------------------------
@@ -141,6 +147,40 @@ void ctkSearchBox::setPlaceholderText(const QString &defaultText)
 #endif
 
 // --------------------------------------------------
+void ctkSearchBox::setShowSearchIcon(bool show)
+{
+  Q_D(ctkSearchBox);
+  d->showSearchIcon = show;
+  this->update();
+}
+
+// --------------------------------------------------
+bool ctkSearchBox::showSearchIcon()const
+{
+  Q_D(const ctkSearchBox);
+  return d->showSearchIcon;
+}
+
+// --------------------------------------------------
+void ctkSearchBox::setAlwaysShowClearIcon(bool show)
+{
+  Q_D(ctkSearchBox);
+  d->alwaysShowClearIcon = show;
+  if (show == true)
+    {
+    d->hideClearIcon = false;
+    }
+  this->update();
+}
+
+// --------------------------------------------------
+bool ctkSearchBox::alwaysShowClearIcon()const
+{
+  Q_D(const ctkSearchBox);
+  return d->alwaysShowClearIcon;
+}
+
+// --------------------------------------------------
 void ctkSearchBox::paintEvent(QPaintEvent * event)
 {
   Q_D(ctkSearchBox);
@@ -153,7 +193,7 @@ void ctkSearchBox::paintEvent(QPaintEvent * event)
   QPainter p(this);
 
   QRect cRect = d->clearRect();
-  QRect sRect = d->searchRect();
+  QRect sRect = d->showSearchIcon ? d->searchRect() : QRect();
 
 #if QT_VERSION >= 0x040700
   QRect r = rect();
@@ -209,13 +249,18 @@ void ctkSearchBox::paintEvent(QPaintEvent * event)
 #endif
 
   // Draw clearIcon
-  QPixmap closePixmap = d->clearIcon.pixmap(cRect.size(),d->clearIconMode);
-  this->style()->drawItemPixmap(&p, cRect, Qt::AlignCenter, closePixmap);
-
-  //Draw searchIcon
-  QPixmap searchPixmap = d->searchIcon.pixmap(sRect.size());
-  this->style()->drawItemPixmap(&p, sRect, Qt::AlignCenter, searchPixmap);
+  if (!d->hideClearIcon)
+    {
+    QPixmap closePixmap = d->clearIcon.pixmap(cRect.size(),d->clearIconMode);
+    this->style()->drawItemPixmap(&p, cRect, Qt::AlignCenter, closePixmap);
+    }
 
+  // Draw searchIcon
+  if (d->showSearchIcon)
+    {
+    QPixmap searchPixmap = d->searchIcon.pixmap(sRect.size());
+    this->style()->drawItemPixmap(&p, sRect, Qt::AlignCenter, searchPixmap);
+    }
 }
 
 // --------------------------------------------------
@@ -229,7 +274,7 @@ void ctkSearchBox::mousePressEvent(QMouseEvent *e)
     return;
     }
 
-  if(d->searchRect().contains(e->pos()))
+  if(d->showSearchIcon && d->searchRect().contains(e->pos()))
     {
     this->selectAll();
     return;
@@ -243,7 +288,8 @@ void ctkSearchBox::mouseMoveEvent(QMouseEvent *e)
 {
   Q_D(ctkSearchBox);
 
-  if(d->clearRect().contains(e->pos()) || d->searchRect().contains(e->pos()))
+  if(d->clearRect().contains(e->pos()) ||
+     (d->showSearchIcon && d->searchRect().contains(e->pos())))
     {
     this->setCursor(Qt::ArrowCursor);
     }
@@ -258,9 +304,9 @@ void ctkSearchBox::mouseMoveEvent(QMouseEvent *e)
 void ctkSearchBox::resizeEvent(QResizeEvent * event)
 {
   Q_D(ctkSearchBox);
-  static int iconSpacing = 4; // hardcoded, same way as pushbutton icon spacing
+  static int iconSpacing = 0; // hardcoded,
   QRect cRect = d->clearRect();
-  QRect sRect = d->searchRect();
+  QRect sRect = d->showSearchIcon ? d->searchRect() : QRect();
   // Set 2 margins each sides of the QLineEdit, according to the icons
   this->setTextMargins( sRect.right() + iconSpacing, 0,
                         event->size().width() - cRect.left() - iconSpacing,0);
@@ -270,6 +316,9 @@ void ctkSearchBox::resizeEvent(QResizeEvent * event)
 void ctkSearchBox::updateClearButtonState()
 {
   Q_D(ctkSearchBox);
-  d->clearIconMode = this->text().isEmpty() ? QIcon::Disabled : QIcon::Normal;
+  if (!d->alwaysShowClearIcon)
+    {
+    d->hideClearIcon = this->text().isEmpty() ? true : false;
+    }
 }
 

+ 7 - 0
Libs/Widgets/ctkSearchBox.h

@@ -50,6 +50,7 @@ class CTK_WIDGETS_EXPORT ctkSearchBox : public QLineEdit
   /// manually.
   Q_PROPERTY(QString placeholderText READ placeholderText WRITE setPlaceholderText)
 #endif
+  Q_PROPERTY(bool showSearchIcon READ showSearchIcon WRITE setShowSearchIcon)
 
 public:
   /// Superclass typedef
@@ -62,6 +63,12 @@ public:
   QString placeholderText()const;
   void setPlaceholderText(const QString& defaultText);
 #endif
+  /// False by default
+  void setShowSearchIcon(bool);
+  bool showSearchIcon()const;
+
+  void setAlwaysShowClearIcon(bool);
+  bool alwaysShowClearIcon()const;
 
 protected slots:
   /// Change the clear icon's state to enabled or disabled.