From c21e0917a5a1650f0350faf88d8d1800beee6e50 Mon Sep 17 00:00:00 2001
From: Nicolas Pope <nicolas.pope@utu.fi>
Date: Thu, 9 Jan 2020 10:50:25 +0200
Subject: [PATCH] Add a search bar

The configuration window now has a search bar which reacts to user
input on the fly.
---
 applications/gui/src/config_window.cpp        | 106 +++++++++++++++++-
 applications/gui/src/config_window.hpp        |   5 +-
 applications/reconstruct/src/main.cpp         |   7 ++
 .../common/cpp/include/ftl/configuration.hpp  |   7 ++
 components/common/cpp/src/configuration.cpp   |  11 ++
 5 files changed, 129 insertions(+), 7 deletions(-)

diff --git a/applications/gui/src/config_window.cpp b/applications/gui/src/config_window.cpp
index 322f817ae..b3c5ef874 100644
--- a/applications/gui/src/config_window.cpp
+++ b/applications/gui/src/config_window.cpp
@@ -6,6 +6,7 @@
 #include <nanogui/entypo.h>
 #include <nanogui/formhelper.h>
 #include <nanogui/vscrollpanel.h>
+#include <nanogui/opengl.h>
 
 #include <vector>
 #include <string>
@@ -15,6 +16,67 @@ using std::string;
 using std::vector;
 using ftl::config::json_t;
 
