Просмотр исходного кода

Merge remote-tracking branch 'benjaminlong/ctksearchbox'

* benjaminlong/ctksearchbox:
  Add ctkSearchBox an advanced QLineEdit for searching
Julien Finet лет назад: 13
Родитель
Сommit
ef5d05392e

+ 3 - 0
Libs/Widgets/CMakeLists.txt

@@ -88,6 +88,8 @@ SET(KIT_SRCS
   ctkRangeSlider.h
   ctkRangeWidget.cpp
   ctkRangeWidget.h
+  ctkSearchBox.cpp
+  ctkSearchBox.h
   ctkScreenshotDialog.cpp
   ctkScreenshotDialog.h
   ctkScreenshotDialog_p.h
@@ -203,6 +205,7 @@ SET(KIT_MOC_SRCS
   ctkRangeWidget.h
   ctkScreenshotDialog.h
   ctkScreenshotDialog_p.h
+  ctkSearchBox.h
   ctkSettings.h
   ctkSettingsDialog.h
   ctkSettingsPanel.h

+ 3 - 1
Libs/Widgets/Plugins/CMakeLists.txt

@@ -12,7 +12,6 @@ SET(PLUGIN_SRCS
   ctkWidgetsPlugins.h
   ctkWidgetsAbstractPlugin.cpp
   ctkWidgetsAbstractPlugin.h
-
   ctkActionsWidgetPlugin.cpp
   ctkActionsWidgetPlugin.h
   ctkAxesWidgetPlugin.cpp
@@ -67,6 +66,8 @@ SET(PLUGIN_SRCS
   ctkTransferFunctionViewPlugin.h
   ctkTreeComboBoxPlugin.cpp
   ctkTreeComboBoxPlugin.h
+  ctkSearchBoxPlugin.cpp
+  ctkSearchBoxPlugin.h
   ctkSettingsPanelPlugin.cpp
   ctkSettingsPanelPlugin.h
   ctkSettingsDialogPlugin.cpp
@@ -110,6 +111,7 @@ SET(PLUGIN_MOC_SRCS
   ctkRangeWidgetPlugin.h
   ctkTransferFunctionViewPlugin.h
   ctkTreeComboBoxPlugin.h
+  ctkSearchBoxPlugin.h
   ctkSettingsPanelPlugin.h
   ctkSettingsDialogPlugin.h
   ctkSliderWidgetPlugin.h

+ 69 - 0
Libs/Widgets/Plugins/ctkSearchBoxPlugin.cpp

@@ -0,0 +1,69 @@
+/*=========================================================================
+
+  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.
+
+=========================================================================*/
+
+// CTK includes
+#include "ctkSearchBoxPlugin.h"
+#include "ctkSearchBox.h"
+
+//-----------------------------------------------------------------------------
+ctkSearchBoxPlugin::ctkSearchBoxPlugin(QObject *_parent)
+  : QObject(_parent)
+{
+
+}
+
+//-----------------------------------------------------------------------------
+QWidget *ctkSearchBoxPlugin::createWidget(QWidget *_parent)
+{
+  ctkSearchBox* _widget = new ctkSearchBox(_parent);
+  return _widget;
+}
+
+//-----------------------------------------------------------------------------
+QString ctkSearchBoxPlugin::domXml() const
+{
+  return "<widget class=\"ctkSearchBox\" \
+          name=\"SearchBox\">\n"
+          "</widget>\n";
+}
+
+// --------------------------------------------------------------------------
+QIcon ctkSearchBoxPlugin::icon() const
+{
+  return QIcon(":/Icons/search.svg");
+}
+
+//-----------------------------------------------------------------------------
+QString ctkSearchBoxPlugin::includeFile() const
+{
+  return "ctkSearchBox.h";
+}
+
+//-----------------------------------------------------------------------------
+bool ctkSearchBoxPlugin::isContainer() const
+{
+  return false;
+}
+
+//-----------------------------------------------------------------------------
+QString ctkSearchBoxPlugin::name() const
+{
+  return "ctkSearchBox";
+}

+ 44 - 0
Libs/Widgets/Plugins/ctkSearchBoxPlugin.h

@@ -0,0 +1,44 @@
+/*=========================================================================
+
+  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 __ctkSearchBoxPlugin_h
+#define __ctkSearchBoxPlugin_h
+
+// CTK includes
+#include "ctkWidgetsAbstractPlugin.h"
+
+class CTK_WIDGETS_PLUGINS_EXPORT ctkSearchBoxPlugin :
+  public QObject,
+  public ctkWidgetsAbstractPlugin
+{
+  Q_OBJECT
+
+public:
+  ctkSearchBoxPlugin(QObject *_parent = 0);
+  
+  QWidget *createWidget(QWidget *_parent);
+  QString  domXml() const;
+  QIcon    icon() const;
+  QString  includeFile() const;
+  bool     isContainer() const;
+  QString  name() const;
+};
+
+#endif

+ 2 - 0
Libs/Widgets/Plugins/ctkWidgetsPlugins.h

@@ -50,6 +50,7 @@
 #include "ctkPopupWidgetPlugin.h"
 #include "ctkRangeSliderPlugin.h"
 #include "ctkRangeWidgetPlugin.h"
+#include "ctkSearchBoxPlugin.h"
 #include "ctkSettingsPanelPlugin.h"
 #include "ctkSettingsDialogPlugin.h"
 #include "ctkSliderWidgetPlugin.h"
@@ -93,6 +94,7 @@ public:
             << new ctkPopupWidgetPlugin
             << new ctkRangeSliderPlugin
             << new ctkRangeWidgetPlugin
+            << new ctkSearchBoxPlugin
             << new ctkSettingsPanelPlugin
             << new ctkSettingsDialogPlugin
             << new ctkSliderWidgetPlugin

+ 173 - 0
Libs/Widgets/Resources/Icons/clear.svg

@@ -0,0 +1,173 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://web.resource.org/cc/"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="159.60748"
+   height="159.60748"
+   id="svg2046"
+   sodipodi:version="0.32"
+   inkscape:version="0.44"
+   version="1.0"
+   sodipodi:docbase="C:\Documents and Settings\Molumen\Desktop"
+   sodipodi:docname="round_error_warning_icon.svg">
+  <defs
+     id="defs2048">
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient5190">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1"
+         offset="0"
+         id="stop5192" />
+      <stop
+         style="stop-color:#ff0000;stop-opacity:1"
+         offset="1"
+         id="stop5194" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5190"
+       id="linearGradient2109"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.312273,0,0,0.320491,127.55,434.3769)"
+       x1="264.582"
+       y1="296.37491"
+       x2="380.6846"
+       y2="553.74487" />
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient5197">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1"
+         offset="0"
+         id="stop5199" />
+      <stop
+         style="stop-color:#ff0000;stop-opacity:1"
+         offset="1"
+         id="stop5201" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5197"
+       id="linearGradient2107"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(-0.301633,8.082223e-2,-8.294933e-2,-0.30957,402.8575,730.1118)"
+       x1="249.48328"
+       y1="262.87494"
+       x2="272.02597"
+       y2="427.54285" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     gridtolerance="10000"
+     guidetolerance="10"
+     objecttolerance="10"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="1.4"
+     inkscape:cx="85.853793"
+     inkscape:cy="82.99359"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     inkscape:window-width="853"
+     inkscape:window-height="569"
+     inkscape:window-x="606"
+     inkscape:window-y="124" />
+  <metadata
+     id="metadata2051">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title>round error warning button</dc:title>
+        <dc:creator>
+          <cc:Agent>
+            <dc:title>Lumen Design Studio</dc:title>
+          </cc:Agent>
+        </dc:creator>
+        <dc:contributor>
+          <cc:Agent>
+            <dc:title>Lumen Design Studio</dc:title>
+          </cc:Agent>
+        </dc:contributor>
+        <dc:description>error warning icon</dc:description>
+        <dc:subject>
+          <rdf:Bag>
+            <rdf:li>error</rdf:li>
+            <rdf:li>icon</rdf:li>
+            <rdf:li>button</rdf:li>
+            <rdf:li>red</rdf:li>
+            <rdf:li>stop</rdf:li>
+            <rdf:li>no</rdf:li>
+            <rdf:li>warning</rdf:li>
+            <rdf:li>negative</rdf:li>
+            <rdf:li>delete</rdf:li>
+            <rdf:li>cancel</rdf:li>
+          </rdf:Bag>
+        </dc:subject>
+        <cc:license
+           rdf:resource="http://web.resource.org/cc/PublicDomain" />
+      </cc:Work>
+      <cc:License
+         rdf:about="http://web.resource.org/cc/PublicDomain">
+        <cc:permits
+           rdf:resource="http://web.resource.org/cc/Reproduction" />
+        <cc:permits
+           rdf:resource="http://web.resource.org/cc/Distribution" />
+        <cc:permits
+           rdf:resource="http://web.resource.org/cc/DerivativeWorks" />
+      </cc:License>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(-311.6248,-429.7013)">
+    <g
+       id="g2100"
+       transform="matrix(0.9,0,0,0.9,170.4421,-31.11303)">
+      <path
+         sodipodi:type="arc"
+         style="fill:red;fill-opacity:1;fill-rule:evenodd;stroke:#cb0000;stroke-width:20.127882;stroke-linecap:butt;stroke-linejoin:miter;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+         id="path2174"
+         sodipodi:cx="384.28571"
+         sodipodi:cy="559.50507"
+         sodipodi:rx="250"
+         sodipodi:ry="250"
+         d="M 634.28571 559.50507 A 250 250 0 1 1  134.28571,559.50507 A 250 250 0 1 1  634.28571 559.50507 z"
+         transform="matrix(0.306862,0,0,0.306862,127.6178,428.9959)" />
+      <path
+         style="fill:url(#linearGradient2107);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+         d="M 264.19015,668.93136 C 283.23679,663.82784 298.36338,651.27316 307.41885,635.10742 C 322.12654,600.28933 267.25204,681.80733 221.92757,666.99805 C 235.1036,671.83026 249.74086,672.80304 264.19015,668.93136 z "
+         id="path2540"
+         sodipodi:nodetypes="cccc" />
+      <path
+         sodipodi:nodetypes="ccccc"
+         id="path2197"
+         d="M 245.65747,529.36222 C 206.22043,529.36222 174.21527,562.20957 174.21527,602.68438 C 174.21527,608.15829 174.82272,613.48618 175.93277,618.61875 C 215.13788,543.15095 257.37836,590.65582 311.83982,575.07214 C 301.20439,548.27438 275.57549,529.36222 245.65747,529.36222 z "
+         style="fill:url(#linearGradient2109);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+      <path
+         sodipodi:nodetypes="ccccccccccccc"
+         id="text2705"
+         d="M 267.42338,640.03739 L 245.74431,618.35831 L 223.86132,640.24129 L 205.98576,622.36573 L 227.86874,600.48275 L 206.18966,578.80367 L 223.58945,561.40388 L 245.26853,583.08296 L 267.21948,561.13201 L 285.09504,579.00757 L 263.14409,600.95853 L 284.82317,622.6376 L 267.42338,640.03739"
+         style="font-size:660.22442627px;font-style:italic;font-variant:normal;font-weight:bold;font-stretch:normal;line-height:100%;writing-mode:lr-tb;text-anchor:start;opacity:0.73529412;fill:white;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Arial" />
+      <path
+         id="text2713"
+         d="M 267.29788,637.00578 L 245.71038,615.41828 L 223.95289,637.17577 L 209.05128,622.27415 L 230.80877,600.51667 L 209.22127,578.92917 L 223.72626,564.42418 L 245.31376,586.01168 L 267.1279,564.19754 L 282.02951,579.09915 L 260.21537,600.91329 L 281.80287,622.50079 L 267.29788,637.00578"
+         style="font-size:550.38305664px;font-style:italic;font-variant:normal;font-weight:bold;font-stretch:normal;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:white;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Arial" />
+    </g>
+  </g>
+</svg>

Разница между файлами не показана из-за своего большого размера
+ 475 - 0
Libs/Widgets/Resources/Icons/search.svg


+ 2 - 0
Libs/Widgets/Resources/ctkWidgets.qrc

@@ -5,5 +5,7 @@
     <file>Icons/plus.png</file>
     <file>Icons/expand-down.png</file>
     <file>Icons/expand-up.png</file>
+    <file>Icons/search.svg</file>
+    <file>Icons/clear.svg</file>
   </qresource>
 </RCC>

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

@@ -49,6 +49,8 @@ SET(TEST_SOURCES
   ctkRangeWidgetTest2.cpp
   ctkDateRangeWidgetTest1.cpp
   ctkScreenshotDialogTest1.cpp
+  ctkSearchBoxTest1.cpp
+  ctkSearchBoxTest2.cpp
   ctkSettingsPanelTest1.cpp
   ctkSettingsPanelTest2.cpp
   ctkSettingsTest1.cpp
@@ -168,6 +170,8 @@ SIMPLE_TEST( ctkRangeSliderTest1 )
 SIMPLE_TEST( ctkRangeWidgetTest1 )
 SIMPLE_TEST( ctkRangeWidgetTest2 )
 SIMPLE_TEST( ctkScreenshotDialogTest1 )
+SIMPLE_TEST( ctkSearchBoxTest1 )
+SIMPLE_TEST( ctkSearchBoxTest2 )
 SIMPLE_TEST( ctkSettingsDialogTest1 )
 SIMPLE_TEST( ctkSettingsPanelTest1 )
 SIMPLE_TEST( ctkSettingsPanelTest2 )

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

@@ -0,0 +1,36 @@
+// Qt includes
+#include <QApplication>
+#include <QDebug>
+#include <QVBoxLayout>
+#include <QTimer>
+
+// CTK includes
+#include "ctkSearchBox.h"
+
+// STD includes
+#include <cstdlib>
+#include <iostream>
+
+// ------------------------------------------------------------------------------
+int ctkSearchBoxTest1(int argc, char* argv[])
+{
+  QApplication app(argc, argv);
+
+  ctkSearchBox search;
+  search.show();
+
+  QWidget topLevel;
+  ctkSearchBox search3;
+  QVBoxLayout* layout = new QVBoxLayout;
+  layout->addWidget(&search3);
+  topLevel.setLayout(layout);
+  topLevel.show();
+
+  if (argc < 2 || QString(argv[1]) != "-I" )
+    {
+    QTimer::singleShot(200, &app, SLOT(quit()));
+    }
+
+  return app.exec();
+}
+

+ 63 - 0
Libs/Widgets/Testing/Cpp/ctkSearchBoxTest2.cpp

@@ -0,0 +1,63 @@
+// Qt includes
+#include <QApplication>
+#include <QDebug>
+#include <QLabel>
+#include <QListView>
+#include <QSortFilterProxyModel>
+#include <QStringListModel>
+#include <QTimer>
+#include <QVBoxLayout>
+
+//CTK includes
+#include "ctkSearchBox.h"
+
+// STD includes
+#include <cstdlib>
+#include <iostream>
+
+// ------------------------------------------------------------------------------
+int ctkSearchBoxTest2(int argc, char* argv[])
+{
+  QApplication app(argc, argv);
+
+  QStringList stringList;
+  stringList<<"totoa"<<"TOTOaa"<<"tic"<<"tac"<<"nice"<<"slicer"<<"monday"<<"july"<<"phone";
+
+  ctkSearchBox search3;
+  search3.show();
+
+  search3.setText("phone");
+
+  QRegExp regExp(search3.text(),Qt::CaseInsensitive, QRegExp::Wildcard);
+
+  //QStringList testFilter = stringList.filter(search3.text());
+  QStringList testFilter = stringList.filter(regExp);
+  qDebug() << "Result of Test Filter : " << testFilter;
+
+  if (testFilter.size() >= stringList.size())
+    {
+    qDebug() << "Line : " << __LINE__<< " error with the filter : " << search3.text();
+    }
+
+  QStringListModel listModel(stringList);
+
+  QSortFilterProxyModel filterModel;
+  filterModel.setSourceModel(&listModel);
+  filterModel.setFilterCaseSensitivity(Qt::CaseInsensitive);
+  filterModel.setFilterWildcard(search3.text());
+
+  QObject::connect(&search3, SIGNAL(textChanged(QString)),
+                   &filterModel, SLOT(setFilterWildcard(QString)));
+
+  QListView listView;
+  listView.setModel(&filterModel);
+  listView.show();
+
+  if (argc < 2 || QString(argv[1]) != "-I" )
+    {
+    QTimer::singleShot(200, &app, SLOT(quit()));
+    }
+
+  return app.exec();
+}
+

+ 4 - 0
Libs/Widgets/ctkCheckablePushButton.cpp

@@ -241,9 +241,12 @@ void ctkCheckablePushButton::paintEvent(QPaintEvent * _event)
   QStyleOptionButton opt;
   this->initStyleOption(&opt);
 
+  // Checkbox size
   QSize indicatorSize = QSize(style()->pixelMetric(QStyle::PM_IndicatorWidth, &opt, this),
                               style()->pixelMetric(QStyle::PM_IndicatorHeight, &opt, this));
+  // Replace the icon size by the checkbox size
   opt.iconSize = indicatorSize;
+  // Draw the panel of the button (no text, no icon)
   style()->drawControl(QStyle::CE_PushButtonBevel, &opt, &p, this);
   // TBD is PE_PanelButtonCommand better ?
   //style()->drawPrimitive(QStyle::PE_PanelButtonCommand, &opt, &p, this);
@@ -258,6 +261,7 @@ void ctkCheckablePushButton::paintEvent(QPaintEvent * _event)
     tf |= Qt::TextHideMnemonic;
     }
   int textWidth = opt.fontMetrics.boundingRect(opt.rect, tf, opt.text).width();
+  // Spacing betweent the text and the checkbox
   int indicatorSpacing = this->style()->pixelMetric(QStyle::PM_CheckBoxLabelSpacing, &opt, this);
   int buttonMargin = this->style()->pixelMetric(QStyle::PM_ButtonMargin, &opt, this);
   // Draw Indicator

+ 197 - 0
Libs/Widgets/ctkSearchBox.cpp

@@ -0,0 +1,197 @@
+/*=========================================================================
+
+  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 <QDebug>
+#include <QIcon>
+#include <QMouseEvent>
+#include <QPainter>
+#include <QRect>
+#include <QStyleOption>
+
+// CTK includes
+#include "ctkSearchBox.h"
+
+// --------------------------------------------------
+class ctkSearchBoxPrivate
+{
+  Q_DECLARE_PUBLIC(ctkSearchBox);
+protected:
+  ctkSearchBox* const q_ptr;
+public:
+  ctkSearchBoxPrivate(ctkSearchBox& object);
+  void init();
+
+  /// Position and size for the clear icon in the QLineEdit
+  QRect clearRect()const;
+  /// Position and size for the search icon in the QLineEdit
+  QRect searchRect()const;
+
+  QIcon clearIcon;
+  QIcon searchIcon;
+
+  QIcon::Mode clearIconMode;
+};
+
+// --------------------------------------------------
+ctkSearchBoxPrivate::ctkSearchBoxPrivate(ctkSearchBox &object)
+  : q_ptr(&object)
+{
+  this->clearIcon = QIcon(":Icons/clear.svg");
+  this->searchIcon = QIcon(":Icons/search.svg");
+  this->clearIconMode = QIcon::Disabled;
+}
+
+// --------------------------------------------------
+void ctkSearchBoxPrivate::init()
+{
+  Q_Q(ctkSearchBox);
+
+  // Set a text by default on the QLineEdit
+  q->setPlaceholderText(q->tr("Search..."));
+
+  QObject::connect(q, SIGNAL(textChanged(const QString&)),
+                   q, SLOT(updateClearButtonState()));
+}
+
+// --------------------------------------------------
+QRect ctkSearchBoxPrivate::clearRect()const
+{
+  Q_Q(const ctkSearchBox);
+  QRect cRect = this->searchRect();
+  cRect.moveLeft(q->width() - cRect.width() - cRect.left());
+  return cRect;
+}
+
+// --------------------------------------------------
+QRect ctkSearchBoxPrivate::searchRect()const
+{
+  Q_Q(const ctkSearchBox);
+  QRect sRect;
+  // If the QLineEdit has a frame, the icon must be shifted from
+  // the frame line width
+  if (q->hasFrame())
+    {
+    QStyleOptionFrameV2 opt;
+    q->initStyleOption(&opt);
+    sRect.moveTopLeft(QPoint(opt.lineWidth, opt.lineWidth));
+    }
+  // Hardcoded: shift by 1 pixel because some styles have a focus frame inside
+  // the line edit frame.
+  sRect.translate(QPoint(1,1));
+  // Square size
+  sRect.setSize(QSize(q->height(),q->height()) -
+                2*QSize(sRect.left(), sRect.top()));
+  return sRect;
+}
+
+// --------------------------------------------------
+ctkSearchBox::ctkSearchBox(QWidget* _parent)
+  : QLineEdit(_parent)
+  , d_ptr(new ctkSearchBoxPrivate(*this))
+{
+  Q_D(ctkSearchBox);
+  d->init();
+}
+
+// --------------------------------------------------
+ctkSearchBox::~ctkSearchBox()
+{
+}
+
+// --------------------------------------------------
+void ctkSearchBox::paintEvent(QPaintEvent * event)
+{
+  Q_D(ctkSearchBox);
+  QPainter p(this);
+
+  // Draw the line edit with text.
+  // Text has already been shifted to the right (in resizeEvent()) to leave
+  // space for the search icon.
+  this->Superclass::paintEvent(event);
+
+  // Draw clearIcon
+  QRect cRect = d->clearRect();
+  QPixmap closePixmap = d->clearIcon.pixmap(cRect.size(),d->clearIconMode);
+  this->style()->drawItemPixmap(&p, cRect, Qt::AlignCenter, closePixmap);
+
+  // Draw searchIcon
+  QRect sRect = d->searchRect();
+  QPixmap searchPixmap = d->searchIcon.pixmap(sRect.size());
+  this->style()->drawItemPixmap(&p, sRect, Qt::AlignCenter, searchPixmap);
+
+}
+
+// --------------------------------------------------
+void ctkSearchBox::mousePressEvent(QMouseEvent *e)
+{
+  Q_D(ctkSearchBox);
+
+  if(d->clearRect().contains(e->pos()))
+    {
+    this->clear();
+    return;
+    }
+
+  if(d->searchRect().contains(e->pos()))
+    {
+    this->selectAll();
+    return;
+    }
+  
+  this->Superclass::mousePressEvent(e);
+}
+
+// --------------------------------------------------
+void ctkSearchBox::mouseMoveEvent(QMouseEvent *e)
+{
+  Q_D(ctkSearchBox);
+
+  if(d->clearRect().contains(e->pos()) || d->searchRect().contains(e->pos()))
+    {
+    this->setCursor(Qt::ArrowCursor);
+    }
+  else
+    {
+    this->setCursor(this->isReadOnly() ? Qt::ArrowCursor : Qt::IBeamCursor);
+    }
+  this->Superclass::mouseMoveEvent(e);
+}
+
+// --------------------------------------------------
+void ctkSearchBox::resizeEvent(QResizeEvent * event)
+{
+  Q_D(ctkSearchBox);
+  static int iconSpacing = 4; // hardcoded, same way as pushbutton icon spacing
+  QRect cRect = d->clearRect();
+  QRect sRect = d->searchRect();
+  // 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);
+}
+
+// --------------------------------------------------
+void ctkSearchBox::updateClearButtonState()
+{
+  Q_D(ctkSearchBox);
+  d->clearIconMode = this->text().isEmpty() ? QIcon::Disabled : QIcon::Normal;
+}
+

+ 73 - 0
Libs/Widgets/ctkSearchBox.h

@@ -0,0 +1,73 @@
+/*=========================================================================
+
+  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 __ctkSearchBox_h
+#define __ctkSearchBox_h
+
+// QT includes
+#include <QLineEdit>
+
+// CTK includes
+#include "ctkWidgetsExport.h"
+
+class ctkSearchBoxPrivate;
+
+/// QLineEdit with two QIcons on each side: search and clear.
+/// "Search" selects all the text
+/// "Clear" clears the current text
+/// See QLineEdit::text to set/get the current text.
+/// ctkSearchBox's purpose is to be used to filter other widgets.
+/// e.g.:
+/// <code>
+///  ctkSearchBox searchBox;
+///  QSortFilterProxyModel filterModel;
+///  QObject::connect(&searchBox, SIGNAL(textChanged(QString)),
+///                   &filterModel, SLOT(setFilterFixedString(QString)));
+///  ...
+/// </code>
+class CTK_WIDGETS_EXPORT ctkSearchBox : public QLineEdit
+{
+  Q_OBJECT
+
+public:
+  /// Superclass typedef
+  typedef QLineEdit Superclass;
+
+  ctkSearchBox(QWidget *parent = 0);
+  virtual ~ctkSearchBox();
+
+protected slots:
+  /// Change the clear icon's state to enabled or disabled.
+  void updateClearButtonState();
+
+protected:
+  virtual void paintEvent(QPaintEvent*);
+  virtual void mousePressEvent(QMouseEvent* event);
+  virtual void mouseMoveEvent(QMouseEvent *event);
+  virtual void resizeEvent(QResizeEvent * event);
+
+  QScopedPointer<ctkSearchBoxPrivate> d_ptr;
+
+private:
+  Q_DECLARE_PRIVATE(ctkSearchBox);
+  Q_DISABLE_COPY(ctkSearchBox);
+};
+#endif // __ctkSearchBox_h
+