mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 08:48:11 +00:00
Help: Add a search mode
This closes #2685, and brings some much needed nostalgia :^)
This commit is contained in:
parent
6131417904
commit
21094b4725
3 changed files with 104 additions and 16 deletions
|
@ -28,6 +28,9 @@
|
||||||
#include "ManualNode.h"
|
#include "ManualNode.h"
|
||||||
#include "ManualPageNode.h"
|
#include "ManualPageNode.h"
|
||||||
#include "ManualSectionNode.h"
|
#include "ManualSectionNode.h"
|
||||||
|
#include <AK/ByteBuffer.h>
|
||||||
|
#include <LibCore/File.h>
|
||||||
|
#include <LibGUI/FilteringProxyModel.h>
|
||||||
|
|
||||||
static ManualSectionNode s_sections[] = {
|
static ManualSectionNode s_sections[] = {
|
||||||
{ "1", "Command-line programs" },
|
{ "1", "Command-line programs" },
|
||||||
|
@ -76,6 +79,25 @@ String ManualModel::page_path(const GUI::ModelIndex& index) const
|
||||||
return page->path();
|
return page->path();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result<StringView, int> ManualModel::page_view(const String& path) const
|
||||||
|
{
|
||||||
|
if (path.is_empty())
|
||||||
|
return StringView {};
|
||||||
|
|
||||||
|
auto mapped_file = m_mapped_files.get(path);
|
||||||
|
if (mapped_file.has_value())
|
||||||
|
return StringView { (const char*)mapped_file.value()->data(), mapped_file.value()->size() };
|
||||||
|
|
||||||
|
auto map = make<MappedFile>(path);
|
||||||
|
if (!map->is_valid())
|
||||||
|
return map->errno_if_invalid();
|
||||||
|
|
||||||
|
StringView view { (const char*)map->data(), map->size() };
|
||||||
|
m_mapped_files.set(path, move(map));
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
String ManualModel::page_and_section(const GUI::ModelIndex& index) const
|
String ManualModel::page_and_section(const GUI::ModelIndex& index) const
|
||||||
{
|
{
|
||||||
if (!index.is_valid())
|
if (!index.is_valid())
|
||||||
|
@ -137,6 +159,10 @@ GUI::Variant ManualModel::data(const GUI::ModelIndex& index, Role role) const
|
||||||
{
|
{
|
||||||
auto* node = static_cast<const ManualNode*>(index.internal_data());
|
auto* node = static_cast<const ManualNode*>(index.internal_data());
|
||||||
switch (role) {
|
switch (role) {
|
||||||
|
case Role::Search:
|
||||||
|
if (!node->is_page())
|
||||||
|
return {};
|
||||||
|
return String(page_view(page_path(index)).value());
|
||||||
case Role::Display:
|
case Role::Display:
|
||||||
return node->name();
|
return node->name();
|
||||||
case Role::Icon:
|
case Role::Icon:
|
||||||
|
@ -156,6 +182,15 @@ void ManualModel::update_section_node_on_toggle(const GUI::ModelIndex& index, co
|
||||||
node->set_open(open);
|
node->set_open(open);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TriState ManualModel::data_matches(const GUI::ModelIndex& index, GUI::Variant term) const
|
||||||
|
{
|
||||||
|
auto view_result = page_view(page_path(index));
|
||||||
|
if (view_result.is_error() || view_result.value().is_empty())
|
||||||
|
return TriState::False;
|
||||||
|
|
||||||
|
return view_result.value().contains(term.as_string()) ? TriState::True : TriState::False;
|
||||||
|
}
|
||||||
|
|
||||||
void ManualModel::update()
|
void ManualModel::update()
|
||||||
{
|
{
|
||||||
did_update();
|
did_update();
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
|
|
||||||
#include <AK/NonnullRefPtr.h>
|
#include <AK/NonnullRefPtr.h>
|
||||||
#include <AK/Optional.h>
|
#include <AK/Optional.h>
|
||||||
|
#include <AK/Result.h>
|
||||||
#include <AK/String.h>
|
#include <AK/String.h>
|
||||||
#include <LibGUI/Model.h>
|
#include <LibGUI/Model.h>
|
||||||
|
|
||||||
|
@ -44,11 +45,13 @@ public:
|
||||||
|
|
||||||
String page_path(const GUI::ModelIndex&) const;
|
String page_path(const GUI::ModelIndex&) const;
|
||||||
String page_and_section(const GUI::ModelIndex&) const;
|
String page_and_section(const GUI::ModelIndex&) const;
|
||||||
|
Result<StringView, int> page_view(const String& path) const;
|
||||||
|
|
||||||
void update_section_node_on_toggle(const GUI::ModelIndex&, const bool);
|
void update_section_node_on_toggle(const GUI::ModelIndex&, const bool);
|
||||||
virtual int row_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override;
|
virtual int row_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override;
|
||||||
virtual int column_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override;
|
virtual int column_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override;
|
||||||
virtual GUI::Variant data(const GUI::ModelIndex&, Role = Role::Display) const override;
|
virtual GUI::Variant data(const GUI::ModelIndex&, Role = Role::Display) const override;
|
||||||
|
virtual TriState data_matches(const GUI::ModelIndex&, GUI::Variant) const override;
|
||||||
virtual void update() override;
|
virtual void update() override;
|
||||||
virtual GUI::ModelIndex parent_index(const GUI::ModelIndex&) const override;
|
virtual GUI::ModelIndex parent_index(const GUI::ModelIndex&) const override;
|
||||||
virtual GUI::ModelIndex index(int row, int column = 0, const GUI::ModelIndex& parent = GUI::ModelIndex()) const override;
|
virtual GUI::ModelIndex index(int row, int column = 0, const GUI::ModelIndex& parent = GUI::ModelIndex()) const override;
|
||||||
|
@ -59,4 +62,5 @@ private:
|
||||||
GUI::Icon m_section_open_icon;
|
GUI::Icon m_section_open_icon;
|
||||||
GUI::Icon m_section_icon;
|
GUI::Icon m_section_icon;
|
||||||
GUI::Icon m_page_icon;
|
GUI::Icon m_page_icon;
|
||||||
|
mutable HashMap<String, OwnPtr<MappedFile>> m_mapped_files;
|
||||||
};
|
};
|
||||||
|
|
|
@ -26,7 +26,6 @@
|
||||||
|
|
||||||
#include "History.h"
|
#include "History.h"
|
||||||
#include "ManualModel.h"
|
#include "ManualModel.h"
|
||||||
#include <AK/ByteBuffer.h>
|
|
||||||
#include <AK/URL.h>
|
#include <AK/URL.h>
|
||||||
#include <LibCore/File.h>
|
#include <LibCore/File.h>
|
||||||
#include <LibDesktop/Launcher.h>
|
#include <LibDesktop/Launcher.h>
|
||||||
|
@ -34,11 +33,14 @@
|
||||||
#include <LibGUI/Action.h>
|
#include <LibGUI/Action.h>
|
||||||
#include <LibGUI/Application.h>
|
#include <LibGUI/Application.h>
|
||||||
#include <LibGUI/BoxLayout.h>
|
#include <LibGUI/BoxLayout.h>
|
||||||
|
#include <LibGUI/FilteringProxyModel.h>
|
||||||
|
#include <LibGUI/ListView.h>
|
||||||
#include <LibGUI/Menu.h>
|
#include <LibGUI/Menu.h>
|
||||||
#include <LibGUI/MenuBar.h>
|
#include <LibGUI/MenuBar.h>
|
||||||
#include <LibGUI/MessageBox.h>
|
#include <LibGUI/MessageBox.h>
|
||||||
#include <LibGUI/Splitter.h>
|
#include <LibGUI/Splitter.h>
|
||||||
#include <LibGUI/TextEditor.h>
|
#include <LibGUI/TabWidget.h>
|
||||||
|
#include <LibGUI/TextBox.h>
|
||||||
#include <LibGUI/ToolBar.h>
|
#include <LibGUI/ToolBar.h>
|
||||||
#include <LibGUI/ToolBarContainer.h>
|
#include <LibGUI/ToolBarContainer.h>
|
||||||
#include <LibGUI/TreeView.h>
|
#include <LibGUI/TreeView.h>
|
||||||
|
@ -100,10 +102,41 @@ int main(int argc, char* argv[])
|
||||||
|
|
||||||
auto model = ManualModel::create();
|
auto model = ManualModel::create();
|
||||||
|
|
||||||
auto& tree_view = splitter.add<GUI::TreeView>();
|
auto& left_tab_bar = splitter.add<GUI::TabWidget>();
|
||||||
|
auto& tree_view = left_tab_bar.add_tab<GUI::TreeView>("Tree");
|
||||||
|
auto& search_view = left_tab_bar.add_tab<GUI::Widget>("Search");
|
||||||
|
search_view.set_layout<GUI::VerticalBoxLayout>();
|
||||||
|
auto& search_box = search_view.add<GUI::TextBox>();
|
||||||
|
auto& search_list_view = search_view.add<GUI::ListView>();
|
||||||
|
search_box.set_preferred_size(0, 20);
|
||||||
|
search_box.set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fixed);
|
||||||
|
search_box.set_text("Search...");
|
||||||
|
search_box.on_focusin = [&] {
|
||||||
|
if (search_box.text() == "Search...")
|
||||||
|
search_box.set_text("");
|
||||||
|
};
|
||||||
|
search_box.on_change = [&] {
|
||||||
|
if (auto model = search_list_view.model()) {
|
||||||
|
auto& search_model = *static_cast<GUI::FilteringProxyModel*>(model);
|
||||||
|
search_model.set_filter_term(search_box.text());
|
||||||
|
search_model.update();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
search_box.on_focusout = [&] {
|
||||||
|
if (search_box.text().is_empty()) {
|
||||||
|
if (auto model = search_list_view.model()) {
|
||||||
|
auto& search_model = *static_cast<GUI::FilteringProxyModel*>(model);
|
||||||
|
search_model.set_filter_term("");
|
||||||
|
}
|
||||||
|
search_box.set_text("Search...");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
search_list_view.set_model(GUI::FilteringProxyModel::construct(model));
|
||||||
|
search_list_view.model()->update();
|
||||||
|
|
||||||
tree_view.set_model(model);
|
tree_view.set_model(model);
|
||||||
tree_view.set_size_policy(GUI::SizePolicy::Fixed, GUI::SizePolicy::Fill);
|
left_tab_bar.set_size_policy(GUI::SizePolicy::Fixed, GUI::SizePolicy::Fill);
|
||||||
tree_view.set_preferred_size(200, 500);
|
left_tab_bar.set_preferred_size(200, 500);
|
||||||
|
|
||||||
auto& page_view = splitter.add<Web::PageView>();
|
auto& page_view = splitter.add<Web::PageView>();
|
||||||
|
|
||||||
|
@ -123,19 +156,13 @@ int main(int argc, char* argv[])
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
dbg() << "Opening page at " << path;
|
auto source_result = model->page_view(path);
|
||||||
|
if (source_result.is_error()) {
|
||||||
auto file = Core::File::construct();
|
GUI::MessageBox::show(window, strerror(source_result.error()), "Failed to open man page", GUI::MessageBox::Type::Error);
|
||||||
file->set_filename(path);
|
|
||||||
|
|
||||||
if (!file->open(Core::IODevice::OpenMode::ReadOnly)) {
|
|
||||||
int saved_errno = errno;
|
|
||||||
GUI::MessageBox::show(window, strerror(saved_errno), "Failed to open man page", GUI::MessageBox::Type::Error);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto buffer = file->read_all();
|
|
||||||
StringView source { (const char*)buffer.data(), buffer.size() };
|
|
||||||
|
|
||||||
|
auto source = source_result.value();
|
||||||
String html;
|
String html;
|
||||||
{
|
{
|
||||||
auto md_document = Markdown::Document::parse(source);
|
auto md_document = Markdown::Document::parse(source);
|
||||||
|
@ -173,6 +200,28 @@ int main(int argc, char* argv[])
|
||||||
GUI::MessageBox::Type::Error);
|
GUI::MessageBox::Type::Error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
search_list_view.on_selection = [&](auto index) {
|
||||||
|
if (!index.is_valid())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (auto model = search_list_view.model()) {
|
||||||
|
auto& search_model = *static_cast<GUI::FilteringProxyModel*>(model);
|
||||||
|
index = search_model.map(index);
|
||||||
|
} else {
|
||||||
|
page_view.set_document(nullptr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String path = model->page_path(index);
|
||||||
|
if (path.is_null()) {
|
||||||
|
page_view.set_document(nullptr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tree_view.selection().clear();
|
||||||
|
tree_view.selection().add(index);
|
||||||
|
history.push(path);
|
||||||
|
update_actions();
|
||||||
|
open_page(path);
|
||||||
|
};
|
||||||
|
|
||||||
page_view.on_link_click = [&](auto& url, auto&, unsigned) {
|
page_view.on_link_click = [&](auto& url, auto&, unsigned) {
|
||||||
if (url.protocol() != "file") {
|
if (url.protocol() != "file") {
|
||||||
|
@ -230,7 +279,7 @@ int main(int argc, char* argv[])
|
||||||
|
|
||||||
app->set_menubar(move(menubar));
|
app->set_menubar(move(menubar));
|
||||||
|
|
||||||
window->set_focused_widget(&tree_view);
|
window->set_focused_widget(&left_tab_bar);
|
||||||
window->show();
|
window->show();
|
||||||
|
|
||||||
return app->exec();
|
return app->exec();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue