1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-28 16:35:08 +00:00

Applications: Add a new Help app

This is a neat simple app that can display the Serenity manual ^)
This commit is contained in:
Sergey Bugaev 2019-09-21 00:47:31 +03:00 committed by Andreas Kling
parent 6ec625d6f3
commit 02ee8cbbe2
12 changed files with 349 additions and 1 deletions

View file

@ -0,0 +1,11 @@
include ../../Makefile.common
OBJS = \
ManualModel.o \
ManualSectionNode.o \
ManualPageNode.o \
main.o
APP = Help
include ../Makefile.common

View file

@ -0,0 +1,105 @@
#include "ManualModel.h"
#include "ManualNode.h"
#include "ManualPageNode.h"
#include "ManualSectionNode.h"
#include <LibDraw/PNGLoader.h>
static ManualSectionNode s_sections[] = {
{ "1", "Command-line programs" },
{ "2", "System calls" }
};
ManualModel::ManualModel()
{
// FIXME: need some help from the icon fairy ^)
m_section_icon.set_bitmap_for_size(16, load_png("/res/icons/16x16/book.png"));
m_page_icon.set_bitmap_for_size(16, load_png("/res/icons/16x16/filetype-unknown.png"));
}
String ManualModel::page_path(const GModelIndex& index) const
{
if (!index.is_valid())
return {};
auto* node = static_cast<const ManualNode*>(index.internal_data());
if (!node->is_page())
return {};
auto* page = static_cast<const ManualPageNode*>(node);
return page->path();
}
String ManualModel::page_and_section(const GModelIndex& index) const
{
if (!index.is_valid())
return {};
auto* node = static_cast<const ManualNode*>(index.internal_data());
if (!node->is_page())
return {};
auto* page = static_cast<const ManualPageNode*>(node);
auto* section = static_cast<const ManualSectionNode*>(page->parent());
return String::format("%s(%s)", page->name().characters(), section->section_name().characters());
}
GModelIndex ManualModel::index(int row, int column, const GModelIndex& parent_index) const
{
if (!parent_index.is_valid())
return create_index(row, column, &s_sections[row]);
auto* parent = static_cast<const ManualNode*>(parent_index.internal_data());
auto* child = &parent->children()[row];
return create_index(row, column, child);
}
GModelIndex ManualModel::parent_index(const GModelIndex& index) const
{
if (!index.is_valid())
return {};
auto* child = static_cast<const ManualNode*>(index.internal_data());
auto* parent = child->parent();
if (parent == nullptr)
return {};
if (parent->parent() == nullptr) {
for (size_t row = 0; row < sizeof(s_sections) / sizeof(s_sections[0]); row++)
if (&s_sections[row] == parent)
return create_index(row, 0, parent);
ASSERT_NOT_REACHED();
}
for (int row = 0; row < parent->parent()->children().size(); row++) {
ManualNode* child_at_row = &parent->parent()->children()[row];
if (child_at_row == parent)
return create_index(row, 0, parent);
}
ASSERT_NOT_REACHED();
}
int ManualModel::row_count(const GModelIndex& index) const
{
if (!index.is_valid())
return sizeof(s_sections) / sizeof(s_sections[0]);
auto* node = static_cast<const ManualNode*>(index.internal_data());
return node->children().size();
}
int ManualModel::column_count(const GModelIndex&) const
{
return 1;
}
GVariant ManualModel::data(const GModelIndex& index, Role role) const
{
auto* node = static_cast<const ManualNode*>(index.internal_data());
switch (role) {
case Role::Display:
return node->name();
case Role::Icon:
if (node->is_page())
return m_page_icon;
return m_section_icon;
default:
return {};
}
}
void ManualModel::update()
{
did_update();
}

View file

