1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 
3 /* caja-column-chooser.h - A column chooser widget
4 
5    Copyright (C) 2004 Novell, Inc.
6 
7    The Mate Library is free software; you can redistribute it and/or
8    modify it under the terms of the GNU Library General Public License as
9    published by the Free Software Foundation; either version 2 of the
10    License, or (at your option) any later version.
11 
12    The Mate Library 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 GNU
15    Library General Public License for more details.
16 
17    You should have received a copy of the GNU Library General Public
18    License along with the Mate Library; see the column COPYING.LIB.  If not,
19    write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
20    Boston, MA 02110-1301, USA.
21 
22    Authors: Dave Camp <dave@ximian.com>
23 */
24 
25 #include <config.h>
26 #include "caja-column-chooser.h"
27 
28 #include <string.h>
29 #include <gtk/gtk.h>
30 #include <glib/gi18n.h>
31 
32 #include "caja-column-utilities.h"
33 
34 struct _CajaColumnChooserPrivate
35 {
36     GtkTreeView *view;
37     GtkListStore *store;
38 
39     GtkWidget *move_up_button;
40     GtkWidget *move_down_button;
41     GtkWidget *use_default_button;
42 
43     CajaFile *file;
44 };
45 
46 enum
47 {
48     COLUMN_VISIBLE,
49     COLUMN_LABEL,
50     COLUMN_NAME,
51     NUM_COLUMNS
52 };
53 
54 enum
55 {
56     PROP_FILE = 1,
57     NUM_PROPERTIES
58 };
59 
60 enum
61 {
62     CHANGED,
63     USE_DEFAULT,
64     LAST_SIGNAL
65 };
66 static guint signals[LAST_SIGNAL] = { 0 };
67 
68 G_DEFINE_TYPE_WITH_PRIVATE (CajaColumnChooser, caja_column_chooser, GTK_TYPE_BOX);
69 
70 static void caja_column_chooser_constructed (GObject *object);
71 
72 static void
caja_column_chooser_set_property(GObject * object,guint param_id,const GValue * value,GParamSpec * pspec)73 caja_column_chooser_set_property (GObject *object,
74                                   guint param_id,
75                                   const GValue *value,
76                                   GParamSpec *pspec)
77 {
78     CajaColumnChooser *chooser;
79 
80     chooser = CAJA_COLUMN_CHOOSER (object);
81 
82     switch (param_id)
83     {
84     case PROP_FILE:
85         chooser->details->file = g_value_get_object (value);
86         break;
87     default:
88         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
89         break;
90     }
91 }
92 
93 static void
caja_column_chooser_class_init(CajaColumnChooserClass * chooser_class)94 caja_column_chooser_class_init (CajaColumnChooserClass *chooser_class)
95 {
96     GObjectClass *oclass;
97 
98     oclass = G_OBJECT_CLASS (chooser_class);
99 
100     oclass->set_property = caja_column_chooser_set_property;
101     oclass->constructed = caja_column_chooser_constructed;
102 
103     signals[CHANGED] = g_signal_new
104                        ("changed",
105                         G_TYPE_FROM_CLASS (chooser_class),
106                         G_SIGNAL_RUN_LAST,
107                         G_STRUCT_OFFSET (CajaColumnChooserClass,
108                                          changed),
109                         NULL, NULL,
110                         g_cclosure_marshal_VOID__VOID,
111                         G_TYPE_NONE, 0);
112 
113     signals[USE_DEFAULT] = g_signal_new
114                            ("use_default",
115                             G_TYPE_FROM_CLASS (chooser_class),
116                             G_SIGNAL_RUN_LAST,
117                             G_STRUCT_OFFSET (CajaColumnChooserClass,
118                                     use_default),
119                             NULL, NULL,
120                             g_cclosure_marshal_VOID__VOID,
121                             G_TYPE_NONE, 0);
122 
123     g_object_class_install_property (oclass,
124                                      PROP_FILE,
125                                      g_param_spec_object ("file",
126                                              "File",
127                                              "The file this column chooser is for",
128                                              CAJA_TYPE_FILE,
129                                              G_PARAM_CONSTRUCT_ONLY |
130                                              G_PARAM_WRITABLE));
131 }
132 
133 static void
update_buttons(CajaColumnChooser * chooser)134 update_buttons (CajaColumnChooser *chooser)
135 {
136     GtkTreeSelection *selection;
137     GtkTreeIter iter;
138 
139     selection = gtk_tree_view_get_selection (chooser->details->view);
140 
141     if (gtk_tree_selection_get_selected (selection, NULL, &iter))
142     {
143         gboolean visible;
144         gboolean top;
145         gboolean bottom;
146         GtkTreePath *first;
147         GtkTreePath *path;
148 
149         gtk_tree_model_get (GTK_TREE_MODEL (chooser->details->store),
150                             &iter,
151                             COLUMN_VISIBLE, &visible,
152                             -1);
153 
154         path = gtk_tree_model_get_path (GTK_TREE_MODEL (chooser->details->store),
155                                         &iter);
156         first = gtk_tree_path_new_first ();
157 
158         top = (gtk_tree_path_compare (path, first) == 0);
159 
160         gtk_tree_path_free (path);
161         gtk_tree_path_free (first);
162 
163         bottom = !gtk_tree_model_iter_next (GTK_TREE_MODEL (chooser->details->store),
164                                             &iter);
165 
166         gtk_widget_set_sensitive (chooser->details->move_up_button,
167                                   !top);
168         gtk_widget_set_sensitive (chooser->details->move_down_button,
169                                   !bottom);
170     }
171     else
172     {
173         gtk_widget_set_sensitive (chooser->details->move_up_button,
174                                   FALSE);
175         gtk_widget_set_sensitive (chooser->details->move_down_button,
176                                   FALSE);
177     }
178 }
179 
180 static void
list_changed(CajaColumnChooser * chooser)181 list_changed (CajaColumnChooser *chooser)
182 {
183     update_buttons (chooser);
184     g_signal_emit (chooser, signals[CHANGED], 0);
185 }
186 
187 static void
visible_toggled_callback(GtkCellRendererToggle * cell,char * path_string,gpointer user_data)188 visible_toggled_callback (GtkCellRendererToggle *cell,
189                           char *path_string,
190                           gpointer user_data)
191 {
192     CajaColumnChooser *chooser;
193     GtkTreePath *path;
194     GtkTreeIter iter;
195     gboolean visible;
196 
197     chooser = CAJA_COLUMN_CHOOSER (user_data);
198 
199     path = gtk_tree_path_new_from_string (path_string);
200     gtk_tree_model_get_iter (GTK_TREE_MODEL (chooser->details->store),
201                              &iter, path);
202     gtk_tree_model_get (GTK_TREE_MODEL (chooser->details->store),
203                         &iter, COLUMN_VISIBLE, &visible, -1);
204     gtk_list_store_set (chooser->details->store,
205                         &iter, COLUMN_VISIBLE, !visible, -1);
206     gtk_tree_path_free (path);
207     list_changed (chooser);
208 }
209 
210 static void
selection_changed_callback(GtkTreeSelection * selection,gpointer user_data)211 selection_changed_callback (GtkTreeSelection *selection, gpointer user_data)
212 {
213     update_buttons (CAJA_COLUMN_CHOOSER (user_data));
214 }
215 
216 static void
row_deleted_callback(GtkTreeModel * model,GtkTreePath * path,gpointer user_data)217 row_deleted_callback (GtkTreeModel *model,
218                       GtkTreePath *path,
219                       gpointer user_data)
220 {
221     list_changed (CAJA_COLUMN_CHOOSER (user_data));
222 }
223 
224 static void
add_tree_view(CajaColumnChooser * chooser)225 add_tree_view (CajaColumnChooser *chooser)
226 {
227     GtkWidget *scrolled;
228     GtkWidget *view;
229     GtkListStore *store;
230     GtkCellRenderer *cell;
231     GtkTreeSelection *selection;
232 
233     view = gtk_tree_view_new ();
234     gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (view), FALSE);
235 
236     store = gtk_list_store_new (NUM_COLUMNS,
237                                 G_TYPE_BOOLEAN,
238                                 G_TYPE_STRING,
239                                 G_TYPE_STRING);
240 
241     gtk_tree_view_set_model (GTK_TREE_VIEW (view),
242                              GTK_TREE_MODEL (store));
243     g_object_unref (store);
244 
245     gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view), TRUE);
246 
247     selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
248     g_signal_connect (selection, "changed",
249                       G_CALLBACK (selection_changed_callback), chooser);
250 
251     cell = gtk_cell_renderer_toggle_new ();
252 
253     g_signal_connect (G_OBJECT (cell), "toggled",
254                       G_CALLBACK (visible_toggled_callback), chooser);
255 
256     gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view),
257             -1, NULL,
258             cell,
259             "active", COLUMN_VISIBLE,
260             NULL);
261 
262     cell = gtk_cell_renderer_text_new ();
263 
264     gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view),
265             -1, NULL,
266             cell,
267             "text", COLUMN_LABEL,
268             NULL);
269 
270     chooser->details->view = GTK_TREE_VIEW (view);
271     chooser->details->store = store;
272 
273     gtk_widget_show (view);
274 
275     scrolled = gtk_scrolled_window_new (NULL, NULL);
276     gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled),
277                                          GTK_SHADOW_IN);
278     gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
279                                     GTK_POLICY_AUTOMATIC,
280                                     GTK_POLICY_AUTOMATIC);
281     gtk_scrolled_window_set_overlay_scrolling (GTK_SCROLLED_WINDOW (scrolled), FALSE);
282 
283     gtk_widget_show (GTK_WIDGET (scrolled));
284 
285     gtk_container_add (GTK_CONTAINER (scrolled), view);
286     gtk_box_pack_start (GTK_BOX (chooser), scrolled, TRUE, TRUE, 0);
287 }
288 
289 static void
move_up_clicked_callback(GtkWidget * button,gpointer user_data)290 move_up_clicked_callback (GtkWidget *button, gpointer user_data)
291 {
292     CajaColumnChooser *chooser;
293     GtkTreeIter iter;
294     GtkTreeSelection *selection;
295 
296     chooser = CAJA_COLUMN_CHOOSER (user_data);
297 
298     selection = gtk_tree_view_get_selection (chooser->details->view);
299 
300     if (gtk_tree_selection_get_selected (selection, NULL, &iter))
301     {
302         GtkTreePath *path;
303         GtkTreeIter prev;
304 
305         path = gtk_tree_model_get_path (GTK_TREE_MODEL (chooser->details->store), &iter);
306         gtk_tree_path_prev (path);
307         if (gtk_tree_model_get_iter (GTK_TREE_MODEL (chooser->details->store), &prev, path))
308         {
309             gtk_list_store_move_before (chooser->details->store,
310                                         &iter,
311                                         &prev);
312         }
313         gtk_tree_path_free (path);
314     }
315 
316     list_changed (chooser);
317 }
318 
319 static void
move_down_clicked_callback(GtkWidget * button,gpointer user_data)320 move_down_clicked_callback (GtkWidget *button, gpointer user_data)
321 {
322     CajaColumnChooser *chooser;
323     GtkTreeIter iter;
324     GtkTreeSelection *selection;
325 
326     chooser = CAJA_COLUMN_CHOOSER (user_data);
327 
328     selection = gtk_tree_view_get_selection (chooser->details->view);
329 
330     if (gtk_tree_selection_get_selected (selection, NULL, &iter))
331     {
332         GtkTreeIter next;
333 
334         next = iter;
335 
336         if (gtk_tree_model_iter_next (GTK_TREE_MODEL (chooser->details->store), &next))
337         {
338             gtk_list_store_move_after (chooser->details->store,
339                                        &iter,
340                                        &next);
341         }
342     }
343 
344     list_changed (chooser);
345 }
346 
347 static void
use_default_clicked_callback(GtkWidget * button,gpointer user_data)348 use_default_clicked_callback (GtkWidget *button, gpointer user_data)
349 {
350     g_signal_emit (CAJA_COLUMN_CHOOSER (user_data),
351                    signals[USE_DEFAULT], 0);
352 }
353 
354 static GtkWidget *
button_new_with_mnemonic(const gchar * icon_name,const gchar * str)355 button_new_with_mnemonic (const gchar *icon_name, const gchar *str)
356 {
357     GtkWidget *image;
358     GtkWidget *button;
359 
360     button = gtk_button_new_with_mnemonic (str);
361     image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_BUTTON);
362 
363     gtk_button_set_image (GTK_BUTTON (button), image);
364 
365     return button;
366 }
367 
368 static void
add_buttons(CajaColumnChooser * chooser)369 add_buttons (CajaColumnChooser *chooser)
370 {
371     GtkWidget *box;
372     GtkWidget *separator;
373 
374     box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 8);
375     gtk_widget_show (box);
376 
377     chooser->details->move_up_button = button_new_with_mnemonic ("go-up",
378                                        _("Move _Up"));
379     g_signal_connect (chooser->details->move_up_button,
380                       "clicked",  G_CALLBACK (move_up_clicked_callback),
381                       chooser);
382     gtk_widget_show_all (chooser->details->move_up_button);
383     gtk_widget_set_sensitive (chooser->details->move_up_button, FALSE);
384     gtk_box_pack_start (GTK_BOX (box), chooser->details->move_up_button,
385                         FALSE, FALSE, 0);
386 
387     chooser->details->move_down_button = button_new_with_mnemonic ("go-down",
388                                          _("Move Dow_n"));
389     g_signal_connect (chooser->details->move_down_button,
390                       "clicked",  G_CALLBACK (move_down_clicked_callback),
391                       chooser);
392     gtk_widget_show_all (chooser->details->move_down_button);
393     gtk_widget_set_sensitive (chooser->details->move_down_button, FALSE);
394     gtk_box_pack_start (GTK_BOX (box), chooser->details->move_down_button,
395                         FALSE, FALSE, 0);
396 
397     separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
398     gtk_widget_show (separator);
399     gtk_box_pack_start (GTK_BOX (box), separator, FALSE, FALSE, 0);
400 
401     chooser->details->use_default_button = gtk_button_new_with_mnemonic (_("Use De_fault"));
402     g_signal_connect (chooser->details->use_default_button,
403                       "clicked",  G_CALLBACK (use_default_clicked_callback),
404                       chooser);
405     gtk_widget_show (chooser->details->use_default_button);
406     gtk_box_pack_start (GTK_BOX (box), chooser->details->use_default_button,
407                         FALSE, FALSE, 0);
408 
409     gtk_box_pack_start (GTK_BOX (chooser), box,
410                         FALSE, FALSE, 0);
411 }
412 
413 static void
populate_tree(CajaColumnChooser * chooser)414 populate_tree (CajaColumnChooser *chooser)
415 {
416     GList *columns;
417     GList *l;
418 
419     columns = caja_get_columns_for_file (chooser->details->file);
420 
421     for (l = columns; l != NULL; l = l->next)
422     {
423         GtkTreeIter iter;
424         CajaColumn *column;
425         char *name;
426         char *label;
427 
428         column = CAJA_COLUMN (l->data);
429 
430         g_object_get (G_OBJECT (column),
431                       "name", &name, "label", &label,
432                       NULL);
433 
434         gtk_list_store_append (chooser->details->store, &iter);
435         gtk_list_store_set (chooser->details->store, &iter,
436                             COLUMN_VISIBLE, FALSE,
437                             COLUMN_LABEL, label,
438                             COLUMN_NAME, name,
439                             -1);
440 
441         g_free (name);
442         g_free (label);
443     }
444 
445     caja_column_list_free (columns);
446 }
447 
448 static void
caja_column_chooser_constructed(GObject * object)449 caja_column_chooser_constructed (GObject *object)
450 {
451     CajaColumnChooser *chooser;
452 
453     chooser = CAJA_COLUMN_CHOOSER (object);
454 
455     populate_tree (chooser);
456 
457     g_signal_connect (chooser->details->store, "row_deleted",
458                       G_CALLBACK (row_deleted_callback), chooser);
459 }
460 
461 static void
caja_column_chooser_init(CajaColumnChooser * chooser)462 caja_column_chooser_init (CajaColumnChooser *chooser)
463 {
464     chooser->details = caja_column_chooser_get_instance_private (chooser);
465 
466     g_object_set (G_OBJECT (chooser),
467                   "homogeneous", FALSE,
468                   "spacing", 8,
469                   "orientation", GTK_ORIENTATION_HORIZONTAL,
470                   NULL);
471 
472     add_tree_view (chooser);
473     add_buttons (chooser);
474 }
475 
476 static void
set_visible_columns(CajaColumnChooser * chooser,char ** visible_columns)477 set_visible_columns (CajaColumnChooser *chooser,
478                      char **visible_columns)
479 {
480     GHashTable *visible_columns_hash;
481     GtkTreeIter iter;
482     int i;
483 
484     visible_columns_hash = g_hash_table_new (g_str_hash, g_str_equal);
485     for (i = 0; visible_columns[i] != NULL; ++i)
486     {
487         g_hash_table_insert (visible_columns_hash,
488                              visible_columns[i],
489                              visible_columns[i]);
490     }
491 
492     if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (chooser->details->store),
493                                        &iter))
494     {
495         do
496         {
497             char *name;
498             gboolean visible;
499 
500             gtk_tree_model_get (GTK_TREE_MODEL (chooser->details->store),
501                                 &iter,
502                                 COLUMN_NAME, &name,
503                                 -1);
504 
505             visible = (g_hash_table_lookup (visible_columns_hash, name) != NULL);
506 
507             gtk_list_store_set (chooser->details->store,
508                                 &iter,
509                                 COLUMN_VISIBLE, visible,
510                                 -1);
511             g_free (name);
512 
513         }
514         while (gtk_tree_model_iter_next (GTK_TREE_MODEL (chooser->details->store), &iter));
515     }
516 
517     g_hash_table_destroy (visible_columns_hash);
518 }
519 
520 static char **
get_column_names(CajaColumnChooser * chooser,gboolean only_visible)521 get_column_names (CajaColumnChooser *chooser, gboolean only_visible)
522 {
523     GPtrArray *ret;
524     GtkTreeIter iter;
525 
526     ret = g_ptr_array_new ();
527     if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (chooser->details->store),
528                                        &iter))
529     {
530         do
531         {
532             char *name;
533             gboolean visible;
534             gtk_tree_model_get (GTK_TREE_MODEL (chooser->details->store),
535                                 &iter,
536                                 COLUMN_VISIBLE, &visible,
537                                 COLUMN_NAME, &name,
538                                 -1);
539             if (!only_visible || visible)
540             {
541                 /* give ownership to the array */
542                 g_ptr_array_add (ret, name);
543             }
544 
545         }
546         while (gtk_tree_model_iter_next (GTK_TREE_MODEL (chooser->details->store), &iter));
547     }
548     g_ptr_array_add (ret, NULL);
549 
550     return (char **) g_ptr_array_free (ret, FALSE);
551 }
552 
553 static gboolean
get_column_iter(CajaColumnChooser * chooser,CajaColumn * column,GtkTreeIter * iter)554 get_column_iter (CajaColumnChooser *chooser,
555                  CajaColumn *column,
556                  GtkTreeIter *iter)
557 {
558     char *column_name;
559 
560     g_object_get (CAJA_COLUMN (column), "name", &column_name, NULL);
561 
562     if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (chooser->details->store),
563                                        iter))
564     {
565         do
566         {
567             char *name;
568 
569 
570             gtk_tree_model_get (GTK_TREE_MODEL (chooser->details->store),
571                                 iter,
572                                 COLUMN_NAME, &name,
573                                 -1);
574             if (!strcmp (name, column_name))
575             {
576                 g_free (column_name);
577                 g_free (name);
578                 return TRUE;
579             }
580 
581             g_free (name);
582         }
583         while (gtk_tree_model_iter_next (GTK_TREE_MODEL (chooser->details->store), iter));
584     }
585     g_free (column_name);
586     return FALSE;
587 }
588 
589 static void
set_column_order(CajaColumnChooser * chooser,char ** column_order)590 set_column_order (CajaColumnChooser *chooser,
591                   char **column_order)
592 
593 {
594     GList *columns;
595     GList *l;
596     GtkTreePath *path;
597 
598     columns = caja_get_columns_for_file (chooser->details->file);
599     columns = caja_sort_columns (columns, column_order);
600 
601     g_signal_handlers_block_by_func (chooser->details->store,
602                                      G_CALLBACK (row_deleted_callback),
603                                      chooser);
604 
605     path = gtk_tree_path_new_first ();
606     for (l = columns; l != NULL; l = l->next)
607     {
608         GtkTreeIter iter;
609 
610         if (get_column_iter (chooser, CAJA_COLUMN (l->data), &iter))
611         {
612             GtkTreeIter before;
613             if (path)
614             {
615                 gtk_tree_model_get_iter (GTK_TREE_MODEL (chooser->details->store),
616                                          &before, path);
617                 gtk_list_store_move_after (chooser->details->store,
618                                            &iter, &before);
619                 gtk_tree_path_next (path);
620 
621             }
622             else
623             {
624                 gtk_list_store_move_after (chooser->details->store,
625                                            &iter, NULL);
626             }
627         }
628     }
629     gtk_tree_path_free (path);
630     g_signal_handlers_unblock_by_func (chooser->details->store,
631                                        G_CALLBACK (row_deleted_callback),
632                                        chooser);
633 
634     caja_column_list_free (columns);
635 }
636 
637 void
caja_column_chooser_set_settings(CajaColumnChooser * chooser,char ** visible_columns,char ** column_order)638 caja_column_chooser_set_settings (CajaColumnChooser *chooser,
639                                   char **visible_columns,
640                                   char **column_order)
641 {
642     g_return_if_fail (CAJA_IS_COLUMN_CHOOSER (chooser));
643     g_return_if_fail (visible_columns != NULL);
644     g_return_if_fail (column_order != NULL);
645 
646     set_visible_columns (chooser, visible_columns);
647     set_column_order (chooser, column_order);
648 
649     list_changed (chooser);
650 }
651 
652 void
caja_column_chooser_get_settings(CajaColumnChooser * chooser,char *** visible_columns,char *** column_order)653 caja_column_chooser_get_settings (CajaColumnChooser *chooser,
654                                   char ***visible_columns,
655                                   char ***column_order)
656 {
657     g_return_if_fail (CAJA_IS_COLUMN_CHOOSER (chooser));
658     g_return_if_fail (visible_columns != NULL);
659     g_return_if_fail (column_order != NULL);
660 
661     *visible_columns = get_column_names (chooser, TRUE);
662     *column_order = get_column_names (chooser, FALSE);
663 }
664 
665 GtkWidget *
caja_column_chooser_new(CajaFile * file)666 caja_column_chooser_new (CajaFile *file)
667 {
668     return g_object_new (CAJA_TYPE_COLUMN_CHOOSER, "file", file, NULL);
669 }
670 
671