summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorhut <hut@lavabit.com>2009-12-11 22:30:07 +0100
committerhut <hut@lavabit.com>2009-12-11 22:30:07 +0100
commitd88232a386dbc2153aa12bea7f30c9c53c414010 (patch)
tree2f6f93ff6769e7fdc829a910f25513c9645d7333
parent3de15ddd7fb0151e5f43f0b8e7d06bd76568e235 (diff)
downloadranger-d88232a386dbc2153aa12bea7f30c9c53c414010.tar.gz
Reorganization of gui/widget hierarchy and directory structure
-rw-r--r--ranger/actions.py8
-rw-r--r--ranger/applications.py4
-rw-r--r--ranger/defaults/keys.py22
-rw-r--r--ranger/environment.py3
-rw-r--r--ranger/fm.py7
-rw-r--r--ranger/gui/color.py3
-rw-r--r--ranger/gui/defaultui.py36
-rw-r--r--ranger/gui/displayable.py167
-rw-r--r--ranger/gui/mouse_event.py16
-rw-r--r--ranger/gui/ui.py140
-rw-r--r--ranger/gui/widget.py74
-rw-r--r--ranger/gui/widgets/__init__.py0
-rw-r--r--ranger/gui/widgets/console.py (renamed from ranger/gui/wconsole.py)38
-rw-r--r--ranger/gui/widgets/filelist.py (renamed from ranger/gui/wdisplay.py)40
-rw-r--r--ranger/gui/widgets/titlebar.py (renamed from ranger/gui/wtitlebar.py)29
-rw-r--r--ranger/main.py6
-rw-r--r--test/tc_directory.py2
17 files changed, 348 insertions, 247 deletions
diff --git a/ranger/actions.py b/ranger/actions.py
index 54a88b11..a36b0c56 100644
--- a/ranger/actions.py
+++ b/ranger/actions.py
@@ -20,7 +20,7 @@ class Actions(EnvironmentAware, SettingsAware):
 			raise SystemExit()
 
 	def resize(self):
-		self.ui.resize()
+		self.ui.update_size()
 
 	def exit(self):
 		raise SystemExit()
@@ -57,7 +57,7 @@ class Actions(EnvironmentAware, SettingsAware):
 		self.env.history_go(relative)
 	
 	def handle_mouse(self):
-		self.ui.handle_mouse(self)
+		self.ui.handle_mouse()
 
 	def execute_file(self, files, app = '', flags = '', mode = 0):
 		if type(files) not in (list, tuple):
@@ -78,7 +78,7 @@ class Actions(EnvironmentAware, SettingsAware):
 		self.execute_file(self.env.cf, app = 'editor')
 
 	def open_console(self, mode = ':'):
-		if self.ui.can('open_console'):
+		if hasattr(self.ui, 'open_console'):
 			self.ui.open_console(mode)
 
 	def move_pointer(self, relative = 0, absolute = None):
@@ -89,7 +89,7 @@ class Actions(EnvironmentAware, SettingsAware):
 				relative = int(relative * self.env.termsize[0]))
 
 	def scroll(self, relative):
-		if self.ui.can('scroll'):
+		if hasattr(self.ui, 'scroll'):
 			self.ui.scroll(relative)
 			self.env.cf = self.env.pwd.pointed_file
 
diff --git a/ranger/applications.py b/ranger/applications.py
index 99a40e93..85dc50f5 100644
--- a/ranger/applications.py
+++ b/ranger/applications.py
@@ -50,8 +50,8 @@ def run(*args, **kw):
 		return process
 
 	else:
-		if fm.ui is not None: fm.ui.exit()
+		if fm.ui: fm.ui.destroy()
 		p = Popen(args, **popen_kw)
 		waitpid_no_intr(p.pid)
-		if fm.ui is not None: fm.ui.initialize()
+		if fm.ui: fm.ui.initialize()
 		return p
diff --git a/ranger/defaults/keys.py b/ranger/defaults/keys.py
index 2f623bca..5edae929 100644
--- a/ranger/defaults/keys.py
+++ b/ranger/defaults/keys.py
@@ -90,7 +90,7 @@ def initialize_commands(command_list):
 
 def initialize_console_commands(command_list):
 	from ranger.actions import Actions as do
-	from ranger.gui.wconsole import WConsole
+	from ranger.gui.widgets.console import Console
 
 	def bind(fnc, *keys):
 		command_list.bind(fnc, *keys)
@@ -107,18 +107,18 @@ def initialize_console_commands(command_list):
 	c = curry
 
 	# movement
