diff --git a/Cargo.toml b/Cargo.toml index 15a9b333..8a7fc56b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,9 +12,9 @@ x11 = { version = "2", features = ["xlib"] } # examples/launcher #pop-launcher = "1.0.3" -pop-launcher = { git = "https://github.com/pop-os/launcher", branch = "master" } +pop-launcher = { path = "../launcher" } serde_json = "1.0.72" -pop-launcher-service = { git = "https://github.com/pop-os/launcher", branch = "master" } +pop-launcher-service = { path = "../launcher/service" } postage = "0.4.1" futures = "0.3.17" glib = "0.14.8" diff --git a/examples/app_library/window/.#mod.rs b/examples/app_library/window/.#mod.rs deleted file mode 120000 index 46713fb8..00000000 --- a/examples/app_library/window/.#mod.rs +++ /dev/null @@ -1 +0,0 @@ -ashleywulber@pop-os.146928:1639590163 \ No newline at end of file diff --git a/examples/appinfo/main.rs b/examples/appinfo/main.rs new file mode 100644 index 00000000..df98f58d --- /dev/null +++ b/examples/appinfo/main.rs @@ -0,0 +1,19 @@ +use gio::DesktopAppInfo; +use glib::prelude::*; +use glib::subclass; +use glib::subclass::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, glib::GBoxed)] +#[gboxed(type_name = "MyBoxed")] +struct MyBoxed(DesktopAppInfo); + +pub fn main() { + let appinfo = DesktopAppInfo::new("firefox.desktop").expect("failed to get app info"); + assert!(MyBoxed::static_type().is_valid()); + + let b = MyBoxed(appinfo); + let v = b.to_value(); + let b2 = v.get::<&MyBoxed>().unwrap(); + assert_eq!(&b, b2); + dbg!(&b2.0.filename()); +} diff --git a/examples/dock/.main.rs.swp b/examples/dock/.main.rs.swp deleted file mode 100644 index 74647281..00000000 Binary files a/examples/dock/.main.rs.swp and /dev/null differ diff --git a/examples/dock/dock_item/mod.rs b/examples/dock/dock_item/mod.rs index 35451dba..f8ad14ac 100644 --- a/examples/dock/dock_item/mod.rs +++ b/examples/dock/dock_item/mod.rs @@ -1,5 +1,6 @@ use gdk4::ContentProvider; use gdk4::Display; +use gio::DesktopAppInfo; use gio::Icon; use gio::ListStore; use gtk4 as gtk; @@ -11,6 +12,8 @@ use gtk::glib; use gtk::prelude::*; use gtk::subclass::prelude::*; +use crate::dock_object::DockObject; + glib::wrapper! { pub struct DockItem(ObjectSubclass) @extends gtk::Widget, gtk::Box; @@ -47,70 +50,72 @@ impl DockItem { // TODO current method seems very messy... // refactor to emit event for removing the item? - pub fn set_app_info( - &self, - app_info: &gio::DesktopAppInfo, - i: u32, - saved_app_model: &ListStore, - ) { - dbg!("setting app info"); - let self_ = imp::DockItem::from_instance(self); - self_.image.set_tooltip_text(Some(&app_info.name())); + pub fn set_app_info(&self, app_info: &DockObject, i: u32, saved_app_model: &ListStore) { + if let Ok(app_info_value) = app_info.property("appinfo") { + if let Ok(Some(app_info)) = app_info_value.get::>() { + dbg!("setting app info"); + let self_ = imp::DockItem::from_instance(self); + self_.image.set_tooltip_text(Some(&app_info.name())); - if let Some(drag_controller) = self_.drag_controller.get() { - if let Some(file) = app_info.filename() { - let provider = 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 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); + if let Some(drag_controller) = self_.drag_controller.get() { + if let Some(file) = app_info.filename() { + let provider = + ContentProvider::for_value(&file.to_string_lossy().to_value()); + drag_controller.set_content(Some(&provider)); } - 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); - } - }), - ); + 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 + }), + ); + 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); + } + }), + ); - let icon = app_info - .icon() - .unwrap_or(Icon::for_string("image-missing").expect("Failed to set default icon")); - drag_controller.connect_drag_begin(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 - 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); - } - } + let icon = app_info.icon().unwrap_or( + Icon::for_string("image-missing").expect("Failed to set default icon"), + ); + drag_controller.connect_drag_begin( + 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 + 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); + } + } + } + }), + ); } - })); + + let icon = app_info.icon().unwrap_or( + Icon::for_string("image-missing").expect("Failed to set default icon"), + ); + + self_.image.set_from_gicon(&icon); + } } - - let icon = app_info - .icon() - .unwrap_or(Icon::for_string("image-missing").expect("Failed to set default icon")); - - self_.image.set_from_gicon(&icon); } // pub fn set_app_info(&self, app_obj: ApplicationObject) { diff --git a/examples/dock/dock_object/imp.rs b/examples/dock/dock_object/imp.rs new file mode 100644 index 00000000..e920c824 --- /dev/null +++ b/examples/dock/dock_object/imp.rs @@ -0,0 +1,79 @@ +use crate::utils::BoxedSearchResults; +use gio::DesktopAppInfo; +use glib::{FromVariant, ParamFlags, ParamSpec, ToVariant, Value, Variant, VariantTy}; +use gtk4::glib; +use gtk4::prelude::*; +use gtk4::subclass::prelude::*; +use once_cell::sync::Lazy; +use std::cell::RefCell; + +// Object holding the state +#[derive(Default)] +pub struct DockObject { + appinfo: RefCell>, + active: RefCell, +} + +// The central trait for subclassing a GObject +#[glib::object_subclass] +impl ObjectSubclass for DockObject { + const NAME: &'static str = "DockObject"; + type Type = super::DockObject; + type ParentType = glib::Object; +} + +// Trait shared by all GObjects +impl ObjectImpl for DockObject { + fn properties() -> &'static [ParamSpec] { + static PROPERTIES: Lazy> = Lazy::new(|| { + vec![ + ParamSpec::new_object( + // Name + "appinfo", + // Nickname + "appinfo", + // Short description + "app info", + DesktopAppInfo::static_type(), + // The property can be read and written to + ParamFlags::READWRITE, + ), + ParamSpec::new_boxed( + // Name + "active", + // Nickname + "active", + // Short description + "active", + BoxedSearchResults::static_type(), + // The property can be read and written to + ParamFlags::READWRITE, + ), + ] + }); + PROPERTIES.as_ref() + } + + fn set_property(&self, _obj: &Self::Type, _id: usize, value: &Value, pspec: &ParamSpec) { + match pspec.name() { + "appinfo" => { + let appinfo = value + .get() + .expect("Value needs to be Option"); + self.appinfo.replace(appinfo); + } + "active" => { + let active = value.get().expect("Value needs to be BoxedSearchResults"); + self.active.replace(active); + } + _ => unimplemented!(), + } + } + + fn property(&self, _obj: &Self::Type, _id: usize, pspec: &ParamSpec) -> Value { + match pspec.name() { + "appinfo" => self.appinfo.borrow().to_value(), + _ => unimplemented!(), + } + } +} diff --git a/examples/dock/dock_object/mod.rs b/examples/dock/dock_object/mod.rs new file mode 100644 index 00000000..7dfa78e3 --- /dev/null +++ b/examples/dock/dock_object/mod.rs @@ -0,0 +1,51 @@ +mod imp; + +use crate::utils::BoxedSearchResults; +use gdk4::glib::Object; +use gio::DesktopAppInfo; +use gtk4::glib; +use gtk4::prelude::AppInfoExt; + +glib::wrapper! { + pub struct DockObject(ObjectSubclass); +} + +impl DockObject { + pub fn new(appinfo: DesktopAppInfo) -> Self { + Object::new(&[("appinfo", &Some(appinfo))]).expect("Failed to create `DockObject`.") + } + + pub fn from_search_results(results: BoxedSearchResults) -> Self { + let appinfo = xdg::BaseDirectories::new() + .expect("could not access XDG Base directory") + .get_data_dirs() + .iter_mut() + .filter_map(|xdg_data_path| { + let defaults = ["Firefox Web Browser", "Files", "Terminal", "Pop!_Shop"]; + xdg_data_path.push("applications"); + dbg!(&xdg_data_path); + std::fs::read_dir(xdg_data_path).ok() + }) + .flatten() + .filter_map(|dir_entry| { + let defaults = ["Firefox Web Browser", "Files", "Terminal", "Pop!_Shop"]; + if let Ok(dir_entry) = dir_entry { + if let Some(path) = dir_entry.path().file_name() { + if let Some(path) = path.to_str() { + if let Some(app_info) = gio::DesktopAppInfo::new(path) { + if app_info.should_show() + && defaults.contains(&app_info.name().as_str()) + { + return Some(DockObject::new(app_info)); + } + } + } + } + } + None + }) + .next(); + Object::new(&[("appinfo", &appinfo), ("active", &results)]) + .expect("Failed to create `DockObject`.") + } +} diff --git a/examples/dock/main.rs b/examples/dock/main.rs index 46f6b18a..4ef52ead 100644 --- a/examples/dock/main.rs +++ b/examples/dock/main.rs @@ -1,5 +1,6 @@ mod application_object; mod dock_item; +mod dock_object; mod utils; mod window; @@ -70,6 +71,7 @@ fn load_css() { } fn main() { + assert!(utils::BoxedSearchResults::static_type().is_valid()); let app = gtk::Application::builder() .application_id("com.cosmic.Launcher") .build(); @@ -78,7 +80,12 @@ fn main() { setup_shortcuts(app); load_css() }); + + // TODO investigate multiple signals to connect_activate + // crashes when called twice bc of singleton app.connect_activate(move |app| { + // Seems that over a long period of time, this might be called multiple times + // The global variables should be initialized outside this closure let (tx, mut rx) = postage::mpsc::channel(1); let mut launcher = spawn_launcher(tx.clone()); if TX.set(tx).is_err() { diff --git a/examples/dock/utils.rs b/examples/dock/utils.rs index 8b137891..cc87d10f 100644 --- a/examples/dock/utils.rs +++ b/examples/dock/utils.rs @@ -1 +1,3 @@ - +#[derive(Clone, Debug, Default, glib::GBoxed)] +#[gboxed(type_name = "BoxedLauncherActive")] +pub struct BoxedSearchResults(pub Vec); diff --git a/examples/dock/window/mod.rs b/examples/dock/window/mod.rs index b91c9a40..4dbdd320 100644 --- a/examples/dock/window/mod.rs +++ b/examples/dock/window/mod.rs @@ -1,6 +1,7 @@ mod imp; // use crate::ApplicationObject; use crate::dock_item::DockItem; +use crate::dock_object::DockObject; use crate::X11_CONN; use gdk4::Rectangle; use gdk4::Surface; @@ -49,7 +50,7 @@ impl Window { // Get state and set model let imp = imp::Window::from_instance(self); - let saved_app_model = gio::ListStore::new(DesktopAppInfo::static_type()); + let saved_app_model = gio::ListStore::new(DockObject::static_type()); let selection_model = gtk::SingleSelection::builder() .autoselect(false) @@ -74,8 +75,7 @@ impl Window { if app_info.should_show() && defaults.contains(&app_info.name().as_str()) { - dbg!(app_info.name()); - saved_app_model.append(&app_info) + saved_app_model.append(&DockObject::new(app_info)); } else { // println!("Ignoring {}", path); } @@ -118,17 +118,19 @@ impl Window { println!("selected app {}", position); // Launch the application when an item of the list is activated if let Some(item) = model.item(position) { - let app_info = item.downcast::().unwrap(); - let context = window.display().app_launch_context(); - if let Err(err) = app_info.launch(&[], Some(&context)) { - gtk::MessageDialog::builder() - .text(&format!("Failed to start {}", app_info.name())) - .secondary_text(&err.to_string()) - .message_type(gtk::MessageType::Error) - .modal(true) - .transient_for(&window) - .build() - .show(); + let app_info = item.downcast::().expect("App model must only contain DockObject"); + if let Ok(Some(app_info)) = app_info.property("appinfo").expect("DockObject must have appinfo property").get::>() { + let context = window.display().app_launch_context(); + if let Err(err) = app_info.launch(&[], Some(&context)) { + gtk::MessageDialog::builder() + .text(&format!("Failed to start {}", app_info.name())) + .secondary_text(&err.to_string()) + .message_type(gtk::MessageType::Error) + .modal(true) + .transient_for(&window) + .build() + .show(); + } } } }), @@ -239,10 +241,12 @@ impl Window { let mut i: u32 = 0; let mut index_of_existing_app: Option = None; while let Some(item) = saved_app_model.item(i) { - if let Ok(cur_app_info) = item.downcast::() { - dbg!(cur_app_info.filename()); - if cur_app_info.filename() == Some(Path::new(&path_str).to_path_buf()) { - index_of_existing_app = Some(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() == Some(Path::new(&path_str).to_path_buf()) { + index_of_existing_app = Some(i); + } } } i += 1; @@ -274,7 +278,7 @@ impl Window { dbg!(index); dbg!("dropped it!"); dbg!(drop_value.type_()); - saved_app_model.insert(index, &app_info); + saved_app_model.insert(index, &DockObject::new(app_info)); } } } @@ -356,8 +360,8 @@ impl Window { let application_object = list_item .item() .expect("The item has to exist.") - .downcast::() - .expect("The item has to be a `DesktopAppInfo`"); + .downcast::() + .expect("The item has to be a `DockObject`"); let dock_item = list_item .child() .expect("The list item child needs to exist.") diff --git a/examples/gtklauncher/README.md b/examples/gtklauncher/README.md deleted file mode 100644 index f2a64530..00000000 --- a/examples/gtklauncher/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# ListView: Applications Launcher - -This example shows how to create a `gtk::ListView` and fill it with applications data from `gio::AppInfo` with the possibility to open an application when an item of the list is activated. - -![Screenshot](screenshot.png) diff --git a/examples/gtklauncher/application_row/application_row.ui b/examples/gtklauncher/application_row/application_row.ui deleted file mode 100644 index a70d7958..00000000 --- a/examples/gtklauncher/application_row/application_row.ui +++ /dev/null @@ -1,53 +0,0 @@ - - - - diff --git a/examples/gtklauncher/application_row/imp.rs b/examples/gtklauncher/application_row/imp.rs deleted file mode 100644 index f27eb54e..00000000 --- a/examples/gtklauncher/application_row/imp.rs +++ /dev/null @@ -1,38 +0,0 @@ -use gtk::glib; -use gtk::prelude::*; -use gtk::subclass::prelude::*; -use gtk4 as gtk; - -use gtk::CompositeTemplate; - -#[derive(Debug, Default, CompositeTemplate)] -#[template(file = "application_row.ui")] -pub struct ApplicationRow { - #[template_child] - pub name: TemplateChild, - #[template_child] - pub description: TemplateChild, - #[template_child] - pub shortcut: TemplateChild, - #[template_child] - pub image: TemplateChild, -} - -#[glib::object_subclass] -impl ObjectSubclass for ApplicationRow { - const NAME: &'static str = "ApplicationRow"; - type Type = super::ApplicationRow; - type ParentType = gtk::Box; - - fn class_init(klass: &mut Self::Class) { - Self::bind_template(klass); - } - - fn instance_init(obj: &glib::subclass::InitializingObject) { - obj.init_template(); - } -} - -impl ObjectImpl for ApplicationRow {} -impl WidgetImpl for ApplicationRow {} -impl BoxImpl for ApplicationRow {} diff --git a/examples/gtklauncher/application_row/mod.rs b/examples/gtklauncher/application_row/mod.rs deleted file mode 100644 index 8054c928..00000000 --- a/examples/gtklauncher/application_row/mod.rs +++ /dev/null @@ -1,39 +0,0 @@ -use gtk4 as gtk; -mod imp; - -use gtk::prelude::*; -use gtk::subclass::prelude::*; -use gtk::{gio, glib}; - -glib::wrapper! { - pub struct ApplicationRow(ObjectSubclass) - @extends gtk::Widget, gtk::Box; -} - -impl Default for ApplicationRow { - fn default() -> Self { - Self::new() - } -} - -impl ApplicationRow { - pub fn new() -> Self { - glib::Object::new(&[]).expect("Failed to create ApplicationRow") - } - - pub fn set_app_info(&self, app_info: &gio::AppInfo) { - let self_ = imp::ApplicationRow::from_instance(self); - self_.name.set_text(&app_info.name()); - if let Some(desc) = app_info.description() { - self_.description.set_text(&desc); - } - if let Some(icon) = app_info.icon() { - self_.image.set_from_gicon(&icon); - } - } - - pub fn set_shortcut(&self, indx: u32) { - let self_ = imp::ApplicationRow::from_instance(self); - self_.shortcut.set_text(&format!("Ctrl + {}", indx)); - } -} diff --git a/examples/gtklauncher/main.rs b/examples/gtklauncher/main.rs deleted file mode 100644 index 584e1897..00000000 --- a/examples/gtklauncher/main.rs +++ /dev/null @@ -1,34 +0,0 @@ -mod application_row; -mod window; - -use gtk::gdk::Display; -use gtk::prelude::*; -use gtk4 as gtk; - -use window::Window; - -fn main() { - let application = gtk::Application::new( - Some("com.github.gtk-rs.examples.apps_launcher"), - Default::default(), - ); - - application.connect_activate(|app| { - let provider = gtk::CssProvider::new(); - provider.load_from_data(include_bytes!("style.css")); - gtk::StyleContext::add_provider_for_display( - &Display::default().expect("Error initializing gtk css provider."), - &provider, - gtk::STYLE_PROVIDER_PRIORITY_APPLICATION, - ); - new_build_ui(app); - }); - - application.run(); -} - -fn new_build_ui(app: >k::Application) { - // Create a new custom window and show it - let window = Window::new(app); - window.show(); -} diff --git a/examples/gtklauncher/screenshot.png b/examples/gtklauncher/screenshot.png deleted file mode 100644 index 8ffcefa1..00000000 Binary files a/examples/gtklauncher/screenshot.png and /dev/null differ diff --git a/examples/gtklauncher/style.css b/examples/gtklauncher/style.css deleted file mode 100644 index 0c72f8a3..00000000 --- a/examples/gtklauncher/style.css +++ /dev/null @@ -1,15 +0,0 @@ -description { - line-height: 1.5em; - background-image: none; - background-color: red; -} - -row.row1 { - background-image: none; - background-color: black; -} - -shortcut { - background-image: none; - background-color: green; -} diff --git a/examples/gtklauncher/window/imp.rs b/examples/gtklauncher/window/imp.rs deleted file mode 100644 index 901181cf..00000000 --- a/examples/gtklauncher/window/imp.rs +++ /dev/null @@ -1,56 +0,0 @@ -use gtk4 as gtk; - -use glib::subclass::InitializingObject; -use gtk::prelude::*; -use gtk::subclass::prelude::*; -use gtk::{gio, glib}; -use gtk::{CompositeTemplate, Entry, ListView}; -use once_cell::sync::OnceCell; - -// Object holding the state -#[derive(CompositeTemplate, Default)] -#[template(file = "window.ui")] -pub struct Window { - #[template_child] - pub entry: TemplateChild, - #[template_child] - pub list_view: TemplateChild, - pub model: OnceCell, -} - -// The central trait for subclassing a GObject -#[glib::object_subclass] -impl ObjectSubclass for Window { - // `NAME` needs to match `class` attribute of template - const NAME: &'static str = "LauncherWindow"; - type Type = super::Window; - type ParentType = gtk::ApplicationWindow; - - fn class_init(klass: &mut Self::Class) { - Self::bind_template(klass); - } - - fn instance_init(obj: &InitializingObject) { - obj.init_template(); - } -} -// Trait shared by all GObjects -impl ObjectImpl for Window { - fn constructed(&self, obj: &Self::Type) { - // Call "constructed" on parent - self.parent_constructed(obj); - - // Setup - obj.setup_model(); - obj.setup_callbacks(); - obj.setup_factory(); - } -} -// Trait shared by all widgets -impl WidgetImpl for Window {} - -// Trait shared by all windows -impl WindowImpl for Window {} - -// Trait shared by all application -impl ApplicationWindowImpl for Window {} diff --git a/examples/gtklauncher/window/mod.rs b/examples/gtklauncher/window/mod.rs deleted file mode 100644 index b5a530cb..00000000 --- a/examples/gtklauncher/window/mod.rs +++ /dev/null @@ -1,245 +0,0 @@ -mod imp; -use gtk4 as gtk; - -use crate::application_row::ApplicationRow; -use glib::Object; -use gtk::prelude::*; -use gtk::subclass::prelude::*; -use gtk::{gio, glib}; -use gtk::{Application, SignalListItemFactory}; - -use libcosmic::x; - -glib::wrapper! { - pub struct Window(ObjectSubclass) - @extends gtk::ApplicationWindow, gtk::Window, gtk::Widget, - @implements gio::ActionGroup, gio::ActionMap, gtk::Accessible, gtk::Buildable, - gtk::ConstraintTarget, gtk::Native, gtk::Root, gtk::ShortcutManager; -} - -impl Window { - pub fn new(app: &Application) -> Self { - //quit shortcut - app.set_accels_for_action("win.quit", &["W", "Escape"]); - //launch shortcuts - for i in 1..10 { - app.set_accels_for_action(&format!("win.launch{}", i), &[&format!("{}", i)]); - } - Object::new(&[("application", app)]).expect("Failed to create `Window`.") - } - - fn model(&self) -> &gio::ListStore { - // Get state - let imp = imp::Window::from_instance(self); - imp.model.get().expect("Could not get model") - } - - fn setup_model(&self) { - // Create new model - let model = gio::ListStore::new(gio::AppInfo::static_type()); - gio::AppInfo::all().iter().for_each(|app_info| { - model.append(app_info); - }); - - // Get state and set model - let imp = imp::Window::from_instance(self); - imp.model.set(model.clone()).expect("Could not set model"); - - // A sorter used to sort AppInfo in the model by their name - let sorter = gtk::CustomSorter::new(move |obj1, obj2| { - let app_info1 = obj1.downcast_ref::().unwrap(); - let app_info2 = obj2.downcast_ref::().unwrap(); - - app_info1 - .name() - .to_lowercase() - .cmp(&app_info2.name().to_lowercase()) - .into() - }); - let filter = gtk::CustomFilter::new(|_obj| true); - let filter_model = gtk::FilterListModel::new(Some(&model), Some(filter).as_ref()); - let sorted_model = gtk::SortListModel::new(Some(&filter_model), Some(&sorter)); - let slice_model = gtk::SliceListModel::new(Some(&sorted_model), 0, 9); - let selection_model = gtk::SingleSelection::new(Some(&slice_model)); - - // Wrap model with selection and pass it to the list view - imp.list_view.set_model(Some(&selection_model)); - } - - fn setup_callbacks(&self) { - // Get state - let imp = imp::Window::from_instance(self); - let window = self.clone().upcast::(); - let list_view = &imp.list_view; - let sorted_model = list_view - .model() - .expect("List view missing selection model") - .downcast::() - .expect("could not downcast listview model to single selection model") - .model() - .downcast::() - .expect("could not downcast single selection model to slice list model.") - .model() - .expect("sorted list model is missing from slice list model") - .downcast::() - .expect("sorted list model could not be downcast"); - let filter_model = sorted_model - .model() - .expect("missing model for sort list model.") - .downcast::() - .expect("could not downcast sort list model to filter list model"); - - let entry = &imp.entry; - let lv = list_view.get(); - for i in 1..10 { - let action_launchi = gio::SimpleAction::new(&format!("launch{}", i), None); - self.add_action(&action_launchi); - let context = list_view.display().app_launch_context().clone(); - let parent_window = list_view.root().unwrap().downcast::().unwrap(); - action_launchi.connect_activate(glib::clone!(@weak lv => move |_action, _parameter| { - let model = lv.model().unwrap(); - let app_info = model.item(i - 1); - if app_info.is_none() { - println!("oops no app for this row..."); - return; - } - let app_info = app_info.unwrap().downcast::().unwrap(); - if let Err(err) = app_info.launch(&[], Some(&context)) { - - gtk::MessageDialog::builder() - .text(&format!("Failed to start {}", app_info.name())) - .secondary_text(&err.to_string()) - .message_type(gtk::MessageType::Error) - .modal(true) - .transient_for(&parent_window) - .build() - .show(); - - println!("oops launch failed") - } - println!("{}", i-1); - })); - } - - // Launch the application when an item of the list is activated - list_view.connect_activate(move |list_view, position| { - let model = list_view.model().unwrap(); - let app_info = model - .item(position) - .unwrap() - .downcast::() - .unwrap(); - - let context = list_view.display().app_launch_context(); - if let Err(err) = app_info.launch(&[], Some(&context)) { - let parent_window = list_view.root().unwrap().downcast::().unwrap(); - - gtk::MessageDialog::builder() - .text(&format!("Failed to start {}", app_info.name())) - .secondary_text(&err.to_string()) - .message_type(gtk::MessageType::Error) - .modal(true) - .transient_for(&parent_window) - .build() - .show(); - } - }); - - entry.connect_changed( - glib::clone!(@weak filter_model, @weak sorted_model => move |search: >k::Entry| { - let search_text = search.text().to_string().to_lowercase(); - let new_filter: gtk::CustomFilter = gtk::CustomFilter::new(move |obj| { - let search_res = obj.downcast_ref::() - .expect("The Object needs to be of type AppInfo"); - search_res.name().to_string().to_lowercase().contains(&search_text) - }); - let search_text = search.text().to_string().to_lowercase(); - let new_sorter: gtk::CustomSorter = gtk::CustomSorter::new(move |obj1, obj2| { - let app_info1 = obj1.downcast_ref::().unwrap(); - let app_info2 = obj2.downcast_ref::().unwrap(); - if search_text == "" { - return app_info1 - .name() - .to_lowercase() - .cmp(&app_info2.name().to_lowercase()) - .into(); - } - - let i_1 = app_info1.name().to_lowercase().find(&search_text); - let i_2 = app_info2.name().to_lowercase().find(&search_text); - match (i_1, i_2) { - (Some(i_1), Some(i_2)) => i_1.cmp(&i_2).into(), - (Some(_), None) => std::cmp::Ordering::Less.into(), - (None, Some(_)) => std::cmp::Ordering::Greater.into(), - _ => app_info1 - .name() - .to_lowercase() - .cmp(&app_info2.name().to_lowercase()) - .into() - } - }); - - filter_model.set_filter(Some(new_filter).as_ref()); - sorted_model.set_sorter(Some(new_sorter).as_ref()); - }), - ); - - window.connect_realize(move |window| { - if let Some((display, surface)) = x::get_window_x11(window) { - unsafe { - x::change_property( - &display, - &surface, - "_NET_WM_WINDOW_TYPE", - x::PropMode::Replace, - &[x::Atom::new(&display, "_NET_WM_WINDOW_TYPE_DIALOG").unwrap()], - ); - } - } else { - println!("failed to get X11 window"); - } - }); - - let action_quit = gio::SimpleAction::new("quit", None); - action_quit.connect_activate(glib::clone!(@weak window => move |_, _| { - window.close(); - })); - self.add_action(&action_quit); - - window.connect_is_active_notify(|win| { - if !win.is_active() { - win.close(); - } - }); - } - - fn setup_factory(&self) { - let factory = SignalListItemFactory::new(); - factory.connect_setup(move |_factory, item| { - let row = ApplicationRow::new(); - item.set_child(Some(&row)); - }); - - // the bind stage is used for "binding" the data to the created widgets on the "setup" stage - factory.connect_bind(move |_factory, list_item| { - let app_info = list_item - .item() - .unwrap() - .downcast::() - .unwrap(); - - let child = list_item - .child() - .unwrap() - .downcast::() - .unwrap(); - child.set_app_info(&app_info); - if list_item.position() < 9 { - child.set_shortcut(list_item.position() + 1); - } - }); - // Set the factory of the list view - let imp = imp::Window::from_instance(self); - imp.list_view.set_factory(Some(&factory)); - } -} diff --git a/examples/gtklauncher/window/window.ui b/examples/gtklauncher/window/window.ui deleted file mode 100644 index 9ff6532d..00000000 --- a/examples/gtklauncher/window/window.ui +++ /dev/null @@ -1,32 +0,0 @@ - - - -