messageTray: make links in message banners clickable
https://bugzilla.gnome.org/show_bug.cgi?id=610219
This commit is contained in:
parent
6a52deec7d
commit
65f0b483f8
@ -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;
|
||||||
|
10
js/ui/dnd.js
10
js/ui/dnd.js
@ -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;
|
||||||
|
@ -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, '<$1');
|
return _text.replace(/<(\/?[^biu]>|[^>\/][^>])/g, '<$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() {
|
||||||
|
@ -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;
|
||||||
|
@ -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,
|
||||||
|
Loading…
Reference in New Issue
Block a user