diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index b58b603..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ diff --git a/.idea/modules.xml b/.idea/modules.xml index 3223494..99e4abf 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,7 +2,7 @@ - + \ No newline at end of file diff --git a/.idea/project-leoali.iml b/.idea/pika-idk-manager.iml similarity index 100% rename from .idea/project-leoali.iml rename to .idea/pika-idk-manager.iml diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 35eb1dd..9c36e98 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -2,5 +2,6 @@ + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..a3af2f4 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + { + "associatedIndex": 8 +} + + + + { + "keyToString": { + "Cargo.Run apt_update.executor": "Run", + "RunOnceActivity.ShowReadmeOnStart": "true", + "RunOnceActivity.rust.reset.selective.auto.import": "true", + "git-widget-placeholder": "master", + "last_opened_file_path": "/home/ward/RustroverProjects/pika-idk-manager", + "node.js.detected.package.eslint": "true", + "node.js.selected.package.eslint": "(autodetect)", + "nodejs_package_manager_path": "npm", + "org.rust.cargo.project.model.PROJECT_DISCOVERY": "true", + "org.rust.first.attach.projects": "true" + } +} + + + + + + + + + + + + + + + + + + + + 1719504951223 + + + + + + \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 29b11c6..b862f56 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1138,6 +1138,23 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pika-idk-manager" +version = "0.1.0" +dependencies = [ + "async-channel", + "async-trait", + "futures 0.3.30", + "gtk4", + "libadwaita", + "rust-apt", + "rust-i18n", + "serde", + "serde_json", + "tokio", + "tokio-uds", +] + [[package]] name = "pin-project-lite" version = "0.2.14" @@ -1208,23 +1225,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "project-leoali" -version = "0.1.0" -dependencies = [ - "async-channel", - "async-trait", - "futures 0.3.30", - "gtk4", - "libadwaita", - "rust-apt", - "rust-i18n", - "serde", - "serde_json", - "tokio", - "tokio-uds", -] - [[package]] name = "quote" version = "1.0.36" diff --git a/Cargo.toml b/Cargo.toml index 3450370..7c73ed0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,9 @@ [package] -name = "project-leoali" +name = "pika-idk-manager" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[[bin]] -name = "gui" -path = "src/gui/main.rs" - [lib] name = "pika_unixsocket_tools" path = "src/pika_unixsocket_tools/lib.rs" diff --git a/locales/en_US.json b/locales/en_US.json new file mode 100644 index 0000000..cbdf86c --- /dev/null +++ b/locales/en_US.json @@ -0,0 +1,6 @@ +{ + "application_name": "Pika IDK yet Manager", + "installed_version_badge_text": "Installed", + "candidate_version_badge_text": "Upgradable", + "arch_label_label": "Arch" +} \ No newline at end of file diff --git a/src/apt_package_row/imp.rs b/src/apt_package_row/imp.rs new file mode 100644 index 0000000..2aff932 --- /dev/null +++ b/src/apt_package_row/imp.rs @@ -0,0 +1,260 @@ +use std::{cell::RefCell, default, sync::OnceLock}; + +use adw::*; +use adw::{prelude::*, subclass::prelude::*}; +use glib::{subclass::Signal, Properties, clone}; +use gtk::{Align, glib, Orientation, SizeGroupMode, SelectionMode, Widget}; +use gtk::Orientation::Horizontal; +use crate::apt_update_page::AptPackageSocket; +use std::env; + +// ANCHOR: custom_button +// Object holding the state +#[derive(Properties, Default)] +#[properties(wrapper_type = super::AptPackageRow)] +pub struct AptPackageRow { + #[property(get, set)] + package_name: RefCell, + #[property(get, set)] + package_arch: RefCell, + #[property(get, set)] + package_installed_version: RefCell, + #[property(get, set)] + package_candidate_version: RefCell +} +// ANCHOR_END: custom_button + +// The central trait for subclassing a GObject +#[glib::object_subclass] +impl ObjectSubclass for AptPackageRow { + const NAME: &'static str = "AptPackageRow"; + type Type = super::AptPackageRow; + type ParentType = adw::ExpanderRow; +} + +// ANCHOR: object_impl +// Trait shared by all GObjects +#[glib::derived_properties] +impl ObjectImpl for AptPackageRow { + fn signals() -> &'static [Signal] { + static SIGNALS: OnceLock> = OnceLock::new(); + SIGNALS.get_or_init(|| vec![Signal::builder("row-deleted").build()]) + } + fn constructed(&self) { + let current_locale = match env::var_os("LANG") { + Some(v) => v.into_string().unwrap().chars() + .take_while(|&ch| ch != '.') + .collect::(), + None => panic!("$LANG is not set"), + }; + rust_i18n::set_locale(¤t_locale); + + self.parent_constructed(); + + // Bind label to number + // `SYNC_CREATE` ensures that the label will be immediately set + let obj = self.obj(); + + let prefix_box = gtk::Box::new(Orientation::Vertical, 0); + + let expandable_box = gtk::Box::new(Orientation::Vertical, 0); + expandable_box.add_css_class("linked"); + + obj.connect_package_name_notify(clone!(@weak prefix_box, @weak expandable_box, @strong obj => move |obj| { + remove_all_children(&prefix_box); + remove_all_children(&expandable_box); + // + let package_name = obj.package_name(); + let package_arch = obj.package_arch(); + let package_installed_version= obj.package_installed_version(); + let package_candidate_version= obj.package_candidate_version(); + // + let package_label = gtk::Label::builder() + .halign(Align::Start) + .margin_start(5) + .margin_end(5) + .margin_bottom(5) + .margin_top(5) + .label(package_name) + .build(); + package_label.add_css_class("size-20-bold-text"); + let version_box = gtk::Box::new(Orientation::Horizontal, 0); + version_box.append(&create_version_badge(package_installed_version, package_candidate_version)); + version_box.append(&create_arch_badge(package_arch)); + prefix_box.append(&package_label); + prefix_box.append(&version_box); + // + let expandable_page_selection_box = gtk::Box::builder() + .orientation(Orientation::Horizontal) + .hexpand(false) + .vexpand(false) + .halign(Align::Start) + .valign(Align::Start) + .build(); + expandable_page_selection_box.add_css_class("linked"); + let description_page_button = gtk::ToggleButton::builder() + .label("Description") + .active(true) + .build(); + let changelog_page_button = gtk::ToggleButton::builder() + .label("Changelog") + .group(&description_page_button) + .build(); + expandable_page_selection_box.append(&description_page_button); + expandable_page_selection_box.append(&changelog_page_button); + expandable_box.append(&expandable_page_selection_box); + })); + + obj.add_prefix(&prefix_box); + obj.add_row(&expandable_box); + + //let obj = self.obj(); + //obj.bind_property("package-name", &package_label, "label") + // .sync_create() + // .bidirectional() + // .build(); + } +} +// Trait shared by all widgets +impl WidgetImpl for AptPackageRow {} + +// Trait shared by all buttons +// Trait shared by all buttons + +impl ListBoxRowImpl for AptPackageRow {} +impl PreferencesRowImpl for AptPackageRow {} +impl ExpanderRowImpl for AptPackageRow {} + +fn create_version_badge(installed_version: String, candidate_version: String) -> gtk::ListBox { + let (base_version, installed_diff, candidate_diff) = get_diff_by_prefix(installed_version, candidate_version); + + let badge_box = gtk::Box::builder() + .halign(Align::Start) + .hexpand(false) + .orientation(Orientation::Horizontal) + .margin_start(5) + .margin_end(5) + .margin_bottom(5) + .margin_top(5) + .build(); + + let installed_version_box = gtk::Box::builder() + .halign(Align::Start) + .hexpand(false) + .orientation(Orientation::Horizontal) + .tooltip_text(t!("installed_version_badge_text")) + .build(); + + let installed_version_base_version_label = gtk::Label::builder() + .label(format!("{}: {}", t!("installed_version_badge_text"), &base_version)) + .valign(Align::Center) + .halign(Align::Start) + .hexpand(false) + .vexpand(true) + .build(); + + let installed_diff_label = gtk::Label::builder() + .label(installed_diff) + .valign(Align::Center) + .halign(Align::Start) + .hexpand(false) + .vexpand(true) + .build(); + installed_diff_label.add_css_class("destructive-color-text"); + + installed_version_box.append(&installed_version_base_version_label.clone()); + installed_version_box.append(&installed_diff_label); + + let label_separator = gtk::Separator::builder() + .margin_start(5) + .margin_end(5) + .build(); + + let candidate_version_box = gtk::Box::builder() + .halign(Align::Start) + .hexpand(false) + .orientation(Orientation::Horizontal) + .tooltip_text(t!("candidate_version_badge_text")) + .build(); + + let candidate_version_base_version_label = gtk::Label::builder() + .label(format!("{}: {}", t!("candidate_version_badge_text"), &base_version)) + .valign(Align::Center) + .halign(Align::Start) + .hexpand(false) + .vexpand(true) + .build(); + + let candidate_diff_label = gtk::Label::builder() + .label(candidate_diff) + .valign(Align::Center) + .halign(Align::Start) + .hexpand(false) + .vexpand(true) + .build(); + candidate_diff_label.add_css_class("success-color-text"); + + candidate_version_box.append(&candidate_version_base_version_label); + candidate_version_box.append(&candidate_diff_label); + + badge_box.append(&installed_version_box); + badge_box.append(&label_separator); + badge_box.append(&candidate_version_box); + + let boxedlist = gtk::ListBox::builder() + .selection_mode(SelectionMode::None) + .halign(Align::Start) + .valign(Align::End) + .margin_start(5) + .margin_end(5) + .margin_bottom(10) + .build(); + + boxedlist.add_css_class("boxed-list"); + boxedlist.append(&badge_box); + boxedlist +} + +fn create_arch_badge(arch: String) -> gtk::ListBox { + let arch_label = gtk::Label::builder() + .halign(Align::Start) + .hexpand(false) + .label(format!("{}: {}", t!("arch_label_label"), arch)) + .margin_start(5) + .margin_end(5) + .margin_bottom(5) + .margin_top(5) + .build(); + + let boxedlist = gtk::ListBox::builder() + .selection_mode(SelectionMode::None) + .halign(Align::Start) + .valign(Align::End) + .margin_start(5) + .margin_end(5) + .margin_bottom(10) + .build(); + + boxedlist.add_css_class("boxed-list"); + boxedlist.append(&arch_label); + boxedlist +} + +fn remove_all_children(parent: &impl IsA) where impl IsA: libadwaita::prelude::IsA, impl IsA: libadwaita::prelude::IsA, impl IsA: libadwaita::prelude::IsA, impl IsA: libadwaita::prelude::IsA, impl IsA: libadwaita::prelude::IsA, impl IsA: libadwaita::prelude::IsA, impl IsA: libadwaita::prelude::IsA, impl IsA: libadwaita::prelude::IsA, impl IsA: libadwaita::prelude::IsA, impl IsA: libadwaita::prelude::IsA, impl IsA: libadwaita::prelude::IsA, impl IsA: libadwaita::prelude::IsA { + while let Some(child) = parent.last_child() { + parent.remove(&child); + } +} + +pub fn get_diff_by_prefix(xs: String, ys: String) -> (String, String, String) { + let mut count = String::new(); + for (x,y) in xs.chars().zip(ys.chars()) { + if x == y { + count.push(x) + } else { + break + } + } + let count_clone0 = count.clone(); + return(count_clone0, xs.trim_start_matches(&count.as_str()).to_string(), ys.trim_start_matches(&count.as_str()).to_string()) +} \ No newline at end of file diff --git a/src/gui/apt_package_row/mod.rs b/src/apt_package_row/mod.rs similarity index 91% rename from src/gui/apt_package_row/mod.rs rename to src/apt_package_row/mod.rs index d794a8e..ca05a5b 100644 --- a/src/gui/apt_package_row/mod.rs +++ b/src/apt_package_row/mod.rs @@ -6,7 +6,7 @@ use crate::apt_update_page::AptPackageSocket; glib::wrapper! { pub struct AptPackageRow(ObjectSubclass) - @extends adw::ActionRow, gtk::Widget, gtk::ListBoxRow, adw::PreferencesRow, + @extends adw::ExpanderRow, gtk::Widget, gtk::ListBoxRow, adw::PreferencesRow, @implements gtk::Accessible, gtk::Actionable, gtk::Buildable, gtk::ConstraintTarget; } diff --git a/src/gui/apt_update_page/mod.rs b/src/apt_update_page/mod.rs similarity index 90% rename from src/gui/apt_update_page/mod.rs rename to src/apt_update_page/mod.rs index ce5dd62..db5504b 100644 --- a/src/gui/apt_update_page/mod.rs +++ b/src/apt_update_page/mod.rs @@ -38,7 +38,7 @@ pub fn apt_update_page(window: adw::ApplicationWindow) -> gtk::Box { }); Command::new("pkexec") - .args(["/home/ward/RustroverProjects/project-leoali/target/debug/apt_update"]) + .args(["/home/ward/RustroverProjects/pika-idk-manager/target/debug/apt_update"]) .spawn(); let main_box = gtk::Box::builder() @@ -66,13 +66,6 @@ pub fn apt_update_page(window: adw::ApplicationWindow) -> gtk::Box { packages_boxedlist.add_css_class("boxed-list"); let rows_size_group = gtk::SizeGroup::new(SizeGroupMode::Both); - packages_boxedlist.append(&AptPackageRow::new(AptPackageSocket{ - name: "name".to_string(), - arch: "arch".to_string(), - installed_version: "0.0".to_string(), - candidate_version: "0.0".to_string() - })); - let packages_viewport = gtk::ScrolledWindow::builder() .hscrollbar_policy(PolicyType::Never) .vexpand(true) @@ -94,14 +87,16 @@ pub fn apt_update_page(window: adw::ApplicationWindow) -> gtk::Box { .hexpand(true) .build(); - apt_update_dialog_child_box.append(>k::Spinner::builder() + let apt_update_dialog_spinner = gtk::Spinner::builder() .hexpand(true) .valign(Align::Start) .halign(Align::Center) .spinning(true) .height_request(128) .width_request(128) - .build()); + .build(); + + apt_update_dialog_child_box.append(&apt_update_dialog_spinner); apt_update_dialog_child_box.append(&apt_update_dialog_progress_bar); let apt_update_dialog = adw::MessageDialog::builder() @@ -147,10 +142,14 @@ pub fn apt_update_page(window: adw::ApplicationWindow) -> gtk::Box { let update_status_server_context = MainContext::default(); // The main loop executes the asynchronous block - update_status_server_context.spawn_local(clone!(@weak apt_update_dialog => async move { + update_status_server_context.spawn_local(clone!(@weak apt_update_dialog, @weak apt_update_dialog_spinner => async move { while let Ok(state) = update_status_receiver.recv().await { match state.as_ref() { "FN_OVERRIDE_SUCCESSFUL" => {} + "FN_OVERRIDE_FAILED" => { + apt_update_dialog_spinner.set_spinning(false); + apt_update_dialog.set_body(&t!("apt_update_dialog_status_failed").to_string()) + } _ => apt_update_dialog.set_body(&state) } } @@ -158,9 +157,14 @@ pub fn apt_update_page(window: adw::ApplicationWindow) -> gtk::Box { let get_upgradable_server_context = MainContext::default(); // The main loop executes the asynchronous block - get_upgradable_server_context.spawn_local(clone!(@weak window => async move { + get_upgradable_server_context.spawn_local(clone!(@weak packages_boxedlist => async move { while let Ok(state) = get_upgradable_receiver.recv().await { - println!("{}", state.name) + packages_boxedlist.append(&AptPackageRow::new(AptPackageSocket { + name: state.name, + arch: state.arch, + installed_version: state.installed_version, + candidate_version: state.candidate_version + })); } })); diff --git a/src/gui/build_ui/mod.rs b/src/build_ui/mod.rs similarity index 100% rename from src/gui/build_ui/mod.rs rename to src/build_ui/mod.rs diff --git a/src/gui/config.rs b/src/config.rs similarity index 100% rename from src/gui/config.rs rename to src/config.rs diff --git a/src/gui/apt_package_row/imp.rs b/src/gui/apt_package_row/imp.rs deleted file mode 100644 index b09f9d9..0000000 --- a/src/gui/apt_package_row/imp.rs +++ /dev/null @@ -1,182 +0,0 @@ -use std::{cell::RefCell, default, sync::OnceLock}; - -use adw::*; -use adw::{prelude::*, subclass::prelude::*}; -use glib::{subclass::Signal, Properties}; -use gtk::{Align, glib, Orientation, SizeGroupMode, SelectionMode}; -use gtk::Orientation::Horizontal; -use crate::apt_update_page::AptPackageSocket; - -// ANCHOR: custom_button -// Object holding the state -#[derive(Properties, Default)] -#[properties(wrapper_type = super::AptPackageRow)] -pub struct AptPackageRow { - #[property(get, set)] - package_name: RefCell, - #[property(get, set)] - package_arch: RefCell, - #[property(get, set)] - package_installed_version: RefCell, - #[property(get, set)] - package_candidate_version: RefCell -} -// ANCHOR_END: custom_button - -// The central trait for subclassing a GObject -#[glib::object_subclass] -impl ObjectSubclass for AptPackageRow { - const NAME: &'static str = "AptPackageRow"; - type Type = super::AptPackageRow; - type ParentType = adw::ActionRow; -} - -// ANCHOR: object_impl -// Trait shared by all GObjects -#[glib::derived_properties] -impl ObjectImpl for AptPackageRow { - fn signals() -> &'static [Signal] { - static SIGNALS: OnceLock> = OnceLock::new(); - SIGNALS.get_or_init(|| vec![Signal::builder("row-deleted").build()]) - } - fn constructed(&self) { - self.parent_constructed(); - - // Bind label to number - // `SYNC_CREATE` ensures that the label will be immediately set - let obj = self.obj(); - - let package_name = obj.package_name(); - let package_arch = obj.package_arch(); - let package_installed_version= obj.package_installed_version(); - let package_candidate_version= obj.package_candidate_version(); - - println!("tetst {}" package_name); - - let prefix_box = gtk::Box::new(Orientation::Vertical, 0); - - let package_label = gtk::Label::builder() - .build(); - package_label.add_css_class("size-20-bold-text"); - - prefix_box.append(&package_label); - prefix_box.append(&create_version_badge(package_installed_version, package_candidate_version)); - - obj.add_prefix(&prefix_box); - - let obj = self.obj(); - obj.bind_property("package-name", &package_label, "label") - .sync_create() - .bidirectional() - .build(); - } -} -// Trait shared by all widgets -impl WidgetImpl for AptPackageRow {} - -// Trait shared by all buttons -// Trait shared by all buttons - -impl ListBoxRowImpl for AptPackageRow {} -impl PreferencesRowImpl for AptPackageRow {} -impl ActionRowImpl for AptPackageRow {} - -fn create_version_badge(installed_version: String, candidate_version: String) -> gtk::ListBox { - let (base_version, installed_diff, candidate_diff) = get_diff_by_prefix(installed_version, candidate_version); - - let badge_box = gtk::Box::builder() - .halign(Align::Start) - .hexpand(false) - .orientation(Orientation::Horizontal) - .margin_start(5) - .margin_end(5) - .margin_bottom(5) - .margin_top(5) - .build(); - - let installed_version_box = gtk::Box::builder() - .halign(Align::Start) - .hexpand(false) - .orientation(Orientation::Horizontal) - .tooltip_text(t!("installed_version_box_tooltip_text")) - .build(); - - let installed_version_base_version_label = gtk::Label::builder() - .label(&base_version) - .valign(Align::Center) - .halign(Align::Start) - .hexpand(false) - .vexpand(true) - .build(); - - let installed_diff_label = gtk::Label::builder() - .label(installed_diff) - .valign(Align::Center) - .halign(Align::Start) - .hexpand(false) - .vexpand(true) - .build(); - installed_diff_label.add_css_class("destructive-color-text"); - - installed_version_box.append(&installed_version_base_version_label.clone()); - installed_version_box.append(&installed_diff_label); - - let label_separator = gtk::Separator::builder() - .margin_start(5) - .margin_end(5) - .build(); - - let candidate_version_box = gtk::Box::builder() - .halign(Align::Start) - .hexpand(false) - .orientation(Orientation::Horizontal) - .tooltip_text(t!("candidate_version_box_tooltip_text")) - .build(); - - let candidate_version_base_version_label = gtk::Label::builder() - .label(base_version) - .valign(Align::Center) - .halign(Align::Start) - .hexpand(false) - .vexpand(true) - .build(); - - let candidate_diff_label = gtk::Label::builder() - .label(candidate_diff) - .valign(Align::Center) - .halign(Align::Start) - .hexpand(false) - .vexpand(true) - .build(); - candidate_diff_label.add_css_class("success-color-text"); - - candidate_version_box.append(&candidate_version_base_version_label); - candidate_version_box.append(&candidate_diff_label); - - badge_box.append(&installed_version_box); - badge_box.append(&label_separator); - badge_box.append(&candidate_version_box); - - let boxedlist = gtk::ListBox::builder() - .selection_mode(SelectionMode::None) - .halign(Align::Start) - .valign(Align::End) - .build(); - - boxedlist.add_css_class("boxed-list"); - boxedlist.append(&badge_box); - boxedlist -} - -pub fn get_diff_by_prefix(xs: String, ys: String) -> (String, String, String) { - let mut count = String::new(); - for (x,y) in xs.chars().zip(ys.chars()) { - if x == y { - count.push(x) - } else { - break - } - } - let count_clone0 = count.clone(); - return(count_clone0, xs.trim_start_matches(&count.as_str()).to_string(), ys.trim_start_matches(&count.as_str()).to_string()) -} \ No newline at end of file diff --git a/src/gui/main.rs b/src/main.rs similarity index 88% rename from src/gui/main.rs rename to src/main.rs index ed19d36..6326e75 100644 --- a/src/gui/main.rs +++ b/src/main.rs @@ -21,10 +21,12 @@ i18n!("locales", fallback = "en_US"); /// main function fn main() { let current_locale = match env::var_os("LANG") { - Some(v) => v.into_string().unwrap(), + Some(v) => v.into_string().unwrap().chars() + .take_while(|&ch| ch != '.') + .collect::(), None => panic!("$LANG is not set"), }; - rust_i18n::set_locale(current_locale.strip_suffix(".UTF-8").unwrap()); + rust_i18n::set_locale(¤t_locale); let application = adw::Application::new( Some(APP_ID), Default::default(), diff --git a/src/gui/style.css b/src/style.css similarity index 100% rename from src/gui/style.css rename to src/style.css