1mod dummy_object;
4pub mod expression;
5mod expression_list_model;
6pub(crate) mod key_bindings;
7mod location;
8pub mod macros;
9pub mod matrix;
10pub mod media;
11pub mod notifications;
12pub(crate) mod oauth;
13mod single_item_list_model;
14pub mod sourceview;
15pub mod string;
16pub mod template_callbacks;
17
18use std::{
19 cell::{Cell, OnceCell, RefCell},
20 fmt, fs,
21 io::{self, Write},
22 path::{Path, PathBuf},
23 rc::{Rc, Weak},
24 sync::{Arc, LazyLock},
25};
26
27use futures_util::{
28 future::{self, Either, Future},
29 pin_mut,
30};
31use gtk::{gio, glib, prelude::*};
32use regex::Regex;
33use tempfile::NamedTempFile;
34
35pub(crate) use self::{
36 dummy_object::DummyObject,
37 expression_list_model::ExpressionListModel,
38 location::{Location, LocationError, LocationExt},
39 single_item_list_model::SingleItemListModel,
40};
41use crate::{PROFILE, RUNTIME};
42
43pub fn data_dir_path(data_type: DataType) -> PathBuf {
46 let mut path = match data_type {
47 DataType::Persistent => glib::user_data_dir(),
48 DataType::Cache => glib::user_cache_dir(),
49 };
50 path.push(PROFILE.dir_name().as_ref());
51
52 path
53}
54
55#[derive(Debug, Clone, Copy)]
57pub enum DataType {
58 Persistent,
60 Cache,
62}
63
64pub enum TimeoutFuture {
65 Timeout,
66}
67
68pub async fn timeout_future<T>(
73 timeout: std::time::Duration,
74 fut: impl Future<Output = T>,
75) -> Result<T, TimeoutFuture> {
76 let timeout = glib::timeout_future(timeout);
77 pin_mut!(fut);
78
79 match future::select(fut, timeout).await {
80 Either::Left((x, _)) => Ok(x),
81 Either::Right(_) => Err(TimeoutFuture::Timeout),
82 }
83}
84
85pub fn freplace(s: String, args: &[(&str, &str)]) -> String {
90 let mut s = s;
91
92 for (k, v) in args {
93 s = s.replace(&format!("{{{k}}}"), v);
94 }
95
96 s
97}
98
99pub static EMOJI_REGEX: LazyLock<Regex> = LazyLock::new(|| {
101 Regex::new(
102 r"(?x)
103 ^
104 [\p{White_Space}\p{Emoji_Component}]*
105 [\p{Emoji}--\p{Decimal_Number}]+
106 [\p{White_Space}\p{Emoji}\p{Emoji_Component}--\p{Decimal_Number}]*
107 $
108 # That string is made of at least one emoji, except digits, possibly more,
109 # possibly with modifiers, possibly with spaces, but nothing else
110 ",
111 )
112 .unwrap()
113});
114
115#[derive(Debug)]
117pub struct BoundObjectInner<T: ObjectType> {
118 obj: T,
119 signal_handler_ids: Vec<glib::SignalHandlerId>,
120}
121
122#[derive(Debug)]
126pub struct BoundObject<T: ObjectType> {
127 inner: RefCell<Option<BoundObjectInner<T>>>,
128}
129
130impl<T: ObjectType> BoundObject<T> {
131 pub fn new() -> Self {
133 Self::default()
134 }
135
136 pub fn set(&self, obj: T, signal_handler_ids: Vec<glib::SignalHandlerId>) {
141 self.disconnect_signals();
142
143 let inner = BoundObjectInner {
144 obj,
145 signal_handler_ids,
146 };
147
148 self.inner.replace(Some(inner));
149 }
150
151 pub fn obj(&self) -> Option<T> {
153 self.inner.borrow().as_ref().map(|inner| inner.obj.clone())
154 }
155
156 pub fn disconnect_signals(&self) {
158 if let Some(inner) = self.inner.take() {
159 for signal_handler_id in inner.signal_handler_ids {
160 inner.obj.disconnect(signal_handler_id);
161 }
162 }
163 }
164}
165
166impl<T: ObjectType> Default for BoundObject<T> {
167 fn default() -> Self {
168 Self {
169 inner: Default::default(),
170 }
171 }
172}
173
174impl<T: ObjectType> Drop for BoundObject<T> {
175 fn drop(&mut self) {
176 self.disconnect_signals();
177 }
178}
179
180impl<T: IsA<glib::Object> + glib::HasParamSpec> glib::property::Property for BoundObject<T> {
181 type Value = Option<T>;
182}
183
184impl<T: IsA<glib::Object>> glib::property::PropertyGet for BoundObject<T> {
185 type Value = Option<T>;
186
187 fn get<R, F: Fn(&Self::Value) -> R>(&self, f: F) -> R {
188 f(&self.obj())
189 }
190}
191
192#[derive(Debug)]
196pub struct BoundObjectWeakRef<T: ObjectType> {
197 weak_obj: glib::WeakRef<T>,
198 signal_handler_ids: RefCell<Vec<glib::SignalHandlerId>>,
199}
200
201impl<T: ObjectType> BoundObjectWeakRef<T> {
202 pub fn new() -> Self {
204 Self::default()
205 }
206
207 pub fn set(&self, obj: &T, signal_handler_ids: Vec<glib::SignalHandlerId>) {
212 self.disconnect_signals();
213
214 self.weak_obj.set(Some(obj));
215 self.signal_handler_ids.replace(signal_handler_ids);
216 }
217
218 pub fn obj(&self) -> Option<T> {
220 self.weak_obj.upgrade()
221 }
222
223 pub fn disconnect_signals(&self) {
225 let signal_handler_ids = self.signal_handler_ids.take();
226
227 if let Some(obj) = self.weak_obj.upgrade() {
228 for signal_handler_id in signal_handler_ids {
229 obj.disconnect(signal_handler_id);
230 }
231 }
232
233 self.weak_obj.set(None);
234 }
235}
236
237impl<T: ObjectType> Default for BoundObjectWeakRef<T> {
238 fn default() -> Self {
239 Self {
240 weak_obj: Default::default(),
241 signal_handler_ids: Default::default(),
242 }
243 }
244}
245
246impl<T: ObjectType> Drop for BoundObjectWeakRef<T> {
247 fn drop(&mut self) {
248 self.disconnect_signals();
249 }
250}
251
252impl<T: IsA<glib::Object> + glib::HasParamSpec> glib::property::Property for BoundObjectWeakRef<T> {
253 type Value = Option<T>;
254}
255
256impl<T: IsA<glib::Object>> glib::property::PropertyGet for BoundObjectWeakRef<T> {
257 type Value = Option<T>;
258
259 fn get<R, F: Fn(&Self::Value) -> R>(&self, f: F) -> R {
260 f(&self.obj())
261 }
262}
263
264#[derive(Debug)]
268pub struct BoundConstructOnlyObject<T: ObjectType> {
269 obj: OnceCell<T>,
270 signal_handler_ids: RefCell<Vec<glib::SignalHandlerId>>,
271}
272
273impl<T: ObjectType> BoundConstructOnlyObject<T> {
274 pub fn new() -> Self {
276 Self::default()
277 }
278
279 pub fn set(&self, obj: T, signal_handler_ids: Vec<glib::SignalHandlerId>) {
283 self.obj.set(obj).unwrap();
284 self.signal_handler_ids.replace(signal_handler_ids);
285 }
286
287 pub fn obj(&self) -> &T {
291 self.obj.get().unwrap()
292 }
293}
294
295impl<T: ObjectType> Default for BoundConstructOnlyObject<T> {
296 fn default() -> Self {
297 Self {
298 obj: Default::default(),
299 signal_handler_ids: Default::default(),
300 }
301 }
302}
303
304impl<T: ObjectType> Drop for BoundConstructOnlyObject<T> {
305 fn drop(&mut self) {
306 let signal_handler_ids = self.signal_handler_ids.take();
307
308 if let Some(obj) = self.obj.get() {
309 for signal_handler_id in signal_handler_ids {
310 obj.disconnect(signal_handler_id);
311 }
312 }
313 }
314}
315
316impl<T: IsA<glib::Object> + glib::HasParamSpec> glib::property::Property
317 for BoundConstructOnlyObject<T>
318{
319 type Value = T;
320}
321
322impl<T: IsA<glib::Object>> glib::property::PropertyGet for BoundConstructOnlyObject<T> {
323 type Value = T;
324
325 fn get<R, F: Fn(&Self::Value) -> R>(&self, f: F) -> R {
326 f(self.obj())
327 }
328}
329
330#[derive(Debug)]
339pub struct OngoingAsyncAction<T> {
340 strong: Rc<AsyncAction<T>>,
341}
342
343impl<T> OngoingAsyncAction<T> {
344 pub fn set(value: T) -> (Self, WeakOngoingAsyncAction<T>) {
348 let strong = Rc::new(AsyncAction::Set(value));
349 let weak = Rc::downgrade(&strong);
350 (Self { strong }, WeakOngoingAsyncAction { weak })
351 }
352
353 pub fn remove() -> (Self, WeakOngoingAsyncAction<T>) {
357 let strong = Rc::new(AsyncAction::Remove);
358 let weak = Rc::downgrade(&strong);
359 (Self { strong }, WeakOngoingAsyncAction { weak })
360 }
361
362 pub fn downgrade(&self) -> WeakOngoingAsyncAction<T> {
364 let weak = Rc::downgrade(&self.strong);
365 WeakOngoingAsyncAction { weak }
366 }
367
368 pub fn action(&self) -> &AsyncAction<T> {
370 &self.strong
371 }
372
373 pub fn as_value(&self) -> Option<&T> {
375 self.strong.as_value()
376 }
377}
378
379#[derive(Debug, Clone)]
381pub struct WeakOngoingAsyncAction<T> {
382 weak: Weak<AsyncAction<T>>,
383}
384
385impl<T> WeakOngoingAsyncAction<T> {
386 pub fn is_ongoing(&self) -> bool {
389 self.weak.strong_count() > 0
390 }
391}
392
393#[derive(Debug, Clone, PartialEq, Eq)]
395pub enum AsyncAction<T> {
396 Set(T),
398
399 Remove,
401}
402
403impl<T> AsyncAction<T> {
404 pub fn as_value(&self) -> Option<&T> {
406 match self {
407 Self::Set(value) => Some(value),
408 Self::Remove => None,
409 }
410 }
411}
412
413#[derive(Debug, Clone)]
417pub struct TokioDrop<T>(OnceCell<T>);
418
419impl<T> TokioDrop<T> {
420 pub fn new() -> Self {
422 Self::default()
423 }
424
425 pub fn get(&self) -> Option<&T> {
429 self.0.get()
430 }
431
432 pub fn set(&self, value: T) -> Result<(), T> {
436 self.0.set(value)
437 }
438}
439
440impl<T> Default for TokioDrop<T> {
441 fn default() -> Self {
442 Self(Default::default())
443 }
444}
445
446impl<T> Drop for TokioDrop<T> {
447 fn drop(&mut self) {
448 let _guard = RUNTIME.enter();
449
450 if let Some(inner) = self.0.take() {
451 drop(inner);
452 }
453 }
454}
455
456impl<T: glib::property::Property> glib::property::Property for TokioDrop<T> {
457 type Value = T::Value;
458}
459
460impl<T> glib::property::PropertyGet for TokioDrop<T> {
461 type Value = T;
462
463 fn get<R, F: Fn(&Self::Value) -> R>(&self, f: F) -> R {
464 f(self.get().unwrap())
465 }
466}
467
468impl<T> glib::property::PropertySet for TokioDrop<T> {
469 type SetValue = T;
470
471 fn set(&self, v: Self::SetValue) {
472 assert!(
473 self.set(v).is_ok(),
474 "TokioDrop value was already initialized"
475 );
476 }
477}
478
479#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, glib::Enum)]
481#[enum_type(name = "LoadingState")]
482pub enum LoadingState {
483 #[default]
485 Initial,
486 Loading,
488 Ready,
490 Error,
492}
493
494pub fn bool_to_accessible_tristate(checked: bool) -> gtk::AccessibleTristate {
496 if checked {
497 gtk::AccessibleTristate::True
498 } else {
499 gtk::AccessibleTristate::False
500 }
501}
502
503#[derive(Debug, Clone)]
505pub enum File {
506 Gio(gio::File),
508 Temp(Arc<NamedTempFile>),
513}
514
515impl File {
516 pub(crate) fn path(&self) -> Option<PathBuf> {
518 match self {
519 Self::Gio(file) => file.path(),
520 Self::Temp(file) => Some(file.path().to_owned()),
521 }
522 }
523
524 pub(crate) fn as_gfile(&self) -> gio::File {
526 match self {
527 Self::Gio(file) => file.clone(),
528 Self::Temp(file) => gio::File::for_path(file.path()),
529 }
530 }
531}
532
533impl From<gio::File> for File {
534 fn from(value: gio::File) -> Self {
535 Self::Gio(value)
536 }
537}
538
539impl From<NamedTempFile> for File {
540 fn from(value: NamedTempFile) -> Self {
541 Self::Temp(value.into())
542 }
543}
544
545static TMP_DIR: LazyLock<Box<Path>> = LazyLock::new(|| {
547 let mut dir = glib::user_runtime_dir();
548 dir.push(PROFILE.dir_name().as_ref());
549 dir.into_boxed_path()
550});
551
552pub(crate) async fn save_data_to_tmp_file(data: Vec<u8>) -> Result<File, std::io::Error> {
557 RUNTIME
558 .spawn_blocking(move || {
559 let dir = TMP_DIR.as_ref();
560 if !dir.exists() {
561 if let Err(error) = fs::create_dir(dir) {
562 if !matches!(error.kind(), io::ErrorKind::AlreadyExists) {
563 return Err(error);
564 }
565 }
566 }
567 let mut file = NamedTempFile::new_in(dir)?;
568 file.write_all(&data)?;
569
570 Ok(file.into())
571 })
572 .await
573 .expect("task was not aborted")
574}
575
576pub struct CountedRef(Rc<InnerCountedRef>);
580
581struct InnerCountedRef {
582 count: Cell<usize>,
584 on_zero: Box<dyn Fn()>,
586 on_non_zero: Box<dyn Fn()>,
588}
589
590impl CountedRef {
591 pub fn new<F1, F2>(on_zero: F1, on_non_zero: F2) -> Self
593 where
594 F1: Fn() + 'static,
595 F2: Fn() + 'static,
596 {
597 Self(
598 InnerCountedRef {
599 count: Default::default(),
600 on_zero: Box::new(on_zero),
601 on_non_zero: Box::new(on_non_zero),
602 }
603 .into(),
604 )
605 }
606
607 pub fn count(&self) -> usize {
609 self.0.count.get()
610 }
611}
612
613impl fmt::Debug for CountedRef {
614 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
615 f.debug_struct("CountedRef")
616 .field("count", &self.count())
617 .finish_non_exhaustive()
618 }
619}
620
621impl Clone for CountedRef {
622 fn clone(&self) -> Self {
623 let count = self.count();
624 self.0.count.set(count.saturating_add(1));
625
626 if count == 0 {
627 (self.0.on_non_zero)();
628 }
629
630 Self(self.0.clone())
631 }
632}
633
634impl Drop for CountedRef {
635 fn drop(&mut self) {
636 let count = self.count();
637 self.0.count.set(count.saturating_sub(1));
638
639 if count == 1 {
640 (self.0.on_zero)();
641 }
642 }
643}