Here's the patch. I hope works fine. It would be nice to put it in 2.0 but I think it's impossible as it's late and I've added a string ("Search", maybe we already have it). Moreover it's my first patch in C++ to LyX, so probably it will be full of inconsistencies.
It adds a search box to Tools -> Preferences and Document -> Settings. It looks for the searched text in all the widgets of the panes and highlights them if match. Panes without matching widgets are disabled and grayed. Some questions: - I'm using QLineEdit::setPlaceHolder(), which was introduced in Qt 4.7, it that OK? - To translate the string I used tr("Search"), is that correct? - Currently the widgets are highlighted in red, alternative ideas? I took insipiration from Qt Creator and Eclipse. Hope you like it. venom00 Index: src/frontends/qt4/PanelStack.cpp =================================================================== --- src/frontends/qt4/PanelStack.cpp (revisione 38358) +++ src/frontends/qt4/PanelStack.cpp (copia locale) @@ -18,9 +18,17 @@ #include <QFontMetrics> #include <QHBoxLayout> +#include <QVBoxLayout> #include <QHeaderView> #include <QStackedWidget> #include <QTreeWidget> +#include <QLineEdit> +#include <QGroupBox> +#include <QLabel> +#include <QAbstractButton> +#include <QComboBox> +#include <QApplication> +#include <QListWidget> #include "support/lassert.h" @@ -35,7 +43,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 +53,26 @@ 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 + search_->setPlaceholderText(tr("Search")); + + connect(search_, SIGNAL(textEdited(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 +158,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 +177,115 @@ } +void PanelStack::filterChanged(QString search) { + bool enable_all = search.length() == 0; + + // Iterator for the tree + QHashIterator<QString, QTreeWidgetItem *> panes_iterator(this->panel_map_); + + // If the search string is empty we have to re-enable everything + if (enable_all) { + while (panes_iterator.hasNext()) { + panes_iterator.next(); + panes_iterator.value()->setDisabled(false); + panes_iterator.value()->setTextColor(0, QApplication::palette().color(QPalette::Active, QPalette::Text)); + } + } else { + // Otherwise disable everything and then re-enable panes matching the + // search criteria + while (panes_iterator.hasNext()) { + panes_iterator.next(); + panes_iterator.value()->setDisabled(true); + panes_iterator.value()->setTextColor(0, QApplication::palette().color(QPalette::Disabled, QPalette::Text)); + } + } + + // Reset the iterator + panes_iterator.toFront(); + + while (panes_iterator.hasNext()) { + panes_iterator.next(); + + // Current widget + QWidget * pane_widget = this->widget_map_[panes_iterator.value()]; + + // First of all we look in the pane name + bool pane_matches = panes_iterator.value()->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 = (new QString(button->text()))->replace('&', "") + .contains(search, Qt::CaseInsensitive); + + } else if (QGroupBox * group_box = qobject_cast<QGroupBox *>(children[child_index])) { + widget_matches = (new QString(group_box->title()))->replace('&', "") + .contains(search, Qt::CaseInsensitive); + + } else if (QLabel * label = qobject_cast<QLabel *>(children[child_index])) { + widget_matches = (new QString(label->text()))->replace('&', "") + .contains(search, Qt::CaseInsensitive); + + } else if (QLineEdit * line_edit = qobject_cast<QLineEdit *>(children[child_index])) { + widget_matches = (new QString(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 = panes_iterator.value(); + 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 Index: src/frontends/qt4/PanelStack.h =================================================================== --- src/frontends/qt4/PanelStack.h (revisione 38358) +++ src/frontends/qt4/PanelStack.h (copia locale) @@ -19,6 +19,7 @@ class QTreeWidget; class QTreeWidgetItem; class QStackedWidget; +class QLineEdit; namespace lyx { namespace frontend { @@ -46,6 +47,8 @@ QSize sizeHint() const; public Q_SLOTS: + /// the option filter changed + void filterChanged(QString search); /// set current panel from an item void switchPanel(QTreeWidgetItem * it, QTreeWidgetItem * previous = 0); /// click on the tree @@ -61,6 +64,9 @@ WidgetMap widget_map_; + /// contains the search box + QLineEdit * search_; + /// contains the items QTreeWidget * list_;
Index: src/frontends/qt4/PanelStack.cpp =================================================================== --- src/frontends/qt4/PanelStack.cpp (revisione 38358) +++ src/frontends/qt4/PanelStack.cpp (copia locale) @@ -18,9 +18,17 @@ #include <QFontMetrics> #include <QHBoxLayout> +#include <QVBoxLayout> #include <QHeaderView> #include <QStackedWidget> #include <QTreeWidget> +#include <QLineEdit> +#include <QGroupBox> +#include <QLabel> +#include <QAbstractButton> +#include <QComboBox> +#include <QApplication> +#include <QListWidget> #include "support/lassert.h" @@ -35,7 +43,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 +53,26 @@ 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 + search_->setPlaceholderText(tr("Search")); + + connect(search_, SIGNAL(textEdited(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 +158,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 +177,115 @@ } +void PanelStack::filterChanged(QString search) { + bool enable_all = search.length() == 0; + + // Iterator for the tree + QHashIterator<QString, QTreeWidgetItem *> panes_iterator(this->panel_map_); + + // If the search string is empty we have to re-enable everything + if (enable_all) { + while (panes_iterator.hasNext()) { + panes_iterator.next(); + panes_iterator.value()->setDisabled(false); + panes_iterator.value()->setTextColor(0, QApplication::palette().color(QPalette::Active, QPalette::Text)); + } + } else { + // Otherwise disable everything and then re-enable panes matching the + // search criteria + while (panes_iterator.hasNext()) { + panes_iterator.next(); + panes_iterator.value()->setDisabled(true); + panes_iterator.value()->setTextColor(0, QApplication::palette().color(QPalette::Disabled, QPalette::Text)); + } + } + + // Reset the iterator + panes_iterator.toFront(); + + while (panes_iterator.hasNext()) { + panes_iterator.next(); + + // Current widget + QWidget * pane_widget = this->widget_map_[panes_iterator.value()]; + + // First of all we look in the pane name + bool pane_matches = panes_iterator.value()->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 = (new QString(button->text()))->replace('&', "") + .contains(search, Qt::CaseInsensitive); + + } else if (QGroupBox * group_box = qobject_cast<QGroupBox *>(children[child_index])) { + widget_matches = (new QString(group_box->title()))->replace('&', "") + .contains(search, Qt::CaseInsensitive); + + } else if (QLabel * label = qobject_cast<QLabel *>(children[child_index])) { + widget_matches = (new QString(label->text()))->replace('&', "") + .contains(search, Qt::CaseInsensitive); + + } else if (QLineEdit * line_edit = qobject_cast<QLineEdit *>(children[child_index])) { + widget_matches = (new QString(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 = panes_iterator.value(); + 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 Index: src/frontends/qt4/PanelStack.h =================================================================== --- src/frontends/qt4/PanelStack.h (revisione 38358) +++ src/frontends/qt4/PanelStack.h (copia locale) @@ -19,6 +19,7 @@ class QTreeWidget; class QTreeWidgetItem; class QStackedWidget; +class QLineEdit; namespace lyx { namespace frontend { @@ -46,6 +47,8 @@ QSize sizeHint() const; public Q_SLOTS: + /// the option filter changed + void filterChanged(QString search); /// set current panel from an item void switchPanel(QTreeWidgetItem * it, QTreeWidgetItem * previous = 0); /// click on the tree @@ -61,6 +64,9 @@ WidgetMap widget_map_; + /// contains the search box + QLineEdit * search_; + /// contains the items QTreeWidget * list_;