prefs actually work. some tweaks to inputs to better support prefs

not all prefs available yet
This commit is contained in:
Nick 2011-02-14 12:26:24 +01:00
parent ac8c928a5b
commit 77eb1a5f82
4 changed files with 402 additions and 22 deletions

View File

@ -330,10 +330,10 @@ class AllTorrents(BaseMode):
td = TorrentDetail(self,tid,self.stdscr,self.encoding)
component.get("ConsoleUI").set_mode(td)
def show_prefrences(self):
def show_preferences(self, core_config):
component.stop(["AllTorrentsStateUpdater"])
self.stdscr.clear()
prefs = Preferences(self,self.stdscr,self.encoding)
prefs = Preferences(self,core_config,self.stdscr,self.encoding)
component.get("ConsoleUI").set_mode(prefs)
def _torrent_filter(self, idx, data):
@ -376,7 +376,6 @@ class AllTorrents(BaseMode):
self.popup.add_line("Q_ueued",data=FILTER.QUEUED,foreground="yellow")
def _do_add(self, result):
result["add_paused"] = (result["add_paused"] == "Yes")
log.debug("Adding Torrent: %s (dl path: %s) (paused: %d)",result["file"],result["path"],result["add_paused"])
def suc_cb(msg):
self.report_message("Torrent Added",msg)
@ -400,7 +399,7 @@ class AllTorrents(BaseMode):
self.popup = InputPopup(self,"Add Torrent (Esc to cancel)",close_cb=self._do_add)
self.popup.add_text_input("Enter path to torrent file:","file")
self.popup.add_text_input("Enter save path:","path",dl)
self.popup.add_select_input("Add Paused:","add_paused",["Yes","No"],ap)
self.popup.add_select_input("Add Paused:","add_paused",["Yes","No"],[True,False],ap)
def report_message(self,title,message):
self.messages.append((title,message))
@ -613,7 +612,7 @@ class AllTorrents(BaseMode):
for l in HELP_LINES:
self.popup.add_line(l)
elif chr(c) == 'p':
self.show_prefrences()
client.core.get_config().addCallback(self.show_preferences)
return
self.refresh(effected_lines)

View File

