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.
10  *
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.
15  *
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/>.
18  */
19 
20 #include "git-log-pane.h"
21 
22 enum
23 {
24 	LOG_COL_REVISION
25 };
26 
27 enum
28 {
29 	LOADING_COL_PULSE,
30 	LOADING_COL_INDICATOR
31 };
32 
33 /* Loading view modes */
34 typedef enum
35 {
36 	/* Show the real log viewer */
37 	LOG_VIEW_NORMAL,
38 
39 	/* Show the loading view */
40 	LOG_VIEW_LOADING
41 } LogViewMode;
42 
43 enum
44 {
45 	BRANCH_COL_ACTIVE,
46 	BRANCH_COL_ACTIVE_ICON,
47 	BRANCH_COL_NAME
48 };
49 
50 /* DnD source targets */
51 static GtkTargetEntry drag_source_targets[] =
52 {
53 	{
54 		"STRING",
55 		0,
56 		0
57 	}
58 };
59 
60 /* DnD target targets */
61 static GtkTargetEntry drag_target_targets[] =
62 {
63 	{
64 		"text/uri-list",
65 		0,
66 		0
67 	}
68 };
69 
70 struct _GitLogPanePriv
71 {
72 	GtkBuilder *builder;
73 	GtkListStore *log_model;
74 	GtkCellRenderer *graph_renderer;
75 	GHashTable *refs;
76 	gchar *path;
77 
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 	GtkTreePath *active_branch_path;
86 
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;
94 
95 	/* Commands */
96 	GitBranchListCommand *branch_list_command;
97 	GitLogMessageCommand *log_message_command;
98 	GitLogCommand        *log_command;
99 };
100 
101 G_DEFINE_TYPE (GitLogPane, git_log_pane, GIT_TYPE_PANE);
102 
103 static void
on_branch_list_command_started(AnjutaCommand * command,GitLogPane * self)104 on_branch_list_command_started (AnjutaCommand *command,
105                                 GitLogPane *self)
106 {
107 	GtkComboBox *branch_combo;
108 	GtkListStore *log_branch_combo_model;
109 
110 	branch_combo = GTK_COMBO_BOX (gtk_builder_get_object (self->priv->builder,
111 	                                                       "branch_combo"));
112 	log_branch_combo_model = GTK_LIST_STORE (gtk_builder_get_object (self->priv->builder,
113 	                                                                 "log_branch_combo_model"));
114 
115 	gtk_combo_box_set_model (branch_combo, NULL);
116 	gtk_list_store_clear (log_branch_combo_model);
117 
118 	g_hash_table_remove_all (self->priv->branches_table);
119 }
120 
121 static void
on_branch_list_command_finished(AnjutaCommand * command,guint return_code,GitLogPane * self)122 on_branch_list_command_finished (AnjutaCommand *command,
123                                  guint return_code,
124                                  GitLogPane *self)
125 {
126 	GtkComboBox *branch_combo;
127 	GtkTreeModel *log_branch_combo_model;
128 	GtkTreePath *path;
129 	GtkTreeIter iter;
130 
131 	branch_combo = GTK_COMBO_BOX (gtk_builder_get_object (self->priv->builder,
132 	                                                          "branch_combo"));
133 	log_branch_combo_model = GTK_TREE_MODEL (gtk_builder_get_object (self->priv->builder,
134 	                                                                 "log_branch_combo_model"));
135 
136 	gtk_combo_box_set_model (branch_combo, log_branch_combo_model);
137 
138 	/* If the user was viewing the active branch, switch to the newly active
139 	 * branch if it changes. If another branch was being viewed, stay on that
140 	 * one */
141 	if ((!self->priv->viewing_active_branch) &&
142 	    (self->priv->selected_branch &&
143 	    g_hash_table_lookup_extended (self->priv->branches_table,
144 	                                  self->priv->selected_branch, NULL,
145 	                                  (gpointer) &path)))
146 	{
147 		gtk_tree_model_get_iter (log_branch_combo_model, &iter, path);
148 		gtk_combo_box_set_active_iter (branch_combo, &iter);
149 	}
150 	else if (self->priv->active_branch_path != NULL)
151 	{
152 		gtk_tree_model_get_iter (log_branch_combo_model, &iter,
153 		                         self->priv->active_branch_path);
154 		gtk_combo_box_set_active_iter (branch_combo, &iter);
155 	}
156 
157 	g_clear_object (&self->priv->branch_list_command);
158 }
159 
160 static void
on_branch_list_command_data_arrived(AnjutaCommand * command,GitLogPane * self)161 on_branch_list_command_data_arrived (AnjutaCommand *command,
162                                      GitLogPane *self)
163 {
164 	GtkListStore *log_branch_combo_model;
165 	GList *current_branch;
166 	GitBranch *branch;
167 	gchar *name;
168 	GtkTreeIter iter;
169 
170 	log_branch_combo_model = GTK_LIST_STORE (gtk_builder_get_object (self->priv->builder,
171 	                                                                 "log_branch_combo_model"));
172 	current_branch = git_branch_list_command_get_output (GIT_BRANCH_LIST_COMMAND (command));
173 
174 	while (current_branch)
175 	{
176 		branch = current_branch->data;
177 		name = git_branch_get_name (branch);
178 
179 		gtk_list_store_append (log_branch_combo_model, &iter);
180 
181 		if (git_branch_is_active (branch))
182 		{
183 			gtk_list_store_set (log_branch_combo_model, &iter,
184 			                    BRANCH_COL_ACTIVE, TRUE,
185 			                    BRANCH_COL_ACTIVE_ICON, GTK_STOCK_APPLY,
186 			                    -1);
187 
188 			if (self->priv->active_branch_path != NULL)
189 				gtk_tree_path_free (self->priv->active_branch_path);
190 
191 			self->priv->active_branch_path = gtk_tree_model_get_path (GTK_TREE_MODEL (log_branch_combo_model),
192 			                                                          &iter);
193 		}
194 		else
195 		{
196 			gtk_list_store_set (log_branch_combo_model, &iter,
197 			                    BRANCH_COL_ACTIVE, FALSE,
198 			                    BRANCH_COL_ACTIVE_ICON, NULL,
199 			                    -1);
200 		}
201 
202 		gtk_list_store_set (log_branch_combo_model, &iter,
203 		                    BRANCH_COL_NAME, name,
204 		                    -1);
205 		g_hash_table_insert (self->priv->branches_table, g_strdup (name),
206 		                     gtk_tree_model_get_path (GTK_TREE_MODEL (log_branch_combo_model),
207 		                                              &iter));
208 
209 		g_free (name);
210 
211 		current_branch = g_list_next (current_branch);
212 	}
213 
214 }
215 
216 static gboolean
on_spinner_timeout(GitLogPane * self)217 on_spinner_timeout (GitLogPane *self)
218 {
219 	if (self->priv->current_spin_count == self->priv->spin_cycle_steps)
220 		self->priv->current_spin_count = 0;
221 	else
222 		self->priv->current_spin_count++;
223 
224 	gtk_list_store_set (self->priv->log_loading_model,
225 	                    &(self->priv->spinner_iter),
226 	                    LOADING_COL_PULSE,
227 	                    self->priv->current_spin_count,
228 	                    -1);
229 	return TRUE;
230 }
231 
232 static void
git_log_pane_set_view_mode(GitLogPane * self,LogViewMode mode)233 git_log_pane_set_view_mode (GitLogPane *self, LogViewMode mode)
234 {
235 	GtkNotebook *loading_notebook;
236 
237 	loading_notebook = GTK_NOTEBOOK (gtk_builder_get_object (self->priv->builder,
238 	                                                         "loading_notebook"));
239 
240 	switch (mode)
241 	{
242 		case LOG_VIEW_LOADING:
243 			/* Don't create more than one timer */
244 			if (self->priv->spin_timer_id <= 0)
245 			{
246 				self->priv->spin_timer_id = g_timeout_add ((guint) self->priv->spin_cycle_duration / self->priv->spin_cycle_steps,
247 				                                           (GSourceFunc) on_spinner_timeout,
248 				                                           self);
249 			}
250 
251 			break;
252 		case LOG_VIEW_NORMAL:
253 			if (self->priv->spin_timer_id > 0)
254 			{
255 				g_source_remove (self->priv->spin_timer_id);
256 				self->priv->spin_timer_id = 0;
257 			}
258 
259 			/* Reset the spinner */
260 			self->priv->current_spin_count = 0;
261 
262 			gtk_list_store_set (self->priv->log_loading_model,
263 			                    &(self->priv->spinner_iter),
264 			                    LOADING_COL_PULSE, 0,
265 			                    -1);
266 			break;
267 		default:
268 			break;
269 	}
270 
271 	gtk_notebook_set_current_page (loading_notebook, mode);
272 }
273 
274 static void
on_log_command_finished(AnjutaCommand * command,guint return_code,GitLogPane * self)275 on_log_command_finished (AnjutaCommand *command, guint return_code,
276 						 GitLogPane *self)
277 {
278 	GtkTreeView *log_view;
279 	GQueue *queue;
280 	GtkTreeIter iter;
281 	GitRevision *revision;
282 
283 	/* Show the actual log view */
284 	git_log_pane_set_view_mode (self, LOG_VIEW_NORMAL);
285 
286 	log_view = GTK_TREE_VIEW (gtk_builder_get_object (self->priv->builder,
287 	                                                  "log_view"));
288 
289 	if (return_code != 0)
290 	{
291 		/* Don't report erros in the log view as this is usually no user requested
292 		 * operation and thus error messages are confusing instead just show an
293 		 * empty log.
294 		 */
295 #if 0
296 		git_pane_report_errors (command, return_code,
297 		                        ANJUTA_PLUGIN_GIT (anjuta_dock_pane_get_plugin (ANJUTA_DOCK_PANE (self))));
298 #endif
299 		goto out;
300 	}
301 
302 	queue = git_log_command_get_output_queue (GIT_LOG_COMMAND (command));
303 
304 	while (g_queue_peek_head (queue))
305 	{
306 		revision = g_queue_pop_head (queue);
307 
308 		gtk_list_store_append (self->priv->log_model, &iter);
309 		gtk_list_store_set (self->priv->log_model, &iter, LOG_COL_REVISION,
310 		                    revision, -1);
311 
312 		g_object_unref (revision);
313 	}
314 
315 	giggle_graph_renderer_validate_model (GIGGLE_GRAPH_RENDERER (self->priv->graph_renderer),
316 										  GTK_TREE_MODEL (self->priv->log_model),
317 										  0);
318 	gtk_tree_view_set_model (GTK_TREE_VIEW (log_view),
319 							 GTK_TREE_MODEL (self->priv->log_model));
320 
321 out:
322 	g_clear_object (&self->priv->log_command);
323 }
324 
325 static void
refresh_log(GitLogPane * self)326 refresh_log (GitLogPane *self)
327 {
328 	Git *plugin;
329 	GtkTreeView *log_view;
330 	GtkTreeViewColumn *graph_column;
331 
332 	plugin = ANJUTA_PLUGIN_GIT (anjuta_dock_pane_get_plugin (ANJUTA_DOCK_PANE (self)));
333 	log_view = GTK_TREE_VIEW (gtk_builder_get_object (self->priv->builder,
334 	                                                  "log_view"));
335 	graph_column = GTK_TREE_VIEW_COLUMN (gtk_builder_get_object (self->priv->builder,
336 	                                                             "graph_column"));
337 
338 	/* Unref the previous command if it's still running. */
339 	if (self->priv->log_command)
340 		g_object_unref (self->priv->log_command);
341 
342 	gtk_tree_view_set_model (log_view, NULL);
343 
344 	/* We don't support filters for now */
345 	self->priv->log_command = git_log_command_new (plugin->project_root_directory,
346 	                                               self->priv->selected_branch,
347 	                                               self->priv->path,
348 	                                               NULL,
349 	                                               NULL,
350 	                                               NULL,
351 	                                               NULL,
352 	                                               NULL,
353 	                                               NULL);
354 
355 	/* Hide the graph column if we're looking at the log of a path. The graph
356 	 * won't be correct in this case. */
357 	if (self->priv->path)
358 		gtk_tree_view_column_set_visible (graph_column, FALSE);
359 	else
360 		gtk_tree_view_column_set_visible (graph_column, TRUE);
361 
362 	g_signal_connect_object (G_OBJECT (self->priv->log_command), "command-finished",
363 	                         G_CALLBACK (on_log_command_finished),
364 	                         self, 0);
365 
366 	gtk_list_store_clear (self->priv->log_model);
367 
368 	/* Show the loading spinner */
369 	git_log_pane_set_view_mode (self, LOG_VIEW_LOADING);
370 
371 	anjuta_command_start (ANJUTA_COMMAND (self->priv->log_command));
372 }
373 
374 static void
on_ref_command_finished(AnjutaCommand * command,guint return_code,GitLogPane * self)375 on_ref_command_finished (AnjutaCommand *command, guint return_code,
376                          GitLogPane *self)
377 {
378 	Git *plugin;
379 
380 	plugin = ANJUTA_PLUGIN_GIT (anjuta_dock_pane_get_plugin (ANJUTA_DOCK_PANE (self)));
381 
382 	if (self->priv->refs)
383 		g_hash_table_unref (self->priv->refs);
384 
385 	self->priv->refs = git_ref_command_get_refs (GIT_REF_COMMAND (command));
386 
387 	/* Unref the previous command if it's still running. */
388 	if (self->priv->branch_list_command)
389 		g_object_unref (self->priv->branch_list_command);
390 
391 	/* Refresh the branch display after the refs get updated */
392 	self->priv->branch_list_command =
393 		git_branch_list_command_new (plugin->project_root_directory,
394 		                             GIT_BRANCH_TYPE_ALL);
395 
396 	g_signal_connect_object (G_OBJECT (self->priv->branch_list_command), "command-started",
397 	                         G_CALLBACK (on_branch_list_command_started),
398 	                         self, 0);
399 
400 	g_signal_connect_object (G_OBJECT (self->priv->branch_list_command), "command-finished",
401 	                         G_CALLBACK (on_branch_list_command_finished),
402 	                         self, 0);
403 
404 	g_signal_connect_object (G_OBJECT (self->priv->branch_list_command), "data-arrived",
405 	                         G_CALLBACK (on_branch_list_command_data_arrived),
406 	                         self, 0);
407 
408 	anjuta_command_start (ANJUTA_COMMAND (self->priv->branch_list_command));
409 }
410 
411 static void
on_branch_combo_changed(GtkComboBox * combo_box,GitLogPane * self)412 on_branch_combo_changed (GtkComboBox *combo_box, GitLogPane *self)
413 {
414 	GtkTreeModel *log_branch_combo_model;
415 	gchar *branch;
416 	GtkTreeIter iter;
417 	gboolean active;
418 
419 	log_branch_combo_model = gtk_combo_box_get_model (combo_box);
420 
421 	if (gtk_combo_box_get_active_iter (combo_box, &iter))
422 	{
423 		gtk_tree_model_get (log_branch_combo_model, &iter,
424 		                    BRANCH_COL_ACTIVE, &active,
425 							BRANCH_COL_NAME, &branch,
426 							-1);
427 
428 		self->priv->viewing_active_branch = active;
429 
430 		g_free (self->priv->selected_branch);
431 		self->priv->selected_branch = g_strdup (branch);
432 
433 		g_free (branch);
434 
435 		/* Refresh the log after the branches are refreshed so that everything
436 		 * happens in a consistent order (refs, branches, log) and that
437 		 * the log only gets refreshed once. Doing the refresh here also
438 		 * lets us kill two birds with one stone: we can handle refreshes and
439 		 * different branch selections all in one place. */
440 		refresh_log (self);
441 	}
442 }
443 
444 
445 static void
author_cell_function(GtkTreeViewColumn * column,GtkCellRenderer * renderer,GtkTreeModel * model,GtkTreeIter * iter,gpointer user_data)446 author_cell_function (GtkTreeViewColumn *column, GtkCellRenderer *renderer,
447 					  GtkTreeModel *model, GtkTreeIter *iter,
448 					  gpointer user_data)
449 {
450 	GitRevision *revision;
451 	gchar *author;
452 
453 	gtk_tree_model_get (model, iter, LOG_COL_REVISION, &revision, -1);
454 	author = git_revision_get_author (revision);
455 
456 	g_object_unref (revision);
457 
458 	g_object_set (renderer, "text", author, NULL);
459 
460 	g_free (author);
461 }
462 
463 static void
date_cell_function(GtkTreeViewColumn * column,GtkCellRenderer * renderer,GtkTreeModel * model,GtkTreeIter * iter,gpointer user_data)464 date_cell_function (GtkTreeViewColumn *column, GtkCellRenderer *renderer,
465 					GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data)
466 {
467 	GitRevision *revision;
468 	gchar *date;
469 
470 	gtk_tree_model_get (model, iter, LOG_COL_REVISION, &revision, -1);
471 	date = git_revision_get_formatted_date (revision);
472 
473 	g_object_unref (revision);
474 
475 	g_object_set (renderer, "text", date, NULL);
476 
477 	g_free (date);
478 }
479 
480 static void
short_log_cell_function(GtkTreeViewColumn * column,GtkCellRenderer * renderer,GtkTreeModel * model,GtkTreeIter * iter,gpointer user_data)481 short_log_cell_function (GtkTreeViewColumn *column, GtkCellRenderer *renderer,
482 						 GtkTreeModel *model, GtkTreeIter *iter,
483 						 gpointer user_data)
484 {
485 	GitRevision *revision;
486 	gchar *short_log;
487 
488 	gtk_tree_model_get (model, iter, LOG_COL_REVISION, &revision, -1);
489 	short_log = git_revision_get_short_log (revision);
490 
491 	g_object_unref (revision);
492 
493 	g_object_set (renderer, "text", short_log, NULL);
494 
495 	g_free (short_log);
496 }
497 
498 static void
ref_icon_cell_function(GtkTreeViewColumn * column,GtkCellRenderer * renderer,GtkTreeModel * model,GtkTreeIter * iter,GitLogPane * self)499 ref_icon_cell_function (GtkTreeViewColumn *column, GtkCellRenderer *renderer,
500 						GtkTreeModel *model, GtkTreeIter *iter,
501 						GitLogPane *self)
502 {
503 	GitRevision *revision;
504 	gchar *sha;
505 
506 	gtk_tree_model_get (model, iter, LOG_COL_REVISION, &revision, -1);
507 	sha = git_revision_get_sha (revision);
508 
509 	g_object_unref (revision);
510 
511 	if (g_hash_table_lookup_extended (self->priv->refs, sha, NULL, NULL))
512 		g_object_set (renderer, "stock-id", GTK_STOCK_INFO, NULL);
513 	else
514 		g_object_set (renderer, "stock-id", NULL, NULL);
515 
516 	g_free (sha);
517 }
518 
519 static gboolean
on_log_view_query_tooltip(GtkWidget * log_view,gint x,gint y,gboolean keyboard_mode,GtkTooltip * tooltip,GitLogPane * self)520 on_log_view_query_tooltip (GtkWidget *log_view, gint x, gint y,
521                            gboolean keyboard_mode, GtkTooltip *tooltip,
522                            GitLogPane *self)
523 {
524 	gboolean ret;
525 	GtkTreeViewColumn *ref_icon_column;
526 	gint bin_x;
527 	gint bin_y;
528 	GtkTreeViewColumn *current_column;
529 	GtkTreePath *path;
530 	GtkTreeModel *model;
531 	GtkTreeIter iter;
532 	GitRevision *revision;
533 	gchar *sha;
534 	GList *ref_list;
535 	GList *current_ref;
536 	GString *tooltip_string;
537 	gchar *ref_name;
538 	GitRefType ref_type;
539 
540 	ret = FALSE;
541 
542 	ref_icon_column = gtk_tree_view_get_column (GTK_TREE_VIEW (log_view), 0);
543 
544 	gtk_tree_view_convert_widget_to_bin_window_coords (GTK_TREE_VIEW (log_view),
545 													   x, y, &bin_x, &bin_y);
546 	if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (log_view), bin_x,
547 									   bin_y, &path, &current_column, NULL,
548 	                                   NULL))
549 	{
550 		/* We need to be in the ref icon column */
551 		if (current_column == ref_icon_column)
552 		{
553 			model = gtk_tree_view_get_model (GTK_TREE_VIEW (log_view));
554 			gtk_tree_model_get_iter (model, &iter, path);
555 
556 			gtk_tree_model_get (model, &iter, LOG_COL_REVISION, &revision, -1);
557 			sha = git_revision_get_sha (revision);
558 
559 			g_object_unref (revision);
560 
561 			ref_list = g_hash_table_lookup (self->priv->refs, sha);
562 			g_free (sha);
563 
564 			if (ref_list)
565 			{
566 				current_ref = ref_list;
567 				tooltip_string = g_string_new ("");
568 
569 				while (current_ref)
570 				{
571 					ref_name = git_ref_get_name (GIT_REF (current_ref->data));
572 					ref_type = git_ref_get_ref_type (GIT_REF (current_ref->data));
573 
574 					if (tooltip_string->len > 0)
575 						g_string_append (tooltip_string, "\n");
576 
577 					switch (ref_type)
578 					{
579 						case GIT_REF_TYPE_BRANCH:
580 							g_string_append_printf (tooltip_string,
581 							                        _("<b>Branch:</b> %s"),
582 							                        ref_name );
583 							break;
584 						case GIT_REF_TYPE_TAG:
585 							g_string_append_printf (tooltip_string,
586 							                        _("<b>Tag:</b> %s"),
587 							                        ref_name);
588 							break;
589 						case GIT_REF_TYPE_REMOTE:
590 							g_string_append_printf (tooltip_string,
591 							                        _("<b>Remote:</b> %s"),
592 							                        ref_name);
593 							break;
594 						default:
595 							break;
596 					}
597 
598 					g_free (ref_name);
599 					current_ref = g_list_next (current_ref);
600 				}
601 
602 				gtk_tooltip_set_markup (tooltip, tooltip_string->str);
603 				g_string_free (tooltip_string, TRUE);
604 
605 				ret = TRUE;
606 			}
607 		}
608 
609 		gtk_tree_path_free (path);
610 	}
611 
612 	return ret;
613 }
614 
615 static void
on_log_message_command_finished(AnjutaCommand * command,guint return_code,GitLogPane * self)616 on_log_message_command_finished (AnjutaCommand *command, guint return_code,
617 								 GitLogPane *self)
618 {
619 	GtkWidget *log_text_view;
620 	GtkTextBuffer *buffer;
621 	gchar *log_message;
622 
623 	log_text_view = GTK_WIDGET (gtk_builder_get_object (self->priv->builder,
624 	                                                    "log_text_view"));
625 	buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (log_text_view));
626 	log_message = git_log_message_command_get_message (GIT_LOG_MESSAGE_COMMAND (command));
627 
628 	gtk_text_buffer_set_text (buffer, log_message, strlen (log_message));
629 
630 	g_free (log_message);
631 
632 	g_clear_object(&self->priv->log_message_command);
633 }
634 
635 static gboolean
on_log_view_row_selected(GtkTreeSelection * selection,GtkTreeModel * model,GtkTreePath * path,gboolean path_currently_selected,GitLogPane * self)636 on_log_view_row_selected (GtkTreeSelection *selection,
637                           GtkTreeModel *model,
638                           GtkTreePath *path,
639                           gboolean path_currently_selected,
640                           GitLogPane *self)
641 {
642 	Git *plugin;
643 	GtkTreeIter iter;
644 	GitRevision *revision;
645 	gchar *sha;
646 
647 	if (!path_currently_selected)
648 	{
649 		plugin = ANJUTA_PLUGIN_GIT (anjuta_dock_pane_get_plugin (ANJUTA_DOCK_PANE (self)));
650 
651 		gtk_tree_model_get_iter (model, &iter, path);
652 		gtk_tree_model_get (model, &iter, LOG_COL_REVISION, &revision, -1);
653 		sha = git_revision_get_sha (revision);
654 
655 		/* Unref the previous command if it's still running. */
656 		if (self->priv->log_message_command)
657 			g_object_unref (self->priv->log_message_command);
658 
659 		self->priv->log_message_command =
660 			git_log_message_command_new (plugin->project_root_directory, sha);
661 
662 		g_free (sha);
663 		g_object_unref (revision);
664 
665 		g_signal_connect_object (G_OBJECT (self->priv->log_message_command), "command-finished",
666 		                         G_CALLBACK (on_log_message_command_finished),
667 		                         self, 0);
668 
669 		anjuta_command_start (ANJUTA_COMMAND (self->priv->log_message_command));
670 	}
671 
672 	return TRUE;
673 }
674 
675 static void
on_log_view_drag_data_get(GtkWidget * log_view,GdkDragContext * drag_context,GtkSelectionData * data,guint info,guint time,GitLogPane * self)676 on_log_view_drag_data_get (GtkWidget *log_view,
677                            GdkDragContext *drag_context,
678                            GtkSelectionData *data,
679                            guint info, guint time,
680                            GitLogPane *self)
681 {
682 	GtkTreeSelection *selection;
683 	GtkTreeIter iter;
684 	GitRevision *revision;
685 	gchar *sha;
686 
687 	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (log_view));
688 
689 	if (gtk_tree_selection_count_selected_rows (selection) > 0)
690 	{
691 		gtk_tree_selection_get_selected (selection, NULL, &iter);
692 
693 		gtk_tree_model_get (GTK_TREE_MODEL (self->priv->log_model), &iter,
694 		                    0, &revision, -1);
695 
696 		sha = git_revision_get_sha (revision);
697 
698 		gtk_selection_data_set_text (data, sha, -1);
699 
700 		g_object_unref (revision);
701 		g_free (sha);
702 	}
703 }
704 
705 static void
on_log_pane_drag_data_received(GtkWidget * widget,GdkDragContext * context,gint x,gint y,GtkSelectionData * data,guint target_type,guint time,GitLogPane * self)706 on_log_pane_drag_data_received (GtkWidget *widget,
707                                 GdkDragContext *context, gint x, gint y,
708                                 GtkSelectionData *data, guint target_type,
709                                 guint time, GitLogPane *self)
710 {
711 	Git *plugin;
712 	AnjutaEntry *path_entry;
713 	gboolean success;
714 	gchar **uri_list;
715 	GFile *parent_file;
716 	GFile *file;
717 	gchar *path;
718 
719 	plugin = ANJUTA_PLUGIN_GIT (anjuta_dock_pane_get_plugin (ANJUTA_DOCK_PANE (self)));
720 	path_entry = ANJUTA_ENTRY (gtk_builder_get_object (self->priv->builder,
721 	                                                   "path_entry"));
722 	success = FALSE;
723 
724 	if ((data != NULL) &&
725 	    (gtk_selection_data_get_length (data) >= 0))
726 	{
727 		if (target_type == 0)
728 		{
729 			uri_list = gtk_selection_data_get_uris (data);
730 			parent_file = NULL;
731 
732 			parent_file = g_file_new_for_path (plugin->project_root_directory);
733 
734 			/* Take only the first file */
735 			file = g_file_new_for_uri (uri_list[0]);
736 
737 			if (parent_file)
738 			{
739 				path = g_file_get_relative_path (parent_file, file);
740 
741 				g_object_unref (parent_file);
742 			}
743 			else
744 				path = g_file_get_path (file);
745 
746 			if (path)
747 			{
748 				anjuta_entry_set_text (path_entry, path);
749 
750 				g_free (self->priv->path);
751 				self->priv->path = g_strdup (path);
752 
753 				refresh_log (self);
754 
755 				g_free (path);
756 			}
757 
758 			success = TRUE;
759 
760 			g_object_unref (file);
761 			g_strfreev (uri_list);
762 		}
763 	}
764 
765 	/* Do not delete source data */
766 	gtk_drag_finish (context, success, FALSE, time);
767 }
768 
769 static gboolean
on_log_pane_drag_drop(GtkWidget * widget,GdkDragContext * context,gint x,gint y,guint time,GitLogPane * self)770 on_log_pane_drag_drop (GtkWidget *widget, GdkDragContext *context,
771                        gint x, gint y, guint time,
772                        GitLogPane *self)
773 {
774 	GdkAtom target_type;
775 
776 	target_type = gtk_drag_dest_find_target (widget, context, NULL);
777 
778 	if (target_type != GDK_NONE)
779 		gtk_drag_get_data (widget, context, target_type, time);
780 	else
781 		gtk_drag_finish (context, FALSE, FALSE, time);
782 
783 	return TRUE;
784 }
785 
786 static void
on_path_entry_icon_release(GtkEntry * entry,GtkEntryIconPosition position,GdkEvent * event,GitLogPane * self)787 on_path_entry_icon_release (GtkEntry *entry,
788                             GtkEntryIconPosition position,
789                             GdkEvent *event,
790                             GitLogPane *self)
791 {
792 	if (position == GTK_ENTRY_ICON_SECONDARY)
793 	{
794 		if (self->priv->path)
795 		{
796 			g_free (self->priv->path);
797 			self->priv->path = NULL;
798 
799 			refresh_log (self);
800 		}
801 	}
802 }
803 
804 static gboolean
on_log_view_button_press_event(GtkWidget * log_view,GdkEventButton * event,GitLogPane * self)805 on_log_view_button_press_event (GtkWidget *log_view, GdkEventButton *event,
806                                 GitLogPane *self)
807 {
808 	GtkMenu *menu;
809 	GtkTreeSelection *selection;
810 	AnjutaPlugin *plugin;
811 	AnjutaUI *ui;
812 
813 	if (event->type == GDK_BUTTON_PRESS && event->button == 3)
814 	{
815 		selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (log_view));
816 
817 		if (gtk_tree_selection_count_selected_rows (selection) > 0)
818 		{
819 			plugin = anjuta_dock_pane_get_plugin (ANJUTA_DOCK_PANE (self));
820 			ui = anjuta_shell_get_ui (plugin->shell, NULL);
821 
822 			menu = GTK_MENU (gtk_ui_manager_get_widget (GTK_UI_MANAGER (ui),
823 			                                            "/GitLogPopup"));
824 
825 			gtk_menu_popup (menu, NULL, NULL, NULL, NULL, event->button,
826 			                event->time);
827 		}
828 	}
829 
830 	return FALSE;
831 }
832 
833 static void
git_log_pane_init(GitLogPane * self)834 git_log_pane_init (GitLogPane *self)
835 {
836 	gchar *objects[] = {"log_pane",
837 						"log_branch_combo_model",
838 						"log_loading_model",
839 						"find_button_image",
840 						NULL};
841 	GError *error = NULL;
842 	GtkWidget *log_pane;
843 	GtkWidget *path_entry;
844 	GtkTreeView *log_view;
845 	GtkTreeViewColumn *ref_icon_column;
846 	GtkTreeViewColumn *graph_column;
847 	GtkTreeViewColumn *short_log_column;
848 	GtkTreeViewColumn *author_column;
849 	GtkTreeViewColumn *date_column;
850 	GtkCellRenderer *ref_icon_renderer;
851 	GtkCellRenderer *short_log_renderer;
852 	GtkCellRenderer *author_renderer;
853 	GtkCellRenderer *date_renderer;
854 	GtkTreeViewColumn *loading_spinner_column;
855 	GtkCellRenderer *loading_spinner_renderer;
856 	GtkCellRenderer *loading_indicator_renderer;
857 	GtkComboBox *branch_combo;
858 	GtkTreeSelection *selection;
859 
860 	self->priv = g_new0 (GitLogPanePriv, 1);
861 	self->priv->builder = gtk_builder_new ();
862 
863 	if (!gtk_builder_add_objects_from_file (self->priv->builder, BUILDER_FILE,
864 	                                        objects,
865 	                                        &error))
866 	{
867 		g_warning ("Couldn't load builder file: %s", error->message);
868 		g_error_free (error);
869 	}
870 
871 	log_pane = GTK_WIDGET (gtk_builder_get_object (self->priv->builder,
872 	                                               "log_pane"));
873 	path_entry = GTK_WIDGET (gtk_builder_get_object (self->priv->builder,
874 	                                                 "path_entry"));
875 	log_view = GTK_TREE_VIEW (gtk_builder_get_object (self->priv->builder,
876 	                                                  "log_view"));
877 	ref_icon_column = GTK_TREE_VIEW_COLUMN (gtk_builder_get_object (self->priv->builder,
878 	                                                                "ref_icon_column"));
879 	graph_column = GTK_TREE_VIEW_COLUMN (gtk_builder_get_object (self->priv->builder,
880 	                                                             "graph_column"));
881 	short_log_column = GTK_TREE_VIEW_COLUMN (gtk_builder_get_object (self->priv->builder,
882 	                                                                 "short_log_column"));
883 	author_column = GTK_TREE_VIEW_COLUMN (gtk_builder_get_object (self->priv->builder,
884 	                                                              "author_column"));
885 	date_column = GTK_TREE_VIEW_COLUMN (gtk_builder_get_object (self->priv->builder,
886 	                                							"date_column"));
887 	ref_icon_renderer = GTK_CELL_RENDERER (gtk_builder_get_object (self->priv->builder,
888 	                                                               "ref_icon_renderer"));
889 	author_renderer = GTK_CELL_RENDERER (gtk_builder_get_object (self->priv->builder,
890 	                                                             "author_renderer"));
891 	date_renderer = GTK_CELL_RENDERER (gtk_builder_get_object (self->priv->builder,
892 	                                                           "date_renderer"));
893 	branch_combo = GTK_COMBO_BOX (gtk_builder_get_object (self->priv->builder,
894 	                                                      "branch_combo"));
895 	loading_spinner_column = GTK_TREE_VIEW_COLUMN (gtk_builder_get_object (self->priv->builder,
896 	                                                                       "loading_spinner_column"));
897 	selection = gtk_tree_view_get_selection (log_view);
898 
899 	/* Path entry */
900 	g_signal_connect (G_OBJECT (path_entry), "icon-release",
901 	                  G_CALLBACK (on_path_entry_icon_release),
902 	                  self);
903 
904 	/* Set up the log model */
905 	self->priv->log_model = gtk_list_store_new (1, GIT_TYPE_REVISION);
906 
907 	/* Ref icon column */
908 	gtk_tree_view_column_set_cell_data_func (ref_icon_column, ref_icon_renderer,
909 	                                         (GtkTreeCellDataFunc) ref_icon_cell_function,
910 	                                         self, NULL);
911 
912 
913 	/* Graph column */
914 	self->priv->graph_renderer = giggle_graph_renderer_new ();
915 
916 	gtk_tree_view_column_pack_start (graph_column, self->priv->graph_renderer,
917 	                                 TRUE);
918 	gtk_tree_view_column_add_attribute (graph_column, self->priv->graph_renderer,
919 	                                    "revision", 0);
920 
921 	/* Short log column. We have to create this render ouselves becuause Glade
922 	 * doesn't seem to give us to option to pack it with expand */
923 	short_log_renderer = gtk_cell_renderer_text_new ();
924 	g_object_set (G_OBJECT (short_log_renderer), "ellipsize",
925 	              PANGO_ELLIPSIZE_END, NULL);
926 	gtk_tree_view_column_pack_start (short_log_column, short_log_renderer,
927 	                                 TRUE);
928 	gtk_tree_view_column_set_cell_data_func (short_log_column, short_log_renderer,
929 	                                         (GtkTreeCellDataFunc) short_log_cell_function,
930 	                                         NULL, NULL);
931 
932 	/* Author column */
933 	gtk_tree_view_column_set_cell_data_func (author_column, author_renderer,
934 	                                         (GtkTreeCellDataFunc) author_cell_function,
935 	                                         NULL, NULL);
936 
937 	/* Date column */
938 	gtk_tree_view_column_set_cell_data_func (date_column, date_renderer,
939 	                                         (GtkTreeCellDataFunc) date_cell_function,
940 	                                         NULL, NULL);
941 
942 	gtk_tree_view_set_model (log_view, GTK_TREE_MODEL (self->priv->log_model));
943 
944 	/* Ref icon tooltip */
945 	g_signal_connect (G_OBJECT (log_view), "query-tooltip",
946 	                  G_CALLBACK (on_log_view_query_tooltip),
947 	                  self);
948 
949 	/* Loading indicator. The loading indicator is a second tree view display
950 	 * that looks just like the real log display, except that it displays a
951 	 * spinner renderer and the text "Loading..." in the Short Log column. */
952 	self->priv->log_loading_model = GTK_LIST_STORE (gtk_builder_get_object (self->priv->builder,
953 	                                                                        "log_loading_model"));
954 	loading_spinner_renderer = gtk_cell_renderer_spinner_new ();
955 	loading_indicator_renderer = gtk_cell_renderer_text_new ();
956 
957 	g_object_set (G_OBJECT (loading_spinner_renderer), "active", TRUE, NULL);
958 
959 	gtk_tree_view_column_pack_start (loading_spinner_column,
960 	                                 loading_spinner_renderer, FALSE);
961 	gtk_tree_view_column_pack_start (loading_spinner_column,
962 	                                 loading_indicator_renderer, TRUE);
963 	gtk_tree_view_column_add_attribute (loading_spinner_column,
964 	                                    loading_spinner_renderer,
965 	                                    "pulse", LOADING_COL_PULSE);
966 	gtk_tree_view_column_add_attribute (loading_spinner_column,
967 	                                    loading_indicator_renderer,
968 	                                    "text", LOADING_COL_INDICATOR);
969 
970 	/* DnD source */
971 	gtk_tree_view_enable_model_drag_source (log_view,
972 	                                        GDK_BUTTON1_MASK,
973 	                                        drag_source_targets,
974 	                                        G_N_ELEMENTS (drag_source_targets),
975 	                                        GDK_ACTION_COPY);
976 
977 	g_signal_connect (G_OBJECT (log_view), "drag-data-get",
978 	                  G_CALLBACK (on_log_view_drag_data_get),
979 	                  self);
980 
981 	/* DnD target. Use this as a means of selecting a file to view the
982 	 * log of. Files or folders would normally be dragged in from the file
983 	 * manager, but they can come from any source that supports URI's. */
984 	gtk_drag_dest_set (log_pane,
985 	                   GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT,
986 	                   drag_target_targets,
987 	                   G_N_ELEMENTS (drag_target_targets),
988 	                   GDK_ACTION_COPY | GDK_ACTION_MOVE);
989 
990 	g_signal_connect (G_OBJECT (log_pane), "drag-data-received",
991 	                  G_CALLBACK (on_log_pane_drag_data_received),
992 	                  self);
993 
994 	g_signal_connect (G_OBJECT (log_pane), "drag-drop",
995 	                  G_CALLBACK (on_log_pane_drag_drop),
996 	                  self);
997 
998 	/* Pop up menu */
999 	g_signal_connect (G_OBJECT (log_view), "button-press-event",
1000 	                  G_CALLBACK (on_log_view_button_press_event),
1001 	                  self);
1002 
1003 	/* The loading view always has one row. Cache a copy of its iter for easy
1004 	 * access. */
1005 	gtk_tree_model_get_iter_first (GTK_TREE_MODEL (self->priv->log_loading_model),
1006 	                               &(self->priv->spinner_iter));
1007 
1008 	/* FIXME: GtkSpinner doesn't have those anymore */
1009 	self->priv->spin_cycle_duration = 1000;
1010 	self->priv->spin_cycle_steps =  12;
1011 
1012 	g_object_set (G_OBJECT (loading_spinner_renderer), "active", TRUE, NULL);
1013 
1014 	/* Log message display */
1015 	gtk_tree_selection_set_select_function (selection,
1016 	                                        (GtkTreeSelectionFunc) on_log_view_row_selected,
1017 	                                        self, NULL);
1018 
1019 	/* Branch handling */
1020 	self->priv->branches_table = g_hash_table_new_full (g_str_hash, g_str_equal,
1021 	                                                    g_free,
1022 	                                                    (GDestroyNotify) gtk_tree_path_free);
1023 
1024 	g_signal_connect (G_OBJECT (branch_combo), "changed",
1025 	                  G_CALLBACK (on_branch_combo_changed),
1026 	                  self);
1027 
1028 }
1029 
1030 static void
git_log_pane_finalize(GObject * object)1031 git_log_pane_finalize (GObject *object)
1032 {
1033 	GitLogPane *self;
1034 	Git *plugin;
1035 
1036 	self = GIT_LOG_PANE (object);
1037 
1038 	/* Disconnect signal handler from ref_command. */
1039 	plugin = ANJUTA_PLUGIN_GIT (anjuta_dock_pane_get_plugin (ANJUTA_DOCK_PANE (self)));
1040 	g_signal_handlers_disconnect_by_func (G_OBJECT (plugin->ref_command),
1041 	                                      on_ref_command_finished, self);
1042 
1043 	g_clear_object (&self->priv->branch_list_command);
1044 	g_clear_object (&self->priv->log_message_command);
1045 	g_clear_object (&self->priv->log_command);
1046 
1047 	/* Remove spin timer source. */
1048 	if (self->priv->spin_timer_id > 0)
1049 		g_source_remove (self->priv->spin_timer_id);
1050 
1051 	g_object_unref (self->priv->builder);
1052 	g_object_unref (self->priv->log_model);
1053 	g_free (self->priv->path);
1054 	g_hash_table_destroy (self->priv->branches_table);
1055 
1056 	if (self->priv->refs)
1057 		g_hash_table_unref (self->priv->refs);
1058 
1059 	g_free (self->priv->selected_branch);
1060 
1061 	if (self->priv->active_branch_path != NULL)
1062 		gtk_tree_path_free (self->priv->active_branch_path);
1063 
1064 	g_free (self->priv);
1065 
1066 	G_OBJECT_CLASS (git_log_pane_parent_class)->finalize (object);
1067 }
1068 
1069 static GtkWidget *
git_log_pane_get_widget(AnjutaDockPane * pane)1070 git_log_pane_get_widget (AnjutaDockPane *pane)
1071 {
1072 	GitLogPane *self;
1073 
1074 	self = GIT_LOG_PANE (pane);
1075 
1076 	return GTK_WIDGET (gtk_builder_get_object (self->priv->builder,
1077 	                                           "log_pane"));
1078 }
1079 
1080 static void
git_log_pane_class_init(GitLogPaneClass * klass)1081 git_log_pane_class_init (GitLogPaneClass *klass)
1082 {
1083 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
1084 	AnjutaDockPaneClass *pane_class = ANJUTA_DOCK_PANE_CLASS (klass);
1085 
1086 	object_class->finalize = git_log_pane_finalize;
1087 	pane_class->get_widget = git_log_pane_get_widget;
1088 	pane_class->refresh = NULL;
1089 }
1090 
1091 
1092 AnjutaDockPane *
git_log_pane_new(Git * plugin)1093 git_log_pane_new (Git *plugin)
1094 {
1095 	GitLogPane *self;
1096 
1097 	self = g_object_new (GIT_TYPE_LOG_PANE, "plugin", plugin, NULL);
1098 
1099 	g_signal_connect_object (G_OBJECT (plugin->ref_command), "command-finished",
1100 	                         G_CALLBACK (on_ref_command_finished),
1101 	                         self, 0);
1102 
1103 	return ANJUTA_DOCK_PANE (self);
1104 }
1105 
1106 void
git_log_pane_set_working_directory(GitLogPane * self,const gchar * working_directory)1107 git_log_pane_set_working_directory (GitLogPane *self,
1108                                     const gchar *working_directory)
1109 {
1110 	/* TODO: Add public function implementation here */
1111 }
1112 
1113 GitRevision *
git_log_pane_get_selected_revision(GitLogPane * self)1114 git_log_pane_get_selected_revision (GitLogPane *self)
1115 {
1116 	GtkTreeView *log_view;
1117 	GtkTreeSelection *selection;
1118 	GitRevision *revision;
1119 	GtkTreeIter iter;
1120 
1121 	log_view = GTK_TREE_VIEW (gtk_builder_get_object (self->priv->builder,
1122 	                                                  "log_view"));
1123 	selection = gtk_tree_view_get_selection (log_view);
1124 	revision = NULL;
1125 
1126 	if (gtk_tree_selection_get_selected (selection, NULL, &iter))
1127 	{
1128 		gtk_tree_model_get (GTK_TREE_MODEL (self->priv->log_model), &iter,
1129 		                    LOG_COL_REVISION, &revision, -1);
1130 	}
1131 
1132 	return revision;
1133 }
1134