Adding upstream version 0.5.6.
[debian/lxappearance.git] / src / icon-theme.c
CommitLineData
09ea0a61
DB
1/*
2 * icon-theme.c
3 *
4 * Copyright 2010 PCMan <pcman.tw@gmail.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 * MA 02110-1301, USA.
20 */
21
22#include "icon-theme.h"
23#include "lxappearance.h"
24#include <string.h>
25#include <unistd.h>
26#include "utils.h"
27
28char** icon_theme_dirs = NULL;
29
30gint icon_theme_cmp_name(IconTheme* t, const char* name)
31{
32 return strcmp(t->name, name);
33}
34
35gint icon_theme_cmp_disp_name(IconTheme* t1, IconTheme* t2)
36{
37 return g_utf8_collate(t1->disp_name, t2->disp_name);
38}
39
40static void icon_theme_free(IconTheme* theme)
41{
42 g_free(theme->comment);
43 g_free(theme->name);
44 if(theme->disp_name != theme->name)
45 g_free(theme->disp_name);
46 g_slice_free(IconTheme, theme);
47}
48
49void load_icon_themes_from_dir(const char* base_dir, const char* theme_dir, GKeyFile* kf)
50{
51 /* NOTE:
52 * 1. theoratically, base_dir is identical to theme_dir
53 * the only case that they are different is when we try to install
54 * a new theme. icon themes in a temporary theme dir may later be
55 * installed to base_dir, and we load it from temp. theme dir
56 * before installation. So theme_dir is the temporary dir containing
57 * the themes to install. base_dir is the destination directory to be
58 * installed to.
59 * 2. base_dir is actually a component of global variable icon_theme_dirs.
60 * so it's safe to use this string without strdup() since its life
61 * span is the same as the whole program and won't be freed. */
62
63 GDir* dir = g_dir_open(theme_dir, 0, NULL);
64 if(dir)
65 {
66 const char* name;
f80d2712 67 while ((name = g_dir_read_name(dir)))
09ea0a61
DB
68 {
69 /* skip "default" */
70 if(G_UNLIKELY(strcmp(name, "default") == 0))
71 continue;
72 /* test if we already have this in list */
73 if(!g_slist_find_custom(app.icon_themes, name, (GCompareFunc)icon_theme_cmp_name))
74 {
75 IconTheme* theme = g_slice_new0(IconTheme);
76 char* index_theme;
77 char* cursor_subdir;
78
79 theme->name = g_strdup(name);
80 index_theme = g_build_filename(theme_dir, name, "index.theme", NULL);
81 theme->base_dir = base_dir;
82 theme->is_removable = (0 == access(base_dir, W_OK));
83
84 if(g_key_file_load_from_file(kf, index_theme, 0, NULL))
85 {
86 /* skip hidden ones */
87 if(!g_key_file_get_boolean(kf, "Icon Theme", "Hidden", NULL))
88 {
89 theme->disp_name = g_key_file_get_locale_string(kf, "Icon Theme", "Name", NULL, NULL);
90 /* test if this is a icon theme or it's a cursor theme */
91 theme->comment = g_key_file_get_locale_string(kf, "Icon Theme", "Comment", NULL, NULL);
92
93 /* icon theme must have this key, so it has icons if it has this key */
94 theme->has_icon = g_key_file_has_key(kf, "Icon Theme", "Directories", NULL);
95 }
96 }
97 else
98 theme->disp_name = theme->name;
99 g_free(index_theme);
100
101 cursor_subdir = g_build_filename(theme_dir, name, "cursors", NULL);
102 /* it contains a cursor theme */
103 if(g_file_test(cursor_subdir, G_FILE_TEST_IS_DIR))
104 theme->has_cursor = TRUE;
105 g_free(cursor_subdir);
106
107 if(theme->has_icon || theme->has_cursor)
108 app.icon_themes = g_slist_insert_sorted(app.icon_themes, theme,
109 (GCompareFunc)icon_theme_cmp_disp_name);
110 else /* this dir contains no icon or cursor theme, drop it. */
111 icon_theme_free(theme);
112 }
113 }
114 g_dir_close(dir);
115 }
116}
117
118static void load_icon_themes()
119{
120 int n, i;
121 gtk_icon_theme_get_search_path(gtk_icon_theme_get_default(), &icon_theme_dirs, &n);
122 GKeyFile* kf = g_key_file_new();
123 for(i = 0; i < n; ++i)
124 {
125 load_icon_themes_from_dir(icon_theme_dirs[i], icon_theme_dirs[i], kf);
126 /* g_debug("icon_theme_dirs[%d] = %s", i, icon_theme_dirs[i]); */
127 }
128 g_key_file_free(kf);
129}
130
131
132static void on_icon_theme_sel_changed(GtkTreeSelection* tree_sel, gpointer user_data)
133{
134 GtkTreeModel* model;
135 GtkTreeIter it;
136 if(gtk_tree_selection_get_selected(tree_sel, &model, &it))
137 {
138 IconTheme* theme;
139 gtk_tree_model_get(model, &it, 1, &theme, -1);
140 if(g_strcmp0(theme->name, app.icon_theme))
141 {
142 g_free(app.icon_theme);
143 app.icon_theme = g_strdup(theme->name);
144 g_object_set(gtk_settings_get_default(), "gtk-icon-theme-name", app.icon_theme, NULL);
145
146 lxappearance_changed();
147 }
148
149 gtk_widget_set_sensitive(app.icon_theme_remove_btn, theme->is_removable);
150 }
151}
152
153static void on_install_theme_clicked(GtkButton* btn, gpointer user_data)
154{
155 install_icon_theme(GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(btn))));
156}
157
158static void on_remove_theme_clicked(GtkButton* btn, gpointer user_data)
159{
09ea0a61
DB
160 GtkTreeSelection* sel;
161 GtkTreeModel* model;
162 GtkTreeIter it;
163
164 if(btn == (GtkButton*)app.icon_theme_remove_btn) /* remove icon theme */
165 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(app.icon_theme_view));
166 else if(btn == (GtkButton*)app.cursor_theme_remove_btn) /* remove cursor theme */
167 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(app.cursor_theme_view));
168 else
169 return;
170
171 if(gtk_tree_selection_get_selected(sel, &model, &it))
172 {
173 IconTheme* theme;
174 gboolean both = theme->has_icon && theme->has_cursor;
175
176 if(gtk_tree_model_iter_n_children(model, NULL) < 2)
177 {
178 /* FIXME: the user shouldn't remove the last available theme.
179 * another list needs to be checked, too. */
180 return;
181 }
182
183 gtk_tree_model_get(model, &it, 1, &theme, -1);
184 if(remove_icon_theme(GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(btn))), theme))
185 {
186 gtk_list_store_remove(GTK_LIST_STORE(model), &it);
187
188 /* select the first theme */
189 gtk_tree_model_get_iter_first(model, &it);
190 gtk_tree_selection_select_iter(sel, &it);
191
192 /* FIXME: in rare case, a theme can contain both icons and cursors */
193 if(both) /* we need to remove item in another list store, too */
194 {
195 model = GTK_TREE_MODEL(model == (GtkTreeModel*)app.icon_theme_store ? app.cursor_theme_store : app.icon_theme_store);
196 /* find the item in another store */
197 if(gtk_tree_model_get_iter_first(model, &it))
198 {
199 IconTheme* theme2;
200 do
201 {
202 gtk_tree_model_get(model, &it, 1, &theme2, -1);
203 if(theme2 == theme)
204 {
205 gtk_list_store_remove(GTK_LIST_STORE(model), &it);
206 /* select the first theme */
207 gtk_tree_model_get_iter_first(model, &it);
208 gtk_tree_selection_select_iter(sel, &it);
209 break;
210 }
211 }while(gtk_tree_model_iter_next(model, &it));
212 }
213 }
214 }
215 }
216}
217
218void icon_theme_init(GtkBuilder* b)
219{
220 GSList* l;
221 GtkTreeIter it;
222 GtkTreeIter icon_theme_sel_it = {0};
223 GtkTreeIter cursor_theme_sel_it = {0};
224 GtkTreeSelection* sel;
225 GtkWidget* btn;
226
227 app.icon_theme_store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_POINTER);
228 app.cursor_theme_store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_POINTER);
229 app.icon_theme_view = GTK_WIDGET(gtk_builder_get_object(b, "icon_theme_view"));
230 app.cursor_theme_view = GTK_WIDGET(gtk_builder_get_object(b, "cursor_theme_view"));
231
232 /* install icon and cursor theme */
233 btn = GTK_WIDGET(gtk_builder_get_object(b, "install_icon_theme"));
234 g_signal_connect(btn, "clicked", G_CALLBACK(on_install_theme_clicked), NULL);
235
236 btn = GTK_WIDGET(gtk_builder_get_object(b, "install_cursor_theme"));
237 g_signal_connect(btn, "clicked", G_CALLBACK(on_install_theme_clicked), NULL);
238
239 /* remove icon theme */
240 app.icon_theme_remove_btn = GTK_WIDGET(gtk_builder_get_object(b, "remove_icon_theme"));
241 g_signal_connect(app.icon_theme_remove_btn, "clicked", G_CALLBACK(on_remove_theme_clicked), NULL);
242
243 app.cursor_theme_remove_btn = GTK_WIDGET(gtk_builder_get_object(b, "remove_cursor_theme"));
244 g_signal_connect(app.cursor_theme_remove_btn, "clicked", G_CALLBACK(on_remove_theme_clicked), NULL);
245
246 /* load icon and cursor themes */
247 load_icon_themes();
248
249 for(l = app.icon_themes; l; l=l->next)
250 {
251 IconTheme* theme = (IconTheme*)l->data;
252
253 if(theme->has_icon)
254 {
255 gtk_list_store_insert_with_values(app.icon_theme_store, &it, -1, 0, theme->disp_name, 1, theme, -1);
256 if(!icon_theme_sel_it.user_data)
257 {
258 if(strcmp(theme->name, app.icon_theme) == 0)
259 icon_theme_sel_it = it;
260 }
261 }
262
263 if(theme->has_cursor)
264 {
265 gtk_list_store_insert_with_values(app.cursor_theme_store, &it, -1, 0, theme->disp_name, 1, theme, -1);
266 if(!cursor_theme_sel_it.user_data)
267 {
268 if(g_strcmp0(theme->name, app.cursor_theme) == 0)
269 cursor_theme_sel_it = it;
270 }
271 }
272 }
273
274 /* select the currently used theme from the list */
275 gtk_tree_view_set_model(GTK_TREE_VIEW(app.icon_theme_view), GTK_TREE_MODEL(app.icon_theme_store));
276 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(app.icon_theme_view));
277 if(icon_theme_sel_it.user_data)
278 {
279 IconTheme* theme;
280 GtkTreePath* tp = gtk_tree_model_get_path(GTK_TREE_MODEL(app.icon_theme_store), &icon_theme_sel_it);
281 gtk_tree_selection_select_iter(sel, &icon_theme_sel_it);
282 gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(app.icon_theme_view), tp, NULL, FALSE, 0, 0);
283 gtk_tree_path_free(tp);
284
285 gtk_tree_model_get(GTK_TREE_MODEL(app.icon_theme_store), &icon_theme_sel_it, 1, &theme, -1);
286 gtk_widget_set_sensitive(app.icon_theme_remove_btn, theme->is_removable);
287 }
288 g_signal_connect(sel, "changed", G_CALLBACK(on_icon_theme_sel_changed), NULL);
289
290 gtk_tree_view_set_model(GTK_TREE_VIEW(app.cursor_theme_view), GTK_TREE_MODEL(app.cursor_theme_store));
291 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(app.cursor_theme_view));
292 if(cursor_theme_sel_it.user_data)
293 {
294 IconTheme* theme;
295 GtkTreePath* tp = gtk_tree_model_get_path(GTK_TREE_MODEL(app.cursor_theme_store), &cursor_theme_sel_it);
296 gtk_tree_selection_select_iter(sel, &cursor_theme_sel_it);
297 gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(app.cursor_theme_view), tp, NULL, FALSE, 0, 0);
298 gtk_tree_path_free(tp);
299
300 gtk_tree_model_get(GTK_TREE_MODEL(app.cursor_theme_store), &cursor_theme_sel_it, 1, &theme, -1);
301 gtk_widget_set_sensitive(app.cursor_theme_remove_btn, theme->is_removable);
302 }
303}