main.cpp 25 KB

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