1 /* -*- mode: C; c-basic-offset: 4 -*-
2  * Drive Mount Applet
3  * Copyright (c) 2004 Canonical Ltd
4  * Copyright 2008 Pierre Ossman
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (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 St, Fifth Floor, Boston, MA  02110-1301  USA
19  *
20  * Author:
21  *   James Henstridge <jamesh@canonical.com>
22  */
23 
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27 
28 #include <gio/gio.h>
29 #include "drive-list.h"
30 #include "drive-button.h"
31 #include <glib/gi18n.h>
32 
33 G_DEFINE_TYPE (DriveList, drive_list, GTK_TYPE_GRID);
34 
35 static GVolumeMonitor *volume_monitor = NULL;
36 
37 static void drive_list_finalize (GObject *object);
38 static void drive_list_dispose  (GObject *object);
39 static void drive_list_add      (GtkContainer *container, GtkWidget *child);
40 static void drive_list_remove   (GtkContainer *container, GtkWidget *child);
41 
42 static void mount_added         (GVolumeMonitor *monitor, GMount *mount, DriveList *self);
43 static void mount_changed       (GVolumeMonitor *monitor, GMount *mount, DriveList *self);
44 static void mount_removed       (GVolumeMonitor *monitor, GMount *mount, DriveList *self);
45 static void volume_added        (GVolumeMonitor *monitor, GVolume *volume, DriveList *self);
46 static void volume_changed      (GVolumeMonitor *monitor, GVolume *volume, DriveList *self);
47 static void volume_removed      (GVolumeMonitor *monitor, GVolume *volume, DriveList *self);
48 static void add_volume          (DriveList *self, GVolume *volume);
49 static void remove_volume       (DriveList *self, GVolume *volume);
50 static void add_mount           (DriveList *self, GMount *mount);
51 static void remove_mount        (DriveList *self, GMount *mount);
52 static void queue_relayout      (DriveList *self);
53 
54 static void
drive_list_class_init(DriveListClass * class)55 drive_list_class_init (DriveListClass *class)
56 {
57     G_OBJECT_CLASS (class)->finalize = drive_list_finalize;
58     G_OBJECT_CLASS (class)->dispose = drive_list_dispose;
59     GTK_CONTAINER_CLASS (class)->add = drive_list_add;
60     GTK_CONTAINER_CLASS (class)->remove = drive_list_remove;
61 }
62 
63 static void
drive_list_init(DriveList * self)64 drive_list_init (DriveList *self)
65 {
66     GList *volumes, *mounts, *tmp;
67 
68     gtk_grid_set_column_homogeneous (GTK_GRID (self), TRUE);
69     gtk_grid_set_row_homogeneous (GTK_GRID (self), TRUE);
70 
71     self->volumes = g_hash_table_new (NULL, NULL);
72     self->mounts = g_hash_table_new (NULL, NULL);
73     self->orientation = GTK_ORIENTATION_HORIZONTAL;
74     self->layout_tag = 0;
75     self->settings = g_settings_new ("org.mate.drivemount");
76     self->icon_size = 24;
77     self->relief = GTK_RELIEF_NORMAL;
78 
79     g_signal_connect (self->settings,
80                       "changed::drivemount-checkmark-color",
81                       G_CALLBACK (settings_color_changed),
82                       self);
83 
84     /* listen for drive connects/disconnects, and add
85      * currently connected drives. */
86     self->count = 0;
87     if (!volume_monitor)
88         volume_monitor = g_volume_monitor_get ();
89 
90     g_signal_connect_object (volume_monitor, "mount_added",
91                              G_CALLBACK (mount_added), self, 0);
92     g_signal_connect_object (volume_monitor, "mount_changed",
93                              G_CALLBACK (mount_changed), self, 0);
94     g_signal_connect_object (volume_monitor, "mount_removed",
95                              G_CALLBACK (mount_removed), self, 0);
96     g_signal_connect_object (volume_monitor, "volume_added",
97                              G_CALLBACK (volume_added), self, 0);
98     g_signal_connect_object (volume_monitor, "volume_changed",
99                              G_CALLBACK (volume_changed), self, 0);
100     g_signal_connect_object (volume_monitor, "volume_removed",
101                              G_CALLBACK (volume_removed), self, 0);
102     volumes = g_volume_monitor_get_volumes (volume_monitor);
103     for (tmp = volumes; tmp != NULL; tmp = tmp->next) {
104         GVolume *volume = tmp->data;
105         add_volume (self, volume);
106         g_object_unref (volume);
107         self->count++;
108     }
109     g_list_free (volumes);
110 
111     mounts = g_volume_monitor_get_mounts (volume_monitor);
112     for (tmp = mounts; tmp != NULL; tmp = tmp->next) {
113         GMount *mount = tmp->data;
114         add_mount (self, mount);
115         g_object_unref (mount);
116         self->count++;
117     }
118     self->dummy = drive_button_new (NULL);
119     gtk_button_set_relief (GTK_BUTTON (self->dummy), self->relief);
120     drive_button_set_size (DRIVE_BUTTON (self->dummy), self->icon_size);
121 
122     if (self->count == 0) {
123         gtk_container_add (GTK_CONTAINER (self), self->dummy);
124         queue_relayout (self);
125         drive_button_queue_update (DRIVE_BUTTON (self->dummy));
126     }
127     g_list_free (mounts);
128 }
129 
130 GtkWidget *
drive_list_new(void)131 drive_list_new (void)
132 {
133     return g_object_new (DRIVE_TYPE_LIST, NULL);
134 }
135 
136 static void
drive_list_finalize(GObject * object)137 drive_list_finalize (GObject *object)
138 {
139     DriveList *self = DRIVE_LIST (object);
140 
141     g_hash_table_destroy (self->volumes);
142     g_hash_table_destroy (self->mounts);
143     g_object_unref (self->settings);
144 
145     if (G_OBJECT_CLASS (drive_list_parent_class)->finalize)
146         (* G_OBJECT_CLASS (drive_list_parent_class)->finalize) (object);
147 }
148 
149 static void
drive_list_dispose(GObject * object)150 drive_list_dispose (GObject *object)
151 {
152     DriveList *self = DRIVE_LIST (object);
153 
154     g_signal_handlers_disconnect_by_func (volume_monitor,
155                                           G_CALLBACK (mount_added), self);
156     g_signal_handlers_disconnect_by_func (volume_monitor,
157                                           G_CALLBACK (mount_changed), self);
158     g_signal_handlers_disconnect_by_func (volume_monitor,
159                                           G_CALLBACK (mount_removed), self);
160     g_signal_handlers_disconnect_by_func (volume_monitor,
161                                           G_CALLBACK (volume_added), self);
162     g_signal_handlers_disconnect_by_func (volume_monitor,
163                                           G_CALLBACK (volume_changed), self);
164     g_signal_handlers_disconnect_by_func (volume_monitor,
165                                           G_CALLBACK (volume_removed), self);
166 
167     if (self->layout_tag)
168         g_source_remove (self->layout_tag);
169     self->layout_tag = 0;
170 
171     if (G_OBJECT_CLASS (drive_list_parent_class)->dispose)
172         (* G_OBJECT_CLASS (drive_list_parent_class)->dispose) (object);
173 }
174 
175 static void
drive_list_add(GtkContainer * container,GtkWidget * child)176 drive_list_add (GtkContainer *container,
177                 GtkWidget    *child)
178 {
179     DriveList *self;
180     DriveButton *button;
181 
182     g_return_if_fail (DRIVE_IS_LIST (container));
183     g_return_if_fail (DRIVE_IS_BUTTON (child));
184 
185     if (GTK_CONTAINER_CLASS (drive_list_parent_class)->add)
186         (* GTK_CONTAINER_CLASS (drive_list_parent_class)->add) (container, child);
187 
188     self = DRIVE_LIST (container);
189     button = DRIVE_BUTTON (child);
190     if (button->volume)
191         g_hash_table_insert (self->volumes, button->volume, button);
192     else
193         g_hash_table_insert (self->mounts, button->mount, button);
194 }
195 
196 static void
drive_list_remove(GtkContainer * container,GtkWidget * child)197 drive_list_remove (GtkContainer *container,
198                    GtkWidget    *child)
199 {
200     DriveList *self;
201     DriveButton *button;
202 
203     g_return_if_fail (DRIVE_IS_LIST (container));
204     g_return_if_fail (DRIVE_IS_BUTTON (child));
205 
206     self = DRIVE_LIST (container);
207     button = DRIVE_BUTTON (child);
208     if (button->volume)
209         g_hash_table_remove (self->volumes, button->volume);
210     else
211         g_hash_table_remove (self->mounts, button->mount);
212 
213     if (GTK_CONTAINER_CLASS (drive_list_parent_class)->remove)
214         (* GTK_CONTAINER_CLASS (drive_list_parent_class)->remove) (container, child);
215 }
216 
217 static void
list_buttons(gpointer key,gpointer value,gpointer user_data)218 list_buttons (gpointer key,
219               gpointer value,
220               gpointer user_data)
221 {
222     GtkWidget *button = value;
223     GList **sorted_buttons = user_data;
224 
225     *sorted_buttons = g_list_insert_sorted (*sorted_buttons, button,
226                                             (GCompareFunc)drive_button_compare);
227 }
228 
229 static gboolean
relayout_buttons(gpointer data)230 relayout_buttons (gpointer data)
231 {
232     DriveList *self = DRIVE_LIST (data);
233     GList *sorted_buttons = NULL, *tmp;
234     int i = 0;
235 
236     self->layout_tag = 0;
237     if ( self->count > 0 ) {
238         g_hash_table_foreach (self->volumes, list_buttons, &sorted_buttons);
239         g_hash_table_foreach (self->mounts, list_buttons, &sorted_buttons);
240 
241         /* position buttons in the table according to their sorted order */
242         for (tmp = sorted_buttons, i = 0; tmp != NULL; tmp = tmp->next, i++) {
243             GtkWidget *button = tmp->data;
244 
245             if (self->orientation == GTK_ORIENTATION_HORIZONTAL) {
246                 gtk_container_child_set (GTK_CONTAINER (self), button,
247                                          "left-attach", i + 1, "top-attach", 0,
248                                          "width", 1, "height", 1,
249                                          NULL);
250             }
251             else {
252                 gtk_container_child_set (GTK_CONTAINER (self), button,
253                                          "left-attach", 0, "top-attach", i + 1,
254                                          "width", 1, "height", 1,
255                                          NULL);
256             }
257         }
258     }
259     else {
260         gtk_widget_show (self->dummy);
261         if (self->orientation == GTK_ORIENTATION_HORIZONTAL) {
262             gtk_container_child_set (GTK_CONTAINER (self), self->dummy,
263                                      "left-attach", i + 1, "top-attach", 0,
264                                      "width", 1, "height", 1,
265                                      NULL);
266         }
267         else {
268             gtk_container_child_set (GTK_CONTAINER (self), self->dummy,
269                                      "left-attach", 0, "top-attach", i + 1,
270                                      "width", 1, "height", 1,
271                                      NULL);
272         }
273     }
274     return FALSE;
275 }
276 
277 static void
queue_relayout(DriveList * self)278 queue_relayout (DriveList *self)
279 {
280     if (!self->layout_tag) {
281         self->layout_tag = g_idle_add (relayout_buttons, self);
282     }
283 }
284 
285 static void
mount_added(GVolumeMonitor * monitor,GMount * mount,DriveList * self)286 mount_added (GVolumeMonitor *monitor,
287              GMount         *mount,
288              DriveList      *self)
289 {
290     add_mount (self, mount);
291     self->count++;
292     if (self->count == 1)
293         gtk_container_remove (GTK_CONTAINER (self), g_object_ref (self->dummy));
294 
295     mount_changed (monitor, mount, self);
296 }
297 
298 static void
mount_changed(GVolumeMonitor * monitor,GMount * mount,DriveList * self)299 mount_changed (GVolumeMonitor *monitor,
300                GMount         *mount,
301                DriveList      *self)
302 {
303     GVolume *volume;
304     DriveButton *button = NULL;;
305 
306     volume = g_mount_get_volume (mount);
307     if (volume) {
308         button = g_hash_table_lookup (self->volumes, volume);
309         g_object_unref (volume);
310     } else {
311         button = g_hash_table_lookup (self->mounts, mount);
312     }
313     if (button)
314         drive_button_queue_update (button);
315 }
316 
317 static void
mount_removed(GVolumeMonitor * monitor,GMount * mount,DriveList * self)318 mount_removed (GVolumeMonitor *monitor,
319                GMount         *mount,
320                DriveList      *self)
321 {
322     remove_mount (self, mount);
323     mount_changed (monitor, mount, self);
324     self->count--;
325     if (self->count == 0) {
326         gtk_container_add (GTK_CONTAINER (self), self->dummy);
327         queue_relayout (self);
328     }
329 }
330 
331 static void
volume_added(GVolumeMonitor * monitor,GVolume * volume,DriveList * self)332 volume_added (GVolumeMonitor *monitor,
333               GVolume        *volume,
334               DriveList      *self)
335 {
336     add_volume (self, volume);
337     self->count++;
338     if (self->count == 1)
339         gtk_container_remove (GTK_CONTAINER (self), g_object_ref (self->dummy));
340 }
341 
342 static void
volume_changed(GVolumeMonitor * monitor,GVolume * volume,DriveList * self)343 volume_changed (GVolumeMonitor *monitor,
344                 GVolume        *volume,
345                 DriveList      *self)
346 {
347     DriveButton *button = NULL;;
348 
349     button = g_hash_table_lookup (self->volumes, volume);
350     if (button)
351         drive_button_queue_update (button);
352 }
353 
354 static void
volume_removed(GVolumeMonitor * monitor,GVolume * volume,DriveList * self)355 volume_removed (GVolumeMonitor *monitor,
356                 GVolume        *volume,
357                 DriveList      *self)
358 {
359     remove_volume (self, volume);
360     self->count--;
361     if (self->count == 0) {
362         gtk_container_add (GTK_CONTAINER (self), self->dummy);
363         queue_relayout (self);
364     }
365 }
366 
367 static void
add_volume(DriveList * self,GVolume * volume)368 add_volume (DriveList *self,
369             GVolume   *volume)
370 {
371     GtkWidget *button;
372 
373     /* if the volume has already been added, return */
374     if (g_hash_table_lookup (self->volumes, volume) != NULL)
375         return;
376 
377     button = drive_button_new (volume);
378     gtk_button_set_relief (GTK_BUTTON (button), self->relief);
379     drive_button_set_size (DRIVE_BUTTON (button), self->icon_size);
380     gtk_container_add (GTK_CONTAINER (self), button);
381     gtk_widget_show (button);
382     queue_relayout (self);
383 }
384 
385 static void
remove_volume(DriveList * self,GVolume * volume)386 remove_volume (DriveList *self,
387                GVolume   *volume)
388 {
389     GtkWidget *button;
390 
391     /* if the volume has already been added, return */
392     button = g_hash_table_lookup (self->volumes, volume);
393     if (button) {
394         gtk_container_remove (GTK_CONTAINER (self), button);
395         queue_relayout (self);
396     }
397 }
398 
399 static void
add_mount(DriveList * self,GMount * mount)400 add_mount (DriveList *self,
401            GMount    *mount)
402 {
403     GtkWidget *button;
404     GVolume *volume;
405 
406     /* ignore mounts reported as shadowed */
407     if (g_mount_is_shadowed (mount)) {
408         return;
409     }
410 
411     /* ignore mounts attached to a volume */
412     volume = g_mount_get_volume (mount);
413     if (volume) {
414         g_object_unref (volume);
415         return;
416     }
417 
418     /* if the mount has already been added, return */
419     if (g_hash_table_lookup (self->mounts, mount) != NULL)
420         return;
421 
422     button = drive_button_new_from_mount (mount);
423     gtk_button_set_relief (GTK_BUTTON (button), self->relief);
424     drive_button_set_size (DRIVE_BUTTON (button), self->icon_size);
425     gtk_container_add (GTK_CONTAINER (self), button);
426     gtk_widget_show (button);
427     queue_relayout (self);
428 }
429 
430 static void
remove_mount(DriveList * self,GMount * mount)431 remove_mount (DriveList *self,
432               GMount    *mount)
433 {
434     GtkWidget *button;
435 
436     /* if the mount has already been added, return */
437     button = g_hash_table_lookup (self->mounts, mount);
438     if (button) {
439         gtk_container_remove (GTK_CONTAINER (self), button);
440         queue_relayout (self);
441     }
442 }
443 
444 void
drive_list_set_orientation(DriveList * self,GtkOrientation orientation)445 drive_list_set_orientation (DriveList      *self,
446                             GtkOrientation  orientation)
447 {
448     g_return_if_fail (DRIVE_IS_LIST (self));
449 
450     if (orientation != self->orientation) {
451         self->orientation = orientation;
452         queue_relayout (self);
453     }
454 }
455 
456 static void
set_icon_size(gpointer key,gpointer value,gpointer user_data)457 set_icon_size (gpointer key,
458                gpointer value,
459                gpointer user_data)
460 {
461     DriveButton *button = value;
462     DriveList *self = user_data;
463 
464     drive_button_set_size (button, self->icon_size);
465 }
466 
467 
468 void
drive_list_set_panel_size(DriveList * self,int panel_size)469 drive_list_set_panel_size (DriveList *self,
470                            int        panel_size)
471 {
472     g_return_if_fail (DRIVE_IS_LIST (self));
473 
474     if (self->icon_size != panel_size) {
475         self->icon_size = panel_size;
476         g_hash_table_foreach (self->volumes, set_icon_size, self);
477         g_hash_table_foreach (self->mounts, set_icon_size, self);
478     }
479 }
480 
481 void
drive_list_redraw(DriveList * self)482 drive_list_redraw (DriveList *self)
483 {
484     g_hash_table_foreach (self->volumes, drive_button_redraw, self);
485     g_hash_table_foreach (self->mounts, drive_button_redraw, self);
486 }
487 
488 void
settings_color_changed(GSettings * settings,gchar * key,DriveList * drive_list)489 settings_color_changed (GSettings *settings,
490                         gchar     *key,
491                         DriveList *drive_list)
492 {
493     g_return_if_fail (DRIVE_IS_LIST (drive_list));
494     drive_list_redraw (drive_list);
495 }
496 
497 static void
set_button_relief(gpointer key,gpointer value,gpointer user_data)498 set_button_relief (gpointer key,
499                    gpointer value,
500                    gpointer user_data)
501 {
502     GtkButton *button = value;
503     DriveList *self = user_data;
504 
505     gtk_button_set_relief (button, self->relief);
506 }
507 
508 void
drive_list_set_transparent(DriveList * self,gboolean transparent)509 drive_list_set_transparent (DriveList *self,
510                             gboolean   transparent)
511 {
512     GtkReliefStyle relief;
513 
514     relief  = transparent ? GTK_RELIEF_NONE : GTK_RELIEF_NORMAL;
515 
516     if (relief == self->relief)
517         return;
518 
519     self->relief = relief;
520     g_hash_table_foreach (self->volumes, set_button_relief, self);
521     g_hash_table_foreach (self->mounts, set_button_relief, self);
522 }
523