deluge/deluge/ui/console/modes/basemode.py
Calum Lind 1e6c811768 [Python-Modernize] lib2to3.fixes.fix_except
* Use 'ex' instead of 'e' to conform with pylint
 * Minimal Flake8 on some files
2014-09-03 17:22:38 +01:00

241 lines
7.5 KiB
Python

#
# basemode.py
#
# Copyright (C) 2011 Nick Lanham <nick@afternight.org>
#
# Most code in this file taken from screen.py:
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
#
# 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.
#
#
import sys
import logging
try:
import curses
except ImportError:
pass
import deluge.component as component
import deluge.ui.console.colors as colors
try:
import signal
from fcntl import ioctl
import termios
import struct
except:
pass
from twisted.internet import reactor
log = logging.getLogger(__name__)
class CursesStdIO(object):
"""fake fd to be registered as a reader with the twisted reactor.
Curses classes needing input should extend this"""
def fileno(self):
""" We want to select on FD 0 """
return 0
def doRead(self):
"""called when input is ready"""
pass
def logPrefix(self): return 'CursesClient'
class BaseMode(CursesStdIO):
def __init__(self, stdscr, encoding=None, do_refresh=True):
"""
A mode that provides a curses screen designed to run as a reader in a twisted reactor.
This mode doesn't do much, just shows status bars and "Base Mode" on the screen
Modes should subclass this and provide overrides for:
_doRead(self) - Handle user input
refresh(self) - draw the mode to the screen
add_string(self, row, string) - add a string of text to be displayed.
see method for detailed info
The init method of a subclass *must* call BaseMode.__init__
Useful fields after calling BaseMode.__init__:
self.stdscr - the curses screen
self.rows - # of rows on the curses screen
self.cols - # of cols on the curses screen
self.topbar - top statusbar
self.bottombar - bottom statusbar
"""
log.debug("BaseMode init!")
self.stdscr = stdscr
# Make the input calls non-blocking
self.stdscr.nodelay(1)
# Strings for the 2 status bars
self.statusbars = component.get("StatusBars")
# Keep track of the screen size
self.rows, self.cols = self.stdscr.getmaxyx()
try:
signal.signal(signal.SIGWINCH, self.on_resize)
except Exception:
log.debug("Unable to catch SIGWINCH signal!")
if not encoding:
self.encoding = sys.getdefaultencoding()
else:
self.encoding = encoding
colors.init_colors()
# Do a refresh right away to draw the screen
if do_refresh:
self.refresh()
def on_resize_norefresh(self, *args):
log.debug("on_resize_from_signal")
# Get the new rows and cols value
self.rows, self.cols = struct.unpack("hhhh", ioctl(0, termios.TIOCGWINSZ ,"\000"*8))[0:2]
curses.resizeterm(self.rows, self.cols)
def on_resize(self, *args):
self.on_resize_norefresh(args)
self.refresh()
def connectionLost(self, reason):
self.close()
def add_string(self, row, string, scr=None, col = 0, pad=True, trim=True):
"""
Adds a string to the desired `:param:row`.
:param row: int, the row number to write the string
:param string: string, the string of text to add
:param scr: curses.window, optional window to add string to instead of self.stdscr
:param col: int, optional starting column offset
:param pad: bool, optional bool if the string should be padded out to the width of the screen
:param trim: bool, optional bool if the string should be trimmed if it is too wide for the screen
The text can be formatted with color using the following format:
"{!fg, bg, attributes, ...!}"
See: http://docs.python.org/library/curses.html#constants for attributes.
Alternatively, it can use some built-in scheme for coloring.
See colors.py for built-in schemes.
"{!scheme!}"
Examples:
"{!blue, black, bold!}My Text is {!white, black!}cool"
"{!info!}I am some info text!"
"{!error!}Uh oh!"
"""
if scr:
screen = scr
else:
screen = self.stdscr
try:
parsed = colors.parse_color_string(string, self.encoding)
except colors.BadColorString as ex:
log.error("Cannot add bad color string %s: %s", string, ex)
return
for index, (color, s) in enumerate(parsed):
if index + 1 == len(parsed) and pad:
# This is the last string so lets append some " " to it
s += " " * (self.cols - (col + len(s)) - 1)
if trim:
y,x = screen.getmaxyx()
if (col+len(s)) > x:
s = "%s..."%s[0:x-4-col]
screen.addstr(row, col, s, color)
col += len(s)
def draw_statusbars(self):
self.add_string(0, self.statusbars.topbar)
self.add_string(self.rows - 1, self.statusbars.bottombar)
# This mode doesn't report errors
def report_message(self):
pass
# This mode doesn't do anything with popups
def set_popup(self,popup):
pass
# This mode doesn't support marking
def clear_marks(self):
pass
def refresh(self):
"""
Refreshes the screen.
Updates the lines based on the`:attr:lines` based on the `:attr:display_lines_offset`
attribute and the status bars.
"""
self.stdscr.erase()
self.draw_statusbars()
# Update the status bars
self.add_string(1,"{!info!}Base Mode (or subclass hasn't overridden refresh)")
self.stdscr.redrawwin()
self.stdscr.refresh()
def doRead(self):
"""
Called when there is data to be read, ie, input from the keyboard.
"""
# We wrap this function to catch exceptions and shutdown the mainloop
try:
self._doRead()
except Exception as ex:
log.exception(ex)
reactor.stop()
def _doRead(self):
# Read the character
c = self.stdscr.getch()
self.stdscr.refresh()
def close(self):
"""
Clean up the curses stuff on exit.
"""
curses.nocbreak()
self.stdscr.keypad(0)
curses.echo()
curses.endwin()