##// END OF EJS Templates
Add new commands refresh and refresh_timer (mapped to "R"/"F5"...
walter.doerwald -
Show More
@@ -1,914 +1,1121 b''
1 1 # -*- coding: iso-8859-1 -*-
2 2
3 3 import ipipe, os, webbrowser, urllib
4 from IPython import ipapi
4 5 import wx
5 6 import wx.grid, wx.html
6 7
7 8 try:
8 9 sorted
9 10 except NameError:
10 11 from ipipe import sorted
11 12
12 13
13 14 __all__ = ["igrid"]
14 15
15 16
16 17 help = """
17 18 <?xml version='1.0' encoding='iso-8859-1'?>
18 19 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
19 20 <html>
20 21 <head>
21 22 <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
22 23 <link rel="stylesheet" href="igrid_help.css" type="text/css" />
23 24 <title>igrid help</title>
24 25 </head>
25 26 <body>
26 27 <h1>igrid help</h1>
27 28
28 29
29 30 <h2>Commands</h2>
30 31
31 32
32 33 <h3>pick (P)</h3>
33 34 <p>Pick the whole row (object is available as "_")</p>
34 35
35 36 <h3>pickattr (Shift-P)</h3>
36 37 <p>Pick the attribute under the cursor</p>
37 38
38 39 <h3>pickallattrs (Shift-C)</h3>
39 <p>Pick' the complete column under the cursor (i.e. the attribute under the
40 <p>Pick the complete column under the cursor (i.e. the attribute under the
40 41 cursor) from all currently fetched objects. These attributes will be returned
41 42 as a list.</p>
42 43
44 <h3>pickinput (I)</h3>
45 <p>Pick the current row as next input line in IPython. Additionally the row is stored as "_"</p>
46
47 <h3>pickinputattr (Shift-I)</h3>
48 <p>Pick the attribute under the cursor as next input line in IPython. Additionally the row is stored as "_"</p>
49
43 50 <h3>enter (E)</h3>
44 51 <p>Enter the object under the cursor. (what this mean depends on the object
45 52 itself, i.e. how it implements iteration). This opens a new browser 'level'.</p>
46 53
47 54 <h3>enterattr (Shift-E)</h3>
48 55 <p>Enter the attribute under the cursor.</p>
49 56
50 57 <h3>detail (D)</h3>
51 58 <p>Show a detail view of the object under the cursor. This shows the name,
52 59 type, doc string and value of the object attributes (and it might show more
53 60 attributes than in the list view, depending on the object).</p>
54 61
55 62 <h3>detailattr (Shift-D)</h3>
56 63 <p>Show a detail view of the attribute under the cursor.</p>
57 64
58 65 <h3>pickrows (M)</h3>
59 66 <p>Pick multiple selected rows (M)</p>
60 67
61 68 <h3>pickrowsattr (CTRL-M)</h3>
62 69 <p>From multiple selected rows pick the cells matching the attribute the cursor is in (CTRL-M)</p>
63 70
64 71 <h3>find (CTRL-F)</h3>
65 72 <p>Find text</p>
66 73
74 <h3>find_expression (CTRL-Shift-F)</h3>
75 <p>Find entries matching an expression</p>
76
67 77 <h3>find_next (F3)</h3>
68 <p>Find next occurrence of the searchtext</p>
78 <p>Find next occurrence</p>
69 79
70 80 <h3>find_previous (Shift-F3)</h3>
71 <p>Find previous occurrence of the searchtext </p>
81 <p>Find previous occurrence</p>
72 82
73 83 <h3>sortattrasc (V)</h3>
74 84 <p>Sort the objects (in ascending order) using the attribute under the cursor as the sort key.</p>
75 85
76 86 <h3>sortattrdesc (Shift-V)</h3>
77 87 <p>Sort the objects (in descending order) using the attribute under the cursor as the sort key.</p>
78 88
89 <h3>refresh_once (R, F5)</h3>
90 <p>Refreshes the display by restarting the iterator</p>
91
92 <h3>refresh_every_second</h3>
93 <p>Refreshes the display by restarting the iterator every second until stopped by stop_refresh.</p>
94
95 <h3>refresh_interval</h3>
96 <p>Refreshes the display by restarting the iterator every X ms (X is a custom interval set by the user) until stopped by stop_refresh.</p>
97
98 <h3>stop_refresh</h3>
99 <p>Stops all refresh timers.</p>
100
79 101 <h3>leave (Backspace, DEL, X)</h3>
80 102 <p>Close current tab (and all the tabs to the right of the current one).</h3>
81 103
82 104 <h3>quit (ESC,Q)</h3>
83 105 <p>Quit igrid and return to the IPython prompt.</p>
84 106
85 107
86 108 <h2>Navigation</h2>
87 109
88 110
89 111 <h3>Jump to the last column of the current row (END, CTRL-E, CTRL-Right)</h3>
90 112
91 113 <h3>Jump to the first column of the current row (HOME, CTRL-A, CTRL-Left)</h3>
92 114
93 115 <h3>Move the cursor one column to the left (&lt;)</h3>
94 116
95 117 <h3>Move the cursor one column to the right (&gt;)</h3>
96 118
97 119 <h3>Jump to the first row in the current column (CTRL-Up)</h3>
98 120
99 121 <h3>Jump to the last row in the current column (CTRL-Down)</h3>
100 122
101 123 </body>
102 124 </html>
125
103 126 """
104 127
105 128
106 129 class IGridRenderer(wx.grid.PyGridCellRenderer):
107 130 """
108 131 This is a custom renderer for our IGridGrid
109 132 """
110 133 def __init__(self, table):
111 134 self.maxchars = 200
112 135 self.table = table
113 136 self.colormap = (
114 137 ( 0, 0, 0),
115 138 (174, 0, 0),
116 139 ( 0, 174, 0),
117 140 (174, 174, 0),
118 141 ( 0, 0, 174),
119 142 (174, 0, 174),
120 143 ( 0, 174, 174),
121 144 ( 64, 64, 64)
122 145 )
123 146
124 147 wx.grid.PyGridCellRenderer.__init__(self)
125 148
126 149 def _getvalue(self, row, col):
127 150 try:
128 151 value = self.table._displayattrs[col].value(self.table.items[row])
129 152 (align, width, text) = ipipe.xformat(value, "cell", self.maxchars)
130 153 except Exception, exc:
131 154 (align, width, text) = ipipe.xformat(exc, "cell", self.maxchars)
132 155 return (align, text)
133 156
134 157 def GetBestSize(self, grid, attr, dc, row, col):
135 158 text = grid.GetCellValue(row, col)
136 159 (align, text) = self._getvalue(row, col)
137 160 dc.SetFont(attr.GetFont())
138 161 (w, h) = dc.GetTextExtent(str(text))
139 162 return wx.Size(min(w+2, 600), h+2) # add border
140 163
141 164 def Draw(self, grid, attr, dc, rect, row, col, isSelected):
142 165 """
143 166 Takes care of drawing everything in the cell; aligns the text
144 167 """
145 168 text = grid.GetCellValue(row, col)
146 169 (align, text) = self._getvalue(row, col)
147 170 if isSelected:
148 171 bg = grid.GetSelectionBackground()
149 172 else:
150 173 bg = ["white", (240, 240, 240)][row%2]
151 174 dc.SetTextBackground(bg)
152 175 dc.SetBrush(wx.Brush(bg, wx.SOLID))
153 176 dc.SetPen(wx.TRANSPARENT_PEN)
154 177 dc.SetFont(attr.GetFont())
155 178 dc.DrawRectangleRect(rect)
156 179 dc.SetClippingRect(rect)
157 180 # Format the text
158 181 if align == -1: # left alignment
159 182 (width, height) = dc.GetTextExtent(str(text))
160 183 x = rect[0]+1
161 184 y = rect[1]+0.5*(rect[3]-height)
162 185
163 186 for (style, part) in text:
164 187 if isSelected:
165 188 fg = grid.GetSelectionForeground()
166 189 else:
167 190 fg = self.colormap[style.fg]
168 191 dc.SetTextForeground(fg)
169 192 (w, h) = dc.GetTextExtent(part)
170 193 dc.DrawText(part, x, y)
171 194 x += w
172 195 elif align == 0: # center alignment
173 196 (width, height) = dc.GetTextExtent(str(text))
174 197 x = rect[0]+0.5*(rect[2]-width)
175 198 y = rect[1]+0.5*(rect[3]-height)
176 199 for (style, part) in text:
177 200 if isSelected:
178 201 fg = grid.GetSelectionForeground()
179 202 else:
180 203 fg = self.colormap[style.fg]
181 204 dc.SetTextForeground(fg)
182 205 (w, h) = dc.GetTextExtent(part)
183 206 dc.DrawText(part, x, y)
184 207 x += w
185 208 else: # right alignment
186 209 (width, height) = dc.GetTextExtent(str(text))
187 210 x = rect[0]+rect[2]-1
188 211 y = rect[1]+0.5*(rect[3]-height)
189 212 for (style, part) in reversed(text):
190 213 (w, h) = dc.GetTextExtent(part)
191 214 x -= w
192 215 if isSelected:
193 216 fg = grid.GetSelectionForeground()
194 217 else:
195 218 fg = self.colormap[style.fg]
196 219 dc.SetTextForeground(fg)
197 220 dc.DrawText(part, x, y)
198 221 dc.DestroyClippingRegion()
199 222
200 223 def Clone(self):
201 224 return IGridRenderer(self.table)
202 225
203 226
204 227 class IGridTable(wx.grid.PyGridTableBase):
205 228 # The data table for the ``IGridGrid``. Some dirty tricks were used here:
206 229 # ``GetValue()`` does not get any values (or at least it does not return
207 230 # anything, accessing the values is done by the renderer)
208 231 # but rather tries to fetch the objects which were requested into the table.
209 232 # General behaviour is: Fetch the first X objects. If the user scrolls down
210 233 # to the last object another bunch of X objects is fetched (if possible)
211 234 def __init__(self, input, fontsize, *attrs):
212 235 wx.grid.PyGridTableBase.__init__(self)
213 236 self.input = input
214 237 self.iterator = ipipe.xiter(input)
215 238 self.items = []
216 239 self.attrs = [ipipe.upgradexattr(attr) for attr in attrs]
217 240 self._displayattrs = self.attrs[:]
218 241 self._displayattrset = set(self.attrs)
219 self._sizing = False
220 242 self.fontsize = fontsize
221 243 self._fetch(1)
244 self.timer = wx.Timer()
245 self.timer.Bind(wx.EVT_TIMER, self.refresh_content)
222 246
223 247 def GetAttr(self, *args):
224 248 attr = wx.grid.GridCellAttr()
225 249 attr.SetFont(wx.Font(self.fontsize, wx.TELETYPE, wx.NORMAL, wx.NORMAL))
226 250 return attr
227 251
228 252 def GetNumberRows(self):
229 253 return len(self.items)
230 254
231 255 def GetNumberCols(self):
232 256 return len(self._displayattrs)
233 257
234 258 def GetColLabelValue(self, col):
235 259 if col < len(self._displayattrs):
236 260 return self._displayattrs[col].name()
237 261 else:
238 262 return ""
239 263
240 264 def GetRowLabelValue(self, row):
241 265 return str(row)
242 266
243 267 def IsEmptyCell(self, row, col):
244 268 return False
245 269
246 270 def _append(self, item):
247 271 self.items.append(item)
248 272 # Nothing to do if the set of attributes has been fixed by the user
249 273 if not self.attrs:
250 274 for attr in ipipe.xattrs(item):
251 275 attr = ipipe.upgradexattr(attr)
252 276 if attr not in self._displayattrset:
253 277 self._displayattrs.append(attr)
254 278 self._displayattrset.add(attr)
255 279
256 280 def _fetch(self, count):
257 281 # Try to fill ``self.items`` with at least ``count`` objects.
258 282 have = len(self.items)
259 283 while self.iterator is not None and have < count:
260 284 try:
261 285 item = self.iterator.next()
262 286 except StopIteration:
263 287 self.iterator = None
264 288 break
265 289 except (KeyboardInterrupt, SystemExit):
266 290 raise
267 291 except Exception, exc:
268 292 have += 1
269 293 self._append(exc)
270 294 self.iterator = None
271 295 break
272 296 else:
273 297 have += 1
274 298 self._append(item)
275 299
276 300 def GetValue(self, row, col):
277 301 # some kind of dummy-function: does not return anything but "";
278 302 # (The value isn't use anyway)
279 303 # its main task is to trigger the fetch of new objects
280 had_cols = self._displayattrs[:]
304 sizing_needed = False
305 had_cols = len(self._displayattrs)
281 306 had_rows = len(self.items)
282 if row == had_rows - 1 and self.iterator is not None and not self._sizing:
307 if row == had_rows - 1 and self.iterator is not None:
283 308 self._fetch(row + 20)
309 sizing_needed = True
284 310 have_rows = len(self.items)
285 311 have_cols = len(self._displayattrs)
286 312 if have_rows > had_rows:
287 313 msg = wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_NOTIFY_ROWS_APPENDED, have_rows - had_rows)
288 314 self.GetView().ProcessTableMessage(msg)
289 self._sizing = True
290 self.GetView().AutoSizeColumns(False)
291 self._sizing = False
315 sizing_needed = True
292 316 if row >= have_rows:
293 317 return ""
294 if self._displayattrs != had_cols:
295 msg = wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_NOTIFY_COLS_APPENDED, have_cols - len(had_cols))
318 if have_cols != had_cols:
319 msg = wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_NOTIFY_COLS_APPENDED, have_cols - had_cols)
296 320 self.GetView().ProcessTableMessage(msg)
321 sizing_needed = True
322 if sizing_needed:
323 self.GetView().AutoSizeColumns(False)
297 324 return ""
298 325
299 326 def SetValue(self, row, col, value):
300 327 pass
301 328
329 def refresh_content(self, event):
330 msg = wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_NOTIFY_ROWS_DELETED, 0, self.GetNumberRows())
331 self.GetView().ProcessTableMessage(msg)
332 self.iterator = ipipe.xiter(self.input)
333 self.items = []
334 self.attrs = [] # _append will calculate new displayattrs
335 self._fetch(1) # fetch one...
336 if self.items:
337 msg = wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_NOTIFY_ROWS_APPENDED, 1)
338 self.GetView().ProcessTableMessage(msg)
339 self.GetValue(0, 0) # and trigger "fetch next 20"
340 item = self.items[0]
341 self.GetView().AutoSizeColumns(False)
342 panel = self.GetView().GetParent()
343 nb = panel.GetParent()
344 current = nb.GetSelection()
345 if nb.GetPage(current) == panel:
346 self.GetView().set_footer(item)
302 347
303 348 class IGridGrid(wx.grid.Grid):
304 349 # The actual grid
305 350 # all methods for selecting/sorting/picking/... data are implemented here
306 351 def __init__(self, panel, input, *attrs):
307 352 wx.grid.Grid.__init__(self, panel)
308 353 fontsize = 9
309 354 self.input = input
310 355 self.table = IGridTable(self.input, fontsize, *attrs)
311 356 self.SetTable(self.table, True)
312 357 self.SetSelectionMode(wx.grid.Grid.wxGridSelectRows)
313 358 self.SetDefaultRenderer(IGridRenderer(self.table))
314 359 self.EnableEditing(False)
315 360 self.Bind(wx.EVT_KEY_DOWN, self.key_pressed)
316 361 self.Bind(wx.grid.EVT_GRID_CELL_LEFT_DCLICK, self.cell_doubleclicked)
317 362 self.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, self.cell_leftclicked)
318 363 self.Bind(wx.grid.EVT_GRID_LABEL_LEFT_DCLICK, self.label_doubleclicked)
319 364 self.Bind(wx.grid.EVT_GRID_LABEL_LEFT_CLICK, self.on_label_leftclick)
320 365 self.Bind(wx.grid.EVT_GRID_RANGE_SELECT, self._on_selected_range)
321 366 self.Bind(wx.grid.EVT_GRID_SELECT_CELL, self._on_selected_cell)
322 367 self.current_selection = set()
323 368 self.maxchars = 200
324 369
325 370 def on_label_leftclick(self, event):
326 371 event.Skip()
327 372
328 373 def error_output(self, text):
329 374 wx.Bell()
330 375 frame = self.GetParent().GetParent().GetParent()
331 frame.SetStatusText(text)
376 frame.SetStatusText(str(text))
332 377
333 378 def _on_selected_range(self, event):
334 379 # Internal update to the selection tracking lists
335 380 if event.Selecting():
336 381 # adding to the list...
337 382 self.current_selection.update(xrange(event.GetTopRow(), event.GetBottomRow()+1))
338 383 else:
339 384 # removal from list
340 385 for index in xrange( event.GetTopRow(), event.GetBottomRow()+1):
341 386 self.current_selection.discard(index)
342 387 event.Skip()
343 388
344 389 def _on_selected_cell(self, event):
345 390 # Internal update to the selection tracking list
346 391 self.current_selection = set([event.GetRow()])
347 392 event.Skip()
348 393
349 394 def sort(self, key, reverse=False):
350 395 """
351 396 Sort the current list of items using the key function ``key``. If
352 397 ``reverse`` is true the sort order is reversed.
353 398 """
354 399 row = self.GetGridCursorRow()
355 400 col = self.GetGridCursorCol()
356 401 curitem = self.table.items[row] # Remember where the cursor is now
357 402 # Sort items
358 403 def realkey(item):
359 404 try:
360 405 return key(item)
361 406 except (KeyboardInterrupt, SystemExit):
362 407 raise
363 408 except Exception:
364 409 return None
365 410 try:
366 411 self.table.items = ipipe.deque(sorted(self.table.items, key=realkey, reverse=reverse))
367 412 except TypeError, exc:
368 413 self.error_output("Exception encountered: %s" % exc)
369 414 return
370 415 # Find out where the object under the cursor went
371 416 for (i, item) in enumerate(self.table.items):
372 417 if item is curitem:
373 418 self.SetGridCursor(i,col)
374 419 self.MakeCellVisible(i,col)
375 420 self.Refresh()
376 421
377 422 def sortattrasc(self):
378 423 """
379 424 Sort in ascending order; sorting criteria is the current attribute
380 425 """
381 426 col = self.GetGridCursorCol()
382 427 attr = self.table._displayattrs[col]
383 428 frame = self.GetParent().GetParent().GetParent()
384 429 if attr is ipipe.noitem:
385 430 self.error_output("no column under cursor")
386 431 return
387 432 frame.SetStatusText("sort by %s (ascending)" % attr.name())
388 433 def key(item):
389 434 try:
390 435 return attr.value(item)
391 436 except (KeyboardInterrupt, SystemExit):
392 437 raise
393 438 except Exception:
394 439 return None
395 440 self.sort(key)
396 441
397 442 def sortattrdesc(self):
398 443 """
399 444 Sort in descending order; sorting criteria is the current attribute
400 445 """
401 446 col = self.GetGridCursorCol()
402 447 attr = self.table._displayattrs[col]
403 448 frame = self.GetParent().GetParent().GetParent()
404 449 if attr is ipipe.noitem:
405 450 self.error_output("no column under cursor")
406 451 return
407 452 frame.SetStatusText("sort by %s (descending)" % attr.name())
408 453 def key(item):
409 454 try:
410 455 return attr.value(item)
411 456 except (KeyboardInterrupt, SystemExit):
412 457 raise
413 458 except Exception:
414 459 return None
415 460 self.sort(key, reverse=True)
416 461
417 462 def label_doubleclicked(self, event):
418 463 row = event.GetRow()
419 464 col = event.GetCol()
420 465 if col == -1:
421 466 self.enter(row)
422 467
423 468 def _getvalue(self, row, col):
424 469 """
425 470 Gets the text which is displayed at ``(row, col)``
426 471 """
427 472 try:
428 473 value = self.table._displayattrs[col].value(self.table.items[row])
429 474 (align, width, text) = ipipe.xformat(value, "cell", self.maxchars)
430 475 except IndexError:
431 476 raise IndexError
432 477 except Exception, exc:
433 478 (align, width, text) = ipipe.xformat(exc, "cell", self.maxchars)
434 479 return text
435 480
436 def search(self, searchtext, startrow=0, startcol=0, search_forward=True):
481 def searchexpression(self, searchexp, startrow=None, search_forward=True ):
482 """
483 Find by expression
484 """
485 frame = self.GetParent().GetParent().GetParent()
486 if searchexp:
487 if search_forward:
488 if not startrow:
489 row = self.GetGridCursorRow()+1
490 else:
491 row = startrow + 1
492 while True:
493 try:
494 foo = self.table.GetValue(row, 0)
495 item = self.table.items[row]
496 try:
497 globals = ipipe.getglobals(None)
498 if eval(searchexp, globals, ipipe.AttrNamespace(item)):
499 self.SetGridCursor(row, 0) # found something
500 self.MakeCellVisible(row, 0)
501 break
502 except (KeyboardInterrupt, SystemExit):
503 raise
504 except Exception, exc:
505 frame.SetStatusText(str(exc))
506 wx.Bell()
507 break # break on error
508 except IndexError:
509 return
510 row += 1
511 else:
512 if not startrow:
513 row = self.GetGridCursorRow() - 1
514 else:
515 row = startrow - 1
516 while True:
517 try:
518 foo = self.table.GetValue(row, 0)
519 item = self.table.items[row]
520 try:
521 globals = ipipe.getglobals(None)
522 if eval(searchexp, globals, ipipe.AttrNamespace(item)):
523 self.SetGridCursor(row, 0) # found something
524 self.MakeCellVisible(row, 0)
525 break
526 except (KeyboardInterrupt, SystemExit):
527 raise
528 except Exception, exc:
529 frame.SetStatusText(str(exc))
530 wx.Bell()
531 break # break on error
532 except IndexError:
533 return
534 row -= 1
535
536
537 def search(self, searchtext, startrow=None, startcol=None, search_forward=True):
437 538 """
438 539 search for ``searchtext``, starting in ``(startrow, startcol)``;
439 540 if ``search_forward`` is true the direction is "forward"
440 541 """
441 row = startrow
442 542 searchtext = searchtext.lower()
443 543 if search_forward:
544 if startrow is not None and startcol is not None:
545 row = startrow
546 else:
547 startcol = self.GetGridCursorCol() + 1
548 row = self.GetGridCursorRow()
549 if startcol >= self.GetNumberCols():
550 startcol = 0
551 row += 1
444 552 while True:
445 553 for col in xrange(startcol, self.table.GetNumberCols()):
446 554 try:
447 555 foo = self.table.GetValue(row, col)
448 556 text = self._getvalue(row, col)
449 557 if searchtext in text.string().lower():
450 558 self.SetGridCursor(row, col)
451 559 self.MakeCellVisible(row, col)
452 560 return
453 561 except IndexError:
454 562 return
455 563 startcol = 0
456 564 row += 1
457 565 else:
566 if startrow is not None and startcol is not None:
567 row = startrow
568 else:
569 startcol = self.GetGridCursorCol() - 1
570 row = self.GetGridCursorRow()
571 if startcol < 0:
572 startcol = self.GetNumberCols() - 1
573 row -= 1
458 574 while True:
459 575 for col in xrange(startcol, -1, -1):
460 576 try:
461 577 foo = self.table.GetValue(row, col)
462 578 text = self._getvalue(row, col)
463 579 if searchtext in text.string().lower():
464 580 self.SetGridCursor(row, col)
465 581 self.MakeCellVisible(row, col)
466 582 return
467 583 except IndexError:
468 584 return
469 585 startcol = self.table.GetNumberCols()-1
470 586 row -= 1
471 587
472 588 def key_pressed(self, event):
473 589 """
474 590 Maps pressed keys to functions
475 591 """
476 592 frame = self.GetParent().GetParent().GetParent()
477 593 frame.SetStatusText("")
478 594 sh = event.ShiftDown()
479 595 ctrl = event.ControlDown()
480 596
481 597 keycode = event.GetKeyCode()
482 598 if keycode == ord("P"):
483 599 row = self.GetGridCursorRow()
484 600 if sh:
485 601 col = self.GetGridCursorCol()
486 602 self.pickattr(row, col)
487 603 else:
488 604 self.pick(row)
489 605 elif keycode == ord("M"):
490 606 if ctrl:
491 607 col = self.GetGridCursorCol()
492 608 self.pickrowsattr(sorted(self.current_selection), col)
493 609 else:
494 610 self.pickrows(sorted(self.current_selection))
495 611 elif keycode in (wx.WXK_BACK, wx.WXK_DELETE, ord("X")) and not (ctrl or sh):
496 612 self.delete_current_notebook()
497 613 elif keycode in (ord("E"), ord("\r")):
498 614 row = self.GetGridCursorRow()
499 615 if sh:
500 616 col = self.GetGridCursorCol()
501 617 self.enterattr(row, col)
502 618 else:
503 619 self.enter(row)
504 620 elif keycode == ord("E") and ctrl:
505 621 row = self.GetGridCursorRow()
506 622 self.SetGridCursor(row, self.GetNumberCols()-1)
507 623 elif keycode == wx.WXK_HOME or (keycode == ord("A") and ctrl):
508 624 row = self.GetGridCursorRow()
509 625 self.SetGridCursor(row, 0)
510 626 elif keycode == ord("C") and sh:
511 627 col = self.GetGridCursorCol()
512 628 attr = self.table._displayattrs[col]
513 629 result = []
514 630 for i in xrange(self.GetNumberRows()):
515 631 result.append(self.table._displayattrs[col].value(self.table.items[i]))
516 632 self.quit(result)
517 633 elif keycode in (wx.WXK_ESCAPE, ord("Q")) and not (ctrl or sh):
518 634 self.quit()
519 635 elif keycode == ord("<"):
520 636 row = self.GetGridCursorRow()
521 637 col = self.GetGridCursorCol()
522 638 if not event.ShiftDown():
523 639 newcol = col - 1
524 640 if newcol >= 0:
525 641 self.SetGridCursor(row, col - 1)
526 642 else:
527 643 newcol = col + 1
528 644 if newcol < self.GetNumberCols():
529 645 self.SetGridCursor(row, col + 1)
530 646 elif keycode == ord("D"):
531 647 col = self.GetGridCursorCol()
532 648 row = self.GetGridCursorRow()
533 649 if not sh:
534 650 self.detail(row, col)
535 651 else:
536 652 self.detail_attr(row, col)
537 653 elif keycode == ord("F") and ctrl:
654 if sh:
655 frame.enter_searchexpression(event)
656 else:
538 657 frame.enter_searchtext(event)
539 658 elif keycode == wx.WXK_F3:
540 659 if sh:
541 660 frame.find_previous(event)
542 661 else:
543 662 frame.find_next(event)
544 663 elif keycode == ord("V"):
545 664 if sh:
546 665 self.sortattrdesc()
547 666 else:
548 667 self.sortattrasc()
549 668 elif keycode == wx.WXK_DOWN:
550 669 row = self.GetGridCursorRow()
551 670 try:
552 671 item = self.table.items[row+1]
553 672 except IndexError:
554 673 item = self.table.items[row]
555 674 self.set_footer(item)
556 675 event.Skip()
557 676 elif keycode == wx.WXK_UP:
558 677 row = self.GetGridCursorRow()
559 678 if row >= 1:
560 679 item = self.table.items[row-1]
561 680 else:
562 681 item = self.table.items[row]
563 682 self.set_footer(item)
564 683 event.Skip()
565 684 elif keycode == wx.WXK_RIGHT:
566 685 row = self.GetGridCursorRow()
567 686 item = self.table.items[row]
568 687 self.set_footer(item)
569 688 event.Skip()
570 689 elif keycode == wx.WXK_LEFT:
571 690 row = self.GetGridCursorRow()
572 691 item = self.table.items[row]
573 692 self.set_footer(item)
574 693 event.Skip()
694 elif keycode == ord("R") or keycode == wx.WXK_F5:
695 self.table.refresh_content(event)
696 elif keycode == ord("I"):
697 row = self.GetGridCursorRow()
698 if not sh:
699 self.pickinput(row)
700 else:
701 col = self.GetGridCursorCol()
702 self.pickinputattr(row, col)
575 703 else:
576 704 event.Skip()
577 705
578 706 def delete_current_notebook(self):
579 707 """
580 708 deletes the current notebook tab
581 709 """
582 710 panel = self.GetParent()
583 711 nb = panel.GetParent()
584 712 current = nb.GetSelection()
585 713 count = nb.GetPageCount()
586 714 if count > 1:
587 715 for i in xrange(count-1, current-1, -1):
588 716 nb.DeletePage(i)
589 717 nb.GetCurrentPage().grid.SetFocus()
590 718 else:
591 719 frame = nb.GetParent()
592 720 frame.SetStatusText("This is the last level!")
593 721
594 722 def _doenter(self, value, *attrs):
595 723 """
596 724 "enter" a special item resulting in a new notebook tab
597 725 """
598 726 panel = self.GetParent()
599 727 nb = panel.GetParent()
600 728 frame = nb.GetParent()
601 729 current = nb.GetSelection()
602 730 count = nb.GetPageCount()
603 731 try: # if we want to enter something non-iterable, e.g. a function
604 732 if current + 1 == count and value is not self.input: # we have an event in the last tab
605 733 frame._add_notebook(value, *attrs)
606 734 elif value != self.input: # we have to delete all tabs newer than [panel] first
607 735 for i in xrange(count-1, current, -1): # some tabs don't close if we don't close in *reverse* order
608 736 nb.DeletePage(i)
609 737 frame._add_notebook(value)
610 738 except TypeError, exc:
611 739 if exc.__class__.__module__ == "exceptions":
612 740 msg = "%s: %s" % (exc.__class__.__name__, exc)
613 741 else:
614 742 msg = "%s.%s: %s" % (exc.__class__.__module__, exc.__class__.__name__, exc)
615 743 frame.SetStatusText(msg)
616 744
617 745 def enterattr(self, row, col):
618 746 try:
619 747 attr = self.table._displayattrs[col]
620 748 value = attr.value(self.table.items[row])
621 749 except Exception, exc:
622 750 self.error_output(str(exc))
623 751 else:
624 752 self._doenter(value)
625 753
626 754 def set_footer(self, item):
627 755 frame = self.GetParent().GetParent().GetParent()
628 frame.SetStatusText(" ".join([str(text) for (style, text) in ipipe.xformat(item, "footer", 20)[2]]))
756 frame.SetStatusText(" ".join([str(text) for (style, text) in ipipe.xformat(item, "footer", 20)[2]]), 0)
629 757
630 758 def enter(self, row):
631 759 try:
632 760 value = self.table.items[row]
633 761 except Exception, exc:
634 762 self.error_output(str(exc))
635 763 else:
636 764 self._doenter(value)
637 765
638 766 def detail(self, row, col):
639 767 """
640 768 shows a detail-view of the current cell
641 769 """
642 770 try:
643 771 attr = self.table._displayattrs[col]
644 772 item = self.table.items[row]
645 773 except Exception, exc:
646 774 self.error_output(str(exc))
647 775 else:
648 776 attrs = [ipipe.AttributeDetail(item, attr) for attr in ipipe.xattrs(item, "detail")]
649 777 self._doenter(attrs)
650 778
651 779 def detail_attr(self, row, col):
652 780 try:
653 781 attr = self.table._displayattrs[col]
654 782 item = attr.value(self.table.items[row])
655 783 except Exception, exc:
656 784 self.error_output(str(exc))
657 785 else:
658 786 attrs = [ipipe.AttributeDetail(item, attr) for attr in ipipe.xattrs(item, "detail")]
659 787 self._doenter(attrs)
660 788
661 789 def quit(self, result=None):
662 790 """
663 791 quit
664 792 """
665 793 frame = self.GetParent().GetParent().GetParent()
666 794 if frame.helpdialog:
667 795 frame.helpdialog.Destroy()
668 796 app = frame.parent
669 797 if app is not None:
670 798 app.result = result
671 799 frame.Close()
672 800 frame.Destroy()
673 801
674 802 def cell_doubleclicked(self, event):
675 803 self.enterattr(event.GetRow(), event.GetCol())
676 804 event.Skip()
677 805
678 806 def cell_leftclicked(self, event):
679 807 row = event.GetRow()
680 808 item = self.table.items[row]
681 809 self.set_footer(item)
682 810 event.Skip()
683 811
684 812 def pick(self, row):
685 813 """
686 814 pick a single row and return to the IPython prompt
687 815 """
688 816 try:
689 817 value = self.table.items[row]
690 818 except Exception, exc:
691 819 self.error_output(str(exc))
692 820 else:
693 821 self.quit(value)
694 822
823 def pickinput(self, row):
824 try:
825 value = self.table.items[row]
826 except Exception, exc:
827 self.error_output(str(exc))
828 else:
829 api = ipapi.get()
830 api.set_next_input(str(value))
831 self.quit(value)
832
833 def pickinputattr(self, row, col):
834 try:
835 attr = self.table._displayattrs[col]
836 value = attr.value(self.table.items[row])
837 except Exception, exc:
838 self.error_output(str(exc))
839 else:
840 api = ipapi.get()
841 api.set_next_input(str(value))
842 self.quit(value)
843
695 844 def pickrows(self, rows):
696 845 """
697 846 pick multiple rows and return to the IPython prompt
698 847 """
699 848 try:
700 849 value = [self.table.items[row] for row in rows]
701 850 except Exception, exc:
702 851 self.error_output(str(exc))
703 852 else:
704 853 self.quit(value)
705 854
706 855 def pickrowsattr(self, rows, col):
707 856 """"
708 857 pick one column from multiple rows
709 858 """
710 859 values = []
711 860 try:
712 861 attr = self.table._displayattrs[col]
713 862 for row in rows:
714 863 try:
715 864 values.append(attr.value(self.table.items[row]))
716 865 except (SystemExit, KeyboardInterrupt):
717 866 raise
718 867 except Exception:
719 868 raise #pass
720 869 except Exception, exc:
721 870 self.error_output(str(exc))
722 871 else:
723 872 self.quit(values)
724 873
725 874 def pickattr(self, row, col):
726 875 try:
727 876 attr = self.table._displayattrs[col]
728 877 value = attr.value(self.table.items[row])
729 878 except Exception, exc:
730 879 self.error_output(str(exc))
731 880 else:
732 881 self.quit(value)
733 882
734 883
735 884 class IGridPanel(wx.Panel):
736 885 # Each IGridPanel contains an IGridGrid
737 886 def __init__(self, parent, input, *attrs):
738 887 wx.Panel.__init__(self, parent, -1)
739 888 self.grid = IGridGrid(self, input, *attrs)
889 self.grid.FitInside()
740 890 sizer = wx.BoxSizer(wx.VERTICAL)
741 891 sizer.Add(self.grid, proportion=1, flag=wx.EXPAND | wx.ALL, border=10)
742 892 self.SetSizer(sizer)
743 893 sizer.Fit(self)
744 894 sizer.SetSizeHints(self)
745 895
746 896
747 897 class IGridHTMLHelp(wx.Frame):
748 898 def __init__(self, parent, title, size):
749 899 wx.Frame.__init__(self, parent, -1, title, size=size)
750 900 html = wx.html.HtmlWindow(self)
751 901 if "gtk2" in wx.PlatformInfo:
752 902 html.SetStandardFonts()
753 903 html.SetPage(help)
754 904
755 905
756 906 class IGridFrame(wx.Frame):
757 907 maxtitlelen = 30
758 908
759 909 def __init__(self, parent, input):
760 910 title = " ".join([str(text) for (style, text) in ipipe.xformat(input, "header", 20)[2]])
761 911 wx.Frame.__init__(self, None, title=title, size=(640, 480))
762 912 self.menubar = wx.MenuBar()
763 913 self.menucounter = 100
764 914 self.m_help = wx.Menu()
765 915 self.m_search = wx.Menu()
766 916 self.m_sort = wx.Menu()
917 self.m_refresh = wx.Menu()
767 918 self.notebook = wx.Notebook(self, -1, style=0)
768 919 self.statusbar = self.CreateStatusBar(1, wx.ST_SIZEGRIP)
920 self.statusbar.SetFieldsCount(2)
921 self.SetStatusWidths([-1, 200])
769 922 self.parent = parent
770 923 self._add_notebook(input)
771 924 self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
772 self.makemenu(self.m_sort, "&Sort (asc)", "Sort ascending", self.sortasc)
773 self.makemenu(self.m_sort, "Sort (&desc)", "Sort descending", self.sortdesc)
774 self.makemenu(self.m_help, "&Help", "Help", self.display_help)
775 self.makemenu(self.m_help, "&Show help in browser", "Show help in browser", self.display_help_in_browser)
776 self.makemenu(self.m_search, "&Find text", "Find text", self.enter_searchtext)
777 self.makemenu(self.m_search, "Find by &expression", "Find by expression", self.enter_searchexpression)
778 self.makemenu(self.m_search, "Find &next", "Find next", self.find_next)
779 self.makemenu(self.m_search, "Find &previous", "Find previous", self.find_previous)
925 self.makemenu(self.m_sort, "&Sort (asc)\tV", "Sort ascending", self.sortasc)
926 self.makemenu(self.m_sort, "Sort (&desc)\tShift-V", "Sort descending", self.sortdesc)
927 self.makemenu(self.m_help, "&Help\tF1", "Help", self.display_help)
928 # self.makemenu(self.m_help, "&Show help in browser", "Show help in browser", self.display_help_in_browser)
929 self.makemenu(self.m_search, "&Find text\tCTRL-F", "Find text", self.enter_searchtext)
930 self.makemenu(self.m_search, "Find by &expression\tCTRL-Shift-F", "Find by expression", self.enter_searchexpression)
931 self.makemenu(self.m_search, "Find &next\tF3", "Find next", self.find_next)
932 self.makemenu(self.m_search, "Find &previous\tShift-F3", "Find previous", self.find_previous)
933 self.makemenu(self.m_refresh, "&Refresh once \tF5", "Refresh once", self.refresh_once)
934 self.makemenu(self.m_refresh, "Refresh every &1s", "Refresh every second", self.refresh_every_second)
935 self.makemenu(self.m_refresh, "Refresh every &X seconds", "Refresh every X seconds", self.refresh_interval)
936 self.makemenu(self.m_refresh, "&Stop all refresh timers", "Stop refresh timers", self.stop_refresh)
780 937 self.menubar.Append(self.m_search, "&Find")
781 938 self.menubar.Append(self.m_sort, "&Sort")
939 self.menubar.Append(self.m_refresh, "&Refresh")
782 940 self.menubar.Append(self.m_help, "&Help")
783 941 self.SetMenuBar(self.menubar)
784 942 self.searchtext = ""
943 self.searchexpression = ""
785 944 self.helpdialog = None
945 self.refresh_interval = 1000
946 self.SetStatusText("Refreshing inactive", 1)
947
948 def refresh_once(self, event):
949 table = self.notebook.GetPage(self.notebook.GetSelection()).grid.table
950 table.refresh_content(event)
951
952 def refresh_interval(self, event):
953 table = self.notebook.GetPage(self.notebook.GetSelection()).grid.table
954 dlg = wx.TextEntryDialog(self, "Enter refresh interval (milliseconds):", "Refresh timer:", defaultValue=str(self.refresh_interval))
955 if dlg.ShowModal() == wx.ID_OK:
956 try:
957 milliseconds = int(dlg.GetValue())
958 except ValueError, exc:
959 self.SetStatusText(str(exc))
960 else:
961 table.timer.Start(milliseconds=milliseconds, oneShot=False)
962 self.SetStatusText("Refresh timer set to %s ms" % milliseconds)
963 self.SetStatusText("Refresh interval: %s ms" % milliseconds, 1)
964 self.refresh_interval = milliseconds
965 dlg.Destroy()
966
967 def stop_refresh(self, event):
968 for i in xrange(self.notebook.GetPageCount()):
969 nb = self.notebook.GetPage(i)
970 nb.grid.table.timer.Stop()
971 self.SetStatusText("Refreshing inactive", 1)
972
973 def refresh_every_second(self, event):
974 table = self.notebook.GetPage(self.notebook.GetSelection()).grid.table
975 table.timer.Start(milliseconds=1000, oneShot=False)
976 self.SetStatusText("Refresh interval: 1000 ms", 1)
786 977
787 978 def sortasc(self, event):
788 979 grid = self.notebook.GetPage(self.notebook.GetSelection()).grid
789 980 grid.sortattrasc()
790 981
791 982 def sortdesc(self, event):
792 983 grid = self.notebook.GetPage(self.notebook.GetSelection()).grid
793 984 grid.sortattrdesc()
794 985
795 986 def find_previous(self, event):
796 987 """
797 988 find previous occurrences
798 989 """
799 if self.searchtext:
800 990 grid = self.notebook.GetPage(self.notebook.GetSelection()).grid
991 if self.searchtext:
801 992 row = grid.GetGridCursorRow()
802 993 col = grid.GetGridCursorCol()
994 self.SetStatusText('Search mode: text; looking for %s' % self.searchtext)
803 995 if col-1 >= 0:
804 996 grid.search(self.searchtext, row, col-1, False)
805 997 else:
806 998 grid.search(self.searchtext, row-1, grid.table.GetNumberCols()-1, False)
999 elif self.searchexpression:
1000 self.SetStatusText("Search mode: expression; looking for %s" % repr(self.searchexpression)[2:-1])
1001 grid.searchexpression(searchexp=self.searchexpression, search_forward=False)
807 1002 else:
808 self.enter_searchtext(event)
1003 self.SetStatusText("No search yet: please enter search-text or -expression")
809 1004
810 1005 def find_next(self, event):
811 1006 """
812 1007 find the next occurrence
813 1008 """
814 if self.searchtext:
815 1009 grid = self.notebook.GetPage(self.notebook.GetSelection()).grid
1010 if self.searchtext != "":
816 1011 row = grid.GetGridCursorRow()
817 1012 col = grid.GetGridCursorCol()
1013 self.SetStatusText('Search mode: text; looking for %s' % self.searchtext)
818 1014 if col+1 < grid.table.GetNumberCols():
819 1015 grid.search(self.searchtext, row, col+1)
820 1016 else:
821 1017 grid.search(self.searchtext, row+1, 0)
1018 elif self.searchexpression != "":
1019 self.SetStatusText('Search mode: expression; looking for %s' % repr(self.searchexpression)[2:-1])
1020 grid.searchexpression(searchexp=self.searchexpression)
822 1021 else:
823 self.enter_searchtext(event)
1022 self.SetStatusText("No search yet: please enter search-text or -expression")
824 1023
825 1024 def display_help(self, event):
826 1025 """
827 1026 Display a help dialog
828 1027 """
829 1028 if self.helpdialog:
830 1029 self.helpdialog.Destroy()
831 1030 self.helpdialog = IGridHTMLHelp(None, title="Help", size=wx.Size(600,400))
832 1031 self.helpdialog.Show()
833 1032
834 1033 def display_help_in_browser(self, event):
835 1034 """
836 1035 Show the help-HTML in a browser (as a ``HtmlWindow`` does not understand
837 1036 CSS this looks better)
838 1037 """
839 1038 filename = urllib.pathname2url(os.path.abspath(os.path.join(os.path.dirname(__file__), "igrid_help.html")))
840 1039 if not filename.startswith("file"):
841 1040 filename = "file:" + filename
842 1041 webbrowser.open(filename, new=1, autoraise=True)
843 1042
844 1043 def enter_searchexpression(self, event):
845 pass
1044 dlg = wx.TextEntryDialog(self, "Find:", "Find matching expression:", defaultValue=self.searchexpression)
1045 if dlg.ShowModal() == wx.ID_OK:
1046 self.searchexpression = dlg.GetValue()
1047 self.searchtext = ""
1048 self.SetStatusText('Search mode: expression; looking for %s' % repr(self.searchexpression)[2:-1])
1049 self.notebook.GetPage(self.notebook.GetSelection()).grid.searchexpression(self.searchexpression)
1050 dlg.Destroy()
846 1051
847 1052 def makemenu(self, menu, label, help, cmd):
848 1053 menu.Append(self.menucounter, label, help)
849 1054 self.Bind(wx.EVT_MENU, cmd, id=self.menucounter)
850 1055 self.menucounter += 1
851 1056
852 1057 def _add_notebook(self, input, *attrs):
853 1058 # Adds another notebook which has the starting object ``input``
854 1059 panel = IGridPanel(self.notebook, input, *attrs)
855 1060 text = str(ipipe.xformat(input, "header", self.maxtitlelen)[2])
856 1061 if len(text) >= self.maxtitlelen:
857 1062 text = text[:self.maxtitlelen].rstrip(".") + "..."
858 1063 self.notebook.AddPage(panel, text, True)
859 1064 panel.grid.SetFocus()
860 1065 self.Layout()
861 1066
862 1067 def OnCloseWindow(self, event):
863 1068 self.Destroy()
864 1069
865 1070 def enter_searchtext(self, event):
866 1071 # Displays a dialog asking for the searchtext
867 dlg = wx.TextEntryDialog(self, "Find:", "Find in list")
1072 dlg = wx.TextEntryDialog(self, "Find:", "Find in list", defaultValue=self.searchtext)
868 1073 if dlg.ShowModal() == wx.ID_OK:
869 1074 self.searchtext = dlg.GetValue()
870 self.notebook.GetPage(self.notebook.GetSelection()).grid.search(self.searchtext, 0, 0)
1075 self.searchexpression = ""
1076 self.SetStatusText('Search mode: text; looking for %s' % self.searchtext)
1077 self.notebook.GetPage(self.notebook.GetSelection()).grid.search(self.searchtext)
871 1078 dlg.Destroy()
872 1079
873 1080
874 1081 class App(wx.App):
875 1082 def __init__(self, input):
876 1083 self.input = input
877 1084 self.result = None # Result to be returned to IPython. Set by quit().
878 1085 wx.App.__init__(self)
879 1086
880 1087 def OnInit(self):
881 1088 frame = IGridFrame(self, self.input)
882 1089 frame.Show()
883 1090 self.SetTopWindow(frame)
884 1091 frame.Raise()
885 1092 return True
886 1093
887 1094
888 1095 class igrid(ipipe.Display):
889 1096 """
890 1097 This is a wx-based display object that can be used instead of ``ibrowse``
891 1098 (which is curses-based) or ``idump`` (which simply does a print).
892 1099 """
893 1100 if wx.VERSION < (2, 7):
894 1101 def display(self):
895 1102 try:
896 1103 # Try to create a "standalone" from. If this works we're probably
897 1104 # running with -wthread.
898 1105 # Note that this sets the parent of the frame to None, but we can't
899 1106 # pass a result object back to the shell anyway.
900 1107 frame = IGridFrame(None, self.input)
901 1108 frame.Show()
902 1109 frame.Raise()
903 1110 except wx.PyNoAppError:
904 1111 # There's no wx application yet => create one.
905 1112 app = App(self.input)
906 1113 app.MainLoop()
907 1114 return app.result
908 1115 else:
909 1116 # With wx 2.7 it gets simpler.
910 1117 def display(self):
911 1118 app = App(self.input)
912 1119 app.MainLoop()
913 1120 return app.result
914 1121
General Comments 0
You need to be logged in to leave comments. Login now