Serenity Operating System
at portability 280 lines 11 kB view raw
1/* 2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright notice, this 9 * list of conditions and the following disclaimer. 10 * 11 * 2. Redistributions in binary form must reproduce the above copyright notice, 12 * this list of conditions and the following disclaimer in the documentation 13 * and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27#include "PropertiesDialog.h" 28#include <AK/StringBuilder.h> 29#include <LibGUI/BoxLayout.h> 30#include <LibGUI/CheckBox.h> 31#include <LibGUI/FilePicker.h> 32#include <LibGUI/MessageBox.h> 33#include <LibGUI/TabWidget.h> 34#include <limits.h> 35#include <pwd.h> 36#include <stdio.h> 37#include <unistd.h> 38 39PropertiesDialog::PropertiesDialog(GUI::FileSystemModel& model, String path, bool disable_rename, Core::Object* parent) 40 : Dialog(parent) 41 , m_model(model) 42{ 43 auto file_path = FileSystemPath(path); 44 ASSERT(file_path.is_valid()); 45 46 auto main_widget = GUI::Widget::construct(); 47 main_widget->set_layout(make<GUI::VerticalBoxLayout>()); 48 main_widget->layout()->set_margins({ 4, 4, 4, 4 }); 49 main_widget->set_fill_with_background_color(true); 50 51 set_main_widget(main_widget); 52 set_rect({ 0, 0, 360, 420 }); 53 set_resizable(false); 54 55 auto tab_widget = main_widget->add<GUI::TabWidget>(); 56 57 auto general_tab = tab_widget->add_tab<GUI::Widget>("General"); 58 general_tab->set_layout(make<GUI::VerticalBoxLayout>()); 59 general_tab->layout()->set_margins({ 12, 8, 12, 8 }); 60 general_tab->layout()->set_spacing(10); 61 62 general_tab->layout()->add_spacer(); 63 64 auto file_container = general_tab->add<GUI::Widget>(); 65 file_container->set_layout(make<GUI::HorizontalBoxLayout>()); 66 file_container->set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fixed); 67 file_container->layout()->set_spacing(20); 68 file_container->set_preferred_size(0, 34); 69 70 m_icon = file_container->add<GUI::Label>(); 71 m_icon->set_size_policy(GUI::SizePolicy::Fixed, GUI::SizePolicy::Fixed); 72 m_icon->set_preferred_size(32, 32); 73 74 m_name = file_path.basename(); 75 76 m_name_box = file_container->add<GUI::TextBox>(); 77 m_name_box->set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fixed); 78 m_name_box->set_preferred_size({ 0, 22 }); 79 m_name_box->set_text(m_name); 80 m_name_box->on_change = [&, disable_rename]() { 81 if (disable_rename) { 82 m_name_box->set_text(m_name); //FIXME: GTextBox does not support set_enabled yet... 83 } else { 84 m_name_dirty = m_name != m_name_box->text(); 85 m_apply_button->set_enabled(true); 86 } 87 }; 88 89 set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/properties.png")); 90 make_divider(general_tab); 91 92 struct stat st; 93 if (lstat(path.characters(), &st)) { 94 perror("stat"); 95 return; 96 } 97 98 struct passwd* user_pw = getpwuid(st.st_uid); 99 struct passwd* group_pw = getpwuid(st.st_gid); 100 ASSERT(user_pw && group_pw); 101 102 m_mode = st.st_mode; 103 104 auto properties = Vector<PropertyValuePair>(); 105 properties.append({ "Type:", get_description(m_mode) }); 106 properties.append({ "Location:", path }); 107 108 if (S_ISLNK(m_mode)) { 109 char link_destination[PATH_MAX]; 110 if (readlink(path.characters(), link_destination, sizeof(link_destination))) { 111 perror("readlink"); 112 return; 113 } 114 115 properties.append({ "Link target:", link_destination }); 116 } 117 118 properties.append({ "Size:", String::format("%zu bytes", st.st_size) }); 119 properties.append({ "Owner:", String::format("%s (%lu)", user_pw->pw_name, static_cast<u32>(user_pw->pw_uid)) }); 120 properties.append({ "Group:", String::format("%s (%lu)", group_pw->pw_name, static_cast<u32>(group_pw->pw_uid)) }); 121 properties.append({ "Created at:", GUI::FileSystemModel::timestamp_string(st.st_ctime) }); 122 properties.append({ "Last modified:", GUI::FileSystemModel::timestamp_string(st.st_mtime) }); 123 124 make_property_value_pairs(properties, general_tab); 125 126 make_divider(general_tab); 127 128 make_permission_checkboxes(general_tab, { S_IRUSR, S_IWUSR, S_IXUSR }, "Owner:", m_mode); 129 make_permission_checkboxes(general_tab, { S_IRGRP, S_IWGRP, S_IXGRP }, "Group:", m_mode); 130 make_permission_checkboxes(general_tab, { S_IROTH, S_IWOTH, S_IXOTH }, "Others:", m_mode); 131 132 general_tab->layout()->add_spacer(); 133 134 auto button_widget = main_widget->add<GUI::Widget>(); 135 button_widget->set_layout(make<GUI::HorizontalBoxLayout>()); 136 button_widget->set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fixed); 137 button_widget->set_preferred_size(0, 24); 138 button_widget->layout()->set_spacing(5); 139 140 button_widget->layout()->add_spacer(); 141 142 make_button("OK", button_widget)->on_click = [&](auto&) {if(apply_changes()) close(); }; 143 make_button("Cancel", button_widget)->on_click = [&](auto&) { close(); }; 144 145 m_apply_button = make_button("Apply", button_widget); 146 m_apply_button->on_click = [&](auto&) { apply_changes(); }; 147 m_apply_button->set_enabled(false); 148 149 update(); 150} 151 152PropertiesDialog::~PropertiesDialog() {} 153 154void PropertiesDialog::update() 155{ 156 m_icon->set_icon(const_cast<Gfx::Bitmap*>(m_model.icon_for_file(m_mode, m_name).bitmap_for_size(32))); 157 set_title(String::format("Properties of \"%s\"", m_name.characters())); 158} 159 160void PropertiesDialog::permission_changed(mode_t mask, bool set) 161{ 162 if (set) { 163 m_mode |= mask; 164 } else { 165 m_mode &= ~mask; 166 } 167 168 m_permissions_dirty = true; 169 m_apply_button->set_enabled(true); 170} 171 172String PropertiesDialog::make_full_path(String name) 173{ 174 return String::format("%s/%s", m_model.root_path().characters(), name.characters()); 175} 176 177bool PropertiesDialog::apply_changes() 178{ 179 if (m_name_dirty) { 180 String new_name = m_name_box->text(); 181 String new_file = make_full_path(new_name).characters(); 182 183 if (GUI::FilePicker::file_exists(new_file)) { 184 GUI::MessageBox::show(String::format("A file \"%s\" already exists!", new_name.characters()), "Error", GUI::MessageBox::Type::Error); 185 return false; 186 } 187 188 if (rename(make_full_path(m_name).characters(), new_file.characters())) { 189 GUI::MessageBox::show(String::format("Could not rename file: %s!", strerror(errno)), "Error", GUI::MessageBox::Type::Error); 190 return false; 191 } 192 193 m_name = new_name; 194 m_name_dirty = false; 195 update(); 196 } 197 198 if (m_permissions_dirty) { 199 if (chmod(make_full_path(m_name).characters(), m_mode)) { 200 GUI::MessageBox::show(String::format("Could not update permissions: %s!", strerror(errno)), "Error", GUI::MessageBox::Type::Error); 201 return false; 202 } 203 204 m_permissions_dirty = false; 205 } 206 207 update(); 208 m_apply_button->set_enabled(false); 209 return true; 210} 211 212void PropertiesDialog::make_permission_checkboxes(NonnullRefPtr<GUI::Widget>& parent, PermissionMasks masks, String label_string, mode_t mode) 213{ 214 auto widget = parent->add<GUI::Widget>(); 215 widget->set_layout(make<GUI::HorizontalBoxLayout>()); 216 widget->set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fixed); 217 widget->set_preferred_size(0, 16); 218 widget->layout()->set_spacing(10); 219 220 auto label = widget->add<GUI::Label>(label_string); 221 label->set_text_alignment(Gfx::TextAlignment::CenterLeft); 222 223 auto box_read = widget->add<GUI::CheckBox>("Read"); 224 box_read->set_checked(mode & masks.read); 225 box_read->on_checked = [&, masks](bool checked) { permission_changed(masks.read, checked); }; 226 227 auto box_write = widget->add<GUI::CheckBox>("Write"); 228 box_write->set_checked(mode & masks.write); 229 box_write->on_checked = [&, masks](bool checked) { permission_changed(masks.write, checked); }; 230 231 auto box_execute = widget->add<GUI::CheckBox>("Execute"); 232 box_execute->set_checked(mode & masks.execute); 233 box_execute->on_checked = [&, masks](bool checked) { permission_changed(masks.execute, checked); }; 234} 235 236void PropertiesDialog::make_property_value_pairs(const Vector<PropertyValuePair>& pairs, NonnullRefPtr<GUI::Widget>& parent) 237{ 238 int max_width = 0; 239 Vector<NonnullRefPtr<GUI::Label>> property_labels; 240 241 property_labels.ensure_capacity(pairs.size()); 242 for (auto pair : pairs) { 243 auto label_container = parent->add<GUI::Widget>(); 244 label_container->set_layout(make<GUI::HorizontalBoxLayout>()); 245 label_container->set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fixed); 246 label_container->set_preferred_size(0, 14); 247 label_container->layout()->set_spacing(12); 248 249 auto label_property = label_container->add<GUI::Label>(pair.property); 250 label_property->set_text_alignment(Gfx::TextAlignment::CenterLeft); 251 label_property->set_size_policy(GUI::SizePolicy::Fixed, GUI::SizePolicy::Fill); 252 253 label_container->add<GUI::Label>(pair.value)->set_text_alignment(Gfx::TextAlignment::CenterLeft); 254 255 max_width = max(max_width, label_property->font().width(pair.property)); 256 property_labels.append(label_property); 257 } 258 259 for (auto label : property_labels) 260 label->set_preferred_size({ max_width, 0 }); 261} 262 263NonnullRefPtr<GUI::Button> PropertiesDialog::make_button(String text, NonnullRefPtr<GUI::Widget>& parent) 264{ 265 auto button = parent->add<GUI::Button>(text); 266 button->set_size_policy(GUI::SizePolicy::Fixed, GUI::SizePolicy::Fixed); 267 button->set_preferred_size(70, 22); 268 return button; 269} 270 271void PropertiesDialog::make_divider(NonnullRefPtr<GUI::Widget>& parent) 272{ 273 parent->layout()->add_spacer(); 274 275 auto divider = parent->add<GUI::Frame>(); 276 divider->set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fixed); 277 divider->set_preferred_size({ 0, 2 }); 278 279 parent->layout()->add_spacer(); 280}