diff --git a/Applications/FileManager/DirectoryTableModel.cpp b/Applications/FileManager/DirectoryTableModel.cpp new file mode 100644 index 0000000000..757a00a62d --- /dev/null +++ b/Applications/FileManager/DirectoryTableModel.cpp @@ -0,0 +1,180 @@ +#include "DirectoryTableModel.h" +#include +#include +#include +#include +#include + +DirectoryTableModel::DirectoryTableModel() +{ + m_directory_icon = GraphicsBitmap::load_from_file(GraphicsBitmap::Format::RGBA32, "/res/icons/folder16.rgb", { 16, 16 }); + m_file_icon = GraphicsBitmap::load_from_file(GraphicsBitmap::Format::RGBA32, "/res/icons/file16.rgb", { 16, 16 }); + m_symlink_icon = GraphicsBitmap::load_from_file(GraphicsBitmap::Format::RGBA32, "/res/icons/link16.rgb", { 16, 16 }); + m_socket_icon = GraphicsBitmap::load_from_file(GraphicsBitmap::Format::RGBA32, "/res/icons/socket16.rgb", { 16, 16 }); +} + +DirectoryTableModel::~DirectoryTableModel() +{ +} + +int DirectoryTableModel::row_count() const +{ + return m_directories.size() + m_files.size(); +} + +int DirectoryTableModel::column_count() const +{ + return Column::__Count; +} + +String DirectoryTableModel::column_name(int column) const +{ + switch (column) { + case Column::Icon: return ""; + case Column::Name: return "Name"; + case Column::Size: return "Size"; + case Column::UID: return "UID"; + case Column::GID: return "GID"; + case Column::Permissions: return "Mode"; + case Column::Inode: return "Inode"; + } + ASSERT_NOT_REACHED(); +} + +GTableModel::ColumnMetadata DirectoryTableModel::column_metadata(int column) const +{ + switch (column) { + case Column::Icon: return { 16, TextAlignment::Center }; + case Column::Name: return { 120, TextAlignment::CenterLeft }; + case Column::Size: return { 80, TextAlignment::CenterRight }; + case Column::UID: return { 80, TextAlignment::CenterRight }; + case Column::GID: return { 80, TextAlignment::CenterRight }; + case Column::Permissions: return { 100, TextAlignment::CenterLeft }; + case Column::Inode: return { 80, TextAlignment::CenterRight }; + } + ASSERT_NOT_REACHED(); +} + +const GraphicsBitmap& DirectoryTableModel::icon_for(const Entry& entry) const +{ + if (S_ISDIR(entry.mode)) + return *m_directory_icon; + if (S_ISLNK(entry.mode)) + return *m_symlink_icon; + if (S_ISSOCK(entry.mode)) + return *m_socket_icon; + return *m_file_icon; +} + + +static String permission_string(mode_t mode) +{ + StringBuilder builder; + if (S_ISDIR(mode)) + builder.append("d"); + else if (S_ISLNK(mode)) + builder.append("l"); + else if (S_ISBLK(mode)) + builder.append("b"); + else if (S_ISCHR(mode)) + builder.append("c"); + else if (S_ISFIFO(mode)) + builder.append("f"); + else if (S_ISSOCK(mode)) + builder.append("s"); + else if (S_ISREG(mode)) + builder.append("-"); + else + builder.append("?"); + + builder.appendf("%c%c%c%c%c%c%c%c", + mode & S_IRUSR ? 'r' : '-', + mode & S_IWUSR ? 'w' : '-', + mode & S_ISUID ? 's' : (mode & S_IXUSR ? 'x' : '-'), + mode & S_IRGRP ? 'r' : '-', + mode & S_IWGRP ? 'w' : '-', + mode & S_ISGID ? 's' : (mode & S_IXGRP ? 'x' : '-'), + mode & S_IROTH ? 'r' : '-', + mode & S_IWOTH ? 'w' : '-' + ); + + if (mode & S_ISVTX) + builder.append("t"); + else + builder.appendf("%c", mode & S_IXOTH ? 'x' : '-'); + return builder.to_string(); +} + +GVariant DirectoryTableModel::data(int row, int column) const +{ + auto& entry = this->entry(row); + switch (column) { + case Column::Icon: return icon_for(entry); + case Column::Name: return entry.name; + case Column::Size: return (int)entry.size; + case Column::UID: return (int)entry.uid; + case Column::GID: return (int)entry.gid; + case Column::Permissions: return permission_string(entry.mode); + case Column::Inode: return (int)entry.inode; + } + ASSERT_NOT_REACHED(); + +} + +void DirectoryTableModel::update() +{ + DIR* dirp = opendir(m_path.characters()); + if (!dirp) { + perror("opendir"); + exit(1); + } + m_directories.clear(); + m_files.clear(); + + m_bytes_in_files = 0; + while (auto* de = readdir(dirp)) { + Entry entry; + entry.name = de->d_name; + struct stat st; + int rc = lstat(String::format("%s/%s", m_path.characters(), de->d_name).characters(), &st); + if (rc < 0) { + perror("lstat"); + continue; + } + entry.size = st.st_size; + entry.mode = st.st_mode; + entry.uid = st.st_uid; + entry.gid = st.st_gid; + entry.inode = st.st_ino; + auto& entries = S_ISDIR(st.st_mode) ? m_directories : m_files; + entries.append(move(entry)); + + if (S_ISREG(entry.mode)) + m_bytes_in_files += st.st_size; + } + closedir(dirp); + + did_update(); +} + +void DirectoryTableModel::open(const String& path) +{ + if (m_path == path) + return; + DIR* dirp = opendir(path.characters()); + if (!dirp) + return; + closedir(dirp); + m_path = path; + update(); + set_selected_index({ 0, 0 }); +} + +void DirectoryTableModel::activate(const GModelIndex& index) +{ + auto& entry = this->entry(index.row()); + if (entry.is_directory()) { + FileSystemPath new_path(String::format("%s/%s", m_path.characters(), entry.name.characters())); + open(new_path.string()); + } +} diff --git a/Applications/FileManager/DirectoryTableModel.h b/Applications/FileManager/DirectoryTableModel.h new file mode 100644 index 0000000000..661517d953 --- /dev/null +++ b/Applications/FileManager/DirectoryTableModel.h @@ -0,0 +1,62 @@ +#pragma once + +#include +#include + +class DirectoryTableModel final : public GTableModel { +public: + DirectoryTableModel(); + virtual ~DirectoryTableModel() override; + + enum Column { + Icon = 0, + Name, + Size, + UID, + GID, + Permissions, + Inode, + __Count, + }; + + virtual int row_count() const override; + virtual int column_count() const override; + virtual String column_name(int column) const override; + virtual ColumnMetadata column_metadata(int column) const override; + virtual GVariant data(int row, int column) const override; + virtual void update() override; + virtual void activate(const GModelIndex&) override; + + String path() const { return m_path; } + void open(const String& path); + size_t bytes_in_files() const { return m_bytes_in_files; } + +private: + struct Entry { + String name; + size_t size { 0 }; + mode_t mode { 0 }; + uid_t uid { 0 }; + uid_t gid { 0 }; + ino_t inode { 0 }; + bool is_directory() const { return S_ISDIR(mode); } + }; + + const Entry& entry(int index) const + { + if (index < m_directories.size()) + return m_directories[index]; + return m_files[index - m_directories.size()]; + } + const GraphicsBitmap& icon_for(const Entry& entry) const; + + String m_path; + Vector m_files; + Vector m_directories; + size_t m_bytes_in_files; + + RetainPtr m_directory_icon; + RetainPtr m_file_icon; + RetainPtr m_symlink_icon; + RetainPtr m_socket_icon; +}; diff --git a/Applications/FileManager/DirectoryTableView.cpp b/Applications/FileManager/DirectoryTableView.cpp new file mode 100644 index 0000000000..cbc274a7aa --- /dev/null +++ b/Applications/FileManager/DirectoryTableView.cpp @@ -0,0 +1,33 @@ +#include "DirectoryTableView.h" + +DirectoryTableView::DirectoryTableView(GWidget* parent) + : GTableView(parent) +{ + set_model(make()); +} + +DirectoryTableView::~DirectoryTableView() +{ +} + +void DirectoryTableView::open(const String& path) +{ + model().open(path); +} + +void DirectoryTableView::model_notification(const GModelNotification& notification) +{ + if (notification.type() == GModelNotification::Type::ModelUpdated) { + set_status_message(String::format("%d item%s (%u byte%s)", + model().row_count(), + model().row_count() != 1 ? "s" : "", + model().bytes_in_files(), + model().bytes_in_files() != 1 ? "s" : "")); + } +} + +void DirectoryTableView::set_status_message(const String& message) +{ + if (on_status_message) + on_status_message(message); +} diff --git a/Applications/FileManager/DirectoryTableView.h b/Applications/FileManager/DirectoryTableView.h new file mode 100644 index 0000000000..8e80d8350e --- /dev/null +++ b/Applications/FileManager/DirectoryTableView.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include "DirectoryTableModel.h" + +class DirectoryTableView final : public GTableView { +public: + explicit DirectoryTableView(GWidget* parent); + virtual ~DirectoryTableView() override; + + void open(const String& path); + String path() const { return model().path(); } + + Function on_path_change; + Function on_status_message; + +private: + virtual void model_notification(const GModelNotification&) override; + + DirectoryTableModel& model() { return static_cast(*GTableView::model()); } + const DirectoryTableModel& model() const { return static_cast(*GTableView::model()); } + + void set_status_message(const String&); +}; diff --git a/Applications/FileManager/DirectoryView.cpp b/Applications/FileManager/DirectoryView.cpp deleted file mode 100644 index 87b5a3cc9c..0000000000 --- a/Applications/FileManager/DirectoryView.cpp +++ /dev/null @@ -1,171 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "DirectoryView.h" - -DirectoryView::DirectoryView(GWidget* parent) - : GWidget(parent) -{ - m_directory_icon = GraphicsBitmap::load_from_file(GraphicsBitmap::Format::RGBA32, "/res/icons/folder16.rgb", { 16, 16 }); - m_file_icon = GraphicsBitmap::load_from_file(GraphicsBitmap::Format::RGBA32, "/res/icons/file16.rgb", { 16, 16 }); - m_symlink_icon = GraphicsBitmap::load_from_file(GraphicsBitmap::Format::RGBA32, "/res/icons/link16.rgb", { 16, 16 }); - m_socket_icon = GraphicsBitmap::load_from_file(GraphicsBitmap::Format::RGBA32, "/res/icons/socket16.rgb", { 16, 16 }); - - m_scrollbar = new GScrollBar(Orientation::Vertical, this); - m_scrollbar->set_step(4); - m_scrollbar->set_big_step(30); - m_scrollbar->on_change = [this] (int) { - update(); - }; -} - -DirectoryView::~DirectoryView() -{ -} - -void DirectoryView::resize_event(GResizeEvent& event) -{ - m_scrollbar->set_relative_rect(event.size().width() - m_scrollbar->preferred_size().width(), 0, m_scrollbar->preferred_size().width(), event.size().height()); -} - -void DirectoryView::open(const String& path) -{ - if (m_path == path) - return; - DIR* dirp = opendir(path.characters()); - if (!dirp) - return; - closedir(dirp); - m_path = path; - reload(); - if (on_path_change) - on_path_change(m_path); - update(); -} - -void DirectoryView::reload() -{ - DIR* dirp = opendir(m_path.characters()); - if (!dirp) { - perror("opendir"); - exit(1); - } - m_directories.clear(); - m_files.clear(); - - size_t bytes_in_files = 0; - while (auto* de = readdir(dirp)) { - Entry entry; - entry.name = de->d_name; - struct stat st; - int rc = lstat(String::format("%s/%s", m_path.characters(), de->d_name).characters(), &st); - if (rc < 0) { - perror("lstat"); - continue; - } - entry.size = st.st_size; - entry.mode = st.st_mode; - auto& entries = S_ISDIR(st.st_mode) ? m_directories : m_files; - entries.append(move(entry)); - - if (S_ISREG(entry.mode)) - bytes_in_files += st.st_size; - } - closedir(dirp); - int excess_height = max(0, (item_count() * item_height()) - height()); - m_scrollbar->set_range(0, excess_height); - - - - set_status_message(String::format("%d item%s (%u byte%s)", - item_count(), - item_count() != 1 ? "s" : "", - bytes_in_files, - bytes_in_files != 1 ? "s" : "")); -} - -const GraphicsBitmap& DirectoryView::icon_for(const Entry& entry) const -{ - if (S_ISDIR(entry.mode)) - return *m_directory_icon; - if (S_ISLNK(entry.mode)) - return *m_symlink_icon; - if (S_ISSOCK(entry.mode)) - return *m_socket_icon; - return *m_file_icon; -} - -static String pretty_byte_size(size_t size) -{ - return String::format("%u", size); -} - -bool DirectoryView::should_show_size_for(const Entry& entry) const -{ - return S_ISREG(entry.mode); -} - -Rect DirectoryView::row_rect(int item_index) const -{ - return { 0, item_index * item_height(), width(), item_height() }; -} - -void DirectoryView::mousedown_event(GMouseEvent& event) -{ - if (event.button() == GMouseButton::Left) { - for (int i = 0; i < item_count(); ++i) { - if (!row_rect(i).contains(event.position())) - continue; - auto& entry = this->entry(i); - if (entry.is_directory()) { - FileSystemPath new_path(String::format("%s/%s", m_path.characters(), entry.name.characters())); - open(new_path.string()); - } - } - } -} - -void DirectoryView::paint_event(GPaintEvent&) -{ - Painter painter(*this); - - painter.translate(0, -m_scrollbar->value()); - - int horizontal_padding = 5; - int icon_size = 16; - int painted_item_index = 0; - - auto process_entries = [&] (const Vector& entries) { - for (int i = 0; i < entries.size(); ++i, ++painted_item_index) { - auto& entry = entries[i]; - int y = painted_item_index * item_height(); - Rect icon_rect(horizontal_padding, y, icon_size, item_height()); - Rect name_rect(icon_rect.right() + horizontal_padding, y, 100, item_height()); - Rect size_rect(name_rect.right() + horizontal_padding, y, 64, item_height()); - painter.fill_rect(row_rect(painted_item_index), painted_item_index % 2 ? Color(210, 210, 210) : Color::White); - painter.blit(icon_rect.location(), icon_for(entry), { 0, 0, icon_size, icon_size }); - painter.draw_text(name_rect, entry.name, TextAlignment::CenterLeft, Color::Black); - if (should_show_size_for(entry)) - painter.draw_text(size_rect, pretty_byte_size(entry.size), TextAlignment::CenterRight, Color::Black); - } - }; - - process_entries(m_directories); - process_entries(m_files); - - Rect unpainted_rect(0, painted_item_index * item_height(), width(), height()); - unpainted_rect.intersect(rect()); - painter.fill_rect(unpainted_rect, Color::White); -} - -void DirectoryView::set_status_message(String&& message) -{ - if (on_status_message) - on_status_message(move(message)); -} diff --git a/Applications/FileManager/DirectoryView.h b/Applications/FileManager/DirectoryView.h deleted file mode 100644 index 234a18eb81..0000000000 --- a/Applications/FileManager/DirectoryView.h +++ /dev/null @@ -1,59 +0,0 @@ -#pragma once - -#include -#include -#include - -class GScrollBar; - -class DirectoryView final : public GWidget { -public: - DirectoryView(GWidget* parent); - virtual ~DirectoryView() override; - - void open(const String& path); - void reload(); - - Function on_path_change; - Function on_status_message; - - int item_height() const { return 16; } - int item_count() const { return m_directories.size() + m_files.size(); } - -private: - virtual void paint_event(GPaintEvent&) override; - virtual void resize_event(GResizeEvent&) override; - virtual void mousedown_event(GMouseEvent&) override; - - void set_status_message(String&&); - - Rect row_rect(int item_index) const; - - struct Entry { - String name; - size_t size { 0 }; - mode_t mode { 0 }; - - bool is_directory() const { return S_ISDIR(mode); } - }; - - const Entry& entry(int index) const - { - if (index < m_directories.size()) - return m_directories[index]; - return m_files[index - m_directories.size()]; - } - const GraphicsBitmap& icon_for(const Entry&) const; - bool should_show_size_for(const Entry&) const; - - Vector m_files; - Vector m_directories; - - String m_path; - RetainPtr m_directory_icon; - RetainPtr m_file_icon; - RetainPtr m_symlink_icon; - RetainPtr m_socket_icon; - - GScrollBar* m_scrollbar { nullptr }; -}; diff --git a/Applications/FileManager/Makefile b/Applications/FileManager/Makefile index 7bbfcc64d0..ae9cbe0f0f 100644 --- a/Applications/FileManager/Makefile +++ b/Applications/FileManager/Makefile @@ -1,5 +1,6 @@ OBJS = \ - DirectoryView.o \ + DirectoryTableModel.o \ + DirectoryTableView.o \ main.o APP = FileManager diff --git a/Applications/FileManager/main.cpp b/Applications/FileManager/main.cpp index b922ee6b5c..454207e5d8 100644 --- a/Applications/FileManager/main.cpp +++ b/Applications/FileManager/main.cpp @@ -8,7 +8,7 @@ #include #include #include -#include "DirectoryView.h" +#include "DirectoryTableView.h" int main(int argc, char** argv) { @@ -63,20 +63,21 @@ int main(int argc, char** argv) toolbar->add_action(copy_action.copy_ref()); toolbar->add_action(delete_action.copy_ref()); - auto* directory_view = new DirectoryView(widget); + auto* directory_table_view = new DirectoryTableView(widget); auto* statusbar = new GStatusBar(widget); statusbar->set_text("Welcome!"); - directory_view->on_path_change = [window] (const String& new_path) { + directory_table_view->on_path_change = [window] (const String& new_path) { window->set_title(String::format("FileManager: %s", new_path.characters())); }; - directory_view->on_status_message = [statusbar] (String message) { + directory_table_view->on_status_message = [statusbar] (String message) { statusbar->set_text(move(message)); }; - directory_view->open("/"); + directory_table_view->open("/"); + directory_table_view->set_focus(true); window->set_should_exit_app_on_close(true); window->show();