@ -58,13 +58,121 @@ class InputField:
return False
def get_value(self):
return None
def set_value(self, value):
pass
class CheckedInput(InputField):
def __init__(self, parent, message, name, checked=False):
self.parent = parent
self.chkd_inact = "[X] %s"%message
self.unchkd_inact = "[ ] %s"%message
self.chkd_act = "[{!black,white,bold!}X{!white,black!}] %s"%message
self.unchkd_act = "[{!black,white,bold!} {!white,black!}] %s"%message
self.name = name
self.checked = checked
def render(self, screen, row, width, active, col=1):
if self.checked and active:
self.parent.add_string(row,self.chkd_act,screen,col,False,True)
elif self.checked:
self.parent.add_string(row,self.chkd_inact,screen,col,False,True)
elif active:
self.parent.add_string(row,self.unchkd_act,screen,col,False,True)
else:
self.parent.add_string(row,self.unchkd_inact,screen,col,False,True)
return 1
def handle_read(self, c):
if c == 32:
self.checked = not self.checked
def get_value(self):
return self.checked
def set_value(self, c):
self.checked = c
class IntSpinInput(InputField):
def __init__(self, parent, message, name, move_func, value, min_val, max_val):
self.parent = parent
self.message = message
self.name = name
self.value = int(value)
self.initvalue = self.value
self.valstr = "%d"%self.value
self.cursor = len(self.valstr)
self.cursoff = len(self.message)+4 # + 4 for the " [ " in the rendered string
self.move_func = move_func
self.min_val = min_val
self.max_val = max_val
def render(self, screen, row, width, active, col=1):
if not active and not self.valstr:
self.value = self.initvalue
self.valstr = "%d"%self.value
self.cursor = len(self.valstr)
if not self.valstr:
self.parent.add_string(row,"%s [ ]"%self.message,screen,col,False,True)
elif active:
self.parent.add_string(row,"%s [ {!black,white,bold!}%d{!white,black!} ]"%(self.message,self.value),screen,col,False,True)
else:
self.parent.add_string(row,"%s [ %d ]"%(self.message,self.value),screen,col,False,True)
if active:
self.move_func(row,self.cursor+self.cursoff)
return 1
def handle_read(self, c):
if c == curses.KEY_PPAGE:
self.value+=1
elif c == curses.KEY_NPAGE:
self.value-=1
elif c == curses.KEY_LEFT:
self.cursor = max(0,self.cursor-1)
elif c == curses.KEY_RIGHT:
self.cursor = min(len(self.valstr),self.cursor+1)
elif c == curses.KEY_HOME:
self.cursor = 0
elif c == curses.KEY_END:
self.cursor = len(self.value)
elif c == curses.KEY_BACKSPACE or c == 127:
if self.valstr and self.cursor > 0:
self.valstr = self.valstr[:self.cursor - 1] + self.valstr[self.cursor:]
self.cursor-=1
if self.valstr:
self.value = int(self.valstr)
elif c == curses.KEY_DC:
if self.valstr and self.cursor < len(self.valstr):
self.valstr = self.valstr[:self.cursor] + self.valstr[self.cursor+1:]
elif c > 47 and c < 58:
if c == 48 and self.cursor == 0: return
if self.cursor == len(self.valstr):
self.valstr += chr(c)
self.value = int(self.valstr)
else:
# Insert into string
self.valstr = self.valstr[:self.cursor] + chr(c) + self.valstr[self.cursor:]
self.value = int(self.valstr)
# Move the cursor forward
self.cursor+=1
def get_value(self):
return self.value
def set_value(self, val):
self.value = int(val)
self.valstr = "%d"%self.value
self.cursor = len(self.valstr)
class SelectInput(InputField):
def __init__(self, parent, message, name, opts, selidx):
def __init__(self, parent, message, name, opts, vals, selidx):
self.parent = parent
self.message = message
self.name = name
self.opts = opts
self.vals = vals
self.selidx = selidx
def render(self, screen, row, width, selected, col=1):
@ -92,7 +200,14 @@ class SelectInput(InputField):
self.selidx = min(len(self.opts)-1,self.selidx+1)
def get_value(self):
return self.opts[self.selidx]
return self.vals[self.selidx]
def set_value(self, nv):
for i,val in enumerate(self.vals):
if nv == val:
self.selidx = i
return
raise Exception("Invalid value for SelectInput")
class TextInput(InputField):
def __init__(self, parent, move_func, width, message, name, value, docmp):
@ -106,7 +221,7 @@ class TextInput(InputField):
self.docmp = docmp
self.tab_count = 0
self.cursor = 0
self.cursor = len(self.value)
self.opts = None
self.opt_off = 0
@ -131,6 +246,10 @@ class TextInput(InputField):
def get_value(self):
return self.value
def set_value(self,val):
self.value = val
self.cursor = len(self.value)
# most of the cursor,input stuff here taken from ui/console/screen.py
def handle_read(self,c):
if c == 9 and self.docmp:
@ -264,8 +383,8 @@ class InputPopup(Popup):
self.inputs.append(TextInput(self.parent, self.move, self.width, message,
name, value, complete))
def add_select_input(self, message, name, opts, default_index=0):
self.inputs.append(SelectInput(self.parent, message, name, opts, default_index))
def add_select_input(self, message, name, opts, vals, default_index=0):
self.inputs.append(SelectInput(self.parent, message, name, opts, vals, default_index))
def _refresh_lines(self):
self._cursor_row = -1

View File

