build-basic-autotools:bgo#688293 - build-basic-autotools: Fix invalid read
[anjuta.git] / libanjuta / anjuta-file-list.c
blobc2fb4267259aef7b1324c5e9b8ddc19fee31a900
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
2 /*
3 * anjuta
4 * Copyright (C) James Liggett 2010 <jrliggett@cox.net>
5 *
6 * anjuta is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * anjuta is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14 * See the GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License along
17 * with this program. If not, see <http://www.gnu.org/licenses/>.
20 #include "anjuta-file-list.h"
22 enum
24 PROP_0,
26 PROP_RELATIVE_PATH,
27 PROP_SHOW_ADD_BUTTON
30 enum
32 COL_PATH,
34 NUM_COLS
37 /* DND Targets */
38 static GtkTargetEntry dnd_target_entries[] =
41 "text/uri-list",
47 struct _AnjutaFileListPriv
49 gchar *relative_path;
50 GtkWidget *list_view;
51 GtkListStore *list_model;
52 GtkWidget *add_button;
53 GtkWidget *copy_button;
54 GtkWidget *remove_button;
56 /* The placeholder iter tells the user that a file can be entered into the
57 * view, or dragged into it. */
58 GtkTreeIter placeholder;
61 G_DEFINE_TYPE (AnjutaFileList, anjuta_file_list, GTK_TYPE_VBOX);
63 static void
64 anjuta_file_list_append_placeholder (AnjutaFileList *self)
66 GtkTreeIter iter;
68 gtk_list_store_append (self->priv->list_model, &iter);
70 gtk_list_store_set (self->priv->list_model, &iter, COL_PATH, NULL, -1);
72 self->priv->placeholder = iter;
76 static void
77 path_cell_data_func (GtkTreeViewColumn *column, GtkCellRenderer *renderer,
78 GtkTreeModel *model, GtkTreeIter *iter,
79 GtkTreeView *list_view)
81 gchar *path;
82 GtkStyle *style;
84 gtk_tree_model_get (model, iter, COL_PATH, &path, -1);
85 style = gtk_widget_get_style (GTK_WIDGET (list_view));
87 /* NULL path means this is the placeholder */
88 if (path)
90 g_object_set (G_OBJECT (renderer),
91 "foreground-gdk", &(style->text[GTK_STATE_NORMAL]),
92 "style", PANGO_STYLE_NORMAL,
93 "text", path,
94 NULL);
96 else
98 g_object_set (G_OBJECT (renderer),
99 "foreground-gdk", &(style->text[GTK_STATE_INSENSITIVE]),
100 "style", PANGO_STYLE_ITALIC,
101 "text", _("Drop a file or enter a path here"),
102 NULL);
105 g_free (path);
108 static void
109 on_path_renderer_editing_started (GtkCellRenderer *renderer,
110 GtkCellEditable *editable,
111 const gchar *tree_path,
112 GtkTreeModel *list_model)
114 GtkTreeIter iter;
115 gchar *path;
117 /* Don't show placeholder text in the edit widget */
118 gtk_tree_model_get_iter_from_string (list_model, &iter, tree_path);
120 gtk_tree_model_get (list_model, &iter, COL_PATH, &path, -1);
122 if (!path)
124 if (GTK_IS_ENTRY (editable))
125 gtk_entry_set_text (GTK_ENTRY (editable), "");
129 static void
130 on_path_renderer_edited (GtkCellRendererText *renderer, gchar *path,
131 gchar *new_text, AnjutaFileList *self)
133 GtkTreeIter iter;
134 gchar *current_path;
136 gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (self->priv->list_model),
137 &iter, path);
138 gtk_tree_model_get (GTK_TREE_MODEL (self->priv->list_model), &iter,
139 COL_PATH, &current_path, -1);
141 /* Interpret empty new_text as a cancellation of the edit */
142 if (g_utf8_strlen (new_text, -1) > 0)
144 /* If the placeholder is being edited, a new one has to be created */
145 if (!current_path)
146 anjuta_file_list_append_placeholder (self);
148 gtk_list_store_set (self->priv->list_model, &iter, COL_PATH, new_text,
149 -1);
153 static void
154 on_list_view_drag_data_received (GtkWidget *list_view, GdkDragContext *context,
155 gint x, gint y, GtkSelectionData *data,
156 gint target_type, gint time,
157 AnjutaFileList *self)
159 gboolean success;
160 gchar **uri_list;
161 gint i;
162 GFile *parent_file;
163 GFile *file;
164 gchar *path;
165 GtkTreeIter iter;
167 success = FALSE;
169 if ((data != NULL) &&
170 (gtk_selection_data_get_length (data) >= 0))
172 if (target_type == 0)
174 uri_list = gtk_selection_data_get_uris (data);
175 parent_file = NULL;
177 if (self->priv->relative_path)
178 parent_file = g_file_new_for_path (self->priv->relative_path);
180 for (i = 0; uri_list[i]; i++)
182 file = g_file_new_for_uri (uri_list[i]);
184 if (parent_file)
186 path = g_file_get_relative_path (parent_file, file);
188 g_object_unref (parent_file);
190 else
191 path = g_file_get_path (file);
193 if (path)
195 gtk_list_store_insert_before (self->priv->list_model,
196 &iter,
197 &(self->priv->placeholder));
198 gtk_list_store_set (self->priv->list_model, &iter,
199 COL_PATH, path,
200 -1);
202 g_free (path);
205 g_object_unref (file);
208 success = TRUE;
210 g_strfreev (uri_list);
214 /* Do not delete source data */
215 gtk_drag_finish (context, success, FALSE, time);
218 static gboolean
219 on_list_view_drag_drop (GtkWidget *widget, GdkDragContext *context,
220 gint x, gint y, guint time, gpointer user_data)
222 GdkAtom target_type;
224 target_type = gtk_drag_dest_find_target (widget, context, NULL);
226 if (target_type != GDK_NONE)
227 gtk_drag_get_data (widget, context, target_type, time);
228 else
229 gtk_drag_finish (context, FALSE, FALSE, time);
231 return TRUE;
234 static gboolean
235 on_list_view_item_selected (GtkTreeSelection *selection, GtkTreeModel *model,
236 GtkTreePath *tree_path,
237 gboolean path_currently_selected,
238 AnjutaFileList *self)
241 gboolean sensitive;
242 GtkTreeIter iter;
243 gchar *path;
245 sensitive = FALSE;
247 if (!path_currently_selected)
249 gtk_tree_model_get_iter (model, &iter, tree_path);
250 gtk_tree_model_get (model, &iter, COL_PATH, &path, -1);
252 if (path)
254 sensitive = TRUE;
256 g_free (path);
260 gtk_widget_set_sensitive (self->priv->copy_button, sensitive);
261 gtk_widget_set_sensitive (self->priv->remove_button, sensitive);
263 return TRUE;
266 static void
267 on_add_button_clicked (GtkButton *button, AnjutaFileList *self)
269 GtkWidget *file_dialog;
270 GSList *paths;
271 GSList *current_path;
272 GtkTreeIter iter;
274 file_dialog = gtk_file_chooser_dialog_new (_("Select Files"),
275 GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (button))),
276 GTK_FILE_CHOOSER_ACTION_OPEN,
277 GTK_STOCK_CANCEL,
278 GTK_RESPONSE_CANCEL,
279 GTK_STOCK_OPEN,
280 GTK_RESPONSE_ACCEPT,
281 NULL);
283 gtk_file_chooser_set_select_multiple (GTK_FILE_CHOOSER (file_dialog),
284 TRUE);
286 if (gtk_dialog_run (GTK_DIALOG (file_dialog)) == GTK_RESPONSE_ACCEPT)
288 paths = gtk_file_chooser_get_filenames (GTK_FILE_CHOOSER (file_dialog));
289 current_path = paths;
291 while (current_path)
293 gtk_list_store_insert_before (self->priv->list_model, &iter,
294 &(self->priv->placeholder));
295 gtk_list_store_set (self->priv->list_model, &iter,
296 COL_PATH, current_path->data,
297 -1);
299 g_free (current_path->data);
301 current_path = g_slist_next (current_path);
304 g_slist_free (paths);
309 gtk_widget_destroy (file_dialog);
312 static void
313 on_copy_button_clicked (GtkButton *button, GtkTreeSelection *selection)
315 GtkTreeModel *list_model;
316 GtkTreeIter selected_iter;
317 GtkTreeIter new_iter;
318 gchar *path;
320 if (gtk_tree_selection_get_selected (selection, &list_model,
321 &selected_iter))
323 gtk_tree_model_get (list_model, &selected_iter, COL_PATH, &path, -1);
324 gtk_list_store_insert_after (GTK_LIST_STORE (list_model), &new_iter,
325 &selected_iter);
327 gtk_list_store_set (GTK_LIST_STORE (list_model), &new_iter, COL_PATH,
328 path, -1);
330 g_free (path);
334 static void
335 on_remove_button_clicked (GtkButton *button, GtkTreeSelection *selection)
337 GtkTreeIter iter;
338 GtkTreeModel *list_model;
340 if (gtk_tree_selection_get_selected (selection, &list_model, &iter))
341 gtk_list_store_remove (GTK_LIST_STORE (list_model), &iter);
344 static void
345 anjuta_file_list_init (AnjutaFileList *self)
347 GtkWidget *scrolled_window;
348 GtkWidget *button_box;
349 GtkWidget *clear_button;
350 GtkTreeSelection *selection;
351 GtkTreeViewColumn *column;
352 GtkCellRenderer *renderer;
354 self->priv = g_new0 (AnjutaFileListPriv, 1);
355 self->priv->list_view = gtk_tree_view_new ();
356 self->priv->list_model = gtk_list_store_new (NUM_COLS, G_TYPE_STRING);
357 self->priv->add_button = gtk_button_new_from_stock (GTK_STOCK_ADD);
358 self->priv->copy_button = gtk_button_new_from_stock (GTK_STOCK_COPY);
359 self->priv->remove_button = gtk_button_new_from_stock (GTK_STOCK_REMOVE);
361 button_box = gtk_hbutton_box_new ();
362 scrolled_window = gtk_scrolled_window_new (NULL, NULL);
363 clear_button = gtk_button_new_from_stock (GTK_STOCK_CLEAR);
365 gtk_widget_set_sensitive (self->priv->copy_button, FALSE);
366 gtk_widget_set_sensitive (self->priv->remove_button, FALSE);
368 g_signal_connect_swapped (G_OBJECT (clear_button), "clicked",
369 G_CALLBACK (anjuta_file_list_clear),
370 self);
372 /* File list view */
373 gtk_box_set_spacing (GTK_BOX (self), 2);
374 gtk_box_set_homogeneous (GTK_BOX (self), FALSE);
376 gtk_tree_view_set_model (GTK_TREE_VIEW (self->priv->list_view),
377 GTK_TREE_MODEL (self->priv->list_model));
378 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (self->priv->list_view),
379 FALSE);
381 /* Path column */
382 column = gtk_tree_view_column_new ();
383 renderer = gtk_cell_renderer_text_new ();
385 gtk_tree_view_column_pack_start (column, renderer, TRUE);
386 gtk_tree_view_column_set_cell_data_func (column, renderer,
387 (GtkTreeCellDataFunc) path_cell_data_func,
388 self->priv->list_view,
389 NULL);
390 g_object_set (G_OBJECT (renderer), "editable", TRUE, NULL);
391 gtk_tree_view_append_column (GTK_TREE_VIEW (self->priv->list_view),
392 column);
394 g_signal_connect (G_OBJECT (renderer), "editing-started",
395 G_CALLBACK (on_path_renderer_editing_started),
396 self->priv->list_model);
398 g_signal_connect (G_OBJECT (renderer), "edited",
399 G_CALLBACK (on_path_renderer_edited),
400 self);
402 /* DND */
403 gtk_tree_view_enable_model_drag_dest (GTK_TREE_VIEW (self->priv->list_view),
404 dnd_target_entries,
405 G_N_ELEMENTS (dnd_target_entries),
406 GDK_ACTION_COPY);
408 g_signal_connect (G_OBJECT (self->priv->list_view), "drag-drop",
409 G_CALLBACK (on_list_view_drag_drop),
410 NULL);
412 g_signal_connect (G_OBJECT (self->priv->list_view), "drag-data-received",
413 G_CALLBACK (on_list_view_drag_data_received),
414 self);
416 /* Selection handling */
417 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self->priv->list_view));
419 gtk_tree_selection_set_select_function (selection,
420 (GtkTreeSelectionFunc) on_list_view_item_selected,
421 self, NULL);
423 g_signal_connect (G_OBJECT (self->priv->add_button), "clicked",
424 G_CALLBACK (on_add_button_clicked),
425 self);
427 g_signal_connect (G_OBJECT (self->priv->copy_button), "clicked",
428 G_CALLBACK (on_copy_button_clicked),
429 selection);
431 g_signal_connect (G_OBJECT (self->priv->remove_button), "clicked",
432 G_CALLBACK (on_remove_button_clicked),
433 selection);
435 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
436 GTK_POLICY_AUTOMATIC,
437 GTK_POLICY_AUTOMATIC);
438 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window),
439 GTK_SHADOW_IN);
440 gtk_container_add (GTK_CONTAINER (scrolled_window), self->priv->list_view);
441 gtk_box_pack_start (GTK_BOX (self), scrolled_window, TRUE, TRUE, 0);
443 /* Button box */
444 gtk_box_set_spacing (GTK_BOX (button_box), 5);
445 gtk_button_box_set_layout (GTK_BUTTON_BOX (button_box),
446 GTK_BUTTONBOX_START);
448 gtk_container_add (GTK_CONTAINER (button_box), self->priv->add_button);
449 gtk_container_add (GTK_CONTAINER (button_box), self->priv->copy_button);
450 gtk_container_add (GTK_CONTAINER (button_box), self->priv->remove_button);
451 gtk_container_add (GTK_CONTAINER (button_box), clear_button);
452 gtk_box_pack_start (GTK_BOX (self), button_box, FALSE, FALSE, 0);
454 anjuta_file_list_append_placeholder (self);
456 gtk_widget_show_all (GTK_WIDGET (self));
458 /* Don't show the Add button by default */
459 gtk_widget_set_visible (self->priv->add_button, FALSE);
462 static void
463 anjuta_file_list_finalize (GObject *object)
465 AnjutaFileList *self;
467 self = ANJUTA_FILE_LIST (object);
469 g_free (self->priv->relative_path);
470 g_free (self->priv);
472 G_OBJECT_CLASS (anjuta_file_list_parent_class)->finalize (object);
475 static void
476 anjuta_file_list_set_property (GObject *object, guint prop_id,
477 const GValue *value, GParamSpec *pspec)
479 AnjutaFileList *self;
481 g_return_if_fail (ANJUTA_IS_FILE_LIST (object));
483 self = ANJUTA_FILE_LIST (object);
485 switch (prop_id)
487 case PROP_RELATIVE_PATH:
488 g_free (self->priv->relative_path);
490 self->priv->relative_path = g_value_dup_string (value);
491 break;
492 case PROP_SHOW_ADD_BUTTON:
493 gtk_widget_set_visible (self->priv->add_button,
494 g_value_get_boolean (value));
495 break;
496 default:
497 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
498 break;
502 static void
503 anjuta_file_list_get_property (GObject *object, guint prop_id, GValue *value,
504 GParamSpec *pspec)
506 AnjutaFileList *self;
508 g_return_if_fail (ANJUTA_IS_FILE_LIST (object));
510 self = ANJUTA_FILE_LIST (object);
512 switch (prop_id)
514 case PROP_RELATIVE_PATH:
515 g_value_set_string (value, self->priv->relative_path);
516 break;
517 case PROP_SHOW_ADD_BUTTON:
518 g_value_set_boolean (value,
519 gtk_widget_get_visible (self->priv->add_button));
520 break;
521 default:
522 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
523 break;
527 static void
528 anjuta_file_list_class_init (AnjutaFileListClass *klass)
530 GObjectClass* object_class = G_OBJECT_CLASS (klass);
532 object_class->finalize = anjuta_file_list_finalize;
533 object_class->set_property = anjuta_file_list_set_property;
534 object_class->get_property = anjuta_file_list_get_property;
536 g_object_class_install_property (object_class,
537 PROP_RELATIVE_PATH,
538 g_param_spec_string ("relative-path",
539 "relative-path",
540 _("Path that all files in the list should be relative to"),
542 G_PARAM_READWRITE));
544 g_object_class_install_property (object_class,
545 PROP_SHOW_ADD_BUTTON,
546 g_param_spec_boolean ("show-add-button",
547 _("Show Add button"),
548 _("Display an Add button"),
549 FALSE,
550 G_PARAM_READWRITE));
554 GtkWidget *
555 anjuta_file_list_new (void)
557 return g_object_new (ANJUTA_TYPE_FILE_LIST, NULL);
560 static gboolean
561 list_model_foreach (GtkTreeModel *list_model, GtkTreePath *tree_path,
562 GtkTreeIter *iter, GList **list)
564 gchar *path;
566 gtk_tree_model_get (list_model, iter, COL_PATH, &path, -1);
568 /* Make sure not to add the placeholder to the list */
569 if (path)
570 *list = g_list_append (*list, path);
572 return FALSE;
575 GList *
576 anjuta_file_list_get_paths (AnjutaFileList *self)
578 GList *list;
580 list = NULL;
582 gtk_tree_model_foreach (GTK_TREE_MODEL (self->priv->list_model),
583 (GtkTreeModelForeachFunc) list_model_foreach,
584 &list);
586 return list;
589 void
590 anjuta_file_list_set_relative_path (AnjutaFileList *self, const gchar *path)
592 g_object_set (G_OBJECT (self), "relative-path", path, NULL);
595 void
596 anjuta_file_list_clear (AnjutaFileList *self)
598 gtk_list_store_clear (self->priv->list_model);
600 anjuta_file_list_append_placeholder (self);