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