mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 05:37:34 +00:00
SpaceAnalyzer: Added context menu for nodes
Currently supports 3 actions: Open, Copy Path, and Delete.
This commit is contained in:
parent
8d6525fc50
commit
ff018607a1
4 changed files with 99 additions and 1 deletions
|
@ -7,4 +7,4 @@ set(SOURCES
|
||||||
)
|
)
|
||||||
|
|
||||||
serenity_app(SpaceAnalyzer ICON app-space-analyzer)
|
serenity_app(SpaceAnalyzer ICON app-space-analyzer)
|
||||||
target_link_libraries(SpaceAnalyzer LibGfx LibGUI)
|
target_link_libraries(SpaceAnalyzer LibDesktop LibGfx LibGUI)
|
||||||
|
|
|
@ -312,6 +312,8 @@ void TreeMapWidget::mousedown_event(GUI::MouseEvent& event)
|
||||||
|
|
||||||
void TreeMapWidget::doubleclick_event(GUI::MouseEvent& event)
|
void TreeMapWidget::doubleclick_event(GUI::MouseEvent& event)
|
||||||
{
|
{
|
||||||
|
if (event.button() != GUI::MouseButton::Left)
|
||||||
|
return;
|
||||||
const TreeMapNode* node = path_node(m_viewpoint);
|
const TreeMapNode* node = path_node(m_viewpoint);
|
||||||
if (node && !node_is_leaf(*node)) {
|
if (node && !node_is_leaf(*node)) {
|
||||||
Vector<int> path = path_to_position(event.position());
|
Vector<int> path = path_to_position(event.position());
|
||||||
|
@ -341,6 +343,12 @@ void TreeMapWidget::mousewheel_event(GUI::MouseEvent& event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TreeMapWidget::context_menu_event(GUI::ContextMenuEvent& context_menu_event)
|
||||||
|
{
|
||||||
|
if (on_context_menu_request)
|
||||||
|
on_context_menu_request(context_menu_event);
|
||||||
|
}
|
||||||
|
|
||||||
void TreeMapWidget::set_tree(RefPtr<TreeMap> tree)
|
void TreeMapWidget::set_tree(RefPtr<TreeMap> tree)
|
||||||
{
|
{
|
||||||
m_tree = tree;
|
m_tree = tree;
|
||||||
|
|
|
@ -50,6 +50,7 @@ class TreeMapWidget final : public GUI::Frame {
|
||||||
public:
|
public:
|
||||||
virtual ~TreeMapWidget() override;
|
virtual ~TreeMapWidget() override;
|
||||||
Function<void()> on_path_change;
|
Function<void()> on_path_change;
|
||||||
|
Function<void(GUI::ContextMenuEvent&)> on_context_menu_request;
|
||||||
size_t path_size() const;
|
size_t path_size() const;
|
||||||
const TreeMapNode* path_node(size_t n) const;
|
const TreeMapNode* path_node(size_t n) const;
|
||||||
size_t viewpoint() const;
|
size_t viewpoint() const;
|
||||||
|
@ -62,6 +63,7 @@ private:
|
||||||
virtual void mousedown_event(GUI::MouseEvent&) override;
|
virtual void mousedown_event(GUI::MouseEvent&) override;
|
||||||
virtual void doubleclick_event(GUI::MouseEvent&) override;
|
virtual void doubleclick_event(GUI::MouseEvent&) override;
|
||||||
virtual void mousewheel_event(GUI::MouseEvent&) override;
|
virtual void mousewheel_event(GUI::MouseEvent&) override;
|
||||||
|
virtual void context_menu_event(GUI::ContextMenuEvent&) override;
|
||||||
|
|
||||||
bool rect_can_contain_children(const Gfx::IntRect& rect) const;
|
bool rect_can_contain_children(const Gfx::IntRect& rect) const;
|
||||||
bool rect_can_contain_label(const Gfx::IntRect& rect) const;
|
bool rect_can_contain_label(const Gfx::IntRect& rect) const;
|
||||||
|
|
|
@ -27,15 +27,19 @@
|
||||||
#include <AK/Queue.h>
|
#include <AK/Queue.h>
|
||||||
#include <AK/QuickSort.h>
|
#include <AK/QuickSort.h>
|
||||||
#include <AK/RefCounted.h>
|
#include <AK/RefCounted.h>
|
||||||
|
#include <AK/URL.h>
|
||||||
#include <Applications/SpaceAnalyzer/SpaceAnalyzerGML.h>
|
#include <Applications/SpaceAnalyzer/SpaceAnalyzerGML.h>
|
||||||
#include <LibCore/DirIterator.h>
|
#include <LibCore/DirIterator.h>
|
||||||
#include <LibCore/File.h>
|
#include <LibCore/File.h>
|
||||||
|
#include <LibDesktop/Launcher.h>
|
||||||
#include <LibGUI/AboutDialog.h>
|
#include <LibGUI/AboutDialog.h>
|
||||||
#include <LibGUI/Application.h>
|
#include <LibGUI/Application.h>
|
||||||
#include <LibGUI/BreadcrumbBar.h>
|
#include <LibGUI/BreadcrumbBar.h>
|
||||||
|
#include <LibGUI/Clipboard.h>
|
||||||
#include <LibGUI/Icon.h>
|
#include <LibGUI/Icon.h>
|
||||||
#include <LibGUI/Menu.h>
|
#include <LibGUI/Menu.h>
|
||||||
#include <LibGUI/MenuBar.h>
|
#include <LibGUI/MenuBar.h>
|
||||||
|
#include <LibGUI/MessageBox.h>
|
||||||
#include <LibGUI/StatusBar.h>
|
#include <LibGUI/StatusBar.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
@ -241,6 +245,28 @@ static void analyze(RefPtr<Tree> tree, SpaceAnalyzer::TreeMapWidget& treemapwidg
|
||||||
treemapwidget.set_tree(tree);
|
treemapwidget.set_tree(tree);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool is_removable(const String& absolute_path)
|
||||||
|
{
|
||||||
|
ASSERT(!absolute_path.is_empty());
|
||||||
|
int access_result = access(absolute_path.characters(), W_OK);
|
||||||
|
if (access_result != 0 && errno != EACCES)
|
||||||
|
perror("access");
|
||||||
|
return access_result == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String get_absolute_path_to_selected_node(const SpaceAnalyzer::TreeMapWidget& treemapwidget, bool include_last_node = true)
|
||||||
|
{
|
||||||
|
StringBuilder path_builder;
|
||||||
|
for (size_t k = 0; k < treemapwidget.path_size() - (include_last_node ? 0 : 1); k++) {
|
||||||
|
if (k != 0) {
|
||||||
|
path_builder.append('/');
|
||||||
|
}
|
||||||
|
const SpaceAnalyzer::TreeMapNode* node = treemapwidget.path_node(k);
|
||||||
|
path_builder.append(node->name());
|
||||||
|
}
|
||||||
|
return path_builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char* argv[])
|
int main(int argc, char* argv[])
|
||||||
{
|
{
|
||||||
auto app = GUI::Application::construct(argc, argv);
|
auto app = GUI::Application::construct(argc, argv);
|
||||||
|
@ -274,6 +300,57 @@ int main(int argc, char* argv[])
|
||||||
help_menu.add_action(GUI::CommonActions::make_about_action(APP_NAME, app_icon, window));
|
help_menu.add_action(GUI::CommonActions::make_about_action(APP_NAME, app_icon, window));
|
||||||
app->set_menubar(move(menubar));
|
app->set_menubar(move(menubar));
|
||||||
|
|
||||||
|
// Configure the nodes context menu.
|
||||||
|
auto open_folder_action = GUI::Action::create("Open Folder", { Mod_Ctrl, Key_O }, Gfx::Bitmap::load_from_file("/res/icons/16x16/open.png"), [&](auto&) {
|
||||||
|
Desktop::Launcher::open(URL::create_with_file_protocol(get_absolute_path_to_selected_node(treemapwidget)));
|
||||||
|
});
|
||||||
|
auto open_containing_folder_action = GUI::Action::create("Open Containing Folder", { Mod_Ctrl, Key_O }, Gfx::Bitmap::load_from_file("/res/icons/16x16/open.png"), [&](auto&) {
|
||||||
|
Desktop::Launcher::open(URL::create_with_file_protocol(get_absolute_path_to_selected_node(treemapwidget, false)));
|
||||||
|
});
|
||||||
|
auto copy_path_action = GUI::Action::create("Copy Path to Clipboard", { Mod_Ctrl, Key_C }, Gfx::Bitmap::load_from_file("/res/icons/16x16/edit-copy.png"), [&](auto&) {
|
||||||
|
GUI::Clipboard::the().set_plain_text(get_absolute_path_to_selected_node(treemapwidget));
|
||||||
|
});
|
||||||
|
auto delete_action = GUI::CommonActions::make_delete_action([&](auto&) {
|
||||||
|
String selected_node_path = get_absolute_path_to_selected_node(treemapwidget);
|
||||||
|
bool try_again = true;
|
||||||
|
while (try_again) {
|
||||||
|
try_again = false;
|
||||||
|
|
||||||
|
auto deletion_result = Core::File::remove(selected_node_path, Core::File::RecursionMode::Allowed, true);
|
||||||
|
if (deletion_result.is_error()) {
|
||||||
|
auto retry_message_result = GUI::MessageBox::show(window,
|
||||||
|
String::formatted("Failed to delete \"{}\": {}. Retry?",
|
||||||
|
deletion_result.error().file,
|
||||||
|
deletion_result.error().error_code.string()),
|
||||||
|
"Deletion failed",
|
||||||
|
GUI::MessageBox::Type::Error,
|
||||||
|
GUI::MessageBox::InputType::YesNo);
|
||||||
|
if (retry_message_result == GUI::MessageBox::ExecYes) {
|
||||||
|
try_again = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
GUI::MessageBox::show(window,
|
||||||
|
String::formatted("Successfuly deleted \"{}\".", selected_node_path),
|
||||||
|
"Deletion completed",
|
||||||
|
GUI::MessageBox::Type::Information,
|
||||||
|
GUI::MessageBox::InputType::OK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Refreshing data always causes resetting the viewport back to "/".
|
||||||
|
// It would be great if we found a way to preserve viewport across refreshes.
|
||||||
|
analyze(tree, treemapwidget, statusbar);
|
||||||
|
});
|
||||||
|
// TODO: Both these menus could've been implemented as one, but it's impossible to change action text after it's shown once.
|
||||||
|
auto folder_node_context_menu = GUI::Menu::construct();
|
||||||
|
folder_node_context_menu->add_action(*open_folder_action);
|
||||||
|
folder_node_context_menu->add_action(*copy_path_action);
|
||||||
|
folder_node_context_menu->add_action(*delete_action);
|
||||||
|
auto file_node_context_menu = GUI::Menu::construct();
|
||||||
|
file_node_context_menu->add_action(*open_containing_folder_action);
|
||||||
|
file_node_context_menu->add_action(*copy_path_action);
|
||||||
|
file_node_context_menu->add_action(*delete_action);
|
||||||
|
|
||||||
// Configure event handlers.
|
// Configure event handlers.
|
||||||
breadcrumbbar.on_segment_click = [&](size_t index) {
|
breadcrumbbar.on_segment_click = [&](size_t index) {
|
||||||
ASSERT(index < treemapwidget.path_size());
|
ASSERT(index < treemapwidget.path_size());
|
||||||
|
@ -291,6 +368,17 @@ int main(int argc, char* argv[])
|
||||||
}
|
}
|
||||||
breadcrumbbar.set_selected_segment(treemapwidget.viewpoint());
|
breadcrumbbar.set_selected_segment(treemapwidget.viewpoint());
|
||||||
};
|
};
|
||||||
|
treemapwidget.on_context_menu_request = [&](const GUI::ContextMenuEvent& event) {
|
||||||
|
String selected_node_path = get_absolute_path_to_selected_node(treemapwidget);
|
||||||
|
if (selected_node_path.is_empty())
|
||||||
|
return;
|
||||||
|
delete_action->set_enabled(is_removable(selected_node_path));
|
||||||
|
if (Core::File::is_directory(selected_node_path)) {
|
||||||
|
folder_node_context_menu->popup(event.screen_position());
|
||||||
|
} else {
|
||||||
|
file_node_context_menu->popup(event.screen_position());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// At startup automatically do an analysis of root.
|
// At startup automatically do an analysis of root.
|
||||||
analyze(tree, treemapwidget, statusbar);
|
analyze(tree, treemapwidget, statusbar);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue