icons.rs 9.21 KB
Newer Older
1
use crate::config;
2
use crate::icon::Icon;
Bilal Elmoussaoui's avatar
Bilal Elmoussaoui committed
3
use crate::widgets::export::ExportDialog;
4
use gettextrs::gettext;
5 6
use glib::Cast;
use gtk;
7 8 9
use gtk::prelude::{BoxExt, ContainerExt, FlowBoxExt, GtkWindowExt, IconThemeExt, ImageExt, StackExt, StyleContextExt, WidgetExt};
use libhandy::{Column, ColumnExt};
use std::cell::RefCell;
10
use std::collections::HashMap;
11 12
use std::fs::File;
use std::io::BufReader;
13
use std::path::PathBuf;
Bilal Elmoussaoui's avatar
Bilal Elmoussaoui committed
14 15

pub struct IconsView {
16 17 18
    pub widget: gtk::Stack,
    icons_container: gtk::Box,
    icons_contexts: Vec<RefCell<IconsViewContext>>,
19
    parent: gtk::ApplicationWindow,
Bilal Elmoussaoui's avatar
Bilal Elmoussaoui committed
20 21 22
}

impl IconsView {
23
    pub fn new(parent: &gtk::ApplicationWindow) -> IconsView {
24 25 26 27
        let widget = gtk::Stack::new();

        let icons_container = gtk::Box::new(gtk::Orientation::Vertical, 6);
        icons_container.set_valign(gtk::Align::Start);
28

29
        let icons_contexts: Vec<RefCell<IconsViewContext>> = Vec::new();
30
        let parent = parent.clone();
31 32 33 34
        let mut icons_view = IconsView {
            widget,
            icons_container,
            icons_contexts,
35
            parent,
36 37
        };
        icons_view.init();
38
        icons_view.fill();
39 40 41
        icons_view
    }

42 43 44 45 46 47 48
    fn init(&self) {
        // No results view
        let no_results_view = gtk::Box::new(gtk::Orientation::Vertical, 6);
        no_results_view.get_style_context().add_class("dim-label");
        no_results_view.set_valign(gtk::Align::Center);
        no_results_view.set_halign(gtk::Align::Center);

49
        let no_results_image = gtk::Image::new_from_icon_name(Some("system-search-symbolic"), gtk::IconSize::Dialog);
50 51
        no_results_image.set_pixel_size(96);

52
        let no_results_label = gtk::Label::new(Some(gettext("No results found").as_str()));
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
        no_results_label.get_style_context().add_class("no-results-label");

        no_results_view.pack_start(&no_results_image, false, false, 0);
        no_results_view.pack_start(&no_results_label, false, false, 0);

        self.widget.add_named(&no_results_view, "no-results");

        let column = Column::new();
        column.set_maximum_width(900);
        // we can't call add as libhandy::Column is not seen as a gtkContainer
        // Thanks to Jordan (Podcasts) for the tip :P
        let column = column.upcast::<gtk::Widget>();
        let column = column.downcast::<gtk::Container>().unwrap();
        column.add(&self.icons_container);
        column.set_margin_bottom(24);

        let scrolled_window = gtk::ScrolledWindow::new(gtk::NONE_ADJUSTMENT, gtk::NONE_ADJUSTMENT);
        scrolled_window.set_vexpand(true);
        scrolled_window.add(&column);

        self.widget.add_named(&scrolled_window, "results");
        scrolled_window.show_all();
    }

77 78
    fn fill(&mut self) {
        // Add Icon Dev kit
79 80 81
        let dev_kit_file: PathBuf = [config::PKGDATADIR, "icons_dev_kit.json"].iter().collect();
        let file = File::open(dev_kit_file).expect("Failed to open icons_dev_kit.json");
        let reader = BufReader::new(file);
82

83 84 85
        let mut dev_kit_icons: Vec<Icon> = serde_json::from_reader(reader).expect("Failed to parse json file");
        dev_kit_icons.sort_by(|a, b| a.name.as_str().cmp(&b.name.as_str()));
        self.add_icons_context(gettext("General").to_string(), dev_kit_icons);
86

87
        // Load default theme icons
88 89 90
        let theme = gtk::IconTheme::get_default().unwrap();
        let mut icon_contexts = theme.list_contexts();
        icon_contexts.sort_by(|a, b| a.as_str().cmp(&b.as_str()));
91

92
        let legacy_contexts = vec!["Legacy"];
93
        let app_icon = format!("{}-symbolic", config::APP_ID);
94 95 96 97 98 99 100
        let icons_blacklist = vec![
            "gesture-rotate-clockwise-symbolic",
            "gesture-pinch-symbolic",
            "gesture-two-finger-swipe-left-symbolic",
            "gesture-two-finger-swipe-right-symbolic",
            "gesture-stretch-symbolic",
            "gesture-rotate-anticlockwise-symbolic",
101
            app_icon.as_str(),
102
        ];
103 104 105 106 107
        for context in icon_contexts.iter() {
            let context_str = context.as_str();
            if legacy_contexts.contains(&context_str) {
                continue;
            }
108 109

            let mut icons: Vec<Icon> = theme
110
                .list_icons(Some(context_str))
111 112
                .into_iter()
                .filter(|icon_name| {
Piotr Drąg's avatar
Piotr Drąg committed
113
                    // filter out unneeded icons
114 115 116 117 118
                    !icons_blacklist.contains(&icon_name.as_str()) && icon_name.ends_with("-symbolic")
                })
                .map(|icon_name| Icon {
                    name: icon_name.as_str().to_string(),
                    is_system: true,
119
                    tags: [context_str.to_string()].to_vec(),
120 121 122 123 124
                })
                .collect();
            icons.sort_by(|a, b| a.name.as_str().cmp(&b.name.as_str()));

            self.add_icons_context(context_str.to_string(), icons);
125 126 127 128
        }
    }

    pub fn filter(&self, search_str: String) {
129 130 131 132 133 134 135 136 137 138
        let mut total_results = 0;

        for icons_context in self.icons_contexts.iter() {
            icons_context.borrow_mut().filter(search_str.clone());
            total_results += icons_context.borrow_mut().n_last_search_results;
        }
        if total_results != 0 {
            self.widget.set_visible_child_name("results");
        } else {
            self.widget.set_visible_child_name("no-results");
139 140
        }
    }
141

142 143
    fn add_icons_context(&mut self, context: String, icons: Vec<Icon>) {
        let icons_view_context = IconsViewContext::new(context, icons, &self.parent);
144 145 146
        self.icons_container.pack_start(&icons_view_context.widget, true, true, 12);
        self.icons_contexts.push(RefCell::new(icons_view_context));
    }
147 148 149 150 151 152
}

struct IconsViewContext {
    pub widget: gtk::Box,
    pub context: String,
    icons_container: gtk::FlowBox,
153
    icons_views: HashMap<gtk::FlowBoxChild, Icon>,
154
    n_last_search_results: u32,
155 156 157
}

impl IconsViewContext {
158
    pub fn new(context: String, icons: Vec<Icon>, parent: &gtk::ApplicationWindow) -> IconsViewContext {
159 160
        let widget = gtk::Box::new(gtk::Orientation::Vertical, 6);
        let icons_container = gtk::FlowBox::new();
161
        let icons_views: HashMap<gtk::FlowBoxChild, Icon> = HashMap::new();
162
        let n_last_search_results: u32 = 0;
163
        let mut icons_view_context = IconsViewContext {
164 165 166
            widget,
            context,
            icons_container,
167
            icons_views,
168 169
            n_last_search_results,
        };
Bilal Elmoussaoui's avatar
Bilal Elmoussaoui committed
170

171
        icons_view_context.init();
172 173
        icons_view_context.fill(icons);
        icons_view_context.connect_signals(&parent);
174
        icons_view_context
Bilal Elmoussaoui's avatar
Bilal Elmoussaoui committed
175 176 177
    }