-	bind(c(WConsole.move, relative = -1), curses.KEY_LEFT, ctrl('b'))
-	bind(c(WConsole.move, relative =  1), curses.KEY_RIGHT, ctrl('f'))
-	bind(c(WConsole.move, absolute = 0), curses.KEY_HOME, ctrl('a'))
-	bind(c(WConsole.move, absolute = -1), curses.KEY_END, ctrl('e'))
-	bind(c(WConsole.delete, 0), curses.KEY_DC, ctrl('d'))
-	bind(c(WConsole.delete, -1), curses.KEY_BACKSPACE, 127, ctrl('h'))
-	bind(c(WConsole.delete_rest, -1), ctrl('U'))
-	bind(c(WConsole.delete_rest,  1), ctrl('K'))
+	bind(c(Console.move, relative = -1), curses.KEY_LEFT, ctrl('b'))
+	bind(c(Console.move, relative =  1), curses.KEY_RIGHT, ctrl('f'))
+	bind(c(Console.move, absolute = 0), curses.KEY_HOME, ctrl('a'))
+	bind(c(Console.move, absolute = -1), curses.KEY_END, ctrl('e'))
+	bind(c(Console.delete, 0), curses.KEY_DC, ctrl('d'))
+	bind(c(Console.delete, -1), curses.KEY_BACKSPACE, 127, ctrl('h'))
+	bind(c(Console.delete_rest, -1), ctrl('U'))
+	bind(c(Console.delete_rest,  1), ctrl('K'))
 
 	# system functions
-	bind(c(WConsole.close),    ESC, ctrl('C'))
-	bind(WConsole.execute,  curses.KEY_ENTER, ctrl('j'))
+	bind(c(Console.close),    ESC, ctrl('C'))
+	bind(Console.execute,  curses.KEY_ENTER, ctrl('j'))
 	bind(c_fm(do.redraw), ctrl('L'))
 	bind(c_fm(do.resize), curses.KEY_RESIZE)
 
diff --git a/ranger/environment.py b/ranger/environment.py
index d7097fc8..9e2c2877 100644
--- a/ranger/environment.py
+++ b/ranger/environment.py
@@ -15,7 +15,8 @@ class Environment(SettingsAware):
 		self.cf = None # current file
 		self.keybuffer = KeyBuffer()
 		self.copy = None
-		self.termsize = (24, 80)
+		self.termsize = None
+#		self.termsize = (24, 80)
 		self.history = History(self.settings.max_history_size)
 
 		from ranger.shared import EnvironmentAware
diff --git a/ranger/fm.py b/ranger/fm.py
index cdcfb8d7..dd74342f 100644
--- a/ranger/fm.py
+++ b/ranger/fm.py
@@ -3,6 +3,7 @@ from ranger.container import Bookmarks
 from ranger import __version__
 
 USAGE = '''%s [options] [path/filename]'''
+CTRL_C = 3
 
 class FM(Actions):
 	def __init__(self, ui = None, bookmarks = None):
@@ -35,8 +36,10 @@ class FM(Actions):
 			try:
 				self.bookmarks.reload_if_outdated()
 				self.ui.draw()
+				self.ui.finalize()
+
 				key = self.ui.get_next_key()
-				self.ui.press(key, self)
+				self.ui.handle_key(key)
 
 				gc_tick += 1
 				if gc_tick > 10:
@@ -44,4 +47,4 @@ class FM(Actions):
 					self.env.garbage_collect()
 
 			except KeyboardInterrupt:
-				self.ui.press(3, self)
+				self.ui.handle_key(CTRL_C, self)
diff --git a/ranger/gui/color.py b/ranger/gui/color.py
index 214383ee..1318ab6f 100644
--- a/ranger/gui/color.py
+++ b/ranger/gui/color.py
@@ -1,9 +1,10 @@
+"""Contains abbreviations to curses' color/attribute constants."""
 import curses
 
 COLOR_PAIRS = {10: 0}
 
 def get_color(fg, bg):
-	import curses
+	"""Returns the color pair for the given fg/bg combination."""
 
 	c = bg+2 + 9*(fg + 2)
 
diff --git a/ranger/gui/defaultui.py b/ranger/gui/defaultui.py
index 547f20ff..8d9f7ded 100644
--- a/ranger/gui/defaultui.py
+++ b/ranger/gui/defaultui.py
@@ -4,28 +4,29 @@ RATIO = ( 0.15, 0.15, 0.4, 0.3 )
 from ranger.gui.ui import UI as SuperClass
 class DefaultUI(SuperClass):
 	def setup(self):