@ -0,0 +1,31 @@
#pragma once
#include <AK/NonnullRefPtr.h>
#include <AK/String.h>
#include <LibGUI/GModel.h>
class ManualModel final : public GModel {
public:
static NonnullRefPtr<ManualModel> create()
{
return adopt(*new ManualModel);
}
virtual ~ManualModel() override {};
String page_path(const GModelIndex&) const;
String page_and_section(const GModelIndex&) const;
virtual int row_count(const GModelIndex& = GModelIndex()) const override;
virtual int column_count(const GModelIndex& = GModelIndex()) const override;
virtual GVariant data(const GModelIndex&, Role = Role::Display) const override;
virtual void update() override;
virtual GModelIndex parent_index(const GModelIndex&) const override;
virtual GModelIndex index(int row, int column = 0, const GModelIndex& parent = GModelIndex()) const override;
private:
ManualModel();
GIcon m_section_icon;
GIcon m_page_icon;
};

View file

@ -0,0 +1,14 @@
#pragma once
#include <AK/NonnullOwnPtrVector.h>
#include <AK/String.h>
class ManualNode {
public:
virtual ~ManualNode() {}
virtual NonnullOwnPtrVector<ManualNode>& children() const = 0;
virtual const ManualNode* parent() const = 0;
virtual String name() const = 0;
virtual bool is_page() const { return false; }
};

View file

@ -0,0 +1,18 @@
#include "ManualPageNode.h"
#include "ManualSectionNode.h"
const ManualNode* ManualPageNode::parent() const
{
return &m_section;
}
NonnullOwnPtrVector<ManualNode>& ManualPageNode::children() const
{
static NonnullOwnPtrVector<ManualNode> empty_vector;
return empty_vector;
}
String ManualPageNode::path() const
{
return String::format("%s/%s.md", m_section.path().characters(), m_page.characters());
}

View file

@ -0,0 +1,27 @@
#pragma once
#include "ManualNode.h"
class ManualSectionNode;
class ManualPageNode : public ManualNode {
public:
virtual ~ManualPageNode() override {}
ManualPageNode(const ManualSectionNode& section, const StringView& page)
: m_section(section)
, m_page(page)
{
}
virtual NonnullOwnPtrVector<ManualNode>& children() const override;
virtual const ManualNode* parent() const override;
virtual String name() const override { return m_page; };
virtual bool is_page() const override { return true; }
String path() const;
private:
const ManualSectionNode& m_section;
String m_page;
};

View file

@ -0,0 +1,26 @@
#include "ManualSectionNode.h"
#include "ManualPageNode.h"
#include <AK/String.h>
#include <LibCore/CDirIterator.h>
String ManualSectionNode::path() const
{
return String::format("/usr/share/man/man%s", m_section.characters());
}
void ManualSectionNode::reify_if_needed() const
{
if (m_reified)
return;
m_reified = true;
CDirIterator dir_iter { path(), CDirIterator::Flags::SkipDots };
while (dir_iter.has_next()) {
String file_name = dir_iter.next_path();
ASSERT(file_name.ends_with(".md"));
String page_name = file_name.substring(0, file_name.length() - 3);
NonnullOwnPtr<ManualNode> child = make<ManualPageNode>(*this, move(page_name));
m_children.append(move(child));
}
}

View file

@ -0,0 +1,34 @@
#pragma once
#include "ManualNode.h"
class ManualSectionNode : public ManualNode {
public:
virtual ~ManualSectionNode() override {}
ManualSectionNode(String section, String name)
: m_section(section)
, m_full_name(String::format("%s. %s", section.characters(), name.characters()))
{
}
virtual NonnullOwnPtrVector<ManualNode>& children() const override
{
reify_if_needed();
return m_children;
}
virtual const ManualNode* parent() const override { return nullptr; }
virtual String name() const override { return m_full_name; }
const String& section_name() const { return m_section; }
String path() const;
private:
void reify_if_needed() const;
String m_section;
String m_full_name;
mutable NonnullOwnPtrVector<ManualNode> m_children;
mutable bool m_reified { false };
};

View file

