mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 06:02:44 +00:00 
			
		
		
		
	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:
		
							parent
							
								
									f249c40aaa
								
							
						
					
					
						commit
						4d3c5fd83e
					
				
					 7 changed files with 205 additions and 6 deletions
				
			
		|  | @ -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 | ||||
|  |  | |||
|  | @ -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
									
								
							
							
						
						
									
										166
									
								
								LibGUI/GFileSystemModel.cpp
									
										
									
									
									
										Normal 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
									
								
							
							
						
						
									
										31
									
								
								LibGUI/GFileSystemModel.h
									
										
									
									
									
										Normal 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 }; | ||||
| }; | ||||
|  | @ -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(); | ||||
| 
 | ||||
|  |  | |||
|  | @ -87,7 +87,7 @@ GVariant TestModel::data(const GModelIndex& index, Role role) const | |||
| } | ||||
| 
 | ||||
| struct GTreeView::MetadataForIndex { | ||||
|     bool open { true }; | ||||
|     bool open { false }; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Andreas Kling
						Andreas Kling