diff --git a/deluge/plugins/Scheduler/deluge/plugins/scheduler/data/scheduler.js b/deluge/plugins/Scheduler/deluge/plugins/scheduler/data/scheduler.js index 79639031a..23dc1f107 100644 --- a/deluge/plugins/Scheduler/deluge/plugins/scheduler/data/scheduler.js +++ b/deluge/plugins/Scheduler/deluge/plugins/scheduler/data/scheduler.js @@ -1,106 +1,624 @@ -ScheduleSelectPanel = Ext.extend(Ext.form.FieldSet, { - constructor: function(config) { - config = Ext.apply({ - title: _('Schedule'), - autoHeight: true - }, config); - ScheduleSelectPanel.superclass.constructor.call(this, config); - }, - - onRender: function(ct, position) { - ScheduleSelectPanel.superclass.onRender.call(this, ct, position); - - var dom = this.body.dom; - var table = createEl(dom, 'table'); - - function createEl(parent, type) { - var el = document.createElement(type); - parent.appendChild(el); - return el; - } - - Ext.each(['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], function(day) { - var row = createEl(table, 'tr'); - var label = createEl(row, 'th'); - label.setAttribute('style', 'font-weight: bold; padding-right: 5px;'); - label.innerHTML = day; - for (var hour = 0; hour < 24; hour++) { - var cell = createEl(row, 'td'); - cell.setAttribute('style', 'border: 1px solid Green; width: 16px; height: 20px; background: LightGreen;'); - } - }); - } +/* +Script: scheduler.js + The client-side javascript code for the Scheduler plugin. + +Copyright: + (C) samuel337 2011 + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, write to: + The Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor + Boston, MA 02110-1301, USA. + + In addition, as a special exception, the copyright holders give + permission to link the code of portions of this program with the OpenSSL + library. + You must obey the GNU General Public License in all respects for all of + the code used other than OpenSSL. If you modify file(s) with this + exception, you may extend this exception to your version of the file(s), + but you are not obligated to do so. If you do not wish to do so, delete + this exception statement from your version. If you delete this exception + statement from all source files in the program, then also delete it here. +*/ + +Ext.ns('Deluge.ux'); + +Deluge.ux.ScheduleSelector = Ext.extend(Ext.form.FieldSet, { + + title: _('Schedule'), + autoHeight: true, + style: 'margin-bottom: 0px; padding-bottom: 0px;', + border: false, + + states: [ + { + name: 'Normal', + backgroundColor: 'LightGreen', + borderColor: 'DarkGreen', + value: 0 + }, + { + name: 'Throttled', + backgroundColor: 'Yellow', + borderColor: 'Gold', + value: 1 + }, + { + name: 'Paused', + backgroundColor: 'OrangeRed', + borderColor: 'FireBrick', + value: 2 + } + ], + daysOfWeek: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], + + initComponent: function() { + Deluge.ux.ScheduleSelector.superclass.initComponent.call(this); + + // ExtJS' radiogroup implementation is very broken for styling. + /*this.stateBrush = this.add({ + xtype: 'radiogroup', + fieldLabel: _('State Brush'), + name: 'current_state_brush', + submitValue: false, + items: [ + { boxLabel: 'Normal', name: 'current_state_brush', inputValue: 0 }, + { boxLabel: 'Throttled', name: 'current_state_brush', inputValue: 1, checked: true }, + { boxLabel: 'Paused', name: 'current_state_brush', inputValue: 2 }, + ] + });*/ + }, + + onRender: function(ct, position) { + Deluge.ux.ScheduleSelector.superclass.onRender.call(this, ct, position); + + var dom = this.body.dom; + + function createEl(parent, type) { + var el = document.createElement(type); + parent.appendChild(el); + return el; + } + + // create state brushes + // tack a random number to the end to avoid clashes + this.stateBrushName = 'schedule-state-brush-' + Math.round(Math.random() * 10000); + + var el1 = createEl(dom, 'div'); + + var el2 = createEl(el1, 'div'); + this.stateBrush = el2; + el2.id = this.stateBrushName; + + // for webkit + var floatAttr = 'float'; + if (el2.style.float == undefined) { + // for firefox + if (el2.style.cssFloat != undefined) floatAttr = 'cssFloat'; + // for IE + if (el2.style.styleFloat != undefined) floatAttr = 'styleFloat'; + } + el2.style[floatAttr] = 'right'; + + for (var i=0; i < this.states.length; i++) { + var el3 = createEl(el2, 'input'); + el3.type = 'radio'; + el3.value = this.states[i].value; + el3.name = this.stateBrushName; + el3.id = this.stateBrushName + '-' + this.states[i].name; + + // isn't the first one + if (i > 0) el3.style.marginLeft = '7px'; + + // assume the first is the default state, so make the 2nd one the default brush + if (i == 1) el3.checked = true; + + var el4 = createEl(el2, 'label'); + el4.appendChild(document.createTextNode(this.states[i].name)); + el4.htmlFor = el3.id; + el4.style.backgroundColor = this.states[i].backgroundColor; + el4.style.borderBottom = '2px solid ' + this.states[i].borderColor; + el4.style.padding = '2px 3px'; + el4.style.marginLeft = '3px'; + } + + el1.appendChild(document.createTextNode('Select a state brush:')); + + el1.style.marginBottom = '10px'; + + // keep the radio buttons separate from the time bars + createEl(dom, 'div').style.clear = 'both'; + + var table = createEl(dom, 'table'); + table.cellSpacing = 0; + + // cache access to cells for easier access later + this.scheduleCells = { }; + + Ext.each(this.daysOfWeek, function(day) { + var cells = [ ]; + var row = createEl(table, 'tr'); + var label = createEl(row, 'th'); + label.setAttribute('style', 'font-weight: bold; padding-right: 5px;'); + label.appendChild(document.createTextNode(day)); + for (var hour = 0; hour < 24; hour++) { + var cell = createEl(row, 'td'); + + // assume the first state is the default state + cell.currentValue = cell.oldValue = this.states[0].value; + cell.day = day; + cell.hour = hour; + + cell.width = '16px'; + cell.height = '20px'; + + cell.style.border = '1px solid #999999'; + // don't repeat borders in between cells + if (hour != 23) // not the last cell + cell.style.borderRight = 'none'; + + this.updateCell(cell); + + cells.push(cell); + + cell = Ext.get(cell); + cell.on('click', this.onCellClick, this); + cell.on('mouseover', this.onCellMouseOver, this); + cell.on('mouseout', this.onCellMouseOut, this); + cell.on('mousedown', this.onCellMouseDown, this); + cell.on('mouseup', this.onCellMouseUp, this); + } + + // insert gap row to provide visual separation + row = createEl(table, 'tr'); + // blank cell to create gap + createEl(row, 'td').height = '3px'; + + this.scheduleCells[day] = cells; + }, this); + }, + + updateCell: function(cell) { + // sanity check + if (cell.currentValue == undefined) return; + + for (var i in this.states) { + var curState = this.states[i]; + if (curState.value == cell.currentValue) { + cell.style.background = curState.backgroundColor; + break; + } + } + }, + + getCurrentBrushValue: function() { + var v = null; + var brushes = Ext.get(this.body.dom).findParent('form').elements[this.stateBrushName]; + Ext.each(brushes, function(b) { + if (b.checked) + v = b.value; + }); + + return v; + }, + + onCellClick: function(event, cell) { + cell.oldValue = cell.currentValue; + + this.dragAnchor = null; + }, + + onCellMouseDown: function(event, cell) { + this.dragAnchor = cell; + }, + + onCellMouseUp: function(event, cell) { + // if we're dragging... + if (this.dragAnchor) { + // set all those between here and the anchor to the new values + if (cell.hour > this.dragAnchor.hour) + this.confirmCells(cell.day, this.dragAnchor.hour, cell.hour); + else if (cell.hour < this.dragAnchor.hour) + this.confirmCells(cell.day, cell.hour, this.dragAnchor.hour); + else + this.confirmCells(cell.day, cell.hour, cell.hour); + + this.hideCellLeftTooltip(); + this.hideCellRightTooltip(); + this.dragAnchor = null; + } + }, + + onCellMouseOver: function(event, cell) { + // LEFT TOOL TIP + // if it isn't showing and we're dragging, show it. + // otherwise if dragging, leave it alone unless we're dragging to the left. + // if we're not dragging, show it. + var leftTooltipCell = null; + if (!this.dragAnchor) + leftTooltipCell = cell; + else if ((this.dragAnchor && this.isCellLeftTooltipHidden()) || + (this.dragAnchor && this.dragAnchor.hour > cell.hour)) + leftTooltipCell = this.dragAnchor; + + if (leftTooltipCell) { + var hour = leftTooltipCell.hour; + var pm = false; + + // convert to 12-hour time + if (hour >= 12) { + pm = true; + if (hour > 12) hour -= 12; + } + // change 0 hour to 12am + else if (hour == 0) { + hour = 12; + } + this.showCellLeftTooltip(hour + ' ' + (pm ? 'pm' : 'am'), leftTooltipCell); + } + + // RIGHT TOOL TIP + var rightTooltipCell = null; + if (this.dragAnchor) { + if (this.dragAnchor.hour == cell.hour) + this.hideCellRightTooltip(); + else if (this.dragAnchor.hour > cell.hour && this.isCellRightTooltipHidden()) + rightTooltipCell = this.dragAnchor; + else // cell.hour > this.dragAnchor.hour + rightTooltipCell = cell; + } + + if (rightTooltipCell) { + var hour = rightTooltipCell.hour; + var pm = false; + + // convert to 12-hour time + if (hour >= 12) { + pm = true; + if (hour > 12) hour -= 12; + } + // change 0 hour to 12am + else if (hour == 0) { + hour = 12; + } + this.showCellRightTooltip(hour + ' ' + (pm ? 'pm' : 'am'), rightTooltipCell); + } + + // preview colour change and + // revert state for all those on the outer side of the drag if dragging + if (this.dragAnchor) { + if (cell.day != this.dragAnchor.day) { + // dragged into another day. Abort! Abort! + Ext.each(this.daysOfWeek, function(day) { + this.revertCells(day, 0, 23); + }, this); + this.dragAnchor = null; + this.hideCellLeftTooltip(); + this.hideCellRightTooltip(); + } + else if (cell.hour > this.dragAnchor.hour) { + // dragging right + this.revertCells(cell.day, cell.hour+1, 23); + this.previewCells(cell.day, this.dragAnchor.hour, cell.hour); + } + else if (cell.hour < this.dragAnchor.hour) { + // dragging left + this.revertCells(cell.day, 0, cell.hour-1); + this.previewCells(cell.day, cell.hour, this.dragAnchor.hour); + } + else { + // back to anchor cell + // don't know if it is from right or left, so revert all except this + this.revertCells(cell.day, cell.hour+1, 23); + this.revertCells(cell.day, 0, cell.hour-1); + } + } + else { + // not dragging, just preview this cell + this.previewCells(cell.day, cell.hour, cell.hour); + } + }, + + onCellMouseOut: function(event, cell) { + if (!this.dragAnchor) + this.hideCellLeftTooltip(); + + // revert state. If new state has been set, old and new will be equal. + // if dragging, this will be handled by the next mouse over + if (this.dragAnchor == null && cell.oldValue != cell.currentValue) { + this.revertCells(cell.day, cell.hour, cell.hour); + } + }, + + previewCells: function(day, fromHour, toHour) { + var cells = this.scheduleCells[day]; + var curBrushValue = this.getCurrentBrushValue(); + + if (toHour > cells.length) toHour = cells.length; + + for (var i=fromHour; i <= toHour; i++) { + if (cells[i].currentValue != curBrushValue) { + cells[i].oldValue = cells[i].currentValue; + cells[i].currentValue = curBrushValue; + this.updateCell(cells[i]); + } + } + }, + + revertCells: function(day, fromHour, toHour) { + var cells = this.scheduleCells[day]; + + if (toHour > cells.length) toHour = cells.length; + + for (var i=fromHour; i <= toHour; i++) { + cells[i].currentValue = cells[i].oldValue; + this.updateCell(cells[i]); + } + }, + + confirmCells: function(day, fromHour, toHour) { + var cells = this.scheduleCells[day]; + + if (toHour > cells.length) toHour = cells.length; + + for (var i=fromHour; i <= toHour; i++) { + if (cells[i].currentValue != cells[i].oldValue) { + cells[i].oldValue = cells[i].currentValue; + } + } + }, + + showCellLeftTooltip: function(text, cell) { + var tooltip = this.cellLeftTooltip; + + if (!tooltip) { + // no cached left tooltip exists, create one + tooltip = document.createElement('div'); + this.cellLeftTooltip = tooltip; + this.body.dom.appendChild(tooltip); + tooltip.style.position = 'absolute'; + tooltip.style.backgroundColor = '#F2F2F2'; + tooltip.style.border = '1px solid #333333'; + tooltip.style.padding = '1px 3px'; + tooltip.style.opacity = 0.8; + } + + // remove all existing children + while (tooltip.childNodes.length > 0) { + tooltip.removeChild(tooltip.firstChild); + } + // add the requested text + tooltip.appendChild(document.createTextNode(text)); + + // place the tooltip + Ext.get(tooltip).alignTo(cell, 'br-tr'); + + // make it visible + tooltip.style.visibility = 'visible'; + }, + + hideCellLeftTooltip: function() { + if (this.cellLeftTooltip) { + this.cellLeftTooltip.style.visibility = 'hidden'; + } + }, + + isCellLeftTooltipHidden: function() { + if (this.cellLeftTooltip) + return this.cellLeftTooltip.style.visibility == 'hidden'; + else + return true; + }, + + showCellRightTooltip: function(text, cell) { + var tooltip = this.cellRightTooltip; + + if (!tooltip) { + // no cached left tooltip exists, create one + tooltip = document.createElement('div'); + this.cellRightTooltip = tooltip; + this.body.dom.appendChild(tooltip); + tooltip.style.position = 'absolute'; + tooltip.style.backgroundColor = '#F2F2F2'; + tooltip.style.border = '1px solid #333333'; + tooltip.style.padding = '1px 3px'; + tooltip.style.opacity = 0.8; + } + + // remove all existing children + while (tooltip.childNodes.length > 0) { + tooltip.removeChild(tooltip.firstChild); + } + // add the requested text + tooltip.appendChild(document.createTextNode(text)); + + // place the tooltip + Ext.get(tooltip).alignTo(cell, 'bl-tl'); + + // make it visible + tooltip.style.visibility = 'visible'; + }, + + hideCellRightTooltip: function() { + if (this.cellRightTooltip) { + this.cellRightTooltip.style.visibility = 'hidden'; + } + }, + + isCellRightTooltipHidden: function() { + if (this.cellRightTooltip) + return this.cellRightTooltip.style.visibility == 'hidden'; + else + return true; + }, + + getConfig: function() { + var config = [ ]; + + for (var i=0; i < 24; i++) { + var hourConfig = [ 0, 0, 0, 0, 0, 0, 0 ]; + + for (var j=0; j < this.daysOfWeek.length; j++) { + hourConfig[j] = parseInt(this.scheduleCells[this.daysOfWeek[j]][i].currentValue); + } + + config.push(hourConfig); + } + + return config; + }, + + setConfig: function(config) { + for (var i=0; i < 24; i++) { + var hourConfig = config[i]; + + for (var j=0; j < this.daysOfWeek.length; j++) { + var cell = this.scheduleCells[this.daysOfWeek[j]][i]; + cell.currentValue = cell.oldValue = hourConfig[j]; + this.updateCell(cell); + } + } + } }); -SchedulerPreferences = Ext.extend(Ext.Panel, { - constructor: function(config) { - config = Ext.apply({ - border: false, - title: _('Scheduler') - }, config); - SchedulerPreferences.superclass.constructor.call(this, config); - }, - - initComponent: function() { - SchedulerPreferences.superclass.initComponent.call(this); +Ext.ns('Deluge.ux.preferences'); - this.form = this.add({ - xtype: 'form', - layout: 'form', - border: false, - autoHeight: true - }); - - this.schedule = this.form.add(new ScheduleSelectPanel()); - - this.slowSettings = this.form.add({ - xtype: 'fieldset', - title: _('Slow Settings'), - autoHeight: true, - defaultType: 'uxspinner' - }); - - this.downloadLimit = this.slowSettings.add({ - fieldLabel: _('Download Limit'), - name: 'download_limit' - }); - this.uploadLimit = this.slowSettings.add({ - fieldLabel: _('Upload Limit'), - name: 'upload_limit' - }); - this.activeTorrents = this.slowSettings.add({ - fieldLabel: _('Active Torrents'), - name: 'active_torrents' - }); - }, - - onRender: function(ct, position) { - SchedulerPreferences.superclass.onRender.call(this, ct, position); - this.form.layout = new Ext.layout.FormLayout(); - this.form.layout.setContainer(this); - this.form.doLayout(); - }, - - onShow: function() { - SchedulerPreferences.superclass.onShow.call(this); - } +Deluge.ux.preferences.SchedulerPage = Ext.extend(Ext.Panel, { + + border: false, + title: _('Scheduler'), + layout: 'fit', + + initComponent: function() { + Deluge.ux.preferences.SchedulerPage.superclass.initComponent.call(this); + + this.form = this.add({ + xtype: 'form', + layout: 'form', + border: false, + autoHeight: true + }); + + this.schedule = this.form.add(new Deluge.ux.ScheduleSelector()); + + this.slowSettings = this.form.add({ + xtype: 'fieldset', + border: false, + title: _('Throttled Settings'), + autoHeight: true, + defaultType: 'spinnerfield', + defaults: { + minValue: -1, + maxValue: 99999 + }, + style: 'margin-top: 5px; margin-bottom: 0px; padding-bottom: 0px;', + labelWidth: 200 + }); + + this.downloadLimit = this.slowSettings.add({ + fieldLabel: _('Maximum Download Speed (KiB/s)'), + name: 'download_limit', + width: 80, + value: -1, + decimalPrecision: 0 + }); + this.uploadLimit = this.slowSettings.add({ + fieldLabel: _('Maximum Upload Speed (KiB/s)'), + name: 'upload_limit', + width: 80, + value: -1, + decimalPrecision: 0 + }); + this.activeTorrents = this.slowSettings.add({ + fieldLabel: _('Active Torrents'), + name: 'active_torrents', + width: 80, + value: -1, + decimalPrecision: 0 + }); + this.activeDownloading = this.slowSettings.add({ + fieldLabel: _('Active Downloading'), + name: 'active_downloading', + width: 80, + value: -1, + decimalPrecision: 0 + }); + this.activeSeeding = this.slowSettings.add({ + fieldLabel: _('Active Seeding'), + name: 'active_seeding', + width: 80, + value: -1, + decimalPrecision: 0 + }); + + deluge.preferences.on('show', this.updateConfig, this); + }, + + onRender: function(ct, position) { + Deluge.ux.preferences.SchedulerPage.superclass.onRender.call(this, ct, position); + this.form.layout = new Ext.layout.FormLayout(); + this.form.layout.setContainer(this); + this.form.doLayout(); + }, + + onApply: function() { + // build settings object + var config = { } + + config['button_state'] = this.schedule.getConfig(); + config['low_down'] = this.downloadLimit.getValue(); + config['low_up'] = this.uploadLimit.getValue(); + config['low_active'] = this.activeTorrents.getValue(); + config['low_active_down'] = this.activeDownloading.getValue(); + config['low_active_up'] = this.activeSeeding.getValue(); + + deluge.client.scheduler.set_config(config); + }, + + onOk: function() { + this.onApply(); + }, + + afterRender: function() { + Deluge.ux.preferences.SchedulerPage.superclass.afterRender.call(this); + this.updateConfig(); + }, + + updateConfig: function() { + deluge.client.scheduler.get_config({ + success: function(config) { + this.schedule.setConfig(config['button_state']); + this.downloadLimit.setValue(config['low_down']); + this.uploadLimit.setValue(config['low_up']); + this.activeTorrents.setValue(config['low_active']); + this.activeDownloading.setValue(config['low_active_down']); + this.activeSeeding.setValue(config['low_active_up']); + }, + scope: this + }); + } }); -SchedulerPlugin = Ext.extend(Deluge.Plugin, { - constructor: function(config) { - config = Ext.apply({ - name: "Scheduler" - }, config); - SchedulerPlugin.superclass.constructor.call(this, config); - }, - - onDisable: function() { - Deluge.Preferences.removePage(this.prefsPage); - }, - - onEnable: function() { - this.prefsPage = new SchedulerPreferences(); - this.prefsPage = Deluge.Preferences.addPage(this.prefsPage); - } +Deluge.plugins.SchedulerPlugin = Ext.extend(Deluge.Plugin, { + + name: 'Scheduler', + + onDisable: function() { + deluge.preferences.removePage(this.prefsPage); + }, + + onEnable: function() { + this.prefsPage = deluge.preferences.addPage(new Deluge.ux.preferences.SchedulerPage()); + } }); -new SchedulerPlugin(); \ No newline at end of file +Deluge.registerPlugin('Scheduler', Deluge.plugins.SchedulerPlugin); diff --git a/deluge/plugins/Scheduler/deluge/plugins/scheduler/webui.py b/deluge/plugins/Scheduler/deluge/plugins/scheduler/webui.py index 85aea5537..eaa22f0f0 100644 --- a/deluge/plugins/Scheduler/deluge/plugins/scheduler/webui.py +++ b/deluge/plugins/Scheduler/deluge/plugins/scheduler/webui.py @@ -48,3 +48,4 @@ log = logging.getLogger(__name__) class WebUI(WebPluginBase): scripts = [get_resource("scheduler.js")] + debug_scripts = scripts