1 | #include <string.h> |
2 | #include "iconbrowserapp.h" |
3 | #include "iconbrowserwin.h" |
4 | #include "iconbrowsericon.h" |
5 | #include "iconbrowsercontext.h" |
6 | #include <gtk/gtk.h> |
7 | |
8 | |
9 | struct _IconBrowserWindow |
10 | { |
11 | GtkApplicationWindow parent; |
12 | |
13 | GtkWidget *symbolic_radio; |
14 | GtkWidget *searchbar; |
15 | GListModel *icon_filter_model; |
16 | GListStore *icon_store; |
17 | GListModel *context_model; |
18 | GListStore *context_store; |
19 | GtkFilter *name_filter; |
20 | GtkFilter *search_mode_filter; |
21 | GtkWidget *details; |
22 | GtkWidget *image1; |
23 | GtkWidget *image2; |
24 | GtkWidget *image3; |
25 | GtkWidget *image4; |
26 | GtkWidget *image5; |
27 | GtkWidget *image6; |
28 | GtkWidget *image7; |
29 | GtkWidget *image8; |
30 | GtkWidget *label8; |
31 | GtkWidget *description; |
32 | }; |
33 | |
34 | struct _IconBrowserWindowClass |
35 | { |
36 | GtkApplicationWindowClass parent_class; |
37 | }; |
38 | |
39 | G_DEFINE_TYPE(IconBrowserWindow, icon_browser_window, GTK_TYPE_APPLICATION_WINDOW); |
40 | |
41 | static GtkIconTheme * |
42 | icon_browser_window_get_icon_theme (IconBrowserWindow *win) |
43 | { |
44 | return gtk_icon_theme_get_for_display (display: gtk_widget_get_display (GTK_WIDGET (win))); |
45 | } |
46 | |
47 | static void |
48 | add_icon (IconBrowserWindow *win, |
49 | const char *name, |
50 | const char *description, |
51 | const char *context) |
52 | { |
53 | GtkIconTheme *icon_theme = icon_browser_window_get_icon_theme (win); |
54 | char *regular_name; |
55 | char *symbolic_name; |
56 | IbIcon *icon; |
57 | |
58 | regular_name = g_strdup (str: name); |
59 | if (!gtk_icon_theme_has_icon (self: icon_theme, icon_name: regular_name)) |
60 | { |
61 | g_free (mem: regular_name); |
62 | regular_name = NULL; |
63 | } |
64 | |
65 | symbolic_name = g_strconcat (string1: name, "-symbolic" , NULL); |
66 | if (!gtk_icon_theme_has_icon (self: icon_theme, icon_name: symbolic_name)) |
67 | { |
68 | g_free (mem: symbolic_name); |
69 | symbolic_name = NULL; |
70 | } |
71 | |
72 | icon = ib_icon_new (regular_name, symbolic_name, description, context); |
73 | g_object_bind_property (source: win->symbolic_radio, source_property: "active" , |
74 | target: icon, target_property: "use-symbolic" , |
75 | flags: G_BINDING_DEFAULT); |
76 | g_list_store_append (store: win->icon_store, item: icon); |
77 | g_object_unref (object: icon); |
78 | } |
79 | |
80 | static void |
81 | add_context (IconBrowserWindow *win, |
82 | const char *id, |
83 | const char *name, |
84 | const char *description) |
85 | { |
86 | IbContext *context; |
87 | |
88 | context = ib_context_new (id, name, description); |
89 | g_list_store_append (store: win->context_store, item: context); |
90 | g_object_unref (object: context); |
91 | } |
92 | |
93 | static void |
94 | populate (IconBrowserWindow *win) |
95 | { |
96 | GFile *file; |
97 | GKeyFile *kf; |
98 | char *data; |
99 | gsize length; |
100 | char **groups; |
101 | int i; |
102 | |
103 | file = g_file_new_for_uri (uri: "resource:/org/gtk/iconbrowser/gtk/icon.list" ); |
104 | g_file_load_contents (file, NULL, contents: &data, length: &length, NULL, NULL); |
105 | |
106 | kf = g_key_file_new (); |
107 | g_key_file_load_from_data (key_file: kf, data, length, flags: G_KEY_FILE_NONE, NULL); |
108 | |
109 | groups = g_key_file_get_groups (key_file: kf, length: &length); |
110 | for (i = 0; i < length; i++) |
111 | { |
112 | const char *context; |
113 | const char *name; |
114 | const char *description; |
115 | char **keys; |
116 | gsize len; |
117 | int j; |
118 | |
119 | context = groups[i]; |
120 | name = g_key_file_get_string (key_file: kf, group_name: context, key: "Name" , NULL); |
121 | description = g_key_file_get_string (key_file: kf, group_name: context, key: "Description" , NULL); |
122 | add_context (win, id: context, name, description); |
123 | |
124 | keys = g_key_file_get_keys (key_file: kf, group_name: context, length: &len, NULL); |
125 | for (j = 0; j < len; j++) |
126 | { |
127 | const char *key = keys[j]; |
128 | const char *value; |
129 | |
130 | if (strcmp (s1: key, s2: "Name" ) == 0 || strcmp (s1: key, s2: "Description" ) == 0) |
131 | continue; |
132 | |
133 | value = g_key_file_get_string (key_file: kf, group_name: context, key, NULL); |
134 | |
135 | add_icon (win, name: key, description: value, context); |
136 | } |
137 | g_strfreev (str_array: keys); |
138 | } |
139 | g_strfreev (str_array: groups); |
140 | } |
141 | |
142 | static gboolean |
143 | filter_by_icon_name (gpointer item, |
144 | gpointer data) |
145 | { |
146 | return ib_icon_get_name (icon: IB_ICON (ptr: item)) != NULL; |
147 | } |
148 | |
149 | static void |
150 | symbolic_toggled (IconBrowserWindow *win) |
151 | { |
152 | gtk_filter_changed (self: win->name_filter, change: GTK_FILTER_CHANGE_DIFFERENT); |
153 | } |
154 | |
155 | static void |
156 | copy_to_clipboard (GtkButton *button, |
157 | IconBrowserWindow *win) |
158 | { |
159 | GdkClipboard *clipboard; |
160 | |
161 | clipboard = gtk_widget_get_clipboard (GTK_WIDGET (win)); |
162 | gdk_clipboard_set_text (clipboard, text: gtk_window_get_title (GTK_WINDOW (win->details))); |
163 | } |
164 | |
165 | static void |
166 | set_image (GtkWidget *image, const char *name, int size) |
167 | { |
168 | gtk_image_set_from_icon_name (GTK_IMAGE (image), icon_name: name); |
169 | gtk_image_set_pixel_size (GTK_IMAGE (image), pixel_size: size); |
170 | } |
171 | |
172 | static void |
173 | item_activated (GtkGridView *view, |
174 | guint position, |
175 | IconBrowserWindow *win) |
176 | { |
177 | GListModel *model = G_LIST_MODEL (ptr: gtk_grid_view_get_model (self: view)); |
178 | IbIcon *icon = g_list_model_get_item (list: model, position); |
179 | const char *name; |
180 | const char *description; |
181 | gboolean symbolic; |
182 | |
183 | name = ib_icon_get_name (icon); |
184 | description = ib_icon_get_description (icon); |
185 | symbolic = ib_icon_get_use_symbolic (icon); |
186 | |
187 | gtk_window_set_title (GTK_WINDOW (win->details), title: name); |
188 | set_image (image: win->image1, name, size: 8); |
189 | set_image (image: win->image2, name, size: 16); |
190 | set_image (image: win->image3, name, size: 18); |
191 | set_image (image: win->image4, name, size: 24); |
192 | set_image (image: win->image5, name, size: 32); |
193 | set_image (image: win->image6, name, size: 48); |
194 | set_image (image: win->image7, name, size: 64); |
195 | if (symbolic) |
196 | { |
197 | gtk_widget_show (widget: win->image8); |
198 | gtk_widget_show (widget: win->label8); |
199 | set_image (image: win->image8, name, size: 64); |
200 | } |
201 | else |
202 | { |
203 | gtk_widget_hide (widget: win->image8); |
204 | gtk_widget_hide (widget: win->label8); |
205 | } |
206 | if (description && description[0]) |
207 | { |
208 | gtk_label_set_text (GTK_LABEL (win->description), str: description); |
209 | gtk_widget_show (widget: win->description); |
210 | } |
211 | else |
212 | { |
213 | gtk_widget_hide (widget: win->description); |
214 | } |
215 | |
216 | gtk_window_present (GTK_WINDOW (win->details)); |
217 | |
218 | g_object_unref (object: icon); |
219 | } |
220 | |
221 | static GdkPaintable * |
222 | get_image_paintable (GtkImage *image) |
223 | { |
224 | const char *icon_name; |
225 | GtkIconTheme *icon_theme; |
226 | GtkIconPaintable *icon; |
227 | int size; |
228 | |
229 | switch (gtk_image_get_storage_type (image)) |
230 | { |
231 | case GTK_IMAGE_PAINTABLE: |
232 | return g_object_ref (gtk_image_get_paintable (image)); |
233 | case GTK_IMAGE_ICON_NAME: |
234 | icon_name = gtk_image_get_icon_name (image); |
235 | size = gtk_image_get_pixel_size (image); |
236 | icon_theme = gtk_icon_theme_get_for_display (display: gtk_widget_get_display (GTK_WIDGET (image))); |
237 | icon = gtk_icon_theme_lookup_icon (self: icon_theme, |
238 | icon_name, |
239 | NULL, |
240 | size, scale: 1, |
241 | direction: gtk_widget_get_direction (GTK_WIDGET (image)), |
242 | flags: 0); |
243 | if (icon == NULL) |
244 | { |
245 | g_print (format: "no icon for %s\n" , icon_name); |
246 | return NULL; |
247 | } |
248 | return GDK_PAINTABLE (ptr: icon); |
249 | case GTK_IMAGE_GICON: |
250 | case GTK_IMAGE_EMPTY: |
251 | default: |
252 | g_warning ("Image storage type %d not handled" , |
253 | gtk_image_get_storage_type (image)); |
254 | return NULL; |
255 | } |
256 | } |
257 | |
258 | static void |
259 | drag_begin (GtkDragSource *source, |
260 | GdkDrag *drag, |
261 | GtkWidget *widget) |
262 | { |
263 | GdkPaintable *paintable; |
264 | |
265 | paintable = get_image_paintable (GTK_IMAGE (widget)); |
266 | if (paintable) |
267 | { |
268 | int w, h; |
269 | |
270 | w = gdk_paintable_get_intrinsic_width (paintable); |
271 | h = gdk_paintable_get_intrinsic_height (paintable); |
272 | gtk_drag_source_set_icon (source, paintable, hot_x: w, hot_y: h); |
273 | g_object_unref (object: paintable); |
274 | } |
275 | } |
276 | |
277 | static GdkContentProvider * |
278 | drag_prepare_texture (GtkDragSource *source, |
279 | double x, |
280 | double y, |
281 | GtkWidget *widget) |
282 | { |
283 | GdkPaintable *paintable = get_image_paintable (GTK_IMAGE (widget)); |
284 | GtkSnapshot *snapshot; |
285 | double width, height; |
286 | GskRenderNode *node; |
287 | GskRenderer *renderer; |
288 | GdkTexture *texture; |
289 | GdkContentProvider *ret; |
290 | |
291 | if (!GDK_IS_PAINTABLE (ptr: paintable)) |
292 | return NULL; |
293 | |
294 | snapshot = gtk_snapshot_new (); |
295 | width = gdk_paintable_get_intrinsic_width (paintable); |
296 | height = gdk_paintable_get_intrinsic_height (paintable); |
297 | gdk_paintable_snapshot (paintable, snapshot, width, height); |
298 | node = gtk_snapshot_free_to_node (snapshot); |
299 | |
300 | renderer = gtk_native_get_renderer (self: gtk_widget_get_native (widget)); |
301 | texture = gsk_renderer_render_texture (renderer, root: node, viewport: &GRAPHENE_RECT_INIT (0, 0, width, height)); |
302 | |
303 | ret = gdk_content_provider_new_typed (GDK_TYPE_TEXTURE, texture); |
304 | |
305 | g_object_unref (object: texture); |
306 | gsk_render_node_unref (node); |
307 | |
308 | return ret; |
309 | } |
310 | |
311 | static GdkContentProvider * |
312 | drag_prepare_file (GtkDragSource *source, |
313 | double x, |
314 | double y, |
315 | GtkWidget *widget) |
316 | { |
317 | GdkContentProvider *content; |
318 | GtkIconTheme *icon_theme; |
319 | const char *name; |
320 | GtkIconPaintable *info; |
321 | |
322 | name = gtk_image_get_icon_name (GTK_IMAGE (widget)); |
323 | icon_theme = gtk_icon_theme_get_for_display (display: gtk_widget_get_display (widget)); |
324 | |
325 | info = gtk_icon_theme_lookup_icon (self: icon_theme, |
326 | icon_name: name, |
327 | NULL, |
328 | size: 32, scale: 1, |
329 | direction: gtk_widget_get_direction (widget), |
330 | flags: 0); |
331 | content = gdk_content_provider_new_typed (G_TYPE_FILE, gtk_icon_paintable_get_file (self: info)); |
332 | g_object_unref (object: info); |
333 | |
334 | return content; |
335 | } |
336 | |
337 | static void |
338 | setup_image_dnd (GtkWidget *image) |
339 | { |
340 | GtkDragSource *source; |
341 | |
342 | source = gtk_drag_source_new (); |
343 | g_signal_connect (source, "prepare" , G_CALLBACK (drag_prepare_texture), image); |
344 | g_signal_connect (source, "drag-begin" , G_CALLBACK (drag_begin), image); |
345 | gtk_widget_add_controller (widget: image, GTK_EVENT_CONTROLLER (source)); |
346 | } |
347 | |
348 | static void |
349 | setup_scalable_image_dnd (GtkWidget *image) |
350 | { |
351 | GtkDragSource *source; |
352 | |
353 | source = gtk_drag_source_new (); |
354 | g_signal_connect (source, "prepare" , G_CALLBACK (drag_prepare_file), image); |
355 | g_signal_connect (source, "drag-begin" , G_CALLBACK (drag_begin), image); |
356 | gtk_widget_add_controller (widget: image, GTK_EVENT_CONTROLLER (source)); |
357 | } |
358 | |
359 | static void |
360 | search_mode_toggled (GtkSearchBar *searchbar, |
361 | GParamSpec *pspec, |
362 | IconBrowserWindow *win) |
363 | { |
364 | if (gtk_search_bar_get_search_mode (bar: searchbar)) |
365 | gtk_single_selection_set_selected (self: GTK_SINGLE_SELECTION (ptr: win->context_model), GTK_INVALID_LIST_POSITION); |
366 | else if (gtk_single_selection_get_selected (self: GTK_SINGLE_SELECTION (ptr: win->context_model)) == GTK_INVALID_LIST_POSITION) |
367 | gtk_single_selection_set_selected (self: GTK_SINGLE_SELECTION (ptr: win->context_model), position: 0); |
368 | |
369 | gtk_filter_changed (self: win->search_mode_filter, change: GTK_FILTER_CHANGE_DIFFERENT); |
370 | } |
371 | |
372 | static void |
373 | selected_name_changed (GtkSingleSelection *selection, |
374 | GParamSpec *pspec, |
375 | IconBrowserWindow *win) |
376 | { |
377 | if (gtk_single_selection_get_selected (self: selection) != GTK_INVALID_LIST_POSITION) |
378 | gtk_search_bar_set_search_mode (GTK_SEARCH_BAR (win->searchbar), FALSE); |
379 | } |
380 | |
381 | static void |
382 | icon_browser_window_init (IconBrowserWindow *win) |
383 | { |
384 | GtkFilter *filter; |
385 | |
386 | gtk_widget_init_template (GTK_WIDGET (win)); |
387 | |
388 | setup_image_dnd (win->image1); |
389 | setup_image_dnd (win->image2); |
390 | setup_image_dnd (win->image3); |
391 | setup_image_dnd (win->image4); |
392 | setup_image_dnd (win->image5); |
393 | setup_image_dnd (win->image6); |
394 | setup_image_dnd (win->image7); |
395 | setup_scalable_image_dnd (win->image8); |
396 | |
397 | gtk_window_set_transient_for (GTK_WINDOW (win->details), GTK_WINDOW (win)); |
398 | gtk_search_bar_set_key_capture_widget (GTK_SEARCH_BAR (win->searchbar), GTK_WIDGET (win)); |
399 | |
400 | populate (win); |
401 | |
402 | filter = gtk_filter_list_model_get_filter (self: GTK_FILTER_LIST_MODEL (ptr: win->icon_filter_model)); |
403 | |
404 | win->name_filter = GTK_FILTER (ptr: gtk_custom_filter_new (match_func: filter_by_icon_name, NULL, NULL)); |
405 | |
406 | gtk_multi_filter_append (self: GTK_MULTI_FILTER (ptr: filter), g_object_ref (win->name_filter)); |
407 | |
408 | g_signal_connect (win->searchbar, "notify::search-mode-enabled" , G_CALLBACK (search_mode_toggled), win); |
409 | g_signal_connect (win->context_model, "notify::selected" , G_CALLBACK (selected_name_changed), win); |
410 | } |
411 | |
412 | static void |
413 | icon_browser_window_finalize (GObject *object) |
414 | { |
415 | IconBrowserWindow *win = ICON_BROWSER_WINDOW (object); |
416 | |
417 | g_clear_object (&win->name_filter); |
418 | |
419 | G_OBJECT_CLASS (icon_browser_window_parent_class)->finalize (object); |
420 | } |
421 | |
422 | static void |
423 | icon_browser_window_class_init (IconBrowserWindowClass *class) |
424 | { |
425 | GObjectClass *object_class = G_OBJECT_CLASS (class); |
426 | |
427 | object_class->finalize = icon_browser_window_finalize; |
428 | |
429 | g_type_ensure (IB_TYPE_ICON); |
430 | g_type_ensure (IB_TYPE_CONTEXT); |
431 | |
432 | gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (class), |
433 | resource_name: "/org/gtk/iconbrowser/gtk/window.ui" ); |
434 | |
435 | gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), IconBrowserWindow, symbolic_radio); |
436 | gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), IconBrowserWindow, searchbar); |
437 | gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), IconBrowserWindow, icon_store); |
438 | gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), IconBrowserWindow, icon_filter_model); |
439 | gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), IconBrowserWindow, context_model); |
440 | gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), IconBrowserWindow, context_store); |
441 | |
442 | gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), IconBrowserWindow, details); |
443 | gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), IconBrowserWindow, image1); |
444 | gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), IconBrowserWindow, image2); |
445 | gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), IconBrowserWindow, image3); |
446 | gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), IconBrowserWindow, image4); |
447 | gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), IconBrowserWindow, image5); |
448 | gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), IconBrowserWindow, image6); |
449 | gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), IconBrowserWindow, image7); |
450 | gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), IconBrowserWindow, image8); |
451 | gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), IconBrowserWindow, label8); |
452 | gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), IconBrowserWindow, description); |
453 | gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), IconBrowserWindow, search_mode_filter); |
454 | |
455 | gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), item_activated); |
456 | gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), copy_to_clipboard); |
457 | gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), symbolic_toggled); |
458 | } |
459 | |
460 | IconBrowserWindow * |
461 | icon_browser_window_new (IconBrowserApp *app) |
462 | { |
463 | return g_object_new (ICON_BROWSER_WINDOW_TYPE, first_property_name: "application" , app, NULL); |
464 | } |
465 | |