git: Implement revision diff viewing
[anjuta.git] / plugins / git / git-log-pane.c
blobe0601a87cd6cd635a6f3b6dc9d0cbebf1749df98
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 "git-log-pane.h"
22 enum
24 LOG_COL_REVISION
27 enum
29 LOADING_COL_PULSE,
30 LOADING_COL_INDICATOR
33 /* Loading view modes */
34 typedef enum
36 /* Show the real log viewer */
37 LOG_VIEW_NORMAL,
39 /* Show the loading view */
40 LOG_VIEW_LOADING
41 } LogViewMode;
43 enum
45 BRANCH_COL_ACTIVE,
46 BRANCH_COL_ACTIVE_ICON,
47 BRANCH_COL_NAME
50 /* DnD source targets */
51 static GtkTargetEntry drag_source_targets[] =
54 "STRING",
60 /* DnD target targets */
61 static GtkTargetEntry drag_target_targets[] =
64 "text/uri-list",
70 struct _GitLogPanePriv
72 GtkBuilder *builder;
73 GtkListStore *log_model;
74 GtkCellRenderer *graph_renderer;
75 GHashTable *refs;
76 gchar *path;
78 /* This table maps branch names and iters in the branch combo model. When
79 * branches get refreshed, use this to make sure that the same branch the
80 * user was looking at stays selected, unless that branch no longer exists.
81 * In that case the new active branch is selected */
82 GHashTable *branches_table;
83 gchar *selected_branch;
84 gboolean viewing_active_branch;
85 GtkTreeIter active_branch_iter;
87 /* Loading spinner data */
88 guint current_spin_count;
89 guint spin_cycle_steps;
90 guint spin_cycle_duration;
91 gint spin_timer_id;
92 GtkListStore *log_loading_model;
93 GtkTreeIter spinner_iter;
96 G_DEFINE_TYPE (GitLogPane, git_log_pane, GIT_TYPE_PANE);
98 static void
99 on_branch_list_command_started (AnjutaCommand *command,
100 GitLogPane *self)
102 GtkComboBox *branch_combo;
103 GtkListStore *log_branch_combo_model;
105 branch_combo = GTK_COMBO_BOX (gtk_builder_get_object (self->priv->builder,
106 "branch_combo"));
107 log_branch_combo_model = GTK_LIST_STORE (gtk_builder_get_object (self->priv->builder,
108 "log_branch_combo_model"));
110 gtk_combo_box_set_model (branch_combo, NULL);
111 gtk_list_store_clear (log_branch_combo_model);
113 g_hash_table_remove_all (self->priv->branches_table);
116 static void
117 on_branch_list_command_finished (AnjutaCommand *command,
118 guint return_code,
119 GitLogPane *self)
121 GtkComboBox *branch_combo;
122 GtkTreeModel *log_branch_combo_model;
123 GtkTreeIter *iter;
125 branch_combo = GTK_COMBO_BOX (gtk_builder_get_object (self->priv->builder,
126 "branch_combo"));
127 log_branch_combo_model = GTK_TREE_MODEL (gtk_builder_get_object (self->priv->builder,
128 "log_branch_combo_model"));
130 gtk_combo_box_set_model (branch_combo, log_branch_combo_model);
132 /* If the user was viewing the active branch, switch to the newly active
133 * branch if it changes. If another branch was being viewed, stay on that
134 * one */
135 if ((!self->priv->viewing_active_branch) &&
136 (self->priv->selected_branch &&
137 g_hash_table_lookup_extended (self->priv->branches_table,
138 self->priv->selected_branch, NULL,
139 (gpointer) &iter)))
141 gtk_combo_box_set_active_iter (branch_combo, iter);
143 else
145 gtk_combo_box_set_active_iter (branch_combo,
146 &(self->priv->active_branch_iter));
151 static void
152 on_branch_list_command_data_arrived (AnjutaCommand *command,
153 GitLogPane *self)
155 GtkListStore *log_branch_combo_model;
156 AnjutaPlugin *plugin;
157 GList *current_branch;
158 GitBranch *branch;
159 gchar *name;
160 GtkTreeIter iter;
162 log_branch_combo_model = GTK_LIST_STORE (gtk_builder_get_object (self->priv->builder,
163 "log_branch_combo_model"));
164 plugin = anjuta_dock_pane_get_plugin (ANJUTA_DOCK_PANE (self));
165 current_branch = git_branch_list_command_get_output (GIT_BRANCH_LIST_COMMAND (command));
167 while (current_branch)
169 branch = current_branch->data;
170 name = git_branch_get_name (branch);
172 gtk_list_store_append (log_branch_combo_model, &iter);
174 if (git_branch_is_active (branch))
176 gtk_list_store_set (log_branch_combo_model, &iter,
177 BRANCH_COL_ACTIVE, TRUE,
178 BRANCH_COL_ACTIVE_ICON, GTK_STOCK_APPLY,
179 -1);
181 self->priv->active_branch_iter = iter;
183 else
185 gtk_list_store_set (log_branch_combo_model, &iter,
186 BRANCH_COL_ACTIVE, FALSE,
187 BRANCH_COL_ACTIVE_ICON, NULL,
188 -1);
191 gtk_list_store_set (log_branch_combo_model, &iter,
192 BRANCH_COL_NAME, name,
193 -1);
194 g_hash_table_insert (self->priv->branches_table, g_strdup (name),
195 g_memdup (&iter, sizeof (GtkTreeIter)));
197 g_free (name);
199 current_branch = g_list_next (current_branch);
204 static gboolean
205 on_spinner_timeout (GitLogPane *self)
207 if (self->priv->current_spin_count == self->priv->spin_cycle_steps)
208 self->priv->current_spin_count = 0;
209 else
210 self->priv->current_spin_count++;
212 gtk_list_store_set (self->priv->log_loading_model,
213 &(self->priv->spinner_iter),
214 LOADING_COL_PULSE,
215 self->priv->current_spin_count,
216 -1);
217 return TRUE;
220 static void
221 git_log_pane_set_view_mode (GitLogPane *self, LogViewMode mode)
223 GtkNotebook *loading_notebook;
225 loading_notebook = GTK_NOTEBOOK (gtk_builder_get_object (self->priv->builder,
226 "loading_notebook"));
228 switch (mode)
230 case LOG_VIEW_LOADING:
231 /* Don't create more than one timer */
232 if (self->priv->spin_timer_id <= 0)
234 self->priv->spin_timer_id = g_timeout_add ((guint) self->priv->spin_cycle_duration / self->priv->spin_cycle_steps,
235 (GSourceFunc) on_spinner_timeout,
236 self);
239 break;
240 case LOG_VIEW_NORMAL:
241 if (self->priv->spin_timer_id > 0)
243 g_source_remove (self->priv->spin_timer_id);
244 self->priv->spin_timer_id = 0;
247 /* Reset the spinner */
248 self->priv->current_spin_count = 0;
250 gtk_list_store_set (self->priv->log_loading_model,
251 &(self->priv->spinner_iter),
252 LOADING_COL_PULSE, 0,
253 -1);
254 break;
255 default:
256 break;
259 gtk_notebook_set_current_page (loading_notebook, mode);
262 static void
263 on_log_command_finished (AnjutaCommand *command, guint return_code,
264 GitLogPane *self)
266 GtkTreeView *log_view;
267 GQueue *queue;
268 GtkTreeIter iter;
269 GitRevision *revision;
271 /* Show the actual log view */
272 git_log_pane_set_view_mode (self, LOG_VIEW_NORMAL);
274 if (return_code != 0)
276 git_pane_report_errors (command, return_code,
277 ANJUTA_PLUGIN_GIT (anjuta_dock_pane_get_plugin (ANJUTA_DOCK_PANE (self))));
278 g_object_unref (command);
280 return;
283 log_view = GTK_TREE_VIEW (gtk_builder_get_object (self->priv->builder,
284 "log_view"));
286 g_object_ref (self->priv->log_model);
287 gtk_tree_view_set_model (GTK_TREE_VIEW (log_view), NULL);
289 queue = git_log_command_get_output_queue (GIT_LOG_COMMAND (command));
291 while (g_queue_peek_head (queue))
293 revision = g_queue_pop_head (queue);
295 gtk_list_store_append (self->priv->log_model, &iter);
296 gtk_list_store_set (self->priv->log_model, &iter, LOG_COL_REVISION,
297 revision, -1);
299 g_object_unref (revision);
302 giggle_graph_renderer_validate_model (GIGGLE_GRAPH_RENDERER (self->priv->graph_renderer),
303 GTK_TREE_MODEL (self->priv->log_model),
305 gtk_tree_view_set_model (GTK_TREE_VIEW (log_view),
306 GTK_TREE_MODEL (self->priv->log_model));
307 g_object_unref (self->priv->log_model);
309 g_object_unref (command);
312 static void
313 refresh_log (GitLogPane *self)
315 Git *plugin;
316 GtkTreeViewColumn *graph_column;
317 GitLogCommand *log_command;
319 plugin = ANJUTA_PLUGIN_GIT (anjuta_dock_pane_get_plugin (ANJUTA_DOCK_PANE (self)));
320 graph_column = GTK_TREE_VIEW_COLUMN (gtk_builder_get_object (self->priv->builder,
321 "graph_column"));
323 /* We don't support filters for now */
324 log_command = git_log_command_new (plugin->project_root_directory,
325 self->priv->selected_branch,
326 self->priv->path,
327 NULL,
328 NULL,
329 NULL,
330 NULL,
331 NULL,
332 NULL);
334 /* Hide the graph column if we're looking at the log of a path. The graph
335 * won't be correct in this case. */
336 if (self->priv->path)
337 gtk_tree_view_column_set_visible (graph_column, FALSE);
338 else
339 gtk_tree_view_column_set_visible (graph_column, TRUE);
341 g_signal_connect (G_OBJECT (log_command), "command-finished",
342 G_CALLBACK (on_log_command_finished),
343 self);
345 gtk_list_store_clear (self->priv->log_model);
347 /* Show the loading spinner */
348 git_log_pane_set_view_mode (self, LOG_VIEW_LOADING);
350 anjuta_command_start (ANJUTA_COMMAND (log_command));
353 static void
354 on_ref_command_finished (AnjutaCommand *command, guint return_code,
355 GitLogPane *self)
357 Git *plugin;
358 GitBranchListCommand *branch_list_command;
360 plugin = ANJUTA_PLUGIN_GIT (anjuta_dock_pane_get_plugin (ANJUTA_DOCK_PANE (self)));
362 if (self->priv->refs)
363 g_hash_table_unref (self->priv->refs);
365 self->priv->refs = git_ref_command_get_refs (GIT_REF_COMMAND (command));
367 /* Refresh the branch display after the refs get updated */
368 branch_list_command = git_branch_list_command_new (plugin->project_root_directory,
369 GIT_BRANCH_TYPE_ALL);
371 g_signal_connect (G_OBJECT (branch_list_command), "command-started",
372 G_CALLBACK (on_branch_list_command_started),
373 self);
375 g_signal_connect (G_OBJECT (branch_list_command), "command-finished",
376 G_CALLBACK (on_branch_list_command_finished),
377 self);
379 g_signal_connect (G_OBJECT (branch_list_command), "command-finished",
380 G_CALLBACK (g_object_unref),
381 NULL);
383 g_signal_connect (G_OBJECT (branch_list_command), "data-arrived",
384 G_CALLBACK (on_branch_list_command_data_arrived),
385 self);
387 anjuta_command_start (ANJUTA_COMMAND (branch_list_command));
390 static void
391 on_branch_combo_changed (GtkComboBox *combo_box, GitLogPane *self)
393 GtkTreeModel *log_branch_combo_model;
394 gchar *branch;
395 GtkTreeIter iter;
396 gboolean active;
398 log_branch_combo_model = gtk_combo_box_get_model (combo_box);
400 if (gtk_combo_box_get_active_iter (combo_box, &iter))
402 gtk_tree_model_get (log_branch_combo_model, &iter,
403 BRANCH_COL_ACTIVE, &active,
404 BRANCH_COL_NAME, &branch,
405 -1);
407 self->priv->viewing_active_branch = active;
409 g_free (self->priv->selected_branch);
410 self->priv->selected_branch = g_strdup (branch);
412 g_free (branch);
414 /* Refresh the log after the branches are refreshed so that everything
415 * happens in a consistent order (refs, branches, log) and that
416 * the log only gets refreshed once. Doing the refresh here also
417 * lets us kill two birds with one stone: we can handle refreshes and
418 * different branch selections all in one place. */
419 refresh_log (self);
424 static void
425 author_cell_function (GtkTreeViewColumn *column, GtkCellRenderer *renderer,
426 GtkTreeModel *model, GtkTreeIter *iter,
427 gpointer user_data)
429 GitRevision *revision;
430 gchar *author;
432 gtk_tree_model_get (model, iter, LOG_COL_REVISION, &revision, -1);
433 author = git_revision_get_author (revision);
435 g_object_unref (revision);
437 g_object_set (renderer, "text", author, NULL);
439 g_free (author);
442 static void
443 date_cell_function (GtkTreeViewColumn *column, GtkCellRenderer *renderer,
444 GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data)
446 GitRevision *revision;
447 gchar *date;
449 gtk_tree_model_get (model, iter, LOG_COL_REVISION, &revision, -1);
450 date = git_revision_get_formatted_date (revision);
452 g_object_unref (revision);
454 g_object_set (renderer, "text", date, NULL);
456 g_free (date);
459 static void
460 short_log_cell_function (GtkTreeViewColumn *column, GtkCellRenderer *renderer,
461 GtkTreeModel *model, GtkTreeIter *iter,
462 gpointer user_data)
464 GitRevision *revision;
465 gchar *short_log;
467 gtk_tree_model_get (model, iter, LOG_COL_REVISION, &revision, -1);
468 short_log = git_revision_get_short_log (revision);
470 g_object_unref (revision);
472 g_object_set (renderer, "text", short_log, NULL);
474 g_free (short_log);
477 static void
478 ref_icon_cell_function (GtkTreeViewColumn *column, GtkCellRenderer *renderer,
479 GtkTreeModel *model, GtkTreeIter *iter,
480 GitLogPane *self)
482 GitRevision *revision;
483 gchar *sha;
485 gtk_tree_model_get (model, iter, LOG_COL_REVISION, &revision, -1);
486 sha = git_revision_get_sha (revision);
488 g_object_unref (revision);
490 if (g_hash_table_lookup_extended (self->priv->refs, sha, NULL, NULL))
491 g_object_set (renderer, "stock-id", GTK_STOCK_INFO, NULL);
492 else
493 g_object_set (renderer, "stock-id", "", NULL);
495 g_free (sha);
498 static gboolean
499 on_log_view_query_tooltip (GtkWidget *log_view, gint x, gint y,
500 gboolean keyboard_mode, GtkTooltip *tooltip,
501 GitLogPane *self)
503 gboolean ret;
504 GtkTreeViewColumn *ref_icon_column;
505 gint bin_x;
506 gint bin_y;
507 GtkTreeViewColumn *current_column;
508 GtkTreePath *path;
509 GtkTreeModel *model;
510 GtkTreeIter iter;
511 GitRevision *revision;
512 gchar *sha;
513 GList *ref_list;
514 GList *current_ref;
515 GString *tooltip_string;
516 gchar *ref_name;
517 GitRefType ref_type;
519 ret = FALSE;
521 ref_icon_column = gtk_tree_view_get_column (GTK_TREE_VIEW (log_view), 0);
523 gtk_tree_view_convert_widget_to_bin_window_coords (GTK_TREE_VIEW (log_view),
524 x, y, &bin_x, &bin_y);
525 if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (log_view), bin_x,
526 bin_y, &path, &current_column, NULL,
527 NULL))
529 /* We need to be in the ref icon column */
530 if (current_column == ref_icon_column)
532 model = gtk_tree_view_get_model (GTK_TREE_VIEW (log_view));
533 gtk_tree_model_get_iter (model, &iter, path);
535 gtk_tree_model_get (model, &iter, LOG_COL_REVISION, &revision, -1);
536 sha = git_revision_get_sha (revision);
538 g_object_unref (revision);
540 ref_list = g_hash_table_lookup (self->priv->refs, sha);
541 g_free (sha);
543 if (ref_list)
545 current_ref = ref_list;
546 tooltip_string = g_string_new ("");
548 while (current_ref)
550 ref_name = git_ref_get_name (GIT_REF (current_ref->data));
551 ref_type = git_ref_get_ref_type (GIT_REF (current_ref->data));
553 if (tooltip_string->len > 0)
554 g_string_append (tooltip_string, "\n");
556 switch (ref_type)
558 case GIT_REF_TYPE_BRANCH:
559 g_string_append_printf (tooltip_string,
560 _("<b>Branch:</b> %s"),
561 ref_name );
562 break;
563 case GIT_REF_TYPE_TAG:
564 g_string_append_printf (tooltip_string,
565 _("<b>Tag:</b> %s"),
566 ref_name);
567 break;
568 case GIT_REF_TYPE_REMOTE:
569 g_string_append_printf (tooltip_string,
570 _("<b>Remote:</b> %s"),
571 ref_name);
572 break;
573 default:
574 break;
577 g_free (ref_name);
578 current_ref = g_list_next (current_ref);
581 gtk_tooltip_set_markup (tooltip, tooltip_string->str);
582 g_string_free (tooltip_string, TRUE);
584 ret = TRUE;
588 gtk_tree_path_free (path);
591 return ret;
594 static void
595 on_log_message_command_finished (AnjutaCommand *command, guint return_code,
596 GitLogPane *self)
598 GtkWidget *log_text_view;
599 GtkTextBuffer *buffer;
600 gchar *log_message;
602 log_text_view = GTK_WIDGET (gtk_builder_get_object (self->priv->builder,
603 "log_text_view"));
604 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (log_text_view));
605 log_message = git_log_message_command_get_message (GIT_LOG_MESSAGE_COMMAND (command));
607 gtk_text_buffer_set_text (buffer, log_message, strlen (log_message));
609 g_free (log_message);
610 g_object_unref (command);
613 static gboolean
614 on_log_view_row_selected (GtkTreeSelection *selection,
615 GtkTreeModel *model,
616 GtkTreePath *path,
617 gboolean path_currently_selected,
618 GitLogPane *self)
620 Git *plugin;
621 GtkTreeIter iter;
622 GitRevision *revision;
623 gchar *sha;
624 GitLogMessageCommand *log_message_command;
626 if (!path_currently_selected)
628 plugin = ANJUTA_PLUGIN_GIT (anjuta_dock_pane_get_plugin (ANJUTA_DOCK_PANE (self)));
630 gtk_tree_model_get_iter (model, &iter, path);
631 gtk_tree_model_get (model, &iter, LOG_COL_REVISION, &revision, -1);
632 sha = git_revision_get_sha (revision);
634 log_message_command = git_log_message_command_new (plugin->project_root_directory,
635 sha);
637 g_free (sha);
638 g_object_unref (revision);
640 g_signal_connect (G_OBJECT (log_message_command), "command-finished",
641 G_CALLBACK (on_log_message_command_finished),
642 self);
644 anjuta_command_start (ANJUTA_COMMAND (log_message_command));
647 return TRUE;
650 static void
651 on_log_view_drag_data_get (GtkWidget *log_view,
652 GdkDragContext *drag_context,
653 GtkSelectionData *data,
654 guint info, guint time,
655 GitLogPane *self)
657 GtkTreeSelection *selection;
658 GtkTreeIter iter;
659 GitRevision *revision;
660 gchar *sha;
662 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (log_view));
664 if (gtk_tree_selection_count_selected_rows (selection) > 0)
666 gtk_tree_selection_get_selected (selection, NULL, &iter);
668 gtk_tree_model_get (GTK_TREE_MODEL (self->priv->log_model), &iter,
669 0, &revision, -1);
671 sha = git_revision_get_sha (revision);
673 gtk_selection_data_set_text (data, sha, -1);
675 g_object_unref (revision);
676 g_free (sha);
680 static void
681 on_log_pane_drag_data_received (GtkWidget *widget,
682 GdkDragContext *context, gint x, gint y,
683 GtkSelectionData *data, guint target_type,
684 guint time, GitLogPane *self)
686 Git *plugin;
687 AnjutaEntry *path_entry;
688 gboolean success;
689 gchar **uri_list;
690 GFile *parent_file;
691 GFile *file;
692 gchar *path;
694 plugin = ANJUTA_PLUGIN_GIT (anjuta_dock_pane_get_plugin (ANJUTA_DOCK_PANE (self)));
695 path_entry = ANJUTA_ENTRY (gtk_builder_get_object (self->priv->builder,
696 "path_entry"));
697 success = FALSE;
699 if ((data != NULL) &&
700 (gtk_selection_data_get_length (data) >= 0))
702 if (target_type == 0)
704 uri_list = gtk_selection_data_get_uris (data);
705 parent_file = NULL;
707 parent_file = g_file_new_for_path (plugin->project_root_directory);
709 /* Take only the first file */
710 file = g_file_new_for_uri (uri_list[0]);
712 if (parent_file)
714 path = g_file_get_relative_path (parent_file, file);
716 g_object_unref (parent_file);
718 else
719 path = g_file_get_path (file);
721 if (path)
723 anjuta_entry_set_text (path_entry, path);
725 g_free (self->priv->path);
726 self->priv->path = g_strdup (path);
728 refresh_log (self);
730 g_free (path);
733 success = TRUE;
735 g_object_unref (file);
736 g_strfreev (uri_list);
740 /* Do not delete source data */
741 gtk_drag_finish (context, success, FALSE, time);
744 static gboolean
745 on_log_pane_drag_drop (GtkWidget *widget, GdkDragContext *context,
746 gint x, gint y, guint time,
747 GitLogPane *self)
749 GdkAtom target_type;
751 target_type = gtk_drag_dest_find_target (widget, context, NULL);
753 if (target_type != GDK_NONE)
754 gtk_drag_get_data (widget, context, target_type, time);
755 else
756 gtk_drag_finish (context, FALSE, FALSE, time);
758 return TRUE;
761 static void
762 on_path_entry_icon_release (GtkEntry *entry,
763 GtkEntryIconPosition position,
764 GdkEvent *event,
765 GitLogPane *self)
767 if (position == GTK_ENTRY_ICON_SECONDARY)
769 if (self->priv->path)
771 g_free (self->priv->path);
772 self->priv->path = NULL;
774 refresh_log (self);
779 static void
780 git_log_pane_init (GitLogPane *self)
782 gchar *objects[] = {"log_pane",
783 "log_branch_combo_model",
784 "log_loading_model",
785 "find_button_image",
786 NULL};
787 GError *error = NULL;
788 GtkWidget *log_pane;
789 GtkWidget *path_entry;
790 GtkTreeView *log_view;
791 GtkTreeViewColumn *ref_icon_column;
792 GtkTreeViewColumn *graph_column;
793 GtkTreeViewColumn *short_log_column;
794 GtkTreeViewColumn *author_column;
795 GtkTreeViewColumn *date_column;
796 GtkCellRenderer *ref_icon_renderer;
797 GtkCellRenderer *short_log_renderer;
798 GtkCellRenderer *author_renderer;
799 GtkCellRenderer *date_renderer;
800 GtkTreeViewColumn *loading_spinner_column;
801 GtkCellRenderer *loading_spinner_renderer;
802 GtkCellRenderer *loading_indicator_renderer;
803 GtkStyle *style;
804 GValue cycle_duration_value = {0,};
805 GValue num_steps_value = {0,};
806 GtkComboBox *branch_combo;
807 GtkTreeSelection *selection;
809 self->priv = g_new0 (GitLogPanePriv, 1);
810 self->priv->builder = gtk_builder_new ();
812 if (!gtk_builder_add_objects_from_file (self->priv->builder, BUILDER_FILE,
813 objects,
814 &error))
816 g_warning ("Couldn't load builder file: %s", error->message);
817 g_error_free (error);
820 log_pane = GTK_WIDGET (gtk_builder_get_object (self->priv->builder,
821 "log_pane"));
822 path_entry = GTK_WIDGET (gtk_builder_get_object (self->priv->builder,
823 "path_entry"));
824 log_view = GTK_TREE_VIEW (gtk_builder_get_object (self->priv->builder,
825 "log_view"));
826 ref_icon_column = GTK_TREE_VIEW_COLUMN (gtk_builder_get_object (self->priv->builder,
827 "ref_icon_column"));
828 graph_column = GTK_TREE_VIEW_COLUMN (gtk_builder_get_object (self->priv->builder,
829 "graph_column"));
830 short_log_column = GTK_TREE_VIEW_COLUMN (gtk_builder_get_object (self->priv->builder,
831 "short_log_column"));
832 author_column = GTK_TREE_VIEW_COLUMN (gtk_builder_get_object (self->priv->builder,
833 "author_column"));
834 date_column = GTK_TREE_VIEW_COLUMN (gtk_builder_get_object (self->priv->builder,
835 "date_column"));
836 ref_icon_renderer = GTK_CELL_RENDERER (gtk_builder_get_object (self->priv->builder,
837 "ref_icon_renderer"));
838 author_renderer = GTK_CELL_RENDERER (gtk_builder_get_object (self->priv->builder,
839 "author_renderer"));
840 date_renderer = GTK_CELL_RENDERER (gtk_builder_get_object (self->priv->builder,
841 "date_renderer"));
842 branch_combo = GTK_COMBO_BOX (gtk_builder_get_object (self->priv->builder,
843 "branch_combo"));
844 loading_spinner_column = GTK_TREE_VIEW_COLUMN (gtk_builder_get_object (self->priv->builder,
845 "loading_spinner_column"));
846 selection = gtk_tree_view_get_selection (log_view);
848 /* Path entry */
849 g_signal_connect (G_OBJECT (path_entry), "icon-release",
850 G_CALLBACK (on_path_entry_icon_release),
851 self);
853 /* Set up the log model */
854 self->priv->log_model = gtk_list_store_new (1, GIT_TYPE_REVISION);
856 /* Ref icon column */
857 gtk_tree_view_column_set_cell_data_func (ref_icon_column, ref_icon_renderer,
858 (GtkTreeCellDataFunc) ref_icon_cell_function,
859 self, NULL);
862 /* Graph column */
863 self->priv->graph_renderer = giggle_graph_renderer_new ();
865 gtk_tree_view_column_pack_start (graph_column, self->priv->graph_renderer,
866 TRUE);
867 gtk_tree_view_column_add_attribute (graph_column, self->priv->graph_renderer,
868 "revision", 0);
870 /* Short log column. We have to create this render ouselves becuause Glade
871 * doesn't seem to give us to option to pack it with expand */
872 short_log_renderer = gtk_cell_renderer_text_new ();
873 g_object_set (G_OBJECT (short_log_renderer), "ellipsize",
874 PANGO_ELLIPSIZE_END, NULL);
875 gtk_tree_view_column_pack_start (short_log_column, short_log_renderer,
876 TRUE);
877 gtk_tree_view_column_set_cell_data_func (short_log_column, short_log_renderer,
878 (GtkTreeCellDataFunc) short_log_cell_function,
879 NULL, NULL);
881 /* Author column */
882 gtk_tree_view_column_set_cell_data_func (author_column, author_renderer,
883 (GtkTreeCellDataFunc) author_cell_function,
884 NULL, NULL);
886 /* Date column */
887 gtk_tree_view_column_set_cell_data_func (date_column, date_renderer,
888 (GtkTreeCellDataFunc) date_cell_function,
889 NULL, NULL);
891 gtk_tree_view_set_model (log_view, GTK_TREE_MODEL (self->priv->log_model));
893 /* Ref icon tooltip */
894 g_signal_connect (G_OBJECT (log_view), "query-tooltip",
895 G_CALLBACK (on_log_view_query_tooltip),
896 self);
898 /* Loading indicator. The loading indicator is a second tree view display
899 * that looks just like the real log display, except that it displays a
900 * spinner renderer and the text "Loading..." in the Short Log column. */
901 self->priv->log_loading_model = GTK_LIST_STORE (gtk_builder_get_object (self->priv->builder,
902 "log_loading_model"));
903 loading_spinner_renderer = gtk_cell_renderer_spinner_new ();
904 loading_indicator_renderer = gtk_cell_renderer_text_new ();
906 g_object_set (G_OBJECT (loading_spinner_renderer), "active", TRUE, NULL);
908 gtk_tree_view_column_pack_start (loading_spinner_column,
909 loading_spinner_renderer, FALSE);
910 gtk_tree_view_column_pack_start (loading_spinner_column,
911 loading_indicator_renderer, TRUE);
912 gtk_tree_view_column_add_attribute (loading_spinner_column,
913 loading_spinner_renderer,
914 "pulse", LOADING_COL_PULSE);
915 gtk_tree_view_column_add_attribute (loading_spinner_column,
916 loading_indicator_renderer,
917 "text", LOADING_COL_INDICATOR);
919 /* DnD source */
920 gtk_tree_view_enable_model_drag_source (log_view,
921 GDK_BUTTON1_MASK,
922 drag_source_targets,
923 G_N_ELEMENTS (drag_source_targets),
924 GDK_ACTION_COPY);
926 g_signal_connect (G_OBJECT (log_view), "drag-data-get",
927 G_CALLBACK (on_log_view_drag_data_get),
928 self);
930 /* DnD target. Use this as a means of selecting a file to view the
931 * log of. Files or folders would normally be dragged in from the file
932 * manager, but they can come from any source that supports URI's. */
933 gtk_drag_dest_set (log_pane,
934 GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT,
935 drag_target_targets,
936 G_N_ELEMENTS (drag_target_targets),
937 GDK_ACTION_COPY | GDK_ACTION_MOVE);
939 g_signal_connect (G_OBJECT (log_pane), "drag-data-received",
940 G_CALLBACK (on_log_pane_drag_data_received),
941 self);
943 g_signal_connect (G_OBJECT (log_pane), "drag-drop",
944 G_CALLBACK (on_log_pane_drag_drop),
945 self);
947 /* The loading view always has one row. Cache a copy of its iter for easy
948 * access. */
949 gtk_tree_model_get_iter_first (GTK_TREE_MODEL (self->priv->log_loading_model),
950 &(self->priv->spinner_iter));
952 /* Get some information about spinner cycles from the theme */
953 style = gtk_widget_get_style (GTK_WIDGET (log_view));
954 g_value_init (&cycle_duration_value, G_TYPE_UINT);
955 g_value_init (&num_steps_value, G_TYPE_UINT);
957 gtk_style_get_style_property (style, GTK_TYPE_SPINNER, "cycle-duration",
958 &cycle_duration_value);
959 gtk_style_get_style_property (style, GTK_TYPE_SPINNER, "num_steps",
960 &num_steps_value);
962 self->priv->spin_cycle_duration = g_value_get_uint (&cycle_duration_value);
963 self->priv->spin_cycle_steps = g_value_get_uint (&num_steps_value);
965 g_value_unset (&cycle_duration_value);
966 g_value_unset (&num_steps_value);
968 g_object_set (G_OBJECT (loading_spinner_renderer), "active", TRUE, NULL);
970 /* Log message display */
971 gtk_tree_selection_set_select_function (selection,
972 (GtkTreeSelectionFunc) on_log_view_row_selected,
973 self, NULL);
975 /* Branch handling */
976 self->priv->branches_table = g_hash_table_new_full (g_str_hash, g_str_equal,
977 g_free, g_free);
979 g_signal_connect (G_OBJECT (branch_combo), "changed",
980 G_CALLBACK (on_branch_combo_changed),
981 self);
985 static void
986 git_log_pane_finalize (GObject *object)
988 GitLogPane *self;
990 self = GIT_LOG_PANE (object);
992 g_object_unref (self->priv->builder);
993 g_free (self->priv->path);
994 g_hash_table_destroy (self->priv->branches_table);
995 g_hash_table_unref (self->priv->refs);
996 g_free (self->priv->selected_branch);
997 g_free (self->priv);
999 G_OBJECT_CLASS (git_log_pane_parent_class)->finalize (object);
1002 static GtkWidget *
1003 git_log_pane_get_widget (AnjutaDockPane *pane)
1005 GitLogPane *self;
1007 self = GIT_LOG_PANE (pane);
1009 return GTK_WIDGET (gtk_builder_get_object (self->priv->builder,
1010 "log_pane"));
1013 static void
1014 git_log_pane_class_init (GitLogPaneClass *klass)
1016 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1017 AnjutaDockPaneClass *pane_class = ANJUTA_DOCK_PANE_CLASS (klass);
1019 object_class->finalize = git_log_pane_finalize;
1020 pane_class->get_widget = git_log_pane_get_widget;
1021 pane_class->refresh = NULL;
1025 AnjutaDockPane *
1026 git_log_pane_new (Git *plugin)
1028 GitLogPane *self;
1030 self = g_object_new (GIT_TYPE_LOG_PANE, "plugin", plugin, NULL);
1032 g_signal_connect (G_OBJECT (plugin->ref_command), "command-finished",
1033 G_CALLBACK (on_ref_command_finished),
1034 self);
1036 return ANJUTA_DOCK_PANE (self);
1039 void
1040 git_log_pane_set_working_directory (GitLogPane *self,
1041 const gchar *working_directory)
1043 /* TODO: Add public function implementation here */
1046 GitRevision *
1047 git_log_pane_get_selected_revision (GitLogPane *self)
1049 GtkTreeView *log_view;
1050 GtkTreeSelection *selection;
1051 GitRevision *revision;
1052 GtkTreeIter iter;
1054 log_view = GTK_TREE_VIEW (gtk_builder_get_object (self->priv->builder,
1055 "log_view"));
1056 selection = gtk_tree_view_get_selection (log_view);
1057 revision = NULL;
1059 if (gtk_tree_selection_get_selected (selection, NULL, &iter))
1061 gtk_tree_model_get (GTK_TREE_MODEL (self->priv->log_model), &iter,
1062 LOG_COL_REVISION, &revision, -1);
1065 return revision;