Skip to content

Commit

Permalink
SpaceAnalyzer: Added context menu for nodes
Browse files Browse the repository at this point in the history
Currently supports 3 actions: Open, Copy Path, and Delete.
  • Loading branch information
speles authored and awesomekling committed Feb 21, 2021
1 parent 8d6525f commit ff01860
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 1 deletion.
2 changes: 1 addition & 1 deletion Userland/Applications/SpaceAnalyzer/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ set(SOURCES
)

serenity_app(SpaceAnalyzer ICON app-space-analyzer)
target_link_libraries(SpaceAnalyzer LibGfx LibGUI)
target_link_libraries(SpaceAnalyzer LibDesktop LibGfx LibGUI)
8 changes: 8 additions & 0 deletions Userland/Applications/SpaceAnalyzer/TreeMapWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,8 @@ void TreeMapWidget::mousedown_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);
if (node && !node_is_leaf(*node)) {
Vector<int> path = path_to_position(event.position());
Expand Down Expand Up @@ -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)
{
m_tree = tree;
Expand Down
2 changes: 2 additions & 0 deletions Userland/Applications/SpaceAnalyzer/TreeMapWidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ class TreeMapWidget final : public GUI::Frame {
public:
virtual ~TreeMapWidget() override;
Function<void()> on_path_change;
Function<void(GUI::ContextMenuEvent&)> on_context_menu_request;
size_t path_size() const;
const TreeMapNode* path_node(size_t n) const;
size_t viewpoint() const;
Expand All @@ -62,6 +63,7 @@ class TreeMapWidget final : public GUI::Frame {
virtual void mousedown_event(GUI::MouseEvent&) override;
virtual void doubleclick_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_label(const Gfx::IntRect& rect) const;
Expand Down
88 changes: 88 additions & 0 deletions Userland/Applications/SpaceAnalyzer/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,19 @@
#include <AK/Queue.h>
#include <AK/QuickSort.h>
#include <AK/RefCounted.h>
#include <AK/URL.h>
#include <Applications/SpaceAnalyzer/SpaceAnalyzerGML.h>
#include <LibCore/DirIterator.h>
#include <LibCore/File.h>
#include <LibDesktop/Launcher.h>
#include <LibGUI/AboutDialog.h>
#include <LibGUI/Application.h>
#include <LibGUI/BreadcrumbBar.h>
#include <LibGUI/Clipboard.h>
#include <LibGUI/Icon.h>
#include <LibGUI/Menu.h>
#include <LibGUI/MenuBar.h>
#include <LibGUI/MessageBox.h>
#include <LibGUI/StatusBar.h>
#include <sys/stat.h>

Expand Down Expand Up @@ -241,6 +245,28 @@ static void analyze(RefPtr<Tree> tree, SpaceAnalyzer::TreeMapWidget& treemapwidg
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[])
{
auto app = GUI::Application::construct(argc, argv);
Expand Down Expand Up @@ -274,6 +300,57 @@ int main(int argc, char* argv[])
help_menu.add_action(GUI::CommonActions::make_about_action(APP_NAME, app_icon, window));
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.
breadcrumbbar.on_segment_click = [&](size_t index) {
ASSERT(index < treemapwidget.path_size());
Expand All @@ -291,6 +368,17 @@ int main(int argc, char* argv[])
}
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.
analyze(tree, treemapwidget, statusbar);
Expand Down

0 comments on commit ff01860

Please sign in to comment.