@ -0,0 +1,79 @@
#include "ManualModel.h"
#include <LibCore/CFile.h>
#include <LibDraw/PNGLoader.h>
#include <LibGUI/GApplication.h>
#include <LibGUI/GMessageBox.h>
#include <LibGUI/GSplitter.h>
#include <LibGUI/GTextEditor.h>
#include <LibGUI/GTreeView.h>
#include <LibGUI/GWindow.h>
#include <LibHTML/HtmlView.h>
#include <LibHTML/Layout/LayoutNode.h>
#include <LibHTML/Parser/CSSParser.h>
#include <LibHTML/Parser/HTMLParser.h>
#include <LibMarkdown/MDDocument.h>
int main(int argc, char* argv[])
{
GApplication app(argc, argv);
auto window = GWindow::construct();
window->set_title("Help");
window->set_rect(300, 200, 570, 500);
auto splitter = GSplitter::construct(Orientation::Horizontal, nullptr);
auto model = ManualModel::create();
auto tree_view = GTreeView::construct(splitter);
tree_view->set_model(model);
tree_view->set_size_policy(SizePolicy::Fixed, SizePolicy::Fill);
tree_view->set_preferred_size(200, 500);
auto html_view = HtmlView::construct(splitter);
extern const char default_stylesheet_source[];
String css = default_stylesheet_source;
auto sheet = parse_css(css);
tree_view->on_selection_change = [&] {
String path = model->page_path(tree_view->selection().first());
if (path.is_null()) {
html_view->set_document(nullptr);
return;
}
dbg() << "Opening page at " << path;
auto file = CFile::construct();
file->set_filename(path);
if (!file->open(CIODevice::OpenMode::ReadOnly)) {
int saved_errno = errno;
GMessageBox::show(strerror(saved_errno), "Failed to open man page", GMessageBox::Type::Error, GMessageBox::InputType::OK, window);
return;
}
auto buffer = file->read_all();
StringView source { (char*)buffer.data(), buffer.size() };
MDDocument md_document;
bool success = md_document.parse(source);
ASSERT(success);
String html = md_document.render_to_html();
auto html_document = parse_html(html);
html_document->normalize();
html_document->add_sheet(sheet);
html_view->set_document(html_document);
String page_and_section = model->page_and_section(tree_view->selection().first());
window->set_title(String::format("Help: %s", page_and_section.characters()));
};
window->set_main_widget(splitter);
window->show();
window->set_icon(load_png("/res/icons/16x16/book.png"));
return app.exec();
}

View file

@ -3,7 +3,7 @@ DEFINES += -DUSERLAND
all: $(APP)
$(APP): $(OBJS)
$(LD) -o $(APP) $(LDFLAGS) $(OBJS) -lgui -ldraw -laudio -lipc -lthread -lvt -lpcidb -lcore -lc
$(LD) -o $(APP) $(LDFLAGS) $(OBJS) -lmarkdown -lhtml -lgui -ldraw -laudio -lipc -lthread -lvt -lpcidb -lcore -lc
.cpp.o:
@echo "CXX $<"; $(CXX) $(CXXFLAGS) -o $@ -c $<

View file

@ -88,6 +88,7 @@ cp ../Applications/Calculator/Calculator mnt/bin/Calculator
cp ../Applications/SoundPlayer/SoundPlayer mnt/bin/SoundPlayer
cp ../Applications/DisplayProperties/DisplayProperties mnt/bin/DisplayProperties
cp ../Applications/Welcome/Welcome mnt/bin/Welcome
cp ../Applications/Help/Help mnt/bin/Help
cp ../Demos/HelloWorld/HelloWorld mnt/bin/HelloWorld
cp ../Demos/HelloWorld2/HelloWorld2 mnt/bin/HelloWorld2
cp ../Demos/RetroFetch/RetroFetch mnt/bin/RetroFetch
@ -127,6 +128,7 @@ ln -s ChanViewer mnt/bin/cv
ln -s Calculator mnt/bin/calc
ln -s Inspector mnt/bin/ins
ln -s SoundPlayer mnt/bin/sp
ln -s Help mnt/bin/help
echo "done"
mkdir -p mnt/boot/

View file

@ -59,6 +59,7 @@ build_targets="$build_targets ../Applications/Terminal"
build_targets="$build_targets ../Applications/TextEditor"
build_targets="$build_targets ../Applications/SoundPlayer"
build_targets="$build_targets ../Applications/Welcome"
build_targets="$build_targets ../Applications/Help"
build_targets="$build_targets ../Demos/Fire"
build_targets="$build_targets ../Demos/HelloWorld"