-		from ranger.gui.wdisplay import WDisplay
-		from ranger.gui.wtitlebar import WTitleBar
-		from ranger.gui.wconsole import WConsole
-		self.titlebar = WTitleBar(self.win, self.colorscheme)
-		self.add_widget(self.titlebar)
+		from ranger.gui.widgets.filelist import FileList
+		from ranger.gui.widgets.titlebar import TitleBar
+		from ranger.gui.widgets.console import Console
+		self.titlebar = TitleBar(self.win)
+		self.add_obj(self.titlebar)
 
 		self.displays = [
-				WDisplay(self.win, self.colorscheme, -2),
-				WDisplay(self.win, self.colorscheme, -1),
-				WDisplay(self.win, self.colorscheme, 0),
-				WDisplay(self.win, self.colorscheme, 1) ]
+				FileList(self.win, -2),
+				FileList(self.win, -1),
+				FileList(self.win, 0),
+				FileList(self.win, 1) ]
 		self.main_display = self.displays[2]
 		self.displays[2].display_infostring = True
 		self.displays[2].main_display = True
 		for disp in self.displays:
-			self.add_widget(disp)
+			self.add_obj(disp)
 
-		self.console = WConsole(self.win, self.colorscheme)
-		self.add_widget(self.console)
+		self.console = Console(self.win)
+		self.add_obj(self.console)
 
-	def resize(self):
-		SuperClass.resize(self)
+	def update_size(self):
+		"""resize all widgets"""
+		SuperClass.update_size(self)
 		y, x = self.win.getmaxyx()
 
 		leftborder = 0
@@ -34,16 +35,15 @@ class DefaultUI(SuperClass):
 		for ratio in RATIO:
 			wid = int(ratio * x)
 			try:
-				self.displays[i].setdim(1, leftborder, y-2, wid - 1)
+				self.displays[i].resize(1, leftborder, y-2, wid - 1)
 			except KeyError:
 				pass
 			leftborder += wid
 			i += 1
 
-		self.titlebar.setdim(0, 0, 1, x)
-		self.console.setdim(y-1, 0, 1, x)
+		self.titlebar.resize(0, 0, 1, x)
+		self.console.resize(y-1, 0, 1, x)
 
-	# ---specials---
 	def open_console(self, mode):
 		if self.console.open(mode):
 			self.console.on_close = self.close_console
