1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 09:48:11 +00:00

Help: Add a search mode

This closes #2685, and brings some much needed nostalgia :^)
This commit is contained in:
AnotherTest 2020-07-04 22:38:34 +04:30 committed by Andreas Kling
parent 6131417904
commit 21094b4725
3 changed files with 104 additions and 16 deletions

View file

@ -28,6 +28,9 @@
#include "ManualNode.h"
#include "ManualPageNode.h"
#include "ManualSectionNode.h"
#include <AK/ByteBuffer.h>
#include <LibCore/File.h>
#include <LibGUI/FilteringProxyModel.h>
static ManualSectionNode s_sections[] = {
{ "1", "Command-line programs" },
@ -76,6 +79,25 @@ String ManualModel::page_path(const GUI::ModelIndex& index) const
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
{
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());
switch (role) {
case Role::Search:
if (!node->is_page())
return {};
return String(page_view(page_path(index)).value());
case Role::Display:
return node->name();
case Role::Icon:
@ -156,6 +182,15 @@ void ManualModel::update_section_node_on_toggle(const GUI::ModelIndex& index, co
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()
{
did_update();

View file

@ -28,6 +28,7 @@
#include <AK/NonnullRefPtr.h>
#include <AK/Optional.h>
#include <AK/Result.h>
#include <AK/String.h>
#include <LibGUI/Model.h>
@ -44,11 +45,13 @@ public:
String page_path(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);
virtual int row_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 TriState data_matches(const GUI::ModelIndex&, GUI::Variant) const override;
virtual void update() 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;
@ -59,4 +62,5 @@ private:
GUI::Icon m_section_open_icon;
GUI::Icon m_section_icon;
GUI::Icon m_page_icon;
mutable HashMap<String, OwnPtr<MappedFile>> m_mapped_files;
};

View file

@ -26,7 +26,6 @@
#include "History.h"
#include "ManualModel.h"
#include <AK/ByteBuffer.h>
#include <AK/URL.h>
#include <LibCore/File.h>
#include <LibDesktop/Launcher.h>
@ -34,11 +33,14 @@
#include <LibGUI/Action.h>
#include <LibGUI/Application.h>
#include <LibGUI/BoxLayout.h>
#include <LibGUI/FilteringProxyModel.h>
#include <LibGUI/ListView.h>
#include <LibGUI/Menu.h>
#include <LibGUI/MenuBar.h>
#include <LibGUI/MessageBox.h>
#include <LibGUI/Splitter.h>
#include <LibGUI/TextEditor.h>
#include <LibGUI/TabWidget.h>
#include <LibGUI/TextBox.h>
#include <LibGUI/ToolBar.h>
#include <LibGUI/ToolBarContainer.h>
#include <LibGUI/TreeView.h>
@ -100,10 +102,41 @@ int main(int argc, char* argv[])
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_size_policy(GUI::SizePolicy::Fixed, GUI::SizePolicy::Fill);
tree_view.set_preferred_size(200, 500);
left_tab_bar.set_size_policy(GUI::SizePolicy::Fixed, GUI::SizePolicy::Fill);
left_tab_bar.set_preferred_size(200, 500);
auto& page_view = splitter.add<Web::PageView>();
@ -123,19 +156,13 @@ int main(int argc, char* argv[])
return;
}
dbg() << "Opening page at " << path;
auto file = Core::File::construct();
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);
auto source_result = model->page_view(path);
if (source_result.is_error()) {
GUI::MessageBox::show(window, strerror(source_result.error()), "Failed to open man page", GUI::MessageBox::Type::Error);
return;
}
auto buffer = file->read_all();
StringView source { (const char*)buffer.data(), buffer.size() };
auto source = source_result.value();
String html;
{
auto md_document = Markdown::Document::parse(source);
@ -173,6 +200,28 @@ int main(int argc, char* argv[])
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) {
if (url.protocol() != "file") {
@ -230,7 +279,7 @@ int main(int argc, char* argv[])
app->set_menubar(move(menubar));
window->set_focused_widget(&tree_view);
window->set_focused_widget(&left_tab_bar);
window->show();
return app->exec();