[gitg] Rework diff selection for improved rubber banding
- From: Jesse van den Kieboom <jessevdk src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gitg] Rework diff selection for improved rubber banding
- Date: Sun, 20 Dec 2015 16:48:33 +0000 (UTC)
commit 98e49eb09e2749d0e80b257141ca88edd0ce65dd
Author: Jesse van den Kieboom <jessevdk gnome org>
Date: Sun Dec 20 17:47:22 2015 +0100
Rework diff selection for improved rubber banding
This changes the behavior to selection being always additive, unless
pressing alt (mod1).
libgitg/Makefile.am | 2 +-
libgitg/gitg-diff-view-file-selectable.vala | 349 ++++++++++++++++++++-------
libgitg/gitg-platform-support-osx.c | 74 ++++++
libgitg/gitg-platform-support.c | 40 +++
libgitg/gitg-platform-support.h | 8 +
vapi/gitg-platform-support.vapi | 7 +
6 files changed, 386 insertions(+), 94 deletions(-)
---
diff --git a/libgitg/Makefile.am b/libgitg/Makefile.am
index 24b6994..385d9b8 100644
--- a/libgitg/Makefile.am
+++ b/libgitg/Makefile.am
@@ -24,7 +24,7 @@ libgitg_libgitg_1_0_la_LIBADD = \
if GDK_WINDOWING_QUARTZ
libgitg_libgitg_1_0_la_LIBADD += -lobjc
libgitg_libgitg_1_0_la_CFLAGS += -xobjective-c
-libgitg_libgitg_1_0_la_LDFLAGS += -framework Foundation
+libgitg_libgitg_1_0_la_LDFLAGS += -framework Foundation -framework AppKit
endif
libgitg_libgitg_1_0_la_VALAFLAGS = \
diff --git a/libgitg/gitg-diff-view-file-selectable.vala b/libgitg/gitg-diff-view-file-selectable.vala
index ec922a3..76b2c50 100644
--- a/libgitg/gitg-diff-view-file-selectable.vala
+++ b/libgitg/gitg-diff-view-file-selectable.vala
@@ -17,14 +17,24 @@
* along with gitg. If not, see <http://www.gnu.org/licenses/>.
*/
+enum Gitg.DiffSelectionMode {
+ NONE,
+ SELECTING,
+ DESELECTING
+}
+
class Gitg.DiffViewFileSelectable : Object
{
private string d_selection_category = "selection";
private Gtk.TextTag d_selection_tag;
- private bool d_is_selecting;
- private bool d_is_deselecting;
+ private DiffSelectionMode d_selection_mode;
private Gtk.TextMark d_start_selection_mark;
private Gtk.TextMark d_end_selection_mark;
+ private Gee.HashMap<int, bool> d_originally_selected;
+ private Gdk.Cursor d_subtract_cursor_ptr;
+ private Gdk.Cursor d_subtract_cursor_hand;
+ private Gdk.Cursor d_cursor_ptr;
+ private Gdk.Cursor d_cursor_hand;
public Gtk.SourceView source_view
{
@@ -46,24 +56,118 @@ class Gitg.DiffViewFileSelectable : Object
source_view.button_press_event.connect(button_press_event_on_view);
source_view.motion_notify_event.connect(motion_notify_event_on_view);
source_view.button_release_event.connect(button_release_event_on_view);
+ source_view.key_press_event.connect(key_press_event_on_view);
+ source_view.key_release_event.connect(key_release_event_on_view);
source_view.get_style_context().add_class("handle-selection");
source_view.realize.connect(() => {
- update_cursor(Gdk.CursorType.LEFT_PTR);
+ update_cursor(cursor_ptr);
});
source_view.notify["state-flags"].connect(() => {
- update_cursor(Gdk.CursorType.LEFT_PTR);
+ update_cursor(cursor_ptr);
});
d_selection_tag = source_view.buffer.create_tag("selection");
source_view.style_updated.connect(update_theme);
update_theme();
+
+ d_originally_selected = new Gee.HashMap<int, bool>();
+ }
+
+ private Gdk.Cursor composite_subtract_cursor(Gdk.CursorType cursor)
+ {
+ int width, height, hot_x, hot_y;
+
+ var surface = PlatformSupport.create_cursor_surface(source_view.get_display(),
+ cursor,
+ out hot_x,
+ out hot_y,
+ out width,
+ out height);
+
+ if (surface == null)
+ {
+ return new Gdk.Cursor.for_display(source_view.get_display(), cursor);
+ }
+
+ var ctx = new Cairo.Context(surface);
+
+ ctx.set_line_width(1);
+
+ const int margin = 2;
+ const int length = 5;
+
+ ctx.set_source_rgb(0, 0, 0);
+ ctx.move_to(width - margin - length + 0.5, margin - (length - 1) / 2 + 0.5);
+ ctx.rel_line_to(length, 0);
+ ctx.stroke();
+
+ ctx.set_source_rgb(1, 1, 1);
+ ctx.move_to(width - margin - length + 0.5, margin - (length - 1) / 2 + 1.5);
+ ctx.rel_line_to(length, 0);
+ ctx.stroke();
+
+ return new Gdk.Cursor.from_surface(source_view.get_display(), surface, hot_x, hot_y);
+ }
+
+ private Gdk.Cursor cursor_ptr
+ {
+ owned get
+ {
+ if (d_cursor_ptr == null)
+ {
+ d_cursor_ptr = new Gdk.Cursor.for_display(source_view.get_display(),
Gdk.CursorType.LEFT_PTR);
+ }
+
+ return d_cursor_ptr;
+ }
+ }
+
+ private Gdk.Cursor cursor_hand
+ {
+ owned get
+ {
+ if (d_cursor_hand == null)
+ {
+ d_cursor_hand = new Gdk.Cursor.for_display(source_view.get_display(),
Gdk.CursorType.HAND1);
+ }
+
+ return d_cursor_hand;
+ }
+ }
+
+ private Gdk.Cursor subtract_cursor_ptr
+ {
+ owned get
+ {
+ if (d_subtract_cursor_ptr == null)
+ {
+ d_subtract_cursor_ptr = composite_subtract_cursor(Gdk.CursorType.LEFT_PTR);
+ }
+
+
+ return d_subtract_cursor_ptr;
+ }
+ }
+
+ private Gdk.Cursor subtract_cursor_hand
+ {
+ owned get
+ {
+ if (d_subtract_cursor_hand == null)
+ {
+ d_subtract_cursor_hand = composite_subtract_cursor(Gdk.CursorType.HAND1);
+ }
+
+
+ return d_subtract_cursor_hand;
+ }
}
- private void update_cursor(Gdk.CursorType type)
+ private void update_cursor(Gdk.Cursor cursor)
{
var window = source_view.get_window(Gtk.TextWindowType.TEXT);
@@ -72,10 +176,32 @@ class Gitg.DiffViewFileSelectable : Object
return;
}
- var cursor = new Gdk.Cursor.for_display(source_view.get_display(), type);
window.set_cursor(cursor);
}
+ private void update_cursor_for_state(Gdk.ModifierType state)
+ {
+ Gtk.TextIter iter;
+
+ var is_hunk = get_iter_from_pointer_position(out iter) && get_line_is_hunk(iter);
+
+ if ((state & Gdk.ModifierType.MOD1_MASK) != 0 || d_selection_mode ==
DiffSelectionMode.DESELECTING)
+ {
+ if (is_hunk)
+ {
+ update_cursor(subtract_cursor_hand);
+ }
+ else
+ {
+ update_cursor(subtract_cursor_ptr);
+ }
+ }
+ else
+ {
+ update_cursor(is_hunk ? cursor_hand : cursor_ptr);
+ }
+ }
+
private void update_theme()
{
var selection_attributes = new Gtk.SourceMarkAttributes();
@@ -98,22 +224,22 @@ class Gitg.DiffViewFileSelectable : Object
private bool get_line_selected(Gtk.TextIter iter)
{
- var text_view = source_view as Gtk.TextView;
Gtk.TextIter start = iter;
start.set_line_offset(0);
- var buffer = text_view.get_buffer() as Gtk.SourceBuffer;
+
+ var buffer = source_view.buffer as Gtk.SourceBuffer;
return buffer.get_source_marks_at_iter(start, d_selection_category) != null;
}
private bool get_line_is_diff(Gtk.TextIter iter)
{
- var text_view = source_view as Gtk.TextView;
Gtk.TextIter start = iter;
start.set_line_offset(0);
- var buffer = text_view.get_buffer() as Gtk.SourceBuffer;
+
+ var buffer = source_view.buffer as Gtk.SourceBuffer;
return (buffer.get_source_marks_at_iter(start, "added") != null) ||
(buffer.get_source_marks_at_iter(start, "removed") != null);
@@ -121,19 +247,19 @@ class Gitg.DiffViewFileSelectable : Object
private bool get_line_is_hunk(Gtk.TextIter iter)
{
- var text_view = source_view as Gtk.TextView;
Gtk.TextIter start = iter;
start.set_line_offset(0);
- var buffer = text_view.get_buffer() as Gtk.SourceBuffer;
+
+ var buffer = source_view.buffer as Gtk.SourceBuffer;
return buffer.get_source_marks_at_iter(start, "header") != null;
}
private bool get_iter_from_pointer_position(out Gtk.TextIter iter)
{
- var text_view = source_view as Gtk.TextView;
- var win = text_view.get_window(Gtk.TextWindowType.TEXT);
+ var win = source_view.get_window(Gtk.TextWindowType.TEXT);
+
int x, y, width, height;
// To silence unassigned iter warning
@@ -152,17 +278,16 @@ class Gitg.DiffViewFileSelectable : Object
}
int win_x, win_y;
- text_view.window_to_buffer_coords(Gtk.TextWindowType.TEXT, x, y, out win_x, out win_y);
- text_view.get_iter_at_location(out iter, win_x, win_y);
+ source_view.window_to_buffer_coords(Gtk.TextWindowType.TEXT, x, y, out win_x, out win_y);
+ source_view.get_iter_at_location(out iter, win_x, win_y);
return true;
}
- private void select_range(Gtk.TextIter start, Gtk.TextIter end)
+ private void update_selection_range(Gtk.TextIter start, Gtk.TextIter end, bool select)
{
- var text_view = source_view as Gtk.TextView;
- var buffer = text_view.get_buffer() as Gtk.SourceBuffer;
+ var buffer = source_view.buffer as Gtk.SourceBuffer;
Gtk.TextIter real_start, real_end;
@@ -172,40 +297,70 @@ class Gitg.DiffViewFileSelectable : Object
real_start.order(real_end);
real_start.set_line_offset(0);
- while (real_start.get_line() <= real_end.get_line())
+ real_end.forward_to_line_end();
+
+ var start_line = real_start.get_line();
+ var end_line = real_end.get_line();
+
+ var current = real_start;
+
+ while (start_line <= end_line)
{
- if (get_line_is_diff(real_start))
+ if (get_line_is_diff(current))
{
- buffer.create_source_mark(null, d_selection_category, real_start);
+ if (!d_originally_selected.has_key(start_line))
+ {
+ d_originally_selected[start_line] = get_line_selected(current);
+ }
+
+ if (select)
+ {
+ buffer.create_source_mark(null, d_selection_category, current);
- var line_end = real_start;
- line_end.forward_to_line_end();
+ var line_end = current;
+ line_end.forward_to_line_end();
- buffer.apply_tag(d_selection_tag, real_start, line_end);
+ buffer.apply_tag(d_selection_tag, current, line_end);
+ }
}
- if (!real_start.forward_line())
+ if (!current.forward_line())
{
break;
}
+
+ start_line++;
+ }
+
+ if (!select)
+ {
+ buffer.remove_source_marks(real_start, real_end, d_selection_category);
+ buffer.remove_tag(d_selection_tag, real_start, real_end);
}
}
- private void deselect_range(Gtk.TextIter start, Gtk.TextIter end)
+ private void clear_original_selection(Gtk.TextIter start, Gtk.TextIter end, bool include_end)
{
- var text_view = source_view as Gtk.TextView;
- var buffer = text_view.get_buffer() as Gtk.SourceBuffer;
+ var current = start;
+ current.set_line_offset(0);
- Gtk.TextIter real_start, real_end;
+ var end_line = end.get_line();
+ var current_line = current.get_line();
- real_start = start;
- real_start.set_line_offset(0);
+ if (include_end)
+ {
+ end_line++;
+ }
- real_end = end;
- real_end.forward_to_line_end();
+ while (current_line < end_line)
+ {
+ var originally_selected = d_originally_selected[current_line];
+
+ update_selection_range(current, current, originally_selected);
- buffer.remove_source_marks(real_start, real_end, d_selection_category);
- buffer.remove_tag(d_selection_tag, real_start, real_end);
+ current.forward_line();
+ current_line++;
+ }
}
private bool button_press_event_on_view(Gdk.EventButton event)
@@ -216,92 +371,81 @@ class Gitg.DiffViewFileSelectable : Object
}
Gtk.TextIter iter;
+
if (!get_iter_from_pointer_position(out iter))
{
return false;
}
- if (get_line_selected(iter))
+ if ((event.state & Gdk.ModifierType.MOD1_MASK) != 0)
{
- d_is_deselecting = true;
- deselect_range(iter, iter);
+ d_selection_mode = DiffSelectionMode.DESELECTING;
}
else
{
- d_is_selecting = true;
- select_range(iter, iter);
+ d_selection_mode = DiffSelectionMode.SELECTING;
}
- var text_view = source_view as Gtk.TextView;
- var buffer = text_view.get_buffer();
+ var buffer = source_view.buffer;
d_start_selection_mark = buffer.create_mark(null, iter, false);
d_end_selection_mark = buffer.create_mark(null, iter, false);
- return false;
+ update_selection(iter);
+ update_cursor_for_state(event.state);
+
+ return true;
}
- private bool motion_notify_event_on_view(Gdk.EventMotion event)
+ private void update_selection(Gtk.TextIter cursor)
{
- Gtk.TextIter iter;
- if (!get_iter_from_pointer_position(out iter))
- {
- return false;
- }
-
- update_cursor(get_line_is_hunk(iter) ? Gdk.CursorType.HAND1 : Gdk.CursorType.LEFT_PTR);
-
- if (!d_is_selecting && !d_is_deselecting)
- {
- return false;
- }
-
- var text_view = source_view as Gtk.TextView;
- var buffer = text_view.get_buffer();
-
- Gtk.TextIter start, end, current;
+ var buffer = source_view.buffer;
- current = iter;
+ Gtk.TextIter start, end;
buffer.get_iter_at_mark(out start, d_start_selection_mark);
- start.order(current);
+ buffer.get_iter_at_mark(out end, d_end_selection_mark);
- if (d_is_selecting)
+ // Clear to original selection
+ if (start.get_line() < end.get_line())
{
- select_range(start, current);
+ var next = cursor;
+ next.forward_line();
+
+ clear_original_selection(next, end, true);
}
else
{
- deselect_range(start, current);
+ clear_original_selection(end, cursor, false);
}
- buffer.get_iter_at_mark(out end, d_end_selection_mark);
- if (!end.in_range(start, current))
- {
- start = end;
- current = iter;
+ update_selection_range(start, cursor, d_selection_mode == DiffSelectionMode.SELECTING);
+ buffer.move_mark(d_end_selection_mark, cursor);
+ }
- start.order(current);
+ private bool motion_notify_event_on_view(Gdk.EventMotion event)
+ {
+ Gtk.TextIter iter;
- if (d_is_selecting)
- {
- deselect_range(start, current);
- }
- else
- {
- select_range(start, current);
- }
+ if (!get_iter_from_pointer_position(out iter))
+ {
+ return false;
}
- buffer.move_mark(d_end_selection_mark, iter);
+ update_cursor_for_state(event.state);
- return false;
+ if (d_selection_mode == DiffSelectionMode.NONE)
+ {
+ return false;
+ }
+
+ update_selection(iter);
+ return true;
}
private void update_has_selection()
{
- var text_view = source_view as Gtk.TextView;
- var buffer = text_view.get_buffer();
+ var buffer = source_view.buffer;
Gtk.TextIter iter;
buffer.get_start_iter(out iter);
@@ -330,20 +474,39 @@ class Gitg.DiffViewFileSelectable : Object
return false;
}
- d_is_selecting = false;
- d_is_deselecting = false;
+ d_selection_mode = DiffSelectionMode.NONE;
- var text_view = source_view as Gtk.TextView;
- var buffer = text_view.get_buffer();
+ var buffer = source_view.buffer;
- buffer.delete_mark(d_start_selection_mark);
- d_start_selection_mark = null;
+ if (d_start_selection_mark != null)
+ {
+ buffer.delete_mark(d_start_selection_mark);
+ d_start_selection_mark = null;
+ }
- buffer.delete_mark(d_end_selection_mark);
- d_end_selection_mark = null;
+ if (d_end_selection_mark != null)
+ {
+ buffer.delete_mark(d_end_selection_mark);
+ d_end_selection_mark = null;
+ }
update_has_selection();
+ d_originally_selected.clear();
+ return true;
+ }
+
+ private bool key_press_event_on_view(Gdk.EventKey event)
+ {
+ stdout.printf(@"press: $(event.state)\n");
+ update_cursor_for_state(event.state);
+ return false;
+ }
+
+ private bool key_release_event_on_view(Gdk.EventKey event)
+ {
+ stdout.printf(@"release: $(event.state)\n");
+ update_cursor_for_state(event.state);
return false;
}
}
diff --git a/libgitg/gitg-platform-support-osx.c b/libgitg/gitg-platform-support-osx.c
index 3c3f04d..d105cce 100644
--- a/libgitg/gitg-platform-support-osx.c
+++ b/libgitg/gitg-platform-support-osx.c
@@ -85,4 +85,78 @@ gitg_platform_support_http_get_finish (GAsyncResult *result,
return g_task_propagate_pointer (G_TASK (result), error);
}
+cairo_surface_t *
+gitg_platform_support_create_cursor_surface (GdkDisplay *display,
+ GdkCursorType cursor_type,
+ gint *hot_x,
+ gint *hot_y,
+ gint *width,
+ gint *height)
+{
+ NSCursor *cursor;
+ NSImage *image;
+ NSBitmapImageRep *image_rep;
+ const unsigned char *pixel_data;
+ NSSize size;
+ NSPoint hotspot;
+ gint w, h;
+ cairo_surface_t *surface, *target;
+ cairo_t *ctx;
+
+ switch (cursor_type)
+ {
+ case GDK_HAND1:
+ cursor = [NSCursor pointingHandCursor];
+ break;
+ default:
+ cursor = [NSCursor arrowCursor];
+ break;
+ }
+
+ image = [cursor image];
+
+ image_rep = [[NSBitmapImageRep alloc] initWithData:[image TIFFRepresentation]];
+ pixel_data = [image_rep bitmapData];
+
+ w = [image_rep pixelsWide];
+ h = [image_rep pixelsHigh];
+
+ hotspot = [cursor hotSpot];
+ size = [image size];
+
+ if (hot_x)
+ {
+ *hot_x = (gint)(hotspot.x);
+ }
+
+ if (hot_y)
+ {
+ *hot_y = (gint)(hotspot.y);
+ }
+
+ if (width)
+ {
+ *width = size.width;
+ }
+
+ if (height)
+ {
+ *height = size.height;
+ }
+
+ surface = cairo_image_surface_create_for_data (pixel_data, CAIRO_FORMAT_ARGB32, w, h, [image_rep
bytesPerRow]);
+ target = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, size.width, size.height);
+
+ ctx = cairo_create (target);
+
+ cairo_scale (ctx, size.width / w, size.height / h);
+ cairo_set_source_surface (ctx, surface, 0, 0);
+ cairo_paint (ctx);
+ cairo_destroy (ctx);
+
+ cairo_surface_destroy (surface);
+
+ return target;
+}
+
// ex:ts=4 noet
diff --git a/libgitg/gitg-platform-support.c b/libgitg/gitg-platform-support.c
index b2406b3..410e5d6 100644
--- a/libgitg/gitg-platform-support.c
+++ b/libgitg/gitg-platform-support.c
@@ -41,4 +41,44 @@ gitg_platform_support_http_get_finish (GAsyncResult *result,
return G_INPUT_STREAM (g_file_read_finish (g_async_result_get_source_object (result), result, error));
}
+cairo_surface_t *
+gitg_platform_support_create_cursor_surface (GdkDisplay *display,
+ GdkCursorType cursor_type,
+ gint *hot_x,
+ gint *hot_y,
+ gint *width,
+ gint *height)
+{
+ GdkCursor *cursor;
+ cairo_surface_t *surface;
+ gint w = 0, h = 0;
+
+ cursor = gdk_cursor_new_for_display (display, cursor_type);
+ surface = gdk_cursor_get_surface (hot_x, hot_y);
+
+ switch (cairo_surface_get_type (surface))
+ {
+ case CAIRO_SURFACE_TYPE_XLIB:
+ w = cairo_xlib_surface_get_width (surface);
+ h = cairo_xlib_surface_get_height (surface);
+ break;
+ case CAIRO_SURFACE_TYPE_IMAGE:
+ w = cairo_image_surface_get_width (surface);
+ h = cairo_image_surface_get_height (surface);
+ break;
+ }
+
+ if (width)
+ {
+ *width = w;
+ }
+
+ if (height)
+ {
+ *height = h;
+ }
+
+ return surface;
+}
+
// ex:ts=4 noet
diff --git a/libgitg/gitg-platform-support.h b/libgitg/gitg-platform-support.h
index 6bb4075..ea502bb 100644
--- a/libgitg/gitg-platform-support.h
+++ b/libgitg/gitg-platform-support.h
@@ -22,6 +22,7 @@
#include <gdk/gdk.h>
#include <gio/gio.h>
+#include <cairo/cairo.h>
gboolean gitg_platform_support_use_native_window_controls (GdkDisplay *display);
@@ -33,6 +34,13 @@ void gitg_platform_support_http_get (GFile *file,
GInputStream *gitg_platform_support_http_get_finish (GAsyncResult *result,
GError **error);
+cairo_surface_t *gitg_platform_support_create_cursor_surface (GdkDisplay *display,
+ GdkCursorType cursor_type,
+ gint *hot_x,
+ gint *hot_y,
+ gint *width,
+ gint *height);
+
#endif /* __GITG_PLATFORM_SUPPORT_H__ */
// ex:ts=4 noet
diff --git a/vapi/gitg-platform-support.vapi b/vapi/gitg-platform-support.vapi
index 7f99103..50b1b99 100644
--- a/vapi/gitg-platform-support.vapi
+++ b/vapi/gitg-platform-support.vapi
@@ -5,5 +5,12 @@ namespace Gitg
{
public static bool use_native_window_controls(Gdk.Display? display = null);
public static async GLib.InputStream http_get(GLib.File url, GLib.Cancellable? cancellable =
null) throws GLib.IOError;
+
+ public static Cairo.Surface create_cursor_surface(Gdk.Display? display,
+ Gdk.CursorType cursor_type,
+ out int hot_x = null,
+ out int hot_y = null,
+ out int width = null,
+ out int height = null);
}
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]