diff --git a/ranger/gui/displayable.py b/ranger/gui/displayable.py
new file mode 100644
index 00000000..4ac8c3fc
--- /dev/null
+++ b/ranger/gui/displayable.py
@@ -0,0 +1,167 @@
+from ranger.shared import FileManagerAware, EnvironmentAware, SettingsAware
+
+class Displayable(EnvironmentAware, FileManagerAware, SettingsAware):
+	focused = False
+	visible = True
+	win = None
+	colorscheme = None
+
+	def __init__(self, win):
+		self.resize(0, 0, 0, 0)
+		self.colorscheme = self.env.settings.colorscheme
+
+		if win is not None:
+			self.win = win
+
+	def __nonzero__(self):
+		"""Always True"""
+		return True
+
+	def color(self, keylist = None, *keys):
+		"""Change the colors from now on."""
+		keys = combine(keylist, keys)
+		self.win.attrset(self.colorscheme.get_attr(*keys))
+
+	def color_at(self, y, x, wid, keylist = None, *keys):
+		"""Change the colors at the specified position"""
+		keys = combine(keylist, keys)
+		self.win.chgat(y, x, wid, self.colorscheme.get_attr(*keys))
+	
+	def color_reset(self):
+		"""Change the colors to the default colors"""
+		Displayable.color(self, 'reset')
+
+	def draw(self):
+		"""Draw the object. Called on every main iteration.
+Containers should call draw() on their contained objects here.
+Override this!"""
+
+	def destroy(self):
+		"""Called when the object is destroyed.
+Override this!"""
+
+	def contains_point(self, y, x):
+		"""Test if the point lies within the boundaries of this object"""
+		return (x >= self.x and x < self.x + self.wid) and \
+				(y >= self.y and y < self.y + self.hei)
+
+	def click(self, event):
+		"""Called when a mouse key is pressed and self.focused is True.
+Override this!"""
+		pass
+
+	def press(self, key):
+		"""Called when a key is pressed and self.focused is True.
+Override this!"""
+		pass
+	
+	def draw(self):
+		"""Draw displayable. Called on every main iteration.
+Override this!"""
+		pass
+
+	def finalize(self):
+		"""Called after every displayable is done drawing.
+Override this!"""
+		pass
+
+	def resize(self, y, x, hei=None, wid=None):
+		"""Resize the widget"""
+		try:
+			maxy, maxx = self.env.termsize
+		except TypeError:
+			pass
+		else:
+			wid = wid or maxx - x
+			hei = hei or maxy - y
+
+			if x + wid > maxx and y + hei > maxy:
+				raise OutOfBoundsException("X and Y out of bounds!")
+
+			if x + wid > maxx:
+				raise OutOfBoundsException("X out of bounds!")
+
+			if y + hei > maxy:
+				raise OutOfBoundsException("Y out of bounds!")
+
+		self.x = x
+		self.y = y
+		self.wid = wid
+		self.hei = hei
+
+
+class DisplayableContainer(Displayable):
+	container = None
+	def __init__(self, win):
+		Displayable.__init__(self, win)
+		self.container = []
+
+	def draw(self):
+		"""Recursively called on objects in container"""
+		for displayable in self.container:
+			if displayable.visible:
+				displayable.draw()
+
+	def finalize(self):
+		"""Recursively called on objects in container"""
+		for displayable in self.container:
+			if displayable.visible:
+				displayable.finalize()
+	
+	def get_focused_obj(self):
+		"""Finds a focused displayable object in the container."""
+		for displayable in self.container:
+			if displayable.focused:
+				return displayable
+			try:
+				obj = displayable.get_focused_obj()
+			except AttributeError:
+				pass
+			else:
+				if obj is not None:
+					return obj
+		return None
+
+	def press(self, key):
+		"""Recursively called on objects in container"""
+		focused_obj = self.get_focused_obj()
+
+		if focused_obj:
+			focused_obj.press(key)
+			return True
+		return False
+	
+	def click(self, event):
+		"""Recursively called on objects in container"""
+		focused_obj = self.get_focused_obj()
+		if focused_obj:
+			focused_obj.press(key)
+			return True
+		return False
+
+	def add_obj(self, obj):
+		self.container.append(obj)
+	
+	def destroy(self):
+		"""Recursively called on objects in container"""
+		for displayable in self.container:
+			displayable.destroy()
+
+#	def resize(self):
+#		"""Recursively called on objects in container"""
+#		for displayable in container:
+#			displayable.resize()
+
+class OutOfBoundsException(Exception):
+	pass
+
+def combine(seq, tup):
+	"""Add seq and tup. Ensures that the result is a tuple."""
+	try:
+		if isinstance(seq, str): raise TypeError
+		return tuple(tuple(seq) + tup)
+	except TypeError:
+		try:
+			return tuple((seq, ) + tup)
+		except:
+			return ()
diff --git a/ranger/gui/mouse_event.py b/ranger/gui/mouse_event.py
new file mode 100644
index 00000000..fa25f5f0
--- /dev/null
+++ b/ranger/gui/mouse_event.py
@@ -0,0 +1,16 @@
+class MouseEvent(object):
+	import curses
+	PRESSED = [ 0,
+			curses.BUTTON1_PRESSED,
+			curses.BUTTON2_PRESSED,
+			curses.BUTTON3_PRESSED,
+			curses.BUTTON4_PRESSED ]
+
+	def __init__(self, getmouse):
+		_, self.x, self.y, _, self.bstate = getmouse
+	
+	def pressed(self, n):
+		try:
+			return (self.bstate & MouseEvent.PRESSED[n]) != 0
+		except:
+			return False
diff --git a/ranger/gui/ui.py b/ranger/gui/ui.py
index c7c10cfe..8abd39df 100644
--- a/ranger/gui/ui.py
+++ b/ranger/gui/ui.py
@@ -1,26 +1,11 @@
 import curses
 
-class MouseEvent(object):
-	import curses
-	PRESSED = [ 0,
-			curses.BUTTON1_PRESSED,
-			curses.BUTTON2_PRESSED,
-			curses.BUTTON3_PRESSED,
-			curses.BUTTON4_PRESSED ]
-
-	def __init__(self, getmouse):
-		_, self.x, self.y, _, self.bstate = getmouse
-	
-	def pressed(self, n):
-		try:
-			return (self.bstate & MouseEvent.PRESSED[n]) != 0
-		except:
-			return False
-
-from ranger.shared import EnvironmentAware, SettingsAware
+from .displayable import DisplayableContainer
+from .mouse_event import MouseEvent
 from ranger.container import CommandList
 
