1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
4 * Copyright (C) James Liggett 2010 <jrliggett@cox.net>
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"
38 static GtkTargetEntry dnd_target_entries
[] =
47 struct _AnjutaFileListPriv
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_BOX
);
64 anjuta_file_list_append_placeholder (AnjutaFileList
*self
)
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
;
77 path_cell_data_func (GtkTreeViewColumn
*column
, GtkCellRenderer
*renderer
,
78 GtkTreeModel
*model
, GtkTreeIter
*iter
,
79 GtkTreeView
*list_view
)
82 GtkStyleContext
*context
;
85 gtk_tree_model_get (model
, iter
, COL_PATH
, &path
, -1);
86 context
= gtk_widget_get_style_context (GTK_WIDGET (list_view
));
88 /* NULL path means this is the placeholder */
91 gtk_style_context_get_color (context
, GTK_STATE_FLAG_NORMAL
, &fg_color
);
92 g_object_set (G_OBJECT (renderer
),
93 "foreground-rgba", &fg_color
,
94 "style", PANGO_STYLE_NORMAL
,
100 gtk_style_context_get_color (context
, GTK_STATE_FLAG_INSENSITIVE
, &fg_color
);
101 g_object_set (G_OBJECT (renderer
),
102 "foreground-rgba", &fg_color
,
103 "style", PANGO_STYLE_ITALIC
,
104 "text", _("Drop a file or enter a path here"),
112 on_path_renderer_editing_started (GtkCellRenderer
*renderer
,
113 GtkCellEditable
*editable
,
114 const gchar
*tree_path
,
115 GtkTreeModel
*list_model
)
120 /* Don't show placeholder text in the edit widget */
121 gtk_tree_model_get_iter_from_string (list_model
, &iter
, tree_path
);
123 gtk_tree_model_get (list_model
, &iter
, COL_PATH
, &path
, -1);
127 if (GTK_IS_ENTRY (editable
))
128 gtk_entry_set_text (GTK_ENTRY (editable
), "");
133 on_path_renderer_edited (GtkCellRendererText
*renderer
, gchar
*path
,
134 gchar
*new_text
, AnjutaFileList
*self
)
139 gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (self
->priv
->list_model
),
141 gtk_tree_model_get (GTK_TREE_MODEL (self
->priv
->list_model
), &iter
,
142 COL_PATH
, ¤t_path
, -1);
144 /* Interpret empty new_text as a cancellation of the edit */
145 if (g_utf8_strlen (new_text
, -1) > 0)
147 /* If the placeholder is being edited, a new one has to be created */
149 anjuta_file_list_append_placeholder (self
);
151 gtk_list_store_set (self
->priv
->list_model
, &iter
, COL_PATH
, new_text
,
157 on_list_view_drag_data_received (GtkWidget
*list_view
, GdkDragContext
*context
,
158 gint x
, gint y
, GtkSelectionData
*data
,
159 gint target_type
, gint time
,
160 AnjutaFileList
*self
)
172 if ((data
!= NULL
) &&
173 (gtk_selection_data_get_length (data
) >= 0))
175 if (target_type
== 0)
177 uri_list
= gtk_selection_data_get_uris (data
);
180 if (self
->priv
->relative_path
)
181 parent_file
= g_file_new_for_path (self
->priv
->relative_path
);
183 for (i
= 0; uri_list
[i
]; i
++)
185 file
= g_file_new_for_uri (uri_list
[i
]);
189 path
= g_file_get_relative_path (parent_file
, file
);
191 g_object_unref (parent_file
);
194 path
= g_file_get_path (file
);
198 gtk_list_store_insert_before (self
->priv
->list_model
,
200 &(self
->priv
->placeholder
));
201 gtk_list_store_set (self
->priv
->list_model
, &iter
,
208 g_object_unref (file
);
213 g_strfreev (uri_list
);
217 /* Do not delete source data */
218 gtk_drag_finish (context
, success
, FALSE
, time
);
222 on_list_view_drag_drop (GtkWidget
*widget
, GdkDragContext
*context
,
223 gint x
, gint y
, guint time
, gpointer user_data
)
227 target_type
= gtk_drag_dest_find_target (widget
, context
, NULL
);
229 if (target_type
!= GDK_NONE
)
230 gtk_drag_get_data (widget
, context
, target_type
, time
);
232 gtk_drag_finish (context
, FALSE
, FALSE
, time
);
238 on_list_view_item_selected (GtkTreeSelection
*selection
, GtkTreeModel
*model
,
239 GtkTreePath
*tree_path
,
240 gboolean path_currently_selected
,
241 AnjutaFileList
*self
)
250 if (!path_currently_selected
)
252 gtk_tree_model_get_iter (model
, &iter
, tree_path
);
253 gtk_tree_model_get (model
, &iter
, COL_PATH
, &path
, -1);
263 gtk_widget_set_sensitive (self
->priv
->copy_button
, sensitive
);
264 gtk_widget_set_sensitive (self
->priv
->remove_button
, sensitive
);
270 on_add_button_clicked (GtkButton
*button
, AnjutaFileList
*self
)
272 GtkWidget
*file_dialog
;
274 GSList
*current_path
;
277 file_dialog
= gtk_file_chooser_dialog_new (_("Select Files"),
278 GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (button
))),
279 GTK_FILE_CHOOSER_ACTION_OPEN
,
286 gtk_file_chooser_set_select_multiple (GTK_FILE_CHOOSER (file_dialog
),
289 if (gtk_dialog_run (GTK_DIALOG (file_dialog
)) == GTK_RESPONSE_ACCEPT
)
291 paths
= gtk_file_chooser_get_filenames (GTK_FILE_CHOOSER (file_dialog
));
292 current_path
= paths
;
296 gtk_list_store_insert_before (self
->priv
->list_model
, &iter
,
297 &(self
->priv
->placeholder
));
298 gtk_list_store_set (self
->priv
->list_model
, &iter
,
299 COL_PATH
, current_path
->data
,
302 g_free (current_path
->data
);
304 current_path
= g_slist_next (current_path
);
307 g_slist_free (paths
);
312 gtk_widget_destroy (file_dialog
);
316 on_copy_button_clicked (GtkButton
*button
, GtkTreeSelection
*selection
)
318 GtkTreeModel
*list_model
;
319 GtkTreeIter selected_iter
;
320 GtkTreeIter new_iter
;
323 if (gtk_tree_selection_get_selected (selection
, &list_model
,
326 gtk_tree_model_get (list_model
, &selected_iter
, COL_PATH
, &path
, -1);
327 gtk_list_store_insert_after (GTK_LIST_STORE (list_model
), &new_iter
,
330 gtk_list_store_set (GTK_LIST_STORE (list_model
), &new_iter
, COL_PATH
,
338 on_remove_button_clicked (GtkButton
*button
, GtkTreeSelection
*selection
)
341 GtkTreeModel
*list_model
;
343 if (gtk_tree_selection_get_selected (selection
, &list_model
, &iter
))
344 gtk_list_store_remove (GTK_LIST_STORE (list_model
), &iter
);
348 anjuta_file_list_init (AnjutaFileList
*self
)
350 GtkWidget
*scrolled_window
;
351 GtkWidget
*button_box
;
352 GtkWidget
*clear_button
;
353 GtkTreeSelection
*selection
;
354 GtkTreeViewColumn
*column
;
355 GtkCellRenderer
*renderer
;
359 g_object_set (self
, "orientation", GTK_ORIENTATION_VERTICAL
, NULL
);
361 self
->priv
= g_new0 (AnjutaFileListPriv
, 1);
362 self
->priv
->list_view
= gtk_tree_view_new ();
363 self
->priv
->list_model
= gtk_list_store_new (NUM_COLS
, G_TYPE_STRING
);
364 self
->priv
->add_button
= gtk_button_new_from_stock (GTK_STOCK_ADD
);
365 self
->priv
->copy_button
= gtk_button_new_from_stock (GTK_STOCK_COPY
);
366 self
->priv
->remove_button
= gtk_button_new_from_stock (GTK_STOCK_REMOVE
);
368 button_box
= gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL
);
369 scrolled_window
= gtk_scrolled_window_new (NULL
, NULL
);
370 clear_button
= gtk_button_new_from_stock (GTK_STOCK_CLEAR
);
372 gtk_widget_set_sensitive (self
->priv
->copy_button
, FALSE
);
373 gtk_widget_set_sensitive (self
->priv
->remove_button
, FALSE
);
375 g_signal_connect_swapped (G_OBJECT (clear_button
), "clicked",
376 G_CALLBACK (anjuta_file_list_clear
),
380 gtk_box_set_spacing (GTK_BOX (self
), 2);
381 gtk_box_set_homogeneous (GTK_BOX (self
), FALSE
);
383 gtk_tree_view_set_model (GTK_TREE_VIEW (self
->priv
->list_view
),
384 GTK_TREE_MODEL (self
->priv
->list_model
));
385 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (self
->priv
->list_view
),
389 column
= gtk_tree_view_column_new ();
390 renderer
= gtk_cell_renderer_text_new ();
392 gtk_tree_view_column_pack_start (column
, renderer
, TRUE
);
393 gtk_tree_view_column_set_cell_data_func (column
, renderer
,
394 (GtkTreeCellDataFunc
) path_cell_data_func
,
395 self
->priv
->list_view
,
397 g_object_set (G_OBJECT (renderer
), "editable", TRUE
, NULL
);
398 gtk_tree_view_append_column (GTK_TREE_VIEW (self
->priv
->list_view
),
401 g_signal_connect (G_OBJECT (renderer
), "editing-started",
402 G_CALLBACK (on_path_renderer_editing_started
),
403 self
->priv
->list_model
);
405 g_signal_connect (G_OBJECT (renderer
), "edited",
406 G_CALLBACK (on_path_renderer_edited
),
410 gtk_tree_view_enable_model_drag_dest (GTK_TREE_VIEW (self
->priv
->list_view
),
412 G_N_ELEMENTS (dnd_target_entries
),
415 g_signal_connect (G_OBJECT (self
->priv
->list_view
), "drag-drop",
416 G_CALLBACK (on_list_view_drag_drop
),
419 g_signal_connect (G_OBJECT (self
->priv
->list_view
), "drag-data-received",
420 G_CALLBACK (on_list_view_drag_data_received
),
423 /* Selection handling */
424 selection
= gtk_tree_view_get_selection (GTK_TREE_VIEW (self
->priv
->list_view
));
426 gtk_tree_selection_set_select_function (selection
,
427 (GtkTreeSelectionFunc
) on_list_view_item_selected
,
430 g_signal_connect (G_OBJECT (self
->priv
->add_button
), "clicked",
431 G_CALLBACK (on_add_button_clicked
),
434 g_signal_connect (G_OBJECT (self
->priv
->copy_button
), "clicked",
435 G_CALLBACK (on_copy_button_clicked
),
438 g_signal_connect (G_OBJECT (self
->priv
->remove_button
), "clicked",
439 G_CALLBACK (on_remove_button_clicked
),
442 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window
),
443 GTK_POLICY_AUTOMATIC
,
444 GTK_POLICY_AUTOMATIC
);
445 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window
),
447 gtk_container_add (GTK_CONTAINER (scrolled_window
), self
->priv
->list_view
);
448 gtk_box_pack_start (GTK_BOX (self
), scrolled_window
, TRUE
, TRUE
, 0);
451 gtk_box_set_spacing (GTK_BOX (button_box
), 5);
452 gtk_button_box_set_layout (GTK_BUTTON_BOX (button_box
),
453 GTK_BUTTONBOX_START
);
455 gtk_container_add (GTK_CONTAINER (button_box
), self
->priv
->add_button
);
456 gtk_container_add (GTK_CONTAINER (button_box
), self
->priv
->copy_button
);
457 gtk_container_add (GTK_CONTAINER (button_box
), self
->priv
->remove_button
);
458 gtk_container_add (GTK_CONTAINER (button_box
), clear_button
);
459 gtk_box_pack_start (GTK_BOX (self
), button_box
, FALSE
, FALSE
, 0);
461 anjuta_file_list_append_placeholder (self
);
463 gtk_widget_show_all (GTK_WIDGET (self
));
465 /* Don't show the Add button by default */
466 gtk_widget_set_visible (self
->priv
->add_button
, FALSE
);
470 anjuta_file_list_finalize (GObject
*object
)
472 AnjutaFileList
*self
;
474 self
= ANJUTA_FILE_LIST (object
);
476 g_free (self
->priv
->relative_path
);
479 G_OBJECT_CLASS (anjuta_file_list_parent_class
)->finalize (object
);
483 anjuta_file_list_set_property (GObject
*object
, guint prop_id
,
484 const GValue
*value
, GParamSpec
*pspec
)
486 AnjutaFileList
*self
;
488 g_return_if_fail (ANJUTA_IS_FILE_LIST (object
));
490 self
= ANJUTA_FILE_LIST (object
);
494 case PROP_RELATIVE_PATH
:
495 g_free (self
->priv
->relative_path
);
497 self
->priv
->relative_path
= g_value_dup_string (value
);
499 case PROP_SHOW_ADD_BUTTON
:
500 gtk_widget_set_visible (self
->priv
->add_button
,
501 g_value_get_boolean (value
));
504 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
510 anjuta_file_list_get_property (GObject
*object
, guint prop_id
, GValue
*value
,
513 AnjutaFileList
*self
;
515 g_return_if_fail (ANJUTA_IS_FILE_LIST (object
));
517 self
= ANJUTA_FILE_LIST (object
);
521 case PROP_RELATIVE_PATH
:
522 g_value_set_string (value
, self
->priv
->relative_path
);
524 case PROP_SHOW_ADD_BUTTON
:
525 g_value_set_boolean (value
,
526 gtk_widget_get_visible (self
->priv
->add_button
));
529 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
535 anjuta_file_list_class_init (AnjutaFileListClass
*klass
)
537 GObjectClass
* object_class
= G_OBJECT_CLASS (klass
);
539 object_class
->finalize
= anjuta_file_list_finalize
;
540 object_class
->set_property
= anjuta_file_list_set_property
;
541 object_class
->get_property
= anjuta_file_list_get_property
;
543 g_object_class_install_property (object_class
,
545 g_param_spec_string ("relative-path",
547 _("Path that all files in the list should be relative to"),
551 g_object_class_install_property (object_class
,
552 PROP_SHOW_ADD_BUTTON
,
553 g_param_spec_boolean ("show-add-button",
554 _("Show Add button"),
555 _("Display an Add button"),
562 anjuta_file_list_new (void)
564 return g_object_new (ANJUTA_TYPE_FILE_LIST
, NULL
);
568 list_model_foreach (GtkTreeModel
*list_model
, GtkTreePath
*tree_path
,
569 GtkTreeIter
*iter
, GList
**list
)
573 gtk_tree_model_get (list_model
, iter
, COL_PATH
, &path
, -1);
575 /* Make sure not to add the placeholder to the list */
577 *list
= g_list_append (*list
, path
);
583 anjuta_file_list_get_paths (AnjutaFileList
*self
)
589 gtk_tree_model_foreach (GTK_TREE_MODEL (self
->priv
->list_model
),
590 (GtkTreeModelForeachFunc
) list_model_foreach
,
597 anjuta_file_list_set_relative_path (AnjutaFileList
*self
, const gchar
*path
)
599 g_object_set (G_OBJECT (self
), "relative-path", path
, NULL
);
603 anjuta_file_list_clear (AnjutaFileList
*self
)
605 gtk_list_store_clear (self
->priv
->list_model
);
607 anjuta_file_list_append_placeholder (self
);