Create the dictionary only once
[pysize.git] / ui / curses / ui_curses.py
blobcb578866e64dcb00a3c6460dca25a085823c418a
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 key_bindings = {
135 ord('q'): lambda:
136 sys.exit(0),
138 curses.KEY_RESIZE: lambda:
139 self._resize(),
141 curses.KEY_DOWN: lambda:
142 self._select(self.tree.get_next_sibling),
144 curses.KEY_UP: lambda:
145 self._select(self.tree.get_previous_sibling),
147 curses.KEY_LEFT: lambda:
148 self._select(self.tree.get_parent),
150 curses.KEY_RIGHT: lambda:
151 self._select(self.tree.get_first_child),
153 ord('\n'): lambda:
154 self._selected()
157 while True:
158 self._refresh()
159 c = self.window.getch()
160 action = key_bindings.get(c, lambda: None)
161 action()
163 def _draw_matrix(self, matrix):
164 for (y, line) in enumerate(matrix.matrix):
165 for (x, char) in enumerate(line):
166 if isinstance(char, int):
167 selected = (char & SELECTED) != 0
168 char &= MASK
169 char = self.MATRIX_TO_CURSES[char]
170 if selected:
171 char |= curses.A_REVERSE
172 self.panel.insch(y, x, char)
173 else:
174 char = char.encode('UTF-8')
175 self.panel.insstr(y, x, char)
177 def _run_curses(win, options, args):
178 app = _CursesApp(win, options, args)
179 app.run()
181 def run(options, args):
182 locale.setlocale(locale.LC_ALL, '')
183 curses.wrapper(_run_curses, options, args)
185 def is_available():
186 return sys.stdin.isatty() and sys.stdout.isatty()