##// END OF EJS Templates
Fix alignment default.
walter.doerwald -
Show More
@@ -1,1413 +1,1415 b''
1 # -*- coding: iso-8859-1 -*-
1 # -*- coding: iso-8859-1 -*-
2
2
3 import curses, textwrap
3 import curses, textwrap
4
4
5 import astyle, ipipe
5 import astyle, ipipe
6
6
7
7
8 _ibrowse_help = """
8 _ibrowse_help = """
9 down
9 down
10 Move the cursor to the next line.
10 Move the cursor to the next line.
11
11
12 up
12 up
13 Move the cursor to the previous line.
13 Move the cursor to the previous line.
14
14
15 pagedown
15 pagedown
16 Move the cursor down one page (minus overlap).
16 Move the cursor down one page (minus overlap).
17
17
18 pageup
18 pageup
19 Move the cursor up one page (minus overlap).
19 Move the cursor up one page (minus overlap).
20
20
21 left
21 left
22 Move the cursor left.
22 Move the cursor left.
23
23
24 right
24 right
25 Move the cursor right.
25 Move the cursor right.
26
26
27 home
27 home
28 Move the cursor to the first column.
28 Move the cursor to the first column.
29
29
30 end
30 end
31 Move the cursor to the last column.
31 Move the cursor to the last column.
32
32
33 prevattr
33 prevattr
34 Move the cursor one attribute column to the left.
34 Move the cursor one attribute column to the left.
35
35
36 nextattr
36 nextattr
37 Move the cursor one attribute column to the right.
37 Move the cursor one attribute column to the right.
38
38
39 pick
39 pick
40 'Pick' the object under the cursor (i.e. the row the cursor is on). This
40 'Pick' the object under the cursor (i.e. the row the cursor is on). This
41 leaves the browser and returns the picked object to the caller. (In IPython
41 leaves the browser and returns the picked object to the caller. (In IPython
42 this object will be available as the '_' variable.)
42 this object will be available as the '_' variable.)
43
43
44 pickattr
44 pickattr
45 'Pick' the attribute under the cursor (i.e. the row/column the cursor is on).
45 'Pick' the attribute under the cursor (i.e. the row/column the cursor is on).
46
46
47 pickallattrs
47 pickallattrs
48 Pick' the complete column under the cursor (i.e. the attribute under the
48 Pick' the complete column under the cursor (i.e. the attribute under the
49 cursor) from all currently fetched objects. These attributes will be returned
49 cursor) from all currently fetched objects. These attributes will be returned
50 as a list.
50 as a list.
51
51
52 tooglemark
52 tooglemark
53 Mark/unmark the object under the cursor. Marked objects have a '!' after the
53 Mark/unmark the object under the cursor. Marked objects have a '!' after the
54 row number).
54 row number).
55
55
56 pickmarked
56 pickmarked
57 'Pick' marked objects. Marked objects will be returned as a list.
57 'Pick' marked objects. Marked objects will be returned as a list.
58
58
59 pickmarkedattr
59 pickmarkedattr
60 'Pick' the attribute under the cursor from all marked objects (This returns a
60 'Pick' the attribute under the cursor from all marked objects (This returns a
61 list).
61 list).
62
62
63 enterdefault
63 enterdefault
64 Enter the object under the cursor. (what this mean depends on the object
64 Enter the object under the cursor. (what this mean depends on the object
65 itself (i.e. how it implements the '__xiter__' method). This opens a new
65 itself (i.e. how it implements the '__xiter__' method). This opens a new
66 browser 'level'.
66 browser 'level'.
67
67
68 enter
68 enter
69 Enter the object under the cursor. If the object provides different enter
69 Enter the object under the cursor. If the object provides different enter
70 modes a menu of all modes will be presented; choose one and enter it (via the
70 modes a menu of all modes will be presented; choose one and enter it (via the
71 'enter' or 'enterdefault' command).
71 'enter' or 'enterdefault' command).
72
72
73 enterattr
73 enterattr
74 Enter the attribute under the cursor.
74 Enter the attribute under the cursor.
75
75
76 leave
76 leave
77 Leave the current browser level and go back to the previous one.
77 Leave the current browser level and go back to the previous one.
78
78
79 detail
79 detail
80 Show a detail view of the object under the cursor. This shows the name, type,
80 Show a detail view of the object under the cursor. This shows the name, type,
81 doc string and value of the object attributes (and it might show more
81 doc string and value of the object attributes (and it might show more
82 attributes than in the list view, depending on the object).
82 attributes than in the list view, depending on the object).
83
83
84 detailattr
84 detailattr
85 Show a detail view of the attribute under the cursor.
85 Show a detail view of the attribute under the cursor.
86
86
87 markrange
87 markrange
88 Mark all objects from the last marked object before the current cursor
88 Mark all objects from the last marked object before the current cursor
89 position to the cursor position.
89 position to the cursor position.
90
90
91 sortattrasc
91 sortattrasc
92 Sort the objects (in ascending order) using the attribute under the cursor as
92 Sort the objects (in ascending order) using the attribute under the cursor as
93 the sort key.
93 the sort key.
94
94
95 sortattrdesc
95 sortattrdesc
96 Sort the objects (in descending order) using the attribute under the cursor as
96 Sort the objects (in descending order) using the attribute under the cursor as
97 the sort key.
97 the sort key.
98
98
99 goto
99 goto
100 Jump to a row. The row number can be entered at the bottom of the screen.
100 Jump to a row. The row number can be entered at the bottom of the screen.
101
101
102 find
102 find
103 Search forward for a row. At the bottom of the screen the condition can be
103 Search forward for a row. At the bottom of the screen the condition can be
104 entered.
104 entered.
105
105
106 findbackwards
106 findbackwards
107 Search backward for a row. At the bottom of the screen the condition can be
107 Search backward for a row. At the bottom of the screen the condition can be
108 entered.
108 entered.
109
109
110 help
110 help
111 This screen.
111 This screen.
112 """
112 """
113
113
114
114
115 class UnassignedKeyError(Exception):
115 class UnassignedKeyError(Exception):
116 """
116 """
117 Exception that is used for reporting unassigned keys.
117 Exception that is used for reporting unassigned keys.
118 """
118 """
119
119
120
120
121 class UnknownCommandError(Exception):
121 class UnknownCommandError(Exception):
122 """
122 """
123 Exception that is used for reporting unknown command (this should never
123 Exception that is used for reporting unknown command (this should never
124 happen).
124 happen).
125 """
125 """
126
126
127
127
128 class CommandError(Exception):
128 class CommandError(Exception):
129 """
129 """
130 Exception that is used for reporting that a command can't be executed.
130 Exception that is used for reporting that a command can't be executed.
131 """
131 """
132
132
133
133
134 class _BrowserCachedItem(object):
134 class _BrowserCachedItem(object):
135 # This is used internally by ``ibrowse`` to store a item together with its
135 # This is used internally by ``ibrowse`` to store a item together with its
136 # marked status.
136 # marked status.
137 __slots__ = ("item", "marked")
137 __slots__ = ("item", "marked")
138
138
139 def __init__(self, item):
139 def __init__(self, item):
140 self.item = item
140 self.item = item
141 self.marked = False
141 self.marked = False
142
142
143
143
144 class _BrowserHelp(object):
144 class _BrowserHelp(object):
145 style_header = astyle.Style.fromstr("red:blacK")
145 style_header = astyle.Style.fromstr("red:blacK")
146 # This is used internally by ``ibrowse`` for displaying the help screen.
146 # This is used internally by ``ibrowse`` for displaying the help screen.
147 def __init__(self, browser):
147 def __init__(self, browser):
148 self.browser = browser
148 self.browser = browser
149
149
150 def __xrepr__(self, mode):
150 def __xrepr__(self, mode):
151 yield (-1, True)
151 yield (-1, True)
152 if mode == "header" or mode == "footer":
152 if mode == "header" or mode == "footer":
153 yield (astyle.style_default, "ibrowse help screen")
153 yield (astyle.style_default, "ibrowse help screen")
154 else:
154 else:
155 yield (astyle.style_default, repr(self))
155 yield (astyle.style_default, repr(self))
156
156
157 def __xiter__(self, mode):
157 def __xiter__(self, mode):
158 # Get reverse key mapping
158 # Get reverse key mapping
159 allkeys = {}
159 allkeys = {}
160 for (key, cmd) in self.browser.keymap.iteritems():
160 for (key, cmd) in self.browser.keymap.iteritems():
161 allkeys.setdefault(cmd, []).append(key)
161 allkeys.setdefault(cmd, []).append(key)
162
162
163 fields = ("key", "description")
163 fields = ("key", "description")
164
164
165 for (i, command) in enumerate(_ibrowse_help.strip().split("\n\n")):
165 for (i, command) in enumerate(_ibrowse_help.strip().split("\n\n")):
166 if i:
166 if i:
167 yield ipipe.Fields(fields, key="", description="")
167 yield ipipe.Fields(fields, key="", description="")
168
168
169 (name, description) = command.split("\n", 1)
169 (name, description) = command.split("\n", 1)
170 keys = allkeys.get(name, [])
170 keys = allkeys.get(name, [])
171 lines = textwrap.wrap(description, 60)
171 lines = textwrap.wrap(description, 60)
172
172
173 yield ipipe.Fields(fields, description=astyle.Text((self.style_header, name)))
173 yield ipipe.Fields(fields, description=astyle.Text((self.style_header, name)))
174 for i in xrange(max(len(keys), len(lines))):
174 for i in xrange(max(len(keys), len(lines))):
175 try:
175 try:
176 key = self.browser.keylabel(keys[i])
176 key = self.browser.keylabel(keys[i])
177 except IndexError:
177 except IndexError:
178 key = ""
178 key = ""
179 try:
179 try:
180 line = lines[i]
180 line = lines[i]
181 except IndexError:
181 except IndexError:
182 line = ""
182 line = ""
183 yield ipipe.Fields(fields, key=key, description=line)
183 yield ipipe.Fields(fields, key=key, description=line)
184
184
185
185
186 class _BrowserLevel(object):
186 class _BrowserLevel(object):
187 # This is used internally to store the state (iterator, fetch items,
187 # This is used internally to store the state (iterator, fetch items,
188 # position of cursor and screen, etc.) of one browser level
188 # position of cursor and screen, etc.) of one browser level
189 # An ``ibrowse`` object keeps multiple ``_BrowserLevel`` objects in
189 # An ``ibrowse`` object keeps multiple ``_BrowserLevel`` objects in
190 # a stack.
190 # a stack.
191 def __init__(self, browser, input, iterator, mainsizey, *attrs):
191 def __init__(self, browser, input, iterator, mainsizey, *attrs):
192 self.browser = browser
192 self.browser = browser
193 self.input = input
193 self.input = input
194 self.header = [x for x in ipipe.xrepr(input, "header") if not isinstance(x[0], int)]
194 self.header = [x for x in ipipe.xrepr(input, "header") if not isinstance(x[0], int)]
195 # iterator for the input
195 # iterator for the input
196 self.iterator = iterator
196 self.iterator = iterator
197
197
198 # is the iterator exhausted?
198 # is the iterator exhausted?
199 self.exhausted = False
199 self.exhausted = False
200
200
201 # attributes to be display (autodetected if empty)
201 # attributes to be display (autodetected if empty)
202 self.attrs = attrs
202 self.attrs = attrs
203
203
204 # fetched items (+ marked flag)
204 # fetched items (+ marked flag)
205 self.items = ipipe.deque()
205 self.items = ipipe.deque()
206
206
207 # Number of marked objects
207 # Number of marked objects
208 self.marked = 0
208 self.marked = 0
209
209
210 # Vertical cursor position
210 # Vertical cursor position
211 self.cury = 0
211 self.cury = 0
212
212
213 # Horizontal cursor position
213 # Horizontal cursor position
214 self.curx = 0
214 self.curx = 0
215
215
216 # Index of first data column
216 # Index of first data column
217 self.datastartx = 0
217 self.datastartx = 0
218
218
219 # Index of first data line
219 # Index of first data line
220 self.datastarty = 0
220 self.datastarty = 0
221
221
222 # height of the data display area
222 # height of the data display area
223 self.mainsizey = mainsizey
223 self.mainsizey = mainsizey
224
224
225 # width of the data display area (changes when scrolling)
225 # width of the data display area (changes when scrolling)
226 self.mainsizex = 0
226 self.mainsizex = 0
227
227
228 # Size of row number (changes when scrolling)
228 # Size of row number (changes when scrolling)
229 self.numbersizex = 0
229 self.numbersizex = 0
230
230
231 # Attribute names to display (in this order)
231 # Attribute names to display (in this order)
232 self.displayattrs = []
232 self.displayattrs = []
233
233
234 # index and name of attribute under the cursor
234 # index and name of attribute under the cursor
235 self.displayattr = (None, ipipe.noitem)
235 self.displayattr = (None, ipipe.noitem)
236
236
237 # Maps attribute names to column widths
237 # Maps attribute names to column widths
238 self.colwidths = {}
238 self.colwidths = {}
239
239
240 self.fetch(mainsizey)
240 self.fetch(mainsizey)
241 self.calcdisplayattrs()
241 self.calcdisplayattrs()
242 # formatted attributes for the items on screen
242 # formatted attributes for the items on screen
243 # (i.e. self.items[self.datastarty:self.datastarty+self.mainsizey])
243 # (i.e. self.items[self.datastarty:self.datastarty+self.mainsizey])
244 self.displayrows = [self.getrow(i) for i in xrange(len(self.items))]
244 self.displayrows = [self.getrow(i) for i in xrange(len(self.items))]
245 self.calcwidths()
245 self.calcwidths()
246 self.calcdisplayattr()
246 self.calcdisplayattr()
247
247
248 def fetch(self, count):
248 def fetch(self, count):
249 # Try to fill ``self.items`` with at least ``count`` objects.
249 # Try to fill ``self.items`` with at least ``count`` objects.
250 have = len(self.items)
250 have = len(self.items)
251 while not self.exhausted and have < count:
251 while not self.exhausted and have < count:
252 try:
252 try:
253 item = self.iterator.next()
253 item = self.iterator.next()
254 except StopIteration:
254 except StopIteration:
255 self.exhausted = True
255 self.exhausted = True
256 break
256 break
257 else:
257 else:
258 have += 1
258 have += 1
259 self.items.append(_BrowserCachedItem(item))
259 self.items.append(_BrowserCachedItem(item))
260
260
261 def calcdisplayattrs(self):
261 def calcdisplayattrs(self):
262 # Calculate which attributes are available from the objects that are
262 # Calculate which attributes are available from the objects that are
263 # currently visible on screen (and store it in ``self.displayattrs``)
263 # currently visible on screen (and store it in ``self.displayattrs``)
264 attrnames = set()
264 attrnames = set()
265 # If the browser object specifies a fixed list of attributes,
265 # If the browser object specifies a fixed list of attributes,
266 # simply use it.
266 # simply use it.
267 if self.attrs:
267 if self.attrs:
268 self.displayattrs = self.attrs
268 self.displayattrs = self.attrs
269 else:
269 else:
270 self.displayattrs = []
270 self.displayattrs = []
271 endy = min(self.datastarty+self.mainsizey, len(self.items))
271 endy = min(self.datastarty+self.mainsizey, len(self.items))
272 for i in xrange(self.datastarty, endy):
272 for i in xrange(self.datastarty, endy):
273 for attrname in ipipe.xattrs(self.items[i].item, "default"):
273 for attrname in ipipe.xattrs(self.items[i].item, "default"):
274 if attrname not in attrnames:
274 if attrname not in attrnames:
275 self.displayattrs.append(attrname)
275 self.displayattrs.append(attrname)
276 attrnames.add(attrname)
276 attrnames.add(attrname)
277
277
278 def getrow(self, i):
278 def getrow(self, i):
279 # Return a dictinary with the attributes for the object
279 # Return a dictinary with the attributes for the object
280 # ``self.items[i]``. Attribute names are taken from
280 # ``self.items[i]``. Attribute names are taken from
281 # ``self.displayattrs`` so ``calcdisplayattrs()`` must have been
281 # ``self.displayattrs`` so ``calcdisplayattrs()`` must have been
282 # called before.
282 # called before.
283 row = {}
283 row = {}
284 item = self.items[i].item
284 item = self.items[i].item
285 for attrname in self.displayattrs:
285 for attrname in self.displayattrs:
286 try:
286 try:
287 value = ipipe._getattr(item, attrname, ipipe.noitem)
287 value = ipipe._getattr(item, attrname, ipipe.noitem)
288 except (KeyboardInterrupt, SystemExit):
288 except (KeyboardInterrupt, SystemExit):
289 raise
289 raise
290 except Exception, exc:
290 except Exception, exc:
291 value = exc
291 value = exc
292 # only store attribute if it exists (or we got an exception)
292 # only store attribute if it exists (or we got an exception)
293 if value is not ipipe.noitem:
293 if value is not ipipe.noitem:
294 parts = []
294 parts = []
295 totallength = 0
295 totallength = 0
296 align = None
296 align = None
297 full = False
297 full = True
298 # Collect parts until we have enough
298 # Collect parts until we have enough
299 for part in ipipe.xrepr(value, "cell"):
299 for part in ipipe.xrepr(value, "cell"):
300 # part gives (alignment, stop)
300 # part gives (alignment, stop)
301 # instead of (style, text)
301 # instead of (style, text)
302 if isinstance(part[0], int):
302 if isinstance(part[0], int):
303 # only consider the first occurence
303 # only consider the first occurence
304 if align is None:
304 if align is None:
305 align = part[0]
305 align = part[0]
306 full = part[1]
306 full = part[1]
307 else:
307 else:
308 parts.append(part)
308 parts.append(part)
309 totallength += len(part[1])
309 totallength += len(part[1])
310 if totallength >= self.browser.maxattrlength and not full:
310 if totallength >= self.browser.maxattrlength and not full:
311 parts.append((astyle.style_ellisis, "..."))
311 parts.append((astyle.style_ellisis, "..."))
312 totallength += 3
312 totallength += 3
313 break
313 break
314 if align is None:
315 align = -1
314 # remember alignment, length and colored parts
316 # remember alignment, length and colored parts
315 row[attrname] = (align, totallength, parts)
317 row[attrname] = (align, totallength, parts)
316 return row
318 return row
317
319
318 def calcwidths(self):
320 def calcwidths(self):
319 # Recalculate the displayed fields and their width.
321 # Recalculate the displayed fields and their width.
320 # ``calcdisplayattrs()'' must have been called and the cache
322 # ``calcdisplayattrs()'' must have been called and the cache
321 # for attributes of the objects on screen (``self.displayrows``)
323 # for attributes of the objects on screen (``self.displayrows``)
322 # must have been filled. This returns a dictionary mapping
324 # must have been filled. This returns a dictionary mapping
323 # colmn names to width.
325 # colmn names to width.
324 self.colwidths = {}
326 self.colwidths = {}
325 for row in self.displayrows:
327 for row in self.displayrows:
326 for attrname in self.displayattrs:
328 for attrname in self.displayattrs:
327 try:
329 try:
328 length = row[attrname][1]
330 length = row[attrname][1]
329 except KeyError:
331 except KeyError:
330 length = 0
332 length = 0
331 # always add attribute to colwidths, even if it doesn't exist
333 # always add attribute to colwidths, even if it doesn't exist
332 if attrname not in self.colwidths:
334 if attrname not in self.colwidths:
333 self.colwidths[attrname] = len(ipipe._attrname(attrname))
335 self.colwidths[attrname] = len(ipipe._attrname(attrname))
334 newwidth = max(self.colwidths[attrname], length)
336 newwidth = max(self.colwidths[attrname], length)
335 self.colwidths[attrname] = newwidth
337 self.colwidths[attrname] = newwidth
336
338
337 # How many characters do we need to paint the item number?
339 # How many characters do we need to paint the item number?
338 self.numbersizex = len(str(self.datastarty+self.mainsizey-1))
340 self.numbersizex = len(str(self.datastarty+self.mainsizey-1))
339 # How must space have we got to display data?
341 # How must space have we got to display data?
340 self.mainsizex = self.browser.scrsizex-self.numbersizex-3
342 self.mainsizex = self.browser.scrsizex-self.numbersizex-3
341 # width of all columns
343 # width of all columns
342 self.datasizex = sum(self.colwidths.itervalues()) + len(self.colwidths)
344 self.datasizex = sum(self.colwidths.itervalues()) + len(self.colwidths)
343
345
344 def calcdisplayattr(self):
346 def calcdisplayattr(self):
345 # Find out on which attribute the cursor is on and store this
347 # Find out on which attribute the cursor is on and store this
346 # information in ``self.displayattr``.
348 # information in ``self.displayattr``.
347 pos = 0
349 pos = 0
348 for (i, attrname) in enumerate(self.displayattrs):
350 for (i, attrname) in enumerate(self.displayattrs):
349 if pos+self.colwidths[attrname] >= self.curx:
351 if pos+self.colwidths[attrname] >= self.curx:
350 self.displayattr = (i, attrname)
352 self.displayattr = (i, attrname)
351 break
353 break
352 pos += self.colwidths[attrname]+1
354 pos += self.colwidths[attrname]+1
353 else:
355 else:
354 self.displayattr = (None, ipipe.noitem)
356 self.displayattr = (None, ipipe.noitem)
355
357
356 def moveto(self, x, y, refresh=False):
358 def moveto(self, x, y, refresh=False):
357 # Move the cursor to the position ``(x,y)`` (in data coordinates,
359 # Move the cursor to the position ``(x,y)`` (in data coordinates,
358 # not in screen coordinates). If ``refresh`` is true, all cached
360 # not in screen coordinates). If ``refresh`` is true, all cached
359 # values will be recalculated (e.g. because the list has been
361 # values will be recalculated (e.g. because the list has been
360 # resorted, so screen positions etc. are no longer valid).
362 # resorted, so screen positions etc. are no longer valid).
361 olddatastarty = self.datastarty
363 olddatastarty = self.datastarty
362 oldx = self.curx
364 oldx = self.curx
363 oldy = self.cury
365 oldy = self.cury
364 x = int(x+0.5)
366 x = int(x+0.5)
365 y = int(y+0.5)
367 y = int(y+0.5)
366 newx = x # remember where we wanted to move
368 newx = x # remember where we wanted to move
367 newy = y # remember where we wanted to move
369 newy = y # remember where we wanted to move
368
370
369 scrollbordery = min(self.browser.scrollbordery, self.mainsizey//2)
371 scrollbordery = min(self.browser.scrollbordery, self.mainsizey//2)
370 scrollborderx = min(self.browser.scrollborderx, self.mainsizex//2)
372 scrollborderx = min(self.browser.scrollborderx, self.mainsizex//2)
371
373
372 # Make sure that the cursor didn't leave the main area vertically
374 # Make sure that the cursor didn't leave the main area vertically
373 if y < 0:
375 if y < 0:
374 y = 0
376 y = 0
375 self.fetch(y+scrollbordery+1) # try to get more items
377 self.fetch(y+scrollbordery+1) # try to get more items
376 if y >= len(self.items):
378 if y >= len(self.items):
377 y = max(0, len(self.items)-1)
379 y = max(0, len(self.items)-1)
378
380
379 # Make sure that the cursor stays on screen vertically
381 # Make sure that the cursor stays on screen vertically
380 if y < self.datastarty+scrollbordery:
382 if y < self.datastarty+scrollbordery:
381 self.datastarty = max(0, y-scrollbordery)
383 self.datastarty = max(0, y-scrollbordery)
382 elif y >= self.datastarty+self.mainsizey-scrollbordery:
384 elif y >= self.datastarty+self.mainsizey-scrollbordery:
383 self.datastarty = max(0, min(y-self.mainsizey+scrollbordery+1,
385 self.datastarty = max(0, min(y-self.mainsizey+scrollbordery+1,
384 len(self.items)-self.mainsizey))
386 len(self.items)-self.mainsizey))
385
387
386 if refresh: # Do we need to refresh the complete display?
388 if refresh: # Do we need to refresh the complete display?
387 self.calcdisplayattrs()
389 self.calcdisplayattrs()
388 endy = min(self.datastarty+self.mainsizey, len(self.items))
390 endy = min(self.datastarty+self.mainsizey, len(self.items))
389 self.displayrows = map(self.getrow, xrange(self.datastarty, endy))
391 self.displayrows = map(self.getrow, xrange(self.datastarty, endy))
390 self.calcwidths()
392 self.calcwidths()
391 # Did we scroll vertically => update displayrows
393 # Did we scroll vertically => update displayrows
392 # and various other attributes
394 # and various other attributes
393 elif self.datastarty != olddatastarty:
395 elif self.datastarty != olddatastarty:
394 # Recalculate which attributes we have to display
396 # Recalculate which attributes we have to display
395 olddisplayattrs = self.displayattrs
397 olddisplayattrs = self.displayattrs
396 self.calcdisplayattrs()
398 self.calcdisplayattrs()
397 # If there are new attributes, recreate the cache
399 # If there are new attributes, recreate the cache
398 if self.displayattrs != olddisplayattrs:
400 if self.displayattrs != olddisplayattrs:
399 endy = min(self.datastarty+self.mainsizey, len(self.items))
401 endy = min(self.datastarty+self.mainsizey, len(self.items))
400 self.displayrows = map(self.getrow, xrange(self.datastarty, endy))
402 self.displayrows = map(self.getrow, xrange(self.datastarty, endy))
401 elif self.datastarty<olddatastarty: # we did scroll up
403 elif self.datastarty<olddatastarty: # we did scroll up
402 # drop rows from the end
404 # drop rows from the end
403 del self.displayrows[self.datastarty-olddatastarty:]
405 del self.displayrows[self.datastarty-olddatastarty:]
404 # fetch new items
406 # fetch new items
405 for i in xrange(olddatastarty-1,
407 for i in xrange(olddatastarty-1,
406 self.datastarty-1, -1):
408 self.datastarty-1, -1):
407 try:
409 try:
408 row = self.getrow(i)
410 row = self.getrow(i)
409 except IndexError:
411 except IndexError:
410 # we didn't have enough objects to fill the screen
412 # we didn't have enough objects to fill the screen
411 break
413 break
412 self.displayrows.insert(0, row)
414 self.displayrows.insert(0, row)
413 else: # we did scroll down
415 else: # we did scroll down
414 # drop rows from the start
416 # drop rows from the start
415 del self.displayrows[:self.datastarty-olddatastarty]
417 del self.displayrows[:self.datastarty-olddatastarty]
416 # fetch new items
418 # fetch new items
417 for i in xrange(olddatastarty+self.mainsizey,
419 for i in xrange(olddatastarty+self.mainsizey,
418 self.datastarty+self.mainsizey):
420 self.datastarty+self.mainsizey):
419 try:
421 try:
420 row = self.getrow(i)
422 row = self.getrow(i)
421 except IndexError:
423 except IndexError:
422 # we didn't have enough objects to fill the screen
424 # we didn't have enough objects to fill the screen
423 break
425 break
424 self.displayrows.append(row)
426 self.displayrows.append(row)
425 self.calcwidths()
427 self.calcwidths()
426
428
427 # Make sure that the cursor didn't leave the data area horizontally
429 # Make sure that the cursor didn't leave the data area horizontally
428 if x < 0:
430 if x < 0:
429 x = 0
431 x = 0
430 elif x >= self.datasizex:
432 elif x >= self.datasizex:
431 x = max(0, self.datasizex-1)
433 x = max(0, self.datasizex-1)
432
434
433 # Make sure that the cursor stays on screen horizontally
435 # Make sure that the cursor stays on screen horizontally
434 if x < self.datastartx+scrollborderx:
436 if x < self.datastartx+scrollborderx:
435 self.datastartx = max(0, x-scrollborderx)
437 self.datastartx = max(0, x-scrollborderx)
436 elif x >= self.datastartx+self.mainsizex-scrollborderx:
438 elif x >= self.datastartx+self.mainsizex-scrollborderx:
437 self.datastartx = max(0, min(x-self.mainsizex+scrollborderx+1,
439 self.datastartx = max(0, min(x-self.mainsizex+scrollborderx+1,
438 self.datasizex-self.mainsizex))
440 self.datasizex-self.mainsizex))
439
441
440 if x == oldx and y == oldy and (x != newx or y != newy): # couldn't move
442 if x == oldx and y == oldy and (x != newx or y != newy): # couldn't move
441 self.browser.beep()
443 self.browser.beep()
442 else:
444 else:
443 self.curx = x
445 self.curx = x
444 self.cury = y
446 self.cury = y
445 self.calcdisplayattr()
447 self.calcdisplayattr()
446
448
447 def sort(self, key, reverse=False):
449 def sort(self, key, reverse=False):
448 """
450 """
449 Sort the currently list of items using the key function ``key``. If
451 Sort the currently list of items using the key function ``key``. If
450 ``reverse`` is true the sort order is reversed.
452 ``reverse`` is true the sort order is reversed.
451 """
453 """
452 curitem = self.items[self.cury] # Remember where the cursor is now
454 curitem = self.items[self.cury] # Remember where the cursor is now
453
455
454 # Sort items
456 # Sort items
455 def realkey(item):
457 def realkey(item):
456 return key(item.item)
458 return key(item.item)
457 self.items = ipipe.deque(sorted(self.items, key=realkey, reverse=reverse))
459 self.items = ipipe.deque(sorted(self.items, key=realkey, reverse=reverse))
458
460
459 # Find out where the object under the cursor went
461 # Find out where the object under the cursor went
460 cury = self.cury
462 cury = self.cury
461 for (i, item) in enumerate(self.items):
463 for (i, item) in enumerate(self.items):
462 if item is curitem:
464 if item is curitem:
463 cury = i
465 cury = i
464 break
466 break
465
467
466 self.moveto(self.curx, cury, refresh=True)
468 self.moveto(self.curx, cury, refresh=True)
467
469
468
470
469 class ibrowse(ipipe.Display):
471 class ibrowse(ipipe.Display):
470 # Show this many lines from the previous screen when paging horizontally
472 # Show this many lines from the previous screen when paging horizontally
471 pageoverlapx = 1
473 pageoverlapx = 1
472
474
473 # Show this many lines from the previous screen when paging vertically
475 # Show this many lines from the previous screen when paging vertically
474 pageoverlapy = 1
476 pageoverlapy = 1
475
477
476 # Start scrolling when the cursor is less than this number of columns
478 # Start scrolling when the cursor is less than this number of columns
477 # away from the left or right screen edge
479 # away from the left or right screen edge
478 scrollborderx = 10
480 scrollborderx = 10
479
481
480 # Start scrolling when the cursor is less than this number of lines
482 # Start scrolling when the cursor is less than this number of lines
481 # away from the top or bottom screen edge
483 # away from the top or bottom screen edge
482 scrollbordery = 5
484 scrollbordery = 5
483
485
484 # Accelerate by this factor when scrolling horizontally
486 # Accelerate by this factor when scrolling horizontally
485 acceleratex = 1.05
487 acceleratex = 1.05
486
488
487 # Accelerate by this factor when scrolling vertically
489 # Accelerate by this factor when scrolling vertically
488 acceleratey = 1.05
490 acceleratey = 1.05
489
491
490 # The maximum horizontal scroll speed
492 # The maximum horizontal scroll speed
491 # (as a factor of the screen width (i.e. 0.5 == half a screen width)
493 # (as a factor of the screen width (i.e. 0.5 == half a screen width)
492 maxspeedx = 0.5
494 maxspeedx = 0.5
493
495
494 # The maximum vertical scroll speed
496 # The maximum vertical scroll speed
495 # (as a factor of the screen height (i.e. 0.5 == half a screen height)
497 # (as a factor of the screen height (i.e. 0.5 == half a screen height)
496 maxspeedy = 0.5
498 maxspeedy = 0.5
497
499
498 # The maximum number of header lines for browser level
500 # The maximum number of header lines for browser level
499 # if the nesting is deeper, only the innermost levels are displayed
501 # if the nesting is deeper, only the innermost levels are displayed
500 maxheaders = 5
502 maxheaders = 5
501
503
502 # The approximate maximum length of a column entry
504 # The approximate maximum length of a column entry
503 maxattrlength = 200
505 maxattrlength = 200
504
506
505 # Styles for various parts of the GUI
507 # Styles for various parts of the GUI
506 style_objheadertext = astyle.Style.fromstr("white:black:bold|reverse")
508 style_objheadertext = astyle.Style.fromstr("white:black:bold|reverse")
507 style_objheadernumber = astyle.Style.fromstr("white:blue:bold|reverse")
509 style_objheadernumber = astyle.Style.fromstr("white:blue:bold|reverse")
508 style_objheaderobject = astyle.Style.fromstr("white:black:reverse")
510 style_objheaderobject = astyle.Style.fromstr("white:black:reverse")
509 style_colheader = astyle.Style.fromstr("blue:white:reverse")
511 style_colheader = astyle.Style.fromstr("blue:white:reverse")
510 style_colheaderhere = astyle.Style.fromstr("green:black:bold|reverse")
512 style_colheaderhere = astyle.Style.fromstr("green:black:bold|reverse")
511 style_colheadersep = astyle.Style.fromstr("blue:black:reverse")
513 style_colheadersep = astyle.Style.fromstr("blue:black:reverse")
512 style_number = astyle.Style.fromstr("blue:white:reverse")
514 style_number = astyle.Style.fromstr("blue:white:reverse")
513 style_numberhere = astyle.Style.fromstr("green:black:bold|reverse")
515 style_numberhere = astyle.Style.fromstr("green:black:bold|reverse")
514 style_sep = astyle.Style.fromstr("blue:black")
516 style_sep = astyle.Style.fromstr("blue:black")
515 style_data = astyle.Style.fromstr("white:black")
517 style_data = astyle.Style.fromstr("white:black")
516 style_datapad = astyle.Style.fromstr("blue:black:bold")
518 style_datapad = astyle.Style.fromstr("blue:black:bold")
517 style_footer = astyle.Style.fromstr("black:white")
519 style_footer = astyle.Style.fromstr("black:white")
518 style_report = astyle.Style.fromstr("white:black")
520 style_report = astyle.Style.fromstr("white:black")
519
521
520 # Column separator in header
522 # Column separator in header
521 headersepchar = "|"
523 headersepchar = "|"
522
524
523 # Character for padding data cell entries
525 # Character for padding data cell entries
524 datapadchar = "."
526 datapadchar = "."
525
527
526 # Column separator in data area
528 # Column separator in data area
527 datasepchar = "|"
529 datasepchar = "|"
528
530
529 # Character to use for "empty" cell (i.e. for non-existing attributes)
531 # Character to use for "empty" cell (i.e. for non-existing attributes)
530 nodatachar = "-"
532 nodatachar = "-"
531
533
532 # Prompts for modes that require keyboard input
534 # Prompts for modes that require keyboard input
533 prompts = {
535 prompts = {
534 "goto": "goto object #: ",
536 "goto": "goto object #: ",
535 "find": "find expression: ",
537 "find": "find expression: ",
536 "findbackwards": "find backwards expression: "
538 "findbackwards": "find backwards expression: "
537 }
539 }
538
540
539 # Maps curses key codes to "function" names
541 # Maps curses key codes to "function" names
540 keymap = {
542 keymap = {
541 ord("q"): "quit",
543 ord("q"): "quit",
542 curses.KEY_UP: "up",
544 curses.KEY_UP: "up",
543 curses.KEY_DOWN: "down",
545 curses.KEY_DOWN: "down",
544 curses.KEY_PPAGE: "pageup",
546 curses.KEY_PPAGE: "pageup",
545 curses.KEY_NPAGE: "pagedown",
547 curses.KEY_NPAGE: "pagedown",
546 curses.KEY_LEFT: "left",
548 curses.KEY_LEFT: "left",
547 curses.KEY_RIGHT: "right",
549 curses.KEY_RIGHT: "right",
548 curses.KEY_HOME: "home",
550 curses.KEY_HOME: "home",
549 curses.KEY_END: "end",
551 curses.KEY_END: "end",
550 ord("<"): "prevattr",
552 ord("<"): "prevattr",
551 0x1b: "prevattr", # SHIFT-TAB
553 0x1b: "prevattr", # SHIFT-TAB
552 ord(">"): "nextattr",
554 ord(">"): "nextattr",
553 ord("\t"):"nextattr", # TAB
555 ord("\t"):"nextattr", # TAB
554 ord("p"): "pick",
556 ord("p"): "pick",
555 ord("P"): "pickattr",
557 ord("P"): "pickattr",
556 ord("C"): "pickallattrs",
558 ord("C"): "pickallattrs",
557 ord("m"): "pickmarked",
559 ord("m"): "pickmarked",
558 ord("M"): "pickmarkedattr",
560 ord("M"): "pickmarkedattr",
559 ord("\n"): "enterdefault",
561 ord("\n"): "enterdefault",
560 # FIXME: What's happening here?
562 # FIXME: What's happening here?
561 8: "leave",
563 8: "leave",
562 127: "leave",
564 127: "leave",
563 curses.KEY_BACKSPACE: "leave",
565 curses.KEY_BACKSPACE: "leave",
564 ord("x"): "leave",
566 ord("x"): "leave",
565 ord("h"): "help",
567 ord("h"): "help",
566 ord("e"): "enter",
568 ord("e"): "enter",
567 ord("E"): "enterattr",
569 ord("E"): "enterattr",
568 ord("d"): "detail",
570 ord("d"): "detail",
569 ord("D"): "detailattr",
571 ord("D"): "detailattr",
570 ord(" "): "tooglemark",
572 ord(" "): "tooglemark",
571 ord("r"): "markrange",
573 ord("r"): "markrange",
572 ord("v"): "sortattrasc",
574 ord("v"): "sortattrasc",
573 ord("V"): "sortattrdesc",
575 ord("V"): "sortattrdesc",
574 ord("g"): "goto",
576 ord("g"): "goto",
575 ord("f"): "find",
577 ord("f"): "find",
576 ord("b"): "findbackwards",
578 ord("b"): "findbackwards",
577 }
579 }
578
580
579 def __init__(self, *attrs):
581 def __init__(self, *attrs):
580 """
582 """
581 Create a new browser. If ``attrs`` is not empty, it is the list
583 Create a new browser. If ``attrs`` is not empty, it is the list
582 of attributes that will be displayed in the browser, otherwise
584 of attributes that will be displayed in the browser, otherwise
583 these will be determined by the objects on screen.
585 these will be determined by the objects on screen.
584 """
586 """
585 self.attrs = attrs
587 self.attrs = attrs
586
588
587 # Stack of browser levels
589 # Stack of browser levels
588 self.levels = []
590 self.levels = []
589 # how many colums to scroll (Changes when accelerating)
591 # how many colums to scroll (Changes when accelerating)
590 self.stepx = 1.
592 self.stepx = 1.
591
593
592 # how many rows to scroll (Changes when accelerating)
594 # how many rows to scroll (Changes when accelerating)
593 self.stepy = 1.
595 self.stepy = 1.
594
596
595 # Beep on the edges of the data area? (Will be set to ``False``
597 # Beep on the edges of the data area? (Will be set to ``False``
596 # once the cursor hits the edge of the screen, so we don't get
598 # once the cursor hits the edge of the screen, so we don't get
597 # multiple beeps).
599 # multiple beeps).
598 self._dobeep = True
600 self._dobeep = True
599
601
600 # Cache for registered ``curses`` colors and styles.
602 # Cache for registered ``curses`` colors and styles.
601 self._styles = {}
603 self._styles = {}
602 self._colors = {}
604 self._colors = {}
603 self._maxcolor = 1
605 self._maxcolor = 1
604
606
605 # How many header lines do we want to paint (the numbers of levels
607 # How many header lines do we want to paint (the numbers of levels
606 # we have, but with an upper bound)
608 # we have, but with an upper bound)
607 self._headerlines = 1
609 self._headerlines = 1
608
610
609 # Index of first header line
611 # Index of first header line
610 self._firstheaderline = 0
612 self._firstheaderline = 0
611
613
612 # curses window
614 # curses window
613 self.scr = None
615 self.scr = None
614 # report in the footer line (error, executed command etc.)
616 # report in the footer line (error, executed command etc.)
615 self._report = None
617 self._report = None
616
618
617 # value to be returned to the caller (set by commands)
619 # value to be returned to the caller (set by commands)
618 self.returnvalue = None
620 self.returnvalue = None
619
621
620 # The mode the browser is in
622 # The mode the browser is in
621 # e.g. normal browsing or entering an argument for a command
623 # e.g. normal browsing or entering an argument for a command
622 self.mode = "default"
624 self.mode = "default"
623
625
624 # The partially entered row number for the goto command
626 # The partially entered row number for the goto command
625 self.goto = ""
627 self.goto = ""
626
628
627 def nextstepx(self, step):
629 def nextstepx(self, step):
628 """
630 """
629 Accelerate horizontally.
631 Accelerate horizontally.
630 """
632 """
631 return max(1., min(step*self.acceleratex,
633 return max(1., min(step*self.acceleratex,
632 self.maxspeedx*self.levels[-1].mainsizex))
634 self.maxspeedx*self.levels[-1].mainsizex))
633
635
634 def nextstepy(self, step):
636 def nextstepy(self, step):
635 """
637 """
636 Accelerate vertically.
638 Accelerate vertically.
637 """
639 """
638 return max(1., min(step*self.acceleratey,
640 return max(1., min(step*self.acceleratey,
639 self.maxspeedy*self.levels[-1].mainsizey))
641 self.maxspeedy*self.levels[-1].mainsizey))
640
642
641 def getstyle(self, style):
643 def getstyle(self, style):
642 """
644 """
643 Register the ``style`` with ``curses`` or get it from the cache,
645 Register the ``style`` with ``curses`` or get it from the cache,
644 if it has been registered before.
646 if it has been registered before.
645 """
647 """
646 try:
648 try:
647 return self._styles[style.fg, style.bg, style.attrs]
649 return self._styles[style.fg, style.bg, style.attrs]
648 except KeyError:
650 except KeyError:
649 attrs = 0
651 attrs = 0
650 for b in astyle.A2CURSES:
652 for b in astyle.A2CURSES:
651 if style.attrs & b:
653 if style.attrs & b:
652 attrs |= astyle.A2CURSES[b]
654 attrs |= astyle.A2CURSES[b]
653 try:
655 try:
654 color = self._colors[style.fg, style.bg]
656 color = self._colors[style.fg, style.bg]
655 except KeyError:
657 except KeyError:
656 curses.init_pair(
658 curses.init_pair(
657 self._maxcolor,
659 self._maxcolor,
658 astyle.COLOR2CURSES[style.fg],
660 astyle.COLOR2CURSES[style.fg],
659 astyle.COLOR2CURSES[style.bg]
661 astyle.COLOR2CURSES[style.bg]
660 )
662 )
661 color = curses.color_pair(self._maxcolor)
663 color = curses.color_pair(self._maxcolor)
662 self._colors[style.fg, style.bg] = color
664 self._colors[style.fg, style.bg] = color
663 self._maxcolor += 1
665 self._maxcolor += 1
664 c = color | attrs
666 c = color | attrs
665 self._styles[style.fg, style.bg, style.attrs] = c
667 self._styles[style.fg, style.bg, style.attrs] = c
666 return c
668 return c
667
669
668 def addstr(self, y, x, begx, endx, text, style):
670 def addstr(self, y, x, begx, endx, text, style):
669 """
671 """
670 A version of ``curses.addstr()`` that can handle ``x`` coordinates
672 A version of ``curses.addstr()`` that can handle ``x`` coordinates
671 that are outside the screen.
673 that are outside the screen.
672 """
674 """
673 text2 = text[max(0, begx-x):max(0, endx-x)]
675 text2 = text[max(0, begx-x):max(0, endx-x)]
674 if text2:
676 if text2:
675 self.scr.addstr(y, max(x, begx), text2, self.getstyle(style))
677 self.scr.addstr(y, max(x, begx), text2, self.getstyle(style))
676 return len(text)
678 return len(text)
677
679
678 def addchr(self, y, x, begx, endx, c, l, style):
680 def addchr(self, y, x, begx, endx, c, l, style):
679 x0 = max(x, begx)
681 x0 = max(x, begx)
680 x1 = min(x+l, endx)
682 x1 = min(x+l, endx)
681 if x1>x0:
683 if x1>x0:
682 self.scr.addstr(y, x0, c*(x1-x0), self.getstyle(style))
684 self.scr.addstr(y, x0, c*(x1-x0), self.getstyle(style))
683 return l
685 return l
684
686
685 def _calcheaderlines(self, levels):
687 def _calcheaderlines(self, levels):
686 # Calculate how many headerlines do we have to display, if we have
688 # Calculate how many headerlines do we have to display, if we have
687 # ``levels`` browser levels
689 # ``levels`` browser levels
688 if levels is None:
690 if levels is None:
689 levels = len(self.levels)
691 levels = len(self.levels)
690 self._headerlines = min(self.maxheaders, levels)
692 self._headerlines = min(self.maxheaders, levels)
691 self._firstheaderline = levels-self._headerlines
693 self._firstheaderline = levels-self._headerlines
692
694
693 def getstylehere(self, style):
695 def getstylehere(self, style):
694 """
696 """
695 Return a style for displaying the original style ``style``
697 Return a style for displaying the original style ``style``
696 in the row the cursor is on.
698 in the row the cursor is on.
697 """
699 """
698 return astyle.Style(style.fg, style.bg, style.attrs | astyle.A_BOLD)
700 return astyle.Style(style.fg, style.bg, style.attrs | astyle.A_BOLD)
699
701
700 def report(self, msg):
702 def report(self, msg):
701 """
703 """
702 Store the message ``msg`` for display below the footer line. This
704 Store the message ``msg`` for display below the footer line. This
703 will be displayed as soon as the screen is redrawn.
705 will be displayed as soon as the screen is redrawn.
704 """
706 """
705 self._report = msg
707 self._report = msg
706
708
707 def enter(self, item, mode, *attrs):
709 def enter(self, item, mode, *attrs):
708 """
710 """
709 Enter the object ``item`` in the mode ``mode``. If ``attrs`` is
711 Enter the object ``item`` in the mode ``mode``. If ``attrs`` is
710 specified, it will be used as a fixed list of attributes to display.
712 specified, it will be used as a fixed list of attributes to display.
711 """
713 """
712 try:
714 try:
713 iterator = ipipe.xiter(item, mode)
715 iterator = ipipe.xiter(item, mode)
714 except (KeyboardInterrupt, SystemExit):
716 except (KeyboardInterrupt, SystemExit):
715 raise
717 raise
716 except Exception, exc:
718 except Exception, exc:
717 curses.beep()
719 curses.beep()
718 self.report(exc)
720 self.report(exc)
719 else:
721 else:
720 self._calcheaderlines(len(self.levels)+1)
722 self._calcheaderlines(len(self.levels)+1)
721 level = _BrowserLevel(
723 level = _BrowserLevel(
722 self,
724 self,
723 item,
725 item,
724 iterator,
726 iterator,
725 self.scrsizey-1-self._headerlines-2,
727 self.scrsizey-1-self._headerlines-2,
726 *attrs
728 *attrs
727 )
729 )
728 self.levels.append(level)
730 self.levels.append(level)
729
731
730 def startkeyboardinput(self, mode):
732 def startkeyboardinput(self, mode):
731 """
733 """
732 Enter mode ``mode``, which requires keyboard input.
734 Enter mode ``mode``, which requires keyboard input.
733 """
735 """
734 self.mode = mode
736 self.mode = mode
735 self.keyboardinput = ""
737 self.keyboardinput = ""
736 self.cursorpos = 0
738 self.cursorpos = 0
737
739
738 def executekeyboardinput(self, mode):
740 def executekeyboardinput(self, mode):
739 exe = getattr(self, "exe_%s" % mode, None)
741 exe = getattr(self, "exe_%s" % mode, None)
740 if exe is not None:
742 if exe is not None:
741 exe()
743 exe()
742 self.mode = "default"
744 self.mode = "default"
743
745
744 def keylabel(self, keycode):
746 def keylabel(self, keycode):
745 """
747 """
746 Return a pretty name for the ``curses`` key ``keycode`` (used in the
748 Return a pretty name for the ``curses`` key ``keycode`` (used in the
747 help screen and in reports about unassigned keys).
749 help screen and in reports about unassigned keys).
748 """
750 """
749 if keycode <= 0xff:
751 if keycode <= 0xff:
750 specialsnames = {
752 specialsnames = {
751 ord("\n"): "RETURN",
753 ord("\n"): "RETURN",
752 ord(" "): "SPACE",
754 ord(" "): "SPACE",
753 ord("\t"): "TAB",
755 ord("\t"): "TAB",
754 ord("\x7f"): "DELETE",
756 ord("\x7f"): "DELETE",
755 ord("\x08"): "BACKSPACE",
757 ord("\x08"): "BACKSPACE",
756 }
758 }
757 if keycode in specialsnames:
759 if keycode in specialsnames:
758 return specialsnames[keycode]
760 return specialsnames[keycode]
759 return repr(chr(keycode))
761 return repr(chr(keycode))
760 for name in dir(curses):
762 for name in dir(curses):
761 if name.startswith("KEY_") and getattr(curses, name) == keycode:
763 if name.startswith("KEY_") and getattr(curses, name) == keycode:
762 return name
764 return name
763 return str(keycode)
765 return str(keycode)
764
766
765 def beep(self, force=False):
767 def beep(self, force=False):
766 if force or self._dobeep:
768 if force or self._dobeep:
767 curses.beep()
769 curses.beep()
768 # don't beep again (as long as the same key is pressed)
770 # don't beep again (as long as the same key is pressed)
769 self._dobeep = False
771 self._dobeep = False
770
772
771 def cmd_quit(self):
773 def cmd_quit(self):
772 self.returnvalue = None
774 self.returnvalue = None
773 return True
775 return True
774
776
775 def cmd_up(self):
777 def cmd_up(self):
776 level = self.levels[-1]
778 level = self.levels[-1]
777 self.report("up")
779 self.report("up")
778 level.moveto(level.curx, level.cury-self.stepy)
780 level.moveto(level.curx, level.cury-self.stepy)
779
781
780 def cmd_down(self):
782 def cmd_down(self):
781 level = self.levels[-1]
783 level = self.levels[-1]
782 self.report("down")
784 self.report("down")
783 level.moveto(level.curx, level.cury+self.stepy)
785 level.moveto(level.curx, level.cury+self.stepy)
784
786
785 def cmd_pageup(self):
787 def cmd_pageup(self):
786 level = self.levels[-1]
788 level = self.levels[-1]
787 self.report("page up")
789 self.report("page up")
788 level.moveto(level.curx, level.cury-level.mainsizey+self.pageoverlapy)
790 level.moveto(level.curx, level.cury-level.mainsizey+self.pageoverlapy)
789
791
790 def cmd_pagedown(self):
792 def cmd_pagedown(self):
791 level = self.levels[-1]
793 level = self.levels[-1]
792 self.report("page down")
794 self.report("page down")
793 level.moveto(level.curx, level.cury+level.mainsizey-self.pageoverlapy)
795 level.moveto(level.curx, level.cury+level.mainsizey-self.pageoverlapy)
794
796
795 def cmd_left(self):
797 def cmd_left(self):
796 level = self.levels[-1]
798 level = self.levels[-1]
797 self.report("left")
799 self.report("left")
798 level.moveto(level.curx-self.stepx, level.cury)
800 level.moveto(level.curx-self.stepx, level.cury)
799
801
800 def cmd_right(self):
802 def cmd_right(self):
801 level = self.levels[-1]
803 level = self.levels[-1]
802 self.report("right")
804 self.report("right")
803 level.moveto(level.curx+self.stepx, level.cury)
805 level.moveto(level.curx+self.stepx, level.cury)
804
806
805 def cmd_home(self):
807 def cmd_home(self):
806 level = self.levels[-1]
808 level = self.levels[-1]
807 self.report("home")
809 self.report("home")
808 level.moveto(0, level.cury)
810 level.moveto(0, level.cury)
809
811
810 def cmd_end(self):
812 def cmd_end(self):
811 level = self.levels[-1]
813 level = self.levels[-1]
812 self.report("end")
814 self.report("end")
813 level.moveto(level.datasizex+level.mainsizey-self.pageoverlapx, level.cury)
815 level.moveto(level.datasizex+level.mainsizey-self.pageoverlapx, level.cury)
814
816
815 def cmd_prevattr(self):
817 def cmd_prevattr(self):
816 level = self.levels[-1]
818 level = self.levels[-1]
817 if level.displayattr[0] is None or level.displayattr[0] == 0:
819 if level.displayattr[0] is None or level.displayattr[0] == 0:
818 self.beep()
820 self.beep()
819 else:
821 else:
820 self.report("prevattr")
822 self.report("prevattr")
821 pos = 0
823 pos = 0
822 for (i, attrname) in enumerate(level.displayattrs):
824 for (i, attrname) in enumerate(level.displayattrs):
823 if i == level.displayattr[0]-1:
825 if i == level.displayattr[0]-1:
824 break
826 break
825 pos += level.colwidths[attrname] + 1
827 pos += level.colwidths[attrname] + 1
826 level.moveto(pos, level.cury)
828 level.moveto(pos, level.cury)
827
829
828 def cmd_nextattr(self):
830 def cmd_nextattr(self):
829 level = self.levels[-1]
831 level = self.levels[-1]
830 if level.displayattr[0] is None or level.displayattr[0] == len(level.displayattrs)-1:
832 if level.displayattr[0] is None or level.displayattr[0] == len(level.displayattrs)-1:
831 self.beep()
833 self.beep()
832 else:
834 else:
833 self.report("nextattr")
835 self.report("nextattr")
834 pos = 0
836 pos = 0
835 for (i, attrname) in enumerate(level.displayattrs):
837 for (i, attrname) in enumerate(level.displayattrs):
836 if i == level.displayattr[0]+1:
838 if i == level.displayattr[0]+1:
837 break
839 break
838 pos += level.colwidths[attrname] + 1
840 pos += level.colwidths[attrname] + 1
839 level.moveto(pos, level.cury)
841 level.moveto(pos, level.cury)
840
842
841 def cmd_pick(self):
843 def cmd_pick(self):
842 level = self.levels[-1]
844 level = self.levels[-1]
843 self.returnvalue = level.items[level.cury].item
845 self.returnvalue = level.items[level.cury].item
844 return True
846 return True
845
847
846 def cmd_pickattr(self):
848 def cmd_pickattr(self):
847 level = self.levels[-1]
849 level = self.levels[-1]
848 attrname = level.displayattr[1]
850 attrname = level.displayattr[1]
849 if attrname is ipipe.noitem:
851 if attrname is ipipe.noitem:
850 curses.beep()
852 curses.beep()
851 self.report(AttributeError(ipipe._attrname(attrname)))
853 self.report(AttributeError(ipipe._attrname(attrname)))
852 return
854 return
853 attr = ipipe._getattr(level.items[level.cury].item, attrname)
855 attr = ipipe._getattr(level.items[level.cury].item, attrname)
854 if attr is ipipe.noitem:
856 if attr is ipipe.noitem:
855 curses.beep()
857 curses.beep()
856 self.report(AttributeError(ipipe._attrname(attrname)))
858 self.report(AttributeError(ipipe._attrname(attrname)))
857 else:
859 else:
858 self.returnvalue = attr
860 self.returnvalue = attr
859 return True
861 return True
860
862
861 def cmd_pickallattrs(self):
863 def cmd_pickallattrs(self):
862 level = self.levels[-1]
864 level = self.levels[-1]
863 attrname = level.displayattr[1]
865 attrname = level.displayattr[1]
864 if attrname is ipipe.noitem:
866 if attrname is ipipe.noitem:
865 curses.beep()
867 curses.beep()
866 self.report(AttributeError(ipipe._attrname(attrname)))
868 self.report(AttributeError(ipipe._attrname(attrname)))
867 return
869 return
868 result = []
870 result = []
869 for cache in level.items:
871 for cache in level.items:
870 attr = ipipe._getattr(cache.item, attrname)
872 attr = ipipe._getattr(cache.item, attrname)
871 if attr is not ipipe.noitem:
873 if attr is not ipipe.noitem:
872 result.append(attr)
874 result.append(attr)
873 self.returnvalue = result
875 self.returnvalue = result
874 return True
876 return True
875
877
876 def cmd_pickmarked(self):
878 def cmd_pickmarked(self):
877 level = self.levels[-1]
879 level = self.levels[-1]
878 self.returnvalue = [cache.item for cache in level.items if cache.marked]
880 self.returnvalue = [cache.item for cache in level.items if cache.marked]
879 return True
881 return True
880
882
881 def cmd_pickmarkedattr(self):
883 def cmd_pickmarkedattr(self):
882 level = self.levels[-1]
884 level = self.levels[-1]
883 attrname = level.displayattr[1]
885 attrname = level.displayattr[1]
884 if attrname is ipipe.noitem:
886 if attrname is ipipe.noitem:
885 curses.beep()
887 curses.beep()
886 self.report(AttributeError(ipipe._attrname(attrname)))
888 self.report(AttributeError(ipipe._attrname(attrname)))
887 return
889 return
888 result = []
890 result = []
889 for cache in level.items:
891 for cache in level.items:
890 if cache.marked:
892 if cache.marked:
891 attr = ipipe._getattr(cache.item, attrname)
893 attr = ipipe._getattr(cache.item, attrname)
892 if attr is not ipipe.noitem:
894 if attr is not ipipe.noitem:
893 result.append(attr)
895 result.append(attr)
894 self.returnvalue = result
896 self.returnvalue = result
895 return True
897 return True
896
898
897 def cmd_markrange(self):
899 def cmd_markrange(self):
898 level = self.levels[-1]
900 level = self.levels[-1]
899 self.report("markrange")
901 self.report("markrange")
900 start = None
902 start = None
901 if level.items:
903 if level.items:
902 for i in xrange(level.cury, -1, -1):
904 for i in xrange(level.cury, -1, -1):
903 if level.items[i].marked:
905 if level.items[i].marked:
904 start = i
906 start = i
905 break
907 break
906 if start is None:
908 if start is None:
907 self.report(CommandError("no mark before cursor"))
909 self.report(CommandError("no mark before cursor"))
908 curses.beep()
910 curses.beep()
909 else:
911 else:
910 for i in xrange(start, level.cury+1):
912 for i in xrange(start, level.cury+1):
911 cache = level.items[i]
913 cache = level.items[i]
912 if not cache.marked:
914 if not cache.marked:
913 cache.marked = True
915 cache.marked = True
914 level.marked += 1
916 level.marked += 1
915
917
916 def cmd_enterdefault(self):
918 def cmd_enterdefault(self):
917 level = self.levels[-1]
919 level = self.levels[-1]
918 try:
920 try:
919 item = level.items[level.cury].item
921 item = level.items[level.cury].item
920 except IndexError:
922 except IndexError:
921 self.report(CommandError("No object"))
923 self.report(CommandError("No object"))
922 curses.beep()
924 curses.beep()
923 else:
925 else:
924 self.report("entering object (default mode)...")
926 self.report("entering object (default mode)...")
925 self.enter(item, "default")
927 self.enter(item, "default")
926
928
927 def cmd_leave(self):
929 def cmd_leave(self):
928 self.report("leave")
930 self.report("leave")
929 if len(self.levels) > 1:
931 if len(self.levels) > 1:
930 self._calcheaderlines(len(self.levels)-1)
932 self._calcheaderlines(len(self.levels)-1)
931 self.levels.pop(-1)
933 self.levels.pop(-1)
932 else:
934 else:
933 self.report(CommandError("This is the last level"))
935 self.report(CommandError("This is the last level"))
934 curses.beep()
936 curses.beep()
935
937
936 def cmd_enter(self):
938 def cmd_enter(self):
937 level = self.levels[-1]
939 level = self.levels[-1]
938 try:
940 try:
939 item = level.items[level.cury].item
941 item = level.items[level.cury].item
940 except IndexError:
942 except IndexError:
941 self.report(CommandError("No object"))
943 self.report(CommandError("No object"))
942 curses.beep()
944 curses.beep()
943 else:
945 else:
944 self.report("entering object...")
946 self.report("entering object...")
945 self.enter(item, None)
947 self.enter(item, None)
946
948
947 def cmd_enterattr(self):
949 def cmd_enterattr(self):
948 level = self.levels[-1]
950 level = self.levels[-1]
949 attrname = level.displayattr[1]
951 attrname = level.displayattr[1]
950 if attrname is ipipe.noitem:
952 if attrname is ipipe.noitem:
951 curses.beep()
953 curses.beep()
952 self.report(AttributeError(ipipe._attrname(attrname)))
954 self.report(AttributeError(ipipe._attrname(attrname)))
953 return
955 return
954 try:
956 try:
955 item = level.items[level.cury].item
957 item = level.items[level.cury].item
956 except IndexError:
958 except IndexError:
957 self.report(CommandError("No object"))
959 self.report(CommandError("No object"))
958 curses.beep()
960 curses.beep()
959 else:
961 else:
960 attr = ipipe._getattr(item, attrname)
962 attr = ipipe._getattr(item, attrname)
961 if attr is ipipe.noitem:
963 if attr is ipipe.noitem:
962 self.report(AttributeError(ipipe._attrname(attrname)))
964 self.report(AttributeError(ipipe._attrname(attrname)))
963 else:
965 else:
964 self.report("entering object attribute %s..." % ipipe._attrname(attrname))
966 self.report("entering object attribute %s..." % ipipe._attrname(attrname))
965 self.enter(attr, None)
967 self.enter(attr, None)
966
968
967 def cmd_detail(self):
969 def cmd_detail(self):
968 level = self.levels[-1]
970 level = self.levels[-1]
969 try:
971 try:
970 item = level.items[level.cury].item
972 item = level.items[level.cury].item
971 except IndexError:
973 except IndexError:
972 self.report(CommandError("No object"))
974 self.report(CommandError("No object"))
973 curses.beep()
975 curses.beep()
974 else:
976 else:
975 self.report("entering detail view for object...")
977 self.report("entering detail view for object...")
976 self.enter(item, "detail")
978 self.enter(item, "detail")
977
979
978 def cmd_detailattr(self):
980 def cmd_detailattr(self):
979 level = self.levels[-1]
981 level = self.levels[-1]
980 attrname = level.displayattr[1]
982 attrname = level.displayattr[1]
981 if attrname is ipipe.noitem:
983 if attrname is ipipe.noitem:
982 curses.beep()
984 curses.beep()
983 self.report(AttributeError(ipipe._attrname(attrname)))
985 self.report(AttributeError(ipipe._attrname(attrname)))
984 return
986 return
985 try:
987 try:
986 item = level.items[level.cury].item
988 item = level.items[level.cury].item
987 except IndexError:
989 except IndexError:
988 self.report(CommandError("No object"))
990 self.report(CommandError("No object"))
989 curses.beep()
991 curses.beep()
990 else:
992 else:
991 attr = ipipe._getattr(item, attrname)
993 attr = ipipe._getattr(item, attrname)
992 if attr is ipipe.noitem:
994 if attr is ipipe.noitem:
993 self.report(AttributeError(ipipe._attrname(attrname)))
995 self.report(AttributeError(ipipe._attrname(attrname)))
994 else:
996 else:
995 self.report("entering detail view for attribute...")
997 self.report("entering detail view for attribute...")
996 self.enter(attr, "detail")
998 self.enter(attr, "detail")
997
999
998 def cmd_tooglemark(self):
1000 def cmd_tooglemark(self):
999 level = self.levels[-1]
1001 level = self.levels[-1]
1000 self.report("toggle mark")
1002 self.report("toggle mark")
1001 try:
1003 try:
1002 item = level.items[level.cury]
1004 item = level.items[level.cury]
1003 except IndexError: # no items?
1005 except IndexError: # no items?
1004 pass
1006 pass
1005 else:
1007 else:
1006 if item.marked:
1008 if item.marked:
1007 item.marked = False
1009 item.marked = False
1008 level.marked -= 1
1010 level.marked -= 1
1009 else:
1011 else:
1010 item.marked = True
1012 item.marked = True
1011 level.marked += 1
1013 level.marked += 1
1012
1014
1013 def cmd_sortattrasc(self):
1015 def cmd_sortattrasc(self):
1014 level = self.levels[-1]
1016 level = self.levels[-1]
1015 attrname = level.displayattr[1]
1017 attrname = level.displayattr[1]
1016 if attrname is ipipe.noitem:
1018 if attrname is ipipe.noitem:
1017 curses.beep()
1019 curses.beep()
1018 self.report(AttributeError(ipipe._attrname(attrname)))
1020 self.report(AttributeError(ipipe._attrname(attrname)))
1019 return
1021 return
1020 self.report("sort by %s (ascending)" % ipipe._attrname(attrname))
1022 self.report("sort by %s (ascending)" % ipipe._attrname(attrname))
1021 def key(item):
1023 def key(item):
1022 try:
1024 try:
1023 return ipipe._getattr(item, attrname, None)
1025 return ipipe._getattr(item, attrname, None)
1024 except (KeyboardInterrupt, SystemExit):
1026 except (KeyboardInterrupt, SystemExit):
1025 raise
1027 raise
1026 except Exception:
1028 except Exception:
1027 return None
1029 return None
1028 level.sort(key)
1030 level.sort(key)
1029
1031
1030 def cmd_sortattrdesc(self):
1032 def cmd_sortattrdesc(self):
1031 level = self.levels[-1]
1033 level = self.levels[-1]
1032 attrname = level.displayattr[1]
1034 attrname = level.displayattr[1]
1033 if attrname is ipipe.noitem:
1035 if attrname is ipipe.noitem:
1034 curses.beep()
1036 curses.beep()
1035 self.report(AttributeError(ipipe._attrname(attrname)))
1037 self.report(AttributeError(ipipe._attrname(attrname)))
1036 return
1038 return
1037 self.report("sort by %s (descending)" % ipipe._attrname(attrname))
1039 self.report("sort by %s (descending)" % ipipe._attrname(attrname))
1038 def key(item):
1040 def key(item):
1039 try:
1041 try:
1040 return ipipe._getattr(item, attrname, None)
1042 return ipipe._getattr(item, attrname, None)
1041 except (KeyboardInterrupt, SystemExit):
1043 except (KeyboardInterrupt, SystemExit):
1042 raise
1044 raise
1043 except Exception:
1045 except Exception:
1044 return None
1046 return None
1045 level.sort(key, reverse=True)
1047 level.sort(key, reverse=True)
1046
1048
1047 def cmd_goto(self):
1049 def cmd_goto(self):
1048 self.startkeyboardinput("goto")
1050 self.startkeyboardinput("goto")
1049
1051
1050 def exe_goto(self):
1052 def exe_goto(self):
1051 level = self.levels[-1]
1053 level = self.levels[-1]
1052 if self.keyboardinput:
1054 if self.keyboardinput:
1053 level.moveto(level.curx, int(self.keyboardinput))
1055 level.moveto(level.curx, int(self.keyboardinput))
1054
1056
1055 def cmd_find(self):
1057 def cmd_find(self):
1056 self.startkeyboardinput("find")
1058 self.startkeyboardinput("find")
1057
1059
1058 def exe_find(self):
1060 def exe_find(self):
1059 level = self.levels[-1]
1061 level = self.levels[-1]
1060 if self.keyboardinput:
1062 if self.keyboardinput:
1061 while True:
1063 while True:
1062 cury = level.cury
1064 cury = level.cury
1063 level.moveto(level.curx, cury+1)
1065 level.moveto(level.curx, cury+1)
1064 if cury == level.cury:
1066 if cury == level.cury:
1065 curses.beep()
1067 curses.beep()
1066 break
1068 break
1067 item = level.items[level.cury].item
1069 item = level.items[level.cury].item
1068 try:
1070 try:
1069 if eval(self.keyboardinput, globals(), ipipe.AttrNamespace(item)):
1071 if eval(self.keyboardinput, globals(), ipipe.AttrNamespace(item)):
1070 break
1072 break
1071 except (KeyboardInterrupt, SystemExit):
1073 except (KeyboardInterrupt, SystemExit):
1072 raise
1074 raise
1073 except Exception, exc:
1075 except Exception, exc:
1074 self.report(exc)
1076 self.report(exc)
1075 curses.beep()
1077 curses.beep()
1076 break # break on error
1078 break # break on error
1077
1079
1078 def cmd_findbackwards(self):
1080 def cmd_findbackwards(self):
1079 self.startkeyboardinput("findbackwards")
1081 self.startkeyboardinput("findbackwards")
1080
1082
1081 def exe_findbackwards(self):
1083 def exe_findbackwards(self):
1082 level = self.levels[-1]
1084 level = self.levels[-1]
1083 if self.keyboardinput:
1085 if self.keyboardinput:
1084 while level.cury:
1086 while level.cury:
1085 level.moveto(level.curx, level.cury-1)
1087 level.moveto(level.curx, level.cury-1)
1086 item = level.items[level.cury].item
1088 item = level.items[level.cury].item
1087 try:
1089 try:
1088 if eval(self.keyboardinput, globals(), ipipe.AttrNamespace(item)):
1090 if eval(self.keyboardinput, globals(), ipipe.AttrNamespace(item)):
1089 break
1091 break
1090 except (KeyboardInterrupt, SystemExit):
1092 except (KeyboardInterrupt, SystemExit):
1091 raise
1093 raise
1092 except Exception, exc:
1094 except Exception, exc:
1093 self.report(exc)
1095 self.report(exc)
1094 curses.beep()
1096 curses.beep()
1095 break # break on error
1097 break # break on error
1096 else:
1098 else:
1097 curses.beep()
1099 curses.beep()
1098
1100
1099 def cmd_help(self):
1101 def cmd_help(self):
1100 """
1102 """
1101 The help command
1103 The help command
1102 """
1104 """
1103 for level in self.levels:
1105 for level in self.levels:
1104 if isinstance(level.input, _BrowserHelp):
1106 if isinstance(level.input, _BrowserHelp):
1105 curses.beep()
1107 curses.beep()
1106 self.report(CommandError("help already active"))
1108 self.report(CommandError("help already active"))
1107 return
1109 return
1108
1110
1109 self.enter(_BrowserHelp(self), "default")
1111 self.enter(_BrowserHelp(self), "default")
1110
1112
1111 def _dodisplay(self, scr):
1113 def _dodisplay(self, scr):
1112 """
1114 """
1113 This method is the workhorse of the browser. It handles screen
1115 This method is the workhorse of the browser. It handles screen
1114 drawing and the keyboard.
1116 drawing and the keyboard.
1115 """
1117 """
1116 self.scr = scr
1118 self.scr = scr
1117 curses.halfdelay(1)
1119 curses.halfdelay(1)
1118 footery = 2
1120 footery = 2
1119
1121
1120 keys = []
1122 keys = []
1121 for (key, cmd) in self.keymap.iteritems():
1123 for (key, cmd) in self.keymap.iteritems():
1122 if cmd == "quit":
1124 if cmd == "quit":
1123 keys.append("%s=%s" % (self.keylabel(key), cmd))
1125 keys.append("%s=%s" % (self.keylabel(key), cmd))
1124 for (key, cmd) in self.keymap.iteritems():
1126 for (key, cmd) in self.keymap.iteritems():
1125 if cmd == "help":
1127 if cmd == "help":
1126 keys.append("%s=%s" % (self.keylabel(key), cmd))
1128 keys.append("%s=%s" % (self.keylabel(key), cmd))
1127 helpmsg = " | %s" % " ".join(keys)
1129 helpmsg = " | %s" % " ".join(keys)
1128
1130
1129 scr.clear()
1131 scr.clear()
1130 msg = "Fetching first batch of objects..."
1132 msg = "Fetching first batch of objects..."
1131 (self.scrsizey, self.scrsizex) = scr.getmaxyx()
1133 (self.scrsizey, self.scrsizex) = scr.getmaxyx()
1132 scr.addstr(self.scrsizey//2, (self.scrsizex-len(msg))//2, msg)
1134 scr.addstr(self.scrsizey//2, (self.scrsizex-len(msg))//2, msg)
1133 scr.refresh()
1135 scr.refresh()
1134
1136
1135 lastc = -1
1137 lastc = -1
1136
1138
1137 self.levels = []
1139 self.levels = []
1138 # enter the first level
1140 # enter the first level
1139 self.enter(self.input, ipipe.xiter(self.input, "default"), *self.attrs)
1141 self.enter(self.input, ipipe.xiter(self.input, "default"), *self.attrs)
1140
1142
1141 self._calcheaderlines(None)
1143 self._calcheaderlines(None)
1142
1144
1143 while True:
1145 while True:
1144 level = self.levels[-1]
1146 level = self.levels[-1]
1145 (self.scrsizey, self.scrsizex) = scr.getmaxyx()
1147 (self.scrsizey, self.scrsizex) = scr.getmaxyx()
1146 level.mainsizey = self.scrsizey-1-self._headerlines-footery
1148 level.mainsizey = self.scrsizey-1-self._headerlines-footery
1147
1149
1148 # Paint object header
1150 # Paint object header
1149 for i in xrange(self._firstheaderline, self._firstheaderline+self._headerlines):
1151 for i in xrange(self._firstheaderline, self._firstheaderline+self._headerlines):
1150 lv = self.levels[i]
1152 lv = self.levels[i]
1151 posx = 0
1153 posx = 0
1152 posy = i-self._firstheaderline
1154 posy = i-self._firstheaderline
1153 endx = self.scrsizex
1155 endx = self.scrsizex
1154 if i: # not the first level
1156 if i: # not the first level
1155 msg = " (%d/%d" % (self.levels[i-1].cury, len(self.levels[i-1].items))
1157 msg = " (%d/%d" % (self.levels[i-1].cury, len(self.levels[i-1].items))
1156 if not self.levels[i-1].exhausted:
1158 if not self.levels[i-1].exhausted:
1157 msg += "+"
1159 msg += "+"
1158 msg += ") "
1160 msg += ") "
1159 endx -= len(msg)+1
1161 endx -= len(msg)+1
1160 posx += self.addstr(posy, posx, 0, endx, " ibrowse #%d: " % i, self.style_objheadertext)
1162 posx += self.addstr(posy, posx, 0, endx, " ibrowse #%d: " % i, self.style_objheadertext)
1161 for (style, text) in lv.header:
1163 for (style, text) in lv.header:
1162 posx += self.addstr(posy, posx, 0, endx, text, self.style_objheaderobject)
1164 posx += self.addstr(posy, posx, 0, endx, text, self.style_objheaderobject)
1163 if posx >= endx:
1165 if posx >= endx:
1164 break
1166 break
1165 if i:
1167 if i:
1166 posx += self.addstr(posy, posx, 0, self.scrsizex, msg, self.style_objheadernumber)
1168 posx += self.addstr(posy, posx, 0, self.scrsizex, msg, self.style_objheadernumber)
1167 posx += self.addchr(posy, posx, 0, self.scrsizex, " ", self.scrsizex-posx, self.style_objheadernumber)
1169 posx += self.addchr(posy, posx, 0, self.scrsizex, " ", self.scrsizex-posx, self.style_objheadernumber)
1168
1170
1169 if not level.items:
1171 if not level.items:
1170 self.addchr(self._headerlines, 0, 0, self.scrsizex, " ", self.scrsizex, self.style_colheader)
1172 self.addchr(self._headerlines, 0, 0, self.scrsizex, " ", self.scrsizex, self.style_colheader)
1171 self.addstr(self._headerlines+1, 0, 0, self.scrsizex, " <empty>", astyle.style_error)
1173 self.addstr(self._headerlines+1, 0, 0, self.scrsizex, " <empty>", astyle.style_error)
1172 scr.clrtobot()
1174 scr.clrtobot()
1173 else:
1175 else:
1174 # Paint column headers
1176 # Paint column headers
1175 scr.move(self._headerlines, 0)
1177 scr.move(self._headerlines, 0)
1176 scr.addstr(" %*s " % (level.numbersizex, "#"), self.getstyle(self.style_colheader))
1178 scr.addstr(" %*s " % (level.numbersizex, "#"), self.getstyle(self.style_colheader))
1177 scr.addstr(self.headersepchar, self.getstyle(self.style_colheadersep))
1179 scr.addstr(self.headersepchar, self.getstyle(self.style_colheadersep))
1178 begx = level.numbersizex+3
1180 begx = level.numbersizex+3
1179 posx = begx-level.datastartx
1181 posx = begx-level.datastartx
1180 for attrname in level.displayattrs:
1182 for attrname in level.displayattrs:
1181 strattrname = ipipe._attrname(attrname)
1183 strattrname = ipipe._attrname(attrname)
1182 cwidth = level.colwidths[attrname]
1184 cwidth = level.colwidths[attrname]
1183 header = strattrname.ljust(cwidth)
1185 header = strattrname.ljust(cwidth)
1184 if attrname == level.displayattr[1]:
1186 if attrname == level.displayattr[1]:
1185 style = self.style_colheaderhere
1187 style = self.style_colheaderhere
1186 else:
1188 else:
1187 style = self.style_colheader
1189 style = self.style_colheader
1188 posx += self.addstr(self._headerlines, posx, begx, self.scrsizex, header, style)
1190 posx += self.addstr(self._headerlines, posx, begx, self.scrsizex, header, style)
1189 posx += self.addstr(self._headerlines, posx, begx, self.scrsizex, self.headersepchar, self.style_colheadersep)
1191 posx += self.addstr(self._headerlines, posx, begx, self.scrsizex, self.headersepchar, self.style_colheadersep)
1190 if posx >= self.scrsizex:
1192 if posx >= self.scrsizex:
1191 break
1193 break
1192 else:
1194 else:
1193 scr.addstr(" "*(self.scrsizex-posx), self.getstyle(self.style_colheader))
1195 scr.addstr(" "*(self.scrsizex-posx), self.getstyle(self.style_colheader))
1194
1196
1195 # Paint rows
1197 # Paint rows
1196 posy = self._headerlines+1+level.datastarty
1198 posy = self._headerlines+1+level.datastarty
1197 for i in xrange(level.datastarty, min(level.datastarty+level.mainsizey, len(level.items))):
1199 for i in xrange(level.datastarty, min(level.datastarty+level.mainsizey, len(level.items))):
1198 cache = level.items[i]
1200 cache = level.items[i]
1199 if i == level.cury:
1201 if i == level.cury:
1200 style = self.style_numberhere
1202 style = self.style_numberhere
1201 else:
1203 else:
1202 style = self.style_number
1204 style = self.style_number
1203
1205
1204 posy = self._headerlines+1+i-level.datastarty
1206 posy = self._headerlines+1+i-level.datastarty
1205 posx = begx-level.datastartx
1207 posx = begx-level.datastartx
1206
1208
1207 scr.move(posy, 0)
1209 scr.move(posy, 0)
1208 scr.addstr(" %*d%s" % (level.numbersizex, i, " !"[cache.marked]), self.getstyle(style))
1210 scr.addstr(" %*d%s" % (level.numbersizex, i, " !"[cache.marked]), self.getstyle(style))
1209 scr.addstr(self.headersepchar, self.getstyle(self.style_sep))
1211 scr.addstr(self.headersepchar, self.getstyle(self.style_sep))
1210
1212
1211 for attrname in level.displayattrs:
1213 for attrname in level.displayattrs:
1212 cwidth = level.colwidths[attrname]
1214 cwidth = level.colwidths[attrname]
1213 try:
1215 try:
1214 (align, length, parts) = level.displayrows[i-level.datastarty][attrname]
1216 (align, length, parts) = level.displayrows[i-level.datastarty][attrname]
1215 except KeyError:
1217 except KeyError:
1216 align = 2
1218 align = 2
1217 style = astyle.style_nodata
1219 style = astyle.style_nodata
1218 padstyle = self.style_datapad
1220 padstyle = self.style_datapad
1219 sepstyle = self.style_sep
1221 sepstyle = self.style_sep
1220 if i == level.cury:
1222 if i == level.cury:
1221 padstyle = self.getstylehere(padstyle)
1223 padstyle = self.getstylehere(padstyle)
1222 sepstyle = self.getstylehere(sepstyle)
1224 sepstyle = self.getstylehere(sepstyle)
1223 if align == 2:
1225 if align == 2:
1224 posx += self.addchr(posy, posx, begx, self.scrsizex, self.nodatachar, cwidth, style)
1226 posx += self.addchr(posy, posx, begx, self.scrsizex, self.nodatachar, cwidth, style)
1225 else:
1227 else:
1226 if align == 1:
1228 if align == 1:
1227 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, cwidth-length, padstyle)
1229 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, cwidth-length, padstyle)
1228 elif align == 0:
1230 elif align == 0:
1229 pad1 = (cwidth-length)//2
1231 pad1 = (cwidth-length)//2
1230 pad2 = cwidth-length-len(pad1)
1232 pad2 = cwidth-length-len(pad1)
1231 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, pad1, padstyle)
1233 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, pad1, padstyle)
1232 for (style, text) in parts:
1234 for (style, text) in parts:
1233 if i == level.cury:
1235 if i == level.cury:
1234 style = self.getstylehere(style)
1236 style = self.getstylehere(style)
1235 posx += self.addstr(posy, posx, begx, self.scrsizex, text, style)
1237 posx += self.addstr(posy, posx, begx, self.scrsizex, text, style)
1236 if posx >= self.scrsizex:
1238 if posx >= self.scrsizex:
1237 break
1239 break
1238 if align == -1:
1240 if align == -1:
1239 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, cwidth-length, padstyle)
1241 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, cwidth-length, padstyle)
1240 elif align == 0:
1242 elif align == 0:
1241 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, pad2, padstyle)
1243 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, pad2, padstyle)
1242 posx += self.addstr(posy, posx, begx, self.scrsizex, self.datasepchar, sepstyle)
1244 posx += self.addstr(posy, posx, begx, self.scrsizex, self.datasepchar, sepstyle)
1243 else:
1245 else:
1244 scr.clrtoeol()
1246 scr.clrtoeol()
1245
1247
1246 # Add blank row headers for the rest of the screen
1248 # Add blank row headers for the rest of the screen
1247 for posy in xrange(posy+1, self.scrsizey-2):
1249 for posy in xrange(posy+1, self.scrsizey-2):
1248 scr.addstr(posy, 0, " " * (level.numbersizex+2), self.getstyle(self.style_colheader))
1250 scr.addstr(posy, 0, " " * (level.numbersizex+2), self.getstyle(self.style_colheader))
1249 scr.clrtoeol()
1251 scr.clrtoeol()
1250
1252
1251 posy = self.scrsizey-footery
1253 posy = self.scrsizey-footery
1252 # Display footer
1254 # Display footer
1253 scr.addstr(posy, 0, " "*self.scrsizex, self.getstyle(self.style_footer))
1255 scr.addstr(posy, 0, " "*self.scrsizex, self.getstyle(self.style_footer))
1254
1256
1255 if level.exhausted:
1257 if level.exhausted:
1256 flag = ""
1258 flag = ""
1257 else:
1259 else:
1258 flag = "+"
1260 flag = "+"
1259
1261
1260 endx = self.scrsizex-len(helpmsg)-1
1262 endx = self.scrsizex-len(helpmsg)-1
1261 scr.addstr(posy, endx, helpmsg, self.getstyle(self.style_footer))
1263 scr.addstr(posy, endx, helpmsg, self.getstyle(self.style_footer))
1262
1264
1263 posx = 0
1265 posx = 0
1264 msg = " %d%s objects (%d marked): " % (len(level.items), flag, level.marked)
1266 msg = " %d%s objects (%d marked): " % (len(level.items), flag, level.marked)
1265 posx += self.addstr(posy, posx, 0, endx, msg, self.style_footer)
1267 posx += self.addstr(posy, posx, 0, endx, msg, self.style_footer)
1266 try:
1268 try:
1267 item = level.items[level.cury].item
1269 item = level.items[level.cury].item
1268 except IndexError: # empty
1270 except IndexError: # empty
1269 pass
1271 pass
1270 else:
1272 else:
1271 for (nostyle, text) in ipipe.xrepr(item, "footer"):
1273 for (nostyle, text) in ipipe.xrepr(item, "footer"):
1272 if not isinstance(nostyle, int):
1274 if not isinstance(nostyle, int):
1273 posx += self.addstr(posy, posx, 0, endx, text, self.style_footer)
1275 posx += self.addstr(posy, posx, 0, endx, text, self.style_footer)
1274 if posx >= endx:
1276 if posx >= endx:
1275 break
1277 break
1276
1278
1277 attrstyle = [(astyle.style_default, "no attribute")]
1279 attrstyle = [(astyle.style_default, "no attribute")]
1278 attrname = level.displayattr[1]
1280 attrname = level.displayattr[1]
1279 if attrname is not ipipe.noitem and attrname is not None:
1281 if attrname is not ipipe.noitem and attrname is not None:
1280 posx += self.addstr(posy, posx, 0, endx, " | ", self.style_footer)
1282 posx += self.addstr(posy, posx, 0, endx, " | ", self.style_footer)
1281 posx += self.addstr(posy, posx, 0, endx, ipipe._attrname(attrname), self.style_footer)
1283 posx += self.addstr(posy, posx, 0, endx, ipipe._attrname(attrname), self.style_footer)
1282 posx += self.addstr(posy, posx, 0, endx, ": ", self.style_footer)
1284 posx += self.addstr(posy, posx, 0, endx, ": ", self.style_footer)
1283 try:
1285 try:
1284 attr = ipipe._getattr(item, attrname)
1286 attr = ipipe._getattr(item, attrname)
1285 except (SystemExit, KeyboardInterrupt):
1287 except (SystemExit, KeyboardInterrupt):
1286 raise
1288 raise
1287 except Exception, exc:
1289 except Exception, exc:
1288 attr = exc
1290 attr = exc
1289 if attr is not ipipe.noitem:
1291 if attr is not ipipe.noitem:
1290 attrstyle = ipipe.xrepr(attr, "footer")
1292 attrstyle = ipipe.xrepr(attr, "footer")
1291 for (nostyle, text) in attrstyle:
1293 for (nostyle, text) in attrstyle:
1292 if not isinstance(nostyle, int):
1294 if not isinstance(nostyle, int):
1293 posx += self.addstr(posy, posx, 0, endx, text, self.style_footer)
1295 posx += self.addstr(posy, posx, 0, endx, text, self.style_footer)
1294 if posx >= endx:
1296 if posx >= endx:
1295 break
1297 break
1296
1298
1297 try:
1299 try:
1298 # Display input prompt
1300 # Display input prompt
1299 if self.mode in self.prompts:
1301 if self.mode in self.prompts:
1300 scr.addstr(self.scrsizey-1, 0,
1302 scr.addstr(self.scrsizey-1, 0,
1301 self.prompts[self.mode] + self.keyboardinput,
1303 self.prompts[self.mode] + self.keyboardinput,
1302 self.getstyle(astyle.style_default))
1304 self.getstyle(astyle.style_default))
1303 # Display report
1305 # Display report
1304 else:
1306 else:
1305 if self._report is not None:
1307 if self._report is not None:
1306 if isinstance(self._report, Exception):
1308 if isinstance(self._report, Exception):
1307 style = self.getstyle(astyle.style_error)
1309 style = self.getstyle(astyle.style_error)
1308 if self._report.__class__.__module__ == "exceptions":
1310 if self._report.__class__.__module__ == "exceptions":
1309 msg = "%s: %s" % \
1311 msg = "%s: %s" % \
1310 (self._report.__class__.__name__, self._report)
1312 (self._report.__class__.__name__, self._report)
1311 else:
1313 else:
1312 msg = "%s.%s: %s" % \
1314 msg = "%s.%s: %s" % \
1313 (self._report.__class__.__module__,
1315 (self._report.__class__.__module__,
1314 self._report.__class__.__name__, self._report)
1316 self._report.__class__.__name__, self._report)
1315 else:
1317 else:
1316 style = self.getstyle(self.style_report)
1318 style = self.getstyle(self.style_report)
1317 msg = self._report
1319 msg = self._report
1318 scr.addstr(self.scrsizey-1, 0, msg[:self.scrsizex], style)
1320 scr.addstr(self.scrsizey-1, 0, msg[:self.scrsizex], style)
1319 self._report = None
1321 self._report = None
1320 else:
1322 else:
1321 scr.move(self.scrsizey-1, 0)
1323 scr.move(self.scrsizey-1, 0)
1322 except curses.error:
1324 except curses.error:
1323 # Protect against error from writing to the last line
1325 # Protect against error from writing to the last line
1324 pass
1326 pass
1325 scr.clrtoeol()
1327 scr.clrtoeol()
1326
1328
1327 # Position cursor
1329 # Position cursor
1328 if self.mode in self.prompts:
1330 if self.mode in self.prompts:
1329 scr.move(self.scrsizey-1, len(self.prompts[self.mode])+self.cursorpos)
1331 scr.move(self.scrsizey-1, len(self.prompts[self.mode])+self.cursorpos)
1330 else:
1332 else:
1331 scr.move(
1333 scr.move(
1332 1+self._headerlines+level.cury-level.datastarty,
1334 1+self._headerlines+level.cury-level.datastarty,
1333 level.numbersizex+3+level.curx-level.datastartx
1335 level.numbersizex+3+level.curx-level.datastartx
1334 )
1336 )
1335 scr.refresh()
1337 scr.refresh()
1336
1338
1337 # Check keyboard
1339 # Check keyboard
1338 while True:
1340 while True:
1339 c = scr.getch()
1341 c = scr.getch()
1340 if self.mode in self.prompts:
1342 if self.mode in self.prompts:
1341 if c in (8, 127, curses.KEY_BACKSPACE):
1343 if c in (8, 127, curses.KEY_BACKSPACE):
1342 if self.cursorpos:
1344 if self.cursorpos:
1343 self.keyboardinput = self.keyboardinput[:self.cursorpos-1] + self.keyboardinput[self.cursorpos:]
1345 self.keyboardinput = self.keyboardinput[:self.cursorpos-1] + self.keyboardinput[self.cursorpos:]
1344 self.cursorpos -= 1
1346 self.cursorpos -= 1
1345 break
1347 break
1346 else:
1348 else:
1347 curses.beep()
1349 curses.beep()
1348 elif c == curses.KEY_LEFT:
1350 elif c == curses.KEY_LEFT:
1349 if self.cursorpos:
1351 if self.cursorpos:
1350 self.cursorpos -= 1
1352 self.cursorpos -= 1
1351 break
1353 break
1352 else:
1354 else:
1353 curses.beep()
1355 curses.beep()
1354 elif c == curses.KEY_RIGHT:
1356 elif c == curses.KEY_RIGHT:
1355 if self.cursorpos < len(self.keyboardinput):
1357 if self.cursorpos < len(self.keyboardinput):
1356 self.cursorpos += 1
1358 self.cursorpos += 1
1357 break
1359 break
1358 else:
1360 else:
1359 curses.beep()
1361 curses.beep()
1360 elif c in (curses.KEY_UP, curses.KEY_DOWN): # cancel
1362 elif c in (curses.KEY_UP, curses.KEY_DOWN): # cancel
1361 self.mode = "default"
1363 self.mode = "default"
1362 break
1364 break
1363 elif c == ord("\n"):
1365 elif c == ord("\n"):
1364 self.executekeyboardinput(self.mode)
1366 self.executekeyboardinput(self.mode)
1365 break
1367 break
1366 elif c != -1:
1368 elif c != -1:
1367 try:
1369 try:
1368 c = chr(c)
1370 c = chr(c)
1369 except ValueError:
1371 except ValueError:
1370 curses.beep()
1372 curses.beep()
1371 else:
1373 else:
1372 if (self.mode == "goto" and not "0" <= c <= "9"):
1374 if (self.mode == "goto" and not "0" <= c <= "9"):
1373 curses.beep()
1375 curses.beep()
1374 else:
1376 else:
1375 self.keyboardinput = self.keyboardinput[:self.cursorpos] + c + self.keyboardinput[self.cursorpos:]
1377 self.keyboardinput = self.keyboardinput[:self.cursorpos] + c + self.keyboardinput[self.cursorpos:]
1376 self.cursorpos += 1
1378 self.cursorpos += 1
1377 break # Redisplay
1379 break # Redisplay
1378 else:
1380 else:
1379 # if no key is pressed slow down and beep again
1381 # if no key is pressed slow down and beep again
1380 if c == -1:
1382 if c == -1:
1381 self.stepx = 1.
1383 self.stepx = 1.
1382 self.stepy = 1.
1384 self.stepy = 1.
1383 self._dobeep = True
1385 self._dobeep = True
1384 else:
1386 else:
1385 # if a different key was pressed slow down and beep too
1387 # if a different key was pressed slow down and beep too
1386 if c != lastc:
1388 if c != lastc:
1387 lastc = c
1389 lastc = c
1388 self.stepx = 1.
1390 self.stepx = 1.
1389 self.stepy = 1.
1391 self.stepy = 1.
1390 self._dobeep = True
1392 self._dobeep = True
1391 cmdname = self.keymap.get(c, None)
1393 cmdname = self.keymap.get(c, None)
1392 if cmdname is None:
1394 if cmdname is None:
1393 self.report(
1395 self.report(
1394 UnassignedKeyError("Unassigned key %s" %
1396 UnassignedKeyError("Unassigned key %s" %
1395 self.keylabel(c)))
1397 self.keylabel(c)))
1396 else:
1398 else:
1397 cmdfunc = getattr(self, "cmd_%s" % cmdname, None)
1399 cmdfunc = getattr(self, "cmd_%s" % cmdname, None)
1398 if cmdfunc is None:
1400 if cmdfunc is None:
1399 self.report(
1401 self.report(
1400 UnknownCommandError("Unknown command %r" %
1402 UnknownCommandError("Unknown command %r" %
1401 (cmdname,)))
1403 (cmdname,)))
1402 elif cmdfunc():
1404 elif cmdfunc():
1403 returnvalue = self.returnvalue
1405 returnvalue = self.returnvalue
1404 self.returnvalue = None
1406 self.returnvalue = None
1405 return returnvalue
1407 return returnvalue
1406 self.stepx = self.nextstepx(self.stepx)
1408 self.stepx = self.nextstepx(self.stepx)
1407 self.stepy = self.nextstepy(self.stepy)
1409 self.stepy = self.nextstepy(self.stepy)
1408 curses.flushinp() # get rid of type ahead
1410 curses.flushinp() # get rid of type ahead
1409 break # Redisplay
1411 break # Redisplay
1410 self.scr = None
1412 self.scr = None
1411
1413
1412 def display(self):
1414 def display(self):
1413 return curses.wrapper(self._dodisplay)
1415 return curses.wrapper(self._dodisplay)
General Comments 0
You need to be logged in to leave comments. Login now