1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
use gtk::{glib, prelude::*, subclass::prelude::*};

use super::CategoryType;

mod imp {
    use std::cell::{Cell, RefCell};

    use super::*;

    #[derive(Debug, Default, glib::Properties)]
    #[properties(wrapper_type = super::CategoryFilter)]
    pub struct CategoryFilter {
        /// The expression to watch.
        #[property(get, set = Self::set_expression, explicit_notify, nullable)]
        pub expression: RefCell<Option<gtk::Expression>>,
        /// The category type to filter.
        #[property(get, set = Self::set_category_type, explicit_notify, builder(CategoryType::default()))]
        pub category_type: Cell<CategoryType>,
    }

    #[glib::object_subclass]
    impl ObjectSubclass for CategoryFilter {
        const NAME: &'static str = "CategoryFilter";
        type Type = super::CategoryFilter;
        type ParentType = gtk::Filter;
    }

    #[glib::derived_properties]
    impl ObjectImpl for CategoryFilter {}

    impl FilterImpl for CategoryFilter {
        fn strictness(&self) -> gtk::FilterMatch {
            if self.category_type.get() == CategoryType::None {
                return gtk::FilterMatch::All;
            }

            if self.expression.borrow().is_none() {
                return gtk::FilterMatch::None;
            }

            gtk::FilterMatch::Some
        }

        fn match_(&self, item: &glib::Object) -> bool {
            let category_type = self.category_type.get();
            if category_type == CategoryType::None {
                return true;
            }

            let Some(value) = self
                .expression
                .borrow()
                .as_ref()
                .and_then(|e| e.evaluate(Some(item)))
                .map(|v| v.get::<CategoryType>().unwrap())
            else {
                return false;
            };

            value == category_type
        }
    }

    impl CategoryFilter {
        /// Set the expression to watch.
        ///
        /// This expression must return a [`CategoryType`].
        fn set_expression(&self, expression: Option<gtk::Expression>) {
            let prev_expression = self.expression.borrow().clone();

            if prev_expression.is_none() && expression.is_none() {
                return;
            }
            let obj = self.obj();

            let change = if self.category_type.get() == CategoryType::None {
                None
            } else if prev_expression.is_none() {
                Some(gtk::FilterChange::LessStrict)
            } else if expression.is_none() {
                Some(gtk::FilterChange::MoreStrict)
            } else {
                Some(gtk::FilterChange::Different)
            };

            self.expression.replace(expression);
            if let Some(change) = change {
                obj.changed(change)
            }
            obj.notify_expression();
        }

        /// Set the category type to filter.
        fn set_category_type(&self, category_type: CategoryType) {
            let prev_category_type = self.category_type.get();

            if prev_category_type == category_type {
                return;
            }
            let obj = self.obj();

            let change = if self.expression.borrow().is_none() {
                None
            } else if prev_category_type == CategoryType::None {
                Some(gtk::FilterChange::MoreStrict)
            } else if category_type == CategoryType::None {
                Some(gtk::FilterChange::LessStrict)
            } else {
                Some(gtk::FilterChange::Different)
            };

            self.category_type.set(category_type);
            if let Some(change) = change {
                obj.changed(change)
            }
            obj.notify_category_type();
        }
    }
}

glib::wrapper! {
    /// A filter by `CategoryType`.
    pub struct CategoryFilter(ObjectSubclass<imp::CategoryFilter>)
        @extends gtk::Filter;
}

impl CategoryFilter {
    pub fn new() -> Self {
        glib::Object::new()
    }
}

impl Default for CategoryFilter {
    fn default() -> Self {
        Self::new()
    }
}