+class SearchBox : public nanogui::TextBox {
+private:
+	std::vector<std::string> configurables_;
+	Widget *buttons_;
+	std::string previous;
+
+	void _setVisible(const std::string &str) {
+		// Check whether the search string has changed to prevent
+		// unnecessary searching.
+		if (str != previous) {
+			for (int i = configurables_.size()-1; i >= 0; --i) {
+				if (configurables_[i].find(mValueTemp) != std::string::npos) {
+					buttons_->childAt(i)->setVisible(true);
+				} else {
+					buttons_->childAt(i)->setVisible(false);
+				}
+			}
+			previous = str;
+		}
+	}
+
+public:
+	SearchBox(Widget *parent, std::vector<std::string> &configurables) : nanogui::TextBox(parent, ""), configurables_(configurables) {
+		setAlignment(TextBox::Alignment::Left);
+		setEditable(true);
+		setPlaceholder("Search");
+	}
+
+	~SearchBox() {
+	}
+
+	bool keyboardEvent(int key, int scancode, int action, int modifier) {
+		TextBox::keyboardEvent(key, scancode, action, modifier);
+		_setVisible(mValueTemp);
+		return true;
+	}
+
+	void setButtons(Widget *buttons) {
+		buttons_ = buttons;
+	}
+};
+
+static std::string titleForURI(const ftl::URI &uri) {
+	auto *cfg = ftl::config::find(uri.getBaseURI());
+	if (cfg && cfg->get<std::string>("title")) {
+		return *cfg->get<std::string>("title");
+	} else if (uri.getPath().size() > 0) {
+		return uri.getPathSegment(-1);
+	} else {
+		return uri.getHost();
+	}
+}
+
+static std::string genPadding(const std::string &str, size_t count) {
+	std::string res = "";
+	for (size_t i=0; i<count; ++i) {
+		res += str;
+	}
+	return res;
+}
+
 ConfigWindow::ConfigWindow(nanogui::Widget *parent, ftl::ctrl::Master *ctrl)
 		: nanogui::Window(parent, "Settings"), ctrl_(ctrl) {
 	using namespace nanogui;
@@ -23,17 +85,52 @@ ConfigWindow::ConfigWindow(nanogui::Widget *parent, ftl::ctrl::Master *ctrl)
 	setPosition(Vector2i(parent->width()/2.0f - 100.0f, parent->height()/2.0f - 100.0f));
 	//setModal(true);
 
-	configurables_ = ftl::config::list();
+	auto configurables = ftl::config::list();
+	const auto size = configurables.size();
 
 	new Label(this, "Select Configurable","sans-bold");
 
+	auto searchBox = new SearchBox(this, configurables);
+
 	auto vscroll = new VScrollPanel(this);
 	vscroll->setFixedHeight(300);
-	Widget *buttons = new Widget(vscroll);
+	auto buttons = new Widget(vscroll);
 	buttons->setLayout(new BoxLayout(Orientation::Vertical, Alignment::Fill));
 
-	for (auto c : configurables_) {
-		auto itembutton = new Button(buttons, c);
+	searchBox->setButtons(buttons);
+
+	std::vector<std::string> configurable_titles(size);
+	for (int i = 0; i < size; ++i) {
+		ftl::URI uri(configurables[i]);
+		std::string label = uri.getFragment();
+
+		size_t pos = label.find_last_of("/");
+		if (pos != std::string::npos) label = label.substr(pos+1);
+
+		std::string parentName = configurables[i];
+		size_t pos2 = parentName.find_last_of("/");
+		if (pos2 != std::string::npos) parentName = parentName.substr(0,pos2);
+
+		// FIXME: Does not indicated parent indentation ... needs sorting?
+
+		if (i > 0 && parentName == configurables[i-1]) {
+			ftl::URI uri(configurables[i-1]);
+			configurable_titles[i-1] = std::string("[") + titleForURI(uri) + std::string("] ") + uri.getFragment();
+
+			auto *prev = dynamic_cast<Button*>(buttons->childAt(buttons->childCount()-1));
+			prev->setCaption(configurable_titles[i-1]);
+			prev->setBackgroundColor(nanogui::Color(0.3f,0.3f,0.3f,1.0f));
+			prev->setTextColor(nanogui::Color(1.0f,1.0f,1.0f,1.0f));
+			prev->setIconPosition(Button::IconPosition::Left);
+			prev->setIcon(ENTYPO_ICON_FOLDER);
+		}
+
+		configurable_titles[i] = label;
+
+		auto itembutton = new nanogui::Button(buttons, configurable_titles[i]);
+		std::string c = configurables[i];
+		itembutton->setTooltip(c);
+		itembutton->setBackgroundColor(nanogui::Color(0.9f,0.9f,0.9f,0.9f));
 		itembutton->setCallback([this,c]() {
 			LOG(INFO) << "Change configurable: " << c;
 			_buildForm(c);
@@ -136,3 +233,4 @@ void ConfigWindow::_buildForm(const std::string &suri) {
 bool ConfigWindow::exists(const std::string &uri) {
 	return ftl::config::find(uri) != nullptr;
 }
+
diff --git a/applications/gui/src/config_window.hpp b/applications/gui/src/config_window.hpp
index a0fe74155..a7acd1171 100644
--- a/applications/gui/src/config_window.hpp
+++ b/applications/gui/src/config_window.hpp
@@ -2,10 +2,10 @@
 #define _FTL_GUI_CFGWINDOW_HPP_
 
 #include <nanogui/window.h>
+#include <nanogui/formhelper.h>
+
 #include <ftl/master.hpp>
 #include <ftl/uuid.hpp>
-
-#include <nanogui/formhelper.h>
 #include <ftl/net_configurable.hpp>
 
 namespace ftl {
@@ -21,7 +21,6 @@ class ConfigWindow : public nanogui::Window {
 
 	private:
 	ftl::ctrl::Master *ctrl_;
-	std::vector<std::string> configurables_;
 	
 	void _buildForm(const std::string &uri);
 	void _addElements(nanogui::FormHelper *form, const std::string &suri);
diff --git a/applications/reconstruct/src/main.cpp b/applications/reconstruct/src/main.cpp
index c3dc37e32..5cce7a5a2 100644
--- a/applications/reconstruct/src/main.cpp
+++ b/applications/reconstruct/src/main.cpp
@@ -227,6 +227,13 @@ static void run(ftl::Configurable *root) {
 	});
 	stream->add(vs);
 
+	for (auto c : ftl::config::getChildren(root->getID())) {
+		LOG(INFO) << "Tagging configurable: " << c->getID();
+		auto tags = c->value<std::vector<std::string>>("tags", nlohmann::json::array({}));
+		tags.push_back("reconstruction");
+		c->set("tags", tags);
+	}
+
 	// ---- Recording code -----------------------------------------------------
 	std::ofstream fileout;
 	ftl::codecs::Writer writer(fileout);
diff --git a/components/common/cpp/include/ftl/configuration.hpp b/components/common/cpp/include/ftl/configuration.hpp
index 18aaf89f9..ed7eee95d 100644
--- a/components/common/cpp/include/ftl/configuration.hpp
+++ b/components/common/cpp/include/ftl/configuration.hpp
@@ -83,6 +83,13 @@ const std::vector<Configurable *> &findByTag(const std::string &tag);
 
 std::vector<std::string> list();
 
+/**
+ * Recursively get all children of a configurable. The given configurable is
+ * also included in the vector, unless it is null,
+ * in which case an empty vector is returned.
+ */
+const std::vector<Configurable *> getChildren(const std::string &uri);
+
 /**
  * Adds a Configurable instance to the database of instances so that it can
  * then be resolved using find().
diff --git a/components/common/cpp/src/configuration.cpp b/components/common/cpp/src/configuration.cpp
index 9fd9d3081..c53f50c78 100644
--- a/components/common/cpp/src/configuration.cpp
+++ b/components/common/cpp/src/configuration.cpp
@@ -212,6 +212,17 @@ std::vector<std::string> ftl::config::list() {
 	return r;
 }
 
+const std::vector<Configurable *> ftl::config::getChildren(const string &uri) {
+	std::vector<Configurable *> children;
+	for (const auto &[curi, c] : config_instance) {
+		auto mismatch = std::mismatch(uri.begin(), uri.end(), curi.begin());
+		if (mismatch.first == uri.end()) {
+			children.push_back(c);
+		}
+	}
+	return children;
+}
+
 void ftl::config::registerConfigurable(ftl::Configurable *cfg) {
 	auto uri = cfg->get<string>("$id");
 	if (!uri) {
-- 
GitLab