main.cpp 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. /*
  2. * Copyright (c) 2021-2022, the SerenityOS developers.
  3. * Copyright (c) 2023, Sam Atkins <atkinssj@serenityos.org>
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include "Tree.h"
  8. #include "TreeMapWidget.h"
  9. #include <AK/LexicalPath.h>
  10. #include <AK/String.h>
  11. #include <AK/URL.h>
  12. #include <Applications/SpaceAnalyzer/SpaceAnalyzerGML.h>
  13. #include <LibDesktop/Launcher.h>
  14. #include <LibFileSystem/FileSystem.h>
  15. #include <LibGUI/Application.h>
  16. #include <LibGUI/BoxLayout.h>
  17. #include <LibGUI/Breadcrumbbar.h>
  18. #include <LibGUI/Clipboard.h>
  19. #include <LibGUI/FileIconProvider.h>
  20. #include <LibGUI/Icon.h>
  21. #include <LibGUI/Menu.h>
  22. #include <LibGUI/Menubar.h>
  23. #include <LibGUI/MessageBox.h>
  24. #include <LibGUI/Statusbar.h>
  25. #include <LibGfx/Bitmap.h>
  26. #include <LibMain/Main.h>
  27. static auto const APP_NAME = "Space Analyzer"_string;
  28. static DeprecatedString get_absolute_path_to_selected_node(SpaceAnalyzer::TreeMapWidget const& tree_map_widget, bool include_last_node = true)
  29. {
  30. StringBuilder path_builder;
  31. for (size_t k = 0; k < tree_map_widget.path_size() - (include_last_node ? 0 : 1); k++) {
  32. if (k != 0) {
  33. path_builder.append('/');
  34. }
  35. TreeNode const* node = tree_map_widget.path_node(k);
  36. path_builder.append(node->name());
  37. }
  38. return path_builder.to_deprecated_string();
  39. }
  40. ErrorOr<int> serenity_main(Main::Arguments arguments)
  41. {
  42. auto app = TRY(GUI::Application::create(arguments));
  43. // Configure application window.
  44. auto app_icon = GUI::Icon::default_icon("app-space-analyzer"sv);
  45. auto window = GUI::Window::construct();
  46. window->set_title(APP_NAME.bytes_as_string_view());
  47. window->resize(640, 480);
  48. window->set_icon(app_icon.bitmap_for_size(16));
  49. // Load widgets.
  50. auto main_widget = TRY(window->set_main_widget<GUI::Widget>());
  51. TRY(main_widget->load_from_gml(space_analyzer_gml));
  52. auto& breadcrumbbar = *main_widget->find_descendant_of_type_named<GUI::Breadcrumbbar>("breadcrumbbar");
  53. auto& tree_map_widget = *main_widget->find_descendant_of_type_named<SpaceAnalyzer::TreeMapWidget>("tree_map");
  54. auto& statusbar = *main_widget->find_descendant_of_type_named<GUI::Statusbar>("statusbar");
  55. GUI::Application::the()->on_action_enter = [&statusbar](GUI::Action& action) {
  56. statusbar.set_override_text(action.status_tip());
  57. };
  58. GUI::Application::the()->on_action_leave = [&statusbar](GUI::Action&) {
  59. statusbar.set_override_text({});
  60. };
  61. tree_map_widget.set_focus(true);
  62. auto file_menu = window->add_menu("&File"_string);
  63. file_menu->add_action(GUI::Action::create("&Analyze", { KeyCode::Key_F5 }, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/reload.png"sv)), [&](auto&) {
  64. // FIXME: Just modify the tree in memory instead of traversing the entire file system
  65. if (auto result = tree_map_widget.analyze(statusbar); result.is_error()) {
  66. GUI::MessageBox::show_error(window, DeprecatedString::formatted("{}", result.error()));
  67. }
  68. }));
  69. file_menu->add_separator();
  70. file_menu->add_action(GUI::CommonActions::make_quit_action([&](auto&) {
  71. app->quit();
  72. }));
  73. auto help_menu = window->add_menu("&Help"_string);
  74. help_menu->add_action(GUI::CommonActions::make_command_palette_action(window));
  75. help_menu->add_action(GUI::CommonActions::make_about_action(APP_NAME, app_icon, window));
  76. auto open_icon = TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/open.png"sv));
  77. // Configure the node's context menu.
  78. auto open_action = GUI::Action::create("Open in File Manager", { Mod_Ctrl, Key_O }, open_icon, [&](auto&) {
  79. auto path_string = get_absolute_path_to_selected_node(tree_map_widget);
  80. if (path_string.is_empty())
  81. return;
  82. if (FileSystem::is_directory(path_string)) {
  83. Desktop::Launcher::open(URL::create_with_file_scheme(path_string));
  84. return;
  85. }
  86. LexicalPath path { path_string };
  87. Desktop::Launcher::open(URL::create_with_file_scheme(path.dirname(), path.basename()));
  88. });
  89. auto copy_icon = TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/edit-copy.png"sv));
  90. auto copy_path_action = GUI::Action::create("Copy Path to Clipboard", { Mod_Ctrl, Key_C }, copy_icon, [&](auto&) {
  91. GUI::Clipboard::the().set_plain_text(get_absolute_path_to_selected_node(tree_map_widget));
  92. });
  93. auto delete_action = GUI::CommonActions::make_delete_action([&](auto&) {
  94. DeprecatedString selected_node_path = get_absolute_path_to_selected_node(tree_map_widget);
  95. bool try_again = true;
  96. while (try_again) {
  97. try_again = false;
  98. auto deletion_result = FileSystem::remove(selected_node_path, FileSystem::RecursionMode::Allowed);
  99. if (deletion_result.is_error()) {
  100. auto retry_message_result = GUI::MessageBox::show(window,
  101. DeprecatedString::formatted("Failed to delete \"{}\": {}. Retry?",
  102. selected_node_path,
  103. deletion_result.error()),
  104. "Deletion failed"sv,
  105. GUI::MessageBox::Type::Error,
  106. GUI::MessageBox::InputType::YesNo);
  107. if (retry_message_result == GUI::MessageBox::ExecResult::Yes) {
  108. try_again = true;
  109. }
  110. } else {
  111. GUI::MessageBox::show(window,
  112. DeprecatedString::formatted("Successfully deleted \"{}\".", selected_node_path),
  113. "Deletion completed"sv,
  114. GUI::MessageBox::Type::Information,
  115. GUI::MessageBox::InputType::OK);
  116. }
  117. }
  118. if (auto result = tree_map_widget.analyze(statusbar); result.is_error()) {
  119. GUI::MessageBox::show_error(window, DeprecatedString::formatted("{}", result.error()));
  120. }
  121. });
  122. auto context_menu = GUI::Menu::construct();
  123. context_menu->add_action(open_action);
  124. context_menu->add_action(copy_path_action);
  125. context_menu->add_action(delete_action);
  126. // Configure event handlers.
  127. breadcrumbbar.on_segment_click = [&](size_t index) {
  128. VERIFY(index < tree_map_widget.path_size());
  129. tree_map_widget.set_viewpoint(index);
  130. };
  131. tree_map_widget.on_path_change = [&]() {
  132. StringBuilder builder;
  133. breadcrumbbar.clear_segments();
  134. for (size_t k = 0; k < tree_map_widget.path_size(); k++) {
  135. if (k == 0) {
  136. if (tree_map_widget.viewpoint() == 0)
  137. window->set_title("/ - SpaceAnalyzer");
  138. breadcrumbbar.append_segment("/", GUI::FileIconProvider::icon_for_path("/"sv).bitmap_for_size(16), "/", "/");
  139. continue;
  140. }
  141. const TreeNode* node = tree_map_widget.path_node(k);
  142. builder.append('/');
  143. builder.append(node->name());
  144. // Sneakily set the window title here, while the StringBuilder holds the right amount of the path.
  145. if (k == tree_map_widget.viewpoint())
  146. window->set_title(DeprecatedString::formatted("{} - SpaceAnalyzer", builder.string_view()));
  147. breadcrumbbar.append_segment(node->name(), GUI::FileIconProvider::icon_for_path(builder.string_view()).bitmap_for_size(16), builder.string_view(), builder.string_view());
  148. }
  149. breadcrumbbar.set_selected_segment(tree_map_widget.viewpoint());
  150. };
  151. tree_map_widget.on_context_menu_request = [&](const GUI::ContextMenuEvent& event) {
  152. DeprecatedString selected_node_path = get_absolute_path_to_selected_node(tree_map_widget);
  153. if (selected_node_path.is_empty())
  154. return;
  155. delete_action->set_enabled(FileSystem::can_delete_or_move(selected_node_path));
  156. if (FileSystem::is_directory(selected_node_path))
  157. open_action->set_text("Open in File Manager");
  158. else
  159. open_action->set_text("Reveal in File Manager");
  160. context_menu->popup(event.screen_position());
  161. };
  162. // At startup automatically do an analysis of root.
  163. TRY(tree_map_widget.analyze(statusbar));
  164. window->show();
  165. return app->exec();
  166. }