[gitg] Add blame mode. Fixes bug #576693
- From: Ignacio Casal Quinteiro <icq src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gitg] Add blame mode. Fixes bug #576693
- Date: Mon, 25 Jul 2011 21:27:56 +0000 (UTC)
commit 9e560ab9e1e14c1e805ee55613111f78090b312d
Author: Ignacio Casal Quinteiro <icq gnome org>
Date: Fri Jul 22 12:58:17 2011 +0200
Add blame mode. Fixes bug #576693
https://bugzilla.gnome.org/show_bug.cgi?id=576693
gitg/Makefile.am | 2 +
gitg/gitg-blame-renderer.c | 454 +++++++++++++++++++++++++++++++
gitg/gitg-blame-renderer.h | 61 +++++
gitg/gitg-revision-files-panel.c | 535 +++++++++++++++++++++++++++++++++++--
gitg/gitg-revision-files-panel.ui | 52 +++-
5 files changed, 1065 insertions(+), 39 deletions(-)
---
diff --git a/gitg/Makefile.am b/gitg/Makefile.am
index 5358908..e74617f 100644
--- a/gitg/Makefile.am
+++ b/gitg/Makefile.am
@@ -14,6 +14,7 @@ AM_CPPFLAGS = \
NOINST_H_FILES = \
gitg-activatable.h \
gitg-avatar-cache.h \
+ gitg-blame-renderer.h \
gitg-branch-actions.h \
gitg-cell-renderer-path.h \
gitg-commit-view.h \
@@ -38,6 +39,7 @@ gitg_SOURCES = \
gitg.c \
gitg-activatable.c \
gitg-avatar-cache.c \
+ gitg-blame-renderer.c \
gitg-branch-actions.c \
gitg-cell-renderer-path.c \
gitg-commit-view.c \
diff --git a/gitg/gitg-blame-renderer.c b/gitg/gitg-blame-renderer.c
new file mode 100644
index 0000000..630c284
--- /dev/null
+++ b/gitg/gitg-blame-renderer.c
@@ -0,0 +1,454 @@
+/*
+ * gitg-blame-renderer.c
+ * This file is part of gitg - git repository viewer
+ *
+ * Copyright (C) 2011 - Ignacio Casal Quinteiro
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "gitg-blame-renderer.h"
+
+#include <libgitg/gitg-revision.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#define GITG_BLAME_RENDERER_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), GITG_TYPE_BLAME_RENDERER, GitgBlameRendererPrivate))
+
+/* Properties */
+enum
+{
+ PROP_0,
+ PROP_REVISION,
+ PROP_SHOW,
+ PROP_GROUP_START
+};
+
+struct _GitgBlameRendererPrivate
+{
+ GitgRevision *revision;
+ gint max_line;
+ gboolean group_start;
+ gboolean show;
+
+ gchar *line_number;
+
+ PangoLayout *cached_layout;
+ PangoLayout *line_layout;
+};
+
+G_DEFINE_TYPE (GitgBlameRenderer, gitg_blame_renderer, GTK_SOURCE_TYPE_GUTTER_RENDERER)
+
+static void
+gitg_blame_renderer_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GitgBlameRenderer *self = GITG_BLAME_RENDERER (object);
+
+ switch (prop_id)
+ {
+ case PROP_REVISION:
+ self->priv->revision = g_value_get_boxed (value);
+ break;
+ case PROP_SHOW:
+ self->priv->show = g_value_get_boolean (value);
+ break;
+ case PROP_GROUP_START:
+ self->priv->group_start = g_value_get_boolean (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gitg_blame_renderer_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GitgBlameRenderer *self = GITG_BLAME_RENDERER (object);
+
+ switch (prop_id)
+ {
+ case PROP_REVISION:
+ g_value_set_boxed (value, self->priv->revision);
+ break;
+ case PROP_SHOW:
+ g_value_set_boolean (value, self->priv->show);
+ break;
+ case PROP_GROUP_START:
+ g_value_set_boolean (value, self->priv->group_start);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gitg_blame_renderer_finalize (GObject *object)
+{
+ GitgBlameRenderer *br = GITG_BLAME_RENDERER (object);
+
+ g_free (br->priv->line_number);
+
+ G_OBJECT_CLASS (gitg_blame_renderer_parent_class)->finalize (object);
+}
+
+static void
+gitg_blame_renderer_begin (GtkSourceGutterRenderer *renderer,
+ cairo_t *cr,
+ GdkRectangle *background_area,
+ GdkRectangle *cell_area,
+ GtkTextIter *start,
+ GtkTextIter *end)
+{
+ GitgBlameRenderer *br = GITG_BLAME_RENDERER (renderer);
+
+ br->priv->cached_layout = gtk_widget_create_pango_layout (GTK_WIDGET (gtk_source_gutter_renderer_get_view (renderer)),
+ NULL);
+ br->priv->line_layout = gtk_widget_create_pango_layout (GTK_WIDGET (gtk_source_gutter_renderer_get_view (renderer)),
+ NULL);
+}
+
+static void
+render_blame (GtkSourceGutterRenderer *renderer,
+ cairo_t *ctx,
+ GdkRectangle *background_area,
+ GdkRectangle *cell_area,
+ GtkTextIter *start,
+ GtkTextIter *end,
+ GtkSourceGutterRendererState renderer_state)
+{
+ GitgBlameRenderer *br = GITG_BLAME_RENDERER (renderer);
+ gchar *text;
+ PangoLayout *layout;
+ GtkWidget *widget;
+ GtkStyleContext *style_context;
+ gchar *sha1;
+ gchar short_sha1[9];
+
+ widget = GTK_WIDGET (gtk_source_gutter_renderer_get_view (renderer));
+ layout = br->priv->cached_layout;
+
+ pango_layout_set_width (layout, -1);
+
+ sha1 = gitg_revision_get_sha1 (br->priv->revision);
+ strncpy (short_sha1, sha1, 8);
+ short_sha1[8] = '\0';
+ g_free (sha1);
+
+ text = g_strdup_printf ("<b>%s</b> %s", short_sha1,
+ gitg_revision_get_author (br->priv->revision));
+
+ pango_layout_set_markup (layout, text, -1);
+ g_free (text);
+ style_context = gtk_widget_get_style_context (widget);
+
+ if (br->priv->group_start)
+ {
+ GdkRGBA bg_color;
+
+ gtk_style_context_get_background_color (style_context, GTK_STATE_INSENSITIVE, &bg_color);
+ gdk_cairo_set_source_rgba (ctx, &bg_color);
+
+ cairo_save (ctx);
+ cairo_move_to (ctx, background_area->x, background_area->y);
+ cairo_line_to (ctx, background_area->x + background_area->width,
+ cell_area->y);
+ cairo_stroke (ctx);
+ cairo_restore (ctx);
+ }
+
+ gtk_render_layout (style_context,
+ ctx,
+ cell_area->x,
+ cell_area->y,
+ layout);
+}
+
+static void
+render_line (GtkSourceGutterRenderer *renderer,
+ cairo_t *ctx,
+ GdkRectangle *background_area,
+ GdkRectangle *cell_area,
+ GtkTextIter *start,
+ GtkTextIter *end,
+ GtkSourceGutterRendererState renderer_state)
+{
+ GitgBlameRenderer *br = GITG_BLAME_RENDERER (renderer);
+ PangoLayout *layout;
+ GtkWidget *widget;
+ GtkStyleContext *style_context;
+ gint width, height;
+
+ widget = GTK_WIDGET (gtk_source_gutter_renderer_get_view (renderer));
+ layout = br->priv->line_layout;
+
+ pango_layout_set_markup (layout, br->priv->line_number, -1);
+ pango_layout_get_size (layout, &width, &height);
+
+ style_context = gtk_widget_get_style_context (widget);
+
+ width /= PANGO_SCALE;
+
+ gtk_render_layout (style_context,
+ ctx,
+ cell_area->x + cell_area->width - width - 5,
+ cell_area->y,
+ layout);
+}
+
+static void
+gitg_blame_renderer_draw (GtkSourceGutterRenderer *renderer,
+ cairo_t *ctx,
+ GdkRectangle *background_area,
+ GdkRectangle *cell_area,
+ GtkTextIter *start,
+ GtkTextIter *end,
+ GtkSourceGutterRendererState renderer_state)
+{
+ GitgBlameRenderer *br = GITG_BLAME_RENDERER (renderer);
+
+ /* Chain up to draw background */
+ GTK_SOURCE_GUTTER_RENDERER_CLASS (
+ gitg_blame_renderer_parent_class)->draw (renderer,
+ ctx,
+ background_area,
+ cell_area,
+ start,
+ end,
+ renderer_state);
+
+ if (br->priv->show && br->priv->revision != NULL)
+ {
+ render_blame (renderer,
+ ctx,
+ background_area,
+ cell_area,
+ start,
+ end,
+ renderer_state);
+ }
+
+ render_line (renderer,
+ ctx,
+ background_area,
+ cell_area,
+ start,
+ end,
+ renderer_state);
+}
+
+static void
+gitg_blame_renderer_end (GtkSourceGutterRenderer *renderer)
+{
+ GitgBlameRenderer *br = GITG_BLAME_RENDERER (renderer);
+
+ g_object_unref (br->priv->cached_layout);
+ br->priv->cached_layout = NULL;
+
+ g_object_unref (br->priv->line_layout);
+ br->priv->line_layout = NULL;
+}
+
+static void
+gutter_renderer_query_data (GtkSourceGutterRenderer *renderer,
+ GtkTextIter *start,
+ GtkTextIter *end,
+ GtkSourceGutterRendererState state)
+{
+ GitgBlameRenderer *br = GITG_BLAME_RENDERER (renderer);
+ gchar *text;
+ gint line;
+ gboolean current_line;
+
+ line = gtk_text_iter_get_line (start) + 1;
+
+ current_line = (state & GTK_SOURCE_GUTTER_RENDERER_STATE_CURSOR) &&
+ gtk_text_view_get_cursor_visible (gtk_source_gutter_renderer_get_view (renderer));
+
+ if (current_line)
+ {
+ text = g_strdup_printf ("<b>%d</b>", line);
+ }
+ else
+ {
+ text = g_strdup_printf ("%d", line);
+ }
+
+ g_free (br->priv->line_number);
+ br->priv->line_number = text;
+}
+
+static void
+measure_text (GitgBlameRenderer *br,
+ const gchar *markup,
+ gint *width)
+{
+ PangoLayout *layout;
+ gint w;
+ gint h;
+ GtkSourceGutterRenderer *r;
+ GtkTextView *view;
+
+ r = GTK_SOURCE_GUTTER_RENDERER (br);
+ view = gtk_source_gutter_renderer_get_view (r);
+
+ layout = gtk_widget_create_pango_layout (GTK_WIDGET (view), NULL);
+
+ pango_layout_set_markup (layout,
+ markup,
+ -1);
+
+ pango_layout_get_size (layout, &w, &h);
+
+ if (width)
+ {
+ *width = w / PANGO_SCALE;
+ }
+
+ g_object_unref (layout);
+}
+
+static GtkTextBuffer *
+get_buffer (GitgBlameRenderer *renderer)
+{
+ GtkTextView *view;
+
+ view = gtk_source_gutter_renderer_get_view (GTK_SOURCE_GUTTER_RENDERER (renderer));
+
+ return gtk_text_view_get_buffer (view);
+}
+
+static void
+recalculate_size (GitgBlameRenderer *br)
+{
+ GtkTextBuffer *buffer;
+ gchar *markup;
+ gint size;
+ gchar *text;
+ gint num, num_digits, i;
+
+ buffer = get_buffer (br);
+
+ num = gtk_text_buffer_get_line_count (buffer);
+ num_digits = 0;
+
+ while (num > 0)
+ {
+ num /= 10;
+ ++num_digits;
+ }
+
+ num_digits = MAX (num_digits, 2);
+
+ text = g_new (gchar, br->priv->max_line + num_digits + 1);
+ for (i = 0; i < br->priv->max_line + num_digits; i++)
+ {
+ text[i] = '0';
+ }
+ text[br->priv->max_line + num_digits] = '\0';
+
+ markup = g_strdup_printf ("<b>%s</b>", text);
+ g_free (text);
+
+ measure_text (br, markup, &size);
+ g_free (markup);
+
+ gtk_source_gutter_renderer_set_size (GTK_SOURCE_GUTTER_RENDERER (br),
+ size);
+}
+
+static void
+update_num_digits (GitgBlameRenderer *renderer,
+ gint max_line)
+{
+ max_line = MAX (max_line, 2);
+
+ if (max_line != renderer->priv->max_line)
+ {
+ renderer->priv->max_line = max_line;
+ recalculate_size (renderer);
+ }
+}
+
+static void
+gitg_blame_renderer_class_init (GitgBlameRendererClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkSourceGutterRendererClass *renderer_class = GTK_SOURCE_GUTTER_RENDERER_CLASS (klass);
+
+ renderer_class->begin = gitg_blame_renderer_begin;
+ renderer_class->draw = gitg_blame_renderer_draw;
+ renderer_class->end= gitg_blame_renderer_end;
+ renderer_class->query_data = gutter_renderer_query_data;
+
+ object_class->set_property = gitg_blame_renderer_set_property;
+ object_class->get_property = gitg_blame_renderer_get_property;
+ object_class->finalize = gitg_blame_renderer_finalize;
+
+ g_object_class_install_property (object_class,
+ PROP_REVISION,
+ g_param_spec_boxed ("revision",
+ "Revision",
+ "Revision",
+ GITG_TYPE_REVISION,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class,
+ PROP_SHOW,
+ g_param_spec_boolean ("show",
+ "Show",
+ "Show",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class,
+ PROP_GROUP_START,
+ g_param_spec_boolean ("group-start",
+ "Group Start",
+ "Group start",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+ g_type_class_add_private (object_class, sizeof (GitgBlameRendererPrivate));
+}
+
+static void
+gitg_blame_renderer_init (GitgBlameRenderer *self)
+{
+ self->priv = GITG_BLAME_RENDERER_GET_PRIVATE (self);
+}
+
+GitgBlameRenderer *
+gitg_blame_renderer_new (void)
+{
+ return g_object_new (GITG_TYPE_BLAME_RENDERER, NULL);
+}
+
+void
+gitg_blame_renderer_set_max_line_count (GitgBlameRenderer *renderer,
+ gint max_line)
+{
+ g_return_if_fail (GITG_IS_BLAME_RENDERER (renderer));
+
+ update_num_digits (renderer, max_line);
+}
diff --git a/gitg/gitg-blame-renderer.h b/gitg/gitg-blame-renderer.h
new file mode 100644
index 0000000..412ac90
--- /dev/null
+++ b/gitg/gitg-blame-renderer.h
@@ -0,0 +1,61 @@
+/*
+ * gitg-blame-renderer.h
+ * This file is part of gitg - git repository viewer
+ *
+ * Copyright (C) 2011 - Ignacio Casal Quinteiro
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GITG_BLAME_RENDERER_H__
+#define __GITG_BLAME_RENDERER_H__
+
+#include <gtksourceview/gtksourceview.h>
+
+G_BEGIN_DECLS
+
+#define GITG_TYPE_BLAME_RENDERER (gitg_blame_renderer_get_type ())
+#define GITG_BLAME_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GITG_TYPE_BLAME_RENDERER, GitgBlameRenderer))
+#define GITG_BLAME_RENDERER_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GITG_TYPE_BLAME_RENDERER, GitgBlameRenderer const))
+#define GITG_BLAME_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GITG_TYPE_BLAME_RENDERER, GitgBlameRendererClass))
+#define GITG_IS_BLAME_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GITG_TYPE_BLAME_RENDERER))
+#define GITG_IS_BLAME_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GITG_TYPE_BLAME_RENDERER))
+#define GITG_BLAME_RENDERER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GITG_TYPE_BLAME_RENDERER, GitgBlameRendererClass))
+
+typedef struct _GitgBlameRenderer GitgBlameRenderer;
+typedef struct _GitgBlameRendererClass GitgBlameRendererClass;
+typedef struct _GitgBlameRendererPrivate GitgBlameRendererPrivate;
+
+struct _GitgBlameRenderer {
+ GtkSourceGutterRenderer parent;
+
+ GitgBlameRendererPrivate *priv;
+};
+
+struct _GitgBlameRendererClass {
+ GtkSourceGutterRendererClass parent_class;
+};
+
+GType gitg_blame_renderer_get_type (void) G_GNUC_CONST;
+
+GitgBlameRenderer *gitg_blame_renderer_new (void);
+
+void gitg_blame_renderer_set_max_line_count (GitgBlameRenderer *renderer,
+ gint max_line_count);
+
+G_END_DECLS
+
+#endif /* __GITG_BLAME_RENDERER_H__ */
diff --git a/gitg/gitg-revision-files-panel.c b/gitg/gitg-revision-files-panel.c
index 8cee3fe..ec698a3 100644
--- a/gitg/gitg-revision-files-panel.c
+++ b/gitg/gitg-revision-files-panel.c
@@ -33,11 +33,14 @@
#include "gitg-utils.h"
#include "gitg-revision-panel.h"
#include "gitg-dirs.h"
+#include "gitg-blame-renderer.h"
#define GITG_REVISION_FILES_VIEW_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), GITG_TYPE_REVISION_FILES_VIEW, GitgRevisionFilesViewPrivate))
#define GITG_REVISION_FILES_PANEL_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), GITG_TYPE_REVISION_FILES_PANEL, GitgRevisionFilesPanelPrivate))
+#define BLAME_DATA "GitgRevisionFilesPanelBlame"
+
enum
{
ICON_COLUMN,
@@ -52,11 +55,27 @@ typedef struct _GitgRevisionFilesViewPrivate GitgRevisionFilesViewPrivate;
struct _GitgRevisionFilesViewPrivate
{
+ GtkNotebook *notebook_files;
+
GtkTreeView *tree_view;
+
GtkSourceView *contents;
GitgShell *content_shell;
GtkTreeStore *store;
+ GtkSourceView *blame_view;
+ GitgShell *blame_shell;
+ GHashTable *blames; /* hash : tag */
+ GitgBlameRenderer *blame_renderer;
+ gint blame_offset;
+ GtkTextTag *active_tag;
+ gboolean blame_active;
+
+ glong query_data_id;
+ glong query_tooltip_id;
+
+ GtkWidget *active_view;
+
gchar *drag_dir;
gchar **drag_files;
@@ -130,6 +149,14 @@ gitg_revision_files_view_finalize (GObject *object)
gitg_io_cancel (GITG_IO (self->priv->loader));
g_object_unref (self->priv->loader);
+ gitg_io_cancel (GITG_IO (self->priv->content_shell));
+ g_object_unref (self->priv->content_shell);
+
+ g_hash_table_unref (self->priv->blames);
+
+ gitg_io_cancel (GITG_IO (self->priv->blame_shell));
+ g_object_unref (self->priv->blame_shell);
+
G_OBJECT_CLASS (gitg_revision_files_view_parent_class)->finalize (object);
}
@@ -187,10 +214,195 @@ set_revision (GitgRevisionFilesView *files_view,
}
}
+static GtkTextTag *
+get_blame_tag_from_iter (GitgRevisionFilesView *files_view,
+ GtkTextIter *iter)
+{
+ GtkTextTag *tag = NULL;
+ GSList *tags, *l;
+
+ tags = gtk_text_iter_get_tags (iter);
+
+ for (l = tags; l != NULL; l = g_slist_next (l))
+ {
+ if (g_object_get_data (l->data, BLAME_DATA) != NULL)
+ {
+ tag = l->data;
+ break;
+ }
+ }
+
+ g_slist_free (tags);
+
+ return tag;
+}
+
+static void
+blame_renderer_query_data_cb (GtkSourceGutterRenderer *renderer,
+ GtkTextIter *start,
+ GtkTextIter *end,
+ GtkSourceGutterRendererState state,
+ GitgRevisionFilesView *files_view)
+{
+ GtkTextTag *tag;
+ GitgRevision *rev;
+ GtkTextIter iter;
+
+ iter = *start;
+ gtk_text_iter_set_line_offset (&iter, 0);
+
+ tag = get_blame_tag_from_iter (files_view, &iter);
+
+ if (tag == NULL)
+ return;
+
+ rev = g_object_get_data (G_OBJECT (tag), BLAME_DATA);
+
+ if (gtk_text_iter_begins_tag (&iter, tag))
+ {
+ gboolean group_start = FALSE;
+
+ if (!gtk_text_iter_is_start (&iter))
+ {
+ group_start = TRUE;
+ }
+
+ g_object_set (renderer,
+ "revision", rev,
+ "show", TRUE,
+ "group-start", group_start,
+ NULL);
+ }
+ else
+ {
+ g_object_set (renderer,
+ "revision", rev,
+ "show", FALSE,
+ "group-start", FALSE,
+ NULL);
+ }
+}
+
+static gboolean
+blame_renderer_query_tooltip_cb (GtkSourceGutterRenderer *renderer,
+ GtkTextIter *iter,
+ GdkRectangle *area,
+ gint x,
+ gint y,
+ GtkTooltip *tooltip,
+ GitgRevisionFilesView *tree)
+{
+ GtkTextTag *tag;
+ GitgRevision *rev;
+ const gchar *author;
+ const gchar *committer;
+ const gchar *subject;
+ gchar *text;
+ gchar *sha1;
+ gchar *author_date;
+ gchar *committer_date;
+
+ tag = get_blame_tag_from_iter (tree, iter);
+
+ if (tag == NULL)
+ return FALSE;
+
+ rev = g_object_get_data (G_OBJECT (tag), BLAME_DATA);
+
+ if (rev == NULL)
+ return FALSE;
+
+ sha1 = gitg_revision_get_sha1 (rev);
+ author_date = gitg_revision_get_author_date_for_display (rev);
+ committer_date = gitg_revision_get_committer_date_for_display (rev);
+
+ author = _("Author");
+ committer = _("Committer");
+ subject = _("Subject");
+
+ text = g_markup_printf_escaped ("<b>SHA:</b> %s\n"
+ "<b>%s:</b> %s <%s> (%s)\n"
+ "<b>%s:</b> %s <%s> (%s)\n"
+ "<b>%s:</b> <i>%s</i>",
+ sha1,
+ author, gitg_revision_get_author (rev),
+ gitg_revision_get_author_email (rev),
+ author_date,
+ committer, gitg_revision_get_committer (rev),
+ gitg_revision_get_committer_email (rev),
+ committer_date,
+ subject, gitg_revision_get_subject (rev));
+
+ g_free (sha1);
+ g_free (author_date);
+ g_free (committer_date);
+ gtk_tooltip_set_markup (tooltip, text);
+ g_free (text);
+
+ return TRUE;
+}
+
+static void
+remove_blames (GitgRevisionFilesView *tree)
+{
+ GtkTextBuffer *buffer;
+ GList *tags, *tag;
+ GtkTextTagTable *table;
+
+ tree->priv->blame_active = FALSE;
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tree->priv->blame_view));
+ table = gtk_text_buffer_get_tag_table (buffer);
+
+ if (tree->priv->query_data_id != 0)
+ {
+ g_signal_handler_disconnect (tree->priv->blame_renderer,
+ tree->priv->query_data_id);
+ tree->priv->query_data_id = 0;
+ }
+
+ if (tree->priv->query_tooltip_id != 0)
+ {
+ g_signal_handler_disconnect (tree->priv->blame_renderer,
+ tree->priv->query_tooltip_id);
+ tree->priv->query_tooltip_id = 0;
+ }
+
+ tags = g_hash_table_get_values (tree->priv->blames);
+
+ for (tag = tags; tag != NULL; tag = g_list_next (tag))
+ {
+ g_object_set_data (tag->data, BLAME_DATA, NULL);
+ gtk_text_tag_table_remove (table, tag->data);
+ }
+
+ g_hash_table_remove_all (tree->priv->blames);
+
+ g_list_free (tags);
+
+ tree->priv->blame_offset = 0;
+ tree->priv->active_tag = NULL;
+
+ g_object_set (tree->priv->blame_renderer,
+ "revision", NULL,
+ "show", FALSE,
+ "group-start", FALSE,
+ NULL);
+
+ gitg_blame_renderer_set_max_line_count (tree->priv->blame_renderer, 0);
+}
+
static void
gitg_revision_files_view_dispose (GObject *object)
{
- set_revision (GITG_REVISION_FILES_VIEW (object), NULL, NULL);
+ GitgRevisionFilesView* files_view = GITG_REVISION_FILES_VIEW (object);
+
+ set_revision (files_view, NULL, NULL);
+
+ if (files_view->priv->blame_active)
+ {
+ remove_blames (files_view);
+ }
G_OBJECT_CLASS (gitg_revision_files_view_parent_class)->dispose (object);
}
@@ -246,31 +458,43 @@ on_row_expanded (GtkTreeView *files_view,
}
static void
-show_binary_information (GitgRevisionFilesView *tree)
+show_binary_information (GitgRevisionFilesView *tree,
+ GtkTextBuffer *buffer)
{
- GtkTextBuffer *buffer;
-
- buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(tree->priv->contents));
-
gtk_text_buffer_set_text (buffer,
- _ ("Cannot display file content as text"),
+ _("Cannot display file content as text"),
-1);
- gtk_source_buffer_set_language (GTK_SOURCE_BUFFER(buffer), NULL);
+ gtk_source_buffer_set_language (GTK_SOURCE_BUFFER (buffer), NULL);
}
static void
-on_selection_changed (GtkTreeSelection *selection,
+on_selection_changed (GtkTreeSelection *selection,
GitgRevisionFilesView *tree)
{
GtkTextBuffer *buffer;
-
- buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tree->priv->contents));
-
+ gboolean blame_mode;
GtkTreeModel *model;
GtkTreeIter iter;
+ GtkTreePath *path = NULL;
+ GList *rows;
+ gchar *name;
+ gchar *content_type;
- gitg_io_cancel (GITG_IO (tree->priv->content_shell));
+ if (tree->priv->active_view == GTK_WIDGET (tree->priv->contents))
+ {
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tree->priv->contents));
+ gitg_io_cancel (GITG_IO (tree->priv->content_shell));
+ blame_mode = FALSE;
+ }
+ else
+ {
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tree->priv->blame_view));
+ blame_mode = TRUE;
+ gitg_io_cancel (GITG_IO (tree->priv->blame_shell));
+
+ remove_blames (tree);
+ }
gtk_text_buffer_set_text (buffer, "", -1);
@@ -279,8 +503,7 @@ on_selection_changed (GtkTreeSelection *selection,
return;
}
- GList *rows = gtk_tree_selection_get_selected_rows (selection, &model);
- GtkTreePath *path = NULL;
+ rows = gtk_tree_selection_get_selected_rows (selection, &model);
if (g_list_length (rows) == 1)
{
@@ -294,8 +517,6 @@ on_selection_changed (GtkTreeSelection *selection,
return;
}
- gchar *name;
- gchar *content_type;
gtk_tree_model_get_iter (model, &iter, path);
gtk_tree_path_free (path);
gtk_tree_model_get (model,
@@ -308,30 +529,57 @@ on_selection_changed (GtkTreeSelection *selection,
if (!content_type)
{
+ g_free (name);
return;
}
if (!gitg_utils_can_display_content_type (content_type))
{
- show_binary_information (tree);
+ show_binary_information (tree, buffer);
}
else
{
GtkSourceLanguage *language;
+ gchar *id;
language = gitg_utils_get_language (name, content_type);
gtk_source_buffer_set_language (GTK_SOURCE_BUFFER(buffer),
language);
- gchar *id = node_identity (tree, &iter);
+ id = node_identity (tree, &iter);
- gitg_shell_run (tree->priv->content_shell,
- gitg_command_new (tree->priv->repository,
- "show",
- "--encoding=UTF-8",
- id,
- NULL),
- NULL);
+ if (!blame_mode)
+ {
+ gitg_shell_run (tree->priv->content_shell,
+ gitg_command_new (tree->priv->repository,
+ "show",
+ "--encoding=UTF-8",
+ id,
+ NULL),
+ NULL);
+ }
+ else
+ {
+ gchar **hash_name;
+
+ gitg_blame_renderer_set_max_line_count (tree->priv->blame_renderer, 0);
+ tree->priv->blame_active = TRUE;
+
+ hash_name = g_strsplit (id, ":", 2);
+ gitg_shell_run (tree->priv->blame_shell,
+ gitg_command_new (tree->priv->repository,
+ "blame",
+ "--encoding=UTF-8",
+ "--root",
+ "-l",
+ hash_name[0],
+ "--",
+ hash_name[1],
+ NULL),
+ NULL);
+
+ g_strfreev (hash_name);
+ }
g_free (id);
}
@@ -510,9 +758,25 @@ on_drag_end (GtkWidget *widget,
}
static void
+on_notebook_files_switch_page (GtkNotebook *notebook,
+ GtkWidget *page,
+ guint page_num,
+ GitgRevisionFilesView *files_view)
+{
+ GtkTreeSelection *selection;
+
+ files_view->priv->active_view = gtk_bin_get_child (GTK_BIN (page));
+
+ selection = gtk_tree_view_get_selection (files_view->priv->tree_view);
+ on_selection_changed (selection, files_view);
+}
+
+static void
gitg_revision_files_view_parser_finished (GtkBuildable *buildable,
- GtkBuilder *builder)
+ GtkBuilder *builder)
{
+ GtkSourceGutter *gutter;
+
if (parent_iface.parser_finished)
{
parent_iface.parser_finished (buildable, builder);
@@ -522,10 +786,26 @@ gitg_revision_files_view_parser_finished (GtkBuildable *buildable,
GitgRevisionFilesView *files_view = GITG_REVISION_FILES_VIEW(buildable);
files_view->priv->tree_view = GTK_TREE_VIEW (gtk_builder_get_object (builder,
"revision_files"));
+ files_view->priv->notebook_files = GTK_NOTEBOOK (gtk_builder_get_object (builder,
+ "notebook_files"));
files_view->priv->contents = GTK_SOURCE_VIEW (gtk_builder_get_object (builder,
"revision_files_contents"));
+ files_view->priv->blame_view = GTK_SOURCE_VIEW (gtk_builder_get_object (builder,
+ "revision_blame_view"));
+ files_view->priv->blame_renderer = gitg_blame_renderer_new ();
+
+ gutter = gtk_source_view_get_gutter (GTK_SOURCE_VIEW (files_view->priv->blame_view),
+ GTK_TEXT_WINDOW_LEFT);
+
+ gtk_source_gutter_insert (gutter,
+ GTK_SOURCE_GUTTER_RENDERER (files_view->priv->blame_renderer),
+ 0);
+
+ /* set the active view */
+ files_view->priv->active_view = GTK_WIDGET (files_view->priv->contents);
gitg_utils_set_monospace_font (GTK_WIDGET(files_view->priv->contents));
+ gitg_utils_set_monospace_font (GTK_WIDGET(files_view->priv->blame_view));
gtk_tree_view_set_model (files_view->priv->tree_view,
GTK_TREE_MODEL(files_view->priv->store));
@@ -570,6 +850,11 @@ gitg_revision_files_view_parser_finished (GtkBuildable *buildable,
"changed",
G_CALLBACK(on_selection_changed),
files_view);
+
+ g_signal_connect (files_view->priv->notebook_files,
+ "switch-page",
+ G_CALLBACK (on_notebook_files_switch_page),
+ files_view);
}
static void
@@ -940,7 +1225,7 @@ on_contents_update (GitgShell *shell,
if (content_type && !gitg_utils_can_display_content_type (content_type))
{
gitg_io_cancel (GITG_IO (shell));
- show_binary_information (tree);
+ show_binary_information (tree, buf);
}
else
{
@@ -955,6 +1240,185 @@ on_contents_update (GitgShell *shell,
}
}
+/**
+ * parse_blame:
+ * @line: the line to be parsed
+ * @real_line: the real connect to be inserted in the text buffer
+ * @sha: the sha get from @line
+ * @size: the size for the gutter
+ */
+static void
+parse_blame (const gchar *line,
+ const gchar **real_line,
+ gchar **sha,
+ gint *size)
+{
+ gunichar c;
+ const gchar *name_end;
+
+ *sha = g_strndup (line, GITG_HASH_SHA_SIZE);
+ line += GITG_HASH_SHA_SIZE;
+ *real_line = strstr (line, ") ");
+ *real_line += 2;
+
+ line = strstr (line, " (");
+ line += 2;
+ name_end = line;
+ c = g_utf8_get_char (name_end);
+
+ while (!g_unichar_isdigit (c))
+ {
+ name_end = g_utf8_next_char (name_end);
+ c = g_utf8_get_char (name_end);
+ }
+ name_end--;
+
+ *size = name_end - line + 15; /* hash name + extra space */
+}
+
+static GtkTextTag *
+get_tag_for_sha (GitgRevisionFilesView *tree,
+ GtkTextBuffer *buffer,
+ const gchar *sha)
+{
+ GtkTextTag *tag;
+
+ tag = g_hash_table_lookup (tree->priv->blames, sha);
+
+ if (tag == NULL)
+ {
+ GitgHash hash;
+ GitgRevision *revision;
+
+ tag = gtk_text_buffer_create_tag (buffer, NULL, NULL);
+
+ gitg_hash_sha1_to_hash (sha, hash);
+ revision = gitg_repository_lookup (tree->priv->repository,
+ hash);
+ g_object_set_data (G_OBJECT (tag), BLAME_DATA, revision);
+
+ g_hash_table_insert (tree->priv->blames, g_strdup (sha),
+ g_object_ref (tag));
+ }
+
+ return tag;
+}
+
+static void
+apply_active_blame_tag (GitgRevisionFilesView *tree,
+ GtkTextBuffer *buffer,
+ GtkTextIter *end)
+{
+ GtkTextIter start;
+
+ gtk_text_buffer_get_iter_at_offset (buffer, &start,
+ tree->priv->blame_offset);
+ gtk_text_buffer_apply_tag (buffer, tree->priv->active_tag,
+ &start, end);
+
+ tree->priv->blame_offset = gtk_text_iter_get_offset (end);
+}
+
+static void
+insert_blame_line (GitgRevisionFilesView *tree,
+ GtkTextBuffer *buffer,
+ const gchar *line,
+ GtkTextIter *iter)
+{
+ GtkTextTag *tag;
+ const gchar *real_line;
+ gint size;
+ gchar *sha;
+
+ parse_blame (line, &real_line, &sha, &size);
+ gitg_blame_renderer_set_max_line_count (tree->priv->blame_renderer, size);
+
+ tag = get_tag_for_sha (tree, buffer, sha);
+
+ if (tag != tree->priv->active_tag)
+ {
+ if (tree->priv->active_tag != NULL)
+ {
+ apply_active_blame_tag (tree, buffer, iter);
+ }
+
+ tree->priv->active_tag = tag;
+ }
+
+ g_free (sha);
+
+ gtk_text_buffer_insert (buffer, iter, real_line, -1);
+ gtk_text_buffer_insert (buffer, iter, "\n", -1);
+}
+
+static void
+on_blame_update (GitgShell *shell,
+ gchar **buffer,
+ GitgRevisionFilesView *tree)
+{
+ GtkTextBuffer *buf;
+ GtkTextIter end;
+ gchar *line;
+
+ buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tree->priv->blame_view));
+ gtk_text_buffer_get_end_iter (buf, &end);
+
+ while ((line = *buffer++))
+ {
+ insert_blame_line (tree, buf, line, &end);
+ }
+
+ if (tree->priv->active_tag != NULL)
+ {
+ apply_active_blame_tag (tree, buf, &end);
+ }
+
+ if (gtk_source_buffer_get_language (GTK_SOURCE_BUFFER (buf)) == NULL)
+ {
+ gchar *content_type = gitg_utils_guess_content_type (buf);
+
+ if (content_type && !gitg_utils_can_display_content_type (content_type))
+ {
+ gitg_io_cancel (GITG_IO (shell));
+ show_binary_information (tree, buf);
+ }
+ else
+ {
+ GtkSourceLanguage *language;
+
+ language = gitg_utils_get_language (NULL, content_type);
+ gtk_source_buffer_set_language (GTK_SOURCE_BUFFER (buf),
+ language);
+ }
+
+ g_free (content_type);
+ }
+}
+
+static void
+on_blame_end (GitgShell *shell,
+ GError *error,
+ GitgRevisionFilesView *tree)
+{
+ GtkSourceGutter *gutter;
+
+ tree->priv->query_data_id =
+ g_signal_connect (tree->priv->blame_renderer,
+ "query-data",
+ G_CALLBACK (blame_renderer_query_data_cb),
+ tree);
+
+ tree->priv->query_tooltip_id =
+ g_signal_connect (tree->priv->blame_renderer,
+ "query-tooltip",
+ G_CALLBACK (blame_renderer_query_tooltip_cb),
+ tree);
+
+ gutter = gtk_source_view_get_gutter (GTK_SOURCE_VIEW (tree->priv->blame_view),
+ GTK_TEXT_WINDOW_LEFT);
+ gtk_source_gutter_queue_draw (gutter);
+}
+
static void
gitg_revision_files_view_init (GitgRevisionFilesView *self)
{
@@ -985,6 +1449,21 @@ gitg_revision_files_view_init (GitgRevisionFilesView *self)
"update",
G_CALLBACK (on_contents_update),
self);
+
+ self->priv->blames = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ NULL);
+
+ self->priv->blame_shell = gitg_shell_new (5000);
+ g_signal_connect (self->priv->blame_shell,
+ "update",
+ G_CALLBACK (on_blame_update),
+ self);
+ g_signal_connect (self->priv->blame_shell,
+ "end",
+ G_CALLBACK (on_blame_end),
+ self);
}
static void
diff --git a/gitg/gitg-revision-files-panel.ui b/gitg/gitg-revision-files-panel.ui
index acdbc8a..57b6361 100644
--- a/gitg/gitg-revision-files-panel.ui
+++ b/gitg/gitg-revision-files-panel.ui
@@ -1,6 +1,7 @@
<?xml version="1.0"?>
<interface>
- <object class="GtkSourceBuffer" id="source_buffer"></object>
+ <object class="GtkSourceBuffer" id="source_buffer_source"></object>
+ <object class="GtkSourceBuffer" id="source_buffer_blame"></object>
<object class="GitgRevisionFilesView" id="revision_files_view">
<property name="visible">True</property>
<property name="can_focus">True</property>
@@ -43,17 +44,46 @@
</packing>
</child>
<child>
- <object class="GtkScrolledWindow" id="scrolled_window_files_contents">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="hscrollbar_policy">automatic</property>
- <property name="vscrollbar_policy">automatic</property>
- <property name="shadow_type">etched-in</property>
+ <object class="GtkNotebook" id="notebook_files">
+ <child>
+ <object class="GtkScrolledWindow" id="scrolled_window_files_contents">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">automatic</property>
+ <property name="vscrollbar_policy">automatic</property>
+ <property name="shadow_type">etched-in</property>
+ <child>
+ <object class="GtkSourceView" id="revision_files_contents">
+ <property name="buffer">source_buffer_source</property>
+ <property name="editable">False</property>
+ <property name="show_line_numbers">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label_source">
+ <property name="label">Source</property>
+ </object>
+ </child>
<child>
- <object class="GtkSourceView" id="revision_files_contents">
- <property name="buffer">source_buffer</property>
- <property name="editable">False</property>
- <property name="show_line_numbers">True</property>
+ <object class="GtkScrolledWindow" id="scrolled_window_blame">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">automatic</property>
+ <property name="vscrollbar_policy">automatic</property>
+ <property name="shadow_type">etched-in</property>
+ <child>
+ <object class="GtkSourceView" id="revision_blame_view">
+ <property name="buffer">source_buffer_blame</property>
+ <property name="editable">False</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label_blame">
+ <property name="label">Blame</property>
</object>
</child>
</object>
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]