@ -0,0 +1,212 @@
#
# preference_panes.py
#
# Copyright (C) 2011 Nick Lanham <nick@afternight.org>
#
# Deluge is free software.
#
# You may 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 of the License, or (at your option)
# any later version.
#
# deluge 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 deluge. 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.
#
#
from deluge.ui.console.modes.input_popup import TextInput,SelectInput,CheckedInput,IntSpinInput
try:
import curses
except ImportError:
pass
import logging
log = logging.getLogger(__name__)
class Header:
def __init__(self, parent, header, space_above, space_below):
self.parent = parent
self.header = "{!white,black,bold!}%s"%header
self.space_above = space_above
self.space_below = space_below
def render(self, screen, row, width, active, offset):
rows = 1
if self.space_above:
row += 1
rows += 1
self.parent.add_string(row,self.header,screen,offset-1,False,True)
if self.space_below: rows += 1
return rows
class BasePane:
def __init__(self, offset, parent, width):
self.offset = offset+1
self.parent = parent
self.width = width
self.inputs = []
self.active_input = -1
def move(self,r,c):
self._cursor_row = r
self._cursor_col = c
def add_config_values(self,conf_dict):
for ipt in self.inputs:
if not isinstance(ipt,Header):
conf_dict[ipt.name] = ipt.get_value()
def update_values(self, conf_dict):
for ipt in self.inputs:
if not isinstance(ipt,Header):
try:
ipt.set_value(conf_dict[ipt.name])
except KeyError: # just ignore if it's not in dict
pass
def render(self, mode, screen, width, active):
self._cursor_row = -1
if self.active_input < 0:
for i,ipt in enumerate(self.inputs):
if not isinstance(ipt,Header):
self.active_input = i
break
crow = 1
for i,ipt in enumerate(self.inputs):
act = active and i==self.active_input
crow += ipt.render(screen,crow,width, act, self.offset)
if active and self._cursor_row >= 0:
curses.curs_set(2)
screen.move(self._cursor_row,self._cursor_col+self.offset-1)
else:
curses.curs_set(0)
# just handles setting the active input
def handle_read(self,c):
if not self.inputs: # no inputs added yet
return
if c == curses.KEY_UP:
nc = max(0,self.active_input-1)
while isinstance(self.inputs[nc], Header):
nc-=1
if nc <= 0: break
if not isinstance(self.inputs[nc], Header):
self.active_input = nc
elif c == curses.KEY_DOWN:
ilen = len(self.inputs)
nc = min(self.active_input+1,ilen-1)
while isinstance(self.inputs[nc], Header):
nc+=1
if nc >= ilen: break
if not isinstance(self.inputs[nc], Header):
self.active_input = nc
else:
self.inputs[self.active_input].handle_read(c)
def add_header(self, header, space_above=False, space_below=False):
self.inputs.append(Header(self.parent, header, space_above, space_below))
def add_text_input(self, name, msg, dflt_val):
self.inputs.append(TextInput(self.parent,self.move,self.width,msg,name,dflt_val,False))
def add_select_input(self, name, msg, opts, vals, selidx):
self.inputs.append(SelectInput(self.parent,msg,name,opts,vals,selidx))
def add_checked_input(self, name, message, checked):
self.inputs.append(CheckedInput(self.parent,message,name,checked))
def add_int_spin_input(self, name, message, value, min_val, max_val):
self.inputs.append(IntSpinInput(self.parent,message,name,self.move,value,min_val,max_val))
class DownloadsPane(BasePane):
def __init__(self, offset, parent, width):
BasePane.__init__(self,offset,parent,width)
self.add_header("Folders")
self.add_text_input("download_location","Download To:",parent.core_config["download_location"])
self.add_header("Allocation")
if parent.core_config["compact_allocation"]:
alloc_idx = 1
else:
alloc_idx = 0
self.add_select_input("compact_allocation","Allocation:",["Use Full Allocation","Use Compact Allocation"],[False,True],alloc_idx)
self.add_header("Options",True)
self.add_checked_input("prioritize_first_last_pieces","Prioritize first and last pieces of torrent",parent.core_config["prioritize_first_last_pieces"])
self.add_checked_input("add_paused","Add torrents in paused state",parent.core_config["add_paused"])
class NetworkPane(BasePane):
def __init__(self, offset, parent, width):
BasePane.__init__(self,offset,parent,width)
class BandwidthPane(BasePane):
def __init__(self, offset, parent, width):
BasePane.__init__(self,offset,parent,width)
self.add_header("Global Bandwidth Usage")
self.add_int_spin_input("max_connections_global","Maximum Connections:",parent.core_config["max_connections_global"],0,1000)
self.add_int_spin_input("max_upload_slots_global","Maximum Upload Slots:",parent.core_config["max_upload_slots_global"],0,1000)
#self.add_int_spin_input("max_download_speed","Maximum Download Speed (KiB/s):",-1,0,1000)
class InterfacePane(BasePane):
def __init__(self, offset, parent, width):
BasePane.__init__(self,offset,parent,width)
# does classic mode make sense in console?
#self.add_header("Classic Mode")
#self.add_checked_input("classic_mode","Enable",False)
# add title bar control here
class OtherPane(BasePane):
def __init__(self, offset, parent, width):
BasePane.__init__(self,offset,parent,width)
self.add_header("GeoIP Database")
self.add_text_input("geoip_db_location","Location:",parent.core_config["geoip_db_location"])
class DaemonPane(BasePane):
def __init__(self, offset, parent, width):
BasePane.__init__(self,offset,parent,width)
self.add_header("Port")
self.add_int_spin_input("daemon_port","Daemon Port:",parent.core_config["daemon_port"],0,1000)
self.add_header("Connections",True)
self.add_checked_input("allow_remote","Allow remote connections",parent.core_config["allow_remote"])
self.add_header("Other",True)
self.add_checked_input("new_release_check","Periodically check the website for new releases",parent.core_config["new_release_check"])
class QueuePane(BasePane):
def __init__(self, offset, parent, width):
BasePane.__init__(self,offset,parent,width)
class ProxyPane(BasePane):
def __init__(self, offset, parent, width):
BasePane.__init__(self,offset,parent,width)
class CachePane(BasePane):
def __init__(self, offset, parent, width):
BasePane.__init__(self,offset,parent,width)

View File

