1use std::{
4 borrow::Cow,
5 cell::{Cell, OnceCell, RefCell},
6 fmt, fs,
7 io::{self, Write},
8 ops::Deref,
9 path::{Path, PathBuf},
10 rc::{Rc, Weak},
11 sync::{Arc, LazyLock, Mutex},
12};
13
14use adw::prelude::*;
15use futures_channel::oneshot;
16use futures_util::future::BoxFuture;
17use gtk::{gio, glib};
18use regex::Regex;
19use tempfile::NamedTempFile;
20use tokio::task::{AbortHandle, JoinHandle};
21use tracing::error;
22
23pub(crate) mod expression;
24mod expression_list_model;
25mod fixed_selection;
26mod grouping_list_model;
27pub(crate) mod key_bindings;
28mod location;
29mod macros;
30pub(crate) mod matrix;
31pub(crate) mod media;
32pub(crate) mod notifications;
33mod placeholder_object;
34mod single_item_list_model;
35pub(crate) mod sourceview;
36pub(crate) mod string;
37mod template_callbacks;
38pub(crate) mod toast;
39
40pub(crate) use self::{
41 expression_list_model::ExpressionListModel,
42 fixed_selection::FixedSelection,
43 grouping_list_model::*,
44 location::{Location, LocationError, LocationExt},
45 placeholder_object::PlaceholderObject,
46 single_item_list_model::SingleItemListModel,
47 template_callbacks::TemplateCallbacks,
48};
49use crate::{PROFILE, RUNTIME};
50
51#[derive(Debug, Clone, Copy)]
53pub(crate) enum DataType {
54 Persistent,
56 Cache,
58}
59
60impl DataType {
61 pub(crate) fn dir_path(self) -> PathBuf {
64 let mut path = match self {
65 DataType::Persistent => glib::user_data_dir(),
66 DataType::Cache => glib::user_cache_dir(),
67 };
68 path.push(PROFILE.dir_name().as_ref());
69
70 path
71 }
72}
73
74pub(crate) fn freplace<'a>(s: &'a str, args: &[(&str, &str)]) -> Cow<'a, str> {
79 let mut s = Cow::Borrowed(s);
80
81 for (k, v) in args {
82 s = Cow::Owned(s.replace(&format!("{{{k}}}"), v));
83 }
84
85 s
86}
87
88pub(crate) static EMOJI_REGEX: LazyLock<Regex> = LazyLock::new(|| {
90 Regex::new(
91 r"(?x)
92 ^
93 [\p{White_Space}\p{Emoji_Component}]*
94 [\p{Emoji}--\p{Decimal_Number}]+
95 [\p{White_Space}\p{Emoji}\p{Emoji_Component}--\p{Decimal_Number}]*
96 $
97 # That string is made of at least one emoji, except digits, possibly more,
98 # possibly with modifiers, possibly with spaces, but nothing else
99 ",
100 )
101 .unwrap()
102});
103
104#[derive(Debug)]
106struct BoundObjectInner<T: ObjectType> {
107 obj: T,
108 signal_handler_ids: Vec<glib::SignalHandlerId>,
109}
110
111#[derive(Debug)]
115pub struct BoundObject<T: ObjectType> {
116 inner: RefCell<Option<BoundObjectInner<T>>>,
117}
118
119impl<T: ObjectType> BoundObject<T> {
120 pub fn new() -> Self {
122 Self::default()
123 }
124
125 pub(crate) fn set(&self, obj: T, signal_handler_ids: Vec<glib::SignalHandlerId>) {
130 self.disconnect_signals();
131
132 let inner = BoundObjectInner {
133 obj,
134 signal_handler_ids,
135 };
136
137 self.inner.replace(Some(inner));
138 }
139
140 pub fn obj(&self) -> Option<T> {
142 self.inner.borrow().as_ref().map(|inner| inner.obj.clone())
143 }
144
145 pub fn disconnect_signals(&self) {
147 if let Some(inner) = self.inner.take() {
148 for signal_handler_id in inner.signal_handler_ids {
149 inner.obj.disconnect(signal_handler_id);
150 }
151 }
152 }
153}
154
155impl<T: ObjectType> Default for BoundObject<T> {
156 fn default() -> Self {
157 Self {
158 inner: Default::default(),
159 }
160 }
161}
162
163impl<T: ObjectType> Drop for BoundObject<T> {
164 fn drop(&mut self) {
165 self.disconnect_signals();
166 }
167}
168
169impl<T: IsA<glib::Object> + glib::HasParamSpec> glib::property::Property for BoundObject<T> {
170 type Value = Option<T>;
171}
172
173impl<T: IsA<glib::Object>> glib::property::PropertyGet for BoundObject<T> {
174 type Value = Option<T>;
175
176 fn get<R, F: Fn(&Self::Value) -> R>(&self, f: F) -> R {
177 f(&self.obj())
178 }
179}
180
181#[derive(Debug)]
185pub struct BoundObjectWeakRef<T: ObjectType> {
186 weak_obj: glib::WeakRef<T>,
187 signal_handler_ids: RefCell<Vec<glib::SignalHandlerId>>,
188}
189
190impl<T: ObjectType> BoundObjectWeakRef<T> {
191 pub fn new() -> Self {
193 Self::default()
194 }
195
196 pub(crate) fn set(&self, obj: &T, signal_handler_ids: Vec<glib::SignalHandlerId>) {
201 self.disconnect_signals();
202
203 self.weak_obj.set(Some(obj));
204 self.signal_handler_ids.replace(signal_handler_ids);
205 }
206
207 pub fn obj(&self) -> Option<T> {
209 self.weak_obj.upgrade()
210 }
211
212 pub fn disconnect_signals(&self) {
214 let signal_handler_ids = self.signal_handler_ids.take();
215
216 if let Some(obj) = self.weak_obj.upgrade() {
217 for signal_handler_id in signal_handler_ids {
218 obj.disconnect(signal_handler_id);
219 }
220 }
221
222 self.weak_obj.set(None);
223 }
224}
225
226impl<T: ObjectType> Default for BoundObjectWeakRef<T> {
227 fn default() -> Self {
228 Self {
229 weak_obj: Default::default(),
230 signal_handler_ids: Default::default(),
231 }
232 }
233}
234
235impl<T: ObjectType> Drop for BoundObjectWeakRef<T> {
236 fn drop(&mut self) {
237 self.disconnect_signals();
238 }
239}
240
241impl<T: IsA<glib::Object> + glib::HasParamSpec> glib::property::Property for BoundObjectWeakRef<T> {
242 type Value = Option<T>;
243}
244
245impl<T: IsA<glib::Object>> glib::property::PropertyGet for BoundObjectWeakRef<T> {
246 type Value = Option<T>;
247
248 fn get<R, F: Fn(&Self::Value) -> R>(&self, f: F) -> R {
249 f(&self.obj())
250 }
251}
252
253#[derive(Debug)]
257pub struct BoundConstructOnlyObject<T: ObjectType> {
258 obj: OnceCell<T>,
259 signal_handler_ids: RefCell<Vec<glib::SignalHandlerId>>,
260}
261
262impl<T: ObjectType> BoundConstructOnlyObject<T> {
263 pub fn new() -> Self {
265 Self::default()
266 }
267
268 pub(crate) fn set(&self, obj: T, signal_handler_ids: Vec<glib::SignalHandlerId>) {
272 self.obj.set(obj).unwrap();
273 self.signal_handler_ids.replace(signal_handler_ids);
274 }
275
276 pub fn obj(&self) -> &T {
280 self.obj.get().unwrap()
281 }
282}
283
284impl<T: ObjectType> Default for BoundConstructOnlyObject<T> {
285 fn default() -> Self {
286 Self {
287 obj: Default::default(),
288 signal_handler_ids: Default::default(),
289 }
290 }
291}
292
293impl<T: ObjectType> Drop for BoundConstructOnlyObject<T> {
294 fn drop(&mut self) {
295 let signal_handler_ids = self.signal_handler_ids.take();
296
297 if let Some(obj) = self.obj.get() {
298 for signal_handler_id in signal_handler_ids {
299 obj.disconnect(signal_handler_id);
300 }
301 }
302 }
303}
304
305impl<T: IsA<glib::Object> + glib::HasParamSpec> glib::property::Property
306 for BoundConstructOnlyObject<T>
307{
308 type Value = T;
309}
310
311impl<T: IsA<glib::Object>> glib::property::PropertyGet for BoundConstructOnlyObject<T> {
312 type Value = T;
313
314 fn get<R, F: Fn(&Self::Value) -> R>(&self, f: F) -> R {
315 f(self.obj())
316 }
317}
318
319#[derive(Debug)]
328pub struct OngoingAsyncAction<T> {
329 strong: Rc<AsyncAction<T>>,
330}
331
332impl<T> OngoingAsyncAction<T> {
333 pub(crate) fn set(value: T) -> (Self, WeakOngoingAsyncAction<T>) {
337 let strong = Rc::new(AsyncAction::Set(value));
338 let weak = Rc::downgrade(&strong);
339 (Self { strong }, WeakOngoingAsyncAction { weak })
340 }
341
342 pub(crate) fn remove() -> (Self, WeakOngoingAsyncAction<T>) {
346 let strong = Rc::new(AsyncAction::Remove);
347 let weak = Rc::downgrade(&strong);
348 (Self { strong }, WeakOngoingAsyncAction { weak })
349 }
350
351 pub(crate) fn as_value(&self) -> Option<&T> {
353 self.strong.as_value()
354 }
355}
356
357#[derive(Debug, Clone)]
359pub struct WeakOngoingAsyncAction<T> {
360 weak: Weak<AsyncAction<T>>,
361}
362
363impl<T> WeakOngoingAsyncAction<T> {
364 pub fn is_ongoing(&self) -> bool {
367 self.weak.strong_count() > 0
368 }
369}
370
371#[derive(Debug, Clone, PartialEq, Eq)]
373pub(crate) enum AsyncAction<T> {
374 Set(T),
376
377 Remove,
379}
380
381impl<T> AsyncAction<T> {
382 pub fn as_value(&self) -> Option<&T> {
384 match self {
385 Self::Set(value) => Some(value),
386 Self::Remove => None,
387 }
388 }
389}
390
391#[derive(Debug, Clone)]
393pub struct TokioDrop<T>(Option<T>);
394
395impl<T> TokioDrop<T> {
396 pub fn new(value: T) -> Self {
398 Self(Some(value))
399 }
400}
401
402impl<T> Deref for TokioDrop<T> {
403 type Target = T;
404
405 fn deref(&self) -> &Self::Target {
406 self.0
407 .as_ref()
408 .expect("TokioDrop should always contain a value")
409 }
410}
411
412impl<T> From<T> for TokioDrop<T> {
413 fn from(value: T) -> Self {
414 Self::new(value)
415 }
416}
417
418impl<T> Drop for TokioDrop<T> {
419 fn drop(&mut self) {
420 let _guard = RUNTIME.enter();
421
422 if let Some(value) = self.0.take() {
423 drop(value);
424 }
425 }
426}
427
428#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, glib::Enum)]
430#[enum_type(name = "LoadingState")]
431pub enum LoadingState {
432 #[default]
434 Initial,
435 Loading,
437 Ready,
439 Error,
441}
442
443pub(crate) fn bool_to_accessible_tristate(checked: bool) -> gtk::AccessibleTristate {
445 if checked {
446 gtk::AccessibleTristate::True
447 } else {
448 gtk::AccessibleTristate::False
449 }
450}
451
452#[derive(Debug, Clone)]
454pub enum File {
455 Gio(gio::File),
457 Temp(Arc<NamedTempFile>),
462}
463
464impl File {
465 pub(crate) fn path(&self) -> Option<PathBuf> {
467 match self {
468 Self::Gio(file) => file.path(),
469 Self::Temp(file) => Some(file.path().to_owned()),
470 }
471 }
472
473 pub(crate) fn as_gfile(&self) -> gio::File {
475 match self {
476 Self::Gio(file) => file.clone(),
477 Self::Temp(file) => gio::File::for_path(file.path()),
478 }
479 }
480}
481
482impl From<gio::File> for File {
483 fn from(value: gio::File) -> Self {
484 Self::Gio(value)
485 }
486}
487
488impl From<NamedTempFile> for File {
489 fn from(value: NamedTempFile) -> Self {
490 Self::Temp(value.into())
491 }
492}
493
494static TMP_DIR: LazyLock<Box<Path>> = LazyLock::new(|| {
496 let mut dir = glib::user_runtime_dir();
497 dir.push(PROFILE.dir_name().as_ref());
498 dir.into_boxed_path()
499});
500
501pub(crate) async fn save_data_to_tmp_file(data: Vec<u8>) -> Result<File, std::io::Error> {
506 RUNTIME
507 .spawn_blocking(move || {
508 let dir = TMP_DIR.as_ref();
509 if !dir.exists()
510 && let Err(error) = fs::create_dir(dir)
511 && !matches!(error.kind(), io::ErrorKind::AlreadyExists)
512 {
513 return Err(error);
514 }
515
516 let mut file = NamedTempFile::new_in(dir)?;
517 file.write_all(&data)?;
518
519 Ok(file.into())
520 })
521 .await
522 .expect("task was not aborted")
523}
524
525pub struct CountedRef(Rc<InnerCountedRef>);
529
530struct InnerCountedRef {
531 count: Cell<usize>,
533 on_zero: Box<dyn Fn()>,
535 on_non_zero: Box<dyn Fn()>,
537}
538
539impl CountedRef {
540 pub(crate) fn new<F1, F2>(on_zero: F1, on_non_zero: F2) -> Self
542 where
543 F1: Fn() + 'static,
544 F2: Fn() + 'static,
545 {
546 Self(
547 InnerCountedRef {
548 count: Default::default(),
549 on_zero: Box::new(on_zero),
550 on_non_zero: Box::new(on_non_zero),
551 }
552 .into(),
553 )
554 }
555
556 pub fn count(&self) -> usize {
558 self.0.count.get()
559 }
560}
561
562impl fmt::Debug for CountedRef {
563 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
564 f.debug_struct("CountedRef")
565 .field("count", &self.count())
566 .finish_non_exhaustive()
567 }
568}
569
570impl Clone for CountedRef {
571 fn clone(&self) -> Self {
572 let count = self.count();
573 self.0.count.set(count.saturating_add(1));
574
575 if count == 0 {
576 (self.0.on_non_zero)();
577 }
578
579 Self(self.0.clone())
580 }
581}
582
583impl Drop for CountedRef {
584 fn drop(&mut self) {
585 let count = self.count();
586 self.0.count.set(count.saturating_sub(1));
587
588 if count == 1 {
589 (self.0.on_zero)();
590 }
591 }
592}
593
594pub(crate) trait ChildPropertyExt {
596 fn child_property(&self) -> Option<gtk::Widget>;
598
599 fn set_child_property(&self, child: Option<&impl IsA<gtk::Widget>>);
601
602 fn child_or_else<W>(&self, f: impl FnOnce() -> W) -> W
606 where
607 W: IsA<gtk::Widget>,
608 {
609 if let Some(child) = self.child_property().and_downcast() {
610 child
611 } else {
612 let child = f();
613 self.set_child_property(Some(&child));
614 child
615 }
616 }
617
618 fn child_or_default<W>(&self) -> W
622 where
623 W: IsA<gtk::Widget> + Default,
624 {
625 self.child_or_else(Default::default)
626 }
627}
628
629impl<W> ChildPropertyExt for W
630where
631 W: IsABin,
632{
633 fn child_property(&self) -> Option<gtk::Widget> {
634 self.child()
635 }
636
637 fn set_child_property(&self, child: Option<&impl IsA<gtk::Widget>>) {
638 self.set_child(child);
639 }
640}
641
642impl ChildPropertyExt for gtk::ListItem {
643 fn child_property(&self) -> Option<gtk::Widget> {
644 self.child()
645 }
646
647 fn set_child_property(&self, child: Option<&impl IsA<gtk::Widget>>) {
648 self.set_child(child);
649 }
650}
651
652pub(crate) trait IsABin: IsA<adw::Bin> {}
662
663impl IsABin for adw::Bin {}
664
665#[derive(Debug, Default)]
670pub(crate) struct AbortableHandle {
671 abort_handle: RefCell<Option<AbortHandle>>,
672}
673
674impl AbortableHandle {
675 pub(crate) async fn await_task<T>(&self, join_handle: JoinHandle<T>) -> Option<T> {
681 self.abort();
682
683 self.abort_handle.replace(Some(join_handle.abort_handle()));
684
685 let result = join_handle.await.ok();
686
687 self.abort_handle.take();
688
689 result
690 }
691
692 pub(crate) fn abort(&self) {
694 if let Some(abort_handle) = self.abort_handle.take() {
695 abort_handle.abort();
696 }
697 }
698}
699
700impl Drop for AbortableHandle {
701 fn drop(&mut self) {
702 self.abort();
703 }
704}
705
706pub(crate) fn resample_slice(slice: &[f32], new_len: usize) -> Cow<'_, [f32]> {
711 let len = slice.len();
712
713 if len == new_len {
714 return Cow::Borrowed(slice);
716 }
717
718 if new_len == 0 {
719 return Cow::Borrowed(&[]);
721 }
722
723 if len <= 1
724 || slice
725 .iter()
726 .all(|value| (*value - slice[0]).abs() < 0.000_001)
727 {
728 let value = slice.first().copied().unwrap_or_default();
731 return Cow::Owned(std::iter::repeat_n(value, new_len).collect());
732 }
733
734 let mut result = Vec::with_capacity(new_len);
736 let ratio = (len - 1) as f32 / (new_len - 1) as f32;
737
738 for i in 0..new_len {
739 let position_abs = i as f32 * ratio;
740 let position_before = position_abs.floor();
741 let position_after = position_abs.ceil();
742 let position_rel = position_abs % 1.0;
743
744 #[allow(clippy::cast_sign_loss)]
746 let value_before = slice[position_before as usize];
747 #[allow(clippy::cast_sign_loss)]
748 let value_after = slice[(position_after as usize).min(slice.len().saturating_sub(1))];
749
750 let value = (1.0 - position_rel) * value_before + position_rel * value_after;
751 result.push(value);
752 }
753
754 Cow::Owned(result)
755}
756
757#[derive(Debug, Clone)]
776pub(crate) struct OneshotNotifier<T = ()> {
777 context: &'static str,
779 sender: Arc<Mutex<Option<oneshot::Sender<T>>>>,
781}
782
783impl<T> OneshotNotifier<T> {
784 pub(crate) fn new(context: &'static str) -> Self {
786 Self {
787 sender: Default::default(),
788 context,
789 }
790 }
791}
792
793impl<T> OneshotNotifier<T>
794where
795 T: Default + Send + 'static,
796{
797 pub(crate) fn listen(&self) -> OneshotNotifierReceiver<T> {
799 let (sender, receiver) = oneshot::channel();
800
801 match self.sender.lock() {
802 Ok(mut guard) => *guard = Some(sender),
803 Err(error) => {
804 error!(
805 context = self.context,
806 "Failed to lock oneshot notifier: {error}"
807 );
808 }
809 }
810
811 OneshotNotifierReceiver(receiver)
812 }
813
814 pub(crate) fn notify_value(&self, value: T) {
817 match self.sender.lock() {
818 Ok(mut guard) => {
819 if let Some(sender) = guard.take() {
820 let _ = sender.send(value);
821 }
822 }
823 Err(error) => {
824 error!(
825 context = self.context,
826 "Failed to lock oneshot notifier: {error}"
827 );
828 }
829 }
830 }
831
832 pub(crate) fn notify(&self) {
835 self.notify_value(T::default());
836 }
837}
838
839#[derive(Debug)]
843pub(crate) struct OneshotNotifierReceiver<T = ()>(oneshot::Receiver<T>);
844
845impl<T> IntoFuture for OneshotNotifierReceiver<T>
846where
847 T: Default + Send + 'static,
848{
849 type Output = T;
850 type IntoFuture = BoxFuture<'static, Self::Output>;
851
852 fn into_future(self) -> Self::IntoFuture {
853 Box::pin(async move { self.0.await.unwrap_or_default() })
854 }
855}