diff --git a/examples/dock/dock_item/mod.rs b/examples/dock/dock_item/mod.rs index c2243316..9523a94b 100644 --- a/examples/dock/dock_item/mod.rs +++ b/examples/dock/dock_item/mod.rs @@ -28,17 +28,10 @@ impl Default for DockItem { impl DockItem { pub fn new() -> Self { - let dc = DragSource::builder() - .name("dock drag source") - .actions(gdk4::DragAction::COPY) - .build(); + let mut actions = gdk4::DragAction::MOVE; + actions.insert(gdk4::DragAction::MOVE); let self_: DockItem = glib::Object::new(&[]).expect("Failed to create DockItem"); - self_.add_controller(&dc); - imp::DockItem::from_instance(&self_) - .drag_controller - .set(dc) - .expect("Failed to set dock item"); self_ } @@ -49,11 +42,10 @@ impl DockItem { .expect("Could not get drag_controller") } - // TODO current method seems very messy... // refactor to emit event for removing the item? - pub fn set_app_info(&self, app_info: &DockObject, i: u32, saved_app_model: &ListStore) { + pub fn set_app_info(&self, dock_object: &DockObject, i: u32, saved_app_model: &ListStore) { let self_ = imp::DockItem::from_instance(self); - if let Ok(app_info_value) = app_info.property("appinfo") { + if let Ok(app_info_value) = dock_object.property("appinfo") { if let Ok(Some(app_info)) = app_info_value.get::>() { self_.image.set_tooltip_text(Some(&app_info.name())); @@ -68,24 +60,34 @@ impl DockItem { ContentProvider::for_value(&file.to_string_lossy().to_value()); drag_controller.set_content(Some(&provider)); } - drag_controller.connect_drag_end(move |_self, _drag, delete_data| { - dbg!("removing", delete_data); - }); - //TODO investigate rare X11 errors when reordering dock items drag_controller.connect_drag_cancel( - glib::clone!(@weak saved_app_model => @default-return true, move |_self, _drag, _delete_data| { - dbg!("removing {}", i); - if saved_app_model.n_items() > i { - saved_app_model.remove(i); - } - true - }), - ); + glib::clone!(@weak saved_app_model, @weak app_info => @default-return true, move |_self, _drag, _delete_data| { + if let Some(item) = saved_app_model.item(i) { + if let Ok(cur_app_info) = item.downcast::() { + if let Ok(Some(cur_app_info)) = cur_app_info.property("appinfo").expect("property appinfo missing from DockObject").get::>() { + // dbg!(cur_app_info.filename()); + if cur_app_info.filename() == app_info.filename() { + saved_app_model.remove(i); + } + } + } + } + true + }), + ); drag_controller.connect_drag_end( - glib::clone!(@weak saved_app_model => move |_self, _drag, delete_data| { - dbg!("removing {}", i); - if delete_data && saved_app_model.n_items() > i { - saved_app_model.remove(i); + glib::clone!(@weak saved_app_model, @weak app_info => move |_self, _drag, _delete_data| { + dbg!(i); + dbg!(_delete_data); + if let Some(item) = saved_app_model.item(i) { + if let Ok(cur_app_info) = item.downcast::() { + if let Ok(Some(cur_app_info)) = cur_app_info.property("appinfo").expect("property appinfo missing from DockObject").get::>() { + // dbg!(cur_app_info.filename()); + if cur_app_info.filename() == app_info.filename() { + saved_app_model.remove(i); + } + } + } } }), ); @@ -97,6 +99,7 @@ impl DockItem { glib::clone!(@weak icon, => move |_self, _drag| { // set drag source icon if possible... // gio Icon is not easily converted to a Paintable, but this seems to be the correct method + _drag.set_selected_action(gdk4::DragAction::MOVE); if let Some(default_display) = &Display::default() { if let Some(icon_theme) = IconTheme::for_display(default_display) { if let Some(paintable_icon) = icon_theme.lookup_by_gicon( @@ -117,7 +120,7 @@ impl DockItem { } else { println!("initializing dock item failed..."); } - if let Ok(active_value) = app_info.property("active") { + if let Ok(active_value) = dock_object.property("active") { if let Ok(active) = active_value.get::() { self_.dots.set_text(""); for _ in active.0 { diff --git a/examples/dock/dock_object/mod.rs b/examples/dock/dock_object/mod.rs index f6924bd2..f2da9d71 100644 --- a/examples/dock/dock_object/mod.rs +++ b/examples/dock/dock_object/mod.rs @@ -16,6 +16,18 @@ impl DockObject { .expect("Failed to create `DockObject`.") } + pub fn from_app_info_path(path: &str) -> Option { + if let Some(appinfo) = gio::DesktopAppInfo::new(path) { + if appinfo.should_show() { + return Some( + Object::new(&[("appinfo", &Some(appinfo)), ("saved", &true)]) + .expect("Failed to create `DockObject`."), + ); + } + } + None + } + pub fn from_search_results(results: BoxedWindowList) -> Self { let appinfo = if let Some(first) = results.0.iter().next() { xdg::BaseDirectories::new() diff --git a/examples/dock/main.rs b/examples/dock/main.rs index 399181e3..2b9cdf8a 100644 --- a/examples/dock/main.rs +++ b/examples/dock/main.rs @@ -5,7 +5,6 @@ mod utils; mod window; use crate::utils::BoxedWindowList; -use async_io::Timer; use futures::executor::block_on; use gdk4::Display; use gio::DesktopAppInfo; @@ -64,7 +63,7 @@ fn spawn_launcher(tx: Sender) -> Connection { if let Ok(reply) = m.body::>() { let _ = sender.send(Event::WindowList(reply)).await; } - Timer::after(Duration::from_millis(200)).await; + glib::timeout_future(Duration::from_millis(100)).await; } } }); @@ -137,8 +136,83 @@ fn main() { .expect("Failed to focus selected window"); } Event::RefreshFromCache => { - //TODO refresh the model from cached_results (required after DnD for example) - // alternatively dnd source could be changed depending on the context? + println!("refreshing model from cache"); + let cached_results = cached_results.as_ref(); + let stack_active = cached_results.iter().fold( + BTreeMap::new(), + |mut acc: BTreeMap, elem| { + if let Some(v) = acc.get_mut(&elem.description) { + v.0.push(elem.clone()); + } else { + acc.insert( + elem.description.clone(), + BoxedWindowList(vec![elem.clone()]), + ); + } + acc + }, + ); + let mut stack_active: Vec = + stack_active.into_values().collect(); + + // update active app stacks for saved apps into the saved app model + // then put the rest in the active app model (which doesn't include saved apps) + let saved_app_model = window.saved_app_model(); + + let mut saved_i: u32 = 0; + while let Some(item) = saved_app_model.item(saved_i) { + if let Ok(dock_obj) = item.downcast::() { + if let Ok(Some(cur_app_info)) = dock_obj + .property("appinfo") + .expect("property appinfo missing from DockObject") + .get::>() + { + if let Some((i, _s)) = stack_active + .iter() + .enumerate() + .find(|(_i, s)| s.0[0].description == cur_app_info.name()) + { + println!( + "found active saved app {} at {}", + _s.0[0].name, i + ); + let active = stack_active.remove(i); + dock_obj + .set_property("active", active.to_value()) + .expect("failed to update dock active apps"); + saved_app_model.items_changed( + saved_i.try_into().unwrap(), + 0, + 0, + ); + } else if let Some(_) = cached_results + .iter() + .find(|s| s.description == cur_app_info.name()) + { + dock_obj + .set_property( + "active", + BoxedWindowList(Vec::new()).to_value(), + ) + .expect("failed to update dock active apps"); + saved_app_model.items_changed( + saved_i.try_into().unwrap(), + 0, + 0, + ); + } + } + } + saved_i += 1; + } + + let active_app_model = window.active_app_model(); + let model_len = active_app_model.n_items(); + let new_results: Vec = stack_active + .into_iter() + .map(|v| DockObject::from_search_results(v).upcast()) + .collect(); + active_app_model.splice(0, model_len, &new_results[..]); } Event::WindowList(mut results) => { // sort to make comparison with cache easier diff --git a/examples/dock/utils.rs b/examples/dock/utils.rs index d42968d2..0bc76a8e 100644 --- a/examples/dock/utils.rs +++ b/examples/dock/utils.rs @@ -1,5 +1,19 @@ +use crate::DockObject; use crate::Item; +use gtk4::glib; +use std::path::PathBuf; #[derive(Clone, Debug, Default, glib::GBoxed)] #[gboxed(type_name = "BoxedWindowList")] pub struct BoxedWindowList(pub Vec); +#[derive(Clone, Debug, Default, glib::GBoxed)] +#[gboxed(type_name = "BoxedDockObject")] +pub struct BoxedDockObject(pub Option); + +pub fn data_path() -> PathBuf { + let mut path = glib::user_data_dir(); + path.push("com.cosmic.dock"); + std::fs::create_dir_all(&path).expect("Could not create directory."); + path.push("data.json"); + path +} diff --git a/examples/dock/window/imp.rs b/examples/dock/window/imp.rs index 4ec4c3f4..c7fd6a72 100644 --- a/examples/dock/window/imp.rs +++ b/examples/dock/window/imp.rs @@ -1,8 +1,12 @@ +use glib::SignalHandlerId; use gtk4 as gtk; use gtk4::Box; +use gtk4::DragSource; use gtk4::DropTarget; use gtk4::EventControllerMotion; use gtk4::Revealer; +use std::cell::RefCell; +use std::rc::Rc; use glib::subclass::InitializingObject; use gtk::prelude::*; @@ -27,6 +31,9 @@ pub struct Window { pub active_app_model: OnceCell, pub cursor_event_controller: OnceCell, pub drop_controller: OnceCell, + pub saved_drag_source: Rc>, + pub active_drag_source: OnceCell, + pub drag_end_signal: Rc>>, pub window_drop_controller: OnceCell, } @@ -53,9 +60,11 @@ impl ObjectImpl for Window { self.parent_constructed(obj); // Setup + obj.setup_model(); obj.setup_event_controller(); obj.setup_drop_target(); - obj.setup_model(); + obj.setup_drag_source(); + obj.restore_saved_apps(); obj.setup_callbacks(); obj.setup_factory(); } diff --git a/examples/dock/window/mod.rs b/examples/dock/window/mod.rs index 0a3da6a3..6dcf19f9 100644 --- a/examples/dock/window/mod.rs +++ b/examples/dock/window/mod.rs @@ -2,24 +2,32 @@ mod imp; // use crate::ApplicationObject; use crate::dock_item::DockItem; use crate::dock_object::DockObject; +use crate::utils::data_path; use crate::BoxedWindowList; use crate::Event; use crate::TX; use crate::X11_CONN; +use gdk4::ContentProvider; +use gdk4::Display; use gdk4::Rectangle; -use gdk4::Surface; use gdk4_x11::X11Display; use gdk4_x11::X11Surface; use gio::DesktopAppInfo; +use gio::Icon; use glib::Type; use gtk4 as gtk; use gtk4::prelude::ListModelExt; +use gtk4::DragSource; use gtk4::DropTarget; use gtk4::EventControllerMotion; +use gtk4::IconTheme; use postage::prelude::Sink; +use std::fs::File; use std::path::Path; use x11rb::connection::Connection; -use x11rb::protocol::xproto::ConnectionExt; +use x11rb::protocol::xproto::ConnectionExt as OtherConnectionExt; +use x11rb::protocol::xproto::*; +use x11rb::wrapper::ConnectionExt; // use crate::application_row::ApplicationRow; use glib::Object; use gtk::prelude::*; @@ -195,150 +203,91 @@ impl Window { } }), ); - window.connect_realize(glib::clone!(@weak revealer, @weak cursor_event_controller => move |window| { + window.connect_realize(glib::clone!(@weak revealer, @weak window_drop_controller, @weak cursor_event_controller => move |window| { if let Some((display, surface)) = x::get_window_x11(window) { // ignore all x11 errors... let xdisplay = display.clone().downcast::().expect("Failed to downgrade X11 Display."); xdisplay.error_trap_push(); - unsafe { - x::change_property( - &display, - &surface, - "_NET_WM_WINDOW_TYPE", - x::PropMode::Replace, - &[x::Atom::new(&display, "_NET_WM_WINDOW_TYPE_DOCK").unwrap()], - ); - } + let conn = X11_CONN.get().expect("Failed to get X11 connection"); + let window_type_atom = conn.intern_atom(false, b"_NET_WM_WINDOW_TYPE").unwrap().reply().unwrap().atom; + let dock_type_atom = conn.intern_atom(false, b"_NET_WM_WINDOW_TYPE_DOCK").unwrap().reply().unwrap().atom; + conn.change_property32( + PropMode::REPLACE, + surface.xid().try_into().unwrap(), + window_type_atom, + AtomEnum::ATOM, + &[dock_type_atom] + ).unwrap(); + let resize = glib::clone!(@weak window, @weak revealer => move || { + let s = window.surface().expect("Failed to get Surface for Window"); + let height = if revealer.reveals_child() { window.height() } else { 4 }; + let width = window.width(); - //TODO clean up duplicated code - cursor_event_controller.connect_enter(glib::clone!(@weak revealer, @weak window => move |_evc, _x, _y| { - // dbg!("hello, mouse entered me :)"); - let resize = glib::clone!(@weak window => move || { - let s = window.surface().expect("Failed to get Surface for Window"); - revealer.set_reveal_child(true); - let height = window.height(); + if let Some((display, _surface)) = x::get_window_x11(&window) { + let monitor = display + .primary_monitor() + .expect("Failed to get Monitor"); + let Rectangle { + x: monitor_x, + y: monitor_y, + width: monitor_width, + height: monitor_height, + } = monitor.geometry(); + // dbg!(monitor_width); + // dbg!(monitor_height); + // dbg!(width); + dbg!(height); + let w_conf = xproto::ConfigureWindowAux::default() + .x((monitor_x + monitor_width / 2 - width / 2).clamp(0, monitor_x + monitor_width - 1)) + .y((monitor_y + monitor_height - height).clamp(0, monitor_y + monitor_height - 1)); + let conn = X11_CONN.get().expect("Failed to get X11_CONN"); - if let Some((display, _surface)) = x::get_window_x11(&window) { - let monitor = display - .primary_monitor() - .expect("Failed to get Monitor"); - let Rectangle { - x: _monitor_x, - y: monitor_y, - width: _monitor_width, - height: monitor_height, - } = monitor.geometry(); - // dbg!(monitor_width); - // dbg!(monitor_height); - // dbg!(width); - dbg!(height); - let w_conf = xproto::ConfigureWindowAux::default() - .y((monitor_y + monitor_height - height).clamp(0, monitor_y + monitor_height - 1)); - let conn = X11_CONN.get().expect("Failed to get X11_CONN"); + let x11surface = gdk4_x11::X11Surface::xid( + &s.clone().downcast::() + .expect("Failed to downcast Surface to X11Surface"), + ); + conn.configure_window( + x11surface.try_into().expect("Failed to convert XID"), + &w_conf, + ) + .expect("failed to configure window..."); + conn.flush().expect("failed to flush"); + } + }); - let x11surface = gdk4_x11::X11Surface::xid( - &s.clone().downcast::() - .expect("Failed to downcast Surface to X11Surface"), - ); - conn.configure_window( - x11surface.try_into().expect("Failed to convert XID"), - &w_conf, - ) - .expect("failed to configure window..."); - conn.flush().expect("failed to flush"); - } - }); - glib::source::idle_add_local_once(resize); + let resize_drop = resize.clone(); + window_drop_controller.connect_enter(glib::clone!(@weak revealer, @weak window => @default-return gdk4::DragAction::COPY, move |_self, _x, _y| { + glib::source::idle_add_local_once(resize_drop.clone()); + revealer.set_reveal_child(true); + gdk4::DragAction::COPY })); + let resize_cursor = resize.clone(); + cursor_event_controller.connect_enter(glib::clone!(@weak revealer, @weak window => move |_evc, _x, _y| { + // dbg!("hello, mouse entered me :)"); + revealer.set_reveal_child(true); + glib::source::idle_add_local_once(resize_cursor.clone()); + })); + + let resize_revealed = resize.clone(); revealer.connect_child_revealed_notify(glib::clone!(@weak window => move |r| { if !r.is_child_revealed() { - let s = window.surface().expect("Failed to get Surface for Window"); - let resize = glib::clone!(@weak s => move || { - // dbg!(r.is_child_revealed()); - let s = window.surface().expect("Failed to get Surface for Window"); - let height = 4; - if let Some((display, _surface)) = x::get_window_x11(&window) { - let monitor = display - .primary_monitor() - .expect("Failed to get Monitor"); - let Rectangle { - x: _monitor_x, - y: monitor_y, - width: _monitor_width, - height: monitor_height, - } = monitor.geometry(); - // dbg!(monitor_width); - // dbg!(monitor_height); - // dbg!(width); - dbg!(height); - let w_conf = xproto::ConfigureWindowAux::default() - .y((monitor_y + monitor_height - height).clamp(0, monitor_y + monitor_height - 1)); - let conn = X11_CONN.get().expect("Failed to get X11_CONN"); - - let x11surface = gdk4_x11::X11Surface::xid( - &s.clone().downcast::() - .expect("Failed to downcast Surface to X11Surface"), - ); - conn.configure_window( - x11surface.try_into().expect("Failed to convert XID"), - &w_conf, - ) - .expect("failed to configure window..."); - conn.flush().expect("failed to flush"); - } else { - println!("failed to get X11 window"); - } - }); - glib::source::idle_add_local_once(resize); - } + glib::source::idle_add_local_once(resize_revealed.clone()); + } })); let s = window.surface().expect("Failed to get Surface for Window"); - let surface_resize_handler = glib::clone!(@weak window => move |s: &Surface| { - let resize = glib::clone!(@weak s => move || { - if let Some((display, _surface)) = x::get_window_x11(&window) { - let width = window.width(); - // let height = s.height() * s.scale_factor(); - // hide by default - let height = 4; - let monitor = display - .primary_monitor() - .expect("Failed to get Monitor"); - let Rectangle { - x: monitor_x, - y: monitor_y, - width: monitor_width, - height: monitor_height, - } = monitor.geometry(); - // dbg!(monitor_width); - // dbg!(monitor_height); - // dbg!(width); - // dbg!(heightt - 1); - let w_conf = xproto::ConfigureWindowAux::default() - .x((monitor_x + monitor_width / 2 - width / 2).clamp(0, monitor_x + monitor_width - 1)) - .y((monitor_y + monitor_height - height).clamp(0, monitor_y + monitor_height - 1)); - let conn = X11_CONN.get().expect("Failed to get X11_CONN"); - - let x11surface = gdk4_x11::X11Surface::xid( - &s.clone().downcast::() - .expect("Failed to downcast Surface to X11Surface"), - ); - conn.configure_window( - x11surface.try_into().expect("Failed to convert XID"), - &w_conf, - ) - .expect("failed to configure window..."); - conn.flush().expect("failed to flush"); - } else { - println!("failed to get X11 window"); - } - }); - glib::source::idle_add_local_once(resize); + let resize_height = resize.clone(); + s.connect_height_notify(move |_s| { + glib::source::idle_add_local_once(resize_height.clone()); + }); + let resize_width = resize.clone(); + s.connect_width_notify(move |_s| { + glib::source::idle_add_local_once(resize_width.clone()); + }); + s.connect_scale_factor_notify(move |_s| { + glib::source::idle_add_local_once(resize.clone()); }); - s.connect_height_notify(surface_resize_handler.clone()); - s.connect_width_notify(surface_resize_handler.clone()); - s.connect_scale_factor_notify(surface_resize_handler); } else { println!("failed to get X11 window"); } @@ -359,20 +308,23 @@ impl Window { revealer.set_reveal_child(true); gdk4::DragAction::COPY })); - // hack for showing hidden dock when drag enters window - window_drop_controller.connect_enter(glib::clone!(@weak revealer => @default-return gdk4::DragAction::COPY, move |_self, _x, _y| { - revealer.set_reveal_child(true); - gdk4::DragAction::empty() - })); - + window_drop_controller.connect_drop(|_, _, _, _| { + println!("dropping into window"); + false + }); let saved_app_list_view = saved_app_list_view.get(); + // drag end handler + // must be modified in case of reorder... + let drag_end = &imp.drag_end_signal; + let saved_drag_source = &imp.saved_drag_source; drop_controller.connect_drop( - glib::clone!(@weak saved_app_model, @weak saved_app_list_view => @default-return true, move |_self, drop_value, x, _y| { + glib::clone!(@weak saved_app_model, @weak saved_app_list_view, @weak drag_end, @weak saved_drag_source => @default-return true, move |_self, drop_value, x, _y| { if let Ok(Some(path_str)) = drop_value.get::>() { let desktop_path = &Path::new(&path_str); if let Some(pathbase) = desktop_path.file_name() { if let Some(app_info) = gio::DesktopAppInfo::new(&pathbase.to_string_lossy()) { + // remove item if already exists let mut i: u32 = 0; let mut index_of_existing_app: Option = None; while let Some(item) = saved_app_model.item(i) { @@ -391,6 +343,9 @@ impl Window { if let Some(index_of_existing_app) = index_of_existing_app { // remove existing entry saved_app_model.remove(index_of_existing_app); + if let Some(old_handle) = drag_end.replace(None) { + glib::signal_handler_disconnect(saved_drag_source.get().expect("Failed to get drag handler"), old_handle); + } } //calculate insertion location @@ -420,9 +375,14 @@ impl Window { // dbg!("rejecting drop"); _self.reject(); } + Self::store_saved_apps(&saved_app_model); true }), ); + + saved_app_model.connect_items_changed(|saved_app_model, _, _removed, _added| { + Self::store_saved_apps(saved_app_model); + }); } fn setup_event_controller(&self) { @@ -442,11 +402,11 @@ impl Window { fn setup_drop_target(&self) { let imp = imp::Window::from_instance(self); let drop_target_widget = &imp.saved_app_list_view; - let drop_actions = gdk4::DragAction::COPY; - // drop_actions.toggle(gdk4::DragAction::MOVE); + let mut drop_actions = gdk4::DragAction::COPY; + drop_actions.insert(gdk4::DragAction::MOVE); let drop_format = gdk4::ContentFormats::for_type(Type::STRING); // causes error for some reason... - // let desktop_type = Type::from_name("GDesktopAppInfo").expect("Invalid GType"); + drop_format.union(&gdk4::ContentFormats::for_type(DockObject::static_type())); let drop_target_controller = DropTarget::builder() .preload(true) .actions(drop_actions) @@ -459,7 +419,7 @@ impl Window { // hack for revealing hidden dock when drag enters dock window let window_drop_target_controller = DropTarget::builder() - .actions(gdk4::DragAction::COPY) + .actions(drop_actions) .formats(&gdk4::ContentFormats::for_type(Type::STRING)) .build(); let enter_handle = &imp.cursor_handle.get(); @@ -470,6 +430,137 @@ impl Window { .expect("Could not set dock dnd drop controller"); } + fn setup_drag_source(&self) { + let imp = imp::Window::from_instance(self); + let saved_app_list_view = &imp.saved_app_list_view.get(); + let saved_app_model = imp + .saved_app_model + .get() + .expect("Failed to get saved app model."); + + let actions = gdk4::DragAction::MOVE; + let saved_drag_source = DragSource::builder() + .name("dock drag source") + .actions(actions) + .build(); + + let drag_end = &imp.drag_end_signal; + saved_app_list_view.add_controller(&saved_drag_source); + saved_drag_source.connect_prepare(glib::clone!(@weak saved_app_model, @weak saved_app_list_view, @weak drag_end => @default-return None, move |self_, x, _y| { + // set drag source icon if possible... + // gio Icon is not easily converted to a Paintable, but this seems to be the correct method + let max_x = saved_app_list_view.allocated_width(); + // dbg!(max_x); + // dbg!(max_y); + let n_buckets = saved_app_model.n_items(); + + let index = (x * n_buckets as f64 / (max_x as f64 + 0.1)) as u32; + if let Some(item) = saved_app_model.item(index) { + if let Some(old_handle) = drag_end.replace(Some(self_.connect_drag_end( + glib::clone!(@weak saved_app_model => move |_self, _drag, _delete_data| { + dbg!(_delete_data); + if _delete_data {saved_app_model.remove(index)}; + }), + ))) { + glib::signal_handler_disconnect(self_, old_handle); + } + + if let Ok(dock_object) = item.downcast::() { + if let Ok(Some(app_info)) = dock_object.property("appinfo").expect("property appinfo missing from DockObject").get::>() { + let icon = app_info + .icon() + .unwrap_or(Icon::for_string("image-missing").expect("Failed to set default icon")); + + if let Some(default_display) = &Display::default() { + if let Some(icon_theme) = IconTheme::for_display(default_display) { + if let Some(paintable_icon) = icon_theme.lookup_by_gicon( + &icon, + 64, + 1, + gtk4::TextDirection::None, + gtk4::IconLookupFlags::empty(), + ) { + self_.set_icon(Some(&paintable_icon), 32, 32); + } + } + } + if let Some(file) = app_info.filename() { + return Some(ContentProvider::for_value(&file.to_string_lossy().to_value())); + } + } + } + } + None + })); + imp.saved_drag_source + .set(saved_drag_source) + .expect("Could not set saved drag source"); + + let active_app_list_view = &imp.active_app_list_view.get(); + let active_app_model = imp + .active_app_model + .get() + .expect("Failed to get saved app model."); + + let actions = gdk4::DragAction::MOVE; + let active_drag_source = DragSource::builder() + .name("dock drag source") + .actions(actions) + .build(); + + active_drag_source.connect_drag_begin(|_self, drag| { + drag.set_selected_action(gdk4::DragAction::MOVE); + }); + + active_app_list_view.add_controller(&active_drag_source); + active_drag_source.connect_prepare(glib::clone!(@weak active_app_model, @weak active_app_list_view, @weak drag_end => @default-return None, move |self_, x, _y| { + let max_x = active_app_list_view.allocated_width(); + // dbg!(max_x); + // dbg!(max_y); + let n_buckets = active_app_model.n_items(); + let index = (x * n_buckets as f64 / (max_x as f64 + 0.1)) as u32; + if let Some(item) = active_app_model.item(index) { + if let Some(old_handle) = drag_end.replace(Some(self_.connect_drag_end( + glib::clone!(@weak active_app_model => move |_self, _drag, _delete_data| { + dbg!(_delete_data); + if _delete_data {active_app_model.remove(index)}; + }), + ))) { + glib::signal_handler_disconnect(self_, old_handle); + } + + if let Ok(dock_object) = item.downcast::() { + if let Ok(Some(app_info)) = dock_object.property("appinfo").expect("property appinfo missing from DockObject").get::>() { + let icon = app_info + .icon() + .unwrap_or(Icon::for_string("image-missing").expect("Failed to set default icon")); + + if let Some(default_display) = &Display::default() { + if let Some(icon_theme) = IconTheme::for_display(default_display) { + if let Some(paintable_icon) = icon_theme.lookup_by_gicon( + &icon, + 64, + 1, + gtk4::TextDirection::None, + gtk4::IconLookupFlags::empty(), + ) { + self_.set_icon(Some(&paintable_icon), 32, 32); + } + } + } + if let Some(file) = app_info.filename() { + return Some(ContentProvider::for_value(&file.to_string_lossy().to_value())); + } + } + } + } + None + })); + imp.active_drag_source + .set(active_drag_source) + .expect("Could not set saved drag source"); + } + fn setup_factory(&self) { let saved_app_factory = SignalListItemFactory::new(); saved_app_factory.connect_setup(move |_, list_item| { @@ -527,4 +618,54 @@ impl Window { // Set the factory of the list view imp.active_app_list_view.set_factory(Some(&active_factory)); } + + fn restore_saved_apps(&self) { + if let Ok(file) = File::open(data_path()) { + if let Ok(data) = serde_json::from_reader::<_, Vec>(file) { + dbg!(&data); + let dock_objects: Vec = data + .into_iter() + .filter_map(|d| { + DockObject::from_app_info_path(&d) + .map(|dockobject| dockobject.upcast::()) + }) + .collect(); + let saved_app_model = self.saved_app_model(); + saved_app_model.splice(saved_app_model.n_items(), 0, &dock_objects); + } else { + println!("Error loading saved apps!"); + // let file = File::create(data_path()).expect("Could not create json file."); + // serde_json::to_writer_pretty(file, &Vec::<&str>::new()) + // .expect("Could not write data to json file"); + } + } + } + + fn store_saved_apps(saved_app_model: &gio::ListStore) { + // Store todo data in vector + let mut backup_data = Vec::new(); + let mut position = 3; + while let Some(item) = saved_app_model.item(position) { + // Get `AppGroup` from `glib::Object` + let dock_object = item + .downcast_ref::() + .expect("The object needs to be of type `AppGroupData`."); + // Add todo data to vector and increase position + if let Ok(Some(app_info)) = dock_object + .property("appinfo") + .expect("DockObject must have appinfo property") + .get::>() + { + if let Some(f) = app_info.filename() { + backup_data.push(f); + } + } + position += 1; + } + dbg!(&backup_data); + // Save state in file + let file = File::create(data_path()).expect("Could not create json file."); + serde_json::to_writer_pretty(file, &backup_data) + .expect("Could not write data to json file"); + } }