    fn init(&self) {
178 179 180
        self.icons_container.set_row_spacing(12);
        self.icons_container.set_column_spacing(12);
        self.icons_container.set_max_children_per_line(14);
Bilal Elmoussaoui's avatar
Bilal Elmoussaoui committed
181
        self.icons_container.set_min_children_per_line(6);
182
        self.icons_container.set_selection_mode(gtk::SelectionMode::None);
Bilal Elmoussaoui's avatar
Bilal Elmoussaoui committed
183

184 185 186 187 188
        let context_label = gtk::Label::new(Some(self.context.as_str()));
        context_label.set_halign(gtk::Align::Start);
        context_label.get_style_context().add_class("icons-view-title");
        self.widget.add(&context_label);
    }
Bilal Elmoussaoui's avatar
Bilal Elmoussaoui committed
189

190
    fn fill(&mut self, icons: Vec<Icon>) {
191
        for icon in icons {
192
            let icon_view = IconView::new(icon.clone());
193
            self.icons_container.add(&icon_view.widget);
194
            self.icons_views.insert(icon_view.widget, icon);
Bilal Elmoussaoui's avatar
Bilal Elmoussaoui committed
195
        }
196
        self.widget.add(&self.icons_container);
Bilal Elmoussaoui's avatar
Bilal Elmoussaoui committed
197
    }
198

199 200
    fn connect_signals(&self, parent: &gtk::ApplicationWindow) {
        let parent_window = parent.clone();
201
        let icons_views = self.icons_views.clone();
Bilal Elmoussaoui's avatar
Bilal Elmoussaoui committed
202 203 204 205 206 207 208 209 210
        self.icons_container.connect_child_activated(move |_, child| {
            match icons_views.get(child) {
                Some(icon) => {
                    let export_dialog = ExportDialog::new(icon);
                    export_dialog.widget.set_transient_for(Some(&parent_window));
                    export_dialog.widget.show_all();
                }
                None => error!("Export Dialog: Couldn't find the icon"),
            };
211 212 213
        });
    }

214 215
    pub fn filter(&mut self, search_str: String) {
        self.n_last_search_results = 0;
216
        let icons_views = self.icons_views.clone();
217
        let filter_closure = move |child: &gtk::FlowBoxChild| -> bool {
218 219
            match icons_views.get(child) {
                Some(icon) => icon.should_display(&search_str),
Bilal Elmoussaoui's avatar
Bilal Elmoussaoui committed
220 221 222 223
                None => {
                    error!("Search: couldn't find the icon");
                    false
                }
224
            }
225 226 227 228 229 230 231 232 233 234
        };
        self.icons_container.set_filter_func(Some(Box::new(filter_closure)));
        for child in self.icons_container.get_children() {
            if child.get_child_visible() {
                self.n_last_search_results += 1;
            }
        }
        self.widget.set_visible(self.n_last_search_results != 0);
    }
}
Bilal Elmoussaoui's avatar
Bilal Elmoussaoui committed
235

236
struct IconView {
237
    pub widget: gtk::FlowBoxChild,
238 239 240
}

impl IconView {
241
    pub fn new(icon: Icon) -> IconView {
242
        let icon_name = icon.name.as_str();
243

244
        let widget = gtk::FlowBoxChild::new();
245 246
        widget.set_name(icon_name);
        let icon_img = gtk::Image::new_from_icon_name(Some(icon_name), gtk::IconSize::Dnd);
247
        widget.set_tooltip_text(Some(icon_name));
248
        widget.add(&icon_img);
249

Bilal Elmoussaoui's avatar
Bilal Elmoussaoui committed
250
        let icon_view = IconView { widget };
251 252
        icon_view
    }
Bilal Elmoussaoui's avatar
Bilal Elmoussaoui committed
253
}