mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 13:32:45 +00:00 
			
		
		
		
	FileManager: Added properties dialog
The user can rename files, change the permissions and view different properties of the file.
This commit is contained in:
		
							parent
							
								
									e630ad5927
								
							
						
					
					
						commit
						b0b523e973
					
				
					 7 changed files with 389 additions and 33 deletions
				
			
		|  | @ -55,9 +55,10 @@ public: | |||
|         callback(*m_item_view); | ||||
|     } | ||||
| 
 | ||||
|     GDirectoryModel& model() { return *m_model; } | ||||
| 
 | ||||
| private: | ||||
|     explicit DirectoryView(GWidget* parent); | ||||
|     GDirectoryModel& model() { return *m_model; } | ||||
|     const GDirectoryModel& model() const { return *m_model; } | ||||
| 
 | ||||
|     void handle_activation(const GModelIndex&); | ||||
|  |  | |||
|  | @ -3,6 +3,7 @@ include ../../Makefile.common | |||
| OBJS = \
 | ||||
|     DirectoryView.o \
 | ||||
|     FileUtils.o \
 | ||||
| 	PropertiesDialog.o \
 | ||||
|     main.o | ||||
| 
 | ||||
| APP = FileManager | ||||
|  |  | |||
							
								
								
									
										258
									
								
								Applications/FileManager/PropertiesDialog.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										258
									
								
								Applications/FileManager/PropertiesDialog.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,258 @@ | |||
| #include "PropertiesDialog.h" | ||||
| #include <AK/StringBuilder.h> | ||||
| #include <LibGUI/GBoxLayout.h> | ||||
| #include <LibGUI/GCheckBox.h> | ||||
| #include <LibGUI/GFilePicker.h> | ||||
| #include <LibGUI/GMessageBox.h> | ||||
| #include <LibGUI/GTabWidget.h> | ||||
| #include <pwd.h> | ||||
| #include <stdio.h> | ||||
| #include <unistd.h> | ||||
| 
 | ||||