-class UI(EnvironmentAware, SettingsAware):
+class UI(DisplayableContainer):
+	is_set_up = False
 	def __init__(self, commandlist = None):
 		import os
 		os.environ['ESCDELAY'] = '25' # don't know a cleaner way
@@ -30,13 +15,12 @@ class UI(EnvironmentAware, SettingsAware):
 			self.settings.keys.initialize_commands(self.commandlist)
 		else:
 			self.commandlist = commandlist
-		self.colorscheme = self.env.settings.colorscheme
-		self.is_set_up = False
 		self.win = curses.initscr()
 
-		self.widgets = []
+		DisplayableContainer.__init__(self, None)
 
 	def initialize(self):
+		"""initialize curses, then call setup (at the first time) and resize."""
 		self.win.leaveok(0)
 		self.win.keypad(1)
 
@@ -46,6 +30,7 @@ class UI(EnvironmentAware, SettingsAware):
 		curses.curs_set(0)
 		curses.start_color()
 		curses.use_default_colors()
+
 		curses.mouseinterval(0)
 		mask = curses.ALL_MOUSE_EVENTS | curses.REPORT_MOUSE_POSITION
 		avail, old = curses.mousemask(mask)
@@ -54,63 +39,37 @@ class UI(EnvironmentAware, SettingsAware):
 		if not self.is_set_up:
 			self.is_set_up = True
 			self.setup()
-		self.resize()
-
-	def exit(self):
-		from ranger import log
-		log("exiting ui!")
-		self.win.keypad(0)
-		curses.nocbreak()
-		curses.echo()
-		curses.curs_set(1)
-		curses.mousemask(0)
-		curses.endwin()
+		self.update_size()
 
-	def handle_mouse(self, fm):
+	def handle_mouse(self):
+		"""Handles mouse input"""
 		try:
 			event = MouseEvent(curses.getmouse())
 		except:
 			return
 
+		from ranger import log
+		log(str(event.bstate))
+
 		if event.pressed(1) or event.pressed(3):
-			for widg in self.widgets:
-				if widg.contains_point(event.y, event.x):
-					widg.click(event, fm)
+			for displayable in self.container:
+				if displayable.contains_point(event.y, event.x):
+					displayable.click(event)
 					break
 
-		if event.pressed(4) or event.pressed(2) or event.bstate & 134217728:
+#		if event.pressed(4) or event.pressed(2) or event.bstate & 134217728:
+		if event.pressed(4) or event.pressed(2):
 			if event.pressed(4):
-				fm.scroll(relative = -3)
+				self.fm.scroll(relative = -3)
 			else:
-				fm.scroll(relative = 3)
+				self.fm.scroll(relative = 3)
 
-	def can(self, attr):
-		return hasattr(self, attr)
-
-	def setup(self):
-		pass
-
-	def resize(self):
-		self.env.termsize = self.win.getmaxyx()
-
-	def redraw(self):
-		self.win.redrawwin()
-		self.win.refresh()
-		self.win.redrawwin()
-
-	def add_widget(self, widg):
-		self.widgets.append(widg)
-
-	def feed_env(self, env):
-		self.env = env
-
-	def press(self, key, fm):
+	def handle_key(self, key):
+		"""Handles key input"""
 		self.env.key_append(key)
 
-		for widg in self.widgets:
-			if widg.focused:
-				widg.press(key, fm, self.env)
-				return
+		if DisplayableContainer.press(self, key):
+			return
 
 		try:
 			cmd = self.commandlist.paths[tuple(self.env.keybuffer)]
@@ -121,21 +80,48 @@ class UI(EnvironmentAware, SettingsAware):
 		if cmd == self.commandlist.dummy_object:
 			return
 
-		cmd.execute(fm)
+		cmd.execute(self.fm)
 		self.env.key_clear()
 
-	def draw(self):
-		self.win.erase()
-		for widg in self.widgets:
-			widg.feed_env(self.env)
-			if widg.visible:
-				widg.draw()
-		for widg in self.widgets:
-			if widg.visible:
-				widg.finalize()
-		self.win.refresh()
-
 	def get_next_key(self):
+		"""Waits for key input and returns the pressed key"""
 		key = self.win.getch()
 		curses.flushinp()
 		return key
