mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-12-29 06:02:07 +03:00
33829f05fe
The immutability of the string is not relevant here, since the string we're given was allocated in the IPC serialization layer and will be destroyed shortly afterwards. Additionally, noone relies on DeprecatedString-specific functionality. This will make it easier to convert the IPC layer itself to String later on.
1324 lines
58 KiB
C++
1324 lines
58 KiB
C++
/*
|
|
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
|
|
* Copyright (c) 2021-2023, Sam Atkins <atkinssj@serenityos.org>
|
|
* Copyright (c) 2021, Mustafa Quraish <mustafa@cs.toronto.edu>
|
|
* Copyright (c) 2022-2023, the SerenityOS developers.
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include "DesktopWidget.h"
|
|
#include "DirectoryView.h"
|
|
#include "FileUtils.h"
|
|
#include "PropertiesWindow.h"
|
|
#include <AK/LexicalPath.h>
|
|
#include <AK/StringBuilder.h>
|
|
#include <AK/Try.h>
|
|
#include <AK/URL.h>
|
|
#include <Applications/FileManager/FileManagerWindowGML.h>
|
|
#include <LibConfig/Client.h>
|
|
#include <LibConfig/Listener.h>
|
|
#include <LibCore/ArgsParser.h>
|
|
#include <LibCore/Process.h>
|
|
#include <LibCore/StandardPaths.h>
|
|
#include <LibCore/System.h>
|
|
#include <LibDesktop/Launcher.h>
|
|
#include <LibFileSystem/FileSystem.h>
|
|
#include <LibFileSystem/TempFile.h>
|
|
#include <LibGUI/Action.h>
|
|
#include <LibGUI/ActionGroup.h>
|
|
#include <LibGUI/Application.h>
|
|
#include <LibGUI/BoxLayout.h>
|
|
#include <LibGUI/Clipboard.h>
|
|
#include <LibGUI/Desktop.h>
|
|
#include <LibGUI/FileIconProvider.h>
|
|
#include <LibGUI/FileSystemModel.h>
|
|
#include <LibGUI/InputBox.h>
|
|
#include <LibGUI/Menu.h>
|
|
#include <LibGUI/Menubar.h>
|
|
#include <LibGUI/MessageBox.h>
|
|
#include <LibGUI/Painter.h>
|
|
#include <LibGUI/PathBreadcrumbbar.h>
|
|
#include <LibGUI/Progressbar.h>
|
|
#include <LibGUI/Splitter.h>
|
|
#include <LibGUI/Statusbar.h>
|
|
#include <LibGUI/Toolbar.h>
|
|
#include <LibGUI/ToolbarContainer.h>
|
|
#include <LibGUI/TreeView.h>
|
|
#include <LibGUI/Widget.h>
|
|
#include <LibGUI/Window.h>
|
|
#include <LibGfx/Palette.h>
|
|
#include <LibMain/Main.h>
|
|
#include <pthread.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
|
|
using namespace FileManager;
|
|
|
|
static ErrorOr<int> run_in_desktop_mode();
|
|
static ErrorOr<int> run_in_windowed_mode(DeprecatedString const& initial_location, DeprecatedString const& entry_focused_on_init);
|
|
static void do_copy(Vector<DeprecatedString> const& selected_file_paths, FileOperation file_operation);
|
|
static void do_paste(DeprecatedString const& target_directory, GUI::Window* window);
|
|
static void do_create_link(Vector<DeprecatedString> const& selected_file_paths, GUI::Window* window);
|
|
static void do_create_archive(Vector<DeprecatedString> const& selected_file_paths, GUI::Window* window);
|
|
static void do_set_wallpaper(DeprecatedString const& file_path, GUI::Window* window);
|
|
static void do_unzip_archive(Vector<DeprecatedString> const& selected_file_paths, GUI::Window* window);
|
|
static void show_properties(DeprecatedString const& container_dir_path, DeprecatedString const& path, Vector<DeprecatedString> const& selected, GUI::Window* window);
|
|
static bool add_launch_handler_actions_to_menu(RefPtr<GUI::Menu>& menu, DirectoryView const& directory_view, DeprecatedString const& full_path, RefPtr<GUI::Action>& default_action, Vector<NonnullRefPtr<LauncherHandler>>& current_file_launch_handlers);
|
|
|
|
ErrorOr<int> serenity_main(Main::Arguments arguments)
|
|
{
|
|
TRY(Core::System::pledge("stdio thread recvfd sendfd unix cpath rpath wpath fattr proc exec sigaction"));
|
|
|
|
struct sigaction act = {};
|
|
act.sa_flags = SA_NOCLDWAIT;
|
|
act.sa_handler = SIG_IGN;
|
|
TRY(Core::System::sigaction(SIGCHLD, &act, nullptr));
|
|
|
|
Core::ArgsParser args_parser;
|
|
bool is_desktop_mode { false };
|
|
bool is_selection_mode { false };
|
|
bool ignore_path_resolution { false };
|
|
DeprecatedString initial_location;
|
|
args_parser.add_option(is_desktop_mode, "Run in desktop mode", "desktop", 'd');
|
|
args_parser.add_option(is_selection_mode, "Show entry in parent folder", "select", 's');
|
|
args_parser.add_option(ignore_path_resolution, "Use raw path, do not resolve real path", "raw", 'r');
|
|
args_parser.add_positional_argument(initial_location, "Path to open", "path", Core::ArgsParser::Required::No);
|
|
args_parser.parse(arguments);
|
|
|
|
auto app = TRY(GUI::Application::create(arguments));
|
|
|
|
TRY(Core::System::pledge("stdio thread recvfd sendfd cpath rpath wpath fattr proc exec unix"));
|
|
|
|
Config::pledge_domains({ "FileManager", "WindowManager" });
|
|
Config::monitor_domain("FileManager");
|
|
Config::monitor_domain("WindowManager");
|
|
|
|
if (is_desktop_mode)
|
|
return run_in_desktop_mode();
|
|
|
|
// our initial location is defined as, in order of precedence:
|
|
// 1. the command-line path argument (e.g. FileManager /bin)
|
|
// 2. the current directory
|
|
// 3. the user's home directory
|
|
// 4. the root directory
|
|
|
|
LexicalPath path(initial_location);
|
|
if (!initial_location.is_empty()) {
|
|
if (auto error_or_path = FileSystem::real_path(initial_location); !ignore_path_resolution && !error_or_path.is_error())
|
|
initial_location = error_or_path.release_value().to_deprecated_string();
|
|
|
|
if (!FileSystem::is_directory(initial_location)) {
|
|
// We want to extract zips to a temporary directory when FileManager is launched with a .zip file as its first argument
|
|
if (path.has_extension(".zip"sv)) {
|
|
auto temp_directory = FileSystem::TempFile::create_temp_directory();
|
|
if (temp_directory.is_error()) {
|
|
dbgln("Failed to create temporary directory during zip extraction: {}", temp_directory.error());
|
|
|
|
GUI::MessageBox::show_error(app->active_window(), "Failed to create temporary directory!"sv);
|
|
return -1;
|
|
}
|
|
|
|
auto temp_directory_path = temp_directory.value()->path();
|
|
auto result = Core::Process::spawn("/bin/unzip"sv, Array { "-d"sv, temp_directory_path, initial_location });
|
|
|
|
if (result.is_error()) {
|
|
dbgln("Failed to extract {} to {}: {}", initial_location, temp_directory_path, result.error());
|
|
|
|
auto message = TRY(String::formatted("Failed to extract {} to {}", initial_location, temp_directory_path));
|
|
GUI::MessageBox::show_error(app->active_window(), message);
|
|
|
|
return -1;
|
|
}
|
|
|
|
return run_in_windowed_mode(temp_directory_path.to_deprecated_string(), path.basename());
|
|
}
|
|
|
|
is_selection_mode = true;
|
|
}
|
|
}
|
|
|
|
if (auto error_or_cwd = FileSystem::current_working_directory(); initial_location.is_empty() && !error_or_cwd.is_error())
|
|
initial_location = error_or_cwd.release_value().to_deprecated_string();
|
|
|
|
if (initial_location.is_empty())
|
|
initial_location = Core::StandardPaths::home_directory();
|
|
|
|
if (initial_location.is_empty())
|
|
initial_location = "/";
|
|
|
|
DeprecatedString focused_entry;
|
|
if (is_selection_mode) {
|
|
initial_location = path.dirname();
|
|
focused_entry = path.basename();
|
|
}
|
|
|
|
return run_in_windowed_mode(initial_location, focused_entry);
|
|
}
|
|
|
|
void do_copy(Vector<DeprecatedString> const& selected_file_paths, FileOperation file_operation)
|
|
{
|
|
VERIFY(!selected_file_paths.is_empty());
|
|
|
|
StringBuilder copy_text;
|
|
if (file_operation == FileOperation::Move) {
|
|
copy_text.append("#cut\n"sv); // This exploits the comment lines in the text/uri-list specification, which might be a bit hackish
|
|
}
|
|
for (auto& path : selected_file_paths) {
|
|
auto url = URL::create_with_file_scheme(path);
|
|
copy_text.appendff("{}\n", url);
|
|
}
|
|
GUI::Clipboard::the().set_data(copy_text.to_deprecated_string().bytes(), "text/uri-list");
|
|
}
|
|
|
|
void do_paste(DeprecatedString const& target_directory, GUI::Window* window)
|
|
{
|
|
auto data_and_type = GUI::Clipboard::the().fetch_data_and_type();
|
|
if (data_and_type.mime_type != "text/uri-list") {
|
|
dbgln("Cannot paste clipboard type {}", data_and_type.mime_type);
|
|
return;
|
|
}
|
|
auto copied_lines = DeprecatedString::copy(data_and_type.data).split('\n');
|
|
if (copied_lines.is_empty()) {
|
|
dbgln("No files to paste");
|
|
return;
|
|
}
|
|
|
|
FileOperation file_operation = FileOperation::Copy;
|
|
if (copied_lines[0] == "#cut") { // cut operation encoded as a text/uri-list comment
|
|
file_operation = FileOperation::Move;
|
|
copied_lines.remove(0);
|
|
}
|
|
|
|
Vector<DeprecatedString> source_paths;
|
|
for (auto& uri_as_string : copied_lines) {
|
|
if (uri_as_string.is_empty())
|
|
continue;
|
|
URL url = uri_as_string;
|
|
if (!url.is_valid() || url.scheme() != "file") {
|
|
dbgln("Cannot paste URI {}", uri_as_string);
|
|
continue;
|
|
}
|
|
source_paths.append(url.serialize_path());
|
|
}
|
|
|
|
if (!source_paths.is_empty()) {
|
|
if (auto result = run_file_operation(file_operation, source_paths, target_directory, window); result.is_error())
|
|
dbgln("Failed to paste files: {}", result.error());
|
|
}
|
|
}
|
|
|
|
void do_create_link(Vector<DeprecatedString> const& selected_file_paths, GUI::Window* window)
|
|
{
|
|
auto path = selected_file_paths.first();
|
|
auto destination = DeprecatedString::formatted("{}/{}", Core::StandardPaths::desktop_directory(), LexicalPath::basename(path));
|
|
if (auto result = FileSystem::link_file(destination, path); result.is_error()) {
|
|
GUI::MessageBox::show(window, DeprecatedString::formatted("Could not create desktop shortcut:\n{}", result.error()), "File Manager"sv,
|
|
GUI::MessageBox::Type::Error);
|
|
}
|
|
}
|
|
|
|
void do_create_archive(Vector<DeprecatedString> const& selected_file_paths, GUI::Window* window)
|
|
{
|
|
String archive_name;
|
|
if (GUI::InputBox::show(window, archive_name, "Enter name:"sv, "Create Archive"sv) != GUI::InputBox::ExecResult::OK)
|
|
return;
|
|
|
|
auto output_directory_path = LexicalPath(selected_file_paths.first());
|
|
|
|
StringBuilder path_builder;
|
|
path_builder.append(output_directory_path.dirname());
|
|
path_builder.append('/');
|
|
if (archive_name.is_empty()) {
|
|
path_builder.append(output_directory_path.parent().basename());
|
|
path_builder.append(".zip"sv);
|
|
} else {
|
|
path_builder.append(archive_name);
|
|
if (!AK::StringUtils::ends_with(archive_name, ".zip"sv, CaseSensitivity::CaseSensitive))
|
|
path_builder.append(".zip"sv);
|
|
}
|
|
auto output_path = path_builder.to_deprecated_string();
|
|
|
|
pid_t zip_pid = fork();
|
|
if (zip_pid < 0) {
|
|
perror("fork");
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
|
|
if (!zip_pid) {
|
|
Vector<DeprecatedString> relative_paths;
|
|
Vector<char const*> arg_list;
|
|
arg_list.append("/bin/zip");
|
|
arg_list.append("-r");
|
|
arg_list.append("-f");
|
|
arg_list.append(output_path.characters());
|
|
for (auto const& path : selected_file_paths) {
|
|
relative_paths.append(LexicalPath::relative_path(path, output_directory_path.dirname()));
|
|
arg_list.append(relative_paths.last().characters());
|
|
}
|
|
arg_list.append(nullptr);
|
|
int rc = execvp("/bin/zip", const_cast<char* const*>(arg_list.data()));
|
|
if (rc < 0) {
|
|
perror("execvp");
|
|
_exit(1);
|
|
}
|
|
} else {
|
|
int status;
|
|
int rc = waitpid(zip_pid, &status, 0);
|
|
if (rc < 0 || !WIFEXITED(status) || WEXITSTATUS(status) != 0)
|
|
GUI::MessageBox::show(window, "Could not create archive"sv, "Archive Error"sv, GUI::MessageBox::Type::Error);
|
|
}
|
|
}
|
|
|
|
void do_set_wallpaper(DeprecatedString const& file_path, GUI::Window* window)
|
|
{
|
|
auto show_error = [&] {
|
|
GUI::MessageBox::show(window, DeprecatedString::formatted("Failed to set {} as wallpaper.", file_path), "Failed to set wallpaper"sv, GUI::MessageBox::Type::Error);
|
|
};
|
|
|
|
auto bitmap_or_error = Gfx::Bitmap::load_from_file(file_path);
|
|
if (bitmap_or_error.is_error()) {
|
|
show_error();
|
|
return;
|
|
}
|
|
|
|
if (!GUI::Desktop::the().set_wallpaper(bitmap_or_error.release_value(), file_path))
|
|
show_error();
|
|
}
|
|
|
|
void do_unzip_archive(Vector<DeprecatedString> const& selected_file_paths, GUI::Window* window)
|
|
{
|
|
DeprecatedString archive_file_path = selected_file_paths.first();
|
|
DeprecatedString output_directory_path = archive_file_path.substring(0, archive_file_path.length() - 4);
|
|
|
|
pid_t unzip_pid = fork();
|
|
if (unzip_pid < 0) {
|
|
perror("fork");
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
|
|
if (!unzip_pid) {
|
|
int rc = execlp("/bin/unzip", "/bin/unzip", "-d", output_directory_path.characters(), archive_file_path.characters(), nullptr);
|
|
if (rc < 0) {
|
|
perror("execlp");
|
|
_exit(1);
|
|
}
|
|
} else {
|
|
// FIXME: this could probably be tied in with the new file operation progress tracking
|
|
int status;
|
|
int rc = waitpid(unzip_pid, &status, 0);
|
|
if (rc < 0 || !WIFEXITED(status) || WEXITSTATUS(status) != 0)
|
|
GUI::MessageBox::show(window, "Could not extract archive"sv, "Extract Archive Error"sv, GUI::MessageBox::Type::Error);
|
|
}
|
|
}
|
|
|
|
void show_properties(DeprecatedString const& container_dir_path, DeprecatedString const& path, Vector<DeprecatedString> const& selected, GUI::Window* window)
|
|
{
|
|
ErrorOr<RefPtr<PropertiesWindow>> properties_or_error = nullptr;
|
|
if (selected.is_empty()) {
|
|
properties_or_error = window->try_add<PropertiesWindow>(path, true);
|
|
} else {
|
|
properties_or_error = window->try_add<PropertiesWindow>(selected.first(), access(container_dir_path.characters(), W_OK) != 0);
|
|
}
|
|
|
|
if (properties_or_error.is_error()) {
|
|
GUI::MessageBox::show(window, "Could not show properties"sv, "Properties Error"sv, GUI::MessageBox::Type::Error);
|
|
return;
|
|
}
|
|
|
|
auto properties = properties_or_error.release_value();
|
|
properties->on_close = [properties = properties.ptr()] {
|
|
properties->remove_from_parent();
|
|
};
|
|
properties->center_on_screen();
|
|
properties->show();
|
|
}
|
|
|
|
bool add_launch_handler_actions_to_menu(RefPtr<GUI::Menu>& menu, DirectoryView const& directory_view, DeprecatedString const& full_path, RefPtr<GUI::Action>& default_action, Vector<NonnullRefPtr<LauncherHandler>>& current_file_launch_handlers)
|
|
{
|
|
current_file_launch_handlers = directory_view.get_launch_handlers(full_path);
|
|
|
|
bool added_open_menu_items = false;
|
|
auto default_file_handler = directory_view.get_default_launch_handler(current_file_launch_handlers);
|
|
if (default_file_handler) {
|
|
auto file_open_action = default_file_handler->create_launch_action([&, full_path = move(full_path)](auto& launcher_handler) {
|
|
directory_view.launch(URL::create_with_file_scheme(full_path), launcher_handler);
|
|
});
|
|
if (default_file_handler->details().launcher_type == Desktop::Launcher::LauncherType::Application)
|
|
file_open_action->set_text(DeprecatedString::formatted("Run {}", file_open_action->text()));
|
|
else
|
|
file_open_action->set_text(DeprecatedString::formatted("Open in {}", file_open_action->text()));
|
|
|
|
default_action = file_open_action;
|
|
|
|
menu->add_action(move(file_open_action));
|
|
added_open_menu_items = true;
|
|
} else {
|
|
default_action.clear();
|
|
}
|
|
|
|
if (current_file_launch_handlers.size() > 1) {
|
|
added_open_menu_items = true;
|
|
auto& file_open_with_menu = menu->add_submenu("Open with"_string.release_value_but_fixme_should_propagate_errors());
|
|
for (auto& handler : current_file_launch_handlers) {
|
|
if (handler == default_file_handler)
|
|
continue;
|
|
file_open_with_menu.add_action(handler->create_launch_action([&, full_path = move(full_path)](auto& launcher_handler) {
|
|
directory_view.launch(URL::create_with_file_scheme(full_path), launcher_handler);
|
|
}));
|
|
}
|
|
}
|
|
|
|
return added_open_menu_items;
|
|
}
|
|
|
|
ErrorOr<int> run_in_desktop_mode()
|
|
{
|
|
(void)Core::Process::set_name("FileManager (Desktop)"sv, Core::Process::SetThreadName::Yes);
|
|
|
|
auto window = TRY(GUI::Window::try_create());
|
|
window->set_title("Desktop Manager");
|
|
window->set_window_type(GUI::WindowType::Desktop);
|
|
window->set_has_alpha_channel(true);
|
|
|
|
auto desktop_icon = TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/desktop.png"sv));
|
|
window->set_icon(desktop_icon);
|
|
|
|
auto desktop_widget = TRY(window->set_main_widget<FileManager::DesktopWidget>());
|
|
TRY(desktop_widget->try_set_layout<GUI::VerticalBoxLayout>());
|
|
|
|
auto directory_view = TRY(desktop_widget->try_add<DirectoryView>(DirectoryView::Mode::Desktop));
|
|
directory_view->set_name("directory_view");
|
|
|
|
auto cut_action = GUI::CommonActions::make_cut_action(
|
|
[&](auto&) {
|
|
auto paths = directory_view->selected_file_paths();
|
|
VERIFY(!paths.is_empty());
|
|
|
|
do_copy(paths, FileOperation::Move);
|
|
},
|
|
window);
|
|
cut_action->set_enabled(false);
|
|
|
|
auto copy_action = GUI::CommonActions::make_copy_action(
|
|
[&](auto&) {
|
|
auto paths = directory_view->selected_file_paths();
|
|
VERIFY(!paths.is_empty());
|
|
|
|
do_copy(paths, FileOperation::Copy);
|
|
},
|
|
window);
|
|
copy_action->set_enabled(false);
|
|
|
|
auto create_archive_action
|
|
= GUI::Action::create(
|
|
"Create &Archive",
|
|
TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/filetype-archive.png"sv)),
|
|
[&](GUI::Action const&) {
|
|
auto paths = directory_view->selected_file_paths();
|
|
if (paths.is_empty())
|
|
return;
|
|
|
|
do_create_archive(paths, directory_view->window());
|
|
},
|
|
window);
|
|
|
|
auto unzip_archive_action
|
|
= GUI::Action::create(
|
|
"E&xtract Here",
|
|
[&](GUI::Action const&) {
|
|
auto paths = directory_view->selected_file_paths();
|
|
if (paths.is_empty())
|
|
return;
|
|
|
|
do_unzip_archive(paths, directory_view->window());
|
|
},
|
|
window);
|
|
|
|
auto set_wallpaper_action
|
|
= GUI::Action::create(
|
|
"Set as Desktop &Wallpaper",
|
|
TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/app-display-settings.png"sv)),
|
|
[&](GUI::Action const&) {
|
|
auto paths = directory_view->selected_file_paths();
|
|
if (paths.is_empty())
|
|
return;
|
|
|
|
do_set_wallpaper(paths.first(), directory_view->window());
|
|
},
|
|
window);
|
|
|
|
directory_view->on_selection_change = [&](GUI::AbstractView const& view) {
|
|
cut_action->set_enabled(!view.selection().is_empty());
|
|
copy_action->set_enabled(!view.selection().is_empty());
|
|
};
|
|
|
|
auto properties_action = GUI::CommonActions::make_properties_action(
|
|
[&](auto&) {
|
|
DeprecatedString path = directory_view->path();
|
|
Vector<DeprecatedString> selected = directory_view->selected_file_paths();
|
|
|
|
show_properties(path, path, selected, directory_view->window());
|
|
},
|
|
window);
|
|
|
|
auto paste_action = GUI::CommonActions::make_paste_action(
|
|
[&](GUI::Action const&) {
|
|
do_paste(directory_view->path(), directory_view->window());
|
|
},
|
|
window);
|
|
paste_action->set_enabled(GUI::Clipboard::the().fetch_mime_type() == "text/uri-list" && access(directory_view->path().characters(), W_OK) == 0);
|
|
|
|
GUI::Clipboard::the().on_change = [&](DeprecatedString const& data_type) {
|
|
paste_action->set_enabled(data_type == "text/uri-list" && access(directory_view->path().characters(), W_OK) == 0);
|
|
};
|
|
|
|
auto desktop_view_context_menu = TRY(GUI::Menu::try_create(TRY("Directory View"_string)));
|
|
|
|
auto file_manager_action = GUI::Action::create("Open in File &Manager", {}, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/app-file-manager.png"sv)), [&](auto&) {
|
|
auto paths = directory_view->selected_file_paths();
|
|
if (paths.is_empty()) {
|
|
Desktop::Launcher::open(URL::create_with_file_scheme(directory_view->path()));
|
|
return;
|
|
}
|
|
|
|
for (auto& path : paths) {
|
|
if (FileSystem::is_directory(path))
|
|
Desktop::Launcher::open(URL::create_with_file_scheme(path));
|
|
}
|
|
});
|
|
|
|
auto open_terminal_action = GUI::Action::create("Open in &Terminal", {}, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/app-terminal.png"sv)), [&](auto&) {
|
|
auto paths = directory_view->selected_file_paths();
|
|
if (paths.is_empty()) {
|
|
spawn_terminal(window, directory_view->path());
|
|
return;
|
|
}
|
|
|
|
for (auto& path : paths) {
|
|
if (FileSystem::is_directory(path)) {
|
|
spawn_terminal(window, path);
|
|
}
|
|
}
|
|
});
|
|
|
|
auto display_properties_action = GUI::Action::create("&Display Settings", {}, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/app-display-settings.png"sv)), [&](GUI::Action const&) {
|
|
Desktop::Launcher::open(URL::create_with_file_scheme("/bin/DisplaySettings"));
|
|
});
|
|
|
|
TRY(desktop_view_context_menu->try_add_action(directory_view->mkdir_action()));
|
|
TRY(desktop_view_context_menu->try_add_action(directory_view->touch_action()));
|
|
TRY(desktop_view_context_menu->try_add_action(paste_action));
|
|
TRY(desktop_view_context_menu->try_add_separator());
|
|
TRY(desktop_view_context_menu->try_add_action(file_manager_action));
|
|
TRY(desktop_view_context_menu->try_add_action(open_terminal_action));
|
|
TRY(desktop_view_context_menu->try_add_separator());
|
|
TRY(desktop_view_context_menu->try_add_action(display_properties_action));
|
|
|
|
auto desktop_context_menu = TRY(GUI::Menu::try_create(TRY("Directory View Directory"_string)));
|
|
|
|
TRY(desktop_context_menu->try_add_action(file_manager_action));
|
|
TRY(desktop_context_menu->try_add_action(open_terminal_action));
|
|
TRY(desktop_context_menu->try_add_separator());
|
|
TRY(desktop_context_menu->try_add_action(cut_action));
|
|
TRY(desktop_context_menu->try_add_action(copy_action));
|
|
TRY(desktop_context_menu->try_add_action(paste_action));
|
|
TRY(desktop_context_menu->try_add_action(directory_view->delete_action()));
|
|
TRY(desktop_context_menu->try_add_action(directory_view->rename_action()));
|
|
TRY(desktop_context_menu->try_add_separator());
|
|
TRY(desktop_context_menu->try_add_action(properties_action));
|
|
|
|
RefPtr<GUI::Menu> file_context_menu;
|
|
Vector<NonnullRefPtr<LauncherHandler>> current_file_handlers;
|
|
RefPtr<GUI::Action> file_context_menu_action_default_action;
|
|
|
|
directory_view->on_context_menu_request = [&](GUI::ModelIndex const& index, GUI::ContextMenuEvent const& event) {
|
|
if (index.is_valid()) {
|
|
auto& node = directory_view->node(index);
|
|
if (node.is_directory()) {
|
|
desktop_context_menu->popup(event.screen_position(), file_manager_action);
|
|
} else {
|
|
file_context_menu = GUI::Menu::construct("Directory View File"_string.release_value_but_fixme_should_propagate_errors());
|
|
|
|
bool added_open_menu_items = add_launch_handler_actions_to_menu(file_context_menu, directory_view, node.full_path(), file_context_menu_action_default_action, current_file_handlers);
|
|
if (added_open_menu_items)
|
|
file_context_menu->add_separator();
|
|
|
|
file_context_menu->add_action(cut_action);
|
|
file_context_menu->add_action(copy_action);
|
|
file_context_menu->add_action(paste_action);
|
|
file_context_menu->add_action(directory_view->delete_action());
|
|
file_context_menu->add_action(directory_view->rename_action());
|
|
file_context_menu->add_action(create_archive_action);
|
|
file_context_menu->add_separator();
|
|
|
|
if (Gfx::Bitmap::is_path_a_supported_image_format(node.name)) {
|
|
file_context_menu->add_action(set_wallpaper_action);
|
|
file_context_menu->add_separator();
|
|
}
|
|
|
|
if (node.full_path().ends_with(".zip"sv, AK::CaseSensitivity::CaseInsensitive)) {
|
|
file_context_menu->add_action(unzip_archive_action);
|
|
file_context_menu->add_separator();
|
|
}
|
|
|
|
file_context_menu->add_action(properties_action);
|
|
file_context_menu->popup(event.screen_position(), file_context_menu_action_default_action);
|
|
}
|
|
} else {
|
|
desktop_view_context_menu->popup(event.screen_position());
|
|
}
|
|
};
|
|
|
|
struct BackgroundWallpaperListener : Config::Listener {
|
|
virtual void config_string_did_change(StringView domain, StringView group, StringView key, StringView value) override
|
|
{
|
|
if (domain == "WindowManager" && group == "Background" && key == "Wallpaper") {
|
|
if (value.is_empty()) {
|
|
GUI::Desktop::the().set_wallpaper(nullptr, {});
|
|
return;
|
|
}
|
|
auto wallpaper_bitmap_or_error = Gfx::Bitmap::load_from_file(value);
|
|
if (wallpaper_bitmap_or_error.is_error())
|
|
dbgln("Failed to load wallpaper bitmap from path: {}", wallpaper_bitmap_or_error.error());
|
|
else
|
|
GUI::Desktop::the().set_wallpaper(wallpaper_bitmap_or_error.release_value(), {});
|
|
}
|
|
}
|
|
} wallpaper_listener;
|
|
|
|
auto selected_wallpaper = Config::read_string("WindowManager"sv, "Background"sv, "Wallpaper"sv, ""sv);
|
|
RefPtr<Gfx::Bitmap> wallpaper_bitmap {};
|
|
if (!selected_wallpaper.is_empty()) {
|
|
wallpaper_bitmap = TRY(Gfx::Bitmap::load_from_file(selected_wallpaper));
|
|
}
|
|
// This sets the wallpaper at startup, even if there is no wallpaper, the
|
|
// desktop should still show the background color. It's fine to pass a
|
|
// nullptr to Desktop::set_wallpaper.
|
|
GUI::Desktop::the().set_wallpaper(wallpaper_bitmap, {});
|
|
|
|
window->show();
|
|
return GUI::Application::the()->exec();
|
|
}
|
|
|
|
ErrorOr<int> run_in_windowed_mode(DeprecatedString const& initial_location, DeprecatedString const& entry_focused_on_init)
|
|
{
|
|
auto window = TRY(GUI::Window::try_create());
|
|
window->set_title("File Manager");
|
|
|
|
auto left = Config::read_i32("FileManager"sv, "Window"sv, "Left"sv, 150);
|
|
auto top = Config::read_i32("FileManager"sv, "Window"sv, "Top"sv, 75);
|
|
auto width = Config::read_i32("FileManager"sv, "Window"sv, "Width"sv, 640);
|
|
auto height = Config::read_i32("FileManager"sv, "Window"sv, "Height"sv, 480);
|
|
auto was_maximized = Config::read_bool("FileManager"sv, "Window"sv, "Maximized"sv, false);
|
|
|
|
auto widget = TRY(window->set_main_widget<GUI::Widget>());
|
|
TRY(widget->load_from_gml(file_manager_window_gml));
|
|
|
|
auto& toolbar_container = *widget->find_descendant_of_type_named<GUI::ToolbarContainer>("toolbar_container");
|
|
auto& main_toolbar = *widget->find_descendant_of_type_named<GUI::Toolbar>("main_toolbar");
|
|
|
|
auto& breadcrumb_toolbar = *widget->find_descendant_of_type_named<GUI::Toolbar>("breadcrumb_toolbar");
|
|
breadcrumb_toolbar.layout()->set_margins({ 0, 6 });
|
|
auto& breadcrumbbar = *widget->find_descendant_of_type_named<GUI::PathBreadcrumbbar>("breadcrumbbar");
|
|
|
|
auto& splitter = *widget->find_descendant_of_type_named<GUI::HorizontalSplitter>("splitter");
|
|
auto& tree_view = *widget->find_descendant_of_type_named<GUI::TreeView>("tree_view");
|
|
|
|
auto directories_model = GUI::FileSystemModel::create({}, GUI::FileSystemModel::Mode::DirectoriesOnly);
|
|
tree_view.set_model(directories_model);
|
|
tree_view.set_column_visible(GUI::FileSystemModel::Column::Icon, false);
|
|
tree_view.set_column_visible(GUI::FileSystemModel::Column::Size, false);
|
|
tree_view.set_column_visible(GUI::FileSystemModel::Column::User, false);
|
|
tree_view.set_column_visible(GUI::FileSystemModel::Column::Group, false);
|
|
tree_view.set_column_visible(GUI::FileSystemModel::Column::Permissions, false);
|
|
tree_view.set_column_visible(GUI::FileSystemModel::Column::ModificationTime, false);
|
|
tree_view.set_column_visible(GUI::FileSystemModel::Column::Inode, false);
|
|
tree_view.set_column_visible(GUI::FileSystemModel::Column::SymlinkTarget, false);
|
|
bool is_reacting_to_tree_view_selection_change = false;
|
|
|
|
auto directory_view = TRY(splitter.try_add<DirectoryView>(DirectoryView::Mode::Normal));
|
|
directory_view->set_name("directory_view");
|
|
|
|
// Open the root directory. FIXME: This is awkward.
|
|
tree_view.toggle_index(directories_model->index(0, 0, {}));
|
|
|
|
auto& statusbar = *widget->find_descendant_of_type_named<GUI::Statusbar>("statusbar");
|
|
|
|
GUI::Application::the()->on_action_enter = [&statusbar](GUI::Action& action) {
|
|
statusbar.set_override_text(action.status_tip());
|
|
};
|
|
|
|
GUI::Application::the()->on_action_leave = [&statusbar](GUI::Action&) {
|
|
statusbar.set_override_text({});
|
|
};
|
|
|
|
auto& progressbar = *widget->find_descendant_of_type_named<GUI::Progressbar>("progressbar");
|
|
progressbar.set_format(GUI::Progressbar::Format::ValueSlashMax);
|
|
progressbar.set_frame_style(Gfx::FrameStyle::SunkenPanel);
|
|
|
|
auto refresh_tree_view = [&] {
|
|
directories_model->invalidate();
|
|
|
|
auto current_path = directory_view->path();
|
|
|
|
struct stat st;
|
|
// If the directory no longer exists, we find a parent that does.
|
|
while (stat(current_path.characters(), &st) != 0) {
|
|
directory_view->open_parent_directory();
|
|
current_path = directory_view->path();
|
|
if (current_path == directories_model->root_path()) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Reselect the existing folder in the tree.
|
|
auto new_index = directories_model->index(current_path, GUI::FileSystemModel::Column::Name);
|
|
if (new_index.is_valid()) {
|
|
tree_view.expand_all_parents_of(new_index);
|
|
tree_view.set_cursor(new_index, GUI::AbstractView::SelectionUpdate::Set, true);
|
|
}
|
|
|
|
directory_view->refresh();
|
|
};
|
|
|
|
auto directory_context_menu = TRY(GUI::Menu::try_create(TRY("Directory View Directory"_string)));
|
|
auto directory_view_context_menu = TRY(GUI::Menu::try_create(TRY("Directory View"_string)));
|
|
auto tree_view_directory_context_menu = TRY(GUI::Menu::try_create(TRY("Tree View Directory"_string)));
|
|
|
|
auto open_parent_directory_action = GUI::Action::create("Open &Parent Directory", { Mod_Alt, Key_Up }, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/open-parent-directory.png"sv)), [&](GUI::Action const&) {
|
|
directory_view->open_parent_directory();
|
|
});
|
|
|
|
auto open_child_directory_action = GUI::Action::create("Open &Child Directory", { Mod_Alt, Key_Down }, [&](GUI::Action const&) {
|
|
breadcrumbbar.select_child_segment();
|
|
});
|
|
|
|
RefPtr<GUI::Action> layout_toolbar_action;
|
|
RefPtr<GUI::Action> layout_location_action;
|
|
RefPtr<GUI::Action> layout_statusbar_action;
|
|
RefPtr<GUI::Action> layout_folderpane_action;
|
|
|
|
auto show_toolbar = Config::read_bool("FileManager"sv, "Layout"sv, "ShowToolbar"sv, true);
|
|
layout_toolbar_action = GUI::Action::create_checkable("&Toolbar", [&](auto& action) {
|
|
if (action.is_checked()) {
|
|
main_toolbar.set_visible(true);
|
|
toolbar_container.set_visible(true);
|
|
} else {
|
|
main_toolbar.set_visible(false);
|
|
if (!breadcrumb_toolbar.is_visible())
|
|
toolbar_container.set_visible(false);
|
|
}
|
|
show_toolbar = action.is_checked();
|
|
Config::write_bool("FileManager"sv, "Layout"sv, "ShowToolbar"sv, action.is_checked());
|
|
});
|
|
layout_toolbar_action->set_checked(show_toolbar);
|
|
main_toolbar.set_visible(show_toolbar);
|
|
|
|
auto show_location = Config::read_bool("FileManager"sv, "Layout"sv, "ShowLocationBar"sv, true);
|
|
layout_location_action = GUI::Action::create_checkable("&Location Bar", [&](auto& action) {
|
|
if (action.is_checked()) {
|
|
breadcrumb_toolbar.set_visible(true);
|
|
toolbar_container.set_visible(true);
|
|
} else {
|
|
breadcrumb_toolbar.set_visible(false);
|
|
if (!main_toolbar.is_visible())
|
|
toolbar_container.set_visible(false);
|
|
}
|
|
show_location = action.is_checked();
|
|
Config::write_bool("FileManager"sv, "Layout"sv, "ShowLocationBar"sv, action.is_checked());
|
|
});
|
|
layout_location_action->set_checked(show_location);
|
|
breadcrumb_toolbar.set_visible(show_location);
|
|
|
|
toolbar_container.set_visible(show_location || show_toolbar);
|
|
|
|
layout_statusbar_action = GUI::Action::create_checkable("&Status Bar", [&](auto& action) {
|
|
action.is_checked() ? statusbar.set_visible(true) : statusbar.set_visible(false);
|
|
Config::write_bool("FileManager"sv, "Layout"sv, "ShowStatusbar"sv, action.is_checked());
|
|
});
|
|
|
|
auto show_statusbar = Config::read_bool("FileManager"sv, "Layout"sv, "ShowStatusbar"sv, true);
|
|
layout_statusbar_action->set_checked(show_statusbar);
|
|
statusbar.set_visible(show_statusbar);
|
|
|
|
layout_folderpane_action = GUI::Action::create_checkable("&Folder Pane", { Mod_Ctrl, Key_P }, [&](auto& action) {
|
|
action.is_checked() ? tree_view.set_visible(true) : tree_view.set_visible(false);
|
|
Config::write_bool("FileManager"sv, "Layout"sv, "ShowFolderPane"sv, action.is_checked());
|
|
});
|
|
|
|
auto show_folderpane = Config::read_bool("FileManager"sv, "Layout"sv, "ShowFolderPane"sv, true);
|
|
layout_folderpane_action->set_checked(show_folderpane);
|
|
tree_view.set_visible(show_folderpane);
|
|
|
|
breadcrumbbar.on_hide_location_box = [&] {
|
|
if (show_location)
|
|
breadcrumb_toolbar.set_visible(true);
|
|
if (!(show_location || show_toolbar))
|
|
toolbar_container.set_visible(false);
|
|
};
|
|
|
|
auto view_type_action_group = make<GUI::ActionGroup>();
|
|
view_type_action_group->set_exclusive(true);
|
|
view_type_action_group->add_action(directory_view->view_as_icons_action());
|
|
view_type_action_group->add_action(directory_view->view_as_table_action());
|
|
view_type_action_group->add_action(directory_view->view_as_columns_action());
|
|
|
|
auto tree_view_selected_file_paths = [&] {
|
|
Vector<DeprecatedString> paths;
|
|
auto& view = tree_view;
|
|
view.selection().for_each_index([&](GUI::ModelIndex const& index) {
|
|
paths.append(directories_model->full_path(index));
|
|
});
|
|
return paths;
|
|
};
|
|
|
|
auto select_all_action = GUI::CommonActions::make_select_all_action([&](auto&) {
|
|
directory_view->current_view().select_all();
|
|
});
|
|
|
|
auto cut_action = GUI::CommonActions::make_cut_action(
|
|
[&](auto&) {
|
|
auto paths = directory_view->selected_file_paths();
|
|
if (paths.is_empty())
|
|
paths = tree_view_selected_file_paths();
|
|
VERIFY(!paths.is_empty());
|
|
|
|
do_copy(paths, FileOperation::Move);
|
|
refresh_tree_view();
|
|
},
|
|
window);
|
|
cut_action->set_enabled(false);
|
|
|
|
auto copy_action = GUI::CommonActions::make_copy_action(
|
|
[&](auto&) {
|
|
auto paths = directory_view->selected_file_paths();
|
|
if (paths.is_empty())
|
|
paths = tree_view_selected_file_paths();
|
|
VERIFY(!paths.is_empty());
|
|
|
|
do_copy(paths, FileOperation::Copy);
|
|
refresh_tree_view();
|
|
},
|
|
window);
|
|
copy_action->set_enabled(false);
|
|
|
|
auto open_in_new_window_action
|
|
= GUI::Action::create(
|
|
"Open in New &Window",
|
|
{},
|
|
TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/app-file-manager.png"sv)),
|
|
[&](GUI::Action const& action) {
|
|
Vector<DeprecatedString> paths;
|
|
if (action.activator() == tree_view_directory_context_menu)
|
|
paths = tree_view_selected_file_paths();
|
|
else
|
|
paths = directory_view->selected_file_paths();
|
|
|
|
for (auto& path : paths) {
|
|
if (FileSystem::is_directory(path))
|
|
Desktop::Launcher::open(URL::create_with_file_scheme(path));
|
|
}
|
|
},
|
|
window);
|
|
|
|
auto open_in_new_terminal_action
|
|
= GUI::Action::create(
|
|
"Open in &Terminal",
|
|
{},
|
|
TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/app-terminal.png"sv)),
|
|
[&](GUI::Action const& action) {
|
|
Vector<DeprecatedString> paths;
|
|
if (action.activator() == tree_view_directory_context_menu)
|
|
paths = tree_view_selected_file_paths();
|
|
else
|
|
paths = directory_view->selected_file_paths();
|
|
|
|
for (auto& path : paths) {
|
|
if (FileSystem::is_directory(path)) {
|
|
spawn_terminal(window, path);
|
|
}
|
|
}
|
|
},
|
|
window);
|
|
|
|
auto shortcut_action
|
|
= GUI::Action::create(
|
|
"Create Desktop &Shortcut",
|
|
{},
|
|
TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/filetype-symlink.png"sv)),
|
|
[&](GUI::Action const&) {
|
|
auto paths = directory_view->selected_file_paths();
|
|
if (paths.is_empty()) {
|
|
return;
|
|
}
|
|
do_create_link(paths, directory_view->window());
|
|
},
|
|
window);
|
|
|
|
auto create_archive_action
|
|
= GUI::Action::create(
|
|
"Create &Archive",
|
|
TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/filetype-archive.png"sv)),
|
|
[&](GUI::Action const&) {
|
|
auto paths = directory_view->selected_file_paths();
|
|
if (paths.is_empty())
|
|
return;
|
|
|
|
do_create_archive(paths, directory_view->window());
|
|
refresh_tree_view();
|
|
},
|
|
window);
|
|
|
|
auto unzip_archive_action
|
|
= GUI::Action::create(
|
|
"E&xtract Here",
|
|
[&](GUI::Action const&) {
|
|
auto paths = directory_view->selected_file_paths();
|
|
if (paths.is_empty())
|
|
return;
|
|
|
|
do_unzip_archive(paths, directory_view->window());
|
|
refresh_tree_view();
|
|
},
|
|
window);
|
|
|
|
auto set_wallpaper_action
|
|
= GUI::Action::create(
|
|
"Set as Desktop &Wallpaper",
|
|
TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/app-display-settings.png"sv)),
|
|
[&](GUI::Action const&) {
|
|
auto paths = directory_view->selected_file_paths();
|
|
if (paths.is_empty())
|
|
return;
|
|
|
|
do_set_wallpaper(paths.first(), directory_view->window());
|
|
},
|
|
window);
|
|
|
|
auto properties_action = GUI::CommonActions::make_properties_action(
|
|
[&](auto& action) {
|
|
DeprecatedString container_dir_path;
|
|
DeprecatedString path;
|
|
Vector<DeprecatedString> selected;
|
|
if (action.activator() == directory_context_menu || directory_view->active_widget()->is_focused()) {
|
|
path = directory_view->path();
|
|
container_dir_path = path;
|
|
selected = directory_view->selected_file_paths();
|
|
} else {
|
|
path = directories_model->full_path(tree_view.selection().first());
|
|
container_dir_path = LexicalPath::basename(path);
|
|
selected = tree_view_selected_file_paths();
|
|
}
|
|
|
|
show_properties(container_dir_path, path, selected, directory_view->window());
|
|
},
|
|
window);
|
|
|
|
auto paste_action = GUI::CommonActions::make_paste_action(
|
|
[&](GUI::Action const& action) {
|
|
DeprecatedString target_directory;
|
|
if (action.activator() == directory_context_menu)
|
|
target_directory = directory_view->selected_file_paths()[0];
|
|
else
|
|
target_directory = directory_view->path();
|
|
do_paste(target_directory, directory_view->window());
|
|
refresh_tree_view();
|
|
},
|
|
window);
|
|
|
|
auto folder_specific_paste_action = GUI::CommonActions::make_paste_action(
|
|
[&](GUI::Action const& action) {
|
|
DeprecatedString target_directory;
|
|
if (action.activator() == directory_context_menu)
|
|
target_directory = directory_view->selected_file_paths()[0];
|
|
else
|
|
target_directory = directory_view->path();
|
|
do_paste(target_directory, directory_view->window());
|
|
refresh_tree_view();
|
|
},
|
|
window);
|
|
|
|
auto go_back_action = GUI::CommonActions::make_go_back_action(
|
|
[&](auto&) {
|
|
directory_view->open_previous_directory();
|
|
},
|
|
window);
|
|
|
|
auto go_forward_action = GUI::CommonActions::make_go_forward_action(
|
|
[&](auto&) {
|
|
directory_view->open_next_directory();
|
|
},
|
|
window);
|
|
|
|
auto go_home_action = GUI::CommonActions::make_go_home_action(
|
|
[&](auto&) {
|
|
directory_view->open(Core::StandardPaths::home_directory());
|
|
},
|
|
window);
|
|
|
|
GUI::Clipboard::the().on_change = [&](DeprecatedString const& data_type) {
|
|
auto current_location = directory_view->path();
|
|
paste_action->set_enabled(data_type == "text/uri-list" && access(current_location.characters(), W_OK) == 0);
|
|
};
|
|
|
|
auto tree_view_delete_action = GUI::CommonActions::make_delete_action(
|
|
[&](auto&) {
|
|
delete_paths(tree_view_selected_file_paths(), true, window);
|
|
refresh_tree_view();
|
|
},
|
|
&tree_view);
|
|
|
|
// This is a little awkward. The menu action does something different depending on which view has focus.
|
|
// It would be nice to find a good abstraction for this instead of creating a branching action like this.
|
|
auto focus_dependent_delete_action = GUI::CommonActions::make_delete_action([&](auto&) {
|
|
if (tree_view.is_focused())
|
|
tree_view_delete_action->activate();
|
|
else
|
|
directory_view->delete_action().activate();
|
|
refresh_tree_view();
|
|
});
|
|
focus_dependent_delete_action->set_enabled(false);
|
|
|
|
auto mkdir_action = GUI::Action::create("&New Directory...", { Mod_Ctrl | Mod_Shift, Key_N }, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/mkdir.png"sv)), [&](GUI::Action const&) {
|
|
directory_view->mkdir_action().activate();
|
|
refresh_tree_view();
|
|
});
|
|
|
|
auto touch_action = GUI::Action::create("New &File...", { Mod_Ctrl | Mod_Shift, Key_F }, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/new.png"sv)), [&](GUI::Action const&) {
|
|
directory_view->touch_action().activate();
|
|
refresh_tree_view();
|
|
});
|
|
|
|
auto file_menu = TRY(window->try_add_menu("&File"_short_string));
|
|
TRY(file_menu->try_add_action(mkdir_action));
|
|
TRY(file_menu->try_add_action(touch_action));
|
|
TRY(file_menu->try_add_action(focus_dependent_delete_action));
|
|
TRY(file_menu->try_add_action(directory_view->rename_action()));
|
|
TRY(file_menu->try_add_separator());
|
|
TRY(file_menu->try_add_action(properties_action));
|
|
TRY(file_menu->try_add_separator());
|
|
TRY(file_menu->try_add_action(GUI::CommonActions::make_quit_action([](auto&) {
|
|
GUI::Application::the()->quit();
|
|
})));
|
|
|
|
auto edit_menu = TRY(window->try_add_menu("&Edit"_short_string));
|
|
TRY(edit_menu->try_add_action(cut_action));
|
|
TRY(edit_menu->try_add_action(copy_action));
|
|
TRY(edit_menu->try_add_action(paste_action));
|
|
TRY(edit_menu->try_add_separator());
|
|
TRY(edit_menu->try_add_action(select_all_action));
|
|
|
|
auto show_dotfiles_in_view = [&](bool show_dotfiles) {
|
|
directory_view->set_should_show_dotfiles(show_dotfiles);
|
|
directories_model->set_should_show_dotfiles(show_dotfiles);
|
|
};
|
|
|
|
auto show_dotfiles_action = GUI::Action::create_checkable("&Show Dotfiles", { Mod_Ctrl, Key_H }, [&](auto& action) {
|
|
show_dotfiles_in_view(action.is_checked());
|
|
refresh_tree_view();
|
|
Config::write_bool("FileManager"sv, "DirectoryView"sv, "ShowDotFiles"sv, action.is_checked());
|
|
});
|
|
|
|
auto show_dotfiles = Config::read_bool("FileManager"sv, "DirectoryView"sv, "ShowDotFiles"sv, false);
|
|
show_dotfiles |= initial_location.contains("/."sv);
|
|
show_dotfiles_action->set_checked(show_dotfiles);
|
|
show_dotfiles_in_view(show_dotfiles);
|
|
|
|
auto view_menu = TRY(window->try_add_menu("&View"_short_string));
|
|
auto layout_menu = TRY(view_menu->try_add_submenu("&Layout"_short_string));
|
|
TRY(layout_menu->try_add_action(*layout_toolbar_action));
|
|
TRY(layout_menu->try_add_action(*layout_location_action));
|
|
TRY(layout_menu->try_add_action(*layout_statusbar_action));
|
|
TRY(layout_menu->try_add_action(*layout_folderpane_action));
|
|
|
|
TRY(view_menu->try_add_separator());
|
|
|
|
TRY(view_menu->try_add_action(directory_view->view_as_icons_action()));
|
|
TRY(view_menu->try_add_action(directory_view->view_as_table_action()));
|
|
TRY(view_menu->try_add_action(directory_view->view_as_columns_action()));
|
|
TRY(view_menu->try_add_separator());
|
|
TRY(view_menu->try_add_action(show_dotfiles_action));
|
|
|
|
auto go_to_location_action = GUI::Action::create("Go to &Location...", { Mod_Ctrl, Key_L }, Key_F6, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/go-to.png"sv)), [&](auto&) {
|
|
toolbar_container.set_visible(true);
|
|
breadcrumb_toolbar.set_visible(true);
|
|
breadcrumbbar.show_location_text_box();
|
|
});
|
|
|
|
auto go_menu = TRY(window->try_add_menu("&Go"_short_string));
|
|
TRY(go_menu->try_add_action(go_back_action));
|
|
TRY(go_menu->try_add_action(go_forward_action));
|
|
TRY(go_menu->try_add_action(open_parent_directory_action));
|
|
TRY(go_menu->try_add_action(open_child_directory_action));
|
|
TRY(go_menu->try_add_action(go_home_action));
|
|
TRY(go_menu->try_add_action(go_to_location_action));
|
|
TRY(go_menu->try_add_separator());
|
|
TRY(go_menu->try_add_action(directory_view->open_terminal_action()));
|
|
|
|
auto help_menu = TRY(window->try_add_menu("&Help"_short_string));
|
|
TRY(help_menu->try_add_action(GUI::CommonActions::make_command_palette_action(window)));
|
|
TRY(help_menu->try_add_action(GUI::CommonActions::make_about_action("File Manager"sv, GUI::Icon::default_icon("app-file-manager"sv), window)));
|
|
|
|
(void)TRY(main_toolbar.try_add_action(go_back_action));
|
|
(void)TRY(main_toolbar.try_add_action(go_forward_action));
|
|
(void)TRY(main_toolbar.try_add_action(open_parent_directory_action));
|
|
(void)TRY(main_toolbar.try_add_action(go_home_action));
|
|
|
|
TRY(main_toolbar.try_add_separator());
|
|
(void)TRY(main_toolbar.try_add_action(directory_view->open_terminal_action()));
|
|
|
|
TRY(main_toolbar.try_add_separator());
|
|
(void)TRY(main_toolbar.try_add_action(mkdir_action));
|
|
(void)TRY(main_toolbar.try_add_action(touch_action));
|
|
TRY(main_toolbar.try_add_separator());
|
|
|
|
(void)TRY(main_toolbar.try_add_action(focus_dependent_delete_action));
|
|
(void)TRY(main_toolbar.try_add_action(directory_view->rename_action()));
|
|
|
|
TRY(main_toolbar.try_add_separator());
|
|
(void)TRY(main_toolbar.try_add_action(cut_action));
|
|
(void)TRY(main_toolbar.try_add_action(copy_action));
|
|
(void)TRY(main_toolbar.try_add_action(paste_action));
|
|
|
|
TRY(main_toolbar.try_add_separator());
|
|
(void)TRY(main_toolbar.try_add_action(directory_view->view_as_icons_action()));
|
|
(void)TRY(main_toolbar.try_add_action(directory_view->view_as_table_action()));
|
|
(void)TRY(main_toolbar.try_add_action(directory_view->view_as_columns_action()));
|
|
|
|
breadcrumbbar.on_path_change = [&](auto selected_path) {
|
|
if (FileSystem::is_directory(selected_path)) {
|
|
directory_view->open(selected_path);
|
|
} else {
|
|
dbgln("Breadcrumb path '{}' doesn't exist", selected_path);
|
|
breadcrumbbar.set_current_path(directory_view->path());
|
|
}
|
|
};
|
|
|
|
directory_view->on_path_change = [&](DeprecatedString const& new_path, bool can_read_in_path, bool can_write_in_path) {
|
|
auto icon = GUI::FileIconProvider::icon_for_path(new_path);
|
|
auto* bitmap = icon.bitmap_for_size(16);
|
|
window->set_icon(bitmap);
|
|
|
|
window->set_title(DeprecatedString::formatted("{} - File Manager", new_path));
|
|
|
|
breadcrumbbar.set_current_path(new_path);
|
|
|
|
if (!is_reacting_to_tree_view_selection_change) {
|
|
auto new_index = directories_model->index(new_path, GUI::FileSystemModel::Column::Name);
|
|
if (new_index.is_valid()) {
|
|
tree_view.expand_all_parents_of(new_index);
|
|
tree_view.set_cursor(new_index, GUI::AbstractView::SelectionUpdate::Set);
|
|
}
|
|
}
|
|
|
|
mkdir_action->set_enabled(can_write_in_path);
|
|
touch_action->set_enabled(can_write_in_path);
|
|
paste_action->set_enabled(can_write_in_path && GUI::Clipboard::the().fetch_mime_type() == "text/uri-list");
|
|
go_forward_action->set_enabled(directory_view->path_history_position() < directory_view->path_history_size() - 1);
|
|
go_back_action->set_enabled(directory_view->path_history_position() > 0);
|
|
open_parent_directory_action->set_enabled(breadcrumbbar.has_parent_segment());
|
|
open_child_directory_action->set_enabled(breadcrumbbar.has_child_segment());
|
|
directory_view->view_as_table_action().set_enabled(can_read_in_path);
|
|
directory_view->view_as_icons_action().set_enabled(can_read_in_path);
|
|
directory_view->view_as_columns_action().set_enabled(can_read_in_path);
|
|
};
|
|
|
|
directory_view->on_accepted_drop = [&] {
|
|
refresh_tree_view();
|
|
};
|
|
|
|
directory_view->on_status_message = [&](StringView message) {
|
|
statusbar.set_text(String::from_utf8(message).release_value_but_fixme_should_propagate_errors());
|
|
};
|
|
|
|
directory_view->on_thumbnail_progress = [&](int done, int total) {
|
|
if (done == total) {
|
|
progressbar.set_visible(false);
|
|
return;
|
|
}
|
|
progressbar.set_range(0, total);
|
|
progressbar.set_value(done);
|
|
progressbar.set_visible(true);
|
|
};
|
|
|
|
directory_view->on_selection_change = [&](GUI::AbstractView& view) {
|
|
auto& selection = view.selection();
|
|
cut_action->set_enabled(!selection.is_empty() && access(directory_view->path().characters(), W_OK) == 0);
|
|
copy_action->set_enabled(!selection.is_empty());
|
|
focus_dependent_delete_action->set_enabled((!tree_view.selection().is_empty() && tree_view.is_focused())
|
|
|| (!directory_view->current_view().selection().is_empty() && access(directory_view->path().characters(), W_OK) == 0));
|
|
};
|
|
|
|
auto directory_open_action = GUI::Action::create("Open", TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/open.png"sv)), [&](auto&) {
|
|
directory_view->open(directory_view->selected_file_paths().first());
|
|
});
|
|
|
|
TRY(directory_context_menu->try_add_action(directory_open_action));
|
|
TRY(directory_context_menu->try_add_action(open_in_new_window_action));
|
|
TRY(directory_context_menu->try_add_action(open_in_new_terminal_action));
|
|
TRY(directory_context_menu->try_add_separator());
|
|
TRY(directory_context_menu->try_add_action(cut_action));
|
|
TRY(directory_context_menu->try_add_action(copy_action));
|
|
TRY(directory_context_menu->try_add_action(folder_specific_paste_action));
|
|
TRY(directory_context_menu->try_add_action(directory_view->delete_action()));
|
|
TRY(directory_context_menu->try_add_action(directory_view->rename_action()));
|
|
TRY(directory_context_menu->try_add_action(shortcut_action));
|
|
TRY(directory_context_menu->try_add_action(create_archive_action));
|
|
TRY(directory_context_menu->try_add_separator());
|
|
TRY(directory_context_menu->try_add_action(properties_action));
|
|
|
|
TRY(directory_view_context_menu->try_add_action(mkdir_action));
|
|
TRY(directory_view_context_menu->try_add_action(touch_action));
|
|
TRY(directory_view_context_menu->try_add_action(paste_action));
|
|
TRY(directory_view_context_menu->try_add_action(directory_view->open_terminal_action()));
|
|
TRY(directory_view_context_menu->try_add_separator());
|
|
TRY(directory_view_context_menu->try_add_action(show_dotfiles_action));
|
|
TRY(directory_view_context_menu->try_add_separator());
|
|
TRY(directory_view_context_menu->try_add_action(properties_action));
|
|
|
|
TRY(tree_view_directory_context_menu->try_add_action(open_in_new_window_action));
|
|
TRY(tree_view_directory_context_menu->try_add_action(open_in_new_terminal_action));
|
|
TRY(tree_view_directory_context_menu->try_add_separator());
|
|
TRY(tree_view_directory_context_menu->try_add_action(mkdir_action));
|
|
TRY(tree_view_directory_context_menu->try_add_action(touch_action));
|
|
TRY(tree_view_directory_context_menu->try_add_action(cut_action));
|
|
TRY(tree_view_directory_context_menu->try_add_action(copy_action));
|
|
TRY(tree_view_directory_context_menu->try_add_action(paste_action));
|
|
TRY(tree_view_directory_context_menu->try_add_action(tree_view_delete_action));
|
|
TRY(tree_view_directory_context_menu->try_add_separator());
|
|
TRY(tree_view_directory_context_menu->try_add_action(properties_action));
|
|
|
|
RefPtr<GUI::Menu> file_context_menu;
|
|
Vector<NonnullRefPtr<LauncherHandler>> current_file_handlers;
|
|
RefPtr<GUI::Action> file_context_menu_action_default_action;
|
|
|
|
directory_view->on_context_menu_request = [&](GUI::ModelIndex const& index, GUI::ContextMenuEvent const& event) {
|
|
if (index.is_valid()) {
|
|
auto& node = directory_view->node(index);
|
|
|
|
if (node.is_directory()) {
|
|
auto should_get_enabled = access(node.full_path().characters(), W_OK) == 0 && GUI::Clipboard::the().fetch_mime_type() == "text/uri-list";
|
|
folder_specific_paste_action->set_enabled(should_get_enabled);
|
|
directory_context_menu->popup(event.screen_position(), directory_open_action);
|
|
} else {
|
|
file_context_menu = GUI::Menu::construct("Directory View File"_string.release_value_but_fixme_should_propagate_errors());
|
|
|
|
bool added_launch_file_handlers = add_launch_handler_actions_to_menu(file_context_menu, directory_view, node.full_path(), file_context_menu_action_default_action, current_file_handlers);
|
|
if (added_launch_file_handlers)
|
|
file_context_menu->add_separator();
|
|
|
|
file_context_menu->add_action(cut_action);
|
|
file_context_menu->add_action(copy_action);
|
|
file_context_menu->add_action(paste_action);
|
|
file_context_menu->add_action(directory_view->delete_action());
|
|
file_context_menu->add_action(directory_view->rename_action());
|
|
file_context_menu->add_action(shortcut_action);
|
|
file_context_menu->add_action(create_archive_action);
|
|
file_context_menu->add_separator();
|
|
|
|
if (Gfx::Bitmap::is_path_a_supported_image_format(node.name)) {
|
|
file_context_menu->add_action(set_wallpaper_action);
|
|
file_context_menu->add_separator();
|
|
}
|
|
|
|
if (node.full_path().ends_with(".zip"sv, AK::CaseSensitivity::CaseInsensitive)) {
|
|
file_context_menu->add_action(unzip_archive_action);
|
|
file_context_menu->add_separator();
|
|
}
|
|
|
|
file_context_menu->add_action(properties_action);
|
|
file_context_menu->popup(event.screen_position(), file_context_menu_action_default_action);
|
|
}
|
|
} else {
|
|
directory_view_context_menu->popup(event.screen_position());
|
|
}
|
|
};
|
|
|
|
tree_view.on_selection_change = [&] {
|
|
focus_dependent_delete_action->set_enabled((!tree_view.selection().is_empty() && tree_view.is_focused())
|
|
|| !directory_view->current_view().selection().is_empty());
|
|
|
|
if (tree_view.selection().is_empty())
|
|
return;
|
|
|
|
if (directories_model->m_previously_selected_index.is_valid())
|
|
directories_model->update_node_on_selection(directories_model->m_previously_selected_index, false);
|
|
|
|
auto const& index = tree_view.selection().first();
|
|
directories_model->update_node_on_selection(index, true);
|
|
directories_model->m_previously_selected_index = index;
|
|
|
|
auto path = directories_model->full_path(index);
|
|
if (directory_view->path() == path)
|
|
return;
|
|
TemporaryChange change(is_reacting_to_tree_view_selection_change, true);
|
|
directory_view->open(path);
|
|
cut_action->set_enabled(!tree_view.selection().is_empty());
|
|
copy_action->set_enabled(!tree_view.selection().is_empty());
|
|
directory_view->delete_action().set_enabled(!tree_view.selection().is_empty());
|
|
};
|
|
|
|
tree_view.on_focus_change = [&](bool has_focus, [[maybe_unused]] GUI::FocusSource const source) {
|
|
focus_dependent_delete_action->set_enabled((!tree_view.selection().is_empty() && has_focus)
|
|
|| !directory_view->current_view().selection().is_empty());
|
|
};
|
|
|
|
tree_view.on_context_menu_request = [&](GUI::ModelIndex const& index, GUI::ContextMenuEvent const& event) {
|
|
if (index.is_valid()) {
|
|
tree_view_directory_context_menu->popup(event.screen_position());
|
|
}
|
|
};
|
|
|
|
breadcrumbbar.on_paths_drop = [&](auto path, GUI::DropEvent const& event) {
|
|
bool const has_accepted_drop = handle_drop(event, path, window).release_value_but_fixme_should_propagate_errors();
|
|
if (has_accepted_drop)
|
|
refresh_tree_view();
|
|
};
|
|
|
|
tree_view.on_drop = [&](GUI::ModelIndex const& index, GUI::DropEvent const& event) {
|
|
auto const& target_node = directories_model->node(index);
|
|
bool const has_accepted_drop = handle_drop(event, target_node.full_path(), window).release_value_but_fixme_should_propagate_errors();
|
|
if (has_accepted_drop) {
|
|
refresh_tree_view();
|
|
const_cast<GUI::DropEvent&>(event).accept();
|
|
}
|
|
};
|
|
|
|
directory_view->open(initial_location);
|
|
directory_view->set_focus(true);
|
|
|
|
paste_action->set_enabled(GUI::Clipboard::the().fetch_mime_type() == "text/uri-list" && access(initial_location.characters(), W_OK) == 0);
|
|
|
|
window->set_rect({ left, top, width, height });
|
|
if (was_maximized)
|
|
window->set_maximized(true);
|
|
|
|
window->show();
|
|
|
|
directory_view->set_view_mode_from_string(Config::read_string("FileManager"sv, "DirectoryView"sv, "ViewMode"sv, "Icon"sv));
|
|
|
|
if (!entry_focused_on_init.is_empty()) {
|
|
auto matches = directory_view->current_view().model()->matches(entry_focused_on_init, GUI::Model::MatchesFlag::MatchFull | GUI::Model::MatchesFlag::FirstMatchOnly);
|
|
if (!matches.is_empty())
|
|
directory_view->current_view().set_cursor(matches.first(), GUI::AbstractView::SelectionUpdate::Set);
|
|
}
|
|
|
|
// Write window position to config file on close request.
|
|
window->on_close_request = [&] {
|
|
Config::write_bool("FileManager"sv, "Window"sv, "Maximized"sv, window->is_maximized());
|
|
if (!window->is_maximized()) {
|
|
Config::write_i32("FileManager"sv, "Window"sv, "Left"sv, window->x());
|
|
Config::write_i32("FileManager"sv, "Window"sv, "Top"sv, window->y());
|
|
Config::write_i32("FileManager"sv, "Window"sv, "Width"sv, window->width());
|
|
Config::write_i32("FileManager"sv, "Window"sv, "Height"sv, window->height());
|
|
}
|
|
return GUI::Window::CloseRequestDecision::Close;
|
|
};
|
|
|
|
return GUI::Application::the()->exec();
|
|
}
|