Some documentation
[pysize.git] / ui / curses / ui_curses.py
blob3689ecdae7d1bb71b6d4dac94162cf7f7dcfaf27
1 import curses
2 import os
3 import sys
4 import time
5 import locale
7 from ui.utils import human_unit, short_string
8 from ui.char_matrix import CharMatrix, MASK, SELECTED
9 from core.pysize_fs_tree import pysize_tree
11 class _CursesApp:
12 """The curses UI."""
13 def __init__(self, win, options, args):
14 self.window = win
15 self.max_depth = options.max_depth
16 self.root_path = args[0]
17 self._set_path(self.root_path)
18 self._init_curses()
20 def _init_curses(self):
21 curses.use_default_colors()
22 curses.start_color()
23 curses.curs_set(0)
25 def dots(*positions):
26 return sum(map(lambda n: 1<<n, positions))
28 self.MATRIX_TO_CURSES = {
29 dots(): ' ',
30 dots(3, 4, 5): curses.ACS_HLINE,
31 dots(1, 4, 7): curses.ACS_VLINE,
32 dots(1, 3, 4, 5, 7): curses.ACS_PLUS,
33 dots(4, 5, 7): curses.ACS_ULCORNER,
34 dots(3, 4, 7): curses.ACS_URCORNER,
35 dots(1, 4, 5): curses.ACS_LLCORNER,
36 dots(1, 3, 4): curses.ACS_LRCORNER,
37 dots(3, 4, 5, 7): curses.ACS_TTEE,
38 dots(1, 3, 4, 5): curses.ACS_BTEE,
39 dots(1, 4, 5, 7): curses.ACS_LTEE,
40 dots(1, 3, 4, 7): curses.ACS_RTEE
43 try:
44 self.status_win = curses.newwin(0, 0)
45 self.panel = curses.newwin(0, 0)
46 self._resize()
47 except curses.error:
48 print 'Cannot create windows'
49 sys.exit(1)
51 def _resize(self):
52 """Called when the terminal size changes."""
53 self.height, self.width = self.window.getmaxyx()
54 self.height -= 1 # Status bar
55 self.need_tree = True
56 self.status_win.resize(1, self.width)
57 self.status_win.mvwin(self.height, 0)
58 self.panel.resize(self.height, self.width)
59 self._set_path(self.path)
60 self._refresh()
62 def _redraw(self):
63 self.window.refresh()
64 if self.need_tree:
65 self.tree = pysize_tree(self.path, self.max_depth,
66 1.0 / self.height)
67 self.need_tree = False
68 if not self.tree.root:
69 return
70 self.panel.erase()
72 self._draw_matrix(CharMatrix(self.width, self.height, self.tree,
73 self.selected_item))
75 self.panel.refresh()
76 self.status_win.erase()
77 self.status_win.hline(0, 0, ord(' ') | curses.A_REVERSE, self.width)
78 END = ' - Pysize'
79 status_line = self.path + ' ' + human_unit(self.tree.root.size)
80 if len(status_line) + len(END) >= self.width:
81 status_line = short_string(status_line, self.width - 1)
82 else:
83 status_line += END
84 self.status_win.addstr(status_line, curses.A_REVERSE)
85 self.status_win.refresh()
87 def _refresh(self):
88 while True:
89 try:
90 self._redraw()
91 break
92 except curses.error, e:
93 print e
94 time.sleep(1)
96 def _set_path(self, path):
97 self.selected_item = None
98 self.selection = None
99 self.path = path
100 self.need_tree = True
102 def _next_step(self):
103 sequel = self.selected_item.get_fullpath()[len(self.path) + 1:]
104 sequel = sequel.split('/', 1)[0]
105 self._set_path(os.path.join(self.path, sequel))
107 def _select(self, function):
108 if not self.selected_item:
109 if self.tree.root.get_children():
110 self.selected_item = self.tree.root.get_children()[0]
111 return
112 selected = function(self.selected_item)
113 if not selected:
114 if function == self.tree.get_first_child and \
115 self.selected_item.is_dir():
116 # Browsing to the right
117 self._next_step()
118 return
119 self.selected_item = selected
120 if self.selected_item is self.tree.root:
121 # Browsing to the left
122 if not os.path.samefile(self.path, self.root_path):
123 parent = os.path.dirname(self.selected_item.get_fullpath())
124 self._set_path(parent)
125 else:
126 self.selected_item = None
128 def _selected(self):
129 if self.selected_item and self.selected_item.is_dir():
130 self._set_path(self.selected_item.get_fullpath())
132 def run(self):
133 """The main dispatcher."""
134 while True:
135 self._refresh()
136 c = self.window.getch()
137 action = {
138 ord('q'): lambda:
139 sys.exit(0),
141 curses.KEY_RESIZE: lambda:
142 self._resize(),
144 curses.KEY_DOWN: lambda:
145 self._select(self.tree.get_next_sibling),
147 curses.KEY_UP: lambda:
148 self._select(self.tree.get_previous_sibling),
150 curses.KEY_LEFT: lambda:
151 self._select(self.tree.get_parent),
153 curses.KEY_RIGHT: lambda:
154 self._select(self.tree.get_first_child),
156 ord('\n'): lambda:
157 self._selected()
158 }.get(c, lambda: None)
159 action()
161 def _draw_matrix(self, matrix):
162 for (y, line) in enumerate(matrix.matrix):
163 for (x, char) in enumerate(line):
164 if isinstance(char, int):
165 selected = (char & SELECTED) != 0
166 char &= MASK
167 char = self.MATRIX_TO_CURSES[char]
168 if selected:
169 char |= curses.A_REVERSE
170 self.panel.insch(y, x, char)
171 else:
172 char = char.encode('UTF-8')
173 self.panel.insstr(y, x, char)
175 def _run_curses(win, options, args):
176 app = _CursesApp(win, options, args)
177 app.run()
179 def run(options, args):
180 locale.setlocale(locale.LC_ALL, '')
181 curses.wrapper(_run_curses, options, args)
183 def is_available():
184 return sys.stdin.isatty() and sys.stdout.isatty()