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