Browse Source

LibGUI: Start working on a GFileSystemModel and hook that up in FileManager.

This is a read-only model for the tree view, at least initially. We'll see
where we take it from there once it's more polished.
Andreas Kling 6 years ago
parent
commit
4d3c5fd83e

+ 1 - 1
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

+ 2 - 0
Applications/FileManager/main.cpp

@@ -12,6 +12,7 @@
 #include <LibGUI/GMessageBox.h>
 #include <LibGUI/GProgressBar.h>
 #include <LibGUI/GTreeView.h>
+#include <LibGUI/GFileSystemModel.h>
 #include <unistd.h>
 #include <signal.h>
 #include <stdio.h>
@@ -53,6 +54,7 @@ int main(int argc, char** argv)
     auto* splitter = new GWidget(widget);
     splitter->set_layout(make<GBoxLayout>(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);

+ 166 - 0
LibGUI/GFileSystemModel.cpp

@@ -0,0 +1,166 @@
+#include <LibGUI/GFileSystemModel.h>
+#include <AK/FileSystemPath.h>
+#include <AK/StringBuilder.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <stdio.h>
+
+struct GFileSystemModel::Node {
+    String name;
+    Node* parent { nullptr };
+    Vector<Node*> 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<String> 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;
+}

+ 31 - 0
LibGUI/GFileSystemModel.h

@@ -0,0 +1,31 @@
+#pragma once
+
+#include <LibGUI/GModel.h>
+
+class GFileSystemModel : public GModel {
+    friend class Node;
+public:
+    static Retained<GFileSystemModel> 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 };
+};

+ 2 - 3
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<void(GModel&)> on_model_update;
     Function<void(const GModelIndex&)> 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();
 

+ 1 - 1
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 };
 };
 
 

+ 2 - 1
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