@ -35,9 +35,12 @@
#
import deluge.component as component
from deluge.ui.client import client
from basemode import BaseMode
from input_popup import SelectInput
from preference_panes import DownloadsPane,NetworkPane,BandwidthPane,InterfacePane
from preference_panes import OtherPane,DaemonPane,QueuePane,ProxyPane,CachePane
from collections import deque
@ -56,7 +59,7 @@ class ZONE:
ACTIONS = 2
class Preferences(BaseMode):
def __init__(self, parent_mode, stdscr, encoding=None):
def __init__(self, parent_mode, core_config, stdscr, encoding=None):
self.parent_mode = parent_mode
self.categories = [_("Downloads"), _("Network"), _("Bandwidth"),
_("Interface"), _("Other"), _("Daemon"), _("Queue"), _("Proxy"),
@ -66,13 +69,30 @@ class Preferences(BaseMode):
self.messages = deque()
self.action_input = None
self.core_config = core_config
self.active_zone = ZONE.CATEGORIES
# how wide is the left 'pane' with categories
self.div_off = 15
BaseMode.__init__(self, stdscr, encoding)
self.action_input = SelectInput(self,None,None,["Cancel","Apply","OK"],0)
BaseMode.__init__(self, stdscr, encoding, False)
# create the panes
self.prefs_width = self.cols-self.div_off-1
self.panes = [
DownloadsPane(self.div_off+2, self, self.prefs_width),
NetworkPane(self.div_off+2, self, self.prefs_width),
BandwidthPane(self.div_off+2, self, self.prefs_width),
InterfacePane(self.div_off+2, self, self.prefs_width),
OtherPane(self.div_off+2, self, self.prefs_width),
DaemonPane(self.div_off+2, self, self.prefs_width),
QueuePane(self.div_off+2, self, self.prefs_width),
ProxyPane(self.div_off+2, self, self.prefs_width),
CachePane(self.div_off+2, self, self.prefs_width)
]
self.action_input = SelectInput(self,None,None,["Cancel","Apply","OK"],[0,1,2],0)
self.refresh()
def __draw_catetories(self):
@ -85,11 +105,13 @@ class Preferences(BaseMode):
self.add_string(i+1,"- %s"%category)
self.stdscr.vline(1,self.div_off,'|',self.rows-2)
def __draw_preferences(self):
self.panes[self.cur_cat].render(self,self.stdscr, self.prefs_width, self.active_zone == ZONE.PREFRENCES)
def __draw_actions(self):
if self.action_input:
selected = self.active_zone == ZONE.ACTIONS
self.stdscr.hline(self.rows-3,self.div_off+1,"_",self.cols)
self.action_input.render(self.stdscr,self.rows-2,self.cols,selected,self.cols-22)
selected = self.active_zone == ZONE.ACTIONS
self.stdscr.hline(self.rows-3,self.div_off+1,"_",self.cols)
self.action_input.render(self.stdscr,self.rows-2,self.cols,selected,self.cols-22)
def refresh(self):
if self.popup == None and self.messages:
@ -103,6 +125,9 @@ class Preferences(BaseMode):
self.__draw_catetories()
self.__draw_actions()
# do this last since it moves the cursor
self.__draw_preferences()
self.stdscr.noutrefresh()
@ -119,7 +144,32 @@ class Preferences(BaseMode):
self.cur_cat = min(len(self.categories)-1,self.cur_cat+1)
def __prefs_read(self, c):
pass
self.panes[self.cur_cat].handle_read(c)
def __apply_prefs(self):
new_core_config = {}
for pane in self.panes:
pane.add_config_values(new_core_config)
# Apply Core Prefs
if client.connected():
# Only do this if we're connected to a daemon
config_to_set = {}
for key in new_core_config.keys():
# The values do not match so this needs to be updated
if self.core_config[key] != new_core_config[key]:
config_to_set[key] = new_core_config[key]
if config_to_set:
# Set each changed config value in the core
client.core.set_config(config_to_set)
client.force_call(True)
# Update the configuration
self.core_config.update(config_to_set)
def __update_preferences(self,core_config):
self.core_config = core_config
for pane in self.panes:
pane.update_values(core_config)
def __actions_read(self, c):
self.action_input.handle_read(c)
@ -128,10 +178,10 @@ class Preferences(BaseMode):
if self.action_input.selidx == 0: # cancel
self.back_to_parent()
elif self.action_input.selidx == 1: # apply
# TODO: Actually apply
pass
self.__apply_prefs()
client.core.get_config().addCallback(self.__update_preferences)
elif self.action_input.selidx == 2: # OK
# TODO: Actually apply
self.__apply_prefs()
self.back_to_parent()
@ -160,7 +210,7 @@ class Preferences(BaseMode):
reactor.stop()
return
elif c == 9:
if c == 9:
self.active_zone += 1
if self.active_zone > ZONE.ACTIONS:
self.active_zone = ZONE.CATEGORIES