1 /*
2 * Copyright (C) 2007 The GNOME Foundation
3 * Written by Thomas Wood <thos@gnome.org>
4 * Jens Granseuer <jensgr@gmx.net>
5 * All Rights Reserved
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 */
21
22 #include "appearance.h"
23
24 #include <glib/gstdio.h>
25 #include <glib/gi18n.h>
26 #include <gio/gio.h>
27 #include <string.h>
28
29 #include "theme-save.h"
30 #include "theme-util.h"
31 #include "capplet-util.h"
32
33 static GQuark error_quark;
34 enum {
35 INVALID_THEME_NAME
36 };
37
38 /* taken from mate-desktop-item.c */
39 static gchar *
escape_string_and_dup(const gchar * s)40 escape_string_and_dup (const gchar *s)
41 {
42 gchar *return_value, *p;
43 const gchar *q;
44 int len = 0;
45
46 if (s == NULL)
47 return g_strdup("");
48
49 q = s;
50 while (*q)
51 {
52 len++;
53 if (strchr ("\n\r\t\\", *q) != NULL)
54 len++;
55 q++;
56 }
57 return_value = p = (gchar *) g_malloc (len + 1);
58 do
59 {
60 switch (*s)
61 {
62 case '\t':
63 *p++ = '\\';
64 *p++ = 't';
65 break;
66 case '\n':
67 *p++ = '\\';
68 *p++ = 'n';
69 break;
70 case '\r':
71 *p++ = '\\';
72 *p++ = 'r';
73 break;
74 case '\\':
75 *p++ = '\\';
76 *p++ = '\\';
77 break;
78 default:
79 *p++ = *s;
80 }
81 }
82 while (*s++);
83 return return_value;
84 }
85
86 static gboolean
check_theme_name(const gchar * theme_name,GError ** error)87 check_theme_name (const gchar *theme_name,
88 GError **error)
89 {
90 if (theme_name == NULL) {
91 g_set_error (error,
92 error_quark,
93 INVALID_THEME_NAME,
94 _("Theme name must be present"));
95 return FALSE;
96 }
97 return TRUE;
98 }
99
100 static gchar *
str_remove_slash(const gchar * src)101 str_remove_slash (const gchar *src)
102 {
103 const gchar *i;
104 gchar *rtn;
105 gint len = 0;
106 i = src;
107
108 while (*i) {
109 if (*i != '/')
110 len++;
111 i++;
112 }
113
114 rtn = (gchar *) g_malloc (len + 1);
115 while (*src) {
116 if (*src != '/') {
117 *rtn = *src;
118 rtn++;
119 }
120 src++;
121 }
122 *rtn = '\0';
123 return rtn - len;
124 }
125
126 static gboolean
setup_directory_structure(const gchar * theme_name,GError ** error)127 setup_directory_structure (const gchar *theme_name,
128 GError **error)
129 {
130 gchar *dir, *theme_name_dir;
131 gboolean retval = TRUE;
132
133 theme_name_dir = str_remove_slash (theme_name);
134
135 dir = g_build_filename (g_get_home_dir (), ".themes", NULL);
136 if (!g_file_test (dir, G_FILE_TEST_EXISTS))
137 g_mkdir (dir, 0775);
138 g_free (dir);
139
140 dir = g_build_filename (g_get_home_dir (), ".themes", theme_name_dir, NULL);
141 if (!g_file_test (dir, G_FILE_TEST_EXISTS))
142 g_mkdir (dir, 0775);
143 g_free (dir);
144
145 dir = g_build_filename (g_get_home_dir (), ".themes", theme_name_dir, "index.theme", NULL);
146 g_free (theme_name_dir);
147
148 if (g_file_test (dir, G_FILE_TEST_EXISTS)) {
149 GtkDialog *dialog;
150 GtkWidget *button;
151 gint response;
152
153 dialog = (GtkDialog *) gtk_message_dialog_new (NULL,
154 GTK_DIALOG_MODAL,
155 GTK_MESSAGE_QUESTION,
156 GTK_BUTTONS_CANCEL,
157 _("The theme already exists. Would you like to replace it?"));
158 button = gtk_dialog_add_button (dialog, _("_Overwrite"), GTK_RESPONSE_ACCEPT);
159 gtk_button_set_image (GTK_BUTTON (button),
160 gtk_image_new_from_icon_name ("document-save", GTK_ICON_SIZE_BUTTON));
161 response = gtk_dialog_run (dialog);
162 gtk_widget_destroy (GTK_WIDGET (dialog));
163 retval = (response != GTK_RESPONSE_CANCEL);
164 }
165 g_free (dir);
166
167 return retval;
168 }
169
170 static gboolean
write_theme_to_disk(MateThemeMetaInfo * theme_info,const gchar * theme_name,const gchar * theme_description,gboolean save_background,gboolean save_notification,GError ** error)171 write_theme_to_disk (MateThemeMetaInfo *theme_info,
172 const gchar *theme_name,
173 const gchar *theme_description,
174 gboolean save_background,
175 gboolean save_notification,
176 GError **error)
177 {
178 gchar* dir;
179 gchar* theme_name_dir;
180 GFile* tmp_file;
181 GFile* target_file;
182 GOutputStream* output;
183
184 gchar* str;
185 gchar* current_background;
186
187 GSettings* settings;
188 const gchar* theme_header = ""
189 "[Desktop Entry]\n"
190 "Name=%s\n"
191 "Type=X-GNOME-Metatheme\n"
192 "Comment=%s\n"
193 "\n"
194 "[X-GNOME-Metatheme]\n"
195 "GtkTheme=%s\n"
196 "MetacityTheme=%s\n"
197 "IconTheme=%s\n";
198
199 theme_name_dir = str_remove_slash (theme_name);
200 dir = g_build_filename (g_get_home_dir (), ".themes", theme_name_dir, "index.theme~", NULL);
201 g_free (theme_name_dir);
202
203 tmp_file = g_file_new_for_path (dir);
204 dir [strlen (dir) - 1] = '\000';
205 target_file = g_file_new_for_path (dir);
206 g_free (dir);
207
208 /* start making the theme file */
209 str = g_strdup_printf(theme_header, theme_name, theme_description, theme_info->gtk_theme_name, theme_info->marco_theme_name, theme_info->icon_theme_name);
210
211 output = G_OUTPUT_STREAM (g_file_replace (tmp_file, NULL, FALSE, G_FILE_CREATE_NONE, NULL, NULL));
212 g_output_stream_write (output, str, strlen (str), NULL, NULL);
213 g_free (str);
214
215 if (theme_info->gtk_color_scheme) {
216 gchar *a, *tmp;
217 tmp = g_strdup (theme_info->gtk_color_scheme);
218 for (a = tmp; *a != '\0'; a++)
219 if (*a == '\n')
220 *a = ',';
221 str = g_strdup_printf ("GtkColorScheme=%s\n", tmp);
222 g_output_stream_write (output, str, strlen (str), NULL, NULL);
223
224 g_free (str);
225 g_free (tmp);
226 }
227
228 if (theme_info->cursor_theme_name) {
229 str = g_strdup_printf ("CursorTheme=%s\n"
230 "CursorSize=%i\n",
231 theme_info->cursor_theme_name,
232 theme_info->cursor_size);
233 g_output_stream_write (output, str, strlen (str), NULL, NULL);
234 g_free (str);
235 }
236
237 if (theme_info->notification_theme_name && save_notification) {
238 str = g_strdup_printf ("NotificationTheme=%s\n", theme_info->notification_theme_name);
239 g_output_stream_write (output, str, strlen (str), NULL, NULL);
240 g_free (str);
241 }
242
243 if (save_background) {
244 settings = g_settings_new (WP_SCHEMA);
245 current_background = g_settings_get_string (settings, WP_FILE_KEY);
246
247 if (current_background != NULL) {
248 str = g_strdup_printf ("BackgroundImage=%s\n", current_background);
249
250 g_output_stream_write (output, str, strlen (str), NULL, NULL);
251
252 g_free (current_background);
253 g_free (str);
254 }
255 g_object_unref (settings);
256 }
257
258 g_file_move (tmp_file, target_file, G_FILE_COPY_OVERWRITE, NULL, NULL, NULL, NULL);
259 g_output_stream_close (output, NULL, NULL);
260
261 g_object_unref (tmp_file);
262 g_object_unref (target_file);
263
264 return TRUE;
265 }
266
267 static gboolean
save_theme_to_disk(MateThemeMetaInfo * theme_info,const gchar * theme_name,const gchar * theme_description,gboolean save_background,gboolean save_notification,GError ** error)268 save_theme_to_disk (MateThemeMetaInfo *theme_info,
269 const gchar *theme_name,
270 const gchar *theme_description,
271 gboolean save_background,
272 gboolean save_notification,
273 GError **error)
274 {
275 if (!check_theme_name (theme_name, error))
276 return FALSE;
277
278 if (!setup_directory_structure (theme_name, error))
279 return FALSE;
280
281 if (!write_theme_to_disk (theme_info, theme_name, theme_description, save_background, save_notification, error))
282 return FALSE;
283
284 return TRUE;
285 }
286
287 static void
save_dialog_response(GtkWidget * save_dialog,gint response_id,AppearanceData * data)288 save_dialog_response (GtkWidget *save_dialog,
289 gint response_id,
290 AppearanceData *data)
291 {
292 if (response_id == GTK_RESPONSE_OK) {
293 GtkWidget *entry;
294 GtkWidget *text_view;
295 GtkTextBuffer *buffer;
296 GtkTextIter start_iter;
297 GtkTextIter end_iter;
298 gchar *buffer_text;
299 MateThemeMetaInfo *theme_info;
300 gchar *theme_description = NULL;
301 gchar *theme_name = NULL;
302 gboolean save_background;
303 gboolean save_notification;
304 GError *error = NULL;
305
306 entry = appearance_capplet_get_widget (data, "save_dialog_entry");
307 theme_name = escape_string_and_dup (gtk_entry_get_text (GTK_ENTRY (entry)));
308
309 text_view = appearance_capplet_get_widget (data, "save_dialog_textview");
310 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view));
311 gtk_text_buffer_get_start_iter (buffer, &start_iter);
312 gtk_text_buffer_get_end_iter (buffer, &end_iter);
313 buffer_text = gtk_text_buffer_get_text (buffer, &start_iter, &end_iter, FALSE);
314 theme_description = escape_string_and_dup (buffer_text);
315 g_free (buffer_text);
316 theme_info = (MateThemeMetaInfo *) g_object_get_data (G_OBJECT (save_dialog), "meta-theme-info");
317 save_background = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (
318 appearance_capplet_get_widget (data, "save_background_checkbutton")));
319 save_notification = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (
320 appearance_capplet_get_widget (data, "save_notification_checkbutton")));
321
322 if (save_theme_to_disk (theme_info, theme_name, theme_description, save_background, save_notification, &error)) {
323 /* remove the custom theme */
324 GtkTreeIter iter;
325
326 if (theme_find_in_model (GTK_TREE_MODEL (data->theme_store), "__custom__", &iter))
327 gtk_list_store_remove (data->theme_store, &iter);
328 }
329
330 g_free (theme_name);
331 g_free (theme_description);
332 g_clear_error (&error);
333 }
334
335 gtk_widget_hide (save_dialog);
336 }
337
338 static void
entry_text_changed(GtkEditable * editable,AppearanceData * data)339 entry_text_changed (GtkEditable *editable,
340 AppearanceData *data)
341 {
342 const gchar *text;
343 GtkWidget *button;
344
345 text = gtk_entry_get_text (GTK_ENTRY (editable));
346 button = appearance_capplet_get_widget (data, "save_dialog_save_button");
347
348 gtk_widget_set_sensitive (button, text != NULL && text[0] != '\000');
349 }
350
351 void
theme_save_dialog_run(MateThemeMetaInfo * theme_info,AppearanceData * data)352 theme_save_dialog_run (MateThemeMetaInfo *theme_info,
353 AppearanceData *data)
354 {
355 GtkWidget *entry;
356 GtkWidget *text_view;
357 GtkTextBuffer *text_buffer;
358
359 entry = appearance_capplet_get_widget (data, "save_dialog_entry");
360 text_view = appearance_capplet_get_widget (data, "save_dialog_textview");
361
362 if (data->theme_save_dialog == NULL) {
363 data->theme_save_dialog = appearance_capplet_get_widget (data, "theme_save_dialog");
364
365 g_signal_connect (data->theme_save_dialog, "response", (GCallback) save_dialog_response, data);
366 g_signal_connect (data->theme_save_dialog, "delete-event", (GCallback) gtk_true, NULL);
367 g_signal_connect (entry, "changed", (GCallback) entry_text_changed, data);
368
369 error_quark = g_quark_from_string ("mate-theme-save");
370 gtk_widget_set_size_request (text_view, 300, 100);
371 }
372
373 gtk_entry_set_text (GTK_ENTRY (entry), "");
374 entry_text_changed (GTK_EDITABLE (entry), data);
375 gtk_widget_grab_focus (entry);
376
377 text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view));
378 gtk_text_buffer_set_text (text_buffer, "", 0);
379 g_object_set_data (G_OBJECT (data->theme_save_dialog), "meta-theme-info", theme_info);
380 gtk_window_set_transient_for (GTK_WINDOW (data->theme_save_dialog),
381 GET_WINDOW ("appearance_window"));
382 gtk_widget_show (data->theme_save_dialog);
383 }
384