+
+	def setup(self):
+		"""Called after an initialize() call.
+Override this!"""
+
+	def redraw(self):
+		"""Redraw the window. This only calls self.win.redrawwin()."""
+		self.win.redrawwin()
+		self.win.refresh()
+		self.win.redrawwin()
+
+	def update_size(self):
+		"""Update self.env.termsize.
+Extend this method to resize all widgets!"""
+		self.env.termsize = self.win.getmaxyx()
+
+	def draw(self):
+		"""Erase the window, then draw all objects in the container"""
+		self.win.erase()
+		DisplayableContainer.draw(self)
+
+	def finalize(self):
+		"""Finalize every object in container and refresh the window"""
+		DisplayableContainer.finalize(self)
+		self.win.refresh()
+
+	def destroy(self):
+		"""Destroy all widgets and turn off curses"""
+#		DisplayableContainer.destroy(self)
+		from ranger import log
+		log("exiting ui!")
+		self.win.keypad(0)
+		curses.nocbreak()
+		curses.echo()
+		curses.curs_set(1)
+#		curses.mousemask(0)
+		curses.endwin()
diff --git a/ranger/gui/widget.py b/ranger/gui/widget.py
deleted file mode 100644
index 093eee14..00000000
--- a/ranger/gui/widget.py
+++ /dev/null
@@ -1,74 +0,0 @@
-
-class OutOfBoundsException(Exception):
-	pass
-
-def combine(keylist, keys):
-	if type(keylist) in (list, tuple):
-		return tuple(tuple(keylist) + keys)
-	else:
-		return tuple((keylist, ) + keys)
-
-from ranger.shared import SettingsAware
-class Widget(SettingsAware):
-	def __init__(self, win, _):
-		self.win = win
-		self.focused = False
-		self.colorscheme = self.settings.colorscheme
-		self.visible = True
-		self.setdim(0, 0, 0, 0)
-
-	def color(self, keylist = None, *keys):
-		keys = combine(keylist, keys)
-		self.win.attrset(self.colorscheme.get_attr(*keys))
-
-	def color_at(self, y, x, wid, keylist = None, *keys):
-		keys = combine(keylist, keys)
-		self.win.chgat(y, x, wid, self.colorscheme.get_attr(*keys))
-	
-	def color_reset(self):
-		Widget.color(self, 'reset')
-
-	def setdim(self, y, x, hei=None, wid=None):
-		maxy, maxx = self.win.getmaxyx()
-
-		wid = wid or maxx - x
-		hei = hei or maxy - y
-
-		if x + wid > maxx and y + hei > maxy:
-			raise OutOfBoundsException("X and Y out of bounds!")
-
-		if x + wid > maxx:
-			raise OutOfBoundsException("X out of bounds!")
-
-		if y + hei > maxy:
-			raise OutOfBoundsException("Y out of bounds!")
-
-		self.x = x
-		self.y = y
-		self.wid = wid
-		self.hei = hei
-	
-	def contains_point(self, y, x):
-		return (x >= self.x and x < self.x + self.wid) and \
-				(y >= self.y and y < self.y + self.hei)
-
-	def feed_env(self, env):
-		pass
-
-	def feed(self):
-		pass
-
-	def click(self, event, fm):
-		pass
-
-	def press(self, key, fm):
-		pass
-	
-	def draw(self):
-		pass
-
-	def finalize(self):
-		pass
-
-	def destroy(self):
-		pass
diff --git a/ranger/gui/widgets/__init__.py b/ranger/gui/widgets/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ranger/gui/widgets/__init__.py
diff --git a/ranger/gui/wconsole.py b/ranger/gui/widgets/console.py
index 581745b4..ab37d628 100644
--- a/ranger/gui/wconsole.py
+++ b/ranger/gui/widgets/console.py
@@ -1,13 +1,15 @@
-from ranger.gui.widget import Widget as SuperClass
+"""The Console widget implements a vim-like console for entering commands,
+searching and executing files."""
+from ..displayable import Displayable
 import curses
 
 CONSOLE_MODES = tuple(':@/?>!')
-CONSOLE_MODES_DICTIONARY = { '@': 'open with: ' }
+CONSOLE_PROMPTS = { '@': 'open with: ' }
 
-class WConsole(SuperClass):
-	def __init__(self, win, colorscheme):
+class Console(Displayable):
+	def __init__(self, win):
 		from ranger.container import CommandList
-		SuperClass.__init__(self, win, colorscheme)
+		Displayable.__init__(self, win)
 		self.mode = None
 		self.visible = False
 		self.commandlist = CommandList()