| PropertiesDialog::PropertiesDialog(GDirectoryModel& model, String path, bool disable_rename, CObject* parent) | ||||
|     : GDialog(parent) | ||||
|     , m_model(model) | ||||
| { | ||||
|     auto file_path = FileSystemPath(path); | ||||
|     ASSERT(file_path.is_valid()); | ||||
| 
 | ||||
|     auto main_widget = GWidget::construct(); | ||||
|     main_widget->set_layout(make<GBoxLayout>(Orientation::Vertical)); | ||||
|     main_widget->layout()->set_margins({ 4, 4, 4, 4 }); | ||||
|     main_widget->set_fill_with_background_color(true); | ||||
| 
 | ||||
|     set_main_widget(main_widget); | ||||
|     set_rect({ 0, 0, 360, 420 }); | ||||
|     set_resizable(false); | ||||
| 
 | ||||
|     auto tab_widget = GTabWidget::construct(main_widget); | ||||
| 
 | ||||
|     auto general_tab = GWidget::construct(tab_widget.ptr()); | ||||
|     general_tab->set_layout(make<GBoxLayout>(Orientation::Vertical)); | ||||
|     general_tab->layout()->set_margins({ 12, 8, 12, 8 }); | ||||
|     general_tab->layout()->set_spacing(10); | ||||
|     tab_widget->add_widget("General", general_tab); | ||||
| 
 | ||||
|     general_tab->layout()->add_spacer(); | ||||
| 
 | ||||
|     auto file_container = GWidget::construct(general_tab.ptr()); | ||||
|     file_container->set_layout(make<GBoxLayout>(Orientation::Horizontal)); | ||||
|     file_container->set_size_policy(SizePolicy::Fill, SizePolicy::Fixed); | ||||
|     file_container->layout()->set_spacing(20); | ||||
|     file_container->set_preferred_size(0, 34); | ||||
| 
 | ||||
|     m_icon = GLabel::construct(file_container); | ||||
|     m_icon->set_size_policy(SizePolicy::Fixed, SizePolicy::Fixed); | ||||
|     m_icon->set_preferred_size(32, 32); | ||||
| 
 | ||||
|     m_name = file_path.basename(); | ||||
| 
 | ||||
|     m_name_box = GTextBox::construct(file_container); | ||||
|     m_name_box->set_size_policy(SizePolicy::Fill, SizePolicy::Fixed); | ||||
|     m_name_box->set_preferred_size({ 0, 22 }); | ||||
|     m_name_box->set_text(m_name); | ||||
|     m_name_box->on_change = [&, disable_rename]() { | ||||
|         if (disable_rename) { | ||||
|             m_name_box->set_text(m_name); //FIXME: GTextBox does not support set_enabled yet...
 | ||||
|         } else { | ||||
|             m_name_dirty = m_name != m_name_box->text(); | ||||
|             m_apply_button->set_enabled(true); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     set_icon(GraphicsBitmap::load_from_file("/res/icons/16x16/properties.png")); | ||||
|     make_divider(general_tab); | ||||
| 
 | ||||
|     struct stat st; | ||||
|     if (lstat(path.characters(), &st)) { | ||||
|         perror("stat"); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     struct passwd* user_pw = getpwuid(st.st_uid); | ||||
|     struct passwd* group_pw = getpwuid(st.st_gid); | ||||
|     ASSERT(user_pw && group_pw); | ||||
| 
 | ||||
|     m_mode = st.st_mode; | ||||
| 
 | ||||
|     auto properties = Vector<PropertyValuePair>(); | ||||
|     properties.append({ "Type:", get_description(m_mode) }); | ||||
|     properties.append({ "Location:", path }); | ||||
| 
 | ||||
|     if (S_ISLNK(m_mode)) { | ||||
|         char link_destination[PATH_MAX]; | ||||
|         if (readlink(path.characters(), link_destination, sizeof(link_destination))) { | ||||
|             perror("readlink"); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         properties.append({ "Link target:", link_destination }); | ||||
|     } | ||||
| 
 | ||||
|     properties.append({ "Size:", String::format("%zu bytes", st.st_size) }); | ||||
|     properties.append({ "Owner:", String::format("%s (%lu)", user_pw->pw_name, static_cast<u32>(user_pw->pw_uid)) }); | ||||
|     properties.append({ "Group:", String::format("%s (%lu)", group_pw->pw_name, static_cast<u32>(group_pw->pw_uid)) }); | ||||
|     properties.append({ "Created at:", GDirectoryModel::timestamp_string(st.st_ctime) }); | ||||
|     properties.append({ "Last modified:", GDirectoryModel::timestamp_string(st.st_mtime) }); | ||||
| 
 | ||||
|     make_property_value_pairs(properties, general_tab); | ||||
| 
 | ||||
|     make_divider(general_tab); | ||||
| 
 | ||||
|     make_permission_checkboxes(general_tab, { S_IRUSR, S_IWUSR, S_IXUSR }, "Owner:", m_mode); | ||||
|     make_permission_checkboxes(general_tab, { S_IRGRP, S_IWGRP, S_IXGRP }, "Group:", m_mode); | ||||
|     make_permission_checkboxes(general_tab, { S_IROTH, S_IWOTH, S_IXOTH }, "Others:", m_mode); | ||||
| 
 | ||||
|     general_tab->layout()->add_spacer(); | ||||
| 
 | ||||
|     auto button_widget = GWidget::construct(main_widget.ptr()); | ||||
|     button_widget->set_layout(make<GBoxLayout>(Orientation::Horizontal)); | ||||
|     button_widget->set_size_policy(SizePolicy::Fill, SizePolicy::Fixed); | ||||
|     button_widget->set_preferred_size(0, 24); | ||||
|     button_widget->layout()->set_spacing(5); | ||||
| 
 | ||||
|     button_widget->layout()->add_spacer(); | ||||
| 
 | ||||
|     make_button("OK", button_widget)->on_click = [&](auto&) {if(apply_changes()) close(); }; | ||||
|     make_button("Cancel", button_widget)->on_click = [&](auto&) { close(); }; | ||||
| 
 | ||||
|     m_apply_button = make_button("Apply", button_widget); | ||||
|     m_apply_button->on_click = [&](auto&) { apply_changes(); }; | ||||
|     m_apply_button->set_enabled(false); | ||||
| 
 | ||||
|     update(); | ||||
| } | ||||
| 
 | ||||
| PropertiesDialog::~PropertiesDialog() {} | ||||
| 
 | ||||
| void PropertiesDialog::update() | ||||
| { | ||||
|     m_model.update(); | ||||
|     m_icon->set_icon(const_cast<GraphicsBitmap*>(m_model.icon_for_file(m_mode, m_name).bitmap_for_size(32))); | ||||
|     set_title(String::format("Properties of \"%s\"", m_name.characters())); | ||||
| } | ||||
| 
 | ||||
| void PropertiesDialog::permission_changed(mode_t mask, bool set) | ||||
| { | ||||
|     if (set) { | ||||
|         m_mode |= mask; | ||||
|     } else { | ||||
|         m_mode &= ~mask; | ||||
|     } | ||||
| 
 | ||||
|     m_permissions_dirty = true; | ||||
|     m_apply_button->set_enabled(true); | ||||
| } | ||||
| 
 | ||||
| String PropertiesDialog::make_full_path(String name) | ||||
| { | ||||
|     return String::format("%s/%s", m_model.path().characters(), name.characters()); | ||||
| } | ||||
| 
 | ||||
| bool PropertiesDialog::apply_changes() | ||||
| { | ||||
|     if (m_name_dirty) { | ||||
|         String new_name = m_name_box->text(); | ||||
|         String new_file = make_full_path(new_name).characters(); | ||||
| 
 | ||||
|         if (GFilePicker::file_exists(new_file)) { | ||||
|             GMessageBox::show(String::format("A file \"%s\" already exists!", new_name.characters()), "Error", GMessageBox::Type::Error); | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         if (rename(make_full_path(m_name).characters(), new_file.characters())) { | ||||
|             GMessageBox::show(String::format("Could not rename file: %s!", strerror(errno)), "Error", GMessageBox::Type::Error); | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         m_name = new_name; | ||||
|         m_name_dirty = false; | ||||
|         update(); | ||||
|     } | ||||
| 
 | ||||
|     if (m_permissions_dirty) { | ||||
|         if (chmod(make_full_path(m_name).characters(), m_mode)) { | ||||
|             GMessageBox::show(String::format("Could not update permissions: %s!", strerror(errno)), "Error", GMessageBox::Type::Error); | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         m_permissions_dirty = false; | ||||
|     } | ||||
| 
 | ||||
|     update(); | ||||
|     m_apply_button->set_enabled(false); | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| void PropertiesDialog::make_permission_checkboxes(NonnullRefPtr<GWidget>& parent, PermissionMasks masks, String label_string, mode_t mode) | ||||
| { | ||||
|     auto widget = GWidget::construct(parent.ptr()); | ||||
|     widget->set_layout(make<GBoxLayout>(Orientation::Horizontal)); | ||||
|     widget->set_size_policy(SizePolicy::Fill, SizePolicy::Fixed); | ||||
|     widget->set_preferred_size(0, 16); | ||||
|     widget->layout()->set_spacing(10); | ||||
| 
 | ||||
|     auto label = GLabel::construct(label_string, widget); | ||||
|     label->set_text_alignment(TextAlignment::CenterLeft); | ||||
| 
 | ||||
|     auto box_read = GCheckBox::construct("Read", widget); | ||||
|     box_read->set_checked(mode & masks.read); | ||||
|     box_read->on_checked = [&, masks](bool checked) { permission_changed(masks.read, checked); }; | ||||
| 
 | ||||
|     auto box_write = GCheckBox::construct("Write", widget); | ||||
|     box_write->set_checked(mode & masks.write); | ||||
|     box_write->on_checked = [&, masks](bool checked) { permission_changed(masks.write, checked); }; | ||||
| 
 | ||||
|     auto box_execute = GCheckBox::construct("Execute", widget); | ||||
|     box_execute->set_checked(mode & masks.execute); | ||||
|     box_execute->on_checked = [&, masks](bool checked) { permission_changed(masks.execute, checked); }; | ||||
| } | ||||
| 
 | ||||
| void PropertiesDialog::make_property_value_pairs(const Vector<PropertyValuePair>& pairs, NonnullRefPtr<GWidget>& parent) | ||||
| { | ||||
|     int max_width = 0; | ||||
|     Vector<NonnullRefPtr<GLabel>> property_labels; | ||||
| 
 | ||||
|     property_labels.ensure_capacity(pairs.size()); | ||||
|     for (auto pair : pairs) { | ||||
|         auto label_container = GWidget::construct(parent.ptr()); | ||||
|         label_container->set_layout(make<GBoxLayout>(Orientation::Horizontal)); | ||||
|         label_container->set_size_policy(SizePolicy::Fill, SizePolicy::Fixed); | ||||
|         label_container->set_preferred_size(0, 14); | ||||
|         label_container->layout()->set_spacing(12); | ||||
| 
 | ||||
|         auto label_property = GLabel::construct(pair.property, label_container); | ||||
|         label_property->set_text_alignment(TextAlignment::CenterLeft); | ||||
|         label_property->set_size_policy(SizePolicy::Fixed, SizePolicy::Fill); | ||||
| 
 | ||||
|         GLabel::construct(pair.value, label_container)->set_text_alignment(TextAlignment::CenterLeft); | ||||
| 
 | ||||
|         max_width = max(max_width, label_property->font().width(pair.property)); | ||||
|         property_labels.append(label_property); | ||||
|     } | ||||
| 
 | ||||
|     for (auto label : property_labels) | ||||
|         label->set_preferred_size({ max_width, 0 }); | ||||
| } | ||||
| 
 | ||||
| NonnullRefPtr<GButton> PropertiesDialog::make_button(String text, NonnullRefPtr<GWidget>& parent) | ||||
| { | ||||
|     auto button = GButton::construct(text, parent.ptr()); | ||||
|     button->set_size_policy(SizePolicy::Fixed, SizePolicy::Fixed); | ||||
|     button->set_preferred_size(70, 22); | ||||
|     return button; | ||||
| } | ||||
| 
 | ||||
| void PropertiesDialog::make_divider(NonnullRefPtr<GWidget>& parent) | ||||
| { | ||||
|     parent->layout()->add_spacer(); | ||||
| 
 | ||||
|     auto divider = GFrame::construct(parent.ptr()); | ||||
|     divider->set_size_policy(SizePolicy::Fill, SizePolicy::Fixed); | ||||
|     divider->set_preferred_size({ 0, 2 }); | ||||
|     divider->set_frame_shape(FrameShape::HorizontalLine); | ||||
|     divider->set_frame_shadow(FrameShadow::Sunken); | ||||
|     divider->set_frame_thickness(2); | ||||
| 
 | ||||
|     parent->layout()->add_spacer(); | ||||
| } | ||||
							
								
								
									
										71
									
								
								Applications/FileManager/PropertiesDialog.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								Applications/FileManager/PropertiesDialog.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,71 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <AK/FileSystemPath.h> | ||||
| #include <LibCore/CFile.h> | ||||
| #include <LibGUI/GButton.h> | ||||
| #include <LibGUI/GDialog.h> | ||||
| #include <LibGUI/GDirectoryModel.h> | ||||
| #include <LibGUI/GLabel.h> | ||||
| #include <LibGUI/GTextBox.h> | ||||
| 
 | ||||
| class PropertiesDialog final : public GDialog { | ||||
|     C_OBJECT(PropertiesDialog) | ||||
| public: | ||||
|     virtual ~PropertiesDialog() override; | ||||
| 
 | ||||
| private: | ||||
|     explicit PropertiesDialog(GDirectoryModel&, String, bool disable_rename, CObject* parent = nullptr); | ||||
| 
 | ||||
|     struct PropertyValuePair { | ||||
|         String property; | ||||
|         String value; | ||||
|     }; | ||||
| 
 | ||||
|     struct PermissionMasks { | ||||
|         mode_t read; | ||||
|         mode_t write; | ||||
|         mode_t execute; | ||||
|     }; | ||||
| 
 | ||||
|     static const String get_description(const mode_t mode) | ||||
|     { | ||||
|         if (S_ISREG(mode)) | ||||
|             return "File"; | ||||
|         if (S_ISDIR(mode)) | ||||
|             return "Directory"; | ||||
|         if (S_ISLNK(mode)) | ||||
|             return "Symbolic link"; | ||||
|         if (S_ISCHR(mode)) | ||||
|             return "Character device"; | ||||
|         if (S_ISBLK(mode)) | ||||
|             return "Block device"; | ||||
|         if (S_ISFIFO(mode)) | ||||
|             return "FIFO (named pipe)"; | ||||
|         if (S_ISSOCK(mode)) | ||||
|             return "Socket"; | ||||
|         if (mode & S_IXUSR) | ||||
|             return "Executable"; | ||||
| 
 | ||||
|         return "Unknown"; | ||||
|     } | ||||
| 
 | ||||
|     NonnullRefPtr<GButton> make_button(String, NonnullRefPtr<GWidget>&); | ||||
|     void make_divider(NonnullRefPtr<GWidget>&); | ||||
|     void make_property_value_pairs(const Vector<PropertyValuePair>& pairs, NonnullRefPtr<GWidget>& parent); | ||||
|     void make_permission_checkboxes(NonnullRefPtr<GWidget>& parent, PermissionMasks, String label_string, mode_t mode); | ||||
|     void permission_changed(mode_t mask, bool set); | ||||
|     bool apply_changes(); | ||||
|     void update(); | ||||
|     String make_full_path(String name); | ||||
| 
 | ||||
|     GDirectoryModel& m_model; | ||||
|     RefPtr<GButton> m_apply_button; | ||||
|     RefPtr<GTextBox> m_name_box; | ||||
|     RefPtr<GLabel> m_icon; | ||||
|     String m_name; | ||||
|     String m_path; | ||||
|     mode_t m_mode; | ||||
|     int m_row; | ||||
|     bool m_permissions_dirty { false }; | ||||
|     bool m_name_dirty { false }; | ||||
| }; | ||||
|  | @ -1,5 +1,6 @@ | |||
| #include "DirectoryView.h" | ||||
| #include "FileUtils.h" | ||||
| #include "PropertiesDialog.h" | ||||
| #include <AK/FileSystemPath.h> | ||||
| #include <AK/StringBuilder.h> | ||||
| #include <LibCore/CConfigFile.h> | ||||
|  | @ -45,13 +46,13 @@ int main(int argc, char** argv) | |||
| 
 | ||||
|     auto window = GWindow::construct(); | ||||
|     window->set_title("File Manager"); | ||||
|      | ||||
| 
 | ||||
|     auto left = config->read_num_entry("Window", "Left", 150); | ||||
|     auto top = config->read_num_entry("Window", "Top", 75); | ||||
|     auto width = config->read_num_entry("Window", "Width", 640); | ||||
|     auto heigth = config->read_num_entry("Window", "Heigth", 480); | ||||
|     window->set_rect( {left, top, width, heigth} ); | ||||
|      | ||||
|     window->set_rect({ left, top, width, heigth }); | ||||
| 
 | ||||
|     auto widget = GWidget::construct(); | ||||
|     widget->set_layout(make<GBoxLayout>(Orientation::Vertical)); | ||||
|     widget->layout()->set_spacing(0); | ||||
|  | @ -154,7 +155,7 @@ int main(int argc, char** argv) | |||
|     view_type_action_group->set_exclusive(true); | ||||
|     view_type_action_group->add_action(*view_as_table_action); | ||||
|     view_type_action_group->add_action(*view_as_icons_action); | ||||
|      | ||||
| 
 | ||||
|     auto selected_file_paths = [&] { | ||||
|         Vector<String> paths; | ||||
|         auto& view = directory_view->current_view(); | ||||
|  | @ -211,9 +212,22 @@ int main(int argc, char** argv) | |||
|     }; | ||||
| 
 | ||||
|     auto properties_action | ||||
|         = GAction::create("Properties...", { Mod_Alt, Key_Return }, GraphicsBitmap::load_from_file("/res/icons/16x16/properties.png"), [](auto&) {}); | ||||
|         = GAction::create("Properties...", { Mod_Alt, Key_Return }, GraphicsBitmap::load_from_file("/res/icons/16x16/properties.png"), [&](auto&) { | ||||
|               auto& model = directory_view->model(); | ||||
|               auto selected = selected_file_paths(); | ||||
| 
 | ||||
|     enum class ConfirmBeforeDelete { No, Yes }; | ||||
|               RefPtr<PropertiesDialog> properties; | ||||
|               if (selected.is_empty()) { | ||||
|                   properties = PropertiesDialog::construct(model, directory_view->path(), true, window); | ||||
|               } else { | ||||
|                   properties = PropertiesDialog::construct(model, selected.first(), false, window); | ||||
|               } | ||||
| 
 | ||||
|               properties->exec(); | ||||
|           }); | ||||
| 
 | ||||
|     enum class ConfirmBeforeDelete { No, | ||||
|         Yes }; | ||||
| 
 | ||||
|     auto do_delete = [&](ConfirmBeforeDelete confirm) { | ||||
|         auto paths = selected_file_paths(); | ||||
|  | @ -423,6 +437,6 @@ int main(int argc, char** argv) | |||
| 
 | ||||
|         return GWindow::CloseRequestDecision::Close; | ||||
|     }; | ||||
|      | ||||
| 
 | ||||
|     return app.exec(); | ||||
| } | ||||
|  |  | |||
|  | @ -9,7 +9,6 @@ | |||
| #include <grp.h> | ||||
| #include <pwd.h> | ||||
| #include <stdio.h> | ||||
| #include <time.h> | ||||
| #include <unistd.h> | ||||
| 
 | ||||
| static HashMap<String, RefPtr<GraphicsBitmap>> s_thumbnail_cache; | ||||
|  | @ -154,20 +153,28 @@ bool GDirectoryModel::fetch_thumbnail_for(const Entry& entry) | |||
|     return false; | ||||
| } | ||||
| 
 | ||||
| GIcon GDirectoryModel::icon_for_file(const mode_t mode, const String name) const | ||||
| { | ||||
|     if (S_ISDIR(mode)) | ||||
|         return m_directory_icon; | ||||
|     if (S_ISLNK(mode)) | ||||
|         return m_symlink_icon; | ||||
|     if (S_ISSOCK(mode)) | ||||
|         return m_socket_icon; | ||||
|     if (mode & S_IXUSR) | ||||
|         return m_executable_icon; | ||||
|     if (name.to_lowercase().ends_with(".wav")) | ||||
|         return m_filetype_sound_icon; | ||||
|     if (name.to_lowercase().ends_with(".html")) | ||||
|         return m_filetype_html_icon; | ||||
|     if (name.to_lowercase().ends_with(".png")) { | ||||
|         return m_filetype_image_icon; | ||||
|     } | ||||
|     return m_file_icon; | ||||
| } | ||||
| 
 | ||||
| GIcon GDirectoryModel::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; | ||||
|     if (entry.mode & S_IXUSR) | ||||
|         return m_executable_icon; | ||||
|     if (entry.name.to_lowercase().ends_with(".wav")) | ||||
|         return m_filetype_sound_icon; | ||||
|     if (entry.name.to_lowercase().ends_with(".html")) | ||||
|         return m_filetype_html_icon; | ||||
|     if (entry.name.to_lowercase().ends_with(".png")) { | ||||
|         if (!entry.thumbnail) { | ||||
|             if (!const_cast<GDirectoryModel*>(this)->fetch_thumbnail_for(entry)) | ||||
|  | @ -175,19 +182,8 @@ GIcon GDirectoryModel::icon_for(const Entry& entry) const | |||
|         } | ||||
|         return GIcon(m_filetype_image_icon.bitmap_for_size(16), *entry.thumbnail); | ||||
|     } | ||||
|     return m_file_icon; | ||||
| } | ||||
| 
 | ||||
| static String timestamp_string(time_t timestamp) | ||||
| { | ||||
|     auto* tm = localtime(×tamp); | ||||
|     return String::format("%4u-%02u-%02u %02u:%02u:%02u", | ||||
|         tm->tm_year + 1900, | ||||
|         tm->tm_mon + 1, | ||||
|         tm->tm_mday, | ||||
|         tm->tm_hour, | ||||
|         tm->tm_min, | ||||
|         tm->tm_sec); | ||||
|     return icon_for_file(entry.mode, entry.name); | ||||
| } | ||||
| 
 | ||||
| static String permission_string(mode_t mode) | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ | |||
| #include <LibCore/CNotifier.h> | ||||
| #include <LibGUI/GModel.h> | ||||
| #include <sys/stat.h> | ||||
| #include <time.h> | ||||
| 
 | ||||
| class GDirectoryModel final : public GModel | ||||
|     , public Weakable<GDirectoryModel> { | ||||
|  | @ -58,6 +59,20 @@ public: | |||
|         return m_files[index - m_directories.size()]; | ||||
|     } | ||||
| 
 | ||||
|     GIcon icon_for_file(const mode_t mode, const String name) const; | ||||
| 
 | ||||
|     static String timestamp_string(time_t timestamp) | ||||
|     { | ||||
|         auto* tm = localtime(×tamp); | ||||
|         return String::format("%4u-%02u-%02u %02u:%02u:%02u", | ||||
|             tm->tm_year + 1900, | ||||
|             tm->tm_mon + 1, | ||||
|             tm->tm_mday, | ||||
|             tm->tm_hour, | ||||
|             tm->tm_min, | ||||
|             tm->tm_sec); | ||||
|     } | ||||
| 
 | ||||
| private: | ||||
|     GDirectoryModel(); | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Till Mayer
						Till Mayer