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