diff --git a/deluge/ui/web/css/Roar.css b/deluge/ui/web/css/Roar.css
new file mode 100644
index 000000000..efa6f539d
--- /dev/null
+++ b/deluge/ui/web/css/Roar.css
@@ -0,0 +1,55 @@
+.roar-body
+{
+ position: absolute;
+ font: 12px/14px "Lucida Grande", Arial, Helvetica, Verdana, sans-serif;
+ color: #fff;
+ text-align: left;
+ z-index: 999;
+}
+
+.roar
+{
+ position: absolute;
+ width: 300px;
+ cursor: pointer;
+}
+.roar-bg
+{
+ position: absolute;
+ z-index: 1000;
+ width: 100%;
+ height: 100%;
+ left: 0;
+ top: 0;
+ background-color: #000;
+ -moz-border-radius: 10px;
+ -webkit-border-radius: 5px;
+ -webkit-box-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
+}
+.roar-body-ugly .roar
+{
+ background-color: #333;
+}
+.roar-body-ugly .roar-bg
+{
+ display: none;
+}
+.roar h3
+{
+ position: relative;
+ padding: 15px 10px 0;
+ margin: 0;
+ border: 0;
+ font-size: 13px;
+ color: #fff;
+ z-index: 1002;
+}
+.roar p
+{
+ position: relative;
+ padding: 10px 10px 15px;
+ margin: 0;
+ font-size: 12px;
+ color: #fff;
+ z-index: 1002;
+}
\ No newline at end of file
diff --git a/deluge/ui/web/index.html b/deluge/ui/web/index.html
index 41be50341..45228c3ab 100644
--- a/deluge/ui/web/index.html
+++ b/deluge/ui/web/index.html
@@ -10,6 +10,7 @@
+
@@ -19,6 +20,7 @@
+
diff --git a/deluge/ui/web/js/Roar.js b/deluge/ui/web/js/Roar.js
new file mode 100644
index 000000000..78580f8c0
--- /dev/null
+++ b/deluge/ui/web/js/Roar.js
@@ -0,0 +1,166 @@
+/**
+ * Roar - Notifications
+ *
+ * Inspired by Growl
+ *
+ * @version 1.0.1
+ *
+ * @license MIT-style license
+ * @author Harald Kirschner
+ * @copyright Author
+ */
+
+var Roar = new Class({
+
+ Implements: [Options, Events, Chain],
+
+ options: {
+ duration: 3000,
+ position: 'upperLeft',
+ container: null,
+ bodyFx: null,
+ itemFx: null,
+ margin: {x: 10, y: 10},
+ offset: 10,
+ className: 'roar',
+ onShow: $empty,
+ onHide: $empty,
+ onRender: $empty
+ },
+
+ initialize: function(options) {
+ this.setOptions(options);
+ this.items = [];
+ this.container = $(this.options.container) || document;
+ },
+
+ alert: function(title, message, options) {
+ var params = Array.link(arguments, {title: String.type, message: String.type, options: Object.type});
+ var items = [new Element('h3', {'html': $pick(params.title, '')})];
+ if (params.message) items.push(new Element('p', {'html': params.message}));
+ return this.inject(items, params.options);
+ },
+
+ inject: function(elements, options) {
+ if (!this.body) this.render();
+ options = options || {};
+
+ var offset = [-this.options.offset, 0];
+ var last = this.items.getLast();
+ if (last) {
+ offset[0] = last.retrieve('roar:offset');
+ offset[1] = offset[0] + last.offsetHeight + this.options.offset;
+ }
+ var to = {'opacity': 1};
+ to[this.align.y] = offset;
+
+ var item = new Element('div', {
+ 'class': this.options.className,
+ 'opacity': 0
+ }).adopt(
+ new Element('div', {
+ 'class': 'roar-bg',
+ 'opacity': 0.7
+ }),
+ elements
+ );
+
+ item.setStyle(this.align.x, 0).store('roar:offset', offset[1]).set('morph', $merge({
+ unit: 'px',
+ link: 'cancel',
+ onStart: Chain.prototype.clearChain,
+ transition: Fx.Transitions.Back.easeOut
+ }, this.options.itemFx));
+
+ var remove = this.remove.create({
+ bind: this,
+ arguments: [item],
+ delay: 10
+ });
+ this.items.push(item.addEvent('click', remove));
+
+ if (this.options.duration) {
+ var over = false;
+ var trigger = (function() {
+ trigger = null;
+ if (!over) remove();
+ }).delay(this.options.duration);
+ item.addEvents({
+ mouseover: function() {
+ over = true;
+ },
+ mouseout: function() {
+ over = false;
+ if (!trigger) remove();
+ }
+ });
+ }
+ item.inject(this.body).morph(to);
+ return this.fireEvent('onShow', [item, this.items.length]);
+ },
+
+ remove: function(item) {
+ var index = this.items.indexOf(item);
+ if (index == -1) return this;
+ this.items.splice(index, 1);
+ item.removeEvents();
+ var to = {opacity: 0};
+ to[this.align.y] = item.getStyle(this.align.y).toInt() - item.offsetHeight - this.options.offset;
+ item.morph(to).get('morph').chain(item.destroy.bind(item));
+ return this.fireEvent('onHide', [item, this.items.length]).callChain(item);
+ },
+
+ empty: function() {
+ while (this.items.length) this.remove(this.items[0]);
+ return this;
+ },
+
+ render: function() {
+ this.position = this.options.position;
+ if ($type(this.position) == 'string') {
+ var position = {x: 'center', y: 'center'};
+ this.align = {x: 'left', y: 'top'};
+ if ((/left|west/i).test(this.position)) position.x = 'left';
+ else if ((/right|east/i).test(this.position)) this.align.x = position.x = 'right';
+ if ((/upper|top|north/i).test(this.position)) position.y = 'top';
+ else if ((/bottom|lower|south/i).test(this.position)) this.align.y = position.y = 'bottom';
+ this.position = position;
+ }
+ this.body = new Element('div', {'class': 'roar-body'}).inject(document.body);
+ if (Browser.Engine.trident4) this.body.addClass('roar-body-ugly');
+ this.moveTo = this.body.setStyles.bind(this.body);
+ this.reposition();
+ if (this.options.bodyFx) {
+ var morph = new Fx.Morph(this.body, $merge({
+ unit: 'px',
+ chain: 'cancel',
+ transition: Fx.Transitions.Circ.easeOut
+ }, this.options.bodyFx));
+ this.moveTo = morph.start.bind(morph);
+ }
+ var repos = this.reposition.bind(this);
+ window.addEvents({
+ scroll: repos,
+ resize: repos
+ });
+ this.fireEvent('onRender', this.body);
+ },
+
+ reposition: function() {
+ var max = document.getCoordinates(), scroll = document.getScroll(), margin = this.options.margin;
+ max.left += scroll.x;
+ max.right += scroll.x;
+ max.top += scroll.y;
+ max.bottom += scroll.y;
+ var rel = ($type(this.container) == 'element') ? this.container.getCoordinates() : max;
+ this.moveTo({
+ left: (this.position.x == 'right')
+ ? (Math.min(rel.right, max.right) - margin.x)
+ : (Math.max(rel.left, max.left) + margin.x),
+ top: (this.position.y == 'bottom')
+ ? (Math.min(rel.bottom, max.bottom) - margin.y)
+ : (Math.max(rel.top, max.top) + margin.y)
+ });
+ }
+
+});
\ No newline at end of file
diff --git a/deluge/ui/web/js/deluge-ui.js b/deluge/ui/web/js/deluge-ui.js
index 0a3d17ef0..3dc763b15 100644
--- a/deluge/ui/web/js/deluge-ui.js
+++ b/deluge/ui/web/js/deluge-ui.js
@@ -25,6 +25,9 @@ Copyright:
Deluge.Ui = {
initialize: function() {
this.errorCount = 0;
+ this.roar = new Roar({
+ position: 'lowerRight'
+ });
Ext.state.Manager.setProvider(new Ext.state.CookieProvider());
this.MainPanel = new Ext.Panel({
id: 'mainPanel',
@@ -52,6 +55,10 @@ Deluge.Ui = {
Deluge.Client = new JSON.RPC('/json');
},
+ notify: function(title, message) {
+ this.roar.alert(title, message);
+ },
+
update: function() {
var filters = Deluge.SideBar.getFilters();
Deluge.Client.web.update_ui(Deluge.Keys.Grid, filters, {
@@ -128,6 +135,7 @@ Deluge.Ui = {
onDisconnect: function() {
this.stop();
+ this.notify('Disconnected', 'Deluge has disconnected from the daemon');
},
/*