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 Guillaume Chazarain <guichaz@yahoo.fr>
24 from pysize
.ui
.utils
import human_unit
, short_string
, 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
.compute_size
import size_observable
30 class _CursesApp(object):
32 def __init__(self
, win
, options
, args
):
34 self
.options
= options
35 self
.max_depth
= options
.max_depth
36 if options
.min_size
!= 'auto':
37 raise Exception, 'curses UI supports only --min-size=auto'
41 self
.last_star_time
= time
.time()
42 size_observable
.add_observer(self
.draw_star
)
45 progress
= update_progress()
47 self
.status_win
.insch(0, self
.width
- 1, progress
, curses
.A_REVERSE
)
48 self
.status_win
.refresh()
50 def _init_curses(self
):
51 curses
.use_default_colors()
56 return sum(map(lambda n
: 1<<n
, positions
))
58 self
.MATRIX_TO_CURSES
= {
60 dots(3, 4, 5): curses
.ACS_HLINE
,
61 dots(1, 4, 7): curses
.ACS_VLINE
,
62 dots(1, 3, 4, 5, 7): curses
.ACS_PLUS
,
63 dots(4, 5, 7): curses
.ACS_ULCORNER
,
64 dots(3, 4, 7): curses
.ACS_URCORNER
,
65 dots(1, 4, 5): curses
.ACS_LLCORNER
,
66 dots(1, 3, 4): curses
.ACS_LRCORNER
,
67 dots(3, 4, 5, 7): curses
.ACS_TTEE
,
68 dots(1, 3, 4, 5): curses
.ACS_BTEE
,
69 dots(1, 4, 5, 7): curses
.ACS_LTEE
,
70 dots(1, 3, 4, 7): curses
.ACS_RTEE
74 self
.status_win
= curses
.newwin(0, 0)
75 self
.panel
= curses
.newwin(0, 0)
78 print 'Cannot create windows'
82 """Called when the terminal size changes."""
83 self
.height
, self
.width
= self
.window
.getmaxyx()
84 self
.height
-= 1 # Status bar
86 self
.status_win
.resize(1, self
.width
)
87 self
.status_win
.mvwin(self
.height
, 0)
88 self
.panel
.resize(self
.height
, self
.width
)
89 self
._set
_paths
(self
.paths
)
95 # -1 accounts for the last horizontal line
96 self
.tree
= pysize_tree(self
.paths
, self
.max_depth
,
97 2.0 / (self
.height
- 1), self
.options
)
98 self
.need_tree
= False
99 if not self
.tree
.root
:
103 self
._draw
_matrix
(CharMatrix(self
.width
, self
.height
, self
.tree
,
107 self
.status_win
.erase()
108 self
.status_win
.hline(0, 0, ord(' ') | curses
.A_REVERSE
, self
.width
)
109 status_line
= sanitize_string(self
.tree
.root
.get_name()) + ' '
110 status_line
+= human_unit(self
.tree
.root
.size
)
112 if len(status_line
) + len(END
) >= self
.width
:
113 status_line
= short_string(status_line
, self
.width
- 2)
116 self
.status_win
.addstr(status_line
, curses
.A_REVERSE
)
117 self
.status_win
.refresh()
124 except curses
.error
, e
:
128 def _set_paths(self
, paths
):
129 self
.selected_node
= None
130 self
.selection
= None
132 self
.need_tree
= True
134 def _next_step(self
):
135 self
._set
_paths
([self
.selected_node
.get_dirname()])
137 def _select(self
, function
):
138 if not self
.selected_node
:
139 if self
.tree
.root
.children
:
140 self
.selected_node
= self
.tree
.root
.children
[0]
142 # Forcing browsing to the left
143 self
._set
_paths
([self
.tree
.root
.get_dirname()])
145 selected
= function(self
.selected_node
)
147 if function
== self
.tree
.get_first_child
and \
148 self
.selected_node
.is_dir():
149 # Browsing to the right
152 self
.selected_node
= selected
153 if self
.selected_node
== self
.tree
.root
:
154 # Browsing to the left
155 self
._set
_paths
([self
.tree
.root
.get_dirname()])
158 if self
.selected_node
:
159 self
._set
_paths
(self
.selected_node
.get_fullpaths())
162 """The main dispatcher."""
167 curses
.KEY_RESIZE
: lambda:
170 curses
.KEY_DOWN
: lambda:
171 self
._select
(self
.tree
.get_next_sibling
),
173 curses
.KEY_UP
: lambda:
174 self
._select
(self
.tree
.get_previous_sibling
),
176 curses
.KEY_LEFT
: lambda:
177 self
._select
(self
.tree
.get_parent
),
179 curses
.KEY_RIGHT
: lambda:
180 self
._select
(self
.tree
.get_first_child
),
188 c
= self
.window
.getch()
189 action
= key_bindings
.get(c
, lambda: None)
192 def _draw_matrix(self
, matrix
):
193 for y
, line
in enumerate(matrix
.matrix
):
194 for x
, char
in enumerate(line
):
195 if isinstance(char
, int):
196 selected
= (char
& SELECTED
) != 0
198 char
= self
.MATRIX_TO_CURSES
[char
]
200 char |
= curses
.A_REVERSE
201 self
.panel
.insch(y
, x
, char
)
204 char
= char
.encode('UTF-8')
205 except UnicodeDecodeError:
207 self
.panel
.insstr(y
, x
, char
)
209 def _run_curses(win
, options
, args
):
211 app
= _CursesApp(win
, options
, args
)
214 def run(options
, args
):
215 if sys
.stdin
.isatty() and sys
.stdout
.isatty():
216 curses
.wrapper(_run_curses
, options
, args
)
218 raise UINotAvailableException