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.
This commit is contained in:
Andreas Kling 2019-03-29 17:03:30 +01:00
parent f249c40aaa
commit 4d3c5fd83e
Notes: sideshowbarker 2024-07-19 14:54:11 +09:00
7 changed files with 205 additions and 6 deletions

View File

@ -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

View File

@ -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
LibGUI/GFileSystemModel.cpp Normal file
View File

@ -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
LibGUI/GFileSystemModel.h Normal file
View File

@ -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 };
};

View File

@ -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();

View File

@ -87,7 +87,7 @@ GVariant TestModel::data(const GModelIndex& index, Role role) const
}
struct GTreeView::MetadataForIndex {
bool open { true };
bool open { false };
};

View File

@ -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