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

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

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