##// END OF EJS Templates
Display keycodes in the range 0x01-0x1F as CTRL-xx....
walter.doerwald -
Show More

The requested changes are too big and content was truncated. Show full diff

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