@@ -16,12 +18,12 @@ class WConsole(SuperClass):
 		self.clear()
 		self.prompt = None
 		self.execute_funcs = {
-				':': WConsole.execute_command,
-				'@': WConsole.execute_openwith_quick,
-				'/': WConsole.execute_search,
-				'?': WConsole.execute_search,
-				'>': WConsole.execute_noreturn,
-				'!': WConsole.execute_openwith }
+				':': Console.execute_command,
+				'@': Console.execute_openwith_quick,
+				'/': Console.execute_search,
+				'?': Console.execute_search,
+				'>': Console.execute_noreturn,
+				'!': Console.execute_openwith }
 	
 	def feed_env(self, env):
 		self.cf = env.cf
@@ -44,7 +46,7 @@ class WConsole(SuperClass):
 		self.last_cursor_mode = curses.curs_set(1)
 		self.mode = mode
 		try:
-			self.prompt = CONSOLE_MODES_DICTIONARY[self.mode]
+			self.prompt = CONSOLE_PROMPTS[self.mode]
 		except KeyError:
 			self.prompt = self.mode
 		self.focused = True
@@ -62,22 +64,20 @@ class WConsole(SuperClass):
 		self.pos = 0
 		self.line = ''
 	
-	def press(self, key, fm, env):
+	def press(self, key):
 		from curses.ascii import ctrl, ESC
-#		from ranger.helper import log
-#		log(key)
 
 		try:
-			cmd = self.commandlist.paths[env.keybuffer]
+			cmd = self.commandlist.paths[self.env.keybuffer]
 		except KeyError:
-			env.key_clear()
+			self.env.key_clear()
 			return
 
 		if cmd == self.commandlist.dummy_object:
 			return
 
-		cmd.execute(self, fm)
-		env.key_clear()
+		cmd.execute(self, self.fm)
+		self.env.key_clear()
 
 	def type_key(self, key):
 		if isinstance(key, int):
diff --git a/ranger/gui/wdisplay.py b/ranger/gui/widgets/filelist.py
index c7e78b8e..493dae24 100644
--- a/ranger/gui/wdisplay.py
+++ b/ranger/gui/widgets/filelist.py
@@ -1,17 +1,17 @@
-from ranger.gui.widget import Widget as SuperClass
+"""The FileList widget displays the contents of a directory or file."""
+from ..displayable import Displayable
 
-class WDisplay(SuperClass):
-	def __init__(self, win, colorscheme, level):
-		SuperClass.__init__(self, win, colorscheme)
+class FileList(Displayable):
+	main_display = False
+	display_infostring = False
+	scroll_begin = 0
+
+	def __init__(self, win, level):
+		Displayable.__init__(self, win)
 		self.level = level
-		self.main_display = False
-		self.display_infostring = False
-		self.scroll_begin = 0
 
-	def feed_env(self, env):
-		self.target = env.at_level(self.level)
-		
-	def click(self, event, fm):
+	def click(self, event):
+		"""Handle a MouseEvent"""
 		from ranger.fsobject.fsobject import T_DIRECTORY
 
 		if self.target is None:
@@ -22,25 +22,28 @@ class WDisplay(SuperClass):
 
 			if event.pressed(1):
 				if not self.main_display:
-					fm.enter_dir(self.target.path)
+					self.fm.enter_dir(self.target.path)
 
 				if index < len(self.target):
-					fm.move_pointer(absolute = index)
+					self.fm.move_pointer(absolute = index)
 			elif event.pressed(3):
 				try:
 					clicked_file = self.target[index]
-					fm.enter_dir(clicked_file.path)
+					self.fm.enter_dir(clicked_file.path)
 				except:
 					pass
 
 		else:
 			if self.level > 0:
-				fm.move_right()
+				self.fm.move_right()
 
 	def draw(self):
+		"""Call either draw_file() or draw_directory()"""
 		from ranger.fsobject.file import File
 		from ranger.fsobject.directory import Directory
 
+		self.target = self.env.at_level(self.level)
+
 		if self.target is None:
 			pass
 		elif type(self.target) == File:
@@ -51,6 +54,7 @@ class WDisplay(SuperClass):
 			self.win.addnstr(self.y, self.x, "unknown type.", self.wid)
 
 	def draw_file(self):
+		"""Draw a preview of the file, if the settings allow it"""
 		if not self.target.accessible:
 			self.win.addnstr(self.y, self.x, "not accessible", self.wid)
 			return
@@ -66,8 +70,8 @@ class WDisplay(SuperClass):
 				pass
 
 	def draw_directory(self):
+		"""Draw the contents of a directory"""
 		from ranger.fsobject.directory import Directory
-		import curses
 		import stat
 
 		self.target.use()
@@ -135,6 +139,7 @@ class WDisplay(SuperClass):
 			self.color_reset()
 
 	def get_scroll_begin(self):
+		"""Determines scroll_begin (the position of the first displayed file)"""
 		offset = self.settings.scroll_offset
 		dirsize = len(self.target)
 		winsize = self.hei
@@ -173,6 +178,7 @@ class WDisplay(SuperClass):
 		return original
 
 	def set_scroll_begin(self):
+		"""Updates the scroll_begin value"""
 		self.scroll_begin = self.get_scroll_begin()
 		self.target.scroll_begin = self.scroll_begin
 
@@ -187,5 +193,3 @@ class WDisplay(SuperClass):
 		if self.target.scroll_begin == old_value:
 			self.target.move_pointer(relative = relative)
 			self.target.scroll_begin += relative
-
-
diff --git a/ranger/gui/wtitlebar.py b/ranger/gui/widgets/titlebar.py
index 65ed2e33..4ec48766 100644
--- a/ranger/gui/wtitlebar.py
+++ b/ranger/gui/widgets/titlebar.py
@@ -1,24 +1,18 @@
-from ranger.gui.widget import Widget as SuperClass
+"""The TitleBar widget displays the current path and some other useful
+information."""
 
-class WTitleBar(SuperClass):
-	def feed_env(self, env):
-		self.pathway = env.pathway
-		self.cf = env.cf
-		self.keybuffer = env.keybuffer
+from ..displayable import Displayable
 
+class TitleBar(Displayable):
 	def draw(self):
 		import curses, socket, os
 		self.win.move(self.y, self.x)
 
-		try:
-			self.color('in_titlebar', 'hostname')
-			string = os.getenv('LOGNAME') + '@' + socket.gethostname()
-			self.win.addnstr(string, self.wid)
-		except:
-			raise
-			pass
+		self.color('in_titlebar', 'hostname')
+		string = os.getenv('LOGNAME') + '@' + socket.gethostname()
+		self.win.addnstr(string, self.wid)
 
-		for path in self.pathway:
+		for path in self.env.pathway:
 			currentx = self.win.getyx()[1]
 
 			if path.islink:
@@ -27,14 +21,14 @@ class WTitleBar(SuperClass):
 				self.color('in_titlebar', 'directory')
 
 			self.win.addnstr(path.basename + '/', max(self.wid - currentx, 0))
-		if self.cf is not None:
+		if self.env.cf is not None:
 			currentx = self.win.getyx()[1]
 			self.color('in_titlebar', 'file')
-			self.win.addnstr(self.cf.basename, max(self.wid - currentx, 0))
+			self.win.addnstr(self.env.cf.basename, max(self.wid - currentx, 0))
 
 		self.color('in_titlebar', 'keybuffer')
 
-		kb = str(self.keybuffer)
+		kb = str(self.env.keybuffer)
 		if self.wid + self.x - currentx > len(kb):
 			self.win.addstr(
 					self.y,
@@ -42,4 +36,3 @@ class WTitleBar(SuperClass):
 					kb)
 
 		self.color_reset()
-
diff --git a/ranger/main.py b/ranger/main.py
index 48297a7c..b6af145d 100644
--- a/ranger/main.py
+++ b/ranger/main.py
@@ -69,11 +69,15 @@ def main():
 		# Run the file manager
 		my_ui.initialize()
 		my_fm.loop()
+	
+#	except:
+#		from ranger import log
+#		log(str(sys.exc_info()))
 
 	finally:
 		# Finish, clean up
 		if 'my_ui' in vars():
-			my_ui.exit()
+			my_ui.destroy()
 
 		if args.cd_after_exit:
 			try: sys.__stderr__.write(env.pwd.path)
diff --git a/test/tc_directory.py b/test/tc_directory.py
index 92a4f01c..434d282f 100644
--- a/test/tc_directory.py
+++ b/test/tc_directory.py
@@ -10,7 +10,7 @@ from ranger.fsobject.directory import Directory
 from os.path import realpath, join, dirname
 TESTDIR = realpath(join(dirname(__file__), 'testdir'))
 TESTFILE = join(TESTDIR, 'testfile5234148')
-NONEXISTANT_DIR = '/this/directory/will/most/certainly/not/exist'
+NONEXISTANT_DIR = join(TESTDIR, 'nonexistant')
 
 import unittest
 class Test1(unittest.TestCase):