##// END OF EJS Templates
Add two new commands to ibrowse: hideattr (mapped to "h")...
walter.doerwald -
Show More

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

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