OK, I've fixed everything you suggested me. Here's the new version. I've tried to limit the search to QAbstractButton, QGroupBox and Qlabel but no big improvements. Maybe we should simply add a delay, so the user can type the whole search query and then start the search, 200-400 ms. Other ideas?
Now when the dialog is hidden the search box is reset. The "rubber" button will be in the next patch, maybe. :P Thanks for your comments! venom00 Index: src/frontends/qt4/GuiDocument.h =================================================================== --- src/frontends/qt4/GuiDocument.h (revisione 38474) +++ src/frontends/qt4/GuiDocument.h (copia locale) @@ -77,6 +77,7 @@ void updateDefaultFormat(); void updatePagestyle(std::string const &, std::string const &); bool isChildIncluded(std::string const &); + void hideView(); /// BufferParams const & params() const { return bp_; } Index: src/frontends/qt4/GuiDocument.cpp =================================================================== --- src/frontends/qt4/GuiDocument.cpp (revisione 38474) +++ src/frontends/qt4/GuiDocument.cpp (copia locale) @@ -2139,6 +2139,11 @@ includeonlys_.end(), child) != includeonlys_.end()); } +void GuiDocument::hideView() { + Dialog::hideView(); + // Reset the search box + this->docPS->resetSearch(); +} void GuiDocument::applyView() { Index: src/frontends/qt4/GuiPrefs.cpp =================================================================== --- src/frontends/qt4/GuiPrefs.cpp (revisione 38474) +++ src/frontends/qt4/GuiPrefs.cpp (copia locale) @@ -3201,6 +3201,11 @@ modules_[i]->update(rc); } +void GuiPreferences::hideView() { + Dialog::hideView(); + // Reset the search box + this->prefsPS->resetSearch(); +} void GuiPreferences::applyView() { Index: src/frontends/qt4/PanelStack.cpp =================================================================== --- src/frontends/qt4/PanelStack.cpp (revisione 38474) +++ src/frontends/qt4/PanelStack.cpp (copia locale) @@ -15,12 +15,21 @@ #include "qt_helpers.h" #include "support/debug.h" +#include "support/foreach.h" +#include <QApplication> +#include <QAbstractButton> +#include <QComboBox> #include <QFontMetrics> +#include <QGroupBox> #include <QHBoxLayout> #include <QHeaderView> +#include <QLabel> +#include <QLineEdit> +#include <QListWidget> #include <QStackedWidget> #include <QTreeWidget> +#include <QVBoxLayout> #include "support/lassert.h" @@ -35,7 +44,9 @@ { list_ = new QTreeWidget(this); stack_ = new QStackedWidget(this); + search_ = new QLineEdit(this); + // Configure tree list_->setRootIsDecorated(false); list_->setColumnCount(1); list_->header()->hide(); @@ -43,14 +54,28 @@ list_->header()->setStretchLastSection(false); list_->setMinimumSize(list_->viewport()->size()); + connect(list_, SIGNAL(currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)), this, SLOT(switchPanel(QTreeWidgetItem *, QTreeWidgetItem*))); 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); } @@ -136,11 +161,18 @@ 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); @@ -148,6 +180,106 @@ } +void PanelStack::filterChanged(QString const & search) { + bool enable_all = search.length() == 0; + + // If the search string is empty we have to re-enable everything + if (enable_all) { + foreach (QTreeWidgetItem * tree_item, panel_map_) { + tree_item->setDisabled(false); + tree_item->setTextColor(0, QApplication::palette().color(QPalette::Active, QPalette::Text)); + } + } else { + // Otherwise disable everything and then re-enable panes matching the + // search criteria + foreach (QTreeWidgetItem * tree_item, panel_map_) { + tree_item->setDisabled(true); + tree_item->setTextColor(0, QApplication::palette().color(QPalette::Disabled, QPalette::Text)); + } + } + + foreach (QTreeWidgetItem * tree_item, panel_map_) { + + // Current widget + QWidget * pane_widget = this->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) { + + // Collect all the children widgets (recursive) + QList<QWidget *> children = pane_widget->findChildren<QWidget *>(); + + // Loops on the list of children widgets + size_t last_child = children.size(); + for (size_t child_index = 0; child_index != last_child; ++child_index) { + bool widget_matches = false; + + // Try to cast to the most common widgets and looks in it's content by each + // 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 *>(children[child_index])) { + widget_matches = button->text().replace('&', "") + .contains(search, Qt::CaseInsensitive); + + } else if (QGroupBox * group_box = qobject_cast<QGroupBox *>(children[child_index])) { + widget_matches = group_box->title().replace('&', "") + .contains(search, Qt::CaseInsensitive); + + } else if (QLabel * label = qobject_cast<QLabel *>(children[child_index])) { + widget_matches = label->text().replace('&', "") + .contains(search, Qt::CaseInsensitive); + + } else if (QLineEdit * line_edit = qobject_cast<QLineEdit *>(children[child_index])) { + widget_matches = line_edit->text().replace('&', "") + .contains(search, Qt::CaseInsensitive); + + } else if (QListWidget * list_widget = qobject_cast<QListWidget *>(children[child_index])) { + widget_matches = (list_widget->findItems(search, Qt::MatchContains)).count() != 0; + + } else if (QTreeWidget * tree_view = qobject_cast<QTreeWidget *>(children[child_index])) { + widget_matches = (tree_view->findItems(search, Qt::MatchContains)).count() != 0; + + } else if (QComboBox * combo_box = qobject_cast<QComboBox *>(children[child_index])) { + 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 = children[child_index]->palette(); + widget_palette.setColor(children[child_index]->foregroundRole(), Qt::red); + children[child_index]->setPalette(widget_palette); + } else { + // Reset the color of the widget + children[child_index]->setPalette( QApplication::palette( children[child_index] ) ); + } + } + + // 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); + item->setDisabled(false); + item->setTextColor(0, QApplication::palette().color(QPalette::Active, QPalette::Text)); + item = item->parent(); + } while (item); + } + } + + } +} + void PanelStack::itemSelected(QTreeWidgetItem * item, int) { // de-select the category if a child is selected @@ -162,6 +294,10 @@ qMax(list_->height(), stack_->height())); } +void PanelStack::resetSearch() { + this->search_->setText(""); +} + } // namespace frontend } // namespace lyx Index: src/frontends/qt4/PanelStack.h =================================================================== --- src/frontends/qt4/PanelStack.h (revisione 38474) +++ src/frontends/qt4/PanelStack.h (copia locale) @@ -16,6 +16,7 @@ #include <QWidget> #include <QHash> +class QLineEdit; class QTreeWidget; class QTreeWidgetItem; class QStackedWidget; @@ -44,8 +45,12 @@ bool isCurrentPanel(QString const & name) const; /// QSize sizeHint() const; + /// resets the search box + void resetSearch(); public Q_SLOTS: + /// the option filter changed + void filterChanged(QString const & search); /// set current panel from an item void switchPanel(QTreeWidgetItem * it, QTreeWidgetItem * previous = 0); /// click on the tree @@ -61,6 +66,9 @@ WidgetMap widget_map_; + /// contains the search box + QLineEdit * search_; + /// contains the items QTreeWidget * list_; Index: src/frontends/qt4/Dialog.h =================================================================== --- src/frontends/qt4/Dialog.h (revisione 38474) +++ src/frontends/qt4/Dialog.h (copia locale) @@ -119,7 +119,7 @@ virtual void applyView() = 0; /// Hide the dialog from sight - void hideView(); + virtual void hideView(); /// Prepare dialog and display it. void showView(); Index: src/frontends/qt4/GuiPrefs.h =================================================================== --- src/frontends/qt4/GuiPrefs.h (revisione 38474) +++ src/frontends/qt4/GuiPrefs.h (copia locale) @@ -67,6 +67,7 @@ void apply(LyXRC & rc) const; void updateRc(LyXRC const & rc); + void hideView(); public Q_SLOTS: void change_adaptor();
Index: src/frontends/qt4/GuiDocument.h =================================================================== --- src/frontends/qt4/GuiDocument.h (revisione 38474) +++ src/frontends/qt4/GuiDocument.h (copia locale) @@ -77,6 +77,7 @@ void updateDefaultFormat(); void updatePagestyle(std::string const &, std::string const &); bool isChildIncluded(std::string const &); + void hideView(); /// BufferParams const & params() const { return bp_; } Index: src/frontends/qt4/GuiDocument.cpp =================================================================== --- src/frontends/qt4/GuiDocument.cpp (revisione 38474) +++ src/frontends/qt4/GuiDocument.cpp (copia locale) @@ -2139,6 +2139,11 @@ includeonlys_.end(), child) != includeonlys_.end()); } +void GuiDocument::hideView() { + Dialog::hideView(); + // Reset the search box + this->docPS->resetSearch(); +} void GuiDocument::applyView() { Index: src/frontends/qt4/GuiPrefs.cpp =================================================================== --- src/frontends/qt4/GuiPrefs.cpp (revisione 38474) +++ src/frontends/qt4/GuiPrefs.cpp (copia locale) @@ -3201,6 +3201,11 @@ modules_[i]->update(rc); } +void GuiPreferences::hideView() { + Dialog::hideView(); + // Reset the search box + this->prefsPS->resetSearch(); +} void GuiPreferences::applyView() { Index: src/frontends/qt4/PanelStack.cpp =================================================================== --- src/frontends/qt4/PanelStack.cpp (revisione 38474) +++ src/frontends/qt4/PanelStack.cpp (copia locale) @@ -15,12 +15,21 @@ #include "qt_helpers.h" #include "support/debug.h" +#include "support/foreach.h" +#include <QApplication> +#include <QAbstractButton> +#include <QComboBox> #include <QFontMetrics> +#include <QGroupBox> #include <QHBoxLayout> #include <QHeaderView> +#include <QLabel> +#include <QLineEdit> +#include <QListWidget> #include <QStackedWidget> #include <QTreeWidget> +#include <QVBoxLayout> #include "support/lassert.h" @@ -35,7 +44,9 @@ { list_ = new QTreeWidget(this); stack_ = new QStackedWidget(this); + search_ = new QLineEdit(this); + // Configure tree list_->setRootIsDecorated(false); list_->setColumnCount(1); list_->header()->hide(); @@ -43,14 +54,28 @@ list_->header()->setStretchLastSection(false); list_->setMinimumSize(list_->viewport()->size()); + connect(list_, SIGNAL(currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)), this, SLOT(switchPanel(QTreeWidgetItem *, QTreeWidgetItem*))); 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); } @@ -136,11 +161,18 @@ 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); @@ -148,6 +180,106 @@ } +void PanelStack::filterChanged(QString const & search) { + bool enable_all = search.length() == 0; + + // If the search string is empty we have to re-enable everything + if (enable_all) { + foreach (QTreeWidgetItem * tree_item, panel_map_) { + tree_item->setDisabled(false); + tree_item->setTextColor(0, QApplication::palette().color(QPalette::Active, QPalette::Text)); + } + } else { + // Otherwise disable everything and then re-enable panes matching the + // search criteria + foreach (QTreeWidgetItem * tree_item, panel_map_) { + tree_item->setDisabled(true); + tree_item->setTextColor(0, QApplication::palette().color(QPalette::Disabled, QPalette::Text)); + } + } + + foreach (QTreeWidgetItem * tree_item, panel_map_) { + + // Current widget + QWidget * pane_widget = this->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) { + + // Collect all the children widgets (recursive) + QList<QWidget *> children = pane_widget->findChildren<QWidget *>(); + + // Loops on the list of children widgets + size_t last_child = children.size(); + for (size_t child_index = 0; child_index != last_child; ++child_index) { + bool widget_matches = false; + + // Try to cast to the most common widgets and looks in it's content by each + // 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 *>(children[child_index])) { + widget_matches = button->text().replace('&', "") + .contains(search, Qt::CaseInsensitive); + + } else if (QGroupBox * group_box = qobject_cast<QGroupBox *>(children[child_index])) { + widget_matches = group_box->title().replace('&', "") + .contains(search, Qt::CaseInsensitive); + + } else if (QLabel * label = qobject_cast<QLabel *>(children[child_index])) { + widget_matches = label->text().replace('&', "") + .contains(search, Qt::CaseInsensitive); + + } else if (QLineEdit * line_edit = qobject_cast<QLineEdit *>(children[child_index])) { + widget_matches = line_edit->text().replace('&', "") + .contains(search, Qt::CaseInsensitive); + + } else if (QListWidget * list_widget = qobject_cast<QListWidget *>(children[child_index])) { + widget_matches = (list_widget->findItems(search, Qt::MatchContains)).count() != 0; + + } else if (QTreeWidget * tree_view = qobject_cast<QTreeWidget *>(children[child_index])) { + widget_matches = (tree_view->findItems(search, Qt::MatchContains)).count() != 0; + + } else if (QComboBox * combo_box = qobject_cast<QComboBox *>(children[child_index])) { + 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 = children[child_index]->palette(); + widget_palette.setColor(children[child_index]->foregroundRole(), Qt::red); + children[child_index]->setPalette(widget_palette); + } else { + // Reset the color of the widget + children[child_index]->setPalette( QApplication::palette( children[child_index] ) ); + } + } + + // 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); + item->setDisabled(false); + item->setTextColor(0, QApplication::palette().color(QPalette::Active, QPalette::Text)); + item = item->parent(); + } while (item); + } + } + + } +} + void PanelStack::itemSelected(QTreeWidgetItem * item, int) { // de-select the category if a child is selected @@ -162,6 +294,10 @@ qMax(list_->height(), stack_->height())); } +void PanelStack::resetSearch() { + this->search_->setText(""); +} + } // namespace frontend } // namespace lyx Index: src/frontends/qt4/PanelStack.h =================================================================== --- src/frontends/qt4/PanelStack.h (revisione 38474) +++ src/frontends/qt4/PanelStack.h (copia locale) @@ -16,6 +16,7 @@ #include <QWidget> #include <QHash> +class QLineEdit; class QTreeWidget; class QTreeWidgetItem; class QStackedWidget; @@ -44,8 +45,12 @@ bool isCurrentPanel(QString const & name) const; /// QSize sizeHint() const; + /// resets the search box + void resetSearch(); public Q_SLOTS: + /// the option filter changed + void filterChanged(QString const & search); /// set current panel from an item void switchPanel(QTreeWidgetItem * it, QTreeWidgetItem * previous = 0); /// click on the tree @@ -61,6 +66,9 @@ WidgetMap widget_map_; + /// contains the search box + QLineEdit * search_; + /// contains the items QTreeWidget * list_; Index: src/frontends/qt4/Dialog.h =================================================================== --- src/frontends/qt4/Dialog.h (revisione 38474) +++ src/frontends/qt4/Dialog.h (copia locale) @@ -119,7 +119,7 @@ virtual void applyView() = 0; /// Hide the dialog from sight - void hideView(); + virtual void hideView(); /// Prepare dialog and display it. void showView(); Index: src/frontends/qt4/GuiPrefs.h =================================================================== --- src/frontends/qt4/GuiPrefs.h (revisione 38474) +++ src/frontends/qt4/GuiPrefs.h (copia locale) @@ -67,6 +67,7 @@ void apply(LyXRC & rc) const; void updateRc(LyXRC const & rc); + void hideView(); public Q_SLOTS: void change_adaptor();