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