> hideView() doesn't work as this is only called when the Ok or Close > button are pressed. Not when the dialog is canceled by Escape. > > Maybe you should put the resetSearch function in a showEvent function > which is called when the dialog is shown.
OK, that's a good idea, this makes the patch more self-contained, but I'll use hideEvent. showEvent is called after the widget is shown, that's not what we want. > However, you should then set the text in the textfield and then > manually reset all widgets to enabled. This means you have to either > block the signals, but better .. listen to textEdited instead of > textChanged. > > + > > + connect(search_, SIGNAL(textChanged(QString)), this, > > SLOT(filterChanged(QString))); > > > textChanged -> textEdited so that this will not react to > programmatically changing the text in the search box as a > consequence of resetSearch() for instance. Why we don't want that? It's correct: "search_->setText(QString())" re-enables all the widgets automatically. See below. > > +bool matches(QString & input, QString const & search) > > QString const & input ... ? > > +{ > > + // Check if the input contains the search string > > + return input.remove('&').contains(search, Qt::CaseInsensitive); > > +} QString & QString::remove ( QChar ch, Qt::CaseSensitivity cs = Qt::CaseSensitive ) <-- Not const method. Actually removes the char from the string. > > +void PanelStack::searchTimeout() > > I would like it more that searchTimeout just calls > search() which then implements the searching. > > Then you can call search() from showEvent as well for instance. Right. > > + // otherwise we disable everything and then selectively > > + // re-enable matching items > > + foreach (QTreeWidgetItem * tree_item, panel_map_) { > > + setTreeItemStatus(tree_item, enable_all); > > + } > > if enable_all is true, we don't need to loop over all widgets... We have to, enable_all (search box empty) means we have to re-enable all the items in the tree, as they could have been disabled by the previous search. Thanks for your comments. venom00 Index: src/frontends/qt4/PanelStack.cpp =================================================================== --- src/frontends/qt4/PanelStack.cpp (revisione 38474) +++ src/frontends/qt4/PanelStack.cpp (copia locale) @@ -15,12 +15,23 @@ #include "qt_helpers.h" #include "support/debug.h" +#include "support/foreach.h" +#include <QApplication> +#include <QAbstractButton> +#include <QColorGroup> +#include <QComboBox> #include <QFontMetrics> +#include <QGroupBox> #include <QHBoxLayout> #include <QHeaderView> +#include <QLabel> +#include <QLineEdit> +#include <QListWidget> +#include <QPalette> #include <QStackedWidget> #include <QTreeWidget> +#include <QVBoxLayout> #include "support/lassert.h" @@ -33,9 +44,16 @@ PanelStack::PanelStack(QWidget * parent) : QWidget(parent) { + delay_search_ = new QTimer(this); list_ = new QTreeWidget(this); stack_ = new QStackedWidget(this); + search_ = new QLineEdit(this); + // Configure the timer + delay_search_->setSingleShot(true); + connect(delay_search_, SIGNAL(timeout()), this, SLOT(searchTimeout())); + + // Configure tree list_->setRootIsDecorated(false); list_->setColumnCount(1); list_->header()->hide(); @@ -48,9 +66,22 @@ connect(list_, SIGNAL(itemClicked (QTreeWidgetItem*, int)), this, SLOT(itemSelected(QTreeWidgetItem *, int))); - QHBoxLayout * layout = new QHBoxLayout(this); - layout->addWidget(list_, 0); - layout->addWidget(stack_, 1); + // Configure the search box +#if QT_VERSION >= 0x040700 + search_->setPlaceholderText(qt_("Search")); +#endif + + connect(search_, SIGNAL(textChanged(QString)), this, SLOT(filterChanged(QString))); + + // Create the output layout, horizontal plus a VBox on the left with the search + // box and the tree + QVBoxLayout * left_layout = new QVBoxLayout(); + left_layout->addWidget(search_, 0); + left_layout->addWidget(list_, 1); + + QHBoxLayout * main_layout = new QHBoxLayout(this); + main_layout->addLayout(left_layout, 0); + main_layout->addWidget(stack_, 1); } @@ -132,22 +163,152 @@ QTreeWidgetItem * previous) { // do nothing when clicked on whitespace (item=NULL) - if( !item ) + if (!item) return; // if we have a category, expand the tree and go to the - // first item + // first enabled item if (item->childCount() > 0) { item->setExpanded(true); - if (previous && previous->parent() != item) - switchPanel( item->child(0), previous ); + if (previous && previous->parent() != item) { + // Looks for a child not disabled + for (int i = 0; i < item->childCount(); ++i) { + if (item->child(i)->flags() & Qt::ItemIsEnabled) { + switchPanel(item->child(i), previous); + break; + } + } + } } else if (QWidget * w = widget_map_.value(item, 0)) { stack_->setCurrentWidget(w); } } +bool matches(QString & input, QString const & search) +{ + // Check if the input contains the search string + return input.remove('&').contains(search, Qt::CaseInsensitive); +} +void setTreeItemStatus(QTreeWidgetItem * tree_item, bool enabled) +{ + // Enable/disable the item + tree_item->setDisabled(!enabled); + + // Change the color from black to gray or viceversa + QPalette::ColorGroup new_color = enabled ? QPalette::Active : QPalette::Disabled; + tree_item->setTextColor(0, QApplication::palette().color(new_color, QPalette::Text)); +} + +void PanelStack::hideEvent(QHideEvent * event) +{ + QWidget::hideEvent(event); + + // Programatically hidden (not simply minimized by the user) + if (!event->spontaneous()) + resetSearch(); +} + +void PanelStack::filterChanged(QString const & /*search*/) +{ + // The text in the search box is changed, reset the timer + // and then search in the widgets + delay_search_->start(300); +} + +void PanelStack::searchTimeout() +{ + search(); +} + +void PanelStack::search() +{ + QString search = search_->text(); + bool enable_all = search.isEmpty(); + + // If the search string is empty we enable all the items + // otherwise we disable everything and then selectively + // re-enable matching items + foreach (QTreeWidgetItem * tree_item, panel_map_) { + setTreeItemStatus(tree_item, enable_all); + } + + foreach (QTreeWidgetItem * tree_item, panel_map_) { + + // Current widget + QWidget * pane_widget = widget_map_[tree_item]; + + // First of all we look in the pane name + bool pane_matches = tree_item->text(0).contains(search, Qt::CaseInsensitive); + + // If the tree item has an associated pane + if (pane_widget) { + + // Loops on the list of children widgets (recursive) + QWidgetList children = pane_widget->findChildren<QWidget *>(); + foreach (QWidget * child_widget, children) { + bool widget_matches = false; + + // Try to cast to the most common widgets and looks in it's content + // It's bad OOP, it would be nice to have a QWidget::toString() overloaded by + // each widget, but this would require to change Qt or subclass each widget. + // Note that we have to ignore the amperstand symbol + if (QAbstractButton * button = qobject_cast<QAbstractButton *>(child_widget)) { + widget_matches = matches(button->text(), search); + + } else if (QGroupBox * group_box = qobject_cast<QGroupBox *>(child_widget)) { + widget_matches = matches(group_box->title(), search); + + } else if (QLabel * label = qobject_cast<QLabel *>(child_widget)) { + widget_matches = matches(label->text(), search); + + } else if (QLineEdit * line_edit = qobject_cast<QLineEdit *>(child_widget)) { + widget_matches = matches(line_edit->text(), search); + + } else if (QListWidget * list_widget = qobject_cast<QListWidget *>(child_widget)) { + widget_matches = (list_widget->findItems(search, Qt::MatchContains)).count() > 0; + + } else if (QTreeWidget * tree_view = qobject_cast<QTreeWidget *>(child_widget)) { + widget_matches = (tree_view->findItems(search, Qt::MatchContains)).count() > 0; + + } else if (QComboBox * combo_box = qobject_cast<QComboBox *>(child_widget)) { + widget_matches = (combo_box->findText(search, Qt::MatchContains)) != -1; + + } else { + continue; + } + + // If this widget meets the search criteria + if (widget_matches && !enable_all) { + // The pane too meets the search criteria + pane_matches = true; + + // Highlight the widget + QPalette widget_palette = child_widget->palette(); + widget_palette.setColor(child_widget->foregroundRole(), Qt::red); + child_widget->setPalette(widget_palette); + } else { + // Reset the color of the widget + child_widget->setPalette(QApplication::palette(child_widget)); + } + } + + // If the pane meets the search criteria + if (pane_matches && !enable_all) { + // Expand and enable the pane and his ancestors (typically just the parent) + QTreeWidgetItem * item = tree_item; + do { + item->setExpanded(true); + setTreeItemStatus(item, true); + item = item->parent(); + } while (item); + } + } + + } +} + void PanelStack::itemSelected(QTreeWidgetItem * item, int) { // de-select the category if a child is selected @@ -162,6 +323,11 @@ qMax(list_->height(), stack_->height())); } +void PanelStack::resetSearch() +{ + search_->setText(QString()); +} + } // namespace frontend } // namespace lyx Index: src/frontends/qt4/PanelStack.h =================================================================== --- src/frontends/qt4/PanelStack.h (revisione 38474) +++ src/frontends/qt4/PanelStack.h (copia locale) @@ -13,12 +13,16 @@ #ifndef PANELSTACK_H #define PANELSTACK_H +#include <QHash> +#include <QHideEvent> +#include <QTimer> #include <QWidget> -#include <QHash> +class QAbstractButton; +class QLineEdit; +class QStackedWidget; class QTreeWidget; class QTreeWidgetItem; -class QStackedWidget; namespace lyx { namespace frontend { @@ -44,13 +48,25 @@ bool isCurrentPanel(QString const & name) const; /// QSize sizeHint() const; + /// perform the search + void search(); + /// reset the search box + void resetSearch(); public Q_SLOTS: + /// the option filter changed + void filterChanged(QString const & search); + /// timeout for the search box + void searchTimeout(); /// set current panel from an item void switchPanel(QTreeWidgetItem * it, QTreeWidgetItem * previous = 0); /// click on the tree void itemSelected(QTreeWidgetItem *, int); +protected: + /// widget hidden + void hideEvent(QHideEvent * event); + private: /// typedef QHash<QString, QTreeWidgetItem *> PanelMap; @@ -61,11 +77,18 @@ WidgetMap widget_map_; + /// contains the search box + QLineEdit * search_; + /// contains the items QTreeWidget * list_; /// contains the panes QStackedWidget * stack_; + + // timer to delay the search between options + QTimer * delay_search_; + }; } // namespace frontend
Index: src/frontends/qt4/PanelStack.cpp =================================================================== --- src/frontends/qt4/PanelStack.cpp (revisione 38474) +++ src/frontends/qt4/PanelStack.cpp (copia locale) @@ -15,12 +15,23 @@ #include "qt_helpers.h" #include "support/debug.h" +#include "support/foreach.h" +#include <QApplication> +#include <QAbstractButton> +#include <QColorGroup> +#include <QComboBox> #include <QFontMetrics> +#include <QGroupBox> #include <QHBoxLayout> #include <QHeaderView> +#include <QLabel> +#include <QLineEdit> +#include <QListWidget> +#include <QPalette> #include <QStackedWidget> #include <QTreeWidget> +#include <QVBoxLayout> #include "support/lassert.h" @@ -33,9 +44,16 @@ PanelStack::PanelStack(QWidget * parent) : QWidget(parent) { + delay_search_ = new QTimer(this); list_ = new QTreeWidget(this); stack_ = new QStackedWidget(this); + search_ = new QLineEdit(this); + // Configure the timer + delay_search_->setSingleShot(true); + connect(delay_search_, SIGNAL(timeout()), this, SLOT(searchTimeout())); + + // Configure tree list_->setRootIsDecorated(false); list_->setColumnCount(1); list_->header()->hide(); @@ -48,9 +66,22 @@ connect(list_, SIGNAL(itemClicked (QTreeWidgetItem*, int)), this, SLOT(itemSelected(QTreeWidgetItem *, int))); - QHBoxLayout * layout = new QHBoxLayout(this); - layout->addWidget(list_, 0); - layout->addWidget(stack_, 1); + // Configure the search box +#if QT_VERSION >= 0x040700 + search_->setPlaceholderText(qt_("Search")); +#endif + + connect(search_, SIGNAL(textChanged(QString)), this, SLOT(filterChanged(QString))); + + // Create the output layout, horizontal plus a VBox on the left with the search + // box and the tree + QVBoxLayout * left_layout = new QVBoxLayout(); + left_layout->addWidget(search_, 0); + left_layout->addWidget(list_, 1); + + QHBoxLayout * main_layout = new QHBoxLayout(this); + main_layout->addLayout(left_layout, 0); + main_layout->addWidget(stack_, 1); } @@ -132,22 +163,152 @@ QTreeWidgetItem * previous) { // do nothing when clicked on whitespace (item=NULL) - if( !item ) + if (!item) return; // if we have a category, expand the tree and go to the - // first item + // first enabled item if (item->childCount() > 0) { item->setExpanded(true); - if (previous && previous->parent() != item) - switchPanel( item->child(0), previous ); + if (previous && previous->parent() != item) { + // Looks for a child not disabled + for (int i = 0; i < item->childCount(); ++i) { + if (item->child(i)->flags() & Qt::ItemIsEnabled) { + switchPanel(item->child(i), previous); + break; + } + } + } } else if (QWidget * w = widget_map_.value(item, 0)) { stack_->setCurrentWidget(w); } } +bool matches(QString & input, QString const & search) +{ + // Check if the input contains the search string + return input.remove('&').contains(search, Qt::CaseInsensitive); +} +void setTreeItemStatus(QTreeWidgetItem * tree_item, bool enabled) +{ + // Enable/disable the item + tree_item->setDisabled(!enabled); + + // Change the color from black to gray or viceversa + QPalette::ColorGroup new_color = enabled ? QPalette::Active : QPalette::Disabled; + tree_item->setTextColor(0, QApplication::palette().color(new_color, QPalette::Text)); +} + +void PanelStack::hideEvent(QHideEvent * event) +{ + QWidget::hideEvent(event); + + // Programatically hidden (not simply minimized by the user) + if (!event->spontaneous()) + resetSearch(); +} + +void PanelStack::filterChanged(QString const & /*search*/) +{ + // The text in the search box is changed, reset the timer + // and then search in the widgets + delay_search_->start(300); +} + +void PanelStack::searchTimeout() +{ + search(); +} + +void PanelStack::search() +{ + QString search = search_->text(); + bool enable_all = search.isEmpty(); + + // If the search string is empty we enable all the items + // otherwise we disable everything and then selectively + // re-enable matching items + foreach (QTreeWidgetItem * tree_item, panel_map_) { + setTreeItemStatus(tree_item, enable_all); + } + + foreach (QTreeWidgetItem * tree_item, panel_map_) { + + // Current widget + QWidget * pane_widget = widget_map_[tree_item]; + + // First of all we look in the pane name + bool pane_matches = tree_item->text(0).contains(search, Qt::CaseInsensitive); + + // If the tree item has an associated pane + if (pane_widget) { + + // Loops on the list of children widgets (recursive) + QWidgetList children = pane_widget->findChildren<QWidget *>(); + foreach (QWidget * child_widget, children) { + bool widget_matches = false; + + // Try to cast to the most common widgets and looks in it's content + // It's bad OOP, it would be nice to have a QWidget::toString() overloaded by + // each widget, but this would require to change Qt or subclass each widget. + // Note that we have to ignore the amperstand symbol + if (QAbstractButton * button = qobject_cast<QAbstractButton *>(child_widget)) { + widget_matches = matches(button->text(), search); + + } else if (QGroupBox * group_box = qobject_cast<QGroupBox *>(child_widget)) { + widget_matches = matches(group_box->title(), search); + + } else if (QLabel * label = qobject_cast<QLabel *>(child_widget)) { + widget_matches = matches(label->text(), search); + + } else if (QLineEdit * line_edit = qobject_cast<QLineEdit *>(child_widget)) { + widget_matches = matches(line_edit->text(), search); + + } else if (QListWidget * list_widget = qobject_cast<QListWidget *>(child_widget)) { + widget_matches = (list_widget->findItems(search, Qt::MatchContains)).count() > 0; + + } else if (QTreeWidget * tree_view = qobject_cast<QTreeWidget *>(child_widget)) { + widget_matches = (tree_view->findItems(search, Qt::MatchContains)).count() > 0; + + } else if (QComboBox * combo_box = qobject_cast<QComboBox *>(child_widget)) { + widget_matches = (combo_box->findText(search, Qt::MatchContains)) != -1; + + } else { + continue; + } + + // If this widget meets the search criteria + if (widget_matches && !enable_all) { + // The pane too meets the search criteria + pane_matches = true; + + // Highlight the widget + QPalette widget_palette = child_widget->palette(); + widget_palette.setColor(child_widget->foregroundRole(), Qt::red); + child_widget->setPalette(widget_palette); + } else { + // Reset the color of the widget + child_widget->setPalette(QApplication::palette(child_widget)); + } + } + + // If the pane meets the search criteria + if (pane_matches && !enable_all) { + // Expand and enable the pane and his ancestors (typically just the parent) + QTreeWidgetItem * item = tree_item; + do { + item->setExpanded(true); + setTreeItemStatus(item, true); + item = item->parent(); + } while (item); + } + } + + } +} + void PanelStack::itemSelected(QTreeWidgetItem * item, int) { // de-select the category if a child is selected @@ -162,6 +323,11 @@ qMax(list_->height(), stack_->height())); } +void PanelStack::resetSearch() +{ + search_->setText(QString()); +} + } // namespace frontend } // namespace lyx Index: src/frontends/qt4/PanelStack.h =================================================================== --- src/frontends/qt4/PanelStack.h (revisione 38474) +++ src/frontends/qt4/PanelStack.h (copia locale) @@ -13,12 +13,16 @@ #ifndef PANELSTACK_H #define PANELSTACK_H +#include <QHash> +#include <QHideEvent> +#include <QTimer> #include <QWidget> -#include <QHash> +class QAbstractButton; +class QLineEdit; +class QStackedWidget; class QTreeWidget; class QTreeWidgetItem; -class QStackedWidget; namespace lyx { namespace frontend { @@ -44,13 +48,25 @@ bool isCurrentPanel(QString const & name) const; /// QSize sizeHint() const; + /// perform the search + void search(); + /// reset the search box + void resetSearch(); public Q_SLOTS: + /// the option filter changed + void filterChanged(QString const & search); + /// timeout for the search box + void searchTimeout(); /// set current panel from an item void switchPanel(QTreeWidgetItem * it, QTreeWidgetItem * previous = 0); /// click on the tree void itemSelected(QTreeWidgetItem *, int); +protected: + /// widget hidden + void hideEvent(QHideEvent * event); + private: /// typedef QHash<QString, QTreeWidgetItem *> PanelMap; @@ -61,11 +77,18 @@ WidgetMap widget_map_; + /// contains the search box + QLineEdit * search_; + /// contains the items QTreeWidget * list_; /// contains the panes QStackedWidget * stack_; + + // timer to delay the search between options + QTimer * delay_search_; + }; } // namespace frontend