igrid.py
914 lines
| 31.5 KiB
| text/x-python
|
PythonLexer
walter.doerwald
|
r538 | # -*- coding: iso-8859-1 -*- | ||
import ipipe, os, webbrowser, urllib | ||||
import wx | ||||
import wx.grid, wx.html | ||||
try: | ||||
sorted | ||||
except NameError: | ||||
from ipipe import sorted | ||||
__all__ = ["igrid"] | ||||
walter.doerwald
|
r641 | help = """ | ||
<?xml version='1.0' encoding='iso-8859-1'?> | ||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> | ||||
<html> | ||||
<head> | ||||
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" /> | ||||
<link rel="stylesheet" href="igrid_help.css" type="text/css" /> | ||||
<title>igrid help</title> | ||||
</head> | ||||
<body> | ||||
<h1>igrid help</h1> | ||||
<h2>Commands</h2> | ||||
<h3>pick (P)</h3> | ||||
<p>Pick the whole row (object is available as "_")</p> | ||||
<h3>pickattr (Shift-P)</h3> | ||||
<p>Pick the attribute under the cursor</p> | ||||
<h3>pickallattrs (Shift-C)</h3> | ||||
<p>Pick' the complete column under the cursor (i.e. the attribute under the | ||||
cursor) from all currently fetched objects. These attributes will be returned | ||||
as a list.</p> | ||||
<h3>enter (E)</h3> | ||||
<p>Enter the object under the cursor. (what this mean depends on the object | ||||
itself, i.e. how it implements iteration). This opens a new browser 'level'.</p> | ||||
<h3>enterattr (Shift-E)</h3> | ||||
<p>Enter the attribute under the cursor.</p> | ||||
<h3>detail (D)</h3> | ||||
<p>Show a detail view of the object under the cursor. This shows the name, | ||||
type, doc string and value of the object attributes (and it might show more | ||||
attributes than in the list view, depending on the object).</p> | ||||
<h3>detailattr (Shift-D)</h3> | ||||
<p>Show a detail view of the attribute under the cursor.</p> | ||||
<h3>pickrows (M)</h3> | ||||
<p>Pick multiple selected rows (M)</p> | ||||
<h3>pickrowsattr (CTRL-M)</h3> | ||||
<p>From multiple selected rows pick the cells matching the attribute the cursor is in (CTRL-M)</p> | ||||
<h3>find (CTRL-F)</h3> | ||||
<p>Find text</p> | ||||
<h3>find_next (F3)</h3> | ||||
<p>Find next occurrence of the searchtext</p> | ||||
<h3>find_previous (Shift-F3)</h3> | ||||
<p>Find previous occurrence of the searchtext </p> | ||||
<h3>sortattrasc (V)</h3> | ||||
<p>Sort the objects (in ascending order) using the attribute under the cursor as the sort key.</p> | ||||
<h3>sortattrdesc (Shift-V)</h3> | ||||
<p>Sort the objects (in descending order) using the attribute under the cursor as the sort key.</p> | ||||
<h3>leave (Backspace, DEL, X)</h3> | ||||
<p>Close current tab (and all the tabs to the right of the current one).</h3> | ||||
<h3>quit (ESC,Q)</h3> | ||||
<p>Quit igrid and return to the IPython prompt.</p> | ||||
<h2>Navigation</h2> | ||||
<h3>Jump to the last column of the current row (END, CTRL-E, CTRL-Right)</h3> | ||||
<h3>Jump to the first column of the current row (HOME, CTRL-A, CTRL-Left)</h3> | ||||
<h3>Move the cursor one column to the left (<)</h3> | ||||
<h3>Move the cursor one column to the right (>)</h3> | ||||
<h3>Jump to the first row in the current column (CTRL-Up)</h3> | ||||
<h3>Jump to the last row in the current column (CTRL-Down)</h3> | ||||
</body> | ||||
</html> | ||||
""" | ||||
walter.doerwald
|
r538 | class IGridRenderer(wx.grid.PyGridCellRenderer): | ||
""" | ||||
This is a custom renderer for our IGridGrid | ||||
""" | ||||
def __init__(self, table): | ||||
self.maxchars = 200 | ||||
self.table = table | ||||
self.colormap = ( | ||||
( 0, 0, 0), | ||||
(174, 0, 0), | ||||
( 0, 174, 0), | ||||
(174, 174, 0), | ||||
( 0, 0, 174), | ||||
(174, 0, 174), | ||||
( 0, 174, 174), | ||||
( 64, 64, 64) | ||||
) | ||||
wx.grid.PyGridCellRenderer.__init__(self) | ||||
def _getvalue(self, row, col): | ||||
try: | ||||
walter.doerwald
|
r542 | value = self.table._displayattrs[col].value(self.table.items[row]) | ||
walter.doerwald
|
r538 | (align, width, text) = ipipe.xformat(value, "cell", self.maxchars) | ||
except Exception, exc: | ||||
(align, width, text) = ipipe.xformat(exc, "cell", self.maxchars) | ||||
return (align, text) | ||||
def GetBestSize(self, grid, attr, dc, row, col): | ||||
text = grid.GetCellValue(row, col) | ||||
(align, text) = self._getvalue(row, col) | ||||
dc.SetFont(attr.GetFont()) | ||||
(w, h) = dc.GetTextExtent(str(text)) | ||||
return wx.Size(min(w+2, 600), h+2) # add border | ||||
def Draw(self, grid, attr, dc, rect, row, col, isSelected): | ||||
""" | ||||
Takes care of drawing everything in the cell; aligns the text | ||||
""" | ||||
text = grid.GetCellValue(row, col) | ||||
(align, text) = self._getvalue(row, col) | ||||
if isSelected: | ||||
bg = grid.GetSelectionBackground() | ||||
else: | ||||
bg = ["white", (240, 240, 240)][row%2] | ||||
dc.SetTextBackground(bg) | ||||
dc.SetBrush(wx.Brush(bg, wx.SOLID)) | ||||
dc.SetPen(wx.TRANSPARENT_PEN) | ||||
dc.SetFont(attr.GetFont()) | ||||
dc.DrawRectangleRect(rect) | ||||
dc.SetClippingRect(rect) | ||||
# Format the text | ||||
if align == -1: # left alignment | ||||
(width, height) = dc.GetTextExtent(str(text)) | ||||
x = rect[0]+1 | ||||
y = rect[1]+0.5*(rect[3]-height) | ||||
for (style, part) in text: | ||||
if isSelected: | ||||
fg = grid.GetSelectionForeground() | ||||
else: | ||||
fg = self.colormap[style.fg] | ||||
dc.SetTextForeground(fg) | ||||
(w, h) = dc.GetTextExtent(part) | ||||
dc.DrawText(part, x, y) | ||||
x += w | ||||
elif align == 0: # center alignment | ||||
(width, height) = dc.GetTextExtent(str(text)) | ||||
x = rect[0]+0.5*(rect[2]-width) | ||||
y = rect[1]+0.5*(rect[3]-height) | ||||
for (style, part) in text: | ||||
if isSelected: | ||||
fg = grid.GetSelectionForeground() | ||||
else: | ||||
fg = self.colormap[style.fg] | ||||
dc.SetTextForeground(fg) | ||||
(w, h) = dc.GetTextExtent(part) | ||||
dc.DrawText(part, x, y) | ||||
x += w | ||||
else: # right alignment | ||||
(width, height) = dc.GetTextExtent(str(text)) | ||||
x = rect[0]+rect[2]-1 | ||||
y = rect[1]+0.5*(rect[3]-height) | ||||
for (style, part) in reversed(text): | ||||
(w, h) = dc.GetTextExtent(part) | ||||
x -= w | ||||
if isSelected: | ||||
fg = grid.GetSelectionForeground() | ||||
else: | ||||
fg = self.colormap[style.fg] | ||||
dc.SetTextForeground(fg) | ||||
dc.DrawText(part, x, y) | ||||
dc.DestroyClippingRegion() | ||||
def Clone(self): | ||||
return IGridRenderer(self.table) | ||||
class IGridTable(wx.grid.PyGridTableBase): | ||||
# The data table for the ``IGridGrid``. Some dirty tricks were used here: | ||||
# ``GetValue()`` does not get any values (or at least it does not return | ||||
# anything, accessing the values is done by the renderer) | ||||
# but rather tries to fetch the objects which were requested into the table. | ||||
# General behaviour is: Fetch the first X objects. If the user scrolls down | ||||
# to the last object another bunch of X objects is fetched (if possible) | ||||
def __init__(self, input, fontsize, *attrs): | ||||
wx.grid.PyGridTableBase.__init__(self) | ||||
self.input = input | ||||
self.iterator = ipipe.xiter(input) | ||||
self.items = [] | ||||
walter.doerwald
|
r542 | self.attrs = [ipipe.upgradexattr(attr) for attr in attrs] | ||
self._displayattrs = self.attrs[:] | ||||
self._displayattrset = set(self.attrs) | ||||
self._sizing = False | ||||
walter.doerwald
|
r538 | self.fontsize = fontsize | ||
walter.doerwald
|
r542 | self._fetch(1) | ||
walter.doerwald
|
r538 | |||
def GetAttr(self, *args): | ||||
attr = wx.grid.GridCellAttr() | ||||
attr.SetFont(wx.Font(self.fontsize, wx.TELETYPE, wx.NORMAL, wx.NORMAL)) | ||||
return attr | ||||
def GetNumberRows(self): | ||||
return len(self.items) | ||||
def GetNumberCols(self): | ||||
walter.doerwald
|
r542 | return len(self._displayattrs) | ||
walter.doerwald
|
r538 | |||
def GetColLabelValue(self, col): | ||||
walter.doerwald
|
r542 | if col < len(self._displayattrs): | ||
return self._displayattrs[col].name() | ||||
walter.doerwald
|
r538 | else: | ||
return "" | ||||
def GetRowLabelValue(self, row): | ||||
return str(row) | ||||
def IsEmptyCell(self, row, col): | ||||
return False | ||||
walter.doerwald
|
r542 | def _append(self, item): | ||
self.items.append(item) | ||||
# Nothing to do if the set of attributes has been fixed by the user | ||||
if not self.attrs: | ||||
for attr in ipipe.xattrs(item): | ||||
attr = ipipe.upgradexattr(attr) | ||||
if attr not in self._displayattrset: | ||||
self._displayattrs.append(attr) | ||||
self._displayattrset.add(attr) | ||||
def _fetch(self, count): | ||||
walter.doerwald
|
r538 | # Try to fill ``self.items`` with at least ``count`` objects. | ||
have = len(self.items) | ||||
while self.iterator is not None and have < count: | ||||
try: | ||||
item = self.iterator.next() | ||||
except StopIteration: | ||||
self.iterator = None | ||||
break | ||||
except (KeyboardInterrupt, SystemExit): | ||||
raise | ||||
except Exception, exc: | ||||
have += 1 | ||||
walter.doerwald
|
r627 | self._append(exc) | ||
walter.doerwald
|
r538 | self.iterator = None | ||
break | ||||
else: | ||||
have += 1 | ||||
walter.doerwald
|
r542 | self._append(item) | ||
walter.doerwald
|
r538 | |||
def GetValue(self, row, col): | ||||
# some kind of dummy-function: does not return anything but ""; | ||||
# (The value isn't use anyway) | ||||
# its main task is to trigger the fetch of new objects | ||||
walter.doerwald
|
r542 | had_cols = self._displayattrs[:] | ||
walter.doerwald
|
r538 | had_rows = len(self.items) | ||
walter.doerwald
|
r542 | if row == had_rows - 1 and self.iterator is not None and not self._sizing: | ||
self._fetch(row + 20) | ||||
walter.doerwald
|
r538 | have_rows = len(self.items) | ||
walter.doerwald
|
r542 | have_cols = len(self._displayattrs) | ||
walter.doerwald
|
r538 | if have_rows > had_rows: | ||
msg = wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_NOTIFY_ROWS_APPENDED, have_rows - had_rows) | ||||
self.GetView().ProcessTableMessage(msg) | ||||
walter.doerwald
|
r542 | self._sizing = True | ||
walter.doerwald
|
r538 | self.GetView().AutoSizeColumns(False) | ||
walter.doerwald
|
r542 | self._sizing = False | ||
walter.doerwald
|
r538 | if row >= have_rows: | ||
return "" | ||||
walter.doerwald
|
r542 | if self._displayattrs != had_cols: | ||
walter.doerwald
|
r538 | msg = wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_NOTIFY_COLS_APPENDED, have_cols - len(had_cols)) | ||
self.GetView().ProcessTableMessage(msg) | ||||
return "" | ||||
def SetValue(self, row, col, value): | ||||
pass | ||||
class IGridGrid(wx.grid.Grid): | ||||
# The actual grid | ||||
# all methods for selecting/sorting/picking/... data are implemented here | ||||
def __init__(self, panel, input, *attrs): | ||||
wx.grid.Grid.__init__(self, panel) | ||||
fontsize = 9 | ||||
self.input = input | ||||
self.table = IGridTable(self.input, fontsize, *attrs) | ||||
self.SetTable(self.table, True) | ||||
self.SetSelectionMode(wx.grid.Grid.wxGridSelectRows) | ||||
self.SetDefaultRenderer(IGridRenderer(self.table)) | ||||
self.EnableEditing(False) | ||||
self.Bind(wx.EVT_KEY_DOWN, self.key_pressed) | ||||
self.Bind(wx.grid.EVT_GRID_CELL_LEFT_DCLICK, self.cell_doubleclicked) | ||||
walter.doerwald
|
r546 | self.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, self.cell_leftclicked) | ||
walter.doerwald
|
r538 | self.Bind(wx.grid.EVT_GRID_LABEL_LEFT_DCLICK, self.label_doubleclicked) | ||
self.Bind(wx.grid.EVT_GRID_LABEL_LEFT_CLICK, self.on_label_leftclick) | ||||
self.Bind(wx.grid.EVT_GRID_RANGE_SELECT, self._on_selected_range) | ||||
self.Bind(wx.grid.EVT_GRID_SELECT_CELL, self._on_selected_cell) | ||||
self.current_selection = set() | ||||
self.maxchars = 200 | ||||
def on_label_leftclick(self, event): | ||||
event.Skip() | ||||
def error_output(self, text): | ||||
wx.Bell() | ||||
frame = self.GetParent().GetParent().GetParent() | ||||
frame.SetStatusText(text) | ||||
def _on_selected_range(self, event): | ||||
# Internal update to the selection tracking lists | ||||
if event.Selecting(): | ||||
# adding to the list... | ||||
self.current_selection.update(xrange(event.GetTopRow(), event.GetBottomRow()+1)) | ||||
else: | ||||
# removal from list | ||||
for index in xrange( event.GetTopRow(), event.GetBottomRow()+1): | ||||
self.current_selection.discard(index) | ||||
event.Skip() | ||||
def _on_selected_cell(self, event): | ||||
# Internal update to the selection tracking list | ||||
self.current_selection = set([event.GetRow()]) | ||||
event.Skip() | ||||
def sort(self, key, reverse=False): | ||||
""" | ||||
Sort the current list of items using the key function ``key``. If | ||||
``reverse`` is true the sort order is reversed. | ||||
""" | ||||
row = self.GetGridCursorRow() | ||||
col = self.GetGridCursorCol() | ||||
curitem = self.table.items[row] # Remember where the cursor is now | ||||
# Sort items | ||||
def realkey(item): | ||||
walter.doerwald
|
r546 | try: | ||
return key(item) | ||||
except (KeyboardInterrupt, SystemExit): | ||||
raise | ||||
except Exception: | ||||
return None | ||||
walter.doerwald
|
r538 | try: | ||
self.table.items = ipipe.deque(sorted(self.table.items, key=realkey, reverse=reverse)) | ||||
except TypeError, exc: | ||||
self.error_output("Exception encountered: %s" % exc) | ||||
return | ||||
# Find out where the object under the cursor went | ||||
for (i, item) in enumerate(self.table.items): | ||||
if item is curitem: | ||||
self.SetGridCursor(i,col) | ||||
self.MakeCellVisible(i,col) | ||||
self.Refresh() | ||||
def sortattrasc(self): | ||||
""" | ||||
Sort in ascending order; sorting criteria is the current attribute | ||||
""" | ||||
col = self.GetGridCursorCol() | ||||
walter.doerwald
|
r547 | attr = self.table._displayattrs[col] | ||
walter.doerwald
|
r538 | frame = self.GetParent().GetParent().GetParent() | ||
if attr is ipipe.noitem: | ||||
self.error_output("no column under cursor") | ||||
return | ||||
frame.SetStatusText("sort by %s (ascending)" % attr.name()) | ||||
def key(item): | ||||
try: | ||||
return attr.value(item) | ||||
except (KeyboardInterrupt, SystemExit): | ||||
raise | ||||
except Exception: | ||||
return None | ||||
self.sort(key) | ||||
def sortattrdesc(self): | ||||
""" | ||||
Sort in descending order; sorting criteria is the current attribute | ||||
""" | ||||
col = self.GetGridCursorCol() | ||||
walter.doerwald
|
r542 | attr = self.table._displayattrs[col] | ||
walter.doerwald
|
r538 | frame = self.GetParent().GetParent().GetParent() | ||
if attr is ipipe.noitem: | ||||
self.error_output("no column under cursor") | ||||
return | ||||
frame.SetStatusText("sort by %s (descending)" % attr.name()) | ||||
def key(item): | ||||
try: | ||||
return attr.value(item) | ||||
except (KeyboardInterrupt, SystemExit): | ||||
raise | ||||
except Exception: | ||||
return None | ||||
self.sort(key, reverse=True) | ||||
def label_doubleclicked(self, event): | ||||
row = event.GetRow() | ||||
col = event.GetCol() | ||||
if col == -1: | ||||
self.enter(row) | ||||
def _getvalue(self, row, col): | ||||
""" | ||||
Gets the text which is displayed at ``(row, col)`` | ||||
""" | ||||
try: | ||||
walter.doerwald
|
r542 | value = self.table._displayattrs[col].value(self.table.items[row]) | ||
walter.doerwald
|
r538 | (align, width, text) = ipipe.xformat(value, "cell", self.maxchars) | ||
except IndexError: | ||||
raise IndexError | ||||
except Exception, exc: | ||||
(align, width, text) = ipipe.xformat(exc, "cell", self.maxchars) | ||||
return text | ||||
def search(self, searchtext, startrow=0, startcol=0, search_forward=True): | ||||
""" | ||||
search for ``searchtext``, starting in ``(startrow, startcol)``; | ||||
if ``search_forward`` is true the direction is "forward" | ||||
""" | ||||
row = startrow | ||||
searchtext = searchtext.lower() | ||||
if search_forward: | ||||
while True: | ||||
for col in xrange(startcol, self.table.GetNumberCols()): | ||||
try: | ||||
foo = self.table.GetValue(row, col) | ||||
text = self._getvalue(row, col) | ||||
if searchtext in text.string().lower(): | ||||
self.SetGridCursor(row, col) | ||||
self.MakeCellVisible(row, col) | ||||
return | ||||
except IndexError: | ||||
return | ||||
startcol = 0 | ||||
row += 1 | ||||
else: | ||||
while True: | ||||
for col in xrange(startcol, -1, -1): | ||||
try: | ||||
foo = self.table.GetValue(row, col) | ||||
text = self._getvalue(row, col) | ||||
if searchtext in text.string().lower(): | ||||
self.SetGridCursor(row, col) | ||||
self.MakeCellVisible(row, col) | ||||
return | ||||
except IndexError: | ||||
return | ||||
startcol = self.table.GetNumberCols()-1 | ||||
row -= 1 | ||||
def key_pressed(self, event): | ||||
""" | ||||
Maps pressed keys to functions | ||||
""" | ||||
frame = self.GetParent().GetParent().GetParent() | ||||
frame.SetStatusText("") | ||||
sh = event.ShiftDown() | ||||
ctrl = event.ControlDown() | ||||
keycode = event.GetKeyCode() | ||||
if keycode == ord("P"): | ||||
row = self.GetGridCursorRow() | ||||
walter.doerwald
|
r578 | if sh: | ||
walter.doerwald
|
r538 | col = self.GetGridCursorCol() | ||
self.pickattr(row, col) | ||||
else: | ||||
self.pick(row) | ||||
elif keycode == ord("M"): | ||||
if ctrl: | ||||
col = self.GetGridCursorCol() | ||||
self.pickrowsattr(sorted(self.current_selection), col) | ||||
else: | ||||
self.pickrows(sorted(self.current_selection)) | ||||
elif keycode in (wx.WXK_BACK, wx.WXK_DELETE, ord("X")) and not (ctrl or sh): | ||||
self.delete_current_notebook() | ||||
walter.doerwald
|
r578 | elif keycode in (ord("E"), ord("\r")): | ||
walter.doerwald
|
r538 | row = self.GetGridCursorRow() | ||
walter.doerwald
|
r578 | if sh: | ||
col = self.GetGridCursorCol() | ||||
self.enterattr(row, col) | ||||
else: | ||||
self.enter(row) | ||||
walter.doerwald
|
r538 | elif keycode == ord("E") and ctrl: | ||
row = self.GetGridCursorRow() | ||||
self.SetGridCursor(row, self.GetNumberCols()-1) | ||||
elif keycode == wx.WXK_HOME or (keycode == ord("A") and ctrl): | ||||
row = self.GetGridCursorRow() | ||||
self.SetGridCursor(row, 0) | ||||
elif keycode == ord("C") and sh: | ||||
col = self.GetGridCursorCol() | ||||
walter.doerwald
|
r542 | attr = self.table._displayattrs[col] | ||
walter.doerwald
|
r573 | result = [] | ||
walter.doerwald
|
r538 | for i in xrange(self.GetNumberRows()): | ||
walter.doerwald
|
r573 | result.append(self.table._displayattrs[col].value(self.table.items[i])) | ||
self.quit(result) | ||||
walter.doerwald
|
r538 | elif keycode in (wx.WXK_ESCAPE, ord("Q")) and not (ctrl or sh): | ||
self.quit() | ||||
elif keycode == ord("<"): | ||||
row = self.GetGridCursorRow() | ||||
col = self.GetGridCursorCol() | ||||
if not event.ShiftDown(): | ||||
newcol = col - 1 | ||||
if newcol >= 0: | ||||
self.SetGridCursor(row, col - 1) | ||||
else: | ||||
newcol = col + 1 | ||||
if newcol < self.GetNumberCols(): | ||||
self.SetGridCursor(row, col + 1) | ||||
elif keycode == ord("D"): | ||||
col = self.GetGridCursorCol() | ||||
row = self.GetGridCursorRow() | ||||
if not sh: | ||||
self.detail(row, col) | ||||
else: | ||||
self.detail_attr(row, col) | ||||
elif keycode == ord("F") and ctrl: | ||||
frame.enter_searchtext(event) | ||||
elif keycode == wx.WXK_F3: | ||||
if sh: | ||||
frame.find_previous(event) | ||||
else: | ||||
frame.find_next(event) | ||||
elif keycode == ord("V"): | ||||
if sh: | ||||
self.sortattrdesc() | ||||
else: | ||||
self.sortattrasc() | ||||
walter.doerwald
|
r546 | elif keycode == wx.WXK_DOWN: | ||
row = self.GetGridCursorRow() | ||||
try: | ||||
item = self.table.items[row+1] | ||||
except IndexError: | ||||
item = self.table.items[row] | ||||
self.set_footer(item) | ||||
event.Skip() | ||||
elif keycode == wx.WXK_UP: | ||||
row = self.GetGridCursorRow() | ||||
if row >= 1: | ||||
item = self.table.items[row-1] | ||||
else: | ||||
item = self.table.items[row] | ||||
self.set_footer(item) | ||||
event.Skip() | ||||
elif keycode == wx.WXK_RIGHT: | ||||
row = self.GetGridCursorRow() | ||||
item = self.table.items[row] | ||||
self.set_footer(item) | ||||
event.Skip() | ||||
elif keycode == wx.WXK_LEFT: | ||||
row = self.GetGridCursorRow() | ||||
item = self.table.items[row] | ||||
self.set_footer(item) | ||||
event.Skip() | ||||
walter.doerwald
|
r538 | else: | ||
event.Skip() | ||||
def delete_current_notebook(self): | ||||
""" | ||||
deletes the current notebook tab | ||||
""" | ||||
panel = self.GetParent() | ||||
nb = panel.GetParent() | ||||
current = nb.GetSelection() | ||||
count = nb.GetPageCount() | ||||
if count > 1: | ||||
for i in xrange(count-1, current-1, -1): | ||||
nb.DeletePage(i) | ||||
nb.GetCurrentPage().grid.SetFocus() | ||||
else: | ||||
frame = nb.GetParent() | ||||
frame.SetStatusText("This is the last level!") | ||||
def _doenter(self, value, *attrs): | ||||
""" | ||||
"enter" a special item resulting in a new notebook tab | ||||
""" | ||||
panel = self.GetParent() | ||||
nb = panel.GetParent() | ||||
frame = nb.GetParent() | ||||
current = nb.GetSelection() | ||||
count = nb.GetPageCount() | ||||
try: # if we want to enter something non-iterable, e.g. a function | ||||
if current + 1 == count and value is not self.input: # we have an event in the last tab | ||||
frame._add_notebook(value, *attrs) | ||||
elif value != self.input: # we have to delete all tabs newer than [panel] first | ||||
for i in xrange(count-1, current, -1): # some tabs don't close if we don't close in *reverse* order | ||||
nb.DeletePage(i) | ||||
frame._add_notebook(value) | ||||
except TypeError, exc: | ||||
if exc.__class__.__module__ == "exceptions": | ||||
msg = "%s: %s" % (exc.__class__.__name__, exc) | ||||
else: | ||||
msg = "%s.%s: %s" % (exc.__class__.__module__, exc.__class__.__name__, exc) | ||||
frame.SetStatusText(msg) | ||||
def enterattr(self, row, col): | ||||
try: | ||||
walter.doerwald
|
r542 | attr = self.table._displayattrs[col] | ||
walter.doerwald
|
r538 | value = attr.value(self.table.items[row]) | ||
except Exception, exc: | ||||
self.error_output(str(exc)) | ||||
else: | ||||
self._doenter(value) | ||||
walter.doerwald
|
r546 | def set_footer(self, item): | ||
frame = self.GetParent().GetParent().GetParent() | ||||
frame.SetStatusText(" ".join([str(text) for (style, text) in ipipe.xformat(item, "footer", 20)[2]])) | ||||
walter.doerwald
|
r538 | def enter(self, row): | ||
try: | ||||
value = self.table.items[row] | ||||
except Exception, exc: | ||||
self.error_output(str(exc)) | ||||
else: | ||||
self._doenter(value) | ||||
def detail(self, row, col): | ||||
""" | ||||
shows a detail-view of the current cell | ||||
""" | ||||
try: | ||||
walter.doerwald
|
r542 | attr = self.table._displayattrs[col] | ||
walter.doerwald
|
r538 | item = self.table.items[row] | ||
except Exception, exc: | ||||
self.error_output(str(exc)) | ||||
else: | ||||
attrs = [ipipe.AttributeDetail(item, attr) for attr in ipipe.xattrs(item, "detail")] | ||||
self._doenter(attrs) | ||||
def detail_attr(self, row, col): | ||||
try: | ||||
walter.doerwald
|
r542 | attr = self.table._displayattrs[col] | ||
walter.doerwald
|
r538 | item = attr.value(self.table.items[row]) | ||
except Exception, exc: | ||||
self.error_output(str(exc)) | ||||
else: | ||||
attrs = [ipipe.AttributeDetail(item, attr) for attr in ipipe.xattrs(item, "detail")] | ||||
self._doenter(attrs) | ||||
walter.doerwald
|
r573 | def quit(self, result=None): | ||
walter.doerwald
|
r538 | """ | ||
quit | ||||
""" | ||||
frame = self.GetParent().GetParent().GetParent() | ||||
walter.doerwald
|
r539 | if frame.helpdialog: | ||
frame.helpdialog.Destroy() | ||||
walter.doerwald
|
r574 | app = frame.parent | ||
if app is not None: | ||||
app.result = result | ||||
walter.doerwald
|
r538 | frame.Close() | ||
frame.Destroy() | ||||
def cell_doubleclicked(self, event): | ||||
self.enterattr(event.GetRow(), event.GetCol()) | ||||
walter.doerwald
|
r546 | event.Skip() | ||
walter.doerwald
|
r538 | |||
walter.doerwald
|
r546 | def cell_leftclicked(self, event): | ||
row = event.GetRow() | ||||
item = self.table.items[row] | ||||
self.set_footer(item) | ||||
event.Skip() | ||||
walter.doerwald
|
r538 | def pick(self, row): | ||
""" | ||||
pick a single row and return to the IPython prompt | ||||
""" | ||||
try: | ||||
value = self.table.items[row] | ||||
except Exception, exc: | ||||
self.error_output(str(exc)) | ||||
else: | ||||
self.quit(value) | ||||
def pickrows(self, rows): | ||||
""" | ||||
pick multiple rows and return to the IPython prompt | ||||
""" | ||||
try: | ||||
value = [self.table.items[row] for row in rows] | ||||
except Exception, exc: | ||||
self.error_output(str(exc)) | ||||
else: | ||||
self.quit(value) | ||||
def pickrowsattr(self, rows, col): | ||||
"""" | ||||
pick one column from multiple rows | ||||
""" | ||||
values = [] | ||||
try: | ||||
walter.doerwald
|
r542 | attr = self.table._displayattrs[col] | ||
walter.doerwald
|
r538 | for row in rows: | ||
try: | ||||
values.append(attr.value(self.table.items[row])) | ||||
except (SystemExit, KeyboardInterrupt): | ||||
raise | ||||
except Exception: | ||||
raise #pass | ||||
except Exception, exc: | ||||
self.error_output(str(exc)) | ||||
else: | ||||
self.quit(values) | ||||
def pickattr(self, row, col): | ||||
try: | ||||
walter.doerwald
|
r542 | attr = self.table._displayattrs[col] | ||
walter.doerwald
|
r538 | value = attr.value(self.table.items[row]) | ||
except Exception, exc: | ||||
self.error_output(str(exc)) | ||||
else: | ||||
self.quit(value) | ||||
class IGridPanel(wx.Panel): | ||||
# Each IGridPanel contains an IGridGrid | ||||
def __init__(self, parent, input, *attrs): | ||||
wx.Panel.__init__(self, parent, -1) | ||||
self.grid = IGridGrid(self, input, *attrs) | ||||
sizer = wx.BoxSizer(wx.VERTICAL) | ||||
sizer.Add(self.grid, proportion=1, flag=wx.EXPAND | wx.ALL, border=10) | ||||
self.SetSizer(sizer) | ||||
sizer.Fit(self) | ||||
sizer.SetSizeHints(self) | ||||
class IGridHTMLHelp(wx.Frame): | ||||
walter.doerwald
|
r641 | def __init__(self, parent, title, size): | ||
walter.doerwald
|
r538 | wx.Frame.__init__(self, parent, -1, title, size=size) | ||
html = wx.html.HtmlWindow(self) | ||||
if "gtk2" in wx.PlatformInfo: | ||||
html.SetStandardFonts() | ||||
walter.doerwald
|
r641 | html.SetPage(help) | ||
walter.doerwald
|
r538 | |||
class IGridFrame(wx.Frame): | ||||
maxtitlelen = 30 | ||||
def __init__(self, parent, input): | ||||
walter.doerwald
|
r548 | title = " ".join([str(text) for (style, text) in ipipe.xformat(input, "header", 20)[2]]) | ||
walter.doerwald
|
r546 | wx.Frame.__init__(self, None, title=title, size=(640, 480)) | ||
walter.doerwald
|
r538 | self.menubar = wx.MenuBar() | ||
self.menucounter = 100 | ||||
self.m_help = wx.Menu() | ||||
self.m_search = wx.Menu() | ||||
self.m_sort = wx.Menu() | ||||
self.notebook = wx.Notebook(self, -1, style=0) | ||||
self.statusbar = self.CreateStatusBar(1, wx.ST_SIZEGRIP) | ||||
self.parent = parent | ||||
self._add_notebook(input) | ||||
self.Bind(wx.EVT_CLOSE, self.OnCloseWindow) | ||||
self.makemenu(self.m_sort, "&Sort (asc)", "Sort ascending", self.sortasc) | ||||
self.makemenu(self.m_sort, "Sort (&desc)", "Sort descending", self.sortdesc) | ||||
self.makemenu(self.m_help, "&Help", "Help", self.display_help) | ||||
self.makemenu(self.m_help, "&Show help in browser", "Show help in browser", self.display_help_in_browser) | ||||
self.makemenu(self.m_search, "&Find text", "Find text", self.enter_searchtext) | ||||
self.makemenu(self.m_search, "Find by &expression", "Find by expression", self.enter_searchexpression) | ||||
self.makemenu(self.m_search, "Find &next", "Find next", self.find_next) | ||||
self.makemenu(self.m_search, "Find &previous", "Find previous", self.find_previous) | ||||
self.menubar.Append(self.m_search, "&Find") | ||||
self.menubar.Append(self.m_sort, "&Sort") | ||||
self.menubar.Append(self.m_help, "&Help") | ||||
self.SetMenuBar(self.menubar) | ||||
self.searchtext = "" | ||||
walter.doerwald
|
r539 | self.helpdialog = None | ||
walter.doerwald
|
r538 | |||
def sortasc(self, event): | ||||
grid = self.notebook.GetPage(self.notebook.GetSelection()).grid | ||||
grid.sortattrasc() | ||||
def sortdesc(self, event): | ||||
grid = self.notebook.GetPage(self.notebook.GetSelection()).grid | ||||
grid.sortattrdesc() | ||||
def find_previous(self, event): | ||||
""" | ||||
find previous occurrences | ||||
""" | ||||
if self.searchtext: | ||||
grid = self.notebook.GetPage(self.notebook.GetSelection()).grid | ||||
row = grid.GetGridCursorRow() | ||||
col = grid.GetGridCursorCol() | ||||
if col-1 >= 0: | ||||
grid.search(self.searchtext, row, col-1, False) | ||||
else: | ||||
grid.search(self.searchtext, row-1, grid.table.GetNumberCols()-1, False) | ||||
else: | ||||
self.enter_searchtext(event) | ||||
def find_next(self, event): | ||||
""" | ||||
find the next occurrence | ||||
""" | ||||
if self.searchtext: | ||||
grid = self.notebook.GetPage(self.notebook.GetSelection()).grid | ||||
row = grid.GetGridCursorRow() | ||||
col = grid.GetGridCursorCol() | ||||
if col+1 < grid.table.GetNumberCols(): | ||||
grid.search(self.searchtext, row, col+1) | ||||
else: | ||||
grid.search(self.searchtext, row+1, 0) | ||||
else: | ||||
self.enter_searchtext(event) | ||||
def display_help(self, event): | ||||
""" | ||||
Display a help dialog | ||||
""" | ||||
walter.doerwald
|
r539 | if self.helpdialog: | ||
self.helpdialog.Destroy() | ||||
walter.doerwald
|
r641 | self.helpdialog = IGridHTMLHelp(None, title="Help", size=wx.Size(600,400)) | ||
walter.doerwald
|
r539 | self.helpdialog.Show() | ||
walter.doerwald
|
r538 | |||
def display_help_in_browser(self, event): | ||||
""" | ||||
Show the help-HTML in a browser (as a ``HtmlWindow`` does not understand | ||||
CSS this looks better) | ||||
""" | ||||
filename = urllib.pathname2url(os.path.abspath(os.path.join(os.path.dirname(__file__), "igrid_help.html"))) | ||||
if not filename.startswith("file"): | ||||
walter.doerwald
|
r539 | filename = "file:" + filename | ||
walter.doerwald
|
r538 | webbrowser.open(filename, new=1, autoraise=True) | ||
walter.doerwald
|
r539 | |||
walter.doerwald
|
r538 | def enter_searchexpression(self, event): | ||
pass | ||||
def makemenu(self, menu, label, help, cmd): | ||||
menu.Append(self.menucounter, label, help) | ||||
self.Bind(wx.EVT_MENU, cmd, id=self.menucounter) | ||||
self.menucounter += 1 | ||||
def _add_notebook(self, input, *attrs): | ||||
# Adds another notebook which has the starting object ``input`` | ||||
panel = IGridPanel(self.notebook, input, *attrs) | ||||
text = str(ipipe.xformat(input, "header", self.maxtitlelen)[2]) | ||||
if len(text) >= self.maxtitlelen: | ||||
text = text[:self.maxtitlelen].rstrip(".") + "..." | ||||
self.notebook.AddPage(panel, text, True) | ||||
panel.grid.SetFocus() | ||||
self.Layout() | ||||
def OnCloseWindow(self, event): | ||||
self.Destroy() | ||||
def enter_searchtext(self, event): | ||||
# Displays a dialog asking for the searchtext | ||||
dlg = wx.TextEntryDialog(self, "Find:", "Find in list") | ||||
if dlg.ShowModal() == wx.ID_OK: | ||||
self.searchtext = dlg.GetValue() | ||||
self.notebook.GetPage(self.notebook.GetSelection()).grid.search(self.searchtext, 0, 0) | ||||
dlg.Destroy() | ||||
walter.doerwald
|
r559 | class App(wx.App): | ||
def __init__(self, input): | ||||
self.input = input | ||||
walter.doerwald
|
r573 | self.result = None # Result to be returned to IPython. Set by quit(). | ||
walter.doerwald
|
r559 | wx.App.__init__(self) | ||
def OnInit(self): | ||||
frame = IGridFrame(self, self.input) | ||||
frame.Show() | ||||
self.SetTopWindow(frame) | ||||
frame.Raise() | ||||
return True | ||||
walter.doerwald
|
r538 | class igrid(ipipe.Display): | ||
""" | ||||
This is a wx-based display object that can be used instead of ``ibrowse`` | ||||
(which is curses-based) or ``idump`` (which simply does a print). | ||||
""" | ||||
walter.doerwald
|
r574 | if wx.VERSION < (2, 7): | ||
def display(self): | ||||
try: | ||||
# Try to create a "standalone" from. If this works we're probably | ||||
# running with -wthread. | ||||
# Note that this sets the parent of the frame to None, but we can't | ||||
# pass a result object back to the shell anyway. | ||||
frame = IGridFrame(None, self.input) | ||||
frame.Show() | ||||
frame.Raise() | ||||
except wx.PyNoAppError: | ||||
# There's no wx application yet => create one. | ||||
app = App(self.input) | ||||
app.MainLoop() | ||||
return app.result | ||||
else: | ||||
# With wx 2.7 it gets simpler. | ||||
def display(self): | ||||
app = App(self.input) | ||||
app.MainLoop() | ||||
return app.result | ||||