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_;
 

Reply via email to