use std::{borrow::Cow, sync::LazyLock};
use glib::Quark;
use gtk::{glib::translate::IntoGlib, pango, prelude::*};
use tracing::debug;
use super::palette::{blue, green, grey, red, yellow};
use crate::{
systemd::{self, enums::ActiveState, generate_file_uri},
systemd_gui::is_dark,
};
use base::enums::UnitDBusLevel;
pub struct UnitInfoWriter {
pub buffer: gtk::TextBuffer,
pub text_iterator: gtk::TextIter,
}
const TAG_NAME_ACTIVE: &str = "active";
const TAG_NAME_ACTIVE_DARK: &str = "active_dark";
const TAG_NAME_RED: &str = "red";
const TAG_NAME_RED_DARK: &str = "red_dark";
const TAG_NAME_DISABLE: &str = "disable";
const TAG_NAME_DISABLE_DARK: &str = "disable_dark";
const TAG_NAME_GREY: &str = "grey";
const TAG_NAME_GREY_DARK: &str = "grey_dark";
const TAG_NAME_STATUS: &str = "blue";
const TAG_NAME_STATUS_DARK: &str = "blue_dark";
pub static TAG_DATA_LINK: LazyLock<Quark> = LazyLock::new(|| Quark::from_str("link"));
pub const PROP_UNDERLINE: &str = "underline";
pub const SPECIAL_GLYPH_TREE_VERTICAL: &str = "│ ";
pub const SPECIAL_GLYPH_TREE_SPACE: &str = " ";
pub const SPECIAL_GLYPH_TREE_RIGHT: &str = "└─";
pub const SPECIAL_GLYPH_TREE_BRANCH: &str = "├─";
const PROP_WEIGHT: &str = "weight";
const PROP_FOREGROUND: &str = "foreground";
pub enum HyperLinkType {
File,
Unit(UnitDBusLevel),
Http,
Man,
None,
}
impl HyperLinkType {
fn ensure_link_type<'a>(&self, link: &'a str) -> Cow<'a, str> {
match self {
HyperLinkType::File => {
if link.starts_with("file://") {
Cow::from(link)
} else {
Cow::from(generate_file_uri(link))
}
}
HyperLinkType::Unit(level) => {
if link.starts_with("unit://") {
Cow::from(link)
} else {
Cow::from(format!("unit://{}?{}", link, level.short()))
}
}
HyperLinkType::Http => Cow::from(link),
HyperLinkType::Man => Cow::from(link),
HyperLinkType::None => Cow::from(link),
}
}
}
impl UnitInfoWriter {
pub fn new(buf: gtk::TextBuffer, iter: gtk::TextIter) -> Self {
Self {
buffer: buf,
text_iterator: iter,
}
}
pub fn insert(&mut self, text: &str) {
self.buffer.insert(&mut self.text_iterator, text);
}
pub fn insertln(&mut self, text: &str) {
self.buffer.insert(&mut self.text_iterator, text);
self.buffer.insert(&mut self.text_iterator, "\n");
}
pub fn newline(&mut self) {
self.buffer.insert(&mut self.text_iterator, "\n");
}
pub fn insert_active(&mut self, text: &str) {
self.insert_tag(text, Self::create_active_tag, None, HyperLinkType::None);
}
pub fn insert_red(&mut self, text: &str) {
self.insert_tag(text, Self::create_red_tag, None, HyperLinkType::None);
}
pub fn insert_disable(&mut self, text: &str) {
self.insert_tag(text, Self::create_disable_tag, None, HyperLinkType::None);
}
pub fn insert_grey(&mut self, text: &str) {
self.insert_tag(text, Self::create_grey_tag, None, HyperLinkType::None);
}
pub fn insert_status(&mut self, text: &str) {
self.insert_tag(text, Self::create_status_tag, None, HyperLinkType::None);
}
pub fn hyperlink(&mut self, text: &str, link: &str, type_: HyperLinkType) {
self.insert_tag(text, Self::create_hyperlink_tag, Some(link), type_);
}
pub fn insert_state(&mut self, state: ActiveState) {
let glyph = state.glyph_str();
match state {
systemd::enums::ActiveState::Active
| systemd::enums::ActiveState::Reloading
| systemd::enums::ActiveState::Activating
| systemd::enums::ActiveState::Refreshing => self.insert_active(glyph),
systemd::enums::ActiveState::Inactive | systemd::enums::ActiveState::Deactivating => {
self.insert(glyph);
}
_ => self.insert_red(glyph),
}
}
fn create_hyperlink_tag(buf: >k::TextBuffer) -> Option<gtk::TextTag> {
buf.create_tag(
None,
&[
(PROP_UNDERLINE, &pango::Underline::SingleLine.to_value()),
],
)
}
fn create_active_tag(buf: >k::TextBuffer) -> Option<gtk::TextTag> {
let (color, name) = if is_dark() {
(green(), TAG_NAME_ACTIVE_DARK)
} else {
(green(), TAG_NAME_ACTIVE)
};
let tag_op = buf.tag_table().lookup(name);
if tag_op.is_some() {
return tag_op;
}
buf.create_tag(
Some(name),
&[
(PROP_FOREGROUND, &color.get_color().to_value()),
(PROP_WEIGHT, &pango::Weight::Bold.into_glib().to_value()),
],
)
}
fn create_red_tag(buf: >k::TextBuffer) -> Option<gtk::TextTag> {
let (color, name) = if is_dark() {
(red(), TAG_NAME_RED_DARK)
} else {
(red(), TAG_NAME_RED)
};
let tag_op = buf.tag_table().lookup(name);
if tag_op.is_some() {
return tag_op;
}
buf.create_tag(
Some(name),
&[
(PROP_FOREGROUND, &color.get_color().to_value()),
(PROP_WEIGHT, &pango::Weight::Bold.into_glib().to_value()),
],
)
}
fn create_disable_tag(buf: >k::TextBuffer) -> Option<gtk::TextTag> {
let (color, name) = if is_dark() {
(yellow(), TAG_NAME_DISABLE_DARK)
} else {
(yellow(), TAG_NAME_DISABLE)
};
let tag_op = buf.tag_table().lookup(name);
if tag_op.is_some() {
return tag_op;
}
buf.create_tag(
Some(name),
&[
(PROP_FOREGROUND, &color.get_color().to_value()),
(PROP_WEIGHT, &pango::Weight::Bold.into_glib().to_value()),
],
)
}
fn create_grey_tag(buf: >k::TextBuffer) -> Option<gtk::TextTag> {
let (color, name) = if is_dark() {
(grey(), TAG_NAME_GREY_DARK)
} else {
(grey(), TAG_NAME_GREY)
};
let tag_op = buf.tag_table().lookup(name);
if tag_op.is_some() {
return tag_op;
}
buf.create_tag(
Some(name),
&[(PROP_FOREGROUND, &color.get_color().to_value())],
)
}
fn create_status_tag(buf: >k::TextBuffer) -> Option<gtk::TextTag> {
let (color, name) = if is_dark() {
(blue(), TAG_NAME_STATUS_DARK)
} else {
(blue(), TAG_NAME_STATUS)
};
let tag_op = buf.tag_table().lookup(name);
if tag_op.is_some() {
return tag_op;
}
buf.create_tag(
Some(name),
&[
(PROP_FOREGROUND, &color.get_color().to_value()),
(PROP_WEIGHT, &pango::Weight::Bold.into_glib().to_value()),
],
)
}
fn insert_tag(
&mut self,
text: &str,
create_tag: impl Fn(>k::TextBuffer) -> Option<gtk::TextTag>,
link: Option<&str>,
type_: HyperLinkType,
) {
let start_offset = self.text_iterator.offset();
self.buffer.insert(&mut self.text_iterator, text);
let tag = create_tag(&self.buffer);
if let Some(tag) = tag {
if let Some(link) = link {
let link = type_.ensure_link_type(link);
let link_value = link.to_value();
debug!("text {text} link {link_value:?}");
unsafe {
tag.set_qdata(*TAG_DATA_LINK, link_value);
}
}
let start_iter = self.buffer.iter_at_offset(start_offset);
self.buffer
.apply_tag(&tag, &start_iter, &self.text_iterator);
}
}
pub fn char_count(&self) -> i32 {
self.buffer.char_count()
}
}