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, ¤t_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