diff --git a/src/filetree.rs b/src/filetree.rs index ad0ac86..342e9dc 100644 --- a/src/filetree.rs +++ b/src/filetree.rs @@ -38,6 +38,15 @@ impl Tree { node = p; } } + pub fn get_full_path(&self, mut node: NodeID) -> String { + let mut filename = self.elems[node].name.to_string(); + while let Some(p) = self.elems[node].parent { + let separator = if cfg!(unix) { "/" } else { "\\" }; + filename = self.elems[p].name.to_string() + separator + filename.as_str(); + node = p; + } + filename + } pub fn add_elem(&mut self, parent: NodeID, name: String, is_file: bool, size: u64) { self.last_id += 1; let node = Node { diff --git a/src/gui.rs b/src/gui.rs index 228e6ef..9c442c2 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -8,6 +8,20 @@ pub fn initiate_ui() { let application = gtk::Application::new(Some(config::APP_NAME), ApplicationFlags::HANDLES_OPEN); application.connect_open(build_ui); application.connect_activate(|app| build_ui(app, &[], "")); + + let action_show = gtk::gio::SimpleAction::new("show", Some(glib::VariantTy::STRING)); + action_show.connect_activate(glib::clone!(@weak application => move |_, param| { + param.map(|x| { + gtk::show_uri(None::<>k::Window>, x.to_string().trim_matches('\''), 0); + //dbg!(gtk::gio::AppInfo::launch_default_for_uri(x.to_string().as_str(), None::<>k::gdk::AppLaunchContext>)) + }); + })); + let action_disabled = gtk::gio::SimpleAction::new("disabled", None); + action_disabled.set_enabled(false); + + application.add_action(&action_show); + application.add_action(&action_disabled); + application.run(); } diff --git a/src/gui/treemap_widget/imp.rs b/src/gui/treemap_widget/imp.rs index a8362bd..3e4cee8 100644 --- a/src/gui/treemap_widget/imp.rs +++ b/src/gui/treemap_widget/imp.rs @@ -1,8 +1,8 @@ use crate::{ - bytes_display, filetree::{Node, NodeID, Tree}, node_color, squarify::{self, GUINode}, + utils, }; use gtk::gdk::RGBA; use gtk::glib; @@ -10,6 +10,7 @@ use gtk::graphene::{Point, Rect}; use gtk::pango; use gtk::prelude::*; use gtk::subclass::prelude::*; +use gtk::PopoverMenu; use gtk::Tooltip; use once_cell::sync::Lazy; use std::cell::RefCell; @@ -27,6 +28,7 @@ pub struct TreeMapWidget { pub thread_handle: RefCell>>, last_width: RefCell, last_height: RefCell, + popover_menu: RefCell>, } #[glib::object_subclass] @@ -79,12 +81,7 @@ fn query_tooltip( let found_node = locate_node(&tree, root, &gui_node_map, x as f32, y as f32); if let Some(node) = found_node { tooltip.set_text(Some( - format!( - "{} ({})", - node.name, - bytes_display::bytes_display(node.size) - ) - .as_str(), + format!("{} ({})", node.name, utils::bytes_display(node.size)).as_str(), )); // unwrap okay, we found it already let rect = gui_node_map.get(&node.id).unwrap().rect; @@ -101,6 +98,57 @@ fn query_tooltip( } } +fn create_context_menu(widget: &super::TreeMapWidget, x: f64, y: f64) { + let imp = widget.imp(); + let gui_node_map = imp.gui_node_map.borrow(); + let tree_mutex = &imp.tree_mutex; + let (found_node, uri, parent_uri) = { + let tree = tree_mutex.lock().unwrap(); + let root = tree.get_elem(0); + let found_node = + locate_node(&tree, root, &gui_node_map, x as f32, y as f32).map(|x| x.clone()); + let uri = found_node + .as_ref() + .and_then(|node| glib::filename_to_uri(tree.get_full_path(node.id), None).ok()); + let parent_uri = found_node + .as_ref() + .and_then(|node| node.parent) + .map(|node_id| tree.get_elem(node_id)) + .and_then(|node| glib::filename_to_uri(tree.get_full_path(node.id), None).ok()); + (found_node, uri, parent_uri) + }; + if let (Some(node), Some(uri)) = (found_node, uri) { + if let Some(popover_menu) = imp.popover_menu.borrow().as_ref() { + popover_menu.set_pointing_to(Some(>k::gdk::Rectangle::new(x as i32, y as i32, 1, 1))); + let menu = gtk::gio::Menu::new(); + let menu_item_name = gtk::gio::MenuItem::new( + Some(&utils::abbreviate_string(&node.name, 15)), + Some("app.disabled"), + ); + menu.append_item(&menu_item_name); + let open_action_name = format!("app.show(\"{}\")", uri); + let menu_item_open = + gtk::gio::MenuItem::new(Some("Open"), Some(open_action_name.as_str())); + menu.append_item(&menu_item_open); + + if node.is_file { + if let Some(parent_uri) = parent_uri { + let dir_action_name = format!("app.show(\"{}\")", parent_uri); + let menu_item_dir = gtk::gio::MenuItem::new( + Some("Show directory"), + Some(dir_action_name.as_str()), + ); + menu.append_item(&menu_item_dir); + } + } + + popover_menu.set_menu_model(Some(&menu)); + + popover_menu.show(); + } + } +} + // try making this an associated function fn refresh(widget: &super::TreeMapWidget) -> Continue { let imp = widget.imp(); @@ -154,6 +202,22 @@ impl ObjectImpl for TreeMapWidget { obj.set_height_request(100); obj.set_has_tooltip(true); obj.connect_query_tooltip(query_tooltip); + self.popover_menu.replace(Some({ + let menu = PopoverMenu::from_model(None::<>k::gio::MenuModel>); + menu.set_parent(&*obj); + menu.set_has_arrow(false); + menu.set_halign(gtk::Align::Start); + menu + })); + + let right_click = gtk::GestureClick::new(); + right_click.set_button(gtk::gdk::ffi::GDK_BUTTON_SECONDARY as u32); + right_click.connect_pressed(glib::clone!(@weak obj => move |right_click, _, x, y| { + right_click.set_state(gtk::EventSequenceState::Claimed); + create_context_menu(&obj, x, y); + })); + + obj.add_controller(right_click); // start refresh timer let nano_to_milli = 1000000; @@ -168,6 +232,9 @@ impl ObjectImpl for TreeMapWidget { if let Some(child) = self.child.borrow_mut().take() { child.unparent(); } + if let Some(popover_menu) = self.popover_menu.borrow_mut().take() { + popover_menu.unparent(); + } } } @@ -189,7 +256,7 @@ fn update_rects( layout.set_text(&format!( "{} ({})", &node.name, - bytes_display::bytes_display(node.size) + utils::bytes_display(node.size) )); let pango_w = pango::units_from_double(gui_node.rect.width() as f64); layout.set_width(pango_w); diff --git a/src/main.rs b/src/main.rs index 04341ed..0280d81 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,12 @@ // prevent a command line window on Windows #![windows_subsystem = "windows"] -mod bytes_display; mod config; mod filetree; mod gui; mod mounts; mod node_color; mod squarify; +mod utils; fn main() { dbg!(mounts::get_mounts()); diff --git a/src/bytes_display.rs b/src/utils.rs similarity index 66% rename from src/bytes_display.rs rename to src/utils.rs index a5a6482..a249fb5 100644 --- a/src/bytes_display.rs +++ b/src/utils.rs @@ -10,3 +10,10 @@ pub fn bytes_display(bytes: u64) -> String { format!("{}B", bytes) } } + +pub fn abbreviate_string(s: &str, max_chars: usize) -> String { + match s.char_indices().nth(max_chars) { + None => s.to_string(), + Some((idx, _)) => s[..idx - 3].to_string() + "...", + } +}