mod dummy_object;
pub mod expression;
mod expression_list_model;
mod location;
pub mod macros;
pub mod matrix;
pub mod media;
pub mod notifications;
pub(crate) mod oidc;
mod single_item_list_model;
pub mod sourceview;
pub mod string;
pub mod template_callbacks;
use std::{
cell::{Cell, OnceCell, RefCell},
fmt, fs,
io::{self, Write},
path::{Path, PathBuf},
rc::{Rc, Weak},
sync::{Arc, LazyLock},
};
use futures_util::{
future::{self, Either, Future},
pin_mut,
};
use gtk::{gdk, gio, glib, prelude::*, subclass::prelude::*};
use regex::Regex;
use tempfile::NamedTempFile;
pub use self::{
dummy_object::DummyObject,
expression_list_model::ExpressionListModel,
location::{Location, LocationError, LocationExt},
single_item_list_model::SingleItemListModel,
};
use crate::{PROFILE, RUNTIME};
pub fn data_dir_path(data_type: DataType) -> PathBuf {
let mut path = match data_type {
DataType::Persistent => glib::user_data_dir(),
DataType::Cache => glib::user_cache_dir(),
};
path.push(PROFILE.dir_name().as_ref());
path
}
#[derive(Debug, Clone, Copy)]
pub enum DataType {
Persistent,
Cache,
}
pub enum TimeoutFuture {
Timeout,
}
pub async fn timeout_future<T>(
timeout: std::time::Duration,
fut: impl Future<Output = T>,
) -> Result<T, TimeoutFuture> {
let timeout = glib::timeout_future(timeout);
pin_mut!(fut);
match future::select(fut, timeout).await {
Either::Left((x, _)) => Ok(x),
Either::Right(_) => Err(TimeoutFuture::Timeout),
}
}
pub fn freplace(s: String, args: &[(&str, &str)]) -> String {
let mut s = s;
for (k, v) in args {
s = s.replace(&format!("{{{k}}}"), v);
}
s
}
pub static EMOJI_REGEX: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(
r"(?x)
^
[\p{White_Space}\p{Emoji_Component}]*
[\p{Emoji}--\p{Decimal_Number}]+
[\p{White_Space}\p{Emoji}\p{Emoji_Component}--\p{Decimal_Number}]*
$
# That string is made of at least one emoji, except digits, possibly more,
# possibly with modifiers, possibly with spaces, but nothing else
",
)
.unwrap()
});
#[derive(Debug)]
pub struct BoundObjectInner<T: ObjectType> {
obj: T,
signal_handler_ids: Vec<glib::SignalHandlerId>,
}
#[derive(Debug)]
pub struct BoundObject<T: ObjectType> {
inner: RefCell<Option<BoundObjectInner<T>>>,
}
impl<T: ObjectType> BoundObject<T> {
pub fn new() -> Self {
Self::default()
}
pub fn set(&self, obj: T, signal_handler_ids: Vec<glib::SignalHandlerId>) {
self.disconnect_signals();
let inner = BoundObjectInner {
obj,
signal_handler_ids,
};
self.inner.replace(Some(inner));
}
pub fn obj(&self) -> Option<T> {
self.inner.borrow().as_ref().map(|inner| inner.obj.clone())
}
pub fn disconnect_signals(&self) {
if let Some(inner) = self.inner.take() {
for signal_handler_id in inner.signal_handler_ids {
inner.obj.disconnect(signal_handler_id);
}
}
}
}
impl<T: ObjectType> Default for BoundObject<T> {
fn default() -> Self {
Self {
inner: Default::default(),
}
}
}
impl<T: ObjectType> Drop for BoundObject<T> {
fn drop(&mut self) {
self.disconnect_signals();
}
}
impl<T: IsA<glib::Object> + glib::HasParamSpec> glib::property::Property for BoundObject<T> {
type Value = Option<T>;
}
impl<T: IsA<glib::Object>> glib::property::PropertyGet for BoundObject<T> {
type Value = Option<T>;
fn get<R, F: Fn(&Self::Value) -> R>(&self, f: F) -> R {
f(&self.obj())
}
}
#[derive(Debug)]
pub struct BoundObjectWeakRef<T: ObjectType> {
weak_obj: glib::WeakRef<T>,
signal_handler_ids: RefCell<Vec<glib::SignalHandlerId>>,
}
impl<T: ObjectType> BoundObjectWeakRef<T> {
pub fn new() -> Self {
Self::default()
}
pub fn set(&self, obj: &T, signal_handler_ids: Vec<glib::SignalHandlerId>) {
self.disconnect_signals();
self.weak_obj.set(Some(obj));
self.signal_handler_ids.replace(signal_handler_ids);
}
pub fn obj(&self) -> Option<T> {
self.weak_obj.upgrade()
}
pub fn disconnect_signals(&self) {
let signal_handler_ids = self.signal_handler_ids.take();
if let Some(obj) = self.weak_obj.upgrade() {
for signal_handler_id in signal_handler_ids {
obj.disconnect(signal_handler_id);
}
}
self.weak_obj.set(None);
}
}
impl<T: ObjectType> Default for BoundObjectWeakRef<T> {
fn default() -> Self {
Self {
weak_obj: Default::default(),
signal_handler_ids: Default::default(),
}
}
}
impl<T: ObjectType> Drop for BoundObjectWeakRef<T> {
fn drop(&mut self) {
self.disconnect_signals();
}
}
impl<T: IsA<glib::Object> + glib::HasParamSpec> glib::property::Property for BoundObjectWeakRef<T> {
type Value = Option<T>;
}
impl<T: IsA<glib::Object>> glib::property::PropertyGet for BoundObjectWeakRef<T> {
type Value = Option<T>;
fn get<R, F: Fn(&Self::Value) -> R>(&self, f: F) -> R {
f(&self.obj())
}
}
#[derive(Debug)]
pub struct BoundConstructOnlyObject<T: ObjectType> {
obj: OnceCell<T>,
signal_handler_ids: RefCell<Vec<glib::SignalHandlerId>>,
}
impl<T: ObjectType> BoundConstructOnlyObject<T> {
pub fn new() -> Self {
Self::default()
}
pub fn set(&self, obj: T, signal_handler_ids: Vec<glib::SignalHandlerId>) {
self.obj.set(obj).unwrap();
self.signal_handler_ids.replace(signal_handler_ids);
}
pub fn obj(&self) -> &T {
self.obj.get().unwrap()
}
}
impl<T: ObjectType> Default for BoundConstructOnlyObject<T> {
fn default() -> Self {
Self {
obj: Default::default(),
signal_handler_ids: Default::default(),
}
}
}
impl<T: ObjectType> Drop for BoundConstructOnlyObject<T> {
fn drop(&mut self) {
let signal_handler_ids = self.signal_handler_ids.take();
if let Some(obj) = self.obj.get() {
for signal_handler_id in signal_handler_ids {
obj.disconnect(signal_handler_id);
}
}
}
}
impl<T: IsA<glib::Object> + glib::HasParamSpec> glib::property::Property
for BoundConstructOnlyObject<T>
{
type Value = T;
}
impl<T: IsA<glib::Object>> glib::property::PropertyGet for BoundConstructOnlyObject<T> {
type Value = T;
fn get<R, F: Fn(&Self::Value) -> R>(&self, f: F) -> R {
f(self.obj())
}
}
#[derive(Debug)]
pub struct OngoingAsyncAction<T> {
strong: Rc<AsyncAction<T>>,
}
impl<T> OngoingAsyncAction<T> {
pub fn set(value: T) -> (Self, WeakOngoingAsyncAction<T>) {
let strong = Rc::new(AsyncAction::Set(value));
let weak = Rc::downgrade(&strong);
(Self { strong }, WeakOngoingAsyncAction { weak })
}
pub fn remove() -> (Self, WeakOngoingAsyncAction<T>) {
let strong = Rc::new(AsyncAction::Remove);
let weak = Rc::downgrade(&strong);
(Self { strong }, WeakOngoingAsyncAction { weak })
}
pub fn downgrade(&self) -> WeakOngoingAsyncAction<T> {
let weak = Rc::downgrade(&self.strong);
WeakOngoingAsyncAction { weak }
}
pub fn action(&self) -> &AsyncAction<T> {
&self.strong
}
pub fn as_value(&self) -> Option<&T> {
self.strong.as_value()
}
}
#[derive(Debug, Clone)]
pub struct WeakOngoingAsyncAction<T> {
weak: Weak<AsyncAction<T>>,
}
impl<T> WeakOngoingAsyncAction<T> {
pub fn is_ongoing(&self) -> bool {
self.weak.strong_count() > 0
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AsyncAction<T> {
Set(T),
Remove,
}
impl<T> AsyncAction<T> {
pub fn as_value(&self) -> Option<&T> {
match self {
Self::Set(value) => Some(value),
Self::Remove => None,
}
}
}
#[derive(Debug, Clone)]
pub struct TokioDrop<T>(OnceCell<T>);
impl<T> TokioDrop<T> {
pub fn new() -> Self {
Self::default()
}
pub fn get(&self) -> Option<&T> {
self.0.get()
}
pub fn set(&self, value: T) -> Result<(), T> {
self.0.set(value)
}
}
impl<T> Default for TokioDrop<T> {
fn default() -> Self {
Self(Default::default())
}
}
impl<T> Drop for TokioDrop<T> {
fn drop(&mut self) {
let _guard = RUNTIME.enter();
if let Some(inner) = self.0.take() {
drop(inner);
}
}
}
impl<T: glib::property::Property> glib::property::Property for TokioDrop<T> {
type Value = T::Value;
}
impl<T> glib::property::PropertyGet for TokioDrop<T> {
type Value = T;
fn get<R, F: Fn(&Self::Value) -> R>(&self, f: F) -> R {
f(self.get().unwrap())
}
}
impl<T> glib::property::PropertySet for TokioDrop<T> {
type SetValue = T;
fn set(&self, v: Self::SetValue) {
assert!(
self.set(v).is_ok(),
"TokioDrop value was already initialized"
);
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, glib::Enum)]
#[enum_type(name = "LoadingState")]
pub enum LoadingState {
#[default]
Initial,
Loading,
Ready,
Error,
}
pub fn bool_to_accessible_tristate(checked: bool) -> gtk::AccessibleTristate {
if checked {
gtk::AccessibleTristate::True
} else {
gtk::AccessibleTristate::False
}
}
pub const ACTIVATE_KEYS: &[gdk::Key] = &[
gdk::Key::space,
gdk::Key::KP_Space,
gdk::Key::Return,
gdk::Key::ISO_Enter,
gdk::Key::KP_Enter,
];
pub fn add_activate_binding_action<T: WidgetClassExt>(klass: &mut T, action: &str) {
for key in ACTIVATE_KEYS {
klass.add_binding_action(*key, gdk::ModifierType::empty(), action);
}
}
#[derive(Debug, Clone)]
pub enum File {
Gio(gio::File),
Temp(Arc<NamedTempFile>),
}
impl File {
pub(crate) fn path(&self) -> Option<PathBuf> {
match self {
Self::Gio(file) => file.path(),
Self::Temp(file) => Some(file.path().to_owned()),
}
}
pub(crate) fn as_gfile(&self) -> gio::File {
match self {
Self::Gio(file) => file.clone(),
Self::Temp(file) => gio::File::for_path(file.path()),
}
}
}
impl From<gio::File> for File {
fn from(value: gio::File) -> Self {
Self::Gio(value)
}
}
impl From<NamedTempFile> for File {
fn from(value: NamedTempFile) -> Self {
Self::Temp(value.into())
}
}
static TMP_DIR: LazyLock<Box<Path>> = LazyLock::new(|| {
let mut dir = glib::user_runtime_dir();
dir.push(PROFILE.dir_name().as_ref());
dir.into_boxed_path()
});
pub(crate) async fn save_data_to_tmp_file(data: Vec<u8>) -> Result<File, std::io::Error> {
RUNTIME
.spawn_blocking(move || {
let dir = TMP_DIR.as_ref();
if !dir.exists() {
if let Err(error) = fs::create_dir(dir) {
if !matches!(error.kind(), io::ErrorKind::AlreadyExists) {
return Err(error);
}
}
}
let mut file = NamedTempFile::new_in(dir)?;
file.write_all(&data)?;
Ok(file.into())
})
.await
.expect("task was not aborted")
}
pub struct CountedRef(Rc<InnerCountedRef>);
struct InnerCountedRef {
count: Cell<usize>,
on_zero: Box<dyn Fn()>,
on_non_zero: Box<dyn Fn()>,
}
impl CountedRef {
pub fn new<F1, F2>(on_zero: F1, on_non_zero: F2) -> Self
where
F1: Fn() + 'static,
F2: Fn() + 'static,
{
Self(
InnerCountedRef {
count: Default::default(),
on_zero: Box::new(on_zero),
on_non_zero: Box::new(on_non_zero),
}
.into(),
)
}
pub fn count(&self) -> usize {
self.0.count.get()
}
}
impl fmt::Debug for CountedRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("CountedRef")
.field("count", &self.count())
.finish_non_exhaustive()
}
}
impl Clone for CountedRef {
fn clone(&self) -> Self {
let count = self.count();
self.0.count.set(count.saturating_add(1));
if count == 0 {
(self.0.on_non_zero)();
}
Self(self.0.clone())
}
}
impl Drop for CountedRef {
fn drop(&mut self) {
let count = self.count();
self.0.count.set(count.saturating_sub(1));
if count == 1 {
(self.0.on_zero)();
}
}
}