1 # This program is free software; you can redistribute it and/or modify
2 # it under the terms of the GNU General Public License as published by
3 # the Free Software Foundation; either version 2 of the License, or
4 # (at your option) any later version.
6 # This program is distributed in the hope that it will be useful,
7 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # GNU Library General Public License for more details.
11 # You should have received a copy of the GNU General Public License
12 # along with this program; if not, write to the Free Software
13 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
15 # See the COPYING file for license information.
17 # Copyright (c) 2006, 2007, 2008 Guillaume Chazarain <guichaz@gmail.com>
24 from pysize
.ui
.utils
import human_unit
, update_progress
25 from pysize
.ui
.utils
import sanitize_string
, UINotAvailableException
26 from pysize
.ui
.char_matrix
import CharMatrix
, MASK
, SELECTED
27 from pysize
.core
.pysize_fs_tree
import pysize_tree
28 from pysize
.core
.pysize_fs_node
import pretty_paths
29 from pysize
.core
.compute_size
import size_observable
30 from pysize
.core
.signals
import install_sigquit_handler
32 class _CursesApp(object):
34 def __init__(self
, win
, options
, args
):
36 self
.options
= options
37 self
.max_depth
= options
.max_depth
39 size_observable
.add_observer(self
.draw_star
)
43 self
.last_star_time
= time
.time()
46 progress
= update_progress()
48 self
.status_win
.insch(0, self
.width
- 1, progress
, curses
.A_REVERSE
)
49 self
.status_win
.refresh()
51 def _init_curses(self
):
52 curses
.use_default_colors()
57 # This call can fail with misconfigured terminals, for example
58 # TERM=xterm-color. This is harmless
62 return sum(map(lambda n
: 1<<n
, positions
))
64 self
.MATRIX_TO_CURSES
= {
66 dots(3, 4, 5): curses
.ACS_HLINE
,
67 dots(1, 4, 7): curses
.ACS_VLINE
,
68 dots(1, 3, 4, 5, 7): curses
.ACS_PLUS
,
69 dots(4, 5, 7): curses
.ACS_ULCORNER
,
70 dots(3, 4, 7): curses
.ACS_URCORNER
,
71 dots(1, 4, 5): curses
.ACS_LLCORNER
,
72 dots(1, 3, 4): curses
.ACS_LRCORNER
,
73 dots(3, 4, 5, 7): curses
.ACS_TTEE
,
74 dots(1, 3, 4, 5): curses
.ACS_BTEE
,
75 dots(1, 4, 5, 7): curses
.ACS_LTEE
,
76 dots(1, 3, 4, 7): curses
.ACS_RTEE
80 self
.status_win
= curses
.newwin(0, 0)
81 self
.panel
= curses
.newwin(0, 0)
84 print 'Cannot create windows'
88 """Called when the terminal size changes."""
89 self
.height
, self
.width
= self
.window
.getmaxyx()
90 self
.height
-= 1 # Status bar
92 self
.status_win
.resize(1, self
.width
)
93 self
.status_win
.mvwin(self
.height
, 0)
94 self
.panel
.resize(self
.height
, self
.width
)
95 self
._set
_paths
(self
.paths
)
100 self
.status_win
.erase()
101 self
.status_win
.hline(0, 0, ord(' ') | curses
.A_REVERSE
, self
.width
)
103 status_line
= '[exploring ' + pretty_paths(self
.paths
) + ']'
105 status_line
= self
.tree
.root
.get_name() + ' '
106 status_line
+= human_unit(self
.tree
.root
.size
)
108 if len(status_line
) + len(END
) > self
.width
:
109 status_line
= sanitize_string(status_line
,
110 max_length
=self
.width
)
112 status_line
= sanitize_string(status_line
+ END
)
114 self
.status_win
.insstr(status_line
.encode('UTF-8'), curses
.A_REVERSE
)
115 self
.status_win
.refresh()
118 # -1 accounts for the last horizontal line
119 self
.tree
= pysize_tree(self
.paths
, self
.max_depth
,
120 2.0 / (self
.height
- 1), self
.options
)
121 self
.need_tree
= False
122 if not self
.tree
.root
:
125 self
._draw
_matrix
(CharMatrix(self
.width
, self
.height
, self
.tree
,
135 except curses
.error
, e
:
139 def _set_paths(self
, paths
):
140 self
.selected_node
= None
141 self
.selection
= None
143 self
.need_tree
= True
145 def _next_step(self
):
146 self
._set
_paths
([self
.selected_node
.get_dirname()])
148 def _select(self
, function
):
149 if not self
.selected_node
:
150 if self
.tree
.root
.children
:
151 self
.selected_node
= self
.tree
.root
.children
[0]
153 # Forcing browsing to the left
154 self
._set
_paths
([self
.tree
.root
.get_dirname()])
156 selected
= function(self
.selected_node
)
158 if function
== self
.tree
.get_first_child
and \
159 self
.selected_node
.is_dir():
160 # Browsing to the right
163 self
.selected_node
= selected
164 if self
.selected_node
== self
.tree
.root
:
165 # Browsing to the left
166 self
._set
_paths
([self
.tree
.root
.get_dirname()])
169 if self
.selected_node
:
170 self
._set
_paths
(self
.selected_node
.get_fullpaths())
173 """The main dispatcher."""
178 curses
.KEY_RESIZE
: lambda:
181 curses
.KEY_DOWN
: lambda:
182 self
._select
(self
.tree
.get_next_sibling
),
184 curses
.KEY_UP
: lambda:
185 self
._select
(self
.tree
.get_previous_sibling
),
187 curses
.KEY_LEFT
: lambda:
188 self
._select
(self
.tree
.get_parent
),
190 curses
.KEY_RIGHT
: lambda:
191 self
._select
(self
.tree
.get_first_child
),
199 c
= self
.window
.getch()
200 action
= key_bindings
.get(c
, lambda: None)
203 def _draw_matrix(self
, matrix
):
204 for y
, line
in enumerate(matrix
.matrix
):
205 for x
, char
in enumerate(line
):
206 if isinstance(char
, int):
207 selected
= (char
& SELECTED
) != 0
209 char
= self
.MATRIX_TO_CURSES
[char
]
211 char |
= curses
.A_REVERSE
212 self
.panel
.insch(y
, x
, char
)
215 char
= char
.encode('UTF-8')
216 except UnicodeDecodeError:
218 self
.panel
.insstr(y
, x
, char
)
220 def _run_curses(win
, options
, args
):
221 install_sigquit_handler()
223 args
= args
or [os
.getcwd()]
224 app
= _CursesApp(win
, options
, args
)
226 except KeyboardInterrupt:
229 def run(options
, args
):
230 if sys
.stdin
.isatty() and sys
.stdout
.isatty():
231 curses
.wrapper(_run_curses
, options
, args
)
233 raise UINotAvailableException