use tracing::{debug, warn};
use crate::{gtk::subclass::prelude::*, widget::unit_list::UnitListPanel};
use gtk::prelude::*;
glib::wrapper! {
pub struct UnitPopMenu(ObjectSubclass<imp::UnitPopMenuImp>)
@extends gtk::Popover, gtk::Widget,
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Orientable, gtk::Native,gtk::ShortcutManager;
}
impl UnitPopMenu {
pub fn new(
units_browser: >k::ColumnView,
unit_list_panel: &UnitListPanel,
filtered_list: >k::FilterListModel,
) -> Self {
let obj: UnitPopMenu = glib::Object::new();
obj.imp()
.set_gesture(units_browser, unit_list_panel, filtered_list);
obj
}
pub fn refresh_buttons_style(&self) {
self.imp().refresh_buttons_style();
}
pub fn set_favorite(&self, fav: bool) {
self.imp().set_favorite(fav);
}
}
mod imp {
use crate::{
consts::{DESTRUCTIVE_ACTION, FLAT, SUGGESTED_ACTION},
format2,
systemd::{data::UnitInfo, enums::UnitFileStatus},
upgrade,
widget::{
InterPanelMessage, highlight_unit_text, set_favorite_info,
unit_list::{UnitCuratedList, UnitListPanel},
},
};
use gettextrs::pgettext;
use glib::WeakRef;
use gtk::{gdk, glib::subclass::types::ObjectSubclass, prelude::*, subclass::prelude::*};
use std::{
cell::{OnceCell, RefCell},
rc::Rc,
};
use tracing::{debug, info, warn};
macro_rules! unit {
($self:expr) => {{
let borrow = $self.unit.borrow();
let Some(unit) = borrow.as_ref() else {
warn!("Pop menu has no unit");
return;
};
unit.clone()
}};
}
macro_rules! unit_list_panel {
($self:expr) => {{
let Some(unit_list_panel) = $self.unit_list_panel.get() else {
warn!("Pop menu has no Unit_list_panel");
return;
};
upgrade!(unit_list_panel)
}};
}
#[derive(Default, gtk::CompositeTemplate)]
#[template(resource = "/io/github/plrigaux/sysd-manager/unit_pop_menu.ui")]
pub struct UnitPopMenuImp {
#[template_child]
unit_label: TemplateChild<gtk::Label>,
#[template_child]
start_button: TemplateChild<gtk::Button>,
#[template_child]
stop_button: TemplateChild<gtk::Button>,
#[template_child]
restart_button: TemplateChild<gtk::Button>,
#[template_child]
enable_button: TemplateChild<gtk::Button>,
#[template_child]
disable_button: TemplateChild<gtk::Button>,
#[template_child]
reenable_button: TemplateChild<gtk::Button>,
#[template_child]
reload_unit_button: TemplateChild<gtk::Button>,
#[template_child]
unit_list_view_menubutton: TemplateChild<gtk::MenuButton>,
#[template_child]
totals_summary_button: TemplateChild<gtk::Button>,
#[template_child]
toggle_favorite_button_content: TemplateChild<adw::ButtonContent>,
pub(super) unit_list_panel: OnceCell<WeakRef<UnitListPanel>>,
unit: RefCell<Option<UnitInfo>>,
}
#[gtk::template_callbacks]
impl UnitPopMenuImp {
#[template_callback]
fn enable_button_clicked(&self, _button: gtk::Button) {
let unit = unit!(self);
let pop_menu = self.obj().clone();
let inter_message = InterPanelMessage::EnableUnit(
unit,
Rc::new(Box::new(move || pop_menu.imp().refresh_buttons_style())),
);
unit_list_panel!(self).button_action(&inter_message);
}
#[template_callback]
fn disable_button_clicked(&self, _button: gtk::Button) {
let unit = unit!(self);
let pop_menu = self.obj().clone();
let inter_message = InterPanelMessage::DisableUnit(
unit,
Rc::new(Box::new(move || pop_menu.imp().refresh_buttons_style())),
);
unit_list_panel!(self).button_action(&inter_message);
}
#[template_callback]
fn reenable_button_clicked(&self, _button: gtk::Button) {
let unit = unit!(self);
let pop_menu = self.obj().clone();
let inter_message = InterPanelMessage::ReenableUnit(
unit,
Rc::new(Box::new(move || pop_menu.imp().refresh_buttons_style())),
);
unit_list_panel!(self).button_action(&inter_message);
}
}
impl UnitPopMenuImp {
fn set_unit(&self, unit: Option<&UnitInfo>) {
if let Some(unit) = unit {
self.unit.replace(Some(unit.clone()));
let primary_name = unit.primary();
self.unit_label.set_label(&primary_name);
self.unit_label.set_tooltip_text(Some(&primary_name));
self.set_tooltip(
&self.start_button,
&primary_name,
&pgettext("controls", "Start unit {}"),
);
self.set_tooltip(
&self.stop_button,
&primary_name,
&pgettext("controls", "Stop unit {}"),
);
self.set_tooltip(
&self.restart_button,
&primary_name,
&pgettext("controls", "Restart unit {}"),
);
self.set_tooltip(
&self.enable_button,
&primary_name,
&pgettext("controls", "Enable unit {}"),
);
self.set_tooltip(
&self.disable_button,
&primary_name,
&pgettext("controls", "Disable unit {}"),
);
self.set_tooltip(
&self.reenable_button,
&primary_name,
&pgettext("controls", "Disable and then Enable unit {}"),
);
self.set_tooltip(
&self.reload_unit_button,
&primary_name,
&pgettext("controls", "Reload unit {} configuration by calling the <b>ExecReload</b> unit file instruction"),
);
self.set_buttons_style(unit);
if let Some(a) = self.unit_list_panel.get()
&& let Some(list_panel) = a.upgrade()
{
self.set_favorite(list_panel.is_favorite(unit));
}
} else {
self.unit.replace(None);
}
}
fn set_tooltip(&self, button: >k::Button, unit_primary: &str, tooltip: &str) {
let unit_str = highlight_unit_text(unit_primary);
let tooltip = format2!(tooltip, unit_str);
button.set_tooltip_markup(Some(&tooltip));
}
pub(super) fn set_gesture(
&self,
units_browser: >k::ColumnView,
unit_list_panel: &UnitListPanel,
filtered_list: >k::FilterListModel,
) {
self.obj().set_parent(units_browser);
let gesture = gtk::GestureClick::builder()
.button(gtk::gdk::BUTTON_SECONDARY)
.build();
let units_browser_wr = units_browser.downgrade();
let filtered_list = filtered_list.downgrade();
let unit_list_panel = unit_list_panel.downgrade();
let _ = self.unit_list_panel.set(unit_list_panel.clone());
let pop_up = self.obj().downgrade();
gesture.connect_pressed(move |_gesture_click, n_press, x, y| {
debug!("Pressed n_press: {n_press} x: {x} y: {y}");
let units_browser = upgrade!(units_browser_wr);
let filtered_list = upgrade!(filtered_list);
let pop_up = upgrade!(pop_up);
let Some(adjustement_offset) = units_browser
.vadjustment()
.map(|adjustment| adjustment.value())
else {
warn!("Failed to retreive the adjusment heigth");
return;
};
let adjusted_y = (adjustement_offset + y) as i32;
let mut child_op = units_browser.first_child();
let mut header_height = 0;
let mut line_id = -2;
while let Some(ref child) = child_op {
let child_name = child.type_().name();
if child_name == "GtkColumnViewRowWidget" {
header_height = child.height();
} else if child_name == "GtkColumnListView" {
line_id = super::retreive_row_id(child, adjusted_y, header_height);
break;
}
child_op = child.next_sibling();
}
debug!("Line id {line_id} list count {}", filtered_list.n_items());
if line_id < 0 {
warn!("Some wrongs line_no {line_id}");
return;
}
let line_id = line_id as u32;
if let Some(object) = filtered_list.item(line_id) {
let unit = object.downcast_ref::<UnitInfo>().expect("Ok");
info!(
"Pointing on Unit {} state {}",
unit.primary(),
unit.active_state()
);
let unit_list_panel = upgrade!(unit_list_panel);
unit_list_panel.set_unit(Some(unit));
pop_up.imp().set_unit(Some(unit));
pop_up.set_pointing_to(Some(&gdk::Rectangle::new(x as i32, y as i32, 1, 1)));
pop_up.popup();
} else if line_id >= filtered_list.n_items() {
warn!(
"Line id: {line_id} is over or equals the number of lines {}",
filtered_list.n_items()
);
} else {
warn!("Menu right click. Something wrong");
}
});
gesture.connect_released(|_g, n_press, x, y| {
debug!("Released n_press: {n_press} x: {x} y: {y}")
});
units_browser.add_controller(gesture);
}
pub(super) fn refresh_buttons_style(&self) {
let Some(ref unit) = *self.unit.borrow() else {
debug!("Pop menu has no unit");
return;
};
self.set_buttons_style(unit);
}
pub(super) fn set_buttons_style(&self, unit: &UnitInfo) {
if unit.active_state().is_inactive() {
self.start_button.remove_css_class(FLAT);
self.start_button.add_css_class(SUGGESTED_ACTION);
self.stop_button.remove_css_class(DESTRUCTIVE_ACTION);
} else {
self.start_button.add_css_class(FLAT);
self.start_button.remove_css_class(SUGGESTED_ACTION);
self.stop_button.add_css_class(DESTRUCTIVE_ACTION);
}
if matches!(
unit.enable_status(),
UnitFileStatus::Disabled | UnitFileStatus::Masked
) {
self.enable_button.set_sensitive(true);
self.reenable_button.set_sensitive(false);
self.disable_button.set_sensitive(false);
} else {
self.enable_button.set_sensitive(false);
self.reenable_button.set_sensitive(true);
self.disable_button.set_sensitive(true);
}
}
pub(crate) fn set_favorite(&self, fav: bool) {
let unit = self.unit.borrow();
let (favorite_icon, tooltip) = set_favorite_info(fav, &unit);
self.toggle_favorite_button_content
.set_icon_name(favorite_icon);
self.toggle_favorite_button_content
.set_tooltip_markup(Some(&tooltip));
}
}
#[glib::object_subclass]
impl ObjectSubclass for UnitPopMenuImp {
const NAME: &'static str = "UnitPopMenu";
type Type = super::UnitPopMenu;
type ParentType = gtk::Popover;
fn class_init(klass: &mut Self::Class) {
klass.bind_template();
klass.bind_template_callbacks();
}
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}
impl ObjectImpl for UnitPopMenuImp {
fn constructed(&self) {
self.parent_constructed();
let menu_views = UnitCuratedList::menu_items();
self.unit_list_view_menubutton
.set_menu_model(Some(&menu_views));
}
}
impl WidgetImpl for UnitPopMenuImp {}
impl PopoverImpl for UnitPopMenuImp {}
}
fn retreive_row_id(widget: >k::Widget, y: i32, header_height: i32) -> i32 {
debug!("widjet {widget:?}");
let mut child_op = widget.first_child();
debug!("child_op {child_op:?}");
let mut row_height = -1;
while let Some(child) = child_op {
let w_type_name = child.type_().name();
debug!("widget type name: {w_type_name}");
if w_type_name == "GtkColumnViewRowWidget" {
row_height = child.height();
if row_height > 0 {
break;
}
}
child_op = child.next_sibling();
}
if row_height > 0 {
debug!("y {y} header_height {header_height} row_height {row_height}");
(y - header_height) / row_height
} else {
warn!("Not a valid row height {row_height}");
-1
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_derive_row_id() {
let y = 44;
let header_height = 10;
let row_height = 20;
let row = (y - header_height) / row_height;
println!("{row}");
let y = 20;
let row = (y - header_height) / row_height;
println!("{row}");
}
}