[polari] chatView: Clean up handling of interactive tags
- From: Florian Müllner <fmuellner src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [polari] chatView: Clean up handling of interactive tags
- Date: Tue, 22 Dec 2015 18:18:54 +0000 (UTC)
commit 6ba4640ba7658105f517ccd2b0c2735cbfe13ba9
Author: Florian Müllner <fmuellner gnome org>
Date: Sat Dec 19 01:41:53 2015 +0100
chatView: Clean up handling of interactive tags
With the new status grouping where the header can be clicked to reveal
collapsed status messages, we now have a second text tag that responds
to input besides clickable links. This makes the code that handles them
centrally quite awkward, as we now need to differentiate not only on the
type of events, but also the tag type interacted with.
Clean this up by splitting out the generally useful event handling into
a ButtonTag class (with some help in TextView for hover-tracking), and
use its ::clicked, ::button-press-event and ::button-release-event signals
to implement links and status group headers and a much more natural way.
https://bugzilla.gnome.org/show_bug.cgi?id=759677
src/chatView.js | 199 +++++++++++++++++++++++++++++++++++++------------------
1 files changed, 135 insertions(+), 64 deletions(-)
---
diff --git a/src/chatView.js b/src/chatView.js
index 77168f7..0354571 100644
--- a/src/chatView.js
+++ b/src/chatView.js
@@ -1,6 +1,7 @@
const Gdk = imports.gi.Gdk;
const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
+const GObject = imports.gi.GObject;
const Gtk = imports.gi.Gtk;
const Pango = imports.gi.Pango;
const PangoCairo = imports.gi.PangoCairo;
@@ -129,6 +130,86 @@ const TextView = new Lang.Class({
}
});
+const ButtonTag = new Lang.Class({
+ Name: 'ButtonTag',
+ Extends: Gtk.TextTag,
+ Properties: {
+ 'hover': GObject.ParamSpec.boolean('hover',
+ 'hover',
+ 'hover',
+ GObject.ParamFlags.READWRITE,
+ false)
+ },
+ Signals: {
+ 'button-press-event': {
+ flags: GObject.SignalFlags.RUN_LAST,
+ param_types: [Gdk.Event.$gtype],
+ return_type: GObject.TYPE_BOOLEAN,
+ accumulator: GObject.AccumulatorType.TRUE_HANDLED
+ },
+ 'button-release-event': {
+ flags: GObject.SignalFlags.RUN_LAST,
+ param_types: [Gdk.Event.$gtype],
+ return_type: GObject.TYPE_BOOLEAN,
+ accumulator: GObject.AccumulatorType.TRUE_HANDLED
+ },
+ 'clicked': { }
+ },
+
+ _init: function(params) {
+ this.parent(params);
+
+ this._hover = false;
+ this._pressed = false;
+ },
+
+ get hover() {
+ return this._hover;
+ },
+
+ set hover(hover) {
+ if (this._hover == hover)
+ return;
+
+ this._hover = hover;
+ this.notify('hover');
+ },
+
+ on_notify: function(pspec) {
+ if (pspec.name == 'hover' && !this.hover)
+ this._pressed = false;
+ },
+
+ 'on_button-press-event': function(event) {
+ let [, button] = event.get_button();
+ this._pressed = button == Gdk.BUTTON_PRIMARY;
+
+ return Gdk.EVENT_STOP;
+ },
+
+ 'on_button-release-event': function(event) {
+ let [, button] = event.get_button();
+ if (!(button == Gdk.BUTTON_PRIMARY && this._pressed))
+ return Gdk.EVENT_PROPAGATE;
+
+ this._pressed = false;
+ this.emit('clicked');
+ return Gdk.EVENT_STOP;
+ },
+
+ vfunc_event: function(object, event, iter) {
+ let type = event.get_event_type();
+
+ if (type != Gdk.EventType.BUTTON_PRESS &&
+ type != Gdk.EventType.BUTTON_RELEASE)
+ return Gdk.EVENT_PROPAGATE;
+
+ let isPress = type == Gdk.EventType.BUTTON_PRESS;
+ return this.emit(isPress ? 'button-press-event'
+ : 'button-release-event', event);
+ }
+});
+
const ChatView = new Lang.Class({
Name: 'ChatView',
@@ -146,7 +227,7 @@ const ChatView = new Lang.Class({
this._toplevelFocus = false;
this._joinTime = 0;
this._maxNickChars = MAX_NICK_CHARS;
- this._hoveringLink = false;
+ this._hoveredButtonTags = [];
this._needsIndicator = true;
this._pending = {};
this._pendingLogs = [];
@@ -171,7 +252,7 @@ const ChatView = new Lang.Class({
this._app = Gio.Application.get_default();
this._app.pasteManager.addWidget(this._view);
- this._linkCursor = Gdk.Cursor.new(Gdk.CursorType.HAND1);
+ this._hoverCursor = Gdk.Cursor.new(Gdk.CursorType.HAND1);
this._channelSignals = [];
this._channel = null;
@@ -313,12 +394,8 @@ const ChatView = new Lang.Class({
this.widget.vadjustment.connect('changed',
Lang.bind(this, this._updateScroll));
this._view.connect('key-press-event', Lang.bind(this, this._onKeyPress));
- this._view.connect('button-release-event',
- Lang.bind(this, this._handleLinkClicks));
- this._view.connect('button-press-event',
- Lang.bind(this, this._handleLinkClicks));
this._view.connect('motion-notify-event',
- Lang.bind(this, this._handleLinkHovers));
+ Lang.bind(this, this._handleButtonTagsHover));
},
_onDestroy: function() {
@@ -527,67 +604,36 @@ const ChatView = new Lang.Class({
menu.popup(null, null, null, button, time);
},
- _handleLinkClicks: function(view, event) {
- let isPress = event.get_event_type() == Gdk.EventType.BUTTON_PRESS;
-
- let [, button] = event.get_button();
- if (button != Gdk.BUTTON_PRIMARY &&
- (button != Gdk.BUTTON_SECONDARY || !isPress))
- return Gdk.EVENT_PROPAGATE;
-
- if (isPress)
- this._clickedUrl = null;
-
+ _handleButtonTagsHover: function(view, event) {
let [, eventX, eventY] = event.get_coords();
let [x, y] = view.window_to_buffer_coords(Gtk.TextWindowType.WIDGET,
eventX, eventY);
-
let iter = view.get_iter_at_location(x, y);
- let tags = iter.get_tags();
- for (let i = 0; i < tags.length; i++) {
- let url = tags[i]._url;
- if (url) {
- if (url.indexOf(':') == -1)
- url = 'http://' + url;
-
- if (isPress) {
- if (button == Gdk.BUTTON_PRIMARY)
- this._clickedUrl = url;
- else
- this._showUrlContextMenu(url, button, event.get_time());
- return Gdk.EVENT_STOP;
- } else if (this._clickedUrl == url) {
- Utils.openURL(url, event.get_time());
- return Gdk.EVENT_STOP;
- }
- break;
- } else if (tags[i].name.startsWith('status-compressed')) {
- if (isPress && button == Gdk.BUTTON_PRIMARY) {
- let statusTag = this._lookupTag('status' + tags[i].name.slice(17)); //
'status-compressed'.length
- statusTag.invisible = !statusTag.invisible;
- }
- return Gdk.EVENT_STOP;
- }
- }
- return Gdk.EVENT_PROPAGATE;
- },
- _handleLinkHovers: function(view, event) {
- let [, eventX, eventY] = event.get_coords();
- let [x, y] = view.window_to_buffer_coords(Gtk.TextWindowType.WIDGET,
- eventX, eventY);
- let iter = view.get_iter_at_location(x, y);
- let tags = iter.get_tags();
- let hovering = false;
- for (let i = 0; i < tags.length && !hovering; i++)
- if (tags[i]._url || tags[i].name.startsWith('status-compressed'))
- hovering = true;
-
- if (this._hoveringLink != hovering) {
- this._hoveringLink = hovering;
- let cursor = this._hoveringLink ? this._linkCursor : null;
+ let hoveredButtonTags = iter.get_tags().filter(
+ function(t) {
+ return t instanceof ButtonTag;
+ });
+
+ hoveredButtonTags.forEach(
+ function(t) {
+ t.hover = true;
+ });
+ this._hoveredButtonTags.forEach(
+ function(t) {
+ t.hover = hoveredButtonTags.indexOf(t) >= 0;
+ });
+
+ let isHovering = hoveredButtonTags.length > 0;
+ let wasHovering = this._hoveredButtonTags.length > 0;
+
+ if (isHovering != wasHovering) {
+ let cursor = isHovering ? this._hoverCursor : null;
this._view.get_window(Gtk.TextWindowType.TEXT).set_cursor(cursor);
}
+
+ this._hoveredButtonTags = hoveredButtonTags;
+
return Gdk.EVENT_PROPAGATE;
},
@@ -802,11 +848,16 @@ const ChatView = new Lang.Class({
let headerTag, groupTag;
if (!headerMark) {
// we are starting a new group
- headerTag = new Gtk.TextTag({ name: headerTagName, invisible: true });
+ headerTag = new ButtonTag({ name: headerTagName, invisible: true });
groupTag = new Gtk.TextTag({ name: groupTagName });
buffer.tag_table.add(headerTag);
buffer.tag_table.add(groupTag);
+ headerTag.connect('clicked',
+ function() {
+ groupTag.invisible = !groupTag.invisible;
+ });
+
this._ensureNewLine();
headerMark = buffer.create_mark('idle-status-start', buffer.get_end_iter(), true);
} else {
@@ -1074,8 +1125,7 @@ const ChatView = new Lang.Class({
let url = urls[i];
this._insertWithTags(iter, text.substr(pos, url.pos - pos), tags);
- let tag = new Gtk.TextTag();
- tag._url = url.url;
+ let tag = this._createUrlTag(url.url);
this._view.get_buffer().tag_table.add(tag);
this._insertWithTags(iter, url.url,
@@ -1086,6 +1136,27 @@ const ChatView = new Lang.Class({
this._insertWithTags(iter, text.substr(pos), tags);
},
+ _createUrlTag: function(url) {
+ if (url.indexOf(':') == -1)
+ url = 'http://' + url;
+
+ let tag = new ButtonTag();
+ tag.connect('clicked',
+ function() {
+ Utils.openURL(url, Gtk.get_current_event_time());
+ });
+ tag.connect('button-press-event', Lang.bind(this,
+ function(tag, event) {
+ let [, button] = event.get_button();
+ if (button != Gdk.BUTTON_SECONDARY)
+ return Gdk.EVENT_PROPAGATE;
+
+ this._showUrlContextMenu(url, button, event.get_time());
+ return Gdk.EVENT_STOP;
+ }));
+ return tag;
+ },
+
_ensureNewLine: function() {
let buffer = this._view.get_buffer();
let iter = buffer.get_end_iter();
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]