messageTray: make links in message banners clickable

https://bugzilla.gnome.org/show_bug.cgi?id=610219
This commit is contained in:
Maxim Ermilov 2010-11-24 02:27:47 +03:00
parent 6a52deec7d
commit 65f0b483f8
5 changed files with 138 additions and 18 deletions

View File

@ -847,6 +847,10 @@ StTooltip StLabel {
color: #cccccc; color: #cccccc;
} }
.url-highlighter {
link-color: #ccccff;
}
/* Message Tray */ /* Message Tray */
#message-tray { #message-tray {
background-gradient-direction: vertical; background-gradient-direction: vertical;

View File

@ -26,9 +26,9 @@ const DragMotionResult = {
}; };
const DRAG_CURSOR_MAP = { const DRAG_CURSOR_MAP = {
0: Shell.Cursor.UNSUPPORTED_TARGET, 0: Shell.Cursor.DND_UNSUPPORTED_TARGET,
1: Shell.Cursor.COPY, 1: Shell.Cursor.DND_COPY,
2: Shell.Cursor.MOVE 2: Shell.Cursor.DND_MOVE
}; };
const DragDropResult = { const DragDropResult = {
@ -221,7 +221,7 @@ _Draggable.prototype = {
if (this._onEventId) if (this._onEventId)
this._ungrabActor(); this._ungrabActor();
this._grabEvents(); this._grabEvents();
global.set_cursor(Shell.Cursor.IN_DRAG); global.set_cursor(Shell.Cursor.DND_IN_DRAG);
this._dragX = this._dragStartX = stageX; this._dragX = this._dragStartX = stageX;
this._dragY = this._dragStartY = stageY; this._dragY = this._dragStartY = stageY;
@ -382,7 +382,7 @@ _Draggable.prototype = {
} }
target = target.get_parent(); target = target.get_parent();
} }
global.set_cursor(Shell.Cursor.IN_DRAG); global.set_cursor(Shell.Cursor.DND_IN_DRAG);
} }
return true; return true;

View File

@ -1,6 +1,8 @@
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ /* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
const Clutter = imports.gi.Clutter; const Clutter = imports.gi.Clutter;
const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
const Gtk = imports.gi.Gtk; const Gtk = imports.gi.Gtk;
const Lang = imports.lang; const Lang = imports.lang;
const Mainloop = imports.mainloop; const Mainloop = imports.mainloop;
@ -14,6 +16,7 @@ const Tweener = imports.ui.tweener;
const Main = imports.ui.main; const Main = imports.ui.main;
const BoxPointer = imports.ui.boxpointer; const BoxPointer = imports.ui.boxpointer;
const Params = imports.misc.params; const Params = imports.misc.params;
const Utils = imports.misc.utils;
const ANIMATION_TIME = 0.2; const ANIMATION_TIME = 0.2;
const NOTIFICATION_TIMEOUT = 4; const NOTIFICATION_TIMEOUT = 4;
@ -44,6 +47,117 @@ function _cleanMarkup(text) {
return _text.replace(/<(\/?[^biu]>|[^>\/][^>])/g, '&lt;$1'); return _text.replace(/<(\/?[^biu]>|[^>\/][^>])/g, '&lt;$1');
} }
function URLHighlighter(text, lineWrap) {
this._init(text, lineWrap);
}
URLHighlighter.prototype = {
_init: function(text, lineWrap) {
if (!text)
text = '';
this.actor = new St.Label({ reactive: true, style_class: 'url-highlighter' });
this._linkColor = '#ccccff';
this.actor.connect('style-changed', Lang.bind(this, function() {
let color = new Clutter.Color();
let hasColor = this.actor.get_theme_node().get_color('link-color', color);
if (hasColor) {
let linkColor = color.to_string().substr(0, 7);
if (linkColor != this._linkColor) {
this._linkColor = linkColor;
this._highlightUrls();
}
}
}));
if (lineWrap) {
this.actor.clutter_text.line_wrap = true;
this.actor.clutter_text.line_wrap_mode = Pango.WrapMode.WORD_CHAR;
this.actor.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
}
this.setMarkup(text);
this.actor.connect('button-release-event', Lang.bind(this, function (actor, event) {
let urlId = this._findUrlAtPos(event);
if (urlId != -1) {
let url = this._urls[urlId].url;
if (url.indexOf(':') == -1)
url = 'http://' + url;
try {
Gio.app_info_launch_default_for_uri(url, global.create_app_launch_context());
return true;
} catch (e) {
// TODO: remove this after gnome 3 release
let p = new Shell.Process({ 'args' : ['gvfs-open', url] });
p.run();
return true;
}
}
return false;
}));
this.actor.connect('motion-event', Lang.bind(this, function(actor, event) {
let urlId = this._findUrlAtPos(event);
if (urlId != -1 && !this._cursorChanged) {
global.set_cursor(Shell.Cursor.POINTING_HAND);
this._cursorChanged = true;
} else if (urlId == -1) {
global.unset_cursor();
this._cursorChanged = false;
}
return false;
}));
this.actor.connect('leave-event', Lang.bind(this, function() {
if (this._cursorChanged) {
this._cursorChanged = false;
global.unset_cursor();
}
}));
},
setMarkup: function(text) {
text = text ? _cleanMarkup(text) : '';
this._text = text;
this.actor.clutter_text.set_markup(text);
/* clutter_text.text contain text without markup */
this._urls = Utils.findUrls(this.actor.clutter_text.text);
this._highlightUrls();
},
_highlightUrls: function() {
// text here contain markup
let urls = Utils.findUrls(this._text);
let markup = '';
let pos = 0;
for (let i = 0; i < urls.length; i++) {
let url = urls[i];
let str = this._text.substr(pos, url.pos - pos);
markup += str + '<span foreground="' + this._linkColor + '"><u>' + url.url + '</u></span>';
pos = url.pos + url.url.length;
}
markup += this._text.substr(pos);
this.actor.clutter_text.set_markup(markup);
},
_findUrlAtPos: function(event) {
let success;
let [x, y] = event.get_coords();
[success, x, y] = this.actor.transform_stage_point(x, y);
let find_pos = -1;
for (let i = 0; i < this.actor.clutter_text.text.length; i++) {
let [success, px, py, line_height] = this.actor.clutter_text.position_to_coords(i);
if (py > y || py + line_height < y || x < px)
continue;
find_pos = i;
}
if (find_pos != -1) {
for (let i = 0; i < this._urls.length; i++)
if (find_pos >= this._urls[i].pos &&
this._urls[i].pos + this._urls[i].url.length > find_pos)
return i;
}
return -1;
}
};
// Notification: // Notification:
// @source: the notification's Source // @source: the notification's Source
// @title: the title // @title: the title
@ -148,7 +262,8 @@ Notification.prototype = {
this._titleLabel = new St.Label(); this._titleLabel = new St.Label();
this._bannerBox.add_actor(this._titleLabel); this._bannerBox.add_actor(this._titleLabel);
this._bannerLabel = new St.Label(); this._bannerUrlHighlighter = new URLHighlighter();
this._bannerLabel = this._bannerUrlHighlighter.actor;
this._bannerBox.add_actor(this._bannerLabel); this._bannerBox.add_actor(this._bannerLabel);
this.update(title, banner, params); this.update(title, banner, params);
@ -214,8 +329,9 @@ Notification.prototype = {
// not fitting fully in the single-line mode. // not fitting fully in the single-line mode.
this._bannerBodyText = this._customContent ? null : banner; this._bannerBodyText = this._customContent ? null : banner;
banner = banner ? _cleanMarkup(banner.replace(/\n/g, ' ')) : ''; banner = banner ? banner.replace(/\n/g, ' ') : '';
this._bannerLabel.clutter_text.set_markup(banner);
this._bannerUrlHighlighter.setMarkup(banner);
this._bannerLabel.queue_relayout(); this._bannerLabel.queue_relayout();
// Add the bannerBody now if we know for sure we'll need it // Add the bannerBody now if we know for sure we'll need it
@ -259,16 +375,10 @@ Notification.prototype = {
// //
// Return value: the newly-added label // Return value: the newly-added label
addBody: function(text) { addBody: function(text) {
let body = new St.Label(); let label = new URLHighlighter(text, true);
body.clutter_text.line_wrap = true;
body.clutter_text.line_wrap_mode = Pango.WrapMode.WORD_CHAR;
body.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
text = text ? _cleanMarkup(text) : ''; this.addActor(label.actor);
body.clutter_text.set_markup(text); return label.actor;
this.addActor(body);
return body;
}, },
_addBannerBody: function() { _addBannerBody: function() {

View File

@ -497,6 +497,9 @@ shell_global_set_cursor (ShellGlobal *global,
case SHELL_CURSOR_DND_UNSUPPORTED_TARGET: case SHELL_CURSOR_DND_UNSUPPORTED_TARGET:
name = "dnd-none"; name = "dnd-none";
break; break;
case SHELL_CURSOR_POINTING_HAND:
name = "hand";
break;
default: default:
g_return_if_reached (); g_return_if_reached ();
} }
@ -516,6 +519,8 @@ shell_global_set_cursor (ShellGlobal *global,
case SHELL_CURSOR_DND_COPY: case SHELL_CURSOR_DND_COPY:
cursor_type = GDK_PLUS; cursor_type = GDK_PLUS;
break; break;
case SHELL_CURSOR_POINTING_HAND:
cursor_type = GDK_HAND2;
case SHELL_CURSOR_DND_UNSUPPORTED_TARGET: case SHELL_CURSOR_DND_UNSUPPORTED_TARGET:
cursor_type = GDK_X_CURSOR; cursor_type = GDK_X_CURSOR;
break; break;

View File

@ -38,7 +38,8 @@ typedef enum {
SHELL_CURSOR_DND_IN_DRAG, SHELL_CURSOR_DND_IN_DRAG,
SHELL_CURSOR_DND_UNSUPPORTED_TARGET, SHELL_CURSOR_DND_UNSUPPORTED_TARGET,
SHELL_CURSOR_DND_MOVE, SHELL_CURSOR_DND_MOVE,
SHELL_CURSOR_DND_COPY SHELL_CURSOR_DND_COPY,
SHELL_CURSOR_POINTING_HAND
} ShellCursor; } ShellCursor;
void shell_global_set_cursor (ShellGlobal *global, void shell_global_set_cursor (ShellGlobal *global,