Serenity Operating System
at portability 675 lines 27 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 "DirectoryView.h" 28#include "FileUtils.h" 29#include "PropertiesDialog.h" 30#include <AK/FileSystemPath.h> 31#include <AK/StringBuilder.h> 32#include <AK/URL.h> 33#include <LibCore/ConfigFile.h> 34#include <LibCore/MimeData.h> 35#include <LibCore/UserInfo.h> 36#include <LibGUI/AboutDialog.h> 37#include <LibGUI/Action.h> 38#include <LibGUI/ActionGroup.h> 39#include <LibGUI/Application.h> 40#include <LibGUI/BoxLayout.h> 41#include <LibGUI/Clipboard.h> 42#include <LibGUI/FileSystemModel.h> 43#include <LibGUI/InputBox.h> 44#include <LibGUI/Label.h> 45#include <LibGUI/Menu.h> 46#include <LibGUI/MenuBar.h> 47#include <LibGUI/MessageBox.h> 48#include <LibGUI/ProgressBar.h> 49#include <LibGUI/Splitter.h> 50#include <LibGUI/StatusBar.h> 51#include <LibGUI/TextEditor.h> 52#include <LibGUI/ToolBar.h> 53#include <LibGUI/TreeView.h> 54#include <LibGUI/Widget.h> 55#include <LibGUI/Window.h> 56#include <signal.h> 57#include <stdio.h> 58#include <unistd.h> 59 60int main(int argc, char** argv) 61{ 62 if (pledge("stdio thread shared_buffer accept unix cpath rpath wpath fattr proc exec", nullptr) < 0) { 63 perror("pledge"); 64 return 1; 65 } 66 67 struct sigaction act; 68 memset(&act, 0, sizeof(act)); 69 act.sa_flags = SA_NOCLDWAIT; 70 act.sa_handler = SIG_IGN; 71 int rc = sigaction(SIGCHLD, &act, nullptr); 72 if (rc < 0) { 73 perror("sigaction"); 74 return 1; 75 } 76 77 RefPtr<Core::ConfigFile> config = Core::ConfigFile::get_for_app("FileManager"); 78 79 GUI::Application app(argc, argv); 80 81 if (pledge("stdio thread shared_buffer accept cpath rpath wpath fattr proc exec", nullptr) < 0) { 82 perror("pledge"); 83 return 1; 84 } 85 86 auto window = GUI::Window::construct(); 87 window->set_title("File Manager"); 88 89 auto left = config->read_num_entry("Window", "Left", 150); 90 auto top = config->read_num_entry("Window", "Top", 75); 91 auto width = config->read_num_entry("Window", "Width", 640); 92 auto heigth = config->read_num_entry("Window", "Heigth", 480); 93 window->set_rect({ left, top, width, heigth }); 94 95 auto widget = GUI::Widget::construct(); 96 widget->set_layout(make<GUI::VerticalBoxLayout>()); 97 widget->layout()->set_spacing(0); 98 99 auto main_toolbar = widget->add<GUI::ToolBar>(); 100 auto location_toolbar = widget->add<GUI::ToolBar>(); 101 location_toolbar->layout()->set_margins({ 6, 3, 6, 3 }); 102 location_toolbar->set_preferred_size(0, 25); 103 104 auto location_label = location_toolbar->add<GUI::Label>("Location: "); 105 location_label->size_to_fit(); 106 107 auto location_textbox = location_toolbar->add<GUI::TextBox>(); 108 109 auto splitter = widget->add<GUI::HorizontalSplitter>(); 110 auto tree_view = splitter->add<GUI::TreeView>(); 111 auto directories_model = GUI::FileSystemModel::create("/", GUI::FileSystemModel::Mode::DirectoriesOnly); 112 tree_view->set_model(directories_model); 113 tree_view->set_column_hidden(GUI::FileSystemModel::Column::Icon, true); 114 tree_view->set_column_hidden(GUI::FileSystemModel::Column::Size, true); 115 tree_view->set_column_hidden(GUI::FileSystemModel::Column::Owner, true); 116 tree_view->set_column_hidden(GUI::FileSystemModel::Column::Group, true); 117 tree_view->set_column_hidden(GUI::FileSystemModel::Column::Permissions, true); 118 tree_view->set_column_hidden(GUI::FileSystemModel::Column::ModificationTime, true); 119 tree_view->set_column_hidden(GUI::FileSystemModel::Column::Inode, true); 120 tree_view->set_column_hidden(GUI::FileSystemModel::Column::SymlinkTarget, true); 121 tree_view->set_size_policy(GUI::SizePolicy::Fixed, GUI::SizePolicy::Fill); 122 tree_view->set_preferred_size(150, 0); 123 auto directory_view = splitter->add<DirectoryView>(); 124 125 auto statusbar = widget->add<GUI::StatusBar>(); 126 127 auto progressbar = statusbar->add<GUI::ProgressBar>(); 128 progressbar->set_caption("Generating thumbnails: "); 129 progressbar->set_format(GUI::ProgressBar::Format::ValueSlashMax); 130 progressbar->set_visible(false); 131 progressbar->set_frame_shape(Gfx::FrameShape::Panel); 132 progressbar->set_frame_shadow(Gfx::FrameShadow::Sunken); 133 progressbar->set_frame_thickness(1); 134 135 location_textbox->on_return_pressed = [&] { 136 directory_view->open(location_textbox->text()); 137 }; 138 139 auto refresh_tree_view = [&] { 140 directories_model->update(); 141 142 auto current_path = directory_view->path(); 143 144 struct stat st; 145 // If the directory no longer exists, we find a parent that does. 146 while (stat(current_path.characters(), &st) != 0) { 147 directory_view->open_parent_directory(); 148 current_path = directory_view->path(); 149 if (current_path == directories_model->root_path()) { 150 break; 151 } 152 } 153 154 // Reselect the existing folder in the tree. 155 auto new_index = directories_model->index(current_path, GUI::FileSystemModel::Column::Name); 156 tree_view->selection().set(new_index); 157 tree_view->scroll_into_view(new_index, Orientation::Vertical); 158 tree_view->update(); 159 160 directory_view->refresh(); 161 }; 162 163 auto directory_context_menu = GUI::Menu::construct("Directory View Directory"); 164 auto file_context_menu = GUI::Menu::construct("Directory View File"); 165 auto directory_view_context_menu = GUI::Menu::construct("Directory View"); 166 auto tree_view_directory_context_menu = GUI::Menu::construct("Tree View Directory"); 167 auto tree_view_context_menu = GUI::Menu::construct("Tree View"); 168 169 auto open_parent_directory_action = GUI::Action::create("Open parent directory", { Mod_Alt, Key_Up }, Gfx::Bitmap::load_from_file("/res/icons/16x16/open-parent-directory.png"), [&](const GUI::Action&) { 170 directory_view->open_parent_directory(); 171 }); 172 173 auto mkdir_action = GUI::Action::create("New directory...", { Mod_Ctrl | Mod_Shift, Key_N }, Gfx::Bitmap::load_from_file("/res/icons/16x16/mkdir.png"), [&](const GUI::Action&) { 174 auto input_box = window->add<GUI::InputBox>("Enter name:", "New directory"); 175 if (input_box->exec() == GUI::InputBox::ExecOK && !input_box->text_value().is_empty()) { 176 auto new_dir_path = canonicalized_path( 177 String::format("%s/%s", 178 directory_view->path().characters(), 179 input_box->text_value().characters())); 180 int rc = mkdir(new_dir_path.characters(), 0777); 181 if (rc < 0) { 182 GUI::MessageBox::show(String::format("mkdir(\"%s\") failed: %s", new_dir_path.characters(), strerror(errno)), "Error", GUI::MessageBox::Type::Error, GUI::MessageBox::InputType::OK, window); 183 } else { 184 refresh_tree_view(); 185 } 186 } 187 }); 188 189 RefPtr<GUI::Action> view_as_table_action; 190 RefPtr<GUI::Action> view_as_icons_action; 191 RefPtr<GUI::Action> view_as_columns_action; 192 193 view_as_table_action = GUI::Action::create( 194 "Table view", { Mod_Ctrl, KeyCode::Key_L }, Gfx::Bitmap::load_from_file("/res/icons/16x16/table-view.png"), [&](const GUI::Action&) { 195 directory_view->set_view_mode(DirectoryView::ViewMode::List); 196 view_as_table_action->set_checked(true); 197 198 config->write_entry("DirectoryView", "ViewMode", "List"); 199 config->sync(); 200 }, 201 window); 202 view_as_table_action->set_checkable(true); 203 204 view_as_icons_action = GUI::Action::create( 205 "Icon view", { Mod_Ctrl, KeyCode::Key_I }, Gfx::Bitmap::load_from_file("/res/icons/16x16/icon-view.png"), [&](const GUI::Action&) { 206 directory_view->set_view_mode(DirectoryView::ViewMode::Icon); 207 view_as_icons_action->set_checked(true); 208 209 config->write_entry("DirectoryView", "ViewMode", "Icon"); 210 config->sync(); 211 }, 212 window); 213 view_as_icons_action->set_checkable(true); 214 215 view_as_columns_action = GUI::Action::create( 216 "Columns view", Gfx::Bitmap::load_from_file("/res/icons/16x16/columns-view.png"), [&](const GUI::Action&) { 217 directory_view->set_view_mode(DirectoryView::ViewMode::Columns); 218 view_as_columns_action->set_checked(true); 219 220 config->write_entry("DirectoryView", "ViewMode", "Columns"); 221 config->sync(); 222 }, 223 window); 224 view_as_columns_action->set_checkable(true); 225 226 auto view_type_action_group = make<GUI::ActionGroup>(); 227 view_type_action_group->set_exclusive(true); 228 view_type_action_group->add_action(*view_as_table_action); 229 view_type_action_group->add_action(*view_as_icons_action); 230 view_type_action_group->add_action(*view_as_columns_action); 231 232 auto selected_file_paths = [&] { 233 Vector<String> paths; 234 auto& view = directory_view->current_view(); 235 auto& model = *view.model(); 236 view.selection().for_each_index([&](const GUI::ModelIndex& index) { 237 auto parent_index = model.parent_index(index); 238 auto name_index = model.index(index.row(), GUI::FileSystemModel::Column::Name, parent_index); 239 auto path = model.data(name_index, GUI::Model::Role::Custom).to_string(); 240 paths.append(path); 241 }); 242 return paths; 243 }; 244 245 auto tree_view_selected_file_paths = [&] { 246 Vector<String> paths; 247 auto& view = tree_view; 248 view->selection().for_each_index([&](const GUI::ModelIndex& index) { 249 paths.append(directories_model->full_path(index)); 250 }); 251 return paths; 252 }; 253 254 auto select_all_action = GUI::Action::create("Select all", { Mod_Ctrl, KeyCode::Key_A }, [&](const GUI::Action&) { 255 directory_view->current_view().select_all(); 256 }); 257 258 auto copy_action = GUI::CommonActions::make_copy_action( 259 [&](const GUI::Action& action) { 260 Vector<String> paths; 261 if (action.activator() == directory_context_menu || directory_view->active_widget()->is_focused()) { 262 paths = selected_file_paths(); 263 } else { 264 paths = tree_view_selected_file_paths(); 265 } 266 if (paths.is_empty()) 267 return; 268 StringBuilder copy_text; 269 for (auto& path : paths) { 270 copy_text.appendf("%s\n", path.characters()); 271 } 272 GUI::Clipboard::the().set_data(copy_text.build(), "file-list"); 273 }, 274 window); 275 copy_action->set_enabled(false); 276 277 auto paste_action = GUI::CommonActions::make_paste_action( 278 [&](const GUI::Action&) { 279 auto data_and_type = GUI::Clipboard::the().data_and_type(); 280 if (data_and_type.type != "file-list") { 281 dbg() << "Cannot paste clipboard type " << data_and_type.type; 282 return; 283 } 284 auto copied_lines = data_and_type.data.split('\n'); 285 if (copied_lines.is_empty()) { 286 dbg() << "No files to paste"; 287 return; 288 } 289 for (auto& current_path : copied_lines) { 290 if (current_path.is_empty()) 291 continue; 292 auto current_directory = directory_view->path(); 293 auto new_path = String::format("%s/%s", 294 current_directory.characters(), 295 FileSystemPath(current_path).basename().characters()); 296 if (!FileUtils::copy_file_or_directory(current_path, new_path)) { 297 auto error_message = String::format("Could not paste %s.", 298 current_path.characters()); 299 GUI::MessageBox::show(error_message, "File Manager", GUI::MessageBox::Type::Error); 300 } else { 301 refresh_tree_view(); 302 } 303 } 304 }, 305 window); 306 paste_action->set_enabled(GUI::Clipboard::the().type() == "file-list"); 307 308 GUI::Clipboard::the().on_content_change = [&](const String& data_type) { 309 paste_action->set_enabled(data_type == "file-list"); 310 }; 311 312 auto properties_action 313 = GUI::Action::create( 314 "Properties...", { Mod_Alt, Key_Return }, Gfx::Bitmap::load_from_file("/res/icons/16x16/properties.png"), [&](const GUI::Action& action) { 315 auto& model = directory_view->model(); 316 String path; 317 Vector<String> selected; 318 if (action.activator() == directory_context_menu || directory_view->active_widget()->is_focused()) { 319 path = directory_view->path(); 320 selected = selected_file_paths(); 321 } else { 322 path = directories_model->full_path(tree_view->selection().first()); 323 selected = tree_view_selected_file_paths(); 324 } 325 326 RefPtr<PropertiesDialog> properties; 327 if (selected.is_empty()) { 328 properties = window->add<PropertiesDialog>(model, path, true); 329 } else { 330 properties = window->add<PropertiesDialog>(model, selected.first(), false); 331 } 332 333 properties->exec(); 334 }, 335 window); 336 337 enum class ConfirmBeforeDelete { 338 No, 339 Yes 340 }; 341 342 auto do_delete = [&](ConfirmBeforeDelete confirm, const GUI::Action& action) { 343 Vector<String> paths; 344 if (action.activator() == directory_context_menu || directory_view->active_widget()->is_focused()) { 345 paths = selected_file_paths(); 346 } else { 347 paths = tree_view_selected_file_paths(); 348 } 349 if (paths.is_empty()) 350 return; 351 { 352 String message; 353 if (paths.size() == 1) { 354 message = String::format("Really delete %s?", FileSystemPath(paths[0]).basename().characters()); 355 } else { 356 message = String::format("Really delete %d files?", paths.size()); 357 } 358 359 if (confirm == ConfirmBeforeDelete::Yes) { 360 auto result = GUI::MessageBox::show( 361 message, 362 "Confirm deletion", 363 GUI::MessageBox::Type::Warning, 364 GUI::MessageBox::InputType::OKCancel, 365 window); 366 if (result == GUI::MessageBox::ExecCancel) 367 return; 368 } 369 } 370 371 for (auto& path : paths) { 372 struct stat st; 373 if (lstat(path.characters(), &st)) { 374 GUI::MessageBox::show( 375 String::format("lstat(%s) failed: %s", path.characters(), strerror(errno)), 376 "Delete failed", 377 GUI::MessageBox::Type::Error, 378 GUI::MessageBox::InputType::OK, 379 window); 380 break; 381 } else { 382 refresh_tree_view(); 383 } 384 385 if (S_ISDIR(st.st_mode)) { 386 String error_path; 387 int error = FileUtils::delete_directory(path, error_path); 388 389 if (error) { 390 GUI::MessageBox::show( 391 String::format("Failed to delete directory \"%s\": %s", error_path.characters(), strerror(error)), 392 "Delete failed", 393 GUI::MessageBox::Type::Error, 394 GUI::MessageBox::InputType::OK, 395 window); 396 break; 397 } else { 398 refresh_tree_view(); 399 } 400 } else if (unlink(path.characters()) < 0) { 401 int saved_errno = errno; 402 GUI::MessageBox::show( 403 String::format("unlink(%s) failed: %s", path.characters(), strerror(saved_errno)), 404 "Delete failed", 405 GUI::MessageBox::Type::Error, 406 GUI::MessageBox::InputType::OK, 407 window); 408 break; 409 } 410 } 411 }; 412 413 auto force_delete_action = GUI::Action::create( 414 "Delete without confirmation", { Mod_Shift, Key_Delete }, [&](const GUI::Action& action) { 415 do_delete(ConfirmBeforeDelete::No, action); 416 }, 417 window); 418 419 auto delete_action = GUI::CommonActions::make_delete_action( 420 [&](const GUI::Action& action) { 421 do_delete(ConfirmBeforeDelete::Yes, action); 422 }, 423 window); 424 delete_action->set_enabled(false); 425 426 auto go_back_action = GUI::CommonActions::make_go_back_action( 427 [&](auto&) { 428 directory_view->open_previous_directory(); 429 }, 430 window); 431 432 auto go_forward_action = GUI::CommonActions::make_go_forward_action( 433 [&](auto&) { 434 directory_view->open_next_directory(); 435 }, 436 window); 437 438 auto go_home_action = GUI::CommonActions::make_go_home_action( 439 [&](auto&) { 440 directory_view->open(get_current_user_home_path()); 441 }, 442 window); 443 444 auto menubar = make<GUI::MenuBar>(); 445 446 auto app_menu = GUI::Menu::construct("File Manager"); 447 app_menu->add_action(mkdir_action); 448 app_menu->add_action(copy_action); 449 app_menu->add_action(paste_action); 450 app_menu->add_action(delete_action); 451 app_menu->add_separator(); 452 app_menu->add_action(GUI::CommonActions::make_quit_action([](auto&) { 453 GUI::Application::the().quit(0); 454 })); 455 menubar->add_menu(move(app_menu)); 456 457 auto view_menu = GUI::Menu::construct("View"); 458 view_menu->add_action(*view_as_icons_action); 459 view_menu->add_action(*view_as_table_action); 460 view_menu->add_action(*view_as_columns_action); 461 menubar->add_menu(move(view_menu)); 462 463 auto go_menu = GUI::Menu::construct("Go"); 464 go_menu->add_action(go_back_action); 465 go_menu->add_action(go_forward_action); 466 go_menu->add_action(open_parent_directory_action); 467 go_menu->add_action(go_home_action); 468 menubar->add_menu(move(go_menu)); 469 470 auto help_menu = GUI::Menu::construct("Help"); 471 help_menu->add_action(GUI::Action::create("About", [&](const GUI::Action&) { 472 GUI::AboutDialog::show("File Manager", Gfx::Bitmap::load_from_file("/res/icons/32x32/filetype-folder.png"), window); 473 })); 474 menubar->add_menu(move(help_menu)); 475 476 app.set_menubar(move(menubar)); 477 478 main_toolbar->add_action(go_back_action); 479 main_toolbar->add_action(go_forward_action); 480 main_toolbar->add_action(open_parent_directory_action); 481 main_toolbar->add_action(go_home_action); 482 483 main_toolbar->add_separator(); 484 main_toolbar->add_action(mkdir_action); 485 main_toolbar->add_action(copy_action); 486 main_toolbar->add_action(paste_action); 487 main_toolbar->add_action(delete_action); 488 489 main_toolbar->add_separator(); 490 main_toolbar->add_action(*view_as_icons_action); 491 main_toolbar->add_action(*view_as_table_action); 492 main_toolbar->add_action(*view_as_columns_action); 493 494 directory_view->on_path_change = [&](const String& new_path) { 495 window->set_title(String::format("File Manager: %s", new_path.characters())); 496 location_textbox->set_text(new_path); 497 auto new_index = directories_model->index(new_path, GUI::FileSystemModel::Column::Name); 498 if (new_index.is_valid()) { 499 tree_view->selection().set(new_index); 500 tree_view->scroll_into_view(new_index, Orientation::Vertical); 501 tree_view->update(); 502 } 503 504 go_forward_action->set_enabled(directory_view->path_history_position() 505 < directory_view->path_history_size() - 1); 506 go_back_action->set_enabled(directory_view->path_history_position() > 0); 507 }; 508 509 directory_view->on_status_message = [&](const StringView& message) { 510 statusbar->set_text(message); 511 }; 512 513 directory_view->on_thumbnail_progress = [&](int done, int total) { 514 if (done == total) { 515 progressbar->set_visible(false); 516 return; 517 } 518 progressbar->set_range(0, total); 519 progressbar->set_value(done); 520 progressbar->set_visible(true); 521 }; 522 523 directory_view->on_selection_change = [&](GUI::AbstractView& view) { 524 // FIXME: Figure out how we can enable/disable the paste action, based on clipboard contents. 525 copy_action->set_enabled(!view.selection().is_empty()); 526 delete_action->set_enabled(!view.selection().is_empty()); 527 }; 528 529 auto open_in_text_editor_action = GUI::Action::create("Open in TextEditor...", Gfx::Bitmap::load_from_file("/res/icons/TextEditor16.png"), [&](auto&) { 530 for (auto& path : selected_file_paths()) { 531 if (!fork()) { 532 int rc = execl("/bin/TextEditor", "TextEditor", path.characters(), nullptr); 533 if (rc < 0) 534 perror("execl"); 535 exit(1); 536 } 537 } 538 }); 539 540 directory_context_menu->add_action(copy_action); 541 directory_context_menu->add_action(paste_action); 542 directory_context_menu->add_action(delete_action); 543 directory_context_menu->add_separator(); 544 directory_context_menu->add_action(properties_action); 545 546 file_context_menu->add_action(copy_action); 547 file_context_menu->add_action(paste_action); 548 file_context_menu->add_action(delete_action); 549 file_context_menu->add_separator(); 550 file_context_menu->add_action(open_in_text_editor_action); 551 file_context_menu->add_separator(); 552 file_context_menu->add_action(properties_action); 553 554 directory_view_context_menu->add_action(mkdir_action); 555 556 tree_view_directory_context_menu->add_action(copy_action); 557 tree_view_directory_context_menu->add_action(paste_action); 558 tree_view_directory_context_menu->add_action(delete_action); 559 tree_view_directory_context_menu->add_separator(); 560 tree_view_directory_context_menu->add_action(properties_action); 561 tree_view_directory_context_menu->add_separator(); 562 tree_view_directory_context_menu->add_action(mkdir_action); 563 564 directory_view->on_context_menu_request = [&](const GUI::AbstractView&, const GUI::ModelIndex& index, const GUI::ContextMenuEvent& event) { 565 if (index.is_valid()) { 566 auto& node = directory_view->model().node(index); 567 568 if (node.is_directory()) 569 directory_context_menu->popup(event.screen_position()); 570 else 571 file_context_menu->popup(event.screen_position()); 572 } else { 573 directory_view_context_menu->popup(event.screen_position()); 574 } 575 }; 576 577 directory_view->on_drop = [&](const GUI::AbstractView&, const GUI::ModelIndex& index, const GUI::DropEvent& event) { 578 if (!index.is_valid()) 579 return; 580 if (!event.mime_data().has_urls()) 581 return; 582 auto urls = event.mime_data().urls(); 583 if (urls.is_empty()) { 584 dbg() << "No files to drop"; 585 return; 586 } 587 588 auto& target_node = directory_view->model().node(index); 589 if (!target_node.is_directory()) 590 return; 591 592 for (auto& url_to_copy : urls) { 593 if (!url_to_copy.is_valid()) 594 continue; 595 auto new_path = String::format("%s/%s", 596 target_node.full_path(directory_view->model()).characters(), 597 FileSystemPath(url_to_copy.path()).basename().characters()); 598 599 if (!FileUtils::copy_file_or_directory(url_to_copy.path(), new_path)) { 600 auto error_message = String::format("Could not copy %s into %s.", 601 url_to_copy.to_string().characters(), 602 new_path.characters()); 603 GUI::MessageBox::show(error_message, "File Manager", GUI::MessageBox::Type::Error); 604 } else { 605 refresh_tree_view(); 606 } 607 } 608 }; 609 610 tree_view->on_selection_change = [&] { 611 auto path = directories_model->full_path(tree_view->selection().first()); 612 if (directory_view->path() == path) 613 return; 614 directory_view->open(path); 615 copy_action->set_enabled(!tree_view->selection().is_empty()); 616 delete_action->set_enabled(!tree_view->selection().is_empty()); 617 }; 618 619 tree_view->on_context_menu_request = [&](const GUI::ModelIndex& index, const GUI::ContextMenuEvent& event) { 620 if (index.is_valid()) { 621 tree_view_directory_context_menu->popup(event.screen_position()); 622 } 623 }; 624 625 // our initial location is defined as, in order of precedence: 626 // 1. the first command-line argument (e.g. FileManager /bin) 627 // 2. the user's home directory 628 // 3. the root directory 629 630 String initial_location; 631 632 if (argc >= 2) 633 initial_location = argv[1]; 634 635 if (initial_location.is_empty()) 636 initial_location = get_current_user_home_path(); 637 638 if (initial_location.is_empty()) 639 initial_location = "/"; 640 641 directory_view->open(initial_location); 642 directory_view->set_focus(true); 643 644 window->set_main_widget(widget); 645 window->show(); 646 647 window->set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/filetype-folder.png")); 648 649 // Read direcory read mode from config. 650 auto dir_view_mode = config->read_entry("DirectoryView", "ViewMode", "Icon"); 651 652 if (dir_view_mode.contains("List")) { 653 directory_view->set_view_mode(DirectoryView::ViewMode::List); 654 view_as_table_action->set_checked(true); 655 } else if (dir_view_mode.contains("Columns")) { 656 directory_view->set_view_mode(DirectoryView::ViewMode::Columns); 657 view_as_columns_action->set_checked(true); 658 } else { 659 directory_view->set_view_mode(DirectoryView::ViewMode::Icon); 660 view_as_icons_action->set_checked(true); 661 } 662 663 // Write window position to config file on close request. 664 window->on_close_request = [&] { 665 config->write_num_entry("Window", "Left", window->x()); 666 config->write_num_entry("Window", "Top", window->y()); 667 config->write_num_entry("Window", "Width", window->width()); 668 config->write_num_entry("Window", "Heigth", window->height()); 669 config->sync(); 670 671 return GUI::Window::CloseRequestDecision::Close; 672 }; 673 674 return app.exec(); 675}