diff --git a/Applications/FileManager/Makefile b/Applications/FileManager/Makefile index e9b229d9fbe..4306a7ecf35 100644 --- a/Applications/FileManager/Makefile +++ b/Applications/FileManager/Makefile @@ -5,7 +5,7 @@ OBJS = \ APP = FileManager -STANDARD_FLAGS = -std=c++17 +STANDARD_FLAGS = -std=c++17 -Wno-sized-deallocation WARNING_FLAGS = -Wextra -Wall -Wundef -Wcast-qual -Wwrite-strings -Wimplicit-fallthrough FLAVOR_FLAGS = -fno-exceptions -fno-rtti OPTIMIZATION_FLAGS = -Os diff --git a/Applications/FileManager/main.cpp b/Applications/FileManager/main.cpp index ef87839545e..a546c3e3956 100644 --- a/Applications/FileManager/main.cpp +++ b/Applications/FileManager/main.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -53,6 +54,7 @@ int main(int argc, char** argv) auto* splitter = new GWidget(widget); splitter->set_layout(make(Orientation::Horizontal)); auto* tree_view = new GTreeView(splitter); + tree_view->set_model(GFileSystemModel::create("/")); tree_view->set_size_policy(SizePolicy::Fixed, SizePolicy::Fill); tree_view->set_preferred_size({ 200, 0 }); auto* directory_view = new DirectoryView(splitter); diff --git a/LibGUI/GFileSystemModel.cpp b/LibGUI/GFileSystemModel.cpp new file mode 100644 index 00000000000..7f80a681786 --- /dev/null +++ b/LibGUI/GFileSystemModel.cpp @@ -0,0 +1,166 @@ +#include +#include +#include +#include +#include +#include +#include + +struct GFileSystemModel::Node { + String name; + Node* parent { nullptr }; + Vector children; + enum Type { Unknown, Directory, File }; + Type type { Unknown }; + + bool has_traversed { false }; + + GModelIndex index(const GFileSystemModel& model) const + { + if (!parent) + return { }; + for (int row = 0; row < parent->children.size(); ++row) { + if (parent->children[row] == this) + return model.create_index(row, 0, parent); + } + ASSERT_NOT_REACHED(); + } + + String full_path(const GFileSystemModel& model) const + { + Vector lineage; + for (auto* ancestor = parent; ancestor; ancestor = ancestor->parent) { + lineage.append(ancestor->name); + } + StringBuilder builder; + builder.append(model.root_path()); + for (int i = lineage.size() - 1; i >= 0; --i) { + builder.append('/'); + builder.append(lineage[i]); + } + builder.append('/'); + builder.append(name); + return FileSystemPath(builder.to_string()).string(); + } + + void traverse_if_needed(const GFileSystemModel& model) + { + if (type != Node::Directory || has_traversed) + return; + has_traversed = true; + + auto full_path = this->full_path(model); + DIR* dirp = opendir(full_path.characters()); + dbgprintf("traverse if needed: %s (%p)\n", full_path.characters(), dirp); + if (!dirp) + return; + + while (auto* de = readdir(dirp)) { + if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) + continue; + struct stat st; + int rc = lstat(String::format("%s/%s", full_path.characters(), de->d_name).characters(), &st); + if (rc < 0) { + perror("lstat"); + continue; + } + auto* child = new Node; + child->name = de->d_name; + child->type = S_ISDIR(st.st_mode) ? Node::Type::Directory : Node::Type::File; + child->parent = this; + children.append(child); + } + + closedir(dirp); + } + + void reify_if_needed(const GFileSystemModel& model) + { + traverse_if_needed(model); + if (type != Node::Type::Unknown) + return; + struct stat st; + auto full_path = this->full_path(model); + int rc = lstat(full_path.characters(), &st); + dbgprintf("lstat(%s) = %d\n", full_path.characters(), rc); + if (rc < 0) { + perror("lstat"); + return; + } + type = S_ISDIR(st.st_mode) ? Node::Type::Directory : Node::Type::File; + } +}; + +GFileSystemModel::GFileSystemModel(const String& root_path) + : m_root_path(FileSystemPath(root_path).string()) +{ + update(); +} + +GFileSystemModel::~GFileSystemModel() +{ +} + +void GFileSystemModel::update() +{ + // FIXME: Support refreshing the model! + if (m_root) + return; + + m_root = new Node; + m_root->name = m_root_path; + m_root->reify_if_needed(*this); +} + +int GFileSystemModel::row_count(const GModelIndex& index) const +{ + if (!index.is_valid()) + return 1; + auto& node = *(Node*)index.internal_data(); + node.reify_if_needed(*this); + if (node.type == Node::Type::Directory) + return node.children.size(); + return 0; +} + +GModelIndex GFileSystemModel::index(int row, int column, const GModelIndex& parent) const +{ + if (!parent.is_valid()) + return create_index(row, column, m_root); + auto& node = *(Node*)parent.internal_data(); + return create_index(row, column, node.children[row]); +} + +GModelIndex GFileSystemModel::parent_index(const GModelIndex& index) const +{ + if (!index.is_valid()) + return { }; + auto& node = *(const Node*)index.internal_data(); + if (!node.parent) + return { }; + return node.parent->index(*this); +} + +GVariant GFileSystemModel::data(const GModelIndex& index, Role role) const +{ + if (!index.is_valid()) + return { }; + auto& node = *(const Node*)index.internal_data(); + if (role == GModel::Role::Display) + return node.name; + if (role == GModel::Role::Icon) { + if (node.type == Node::Directory) + return GIcon::default_icon("filetype-folder"); + return GIcon::default_icon("filetype-unknown"); + } + return { }; +} + +void GFileSystemModel::activate(const GModelIndex&) +{ +} + +int GFileSystemModel::column_count(const GModelIndex&) const +{ + return 1; +} diff --git a/LibGUI/GFileSystemModel.h b/LibGUI/GFileSystemModel.h new file mode 100644 index 00000000000..c465d68b6e5 --- /dev/null +++ b/LibGUI/GFileSystemModel.h @@ -0,0 +1,31 @@ +#pragma once + +#include + +class GFileSystemModel : public GModel { + friend class Node; +public: + static Retained create(const String& root_path = "/") + { + return adopt(*new GFileSystemModel(root_path)); + } + virtual ~GFileSystemModel() override; + + String root_path() const { return m_root_path; } + + 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& = GModelIndex()) const override; + virtual void activate(const GModelIndex&) override; + +private: + explicit GFileSystemModel(const String& root_path); + + String m_root_path; + + struct Node; + Node* m_root { nullptr }; +}; diff --git a/LibGUI/GModel.h b/LibGUI/GModel.h index 4dd2df72ae1..94364b6889f 100644 --- a/LibGUI/GModel.h +++ b/LibGUI/GModel.h @@ -53,6 +53,8 @@ public: virtual ColumnMetadata column_metadata(int) const { return { }; } virtual GVariant data(const GModelIndex&, Role = Role::Display) const = 0; virtual void update() = 0; + virtual GModelIndex parent_index(const GModelIndex&) const { return { }; } + virtual GModelIndex index(int row, int column = 0, const GModelIndex& = GModelIndex()) const { return create_index(row, column); } virtual void activate(const GModelIndex&) { } bool is_valid(const GModelIndex& index) const @@ -76,9 +78,6 @@ public: Function on_model_update; Function on_selection_changed; - virtual GModelIndex parent_index(const GModelIndex&) const { return { }; } - virtual GModelIndex index(int row, int column = 0, const GModelIndex& = GModelIndex()) const { return create_index(row, column); } - protected: GModel(); diff --git a/LibGUI/GTreeView.cpp b/LibGUI/GTreeView.cpp index f4f173e06f3..eef89818add 100644 --- a/LibGUI/GTreeView.cpp +++ b/LibGUI/GTreeView.cpp @@ -87,7 +87,7 @@ GVariant TestModel::data(const GModelIndex& index, Role role) const } struct GTreeView::MetadataForIndex { - bool open { true }; + bool open { false }; }; diff --git a/LibGUI/Makefile b/LibGUI/Makefile index 369e53acc33..372ec0630b3 100644 --- a/LibGUI/Makefile +++ b/LibGUI/Makefile @@ -55,6 +55,7 @@ LIBGUI_OBJS = \ GElapsedTimer.o \ GFrame.o \ GTreeView.o \ + GFileSystemModel.o \ GWindow.o OBJS = $(SHAREDGRAPHICS_OBJS) $(LIBGUI_OBJS) @@ -62,7 +63,7 @@ OBJS = $(SHAREDGRAPHICS_OBJS) $(LIBGUI_OBJS) LIBS = -lc LIBRARY = libgui.a -STANDARD_FLAGS = -std=c++17 +STANDARD_FLAGS = -std=c++17 -Wno-sized-deallocation WARNING_FLAGS = -Wextra -Wall -Wundef -Wcast-qual -Wwrite-strings -Wimplicit-fallthrough FLAVOR_FLAGS = -fno-exceptions -fno-rtti OPTIMIZATION_FLAGS = -Os