1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
2 
3 /*
4    caja-directory-background.c: Helper for the background of a widget
5                                 that is viewing a particular location.
6 
7    Copyright (C) 2000 Eazel, Inc.
8    Copyright (C) 2012 Jasmine Hassan <jasmine.aura@gmail.com>
9    Copyright (C) 2012-2021 The MATE developers
10 
11    This program is free software; you can redistribute it and/or
12    modify it under the terms of the GNU General Public License as
13    published by the Free Software Foundation; either version 2 of the
14    License, or (at your option) any later version.
15 
16    This program is distributed in the hope that it will be useful,
17    but WITHOUT ANY WARRANTY; without even the implied warranty of
18    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19    General Public License for more details.
20 
21    You should have received a copy of the GNU General Public
22    License along with this program; if not, write to the
23    Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
24    Boston, MA 02110-1301, USA.
25 
26    Authors: Darin Adler <darin@bentspoon.com>
27             Jasmine Hassan <jasmine.aura@gmail.com>
28 */
29 
30 #include <config.h>
31 #include <gtk/gtk.h>
32 #include <string.h>
33 
34 #include <eel/eel-gdk-extensions.h>
35 #include <eel/eel-gtk-extensions.h>
36 #include <eel/eel-background.h>
37 
38 #include "caja-directory-background.h"
39 #include "caja-dnd.h"
40 #include "caja-global-preferences.h"
41 #include "caja-metadata.h"
42 #include "caja-file-attributes.h"
43 
44 static void caja_background_changed_cb (EelBackground *background,
45                                         GdkDragAction  action,
46                                         CajaFile      *file);
47 
48 static void
caja_background_get_default_settings(char ** color,char ** image)49 caja_background_get_default_settings (char **color,
50                                       char **image)
51 {
52     gboolean background_set;
53 
54     background_set = g_settings_get_boolean (caja_preferences, CAJA_PREFERENCES_BACKGROUND_SET);
55 
56     if (background_set && color)
57         *color = g_settings_get_string (caja_preferences, CAJA_PREFERENCES_BACKGROUND_COLOR);
58 
59     if (background_set && image)
60         *image =  g_settings_get_string (caja_preferences, CAJA_PREFERENCES_BACKGROUND_URI);
61 }
62 
63 static void
caja_background_load_from_file_metadata(CajaFile * file,EelBackground * background)64 caja_background_load_from_file_metadata (CajaFile      *file,
65                                          EelBackground *background)
66 {
67     char *color, *image;
68 
69     g_assert (EEL_IS_BACKGROUND (background));
70     g_assert (CAJA_IS_FILE (file));
71     g_assert (g_object_get_data (G_OBJECT (background), "eel_background_file") == file);
72 
73     color = caja_file_get_metadata (file, CAJA_METADATA_KEY_LOCATION_BACKGROUND_COLOR, NULL);
74     image = caja_file_get_metadata (file, CAJA_METADATA_KEY_LOCATION_BACKGROUND_IMAGE, NULL);
75 
76     /* if there's none, read the default from the theme */
77     if (color == NULL && image == NULL)
78         caja_background_get_default_settings (&color, &image);
79 
80     /* Block the other handler while we are responding to changes
81      * in the metadata so it doesn't try to change the metadata.
82      */
83     g_signal_handlers_block_by_func (background, G_CALLBACK (caja_background_changed_cb), file);
84 
85     eel_background_set_color (background, color);
86     /* non-tiled only available for desktop, at least for now */
87     eel_bg_set_placement (background, MATE_BG_PLACEMENT_TILED);
88     eel_background_set_image_uri (background, image);
89 
90     /* Unblock the handler. */
91     g_signal_handlers_unblock_by_func (background, G_CALLBACK (caja_background_changed_cb), file);
92 
93     g_free (color);
94     g_free (image);
95 }
96 
97 /* handle the file changed signal */
98 static void
caja_background_settings_notify_cb(CajaFile * file,EelBackground * background)99 caja_background_settings_notify_cb (CajaFile *file,
100                                     EelBackground *background)
101 {
102     caja_background_load_from_file_metadata (file, background);
103 }
104 
105 /* handle the theme changing */
106 static void
caja_background_theme_notify_cb(GSettings * settings,const gchar * key,gpointer user_data)107 caja_background_theme_notify_cb (GSettings   *settings,
108                                  const gchar *key,
109                                  gpointer     user_data)
110 {
111     CajaFile *file;
112     EelBackground *background = EEL_BACKGROUND (user_data);
113 
114     file = g_object_get_data (G_OBJECT (background), "eel_background_file");
115 
116     if (file)
117         caja_background_settings_notify_cb (file, background);
118 }
119 
120 /* handle the background changed signal */
121 static void
caja_background_changed_cb(EelBackground * background,GdkDragAction action,CajaFile * file)122 caja_background_changed_cb (EelBackground *background,
123                             GdkDragAction  action,
124                             CajaFile   *file)
125 {
126     g_assert (EEL_IS_BACKGROUND (background));
127     g_assert (CAJA_IS_FILE (file));
128     g_assert (g_object_get_data (G_OBJECT (background), "eel_background_file") == file);
129 
130     char *color = eel_background_get_color (background);
131     char *image = eel_background_get_image_uri (background);
132 
133     /* Block the other handler while we are writing metadata so it doesn't
134      * try to change the background.
135      */
136     g_signal_handlers_block_by_func (file, G_CALLBACK (caja_background_settings_notify_cb),
137                                      background);
138 
139     if (action != (GdkDragAction) CAJA_DND_ACTION_SET_AS_FOLDER_BACKGROUND &&
140             action != (GdkDragAction) CAJA_DND_ACTION_SET_AS_GLOBAL_BACKGROUND)
141     {
142         action = (GdkDragAction) GPOINTER_TO_INT (g_object_get_data (G_OBJECT (background),
143                                                   "default_drag_action"));
144     }
145 
146     if (action == (GdkDragAction) CAJA_DND_ACTION_SET_AS_GLOBAL_BACKGROUND)
147     {
148         caja_file_set_metadata (file, CAJA_METADATA_KEY_LOCATION_BACKGROUND_COLOR, NULL, NULL);
149         caja_file_set_metadata (file, CAJA_METADATA_KEY_LOCATION_BACKGROUND_IMAGE, NULL, NULL);
150 
151         g_signal_handlers_block_by_func (caja_preferences,
152                                          G_CALLBACK (caja_background_theme_notify_cb),
153                                          background);
154 
155         g_settings_set_string (caja_preferences,
156                                CAJA_PREFERENCES_BACKGROUND_COLOR, color ? color : "");
157         g_settings_set_string (caja_preferences,
158                                CAJA_PREFERENCES_BACKGROUND_URI, image ? image : "");
159 
160         g_settings_set_boolean (caja_preferences, CAJA_PREFERENCES_BACKGROUND_SET, TRUE);
161 
162         g_signal_handlers_unblock_by_func (caja_preferences,
163                                            G_CALLBACK (caja_background_theme_notify_cb),
164                                            background);
165     } else {
166         caja_file_set_metadata (file, CAJA_METADATA_KEY_LOCATION_BACKGROUND_COLOR, NULL, color);
167         caja_file_set_metadata (file, CAJA_METADATA_KEY_LOCATION_BACKGROUND_IMAGE, NULL, image);
168     }
169 
170     /* Unblock the handler. */
171     g_signal_handlers_unblock_by_func (file, G_CALLBACK (caja_background_settings_notify_cb),
172                                        background);
173 
174     g_free (color);
175     g_free (image);
176 }
177 
178 /* handle the background reset signal by setting values from the current theme */
179 static void
caja_background_reset_cb(EelBackground * background,CajaFile * file)180 caja_background_reset_cb (EelBackground *background,
181                           CajaFile  *file)
182 {
183     char *color, *image;
184 
185     /* Block the other handler while we are writing metadata so it doesn't
186      * try to change the background.
187      */
188     g_signal_handlers_block_by_func (file, G_CALLBACK (caja_background_settings_notify_cb),
189                                      background);
190 
191     color = caja_file_get_metadata (file, CAJA_METADATA_KEY_LOCATION_BACKGROUND_COLOR, NULL);
192     image = caja_file_get_metadata (file, CAJA_METADATA_KEY_LOCATION_BACKGROUND_IMAGE, NULL);
193     if (!color && !image)
194     {
195         g_signal_handlers_block_by_func (caja_preferences,
196                                          G_CALLBACK (caja_background_theme_notify_cb),
197                                          background);
198         g_settings_set_boolean (caja_preferences, CAJA_PREFERENCES_BACKGROUND_SET, FALSE);
199         g_signal_handlers_unblock_by_func (caja_preferences,
200                                            G_CALLBACK (caja_background_theme_notify_cb),
201                                            background);
202     }
203     else
204     {
205         /* reset the metadata */
206         caja_file_set_metadata (file, CAJA_METADATA_KEY_LOCATION_BACKGROUND_COLOR, NULL, NULL);
207         caja_file_set_metadata (file, CAJA_METADATA_KEY_LOCATION_BACKGROUND_IMAGE, NULL, NULL);
208     }
209     g_free (color);
210     g_free (image);
211 
212     /* Unblock the handler. */
213     g_signal_handlers_unblock_by_func (file, G_CALLBACK (caja_background_settings_notify_cb),
214                                        background);
215 
216     caja_background_settings_notify_cb (file, background);
217 }
218 
219 /* handle the background destroyed signal */
220 static void
caja_background_weak_notify(gpointer data,GObject * background)221 caja_background_weak_notify (gpointer data,
222                              GObject *background)
223 {
224     CajaFile *file = CAJA_FILE (data);
225 
226     g_signal_handlers_disconnect_by_func (file, G_CALLBACK (caja_background_settings_notify_cb),
227                                           background);
228     caja_file_monitor_remove (file, background);
229     g_signal_handlers_disconnect_by_func (caja_preferences, caja_background_theme_notify_cb,
230                                           background);
231 }
232 
233 /* key routine that hooks up a background and location */
234 void
caja_connect_background_to_file_metadata(GtkWidget * widget,CajaFile * file,GdkDragAction default_drag_action)235 caja_connect_background_to_file_metadata (GtkWidget     *widget,
236                                           CajaFile      *file,
237                                           GdkDragAction  default_drag_action)
238 {
239     EelBackground *background;
240     gpointer old_file;
241 
242     /* Get at the background object we'll be connecting. */
243     background = eel_get_widget_background (widget);
244 
245     /* Check if it is already connected. */
246     old_file = g_object_get_data (G_OBJECT (background), "eel_background_file");
247     if (old_file == file)
248         return;
249 
250     /* Disconnect old signal handlers. */
251     if (old_file != NULL)
252     {
253         g_assert (CAJA_IS_FILE (old_file));
254 
255         g_signal_handlers_disconnect_by_func (background,
256                                               G_CALLBACK (caja_background_changed_cb), old_file);
257         g_signal_handlers_disconnect_by_func (background,
258                                               G_CALLBACK (caja_background_reset_cb), old_file);
259 
260         g_object_weak_unref (G_OBJECT (background), caja_background_weak_notify, old_file);
261 
262         g_signal_handlers_disconnect_by_func (old_file,
263                                               G_CALLBACK (caja_background_settings_notify_cb),
264                                               background);
265 
266         caja_file_monitor_remove (old_file, background);
267 
268         g_signal_handlers_disconnect_by_func (caja_preferences, caja_background_theme_notify_cb,
269                                               background);
270     }
271 
272     /* Attach the new directory. */
273     caja_file_ref (file);
274     g_object_set_data_full (G_OBJECT (background), "eel_background_file",
275                             file, (GDestroyNotify) caja_file_unref);
276 
277     g_object_set_data (G_OBJECT (background), "default_drag_action",
278                        GINT_TO_POINTER (default_drag_action));
279 
280     /* Connect new signal handlers. */
281     if (file != NULL)
282     {
283         g_signal_connect_object (background, "settings_changed",
284                                  G_CALLBACK (caja_background_changed_cb), file, 0);
285 
286         g_signal_connect_object (background, "reset",
287                                  G_CALLBACK (caja_background_reset_cb), file, 0);
288 
289         g_signal_connect_object (file, "changed",
290                                  G_CALLBACK (caja_background_settings_notify_cb), background, 0);
291 
292         g_object_weak_ref (G_OBJECT (background), caja_background_weak_notify, file);
293 
294         /* arrange to receive file metadata */
295         caja_file_monitor_add (file, background, CAJA_FILE_ATTRIBUTE_INFO);
296 
297         /* arrange for notification when the theme changes */
298         g_signal_connect (caja_preferences, "changed::" CAJA_PREFERENCES_BACKGROUND_SET,
299                           G_CALLBACK(caja_background_theme_notify_cb), background);
300         g_signal_connect (caja_preferences, "changed::" CAJA_PREFERENCES_BACKGROUND_COLOR,
301                           G_CALLBACK(caja_background_theme_notify_cb), background);
302         g_signal_connect (caja_preferences, "changed::" CAJA_PREFERENCES_BACKGROUND_URI,
303                           G_CALLBACK(caja_background_theme_notify_cb), background);
304     }
305 
306     /* Update the background based on the file metadata. */
307     caja_background_load_from_file_metadata (file, background);
308 }
309 
310 /**
311  * DESKTOP BACKGROUND HANDLING
312  */
313 
314 /* handle the desktop background "settings_changed" signal */
315 static void
desktop_background_changed_cb(EelBackground * background,GdkDragAction action,gpointer user_data)316 desktop_background_changed_cb (EelBackground *background,
317                                GdkDragAction  action,
318                                gpointer       user_data)
319 {
320     eel_bg_save_to_gsettings (background,
321                               mate_background_preferences);
322 }
323 
324 /* delayed initializor of desktop background after GSettings changes */
325 static gboolean
desktop_background_prefs_change_event_idle_cb(EelBackground * background)326 desktop_background_prefs_change_event_idle_cb (EelBackground *background)
327 {
328     gchar *desktop_color = NULL;
329 
330     eel_bg_load_from_gsettings (background,
331                                 mate_background_preferences);
332 
333     desktop_color = eel_bg_get_desktop_color (background);
334     eel_background_set_color (background, desktop_color);
335 
336     g_free(desktop_color);
337     g_object_unref (background);
338 
339     return FALSE;       /* remove from the list of event sources */
340 }
341 
342 /* handle the desktop background "reset" signal: reset to schema's defaults */
343 static void
desktop_background_reset_cb(EelBackground * background,gpointer user_data)344 desktop_background_reset_cb (EelBackground *background,
345                              gpointer       user_data)
346 {
347     /* Reset to defaults, and save */
348     eel_bg_load_from_system_gsettings (background,
349                                        mate_background_preferences,
350                                        TRUE);
351     /* Reload from saved settings */
352     g_idle_add ((GSourceFunc) desktop_background_prefs_change_event_idle_cb,
353                 g_object_ref (background));
354 }
355 
356 /* handle the desktop GSettings "change-event" (batch changes) signal */
357 static gboolean
desktop_background_prefs_change_event_cb(GSettings * settings,gpointer keys,gint n_keys,gpointer user_data)358 desktop_background_prefs_change_event_cb (GSettings *settings,
359                                           gpointer   keys,
360                                           gint       n_keys,
361                                           gpointer   user_data)
362 {
363     EelBackground *background = user_data;
364 
365     /* Defer signal processing to avoid making the dconf backend deadlock, and
366      * hold a ref to avoid accessing fields of an object that was destroyed.
367      */
368     g_idle_add ((GSourceFunc) desktop_background_prefs_change_event_idle_cb,
369                 g_object_ref (background));
370 
371     return FALSE;       /* let the event propagate further */
372 }
373 
374 static void
desktop_background_weak_notify(gpointer data,GObject * object)375 desktop_background_weak_notify (gpointer data,
376                                 GObject *object)
377 {
378     g_signal_handlers_disconnect_by_func (mate_background_preferences,
379                                           G_CALLBACK (desktop_background_prefs_change_event_cb),
380                                           object);
381 }
382 
383 void
caja_connect_desktop_background_to_settings(CajaIconContainer * icon_container)384 caja_connect_desktop_background_to_settings (CajaIconContainer *icon_container)
385 {
386     EelBackground *background;
387 
388     background = eel_get_widget_background (GTK_WIDGET (icon_container));
389 
390     eel_background_set_desktop (background, TRUE);
391 
392     g_signal_connect_object (background, "settings_changed",
393                              G_CALLBACK (desktop_background_changed_cb), NULL, 0);
394 
395     g_signal_connect_object (background, "reset",
396                              G_CALLBACK (desktop_background_reset_cb), NULL, 0);
397 
398     eel_bg_load_from_gsettings (background,
399                                 mate_background_preferences);
400 
401     /* Connect to "change-event" signal to receive *groups of changes* before
402      * they are split out into multiple emissions of the "changed" signal.
403      */
404     g_signal_connect (mate_background_preferences, "change-event",
405                       G_CALLBACK (desktop_background_prefs_change_event_cb),
406                       background);
407 
408     g_object_weak_ref (G_OBJECT (background),
409                        desktop_background_weak_notify, NULL);
410 }
411