##// END OF EJS Templates
The input object can now be passed to the constructor of ibrowse/igrid....
walter.doerwald -
Show More
@@ -1,1765 +1,1769 b''
1 # -*- coding: iso-8859-1 -*-
1 # -*- coding: iso-8859-1 -*-
2
2
3 import curses, fcntl, signal, struct, tty, textwrap, inspect
3 import curses, fcntl, signal, struct, tty, textwrap, inspect
4
4
5 from IPython import ipapi
5 from IPython import ipapi
6
6
7 import astyle, ipipe
7 import astyle, ipipe
8
8
9
9
10 # Python 2.3 compatibility
10 # Python 2.3 compatibility
11 try:
11 try:
12 set
12 set
13 except NameError:
13 except NameError:
14 import sets
14 import sets
15 set = sets.Set
15 set = sets.Set
16
16
17 # Python 2.3 compatibility
17 # Python 2.3 compatibility
18 try:
18 try:
19 sorted
19 sorted
20 except NameError:
20 except NameError:
21 from ipipe import sorted
21 from ipipe import sorted
22
22
23
23
24 class UnassignedKeyError(Exception):
24 class UnassignedKeyError(Exception):
25 """
25 """
26 Exception that is used for reporting unassigned keys.
26 Exception that is used for reporting unassigned keys.
27 """
27 """
28
28
29
29
30 class UnknownCommandError(Exception):
30 class UnknownCommandError(Exception):
31 """
31 """
32 Exception that is used for reporting unknown commands (this should never
32 Exception that is used for reporting unknown commands (this should never
33 happen).
33 happen).
34 """
34 """
35
35
36
36
37 class CommandError(Exception):
37 class CommandError(Exception):
38 """
38 """
39 Exception that is used for reporting that a command can't be executed.
39 Exception that is used for reporting that a command can't be executed.
40 """
40 """
41
41
42
42
43 class Keymap(dict):
43 class Keymap(dict):
44 """
44 """
45 Stores mapping of keys to commands.
45 Stores mapping of keys to commands.
46 """
46 """
47 def __init__(self):
47 def __init__(self):
48 self._keymap = {}
48 self._keymap = {}
49
49
50 def __setitem__(self, key, command):
50 def __setitem__(self, key, command):
51 if isinstance(key, str):
51 if isinstance(key, str):
52 for c in key:
52 for c in key:
53 dict.__setitem__(self, ord(c), command)
53 dict.__setitem__(self, ord(c), command)
54 else:
54 else:
55 dict.__setitem__(self, key, command)
55 dict.__setitem__(self, key, command)
56
56
57 def __getitem__(self, key):
57 def __getitem__(self, key):
58 if isinstance(key, str):
58 if isinstance(key, str):
59 key = ord(key)
59 key = ord(key)
60 return dict.__getitem__(self, key)
60 return dict.__getitem__(self, key)
61
61
62 def __detitem__(self, key):
62 def __detitem__(self, key):
63 if isinstance(key, str):
63 if isinstance(key, str):
64 key = ord(key)
64 key = ord(key)
65 dict.__detitem__(self, key)
65 dict.__detitem__(self, key)
66
66
67 def register(self, command, *keys):
67 def register(self, command, *keys):
68 for key in keys:
68 for key in keys:
69 self[key] = command
69 self[key] = command
70
70
71 def get(self, key, default=None):
71 def get(self, key, default=None):
72 if isinstance(key, str):
72 if isinstance(key, str):
73 key = ord(key)
73 key = ord(key)
74 return dict.get(self, key, default)
74 return dict.get(self, key, default)
75
75
76 def findkey(self, command, default=ipipe.noitem):
76 def findkey(self, command, default=ipipe.noitem):
77 for (key, commandcandidate) in self.iteritems():
77 for (key, commandcandidate) in self.iteritems():
78 if commandcandidate == command:
78 if commandcandidate == command:
79 return key
79 return key
80 if default is ipipe.noitem:
80 if default is ipipe.noitem:
81 raise KeyError(command)
81 raise KeyError(command)
82 return default
82 return default
83
83
84
84
85 class _BrowserCachedItem(object):
85 class _BrowserCachedItem(object):
86 # This is used internally by ``ibrowse`` to store a item together with its
86 # This is used internally by ``ibrowse`` to store a item together with its
87 # marked status.
87 # marked status.
88 __slots__ = ("item", "marked")
88 __slots__ = ("item", "marked")
89
89
90 def __init__(self, item):
90 def __init__(self, item):
91 self.item = item
91 self.item = item
92 self.marked = False
92 self.marked = False
93
93
94
94
95 class _BrowserHelp(object):
95 class _BrowserHelp(object):
96 style_header = astyle.Style.fromstr("yellow:black:bold")
96 style_header = astyle.Style.fromstr("yellow:black:bold")
97 # This is used internally by ``ibrowse`` for displaying the help screen.
97 # This is used internally by ``ibrowse`` for displaying the help screen.
98 def __init__(self, browser):
98 def __init__(self, browser):
99 self.browser = browser
99 self.browser = browser
100
100
101 def __xrepr__(self, mode):
101 def __xrepr__(self, mode):
102 yield (-1, True)
102 yield (-1, True)
103 if mode == "header" or mode == "footer":
103 if mode == "header" or mode == "footer":
104 yield (astyle.style_default, "ibrowse help screen")
104 yield (astyle.style_default, "ibrowse help screen")
105 else:
105 else:
106 yield (astyle.style_default, repr(self))
106 yield (astyle.style_default, repr(self))
107
107
108 def __iter__(self):
108 def __iter__(self):
109 # Get reverse key mapping
109 # Get reverse key mapping
110 allkeys = {}
110 allkeys = {}
111 for (key, cmd) in self.browser.keymap.iteritems():
111 for (key, cmd) in self.browser.keymap.iteritems():
112 allkeys.setdefault(cmd, []).append(key)
112 allkeys.setdefault(cmd, []).append(key)
113
113
114 fields = ("key", "description")
114 fields = ("key", "description")
115
115
116 commands = []
116 commands = []
117 for name in dir(self.browser):
117 for name in dir(self.browser):
118 if name.startswith("cmd_"):
118 if name.startswith("cmd_"):
119 command = getattr(self.browser, name)
119 command = getattr(self.browser, name)
120 commands.append((inspect.getsourcelines(command)[-1], name[4:], command))
120 commands.append((inspect.getsourcelines(command)[-1], name[4:], command))
121 commands.sort()
121 commands.sort()
122 commands = [(c[1], c[2]) for c in commands]
122 commands = [(c[1], c[2]) for c in commands]
123 for (i, (name, command)) in enumerate(commands):
123 for (i, (name, command)) in enumerate(commands):
124 if i:
124 if i:
125 yield ipipe.Fields(fields, key="", description="")
125 yield ipipe.Fields(fields, key="", description="")
126
126
127 description = command.__doc__
127 description = command.__doc__
128 if description is None:
128 if description is None:
129 lines = []
129 lines = []
130 else:
130 else:
131 lines = [l.strip() for l in description.splitlines() if l.strip()]
131 lines = [l.strip() for l in description.splitlines() if l.strip()]
132 description = "\n".join(lines)
132 description = "\n".join(lines)
133 lines = textwrap.wrap(description, 60)
133 lines = textwrap.wrap(description, 60)
134 keys = allkeys.get(name, [])
134 keys = allkeys.get(name, [])
135
135
136 yield ipipe.Fields(fields, key="", description=astyle.Text((self.style_header, name)))
136 yield ipipe.Fields(fields, key="", description=astyle.Text((self.style_header, name)))
137 for i in xrange(max(len(keys), len(lines))):
137 for i in xrange(max(len(keys), len(lines))):
138 try:
138 try:
139 key = self.browser.keylabel(keys[i])
139 key = self.browser.keylabel(keys[i])
140 except IndexError:
140 except IndexError:
141 key = ""
141 key = ""
142 try:
142 try:
143 line = lines[i]
143 line = lines[i]
144 except IndexError:
144 except IndexError:
145 line = ""
145 line = ""
146 yield ipipe.Fields(fields, key=key, description=line)
146 yield ipipe.Fields(fields, key=key, description=line)
147
147
148
148
149 class _BrowserLevel(object):
149 class _BrowserLevel(object):
150 # This is used internally to store the state (iterator, fetch items,
150 # This is used internally to store the state (iterator, fetch items,
151 # position of cursor and screen, etc.) of one browser level
151 # position of cursor and screen, etc.) of one browser level
152 # An ``ibrowse`` object keeps multiple ``_BrowserLevel`` objects in
152 # An ``ibrowse`` object keeps multiple ``_BrowserLevel`` objects in
153 # a stack.
153 # a stack.
154 def __init__(self, browser, input, mainsizey, *attrs):
154 def __init__(self, browser, input, mainsizey, *attrs):
155 self.browser = browser
155 self.browser = browser
156 self.input = input
156 self.input = input
157 self.header = [x for x in ipipe.xrepr(input, "header") if not isinstance(x[0], int)]
157 self.header = [x for x in ipipe.xrepr(input, "header") if not isinstance(x[0], int)]
158 # iterator for the input
158 # iterator for the input
159 self.iterator = ipipe.xiter(input)
159 self.iterator = ipipe.xiter(input)
160
160
161 # is the iterator exhausted?
161 # is the iterator exhausted?
162 self.exhausted = False
162 self.exhausted = False
163
163
164 # attributes to be display (autodetected if empty)
164 # attributes to be display (autodetected if empty)
165 self.attrs = attrs
165 self.attrs = attrs
166
166
167 # fetched items (+ marked flag)
167 # fetched items (+ marked flag)
168 self.items = ipipe.deque()
168 self.items = ipipe.deque()
169
169
170 # Number of marked objects
170 # Number of marked objects
171 self.marked = 0
171 self.marked = 0
172
172
173 # Vertical cursor position
173 # Vertical cursor position
174 self.cury = 0
174 self.cury = 0
175
175
176 # Horizontal cursor position
176 # Horizontal cursor position
177 self.curx = 0
177 self.curx = 0
178
178
179 # Index of first data column
179 # Index of first data column
180 self.datastartx = 0
180 self.datastartx = 0
181
181
182 # Index of first data line
182 # Index of first data line
183 self.datastarty = 0
183 self.datastarty = 0
184
184
185 # height of the data display area
185 # height of the data display area
186 self.mainsizey = mainsizey
186 self.mainsizey = mainsizey
187
187
188 # width of the data display area (changes when scrolling)
188 # width of the data display area (changes when scrolling)
189 self.mainsizex = 0
189 self.mainsizex = 0
190
190
191 # Size of row number (changes when scrolling)
191 # Size of row number (changes when scrolling)
192 self.numbersizex = 0
192 self.numbersizex = 0
193
193
194 # Attributes to display (in this order)
194 # Attributes to display (in this order)
195 self.displayattrs = []
195 self.displayattrs = []
196
196
197 # index and attribute under the cursor
197 # index and attribute under the cursor
198 self.displayattr = (None, ipipe.noitem)
198 self.displayattr = (None, ipipe.noitem)
199
199
200 # Maps attributes to column widths
200 # Maps attributes to column widths
201 self.colwidths = {}
201 self.colwidths = {}
202
202
203 # Set of hidden attributes
203 # Set of hidden attributes
204 self.hiddenattrs = set()
204 self.hiddenattrs = set()
205
205
206 # This takes care of all the caches etc.
206 # This takes care of all the caches etc.
207 self.moveto(0, 0, refresh=True)
207 self.moveto(0, 0, refresh=True)
208
208
209 def fetch(self, count):
209 def fetch(self, count):
210 # Try to fill ``self.items`` with at least ``count`` objects.
210 # Try to fill ``self.items`` with at least ``count`` objects.
211 have = len(self.items)
211 have = len(self.items)
212 while not self.exhausted and have < count:
212 while not self.exhausted and have < count:
213 try:
213 try:
214 item = self.iterator.next()
214 item = self.iterator.next()
215 except StopIteration:
215 except StopIteration:
216 self.exhausted = True
216 self.exhausted = True
217 break
217 break
218 except (KeyboardInterrupt, SystemExit):
218 except (KeyboardInterrupt, SystemExit):
219 raise
219 raise
220 except Exception, exc:
220 except Exception, exc:
221 have += 1
221 have += 1
222 self.items.append(_BrowserCachedItem(exc))
222 self.items.append(_BrowserCachedItem(exc))
223 self.exhausted = True
223 self.exhausted = True
224 break
224 break
225 else:
225 else:
226 have += 1
226 have += 1
227 self.items.append(_BrowserCachedItem(item))
227 self.items.append(_BrowserCachedItem(item))
228
228
229 def calcdisplayattrs(self):
229 def calcdisplayattrs(self):
230 # Calculate which attributes are available from the objects that are
230 # Calculate which attributes are available from the objects that are
231 # currently visible on screen (and store it in ``self.displayattrs``)
231 # currently visible on screen (and store it in ``self.displayattrs``)
232
232
233 attrs = set()
233 attrs = set()
234 self.displayattrs = []
234 self.displayattrs = []
235 if self.attrs:
235 if self.attrs:
236 # If the browser object specifies a fixed list of attributes,
236 # If the browser object specifies a fixed list of attributes,
237 # simply use it (removing hidden attributes).
237 # simply use it (removing hidden attributes).
238 for attr in self.attrs:
238 for attr in self.attrs:
239 attr = ipipe.upgradexattr(attr)
239 attr = ipipe.upgradexattr(attr)
240 if attr not in attrs and attr not in self.hiddenattrs:
240 if attr not in attrs and attr not in self.hiddenattrs:
241 self.displayattrs.append(attr)
241 self.displayattrs.append(attr)
242 attrs.add(attr)
242 attrs.add(attr)
243 else:
243 else:
244 endy = min(self.datastarty+self.mainsizey, len(self.items))
244 endy = min(self.datastarty+self.mainsizey, len(self.items))
245 for i in xrange(self.datastarty, endy):
245 for i in xrange(self.datastarty, endy):
246 for attr in ipipe.xattrs(self.items[i].item, "default"):
246 for attr in ipipe.xattrs(self.items[i].item, "default"):
247 if attr not in attrs and attr not in self.hiddenattrs:
247 if attr not in attrs and attr not in self.hiddenattrs:
248 self.displayattrs.append(attr)
248 self.displayattrs.append(attr)
249 attrs.add(attr)
249 attrs.add(attr)
250
250
251 def getrow(self, i):
251 def getrow(self, i):
252 # Return a dictionary with the attributes for the object
252 # Return a dictionary with the attributes for the object
253 # ``self.items[i]``. Attribute names are taken from
253 # ``self.items[i]``. Attribute names are taken from
254 # ``self.displayattrs`` so ``calcdisplayattrs()`` must have been
254 # ``self.displayattrs`` so ``calcdisplayattrs()`` must have been
255 # called before.
255 # called before.
256 row = {}
256 row = {}
257 item = self.items[i].item
257 item = self.items[i].item
258 for attr in self.displayattrs:
258 for attr in self.displayattrs:
259 try:
259 try:
260 value = attr.value(item)
260 value = attr.value(item)
261 except (KeyboardInterrupt, SystemExit):
261 except (KeyboardInterrupt, SystemExit):
262 raise
262 raise
263 except Exception, exc:
263 except Exception, exc:
264 value = exc
264 value = exc
265 # only store attribute if it exists (or we got an exception)
265 # only store attribute if it exists (or we got an exception)
266 if value is not ipipe.noitem:
266 if value is not ipipe.noitem:
267 # remember alignment, length and colored text
267 # remember alignment, length and colored text
268 row[attr] = ipipe.xformat(value, "cell", self.browser.maxattrlength)
268 row[attr] = ipipe.xformat(value, "cell", self.browser.maxattrlength)
269 return row
269 return row
270
270
271 def calcwidths(self):
271 def calcwidths(self):
272 # Recalculate the displayed fields and their widths.
272 # Recalculate the displayed fields and their widths.
273 # ``calcdisplayattrs()'' must have been called and the cache
273 # ``calcdisplayattrs()'' must have been called and the cache
274 # for attributes of the objects on screen (``self.displayrows``)
274 # for attributes of the objects on screen (``self.displayrows``)
275 # must have been filled. This sets ``self.colwidths`` which maps
275 # must have been filled. This sets ``self.colwidths`` which maps
276 # attribute descriptors to widths.
276 # attribute descriptors to widths.
277 self.colwidths = {}
277 self.colwidths = {}
278 for row in self.displayrows:
278 for row in self.displayrows:
279 for attr in self.displayattrs:
279 for attr in self.displayattrs:
280 try:
280 try:
281 length = row[attr][1]
281 length = row[attr][1]
282 except KeyError:
282 except KeyError:
283 length = 0
283 length = 0
284 # always add attribute to colwidths, even if it doesn't exist
284 # always add attribute to colwidths, even if it doesn't exist
285 if attr not in self.colwidths:
285 if attr not in self.colwidths:
286 self.colwidths[attr] = len(attr.name())
286 self.colwidths[attr] = len(attr.name())
287 newwidth = max(self.colwidths[attr], length)
287 newwidth = max(self.colwidths[attr], length)
288 self.colwidths[attr] = newwidth
288 self.colwidths[attr] = newwidth
289
289
290 # How many characters do we need to paint the largest item number?
290 # How many characters do we need to paint the largest item number?
291 self.numbersizex = len(str(self.datastarty+self.mainsizey-1))
291 self.numbersizex = len(str(self.datastarty+self.mainsizey-1))
292 # How must space have we got to display data?
292 # How must space have we got to display data?
293 self.mainsizex = self.browser.scrsizex-self.numbersizex-3
293 self.mainsizex = self.browser.scrsizex-self.numbersizex-3
294 # width of all columns
294 # width of all columns
295 self.datasizex = sum(self.colwidths.itervalues()) + len(self.colwidths)
295 self.datasizex = sum(self.colwidths.itervalues()) + len(self.colwidths)
296
296
297 def calcdisplayattr(self):
297 def calcdisplayattr(self):
298 # Find out which attribute the cursor is on and store this
298 # Find out which attribute the cursor is on and store this
299 # information in ``self.displayattr``.
299 # information in ``self.displayattr``.
300 pos = 0
300 pos = 0
301 for (i, attr) in enumerate(self.displayattrs):
301 for (i, attr) in enumerate(self.displayattrs):
302 if pos+self.colwidths[attr] >= self.curx:
302 if pos+self.colwidths[attr] >= self.curx:
303 self.displayattr = (i, attr)
303 self.displayattr = (i, attr)
304 break
304 break
305 pos += self.colwidths[attr]+1
305 pos += self.colwidths[attr]+1
306 else:
306 else:
307 self.displayattr = (None, ipipe.noitem)
307 self.displayattr = (None, ipipe.noitem)
308
308
309 def moveto(self, x, y, refresh=False):
309 def moveto(self, x, y, refresh=False):
310 # Move the cursor to the position ``(x,y)`` (in data coordinates,
310 # Move the cursor to the position ``(x,y)`` (in data coordinates,
311 # not in screen coordinates). If ``refresh`` is true, all cached
311 # not in screen coordinates). If ``refresh`` is true, all cached
312 # values will be recalculated (e.g. because the list has been
312 # values will be recalculated (e.g. because the list has been
313 # resorted, so screen positions etc. are no longer valid).
313 # resorted, so screen positions etc. are no longer valid).
314 olddatastarty = self.datastarty
314 olddatastarty = self.datastarty
315 oldx = self.curx
315 oldx = self.curx
316 oldy = self.cury
316 oldy = self.cury
317 x = int(x+0.5)
317 x = int(x+0.5)
318 y = int(y+0.5)
318 y = int(y+0.5)
319 newx = x # remember where we wanted to move
319 newx = x # remember where we wanted to move
320 newy = y # remember where we wanted to move
320 newy = y # remember where we wanted to move
321
321
322 scrollbordery = min(self.browser.scrollbordery, self.mainsizey//2)
322 scrollbordery = min(self.browser.scrollbordery, self.mainsizey//2)
323 scrollborderx = min(self.browser.scrollborderx, self.mainsizex//2)
323 scrollborderx = min(self.browser.scrollborderx, self.mainsizex//2)
324
324
325 # Make sure that the cursor didn't leave the main area vertically
325 # Make sure that the cursor didn't leave the main area vertically
326 if y < 0:
326 if y < 0:
327 y = 0
327 y = 0
328 # try to get enough items to fill the screen
328 # try to get enough items to fill the screen
329 self.fetch(max(y+scrollbordery+1, self.mainsizey))
329 self.fetch(max(y+scrollbordery+1, self.mainsizey))
330 if y >= len(self.items):
330 if y >= len(self.items):
331 y = max(0, len(self.items)-1)
331 y = max(0, len(self.items)-1)
332
332
333 # Make sure that the cursor stays on screen vertically
333 # Make sure that the cursor stays on screen vertically
334 if y < self.datastarty+scrollbordery:
334 if y < self.datastarty+scrollbordery:
335 self.datastarty = max(0, y-scrollbordery)
335 self.datastarty = max(0, y-scrollbordery)
336 elif y >= self.datastarty+self.mainsizey-scrollbordery:
336 elif y >= self.datastarty+self.mainsizey-scrollbordery:
337 self.datastarty = max(0, min(y-self.mainsizey+scrollbordery+1,
337 self.datastarty = max(0, min(y-self.mainsizey+scrollbordery+1,
338 len(self.items)-self.mainsizey))
338 len(self.items)-self.mainsizey))
339
339
340 if refresh: # Do we need to refresh the complete display?
340 if refresh: # Do we need to refresh the complete display?
341 self.calcdisplayattrs()
341 self.calcdisplayattrs()
342 endy = min(self.datastarty+self.mainsizey, len(self.items))
342 endy = min(self.datastarty+self.mainsizey, len(self.items))
343 self.displayrows = map(self.getrow, xrange(self.datastarty, endy))
343 self.displayrows = map(self.getrow, xrange(self.datastarty, endy))
344 self.calcwidths()
344 self.calcwidths()
345 # Did we scroll vertically => update displayrows
345 # Did we scroll vertically => update displayrows
346 # and various other attributes
346 # and various other attributes
347 elif self.datastarty != olddatastarty:
347 elif self.datastarty != olddatastarty:
348 # Recalculate which attributes we have to display
348 # Recalculate which attributes we have to display
349 olddisplayattrs = self.displayattrs
349 olddisplayattrs = self.displayattrs
350 self.calcdisplayattrs()
350 self.calcdisplayattrs()
351 # If there are new attributes, recreate the cache
351 # If there are new attributes, recreate the cache
352 if self.displayattrs != olddisplayattrs:
352 if self.displayattrs != olddisplayattrs:
353 endy = min(self.datastarty+self.mainsizey, len(self.items))
353 endy = min(self.datastarty+self.mainsizey, len(self.items))
354 self.displayrows = map(self.getrow, xrange(self.datastarty, endy))
354 self.displayrows = map(self.getrow, xrange(self.datastarty, endy))
355 elif self.datastarty<olddatastarty: # we did scroll up
355 elif self.datastarty<olddatastarty: # we did scroll up
356 # drop rows from the end
356 # drop rows from the end
357 del self.displayrows[self.datastarty-olddatastarty:]
357 del self.displayrows[self.datastarty-olddatastarty:]
358 # fetch new items
358 # fetch new items
359 for i in xrange(min(olddatastarty, self.datastarty+self.mainsizey)-1,
359 for i in xrange(min(olddatastarty, self.datastarty+self.mainsizey)-1,
360 self.datastarty-1, -1):
360 self.datastarty-1, -1):
361 try:
361 try:
362 row = self.getrow(i)
362 row = self.getrow(i)
363 except IndexError:
363 except IndexError:
364 # we didn't have enough objects to fill the screen
364 # we didn't have enough objects to fill the screen
365 break
365 break
366 self.displayrows.insert(0, row)
366 self.displayrows.insert(0, row)
367 else: # we did scroll down
367 else: # we did scroll down
368 # drop rows from the start
368 # drop rows from the start
369 del self.displayrows[:self.datastarty-olddatastarty]
369 del self.displayrows[:self.datastarty-olddatastarty]
370 # fetch new items
370 # fetch new items
371 for i in xrange(max(olddatastarty+self.mainsizey, self.datastarty),
371 for i in xrange(max(olddatastarty+self.mainsizey, self.datastarty),
372 self.datastarty+self.mainsizey):
372 self.datastarty+self.mainsizey):
373 try:
373 try:
374 row = self.getrow(i)
374 row = self.getrow(i)
375 except IndexError:
375 except IndexError:
376 # we didn't have enough objects to fill the screen
376 # we didn't have enough objects to fill the screen
377 break
377 break
378 self.displayrows.append(row)
378 self.displayrows.append(row)
379 self.calcwidths()
379 self.calcwidths()
380
380
381 # Make sure that the cursor didn't leave the data area horizontally
381 # Make sure that the cursor didn't leave the data area horizontally
382 if x < 0:
382 if x < 0:
383 x = 0
383 x = 0
384 elif x >= self.datasizex:
384 elif x >= self.datasizex:
385 x = max(0, self.datasizex-1)
385 x = max(0, self.datasizex-1)
386
386
387 # Make sure that the cursor stays on screen horizontally
387 # Make sure that the cursor stays on screen horizontally
388 if x < self.datastartx+scrollborderx:
388 if x < self.datastartx+scrollborderx:
389 self.datastartx = max(0, x-scrollborderx)
389 self.datastartx = max(0, x-scrollborderx)
390 elif x >= self.datastartx+self.mainsizex-scrollborderx:
390 elif x >= self.datastartx+self.mainsizex-scrollborderx:
391 self.datastartx = max(0, min(x-self.mainsizex+scrollborderx+1,
391 self.datastartx = max(0, min(x-self.mainsizex+scrollborderx+1,
392 self.datasizex-self.mainsizex))
392 self.datasizex-self.mainsizex))
393
393
394 if x == oldx and y == oldy and (x != newx or y != newy): # couldn't move
394 if x == oldx and y == oldy and (x != newx or y != newy): # couldn't move
395 self.browser.beep()
395 self.browser.beep()
396 else:
396 else:
397 self.curx = x
397 self.curx = x
398 self.cury = y
398 self.cury = y
399 self.calcdisplayattr()
399 self.calcdisplayattr()
400
400
401 def sort(self, key, reverse=False):
401 def sort(self, key, reverse=False):
402 """
402 """
403 Sort the currently list of items using the key function ``key``. If
403 Sort the currently list of items using the key function ``key``. If
404 ``reverse`` is true the sort order is reversed.
404 ``reverse`` is true the sort order is reversed.
405 """
405 """
406 curitem = self.items[self.cury] # Remember where the cursor is now
406 curitem = self.items[self.cury] # Remember where the cursor is now
407
407
408 # Sort items
408 # Sort items
409 def realkey(item):
409 def realkey(item):
410 return key(item.item)
410 return key(item.item)
411 self.items = ipipe.deque(sorted(self.items, key=realkey, reverse=reverse))
411 self.items = ipipe.deque(sorted(self.items, key=realkey, reverse=reverse))
412
412
413 # Find out where the object under the cursor went
413 # Find out where the object under the cursor went
414 cury = self.cury
414 cury = self.cury
415 for (i, item) in enumerate(self.items):
415 for (i, item) in enumerate(self.items):
416 if item is curitem:
416 if item is curitem:
417 cury = i
417 cury = i
418 break
418 break
419
419
420 self.moveto(self.curx, cury, refresh=True)
420 self.moveto(self.curx, cury, refresh=True)
421
421
422 def refresh(self):
422 def refresh(self):
423 """
423 """
424 Restart iterating the input.
424 Restart iterating the input.
425 """
425 """
426 self.iterator = ipipe.xiter(self.input)
426 self.iterator = ipipe.xiter(self.input)
427 self.items.clear()
427 self.items.clear()
428 self.exhausted = False
428 self.exhausted = False
429 self.datastartx = self.datastarty = 0
429 self.datastartx = self.datastarty = 0
430 self.moveto(0, 0, refresh=True)
430 self.moveto(0, 0, refresh=True)
431
431
432 def refreshfind(self):
432 def refreshfind(self):
433 """
433 """
434 Restart iterating the input and go back to the same object as before
434 Restart iterating the input and go back to the same object as before
435 (if it can be found in the new iterator).
435 (if it can be found in the new iterator).
436 """
436 """
437 try:
437 try:
438 oldobject = self.items[self.cury].item
438 oldobject = self.items[self.cury].item
439 except IndexError:
439 except IndexError:
440 oldobject = ipipe.noitem
440 oldobject = ipipe.noitem
441 self.iterator = ipipe.xiter(self.input)
441 self.iterator = ipipe.xiter(self.input)
442 self.items.clear()
442 self.items.clear()
443 self.exhausted = False
443 self.exhausted = False
444 while True:
444 while True:
445 self.fetch(len(self.items)+1)
445 self.fetch(len(self.items)+1)
446 if self.exhausted:
446 if self.exhausted:
447 curses.beep()
447 curses.beep()
448 self.datastartx = self.datastarty = 0
448 self.datastartx = self.datastarty = 0
449 self.moveto(self.curx, 0, refresh=True)
449 self.moveto(self.curx, 0, refresh=True)
450 break
450 break
451 if self.items[-1].item == oldobject:
451 if self.items[-1].item == oldobject:
452 self.datastartx = self.datastarty = 0
452 self.datastartx = self.datastarty = 0
453 self.moveto(self.curx, len(self.items)-1, refresh=True)
453 self.moveto(self.curx, len(self.items)-1, refresh=True)
454 break
454 break
455
455
456
456
457 class _CommandInput(object):
457 class _CommandInput(object):
458 keymap = Keymap()
458 keymap = Keymap()
459 keymap.register("left", curses.KEY_LEFT)
459 keymap.register("left", curses.KEY_LEFT)
460 keymap.register("right", curses.KEY_RIGHT)
460 keymap.register("right", curses.KEY_RIGHT)
461 keymap.register("home", curses.KEY_HOME, "\x01") # Ctrl-A
461 keymap.register("home", curses.KEY_HOME, "\x01") # Ctrl-A
462 keymap.register("end", curses.KEY_END, "\x05") # Ctrl-E
462 keymap.register("end", curses.KEY_END, "\x05") # Ctrl-E
463 # FIXME: What's happening here?
463 # FIXME: What's happening here?
464 keymap.register("backspace", curses.KEY_BACKSPACE, "\x08\x7f")
464 keymap.register("backspace", curses.KEY_BACKSPACE, "\x08\x7f")
465 keymap.register("delete", curses.KEY_DC)
465 keymap.register("delete", curses.KEY_DC)
466 keymap.register("delend", 0x0b) # Ctrl-K
466 keymap.register("delend", 0x0b) # Ctrl-K
467 keymap.register("execute", "\r\n")
467 keymap.register("execute", "\r\n")
468 keymap.register("up", curses.KEY_UP)
468 keymap.register("up", curses.KEY_UP)
469 keymap.register("down", curses.KEY_DOWN)
469 keymap.register("down", curses.KEY_DOWN)
470 keymap.register("incsearchup", curses.KEY_PPAGE)
470 keymap.register("incsearchup", curses.KEY_PPAGE)
471 keymap.register("incsearchdown", curses.KEY_NPAGE)
471 keymap.register("incsearchdown", curses.KEY_NPAGE)
472 keymap.register("exit", "\x18"), # Ctrl-X
472 keymap.register("exit", "\x18"), # Ctrl-X
473
473
474 def __init__(self, prompt):
474 def __init__(self, prompt):
475 self.prompt = prompt
475 self.prompt = prompt
476 self.history = []
476 self.history = []
477 self.maxhistory = 100
477 self.maxhistory = 100
478 self.input = ""
478 self.input = ""
479 self.curx = 0
479 self.curx = 0
480 self.cury = -1 # blank line
480 self.cury = -1 # blank line
481
481
482 def start(self):
482 def start(self):
483 self.input = ""
483 self.input = ""
484 self.curx = 0
484 self.curx = 0
485 self.cury = -1 # blank line
485 self.cury = -1 # blank line
486
486
487 def handlekey(self, browser, key):
487 def handlekey(self, browser, key):
488 cmdname = self.keymap.get(key, None)
488 cmdname = self.keymap.get(key, None)
489 if cmdname is not None:
489 if cmdname is not None:
490 cmdfunc = getattr(self, "cmd_%s" % cmdname, None)
490 cmdfunc = getattr(self, "cmd_%s" % cmdname, None)
491 if cmdfunc is not None:
491 if cmdfunc is not None:
492 return cmdfunc(browser)
492 return cmdfunc(browser)
493 curses.beep()
493 curses.beep()
494 elif key != -1:
494 elif key != -1:
495 try:
495 try:
496 char = chr(key)
496 char = chr(key)
497 except ValueError:
497 except ValueError:
498 curses.beep()
498 curses.beep()
499 else:
499 else:
500 return self.handlechar(browser, char)
500 return self.handlechar(browser, char)
501
501
502 def handlechar(self, browser, char):
502 def handlechar(self, browser, char):
503 self.input = self.input[:self.curx] + char + self.input[self.curx:]
503 self.input = self.input[:self.curx] + char + self.input[self.curx:]
504 self.curx += 1
504 self.curx += 1
505 return True
505 return True
506
506
507 def dohistory(self):
507 def dohistory(self):
508 self.history.insert(0, self.input)
508 self.history.insert(0, self.input)
509 del self.history[:-self.maxhistory]
509 del self.history[:-self.maxhistory]
510
510
511 def cmd_backspace(self, browser):
511 def cmd_backspace(self, browser):
512 if self.curx:
512 if self.curx:
513 self.input = self.input[:self.curx-1] + self.input[self.curx:]
513 self.input = self.input[:self.curx-1] + self.input[self.curx:]
514 self.curx -= 1
514 self.curx -= 1
515 return True
515 return True
516 else:
516 else:
517 curses.beep()
517 curses.beep()
518
518
519 def cmd_delete(self, browser):
519 def cmd_delete(self, browser):
520 if self.curx<len(self.input):
520 if self.curx<len(self.input):
521 self.input = self.input[:self.curx] + self.input[self.curx+1:]
521 self.input = self.input[:self.curx] + self.input[self.curx+1:]
522 return True
522 return True
523 else:
523 else:
524 curses.beep()
524 curses.beep()
525
525
526 def cmd_delend(self, browser):
526 def cmd_delend(self, browser):
527 if self.curx<len(self.input):
527 if self.curx<len(self.input):
528 self.input = self.input[:self.curx]
528 self.input = self.input[:self.curx]
529 return True
529 return True
530
530
531 def cmd_left(self, browser):
531 def cmd_left(self, browser):
532 if self.curx:
532 if self.curx:
533 self.curx -= 1
533 self.curx -= 1
534 return True
534 return True
535 else:
535 else:
536 curses.beep()
536 curses.beep()
537
537
538 def cmd_right(self, browser):
538 def cmd_right(self, browser):
539 if self.curx < len(self.input):
539 if self.curx < len(self.input):
540 self.curx += 1
540 self.curx += 1
541 return True
541 return True
542 else:
542 else:
543 curses.beep()
543 curses.beep()
544
544
545 def cmd_home(self, browser):
545 def cmd_home(self, browser):
546 if self.curx:
546 if self.curx:
547 self.curx = 0
547 self.curx = 0
548 return True
548 return True
549 else:
549 else:
550 curses.beep()
550 curses.beep()
551
551
552 def cmd_end(self, browser):
552 def cmd_end(self, browser):
553 if self.curx < len(self.input):
553 if self.curx < len(self.input):
554 self.curx = len(self.input)
554 self.curx = len(self.input)
555 return True
555 return True
556 else:
556 else:
557 curses.beep()
557 curses.beep()
558
558
559 def cmd_up(self, browser):
559 def cmd_up(self, browser):
560 if self.cury < len(self.history)-1:
560 if self.cury < len(self.history)-1:
561 self.cury += 1
561 self.cury += 1
562 self.input = self.history[self.cury]
562 self.input = self.history[self.cury]
563 self.curx = len(self.input)
563 self.curx = len(self.input)
564 return True
564 return True
565 else:
565 else:
566 curses.beep()
566 curses.beep()
567
567
568 def cmd_down(self, browser):
568 def cmd_down(self, browser):
569 if self.cury >= 0:
569 if self.cury >= 0:
570 self.cury -= 1
570 self.cury -= 1
571 if self.cury>=0:
571 if self.cury>=0:
572 self.input = self.history[self.cury]
572 self.input = self.history[self.cury]
573 else:
573 else:
574 self.input = ""
574 self.input = ""
575 self.curx = len(self.input)
575 self.curx = len(self.input)
576 return True
576 return True
577 else:
577 else:
578 curses.beep()
578 curses.beep()
579
579
580 def cmd_incsearchup(self, browser):
580 def cmd_incsearchup(self, browser):
581 prefix = self.input[:self.curx]
581 prefix = self.input[:self.curx]
582 cury = self.cury
582 cury = self.cury
583 while True:
583 while True:
584 cury += 1
584 cury += 1
585 if cury >= len(self.history):
585 if cury >= len(self.history):
586 break
586 break
587 if self.history[cury].startswith(prefix):
587 if self.history[cury].startswith(prefix):
588 self.input = self.history[cury]
588 self.input = self.history[cury]
589 self.cury = cury
589 self.cury = cury
590 return True
590 return True
591 curses.beep()
591 curses.beep()
592
592
593 def cmd_incsearchdown(self, browser):
593 def cmd_incsearchdown(self, browser):
594 prefix = self.input[:self.curx]
594 prefix = self.input[:self.curx]
595 cury = self.cury
595 cury = self.cury
596 while True:
596 while True:
597 cury -= 1
597 cury -= 1
598 if cury <= 0:
598 if cury <= 0:
599 break
599 break
600 if self.history[cury].startswith(prefix):
600 if self.history[cury].startswith(prefix):
601 self.input = self.history[self.cury]
601 self.input = self.history[self.cury]
602 self.cury = cury
602 self.cury = cury
603 return True
603 return True
604 curses.beep()
604 curses.beep()
605
605
606 def cmd_exit(self, browser):
606 def cmd_exit(self, browser):
607 browser.mode = "default"
607 browser.mode = "default"
608 return True
608 return True
609
609
610 def cmd_execute(self, browser):
610 def cmd_execute(self, browser):
611 raise NotImplementedError
611 raise NotImplementedError
612
612
613
613
614 class _CommandGoto(_CommandInput):
614 class _CommandGoto(_CommandInput):
615 def __init__(self):
615 def __init__(self):
616 _CommandInput.__init__(self, "goto object #")
616 _CommandInput.__init__(self, "goto object #")
617
617
618 def handlechar(self, browser, char):
618 def handlechar(self, browser, char):
619 # Only accept digits
619 # Only accept digits
620 if not "0" <= char <= "9":
620 if not "0" <= char <= "9":
621 curses.beep()
621 curses.beep()
622 else:
622 else:
623 return _CommandInput.handlechar(self, browser, char)
623 return _CommandInput.handlechar(self, browser, char)
624
624
625 def cmd_execute(self, browser):
625 def cmd_execute(self, browser):
626 level = browser.levels[-1]
626 level = browser.levels[-1]
627 if self.input:
627 if self.input:
628 self.dohistory()
628 self.dohistory()
629 level.moveto(level.curx, int(self.input))
629 level.moveto(level.curx, int(self.input))
630 browser.mode = "default"
630 browser.mode = "default"
631 return True
631 return True
632
632
633
633
634 class _CommandFind(_CommandInput):
634 class _CommandFind(_CommandInput):
635 def __init__(self):
635 def __init__(self):
636 _CommandInput.__init__(self, "find expression")
636 _CommandInput.__init__(self, "find expression")
637
637
638 def cmd_execute(self, browser):
638 def cmd_execute(self, browser):
639 level = browser.levels[-1]
639 level = browser.levels[-1]
640 if self.input:
640 if self.input:
641 self.dohistory()
641 self.dohistory()
642 while True:
642 while True:
643 cury = level.cury
643 cury = level.cury
644 level.moveto(level.curx, cury+1)
644 level.moveto(level.curx, cury+1)
645 if cury == level.cury:
645 if cury == level.cury:
646 curses.beep()
646 curses.beep()
647 break # hit end
647 break # hit end
648 item = level.items[level.cury].item
648 item = level.items[level.cury].item
649 try:
649 try:
650 globals = ipipe.getglobals(None)
650 globals = ipipe.getglobals(None)
651 if eval(self.input, globals, ipipe.AttrNamespace(item)):
651 if eval(self.input, globals, ipipe.AttrNamespace(item)):
652 break # found something
652 break # found something
653 except (KeyboardInterrupt, SystemExit):
653 except (KeyboardInterrupt, SystemExit):
654 raise
654 raise
655 except Exception, exc:
655 except Exception, exc:
656 browser.report(exc)
656 browser.report(exc)
657 curses.beep()
657 curses.beep()
658 break # break on error
658 break # break on error
659 browser.mode = "default"
659 browser.mode = "default"
660 return True
660 return True
661
661
662
662
663 class _CommandFindBackwards(_CommandInput):
663 class _CommandFindBackwards(_CommandInput):
664 def __init__(self):
664 def __init__(self):
665 _CommandInput.__init__(self, "find backwards expression")
665 _CommandInput.__init__(self, "find backwards expression")
666
666
667 def cmd_execute(self, browser):
667 def cmd_execute(self, browser):
668 level = browser.levels[-1]
668 level = browser.levels[-1]
669 if self.input:
669 if self.input:
670 self.dohistory()
670 self.dohistory()
671 while level.cury:
671 while level.cury:
672 level.moveto(level.curx, level.cury-1)
672 level.moveto(level.curx, level.cury-1)
673 item = level.items[level.cury].item
673 item = level.items[level.cury].item
674 try:
674 try:
675 globals = ipipe.getglobals(None)
675 globals = ipipe.getglobals(None)
676 if eval(self.input, globals, ipipe.AttrNamespace(item)):
676 if eval(self.input, globals, ipipe.AttrNamespace(item)):
677 break # found something
677 break # found something
678 except (KeyboardInterrupt, SystemExit):
678 except (KeyboardInterrupt, SystemExit):
679 raise
679 raise
680 except Exception, exc:
680 except Exception, exc:
681 browser.report(exc)
681 browser.report(exc)
682 curses.beep()
682 curses.beep()
683 break # break on error
683 break # break on error
684 else:
684 else:
685 curses.beep()
685 curses.beep()
686 browser.mode = "default"
686 browser.mode = "default"
687 return True
687 return True
688
688
689
689
690 class ibrowse(ipipe.Display):
690 class ibrowse(ipipe.Display):
691 # Show this many lines from the previous screen when paging horizontally
691 # Show this many lines from the previous screen when paging horizontally
692 pageoverlapx = 1
692 pageoverlapx = 1
693
693
694 # Show this many lines from the previous screen when paging vertically
694 # Show this many lines from the previous screen when paging vertically
695 pageoverlapy = 1
695 pageoverlapy = 1
696
696
697 # Start scrolling when the cursor is less than this number of columns
697 # Start scrolling when the cursor is less than this number of columns
698 # away from the left or right screen edge
698 # away from the left or right screen edge
699 scrollborderx = 10
699 scrollborderx = 10
700
700
701 # Start scrolling when the cursor is less than this number of lines
701 # Start scrolling when the cursor is less than this number of lines
702 # away from the top or bottom screen edge
702 # away from the top or bottom screen edge
703 scrollbordery = 5
703 scrollbordery = 5
704
704
705 # Accelerate by this factor when scrolling horizontally
705 # Accelerate by this factor when scrolling horizontally
706 acceleratex = 1.05
706 acceleratex = 1.05
707
707
708 # Accelerate by this factor when scrolling vertically
708 # Accelerate by this factor when scrolling vertically
709 acceleratey = 1.05
709 acceleratey = 1.05
710
710
711 # The maximum horizontal scroll speed
711 # The maximum horizontal scroll speed
712 # (as a factor of the screen width (i.e. 0.5 == half a screen width)
712 # (as a factor of the screen width (i.e. 0.5 == half a screen width)
713 maxspeedx = 0.5
713 maxspeedx = 0.5
714
714
715 # The maximum vertical scroll speed
715 # The maximum vertical scroll speed
716 # (as a factor of the screen height (i.e. 0.5 == half a screen height)
716 # (as a factor of the screen height (i.e. 0.5 == half a screen height)
717 maxspeedy = 0.5
717 maxspeedy = 0.5
718
718
719 # The maximum number of header lines for browser level
719 # The maximum number of header lines for browser level
720 # if the nesting is deeper, only the innermost levels are displayed
720 # if the nesting is deeper, only the innermost levels are displayed
721 maxheaders = 5
721 maxheaders = 5
722
722
723 # The approximate maximum length of a column entry
723 # The approximate maximum length of a column entry
724 maxattrlength = 200
724 maxattrlength = 200
725
725
726 # Styles for various parts of the GUI
726 # Styles for various parts of the GUI
727 style_objheadertext = astyle.Style.fromstr("white:black:bold|reverse")
727 style_objheadertext = astyle.Style.fromstr("white:black:bold|reverse")
728 style_objheadernumber = astyle.Style.fromstr("white:blue:bold|reverse")
728 style_objheadernumber = astyle.Style.fromstr("white:blue:bold|reverse")
729 style_objheaderobject = astyle.Style.fromstr("white:black:reverse")
729 style_objheaderobject = astyle.Style.fromstr("white:black:reverse")
730 style_colheader = astyle.Style.fromstr("blue:white:reverse")
730 style_colheader = astyle.Style.fromstr("blue:white:reverse")
731 style_colheaderhere = astyle.Style.fromstr("green:black:bold|reverse")
731 style_colheaderhere = astyle.Style.fromstr("green:black:bold|reverse")
732 style_colheadersep = astyle.Style.fromstr("blue:black:reverse")
732 style_colheadersep = astyle.Style.fromstr("blue:black:reverse")
733 style_number = astyle.Style.fromstr("blue:white:reverse")
733 style_number = astyle.Style.fromstr("blue:white:reverse")
734 style_numberhere = astyle.Style.fromstr("green:black:bold|reverse")
734 style_numberhere = astyle.Style.fromstr("green:black:bold|reverse")
735 style_sep = astyle.Style.fromstr("blue:black")
735 style_sep = astyle.Style.fromstr("blue:black")
736 style_data = astyle.Style.fromstr("white:black")
736 style_data = astyle.Style.fromstr("white:black")
737 style_datapad = astyle.Style.fromstr("blue:black:bold")
737 style_datapad = astyle.Style.fromstr("blue:black:bold")
738 style_footer = astyle.Style.fromstr("black:white")
738 style_footer = astyle.Style.fromstr("black:white")
739 style_report = astyle.Style.fromstr("white:black")
739 style_report = astyle.Style.fromstr("white:black")
740
740
741 # Column separator in header
741 # Column separator in header
742 headersepchar = "|"
742 headersepchar = "|"
743
743
744 # Character for padding data cell entries
744 # Character for padding data cell entries
745 datapadchar = "."
745 datapadchar = "."
746
746
747 # Column separator in data area
747 # Column separator in data area
748 datasepchar = "|"
748 datasepchar = "|"
749
749
750 # Character to use for "empty" cell (i.e. for non-existing attributes)
750 # Character to use for "empty" cell (i.e. for non-existing attributes)
751 nodatachar = "-"
751 nodatachar = "-"
752
752
753 # Prompts for modes that require keyboard input
753 # Prompts for modes that require keyboard input
754 prompts = {
754 prompts = {
755 "goto": _CommandGoto(),
755 "goto": _CommandGoto(),
756 "find": _CommandFind(),
756 "find": _CommandFind(),
757 "findbackwards": _CommandFindBackwards()
757 "findbackwards": _CommandFindBackwards()
758 }
758 }
759
759
760 # Maps curses key codes to "function" names
760 # Maps curses key codes to "function" names
761 keymap = Keymap()
761 keymap = Keymap()
762 keymap.register("quit", "q")
762 keymap.register("quit", "q")
763 keymap.register("up", curses.KEY_UP)
763 keymap.register("up", curses.KEY_UP)
764 keymap.register("down", curses.KEY_DOWN)
764 keymap.register("down", curses.KEY_DOWN)
765 keymap.register("pageup", curses.KEY_PPAGE)
765 keymap.register("pageup", curses.KEY_PPAGE)
766 keymap.register("pagedown", curses.KEY_NPAGE)
766 keymap.register("pagedown", curses.KEY_NPAGE)
767 keymap.register("left", curses.KEY_LEFT)
767 keymap.register("left", curses.KEY_LEFT)
768 keymap.register("right", curses.KEY_RIGHT)
768 keymap.register("right", curses.KEY_RIGHT)
769 keymap.register("home", curses.KEY_HOME, "\x01")
769 keymap.register("home", curses.KEY_HOME, "\x01")
770 keymap.register("end", curses.KEY_END, "\x05")
770 keymap.register("end", curses.KEY_END, "\x05")
771 keymap.register("prevattr", "<\x1b")
771 keymap.register("prevattr", "<\x1b")
772 keymap.register("nextattr", ">\t")
772 keymap.register("nextattr", ">\t")
773 keymap.register("pick", "p")
773 keymap.register("pick", "p")
774 keymap.register("pickattr", "P")
774 keymap.register("pickattr", "P")
775 keymap.register("pickallattrs", "C")
775 keymap.register("pickallattrs", "C")
776 keymap.register("pickmarked", "m")
776 keymap.register("pickmarked", "m")
777 keymap.register("pickmarkedattr", "M")
777 keymap.register("pickmarkedattr", "M")
778 keymap.register("pickinput", "i")
778 keymap.register("pickinput", "i")
779 keymap.register("pickinputattr", "I")
779 keymap.register("pickinputattr", "I")
780 keymap.register("hideattr", "h")
780 keymap.register("hideattr", "h")
781 keymap.register("unhideattrs", "H")
781 keymap.register("unhideattrs", "H")
782 keymap.register("help", "?")
782 keymap.register("help", "?")
783 keymap.register("enter", "\r\n")
783 keymap.register("enter", "\r\n")
784 keymap.register("enterattr", "E")
784 keymap.register("enterattr", "E")
785 # FIXME: What's happening here?
785 # FIXME: What's happening here?
786 keymap.register("leave", curses.KEY_BACKSPACE, "x\x08\x7f")
786 keymap.register("leave", curses.KEY_BACKSPACE, "x\x08\x7f")
787 keymap.register("detail", "d")
787 keymap.register("detail", "d")
788 keymap.register("detailattr", "D")
788 keymap.register("detailattr", "D")
789 keymap.register("tooglemark", " ")
789 keymap.register("tooglemark", " ")
790 keymap.register("markrange", "%")
790 keymap.register("markrange", "%")
791 keymap.register("sortattrasc", "v")
791 keymap.register("sortattrasc", "v")
792 keymap.register("sortattrdesc", "V")
792 keymap.register("sortattrdesc", "V")
793 keymap.register("goto", "g")
793 keymap.register("goto", "g")
794 keymap.register("find", "f")
794 keymap.register("find", "f")
795 keymap.register("findbackwards", "b")
795 keymap.register("findbackwards", "b")
796 keymap.register("refresh", "r")
796 keymap.register("refresh", "r")
797 keymap.register("refreshfind", "R")
797 keymap.register("refreshfind", "R")
798
798
799 def __init__(self, *attrs):
799 def __init__(self, input=None, attrs=None):
800 """
800 """
801 Create a new browser. If ``attrs`` is not empty, it is the list
801 Create a new browser. If ``attrs`` is not empty, it is the list
802 of attributes that will be displayed in the browser, otherwise
802 of attributes that will be displayed in the browser, otherwise
803 these will be determined by the objects on screen.
803 these will be determined by the objects on screen.
804 """
804 """
805 self.input = input
806
807 if attrs is None:
808 attrs = ()
805 self.attrs = attrs
809 self.attrs = attrs
806
810
807 # Stack of browser levels
811 # Stack of browser levels
808 self.levels = []
812 self.levels = []
809 # how many colums to scroll (Changes when accelerating)
813 # how many colums to scroll (Changes when accelerating)
810 self.stepx = 1.
814 self.stepx = 1.
811
815
812 # how many rows to scroll (Changes when accelerating)
816 # how many rows to scroll (Changes when accelerating)
813 self.stepy = 1.
817 self.stepy = 1.
814
818
815 # Beep on the edges of the data area? (Will be set to ``False``
819 # Beep on the edges of the data area? (Will be set to ``False``
816 # once the cursor hits the edge of the screen, so we don't get
820 # once the cursor hits the edge of the screen, so we don't get
817 # multiple beeps).
821 # multiple beeps).
818 self._dobeep = True
822 self._dobeep = True
819
823
820 # Cache for registered ``curses`` colors and styles.
824 # Cache for registered ``curses`` colors and styles.
821 self._styles = {}
825 self._styles = {}
822 self._colors = {}
826 self._colors = {}
823 self._maxcolor = 1
827 self._maxcolor = 1
824
828
825 # How many header lines do we want to paint (the numbers of levels
829 # How many header lines do we want to paint (the numbers of levels
826 # we have, but with an upper bound)
830 # we have, but with an upper bound)
827 self._headerlines = 1
831 self._headerlines = 1
828
832
829 # Index of first header line
833 # Index of first header line
830 self._firstheaderline = 0
834 self._firstheaderline = 0
831
835
832 # curses window
836 # curses window
833 self.scr = None
837 self.scr = None
834 # report in the footer line (error, executed command etc.)
838 # report in the footer line (error, executed command etc.)
835 self._report = None
839 self._report = None
836
840
837 # value to be returned to the caller (set by commands)
841 # value to be returned to the caller (set by commands)
838 self.returnvalue = None
842 self.returnvalue = None
839
843
840 # The mode the browser is in
844 # The mode the browser is in
841 # e.g. normal browsing or entering an argument for a command
845 # e.g. normal browsing or entering an argument for a command
842 self.mode = "default"
846 self.mode = "default"
843
847
844 # set by the SIGWINCH signal handler
848 # set by the SIGWINCH signal handler
845 self.resized = False
849 self.resized = False
846
850
847 def nextstepx(self, step):
851 def nextstepx(self, step):
848 """
852 """
849 Accelerate horizontally.
853 Accelerate horizontally.
850 """
854 """
851 return max(1., min(step*self.acceleratex,
855 return max(1., min(step*self.acceleratex,
852 self.maxspeedx*self.levels[-1].mainsizex))
856 self.maxspeedx*self.levels[-1].mainsizex))
853
857
854 def nextstepy(self, step):
858 def nextstepy(self, step):
855 """
859 """
856 Accelerate vertically.
860 Accelerate vertically.
857 """
861 """
858 return max(1., min(step*self.acceleratey,
862 return max(1., min(step*self.acceleratey,
859 self.maxspeedy*self.levels[-1].mainsizey))
863 self.maxspeedy*self.levels[-1].mainsizey))
860
864
861 def getstyle(self, style):
865 def getstyle(self, style):
862 """
866 """
863 Register the ``style`` with ``curses`` or get it from the cache,
867 Register the ``style`` with ``curses`` or get it from the cache,
864 if it has been registered before.
868 if it has been registered before.
865 """
869 """
866 try:
870 try:
867 return self._styles[style.fg, style.bg, style.attrs]
871 return self._styles[style.fg, style.bg, style.attrs]
868 except KeyError:
872 except KeyError:
869 attrs = 0
873 attrs = 0
870 for b in astyle.A2CURSES:
874 for b in astyle.A2CURSES:
871 if style.attrs & b:
875 if style.attrs & b:
872 attrs |= astyle.A2CURSES[b]
876 attrs |= astyle.A2CURSES[b]
873 try:
877 try:
874 color = self._colors[style.fg, style.bg]
878 color = self._colors[style.fg, style.bg]
875 except KeyError:
879 except KeyError:
876 curses.init_pair(
880 curses.init_pair(
877 self._maxcolor,
881 self._maxcolor,
878 astyle.COLOR2CURSES[style.fg],
882 astyle.COLOR2CURSES[style.fg],
879 astyle.COLOR2CURSES[style.bg]
883 astyle.COLOR2CURSES[style.bg]
880 )
884 )
881 color = curses.color_pair(self._maxcolor)
885 color = curses.color_pair(self._maxcolor)
882 self._colors[style.fg, style.bg] = color
886 self._colors[style.fg, style.bg] = color
883 self._maxcolor += 1
887 self._maxcolor += 1
884 c = color | attrs
888 c = color | attrs
885 self._styles[style.fg, style.bg, style.attrs] = c
889 self._styles[style.fg, style.bg, style.attrs] = c
886 return c
890 return c
887
891
888 def addstr(self, y, x, begx, endx, text, style):
892 def addstr(self, y, x, begx, endx, text, style):
889 """
893 """
890 A version of ``curses.addstr()`` that can handle ``x`` coordinates
894 A version of ``curses.addstr()`` that can handle ``x`` coordinates
891 that are outside the screen.
895 that are outside the screen.
892 """
896 """
893 text2 = text[max(0, begx-x):max(0, endx-x)]
897 text2 = text[max(0, begx-x):max(0, endx-x)]
894 if text2:
898 if text2:
895 self.scr.addstr(y, max(x, begx), text2, self.getstyle(style))
899 self.scr.addstr(y, max(x, begx), text2, self.getstyle(style))
896 return len(text)
900 return len(text)
897
901
898 def addchr(self, y, x, begx, endx, c, l, style):
902 def addchr(self, y, x, begx, endx, c, l, style):
899 x0 = max(x, begx)
903 x0 = max(x, begx)
900 x1 = min(x+l, endx)
904 x1 = min(x+l, endx)
901 if x1>x0:
905 if x1>x0:
902 self.scr.addstr(y, x0, c*(x1-x0), self.getstyle(style))
906 self.scr.addstr(y, x0, c*(x1-x0), self.getstyle(style))
903 return l
907 return l
904
908
905 def _calcheaderlines(self, levels):
909 def _calcheaderlines(self, levels):
906 # Calculate how many headerlines do we have to display, if we have
910 # Calculate how many headerlines do we have to display, if we have
907 # ``levels`` browser levels
911 # ``levels`` browser levels
908 if levels is None:
912 if levels is None:
909 levels = len(self.levels)
913 levels = len(self.levels)
910 self._headerlines = min(self.maxheaders, levels)
914 self._headerlines = min(self.maxheaders, levels)
911 self._firstheaderline = levels-self._headerlines
915 self._firstheaderline = levels-self._headerlines
912
916
913 def getstylehere(self, style):
917 def getstylehere(self, style):
914 """
918 """
915 Return a style for displaying the original style ``style``
919 Return a style for displaying the original style ``style``
916 in the row the cursor is on.
920 in the row the cursor is on.
917 """
921 """
918 return astyle.Style(style.fg, astyle.COLOR_BLUE, style.attrs | astyle.A_BOLD)
922 return astyle.Style(style.fg, astyle.COLOR_BLUE, style.attrs | astyle.A_BOLD)
919
923
920 def report(self, msg):
924 def report(self, msg):
921 """
925 """
922 Store the message ``msg`` for display below the footer line. This
926 Store the message ``msg`` for display below the footer line. This
923 will be displayed as soon as the screen is redrawn.
927 will be displayed as soon as the screen is redrawn.
924 """
928 """
925 self._report = msg
929 self._report = msg
926
930
927 def enter(self, item, *attrs):
931 def enter(self, item, *attrs):
928 """
932 """
929 Enter the object ``item``. If ``attrs`` is specified, it will be used
933 Enter the object ``item``. If ``attrs`` is specified, it will be used
930 as a fixed list of attributes to display.
934 as a fixed list of attributes to display.
931 """
935 """
932 if self.levels and item is self.levels[-1].input:
936 if self.levels and item is self.levels[-1].input:
933 curses.beep()
937 curses.beep()
934 self.report(CommandError("Recursion on input object"))
938 self.report(CommandError("Recursion on input object"))
935 else:
939 else:
936 oldlevels = len(self.levels)
940 oldlevels = len(self.levels)
937 self._calcheaderlines(oldlevels+1)
941 self._calcheaderlines(oldlevels+1)
938 try:
942 try:
939 level = _BrowserLevel(
943 level = _BrowserLevel(
940 self,
944 self,
941 item,
945 item,
942 self.scrsizey-1-self._headerlines-2,
946 self.scrsizey-1-self._headerlines-2,
943 *attrs
947 *attrs
944 )
948 )
945 except (KeyboardInterrupt, SystemExit):
949 except (KeyboardInterrupt, SystemExit):
946 raise
950 raise
947 except Exception, exc:
951 except Exception, exc:
948 if not self.levels:
952 if not self.levels:
949 raise
953 raise
950 self._calcheaderlines(oldlevels)
954 self._calcheaderlines(oldlevels)
951 curses.beep()
955 curses.beep()
952 self.report(exc)
956 self.report(exc)
953 else:
957 else:
954 self.levels.append(level)
958 self.levels.append(level)
955
959
956 def startkeyboardinput(self, mode):
960 def startkeyboardinput(self, mode):
957 """
961 """
958 Enter mode ``mode``, which requires keyboard input.
962 Enter mode ``mode``, which requires keyboard input.
959 """
963 """
960 self.mode = mode
964 self.mode = mode
961 self.prompts[mode].start()
965 self.prompts[mode].start()
962
966
963 def keylabel(self, keycode):
967 def keylabel(self, keycode):
964 """
968 """
965 Return a pretty name for the ``curses`` key ``keycode`` (used in the
969 Return a pretty name for the ``curses`` key ``keycode`` (used in the
966 help screen and in reports about unassigned keys).
970 help screen and in reports about unassigned keys).
967 """
971 """
968 if keycode <= 0xff:
972 if keycode <= 0xff:
969 specialsnames = {
973 specialsnames = {
970 ord("\n"): "RETURN",
974 ord("\n"): "RETURN",
971 ord(" "): "SPACE",
975 ord(" "): "SPACE",
972 ord("\t"): "TAB",
976 ord("\t"): "TAB",
973 ord("\x7f"): "DELETE",
977 ord("\x7f"): "DELETE",
974 ord("\x08"): "BACKSPACE",
978 ord("\x08"): "BACKSPACE",
975 }
979 }
976 if keycode in specialsnames:
980 if keycode in specialsnames:
977 return specialsnames[keycode]
981 return specialsnames[keycode]
978 elif 0x00 < keycode < 0x20:
982 elif 0x00 < keycode < 0x20:
979 return "CTRL-%s" % chr(keycode + 64)
983 return "CTRL-%s" % chr(keycode + 64)
980 return repr(chr(keycode))
984 return repr(chr(keycode))
981 for name in dir(curses):
985 for name in dir(curses):
982 if name.startswith("KEY_") and getattr(curses, name) == keycode:
986 if name.startswith("KEY_") and getattr(curses, name) == keycode:
983 return name
987 return name
984 return str(keycode)
988 return str(keycode)
985
989
986 def beep(self, force=False):
990 def beep(self, force=False):
987 if force or self._dobeep:
991 if force or self._dobeep:
988 curses.beep()
992 curses.beep()
989 # don't beep again (as long as the same key is pressed)
993 # don't beep again (as long as the same key is pressed)
990 self._dobeep = False
994 self._dobeep = False
991
995
992 def cmd_up(self):
996 def cmd_up(self):
993 """
997 """
994 Move the cursor to the previous row.
998 Move the cursor to the previous row.
995 """
999 """
996 level = self.levels[-1]
1000 level = self.levels[-1]
997 self.report("up")
1001 self.report("up")
998 level.moveto(level.curx, level.cury-self.stepy)
1002 level.moveto(level.curx, level.cury-self.stepy)
999
1003
1000 def cmd_down(self):
1004 def cmd_down(self):
1001 """
1005 """
1002 Move the cursor to the next row.
1006 Move the cursor to the next row.
1003 """
1007 """
1004 level = self.levels[-1]
1008 level = self.levels[-1]
1005 self.report("down")
1009 self.report("down")
1006 level.moveto(level.curx, level.cury+self.stepy)
1010 level.moveto(level.curx, level.cury+self.stepy)
1007
1011
1008 def cmd_pageup(self):
1012 def cmd_pageup(self):
1009 """
1013 """
1010 Move the cursor up one page.
1014 Move the cursor up one page.
1011 """
1015 """
1012 level = self.levels[-1]
1016 level = self.levels[-1]
1013 self.report("page up")
1017 self.report("page up")
1014 level.moveto(level.curx, level.cury-level.mainsizey+self.pageoverlapy)
1018 level.moveto(level.curx, level.cury-level.mainsizey+self.pageoverlapy)
1015
1019
1016 def cmd_pagedown(self):
1020 def cmd_pagedown(self):
1017 """
1021 """
1018 Move the cursor down one page.
1022 Move the cursor down one page.
1019 """
1023 """
1020 level = self.levels[-1]
1024 level = self.levels[-1]
1021 self.report("page down")
1025 self.report("page down")
1022 level.moveto(level.curx, level.cury+level.mainsizey-self.pageoverlapy)
1026 level.moveto(level.curx, level.cury+level.mainsizey-self.pageoverlapy)
1023
1027
1024 def cmd_left(self):
1028 def cmd_left(self):
1025 """
1029 """
1026 Move the cursor left.
1030 Move the cursor left.
1027 """
1031 """
1028 level = self.levels[-1]
1032 level = self.levels[-1]
1029 self.report("left")
1033 self.report("left")
1030 level.moveto(level.curx-self.stepx, level.cury)
1034 level.moveto(level.curx-self.stepx, level.cury)
1031
1035
1032 def cmd_right(self):
1036 def cmd_right(self):
1033 """
1037 """
1034 Move the cursor right.
1038 Move the cursor right.
1035 """
1039 """
1036 level = self.levels[-1]
1040 level = self.levels[-1]
1037 self.report("right")
1041 self.report("right")
1038 level.moveto(level.curx+self.stepx, level.cury)
1042 level.moveto(level.curx+self.stepx, level.cury)
1039
1043
1040 def cmd_home(self):
1044 def cmd_home(self):
1041 """
1045 """
1042 Move the cursor to the first column.
1046 Move the cursor to the first column.
1043 """
1047 """
1044 level = self.levels[-1]
1048 level = self.levels[-1]
1045 self.report("home")
1049 self.report("home")
1046 level.moveto(0, level.cury)
1050 level.moveto(0, level.cury)
1047
1051
1048 def cmd_end(self):
1052 def cmd_end(self):
1049 """
1053 """
1050 Move the cursor to the last column.
1054 Move the cursor to the last column.
1051 """
1055 """
1052 level = self.levels[-1]
1056 level = self.levels[-1]
1053 self.report("end")
1057 self.report("end")
1054 level.moveto(level.datasizex+level.mainsizey-self.pageoverlapx, level.cury)
1058 level.moveto(level.datasizex+level.mainsizey-self.pageoverlapx, level.cury)
1055
1059
1056 def cmd_prevattr(self):
1060 def cmd_prevattr(self):
1057 """
1061 """
1058 Move the cursor one attribute column to the left.
1062 Move the cursor one attribute column to the left.
1059 """
1063 """
1060 level = self.levels[-1]
1064 level = self.levels[-1]
1061 if level.displayattr[0] is None or level.displayattr[0] == 0:
1065 if level.displayattr[0] is None or level.displayattr[0] == 0:
1062 self.beep()
1066 self.beep()
1063 else:
1067 else:
1064 self.report("prevattr")
1068 self.report("prevattr")
1065 pos = 0
1069 pos = 0
1066 for (i, attrname) in enumerate(level.displayattrs):
1070 for (i, attrname) in enumerate(level.displayattrs):
1067 if i == level.displayattr[0]-1:
1071 if i == level.displayattr[0]-1:
1068 break
1072 break
1069 pos += level.colwidths[attrname] + 1
1073 pos += level.colwidths[attrname] + 1
1070 level.moveto(pos, level.cury)
1074 level.moveto(pos, level.cury)
1071
1075
1072 def cmd_nextattr(self):
1076 def cmd_nextattr(self):
1073 """
1077 """
1074 Move the cursor one attribute column to the right.
1078 Move the cursor one attribute column to the right.
1075 """
1079 """
1076 level = self.levels[-1]
1080 level = self.levels[-1]
1077 if level.displayattr[0] is None or level.displayattr[0] == len(level.displayattrs)-1:
1081 if level.displayattr[0] is None or level.displayattr[0] == len(level.displayattrs)-1:
1078 self.beep()
1082 self.beep()
1079 else:
1083 else:
1080 self.report("nextattr")
1084 self.report("nextattr")
1081 pos = 0
1085 pos = 0
1082 for (i, attrname) in enumerate(level.displayattrs):
1086 for (i, attrname) in enumerate(level.displayattrs):
1083 if i == level.displayattr[0]+1:
1087 if i == level.displayattr[0]+1:
1084 break
1088 break
1085 pos += level.colwidths[attrname] + 1
1089 pos += level.colwidths[attrname] + 1
1086 level.moveto(pos, level.cury)
1090 level.moveto(pos, level.cury)
1087
1091
1088 def cmd_pick(self):
1092 def cmd_pick(self):
1089 """
1093 """
1090 'Pick' the object under the cursor (i.e. the row the cursor is on).
1094 'Pick' the object under the cursor (i.e. the row the cursor is on).
1091 This leaves the browser and returns the picked object to the caller.
1095 This leaves the browser and returns the picked object to the caller.
1092 (In IPython this object will be available as the ``_`` variable.)
1096 (In IPython this object will be available as the ``_`` variable.)
1093 """
1097 """
1094 level = self.levels[-1]
1098 level = self.levels[-1]
1095 self.returnvalue = level.items[level.cury].item
1099 self.returnvalue = level.items[level.cury].item
1096 return True
1100 return True
1097
1101
1098 def cmd_pickattr(self):
1102 def cmd_pickattr(self):
1099 """
1103 """
1100 'Pick' the attribute under the cursor (i.e. the row/column the
1104 'Pick' the attribute under the cursor (i.e. the row/column the
1101 cursor is on).
1105 cursor is on).
1102 """
1106 """
1103 level = self.levels[-1]
1107 level = self.levels[-1]
1104 attr = level.displayattr[1]
1108 attr = level.displayattr[1]
1105 if attr is ipipe.noitem:
1109 if attr is ipipe.noitem:
1106 curses.beep()
1110 curses.beep()
1107 self.report(CommandError("no column under cursor"))
1111 self.report(CommandError("no column under cursor"))
1108 return
1112 return
1109 value = attr.value(level.items[level.cury].item)
1113 value = attr.value(level.items[level.cury].item)
1110 if value is ipipe.noitem:
1114 if value is ipipe.noitem:
1111 curses.beep()
1115 curses.beep()
1112 self.report(AttributeError(attr.name()))
1116 self.report(AttributeError(attr.name()))
1113 else:
1117 else:
1114 self.returnvalue = value
1118 self.returnvalue = value
1115 return True
1119 return True
1116
1120
1117 def cmd_pickallattrs(self):
1121 def cmd_pickallattrs(self):
1118 """
1122 """
1119 Pick' the complete column under the cursor (i.e. the attribute under
1123 Pick' the complete column under the cursor (i.e. the attribute under
1120 the cursor) from all currently fetched objects. These attributes
1124 the cursor) from all currently fetched objects. These attributes
1121 will be returned as a list.
1125 will be returned as a list.
1122 """
1126 """
1123 level = self.levels[-1]
1127 level = self.levels[-1]
1124 attr = level.displayattr[1]
1128 attr = level.displayattr[1]
1125 if attr is ipipe.noitem:
1129 if attr is ipipe.noitem:
1126 curses.beep()
1130 curses.beep()
1127 self.report(CommandError("no column under cursor"))
1131 self.report(CommandError("no column under cursor"))
1128 return
1132 return
1129 result = []
1133 result = []
1130 for cache in level.items:
1134 for cache in level.items:
1131 value = attr.value(cache.item)
1135 value = attr.value(cache.item)
1132 if value is not ipipe.noitem:
1136 if value is not ipipe.noitem:
1133 result.append(value)
1137 result.append(value)
1134 self.returnvalue = result
1138 self.returnvalue = result
1135 return True
1139 return True
1136
1140
1137 def cmd_pickmarked(self):
1141 def cmd_pickmarked(self):
1138 """
1142 """
1139 'Pick' marked objects. Marked objects will be returned as a list.
1143 'Pick' marked objects. Marked objects will be returned as a list.
1140 """
1144 """
1141 level = self.levels[-1]
1145 level = self.levels[-1]
1142 self.returnvalue = [cache.item for cache in level.items if cache.marked]
1146 self.returnvalue = [cache.item for cache in level.items if cache.marked]
1143 return True
1147 return True
1144
1148
1145 def cmd_pickmarkedattr(self):
1149 def cmd_pickmarkedattr(self):
1146 """
1150 """
1147 'Pick' the attribute under the cursor from all marked objects
1151 'Pick' the attribute under the cursor from all marked objects
1148 (This returns a list).
1152 (This returns a list).
1149 """
1153 """
1150
1154
1151 level = self.levels[-1]
1155 level = self.levels[-1]
1152 attr = level.displayattr[1]
1156 attr = level.displayattr[1]
1153 if attr is ipipe.noitem:
1157 if attr is ipipe.noitem:
1154 curses.beep()
1158 curses.beep()
1155 self.report(CommandError("no column under cursor"))
1159 self.report(CommandError("no column under cursor"))
1156 return
1160 return
1157 result = []
1161 result = []
1158 for cache in level.items:
1162 for cache in level.items:
1159 if cache.marked:
1163 if cache.marked:
1160 value = attr.value(cache.item)
1164 value = attr.value(cache.item)
1161 if value is not ipipe.noitem:
1165 if value is not ipipe.noitem:
1162 result.append(value)
1166 result.append(value)
1163 self.returnvalue = result
1167 self.returnvalue = result
1164 return True
1168 return True
1165
1169
1166 def cmd_pickinput(self):
1170 def cmd_pickinput(self):
1167 """
1171 """
1168 Use the object under the cursor (i.e. the row the cursor is on) as
1172 Use the object under the cursor (i.e. the row the cursor is on) as
1169 the next input line. This leaves the browser and puts the picked object
1173 the next input line. This leaves the browser and puts the picked object
1170 in the input.
1174 in the input.
1171 """
1175 """
1172 level = self.levels[-1]
1176 level = self.levels[-1]
1173 value = level.items[level.cury].item
1177 value = level.items[level.cury].item
1174 self.returnvalue = None
1178 self.returnvalue = None
1175 api = ipapi.get()
1179 api = ipapi.get()
1176 api.set_next_input(str(value))
1180 api.set_next_input(str(value))
1177 return True
1181 return True
1178
1182
1179 def cmd_pickinputattr(self):
1183 def cmd_pickinputattr(self):
1180 """
1184 """
1181 Use the attribute under the cursor i.e. the row/column the cursor is on)
1185 Use the attribute under the cursor i.e. the row/column the cursor is on)
1182 as the next input line. This leaves the browser and puts the picked
1186 as the next input line. This leaves the browser and puts the picked
1183 object in the input.
1187 object in the input.
1184 """
1188 """
1185 level = self.levels[-1]
1189 level = self.levels[-1]
1186 attr = level.displayattr[1]
1190 attr = level.displayattr[1]
1187 if attr is ipipe.noitem:
1191 if attr is ipipe.noitem:
1188 curses.beep()
1192 curses.beep()
1189 self.report(CommandError("no column under cursor"))
1193 self.report(CommandError("no column under cursor"))
1190 return
1194 return
1191 value = attr.value(level.items[level.cury].item)
1195 value = attr.value(level.items[level.cury].item)
1192 if value is ipipe.noitem:
1196 if value is ipipe.noitem:
1193 curses.beep()
1197 curses.beep()
1194 self.report(AttributeError(attr.name()))
1198 self.report(AttributeError(attr.name()))
1195 self.returnvalue = None
1199 self.returnvalue = None
1196 api = ipapi.get()
1200 api = ipapi.get()
1197 api.set_next_input(str(value))
1201 api.set_next_input(str(value))
1198 return True
1202 return True
1199
1203
1200 def cmd_markrange(self):
1204 def cmd_markrange(self):
1201 """
1205 """
1202 Mark all objects from the last marked object before the current cursor
1206 Mark all objects from the last marked object before the current cursor
1203 position to the cursor position.
1207 position to the cursor position.
1204 """
1208 """
1205 level = self.levels[-1]
1209 level = self.levels[-1]
1206 self.report("markrange")
1210 self.report("markrange")
1207 start = None
1211 start = None
1208 if level.items:
1212 if level.items:
1209 for i in xrange(level.cury, -1, -1):
1213 for i in xrange(level.cury, -1, -1):
1210 if level.items[i].marked:
1214 if level.items[i].marked:
1211 start = i
1215 start = i
1212 break
1216 break
1213 if start is None:
1217 if start is None:
1214 self.report(CommandError("no mark before cursor"))
1218 self.report(CommandError("no mark before cursor"))
1215 curses.beep()
1219 curses.beep()
1216 else:
1220 else:
1217 for i in xrange(start, level.cury+1):
1221 for i in xrange(start, level.cury+1):
1218 cache = level.items[i]
1222 cache = level.items[i]
1219 if not cache.marked:
1223 if not cache.marked:
1220 cache.marked = True
1224 cache.marked = True
1221 level.marked += 1
1225 level.marked += 1
1222
1226
1223 def cmd_enter(self):
1227 def cmd_enter(self):
1224 """
1228 """
1225 Enter the object under the cursor. (what this mean depends on the object
1229 Enter the object under the cursor. (what this mean depends on the object
1226 itself (i.e. how it implements iteration). This opens a new browser 'level'.
1230 itself (i.e. how it implements iteration). This opens a new browser 'level'.
1227 """
1231 """
1228 level = self.levels[-1]
1232 level = self.levels[-1]
1229 try:
1233 try:
1230 item = level.items[level.cury].item
1234 item = level.items[level.cury].item
1231 except IndexError:
1235 except IndexError:
1232 self.report(CommandError("No object"))
1236 self.report(CommandError("No object"))
1233 curses.beep()
1237 curses.beep()
1234 else:
1238 else:
1235 self.report("entering object...")
1239 self.report("entering object...")
1236 self.enter(item)
1240 self.enter(item)
1237
1241
1238 def cmd_leave(self):
1242 def cmd_leave(self):
1239 """
1243 """
1240 Leave the current browser level and go back to the previous one.
1244 Leave the current browser level and go back to the previous one.
1241 """
1245 """
1242 self.report("leave")
1246 self.report("leave")
1243 if len(self.levels) > 1:
1247 if len(self.levels) > 1:
1244 self._calcheaderlines(len(self.levels)-1)
1248 self._calcheaderlines(len(self.levels)-1)
1245 self.levels.pop(-1)
1249 self.levels.pop(-1)
1246 else:
1250 else:
1247 self.report(CommandError("This is the last level"))
1251 self.report(CommandError("This is the last level"))
1248 curses.beep()
1252 curses.beep()
1249
1253
1250 def cmd_enterattr(self):
1254 def cmd_enterattr(self):
1251 """
1255 """
1252 Enter the attribute under the cursor.
1256 Enter the attribute under the cursor.
1253 """
1257 """
1254 level = self.levels[-1]
1258 level = self.levels[-1]
1255 attr = level.displayattr[1]
1259 attr = level.displayattr[1]
1256 if attr is ipipe.noitem:
1260 if attr is ipipe.noitem:
1257 curses.beep()
1261 curses.beep()
1258 self.report(CommandError("no column under cursor"))
1262 self.report(CommandError("no column under cursor"))
1259 return
1263 return
1260 try:
1264 try:
1261 item = level.items[level.cury].item
1265 item = level.items[level.cury].item
1262 except IndexError:
1266 except IndexError:
1263 self.report(CommandError("No object"))
1267 self.report(CommandError("No object"))
1264 curses.beep()
1268 curses.beep()
1265 else:
1269 else:
1266 value = attr.value(item)
1270 value = attr.value(item)
1267 name = attr.name()
1271 name = attr.name()
1268 if value is ipipe.noitem:
1272 if value is ipipe.noitem:
1269 self.report(AttributeError(name))
1273 self.report(AttributeError(name))
1270 else:
1274 else:
1271 self.report("entering object attribute %s..." % name)
1275 self.report("entering object attribute %s..." % name)
1272 self.enter(value)
1276 self.enter(value)
1273
1277
1274 def cmd_detail(self):
1278 def cmd_detail(self):
1275 """
1279 """
1276 Show a detail view of the object under the cursor. This shows the
1280 Show a detail view of the object under the cursor. This shows the
1277 name, type, doc string and value of the object attributes (and it
1281 name, type, doc string and value of the object attributes (and it
1278 might show more attributes than in the list view, depending on
1282 might show more attributes than in the list view, depending on
1279 the object).
1283 the object).
1280 """
1284 """
1281 level = self.levels[-1]
1285 level = self.levels[-1]
1282 try:
1286 try:
1283 item = level.items[level.cury].item
1287 item = level.items[level.cury].item
1284 except IndexError:
1288 except IndexError:
1285 self.report(CommandError("No object"))
1289 self.report(CommandError("No object"))
1286 curses.beep()
1290 curses.beep()
1287 else:
1291 else:
1288 self.report("entering detail view for object...")
1292 self.report("entering detail view for object...")
1289 attrs = [ipipe.AttributeDetail(item, attr) for attr in ipipe.xattrs(item, "detail")]
1293 attrs = [ipipe.AttributeDetail(item, attr) for attr in ipipe.xattrs(item, "detail")]
1290 self.enter(attrs)
1294 self.enter(attrs)
1291
1295
1292 def cmd_detailattr(self):
1296 def cmd_detailattr(self):
1293 """
1297 """
1294 Show a detail view of the attribute under the cursor.
1298 Show a detail view of the attribute under the cursor.
1295 """
1299 """
1296 level = self.levels[-1]
1300 level = self.levels[-1]
1297 attr = level.displayattr[1]
1301 attr = level.displayattr[1]
1298 if attr is ipipe.noitem:
1302 if attr is ipipe.noitem:
1299 curses.beep()
1303 curses.beep()
1300 self.report(CommandError("no attribute"))
1304 self.report(CommandError("no attribute"))
1301 return
1305 return
1302 try:
1306 try:
1303 item = level.items[level.cury].item
1307 item = level.items[level.cury].item
1304 except IndexError:
1308 except IndexError:
1305 self.report(CommandError("No object"))
1309 self.report(CommandError("No object"))
1306 curses.beep()
1310 curses.beep()
1307 else:
1311 else:
1308 try:
1312 try:
1309 item = attr.value(item)
1313 item = attr.value(item)
1310 except (KeyboardInterrupt, SystemExit):
1314 except (KeyboardInterrupt, SystemExit):
1311 raise
1315 raise
1312 except Exception, exc:
1316 except Exception, exc:
1313 self.report(exc)
1317 self.report(exc)
1314 else:
1318 else:
1315 self.report("entering detail view for attribute %s..." % attr.name())
1319 self.report("entering detail view for attribute %s..." % attr.name())
1316 attrs = [ipipe.AttributeDetail(item, attr) for attr in ipipe.xattrs(item, "detail")]
1320 attrs = [ipipe.AttributeDetail(item, attr) for attr in ipipe.xattrs(item, "detail")]
1317 self.enter(attrs)
1321 self.enter(attrs)
1318
1322
1319 def cmd_tooglemark(self):
1323 def cmd_tooglemark(self):
1320 """
1324 """
1321 Mark/unmark the object under the cursor. Marked objects have a '!'
1325 Mark/unmark the object under the cursor. Marked objects have a '!'
1322 after the row number).
1326 after the row number).
1323 """
1327 """
1324 level = self.levels[-1]
1328 level = self.levels[-1]
1325 self.report("toggle mark")
1329 self.report("toggle mark")
1326 try:
1330 try:
1327 item = level.items[level.cury]
1331 item = level.items[level.cury]
1328 except IndexError: # no items?
1332 except IndexError: # no items?
1329 pass
1333 pass
1330 else:
1334 else:
1331 if item.marked:
1335 if item.marked:
1332 item.marked = False
1336 item.marked = False
1333 level.marked -= 1
1337 level.marked -= 1
1334 else:
1338 else:
1335 item.marked = True
1339 item.marked = True
1336 level.marked += 1
1340 level.marked += 1
1337
1341
1338 def cmd_sortattrasc(self):
1342 def cmd_sortattrasc(self):
1339 """
1343 """
1340 Sort the objects (in ascending order) using the attribute under
1344 Sort the objects (in ascending order) using the attribute under
1341 the cursor as the sort key.
1345 the cursor as the sort key.
1342 """
1346 """
1343 level = self.levels[-1]
1347 level = self.levels[-1]
1344 attr = level.displayattr[1]
1348 attr = level.displayattr[1]
1345 if attr is ipipe.noitem:
1349 if attr is ipipe.noitem:
1346 curses.beep()
1350 curses.beep()
1347 self.report(CommandError("no column under cursor"))
1351 self.report(CommandError("no column under cursor"))
1348 return
1352 return
1349 self.report("sort by %s (ascending)" % attr.name())
1353 self.report("sort by %s (ascending)" % attr.name())
1350 def key(item):
1354 def key(item):
1351 try:
1355 try:
1352 return attr.value(item)
1356 return attr.value(item)
1353 except (KeyboardInterrupt, SystemExit):
1357 except (KeyboardInterrupt, SystemExit):
1354 raise
1358 raise
1355 except Exception:
1359 except Exception:
1356 return None
1360 return None
1357 level.sort(key)
1361 level.sort(key)
1358
1362
1359 def cmd_sortattrdesc(self):
1363 def cmd_sortattrdesc(self):
1360 """
1364 """
1361 Sort the objects (in descending order) using the attribute under
1365 Sort the objects (in descending order) using the attribute under
1362 the cursor as the sort key.
1366 the cursor as the sort key.
1363 """
1367 """
1364 level = self.levels[-1]
1368 level = self.levels[-1]
1365 attr = level.displayattr[1]
1369 attr = level.displayattr[1]
1366 if attr is ipipe.noitem:
1370 if attr is ipipe.noitem:
1367 curses.beep()
1371 curses.beep()
1368 self.report(CommandError("no column under cursor"))
1372 self.report(CommandError("no column under cursor"))
1369 return
1373 return
1370 self.report("sort by %s (descending)" % attr.name())
1374 self.report("sort by %s (descending)" % attr.name())
1371 def key(item):
1375 def key(item):
1372 try:
1376 try:
1373 return attr.value(item)
1377 return attr.value(item)
1374 except (KeyboardInterrupt, SystemExit):
1378 except (KeyboardInterrupt, SystemExit):
1375 raise
1379 raise
1376 except Exception:
1380 except Exception:
1377 return None
1381 return None
1378 level.sort(key, reverse=True)
1382 level.sort(key, reverse=True)
1379
1383
1380 def cmd_hideattr(self):
1384 def cmd_hideattr(self):
1381 """
1385 """
1382 Hide the attribute under the cursor.
1386 Hide the attribute under the cursor.
1383 """
1387 """
1384 level = self.levels[-1]
1388 level = self.levels[-1]
1385 if level.displayattr[0] is None:
1389 if level.displayattr[0] is None:
1386 self.beep()
1390 self.beep()
1387 else:
1391 else:
1388 self.report("hideattr")
1392 self.report("hideattr")
1389 level.hiddenattrs.add(level.displayattr[1])
1393 level.hiddenattrs.add(level.displayattr[1])
1390 level.moveto(level.curx, level.cury, refresh=True)
1394 level.moveto(level.curx, level.cury, refresh=True)
1391
1395
1392 def cmd_unhideattrs(self):
1396 def cmd_unhideattrs(self):
1393 """
1397 """
1394 Make all attributes visible again.
1398 Make all attributes visible again.
1395 """
1399 """
1396 level = self.levels[-1]
1400 level = self.levels[-1]
1397 self.report("unhideattrs")
1401 self.report("unhideattrs")
1398 level.hiddenattrs.clear()
1402 level.hiddenattrs.clear()
1399 level.moveto(level.curx, level.cury, refresh=True)
1403 level.moveto(level.curx, level.cury, refresh=True)
1400
1404
1401 def cmd_goto(self):
1405 def cmd_goto(self):
1402 """
1406 """
1403 Jump to a row. The row number can be entered at the
1407 Jump to a row. The row number can be entered at the
1404 bottom of the screen.
1408 bottom of the screen.
1405 """
1409 """
1406 self.startkeyboardinput("goto")
1410 self.startkeyboardinput("goto")
1407
1411
1408 def cmd_find(self):
1412 def cmd_find(self):
1409 """
1413 """
1410 Search forward for a row. The search condition can be entered at the
1414 Search forward for a row. The search condition can be entered at the
1411 bottom of the screen.
1415 bottom of the screen.
1412 """
1416 """
1413 self.startkeyboardinput("find")
1417 self.startkeyboardinput("find")
1414
1418
1415 def cmd_findbackwards(self):
1419 def cmd_findbackwards(self):
1416 """
1420 """
1417 Search backward for a row. The search condition can be entered at the
1421 Search backward for a row. The search condition can be entered at the
1418 bottom of the screen.
1422 bottom of the screen.
1419 """
1423 """
1420 self.startkeyboardinput("findbackwards")
1424 self.startkeyboardinput("findbackwards")
1421
1425
1422 def cmd_refresh(self):
1426 def cmd_refresh(self):
1423 """
1427 """
1424 Refreshes the display by restarting the iterator.
1428 Refreshes the display by restarting the iterator.
1425 """
1429 """
1426 level = self.levels[-1]
1430 level = self.levels[-1]
1427 self.report("refresh")
1431 self.report("refresh")
1428 level.refresh()
1432 level.refresh()
1429
1433
1430 def cmd_refreshfind(self):
1434 def cmd_refreshfind(self):
1431 """
1435 """
1432 Refreshes the display by restarting the iterator and goes back to the
1436 Refreshes the display by restarting the iterator and goes back to the
1433 same object the cursor was on before restarting (if this object can't be
1437 same object the cursor was on before restarting (if this object can't be
1434 found the cursor jumps back to the first object).
1438 found the cursor jumps back to the first object).
1435 """
1439 """
1436 level = self.levels[-1]
1440 level = self.levels[-1]
1437 self.report("refreshfind")
1441 self.report("refreshfind")
1438 level.refreshfind()
1442 level.refreshfind()
1439
1443
1440 def cmd_help(self):
1444 def cmd_help(self):
1441 """
1445 """
1442 Opens the help screen as a new browser level, describing keyboard
1446 Opens the help screen as a new browser level, describing keyboard
1443 shortcuts.
1447 shortcuts.
1444 """
1448 """
1445 for level in self.levels:
1449 for level in self.levels:
1446 if isinstance(level.input, _BrowserHelp):
1450 if isinstance(level.input, _BrowserHelp):
1447 curses.beep()
1451 curses.beep()
1448 self.report(CommandError("help already active"))
1452 self.report(CommandError("help already active"))
1449 return
1453 return
1450
1454
1451 self.enter(_BrowserHelp(self))
1455 self.enter(_BrowserHelp(self))
1452
1456
1453 def cmd_quit(self):
1457 def cmd_quit(self):
1454 """
1458 """
1455 Quit the browser and return to the IPython prompt.
1459 Quit the browser and return to the IPython prompt.
1456 """
1460 """
1457 self.returnvalue = None
1461 self.returnvalue = None
1458 return True
1462 return True
1459
1463
1460 def sigwinchhandler(self, signal, frame):
1464 def sigwinchhandler(self, signal, frame):
1461 self.resized = True
1465 self.resized = True
1462
1466
1463 def _dodisplay(self, scr):
1467 def _dodisplay(self, scr):
1464 """
1468 """
1465 This method is the workhorse of the browser. It handles screen
1469 This method is the workhorse of the browser. It handles screen
1466 drawing and the keyboard.
1470 drawing and the keyboard.
1467 """
1471 """
1468 self.scr = scr
1472 self.scr = scr
1469 curses.halfdelay(1)
1473 curses.halfdelay(1)
1470 footery = 2
1474 footery = 2
1471
1475
1472 keys = []
1476 keys = []
1473 for cmd in ("quit", "help"):
1477 for cmd in ("quit", "help"):
1474 key = self.keymap.findkey(cmd, None)
1478 key = self.keymap.findkey(cmd, None)
1475 if key is not None:
1479 if key is not None:
1476 keys.append("%s=%s" % (self.keylabel(key), cmd))
1480 keys.append("%s=%s" % (self.keylabel(key), cmd))
1477 helpmsg = " | %s" % " ".join(keys)
1481 helpmsg = " | %s" % " ".join(keys)
1478
1482
1479 scr.clear()
1483 scr.clear()
1480 msg = "Fetching first batch of objects..."
1484 msg = "Fetching first batch of objects..."
1481 (self.scrsizey, self.scrsizex) = scr.getmaxyx()
1485 (self.scrsizey, self.scrsizex) = scr.getmaxyx()
1482 scr.addstr(self.scrsizey//2, (self.scrsizex-len(msg))//2, msg)
1486 scr.addstr(self.scrsizey//2, (self.scrsizex-len(msg))//2, msg)
1483 scr.refresh()
1487 scr.refresh()
1484
1488
1485 lastc = -1
1489 lastc = -1
1486
1490
1487 self.levels = []
1491 self.levels = []
1488 # enter the first level
1492 # enter the first level
1489 self.enter(self.input, *self.attrs)
1493 self.enter(self.input, *self.attrs)
1490
1494
1491 self._calcheaderlines(None)
1495 self._calcheaderlines(None)
1492
1496
1493 while True:
1497 while True:
1494 level = self.levels[-1]
1498 level = self.levels[-1]
1495 (self.scrsizey, self.scrsizex) = scr.getmaxyx()
1499 (self.scrsizey, self.scrsizex) = scr.getmaxyx()
1496 level.mainsizey = self.scrsizey-1-self._headerlines-footery
1500 level.mainsizey = self.scrsizey-1-self._headerlines-footery
1497
1501
1498 # Paint object header
1502 # Paint object header
1499 for i in xrange(self._firstheaderline, self._firstheaderline+self._headerlines):
1503 for i in xrange(self._firstheaderline, self._firstheaderline+self._headerlines):
1500 lv = self.levels[i]
1504 lv = self.levels[i]
1501 posx = 0
1505 posx = 0
1502 posy = i-self._firstheaderline
1506 posy = i-self._firstheaderline
1503 endx = self.scrsizex
1507 endx = self.scrsizex
1504 if i: # not the first level
1508 if i: # not the first level
1505 msg = " (%d/%d" % (self.levels[i-1].cury, len(self.levels[i-1].items))
1509 msg = " (%d/%d" % (self.levels[i-1].cury, len(self.levels[i-1].items))
1506 if not self.levels[i-1].exhausted:
1510 if not self.levels[i-1].exhausted:
1507 msg += "+"
1511 msg += "+"
1508 msg += ") "
1512 msg += ") "
1509 endx -= len(msg)+1
1513 endx -= len(msg)+1
1510 posx += self.addstr(posy, posx, 0, endx, " ibrowse #%d: " % i, self.style_objheadertext)
1514 posx += self.addstr(posy, posx, 0, endx, " ibrowse #%d: " % i, self.style_objheadertext)
1511 for (style, text) in lv.header:
1515 for (style, text) in lv.header:
1512 posx += self.addstr(posy, posx, 0, endx, text, self.style_objheaderobject)
1516 posx += self.addstr(posy, posx, 0, endx, text, self.style_objheaderobject)
1513 if posx >= endx:
1517 if posx >= endx:
1514 break
1518 break
1515 if i:
1519 if i:
1516 posx += self.addstr(posy, posx, 0, self.scrsizex, msg, self.style_objheadernumber)
1520 posx += self.addstr(posy, posx, 0, self.scrsizex, msg, self.style_objheadernumber)
1517 posx += self.addchr(posy, posx, 0, self.scrsizex, " ", self.scrsizex-posx, self.style_objheadernumber)
1521 posx += self.addchr(posy, posx, 0, self.scrsizex, " ", self.scrsizex-posx, self.style_objheadernumber)
1518
1522
1519 if not level.items:
1523 if not level.items:
1520 self.addchr(self._headerlines, 0, 0, self.scrsizex, " ", self.scrsizex, self.style_colheader)
1524 self.addchr(self._headerlines, 0, 0, self.scrsizex, " ", self.scrsizex, self.style_colheader)
1521 self.addstr(self._headerlines+1, 0, 0, self.scrsizex, " <empty>", astyle.style_error)
1525 self.addstr(self._headerlines+1, 0, 0, self.scrsizex, " <empty>", astyle.style_error)
1522 scr.clrtobot()
1526 scr.clrtobot()
1523 else:
1527 else:
1524 # Paint column headers
1528 # Paint column headers
1525 scr.move(self._headerlines, 0)
1529 scr.move(self._headerlines, 0)
1526 scr.addstr(" %*s " % (level.numbersizex, "#"), self.getstyle(self.style_colheader))
1530 scr.addstr(" %*s " % (level.numbersizex, "#"), self.getstyle(self.style_colheader))
1527 scr.addstr(self.headersepchar, self.getstyle(self.style_colheadersep))
1531 scr.addstr(self.headersepchar, self.getstyle(self.style_colheadersep))
1528 begx = level.numbersizex+3
1532 begx = level.numbersizex+3
1529 posx = begx-level.datastartx
1533 posx = begx-level.datastartx
1530 for attr in level.displayattrs:
1534 for attr in level.displayattrs:
1531 attrname = attr.name()
1535 attrname = attr.name()
1532 cwidth = level.colwidths[attr]
1536 cwidth = level.colwidths[attr]
1533 header = attrname.ljust(cwidth)
1537 header = attrname.ljust(cwidth)
1534 if attr is level.displayattr[1]:
1538 if attr is level.displayattr[1]:
1535 style = self.style_colheaderhere
1539 style = self.style_colheaderhere
1536 else:
1540 else:
1537 style = self.style_colheader
1541 style = self.style_colheader
1538 posx += self.addstr(self._headerlines, posx, begx, self.scrsizex, header, style)
1542 posx += self.addstr(self._headerlines, posx, begx, self.scrsizex, header, style)
1539 posx += self.addstr(self._headerlines, posx, begx, self.scrsizex, self.headersepchar, self.style_colheadersep)
1543 posx += self.addstr(self._headerlines, posx, begx, self.scrsizex, self.headersepchar, self.style_colheadersep)
1540 if posx >= self.scrsizex:
1544 if posx >= self.scrsizex:
1541 break
1545 break
1542 else:
1546 else:
1543 scr.addstr(" "*(self.scrsizex-posx), self.getstyle(self.style_colheader))
1547 scr.addstr(" "*(self.scrsizex-posx), self.getstyle(self.style_colheader))
1544
1548
1545 # Paint rows
1549 # Paint rows
1546 posy = self._headerlines+1+level.datastarty
1550 posy = self._headerlines+1+level.datastarty
1547 for i in xrange(level.datastarty, min(level.datastarty+level.mainsizey, len(level.items))):
1551 for i in xrange(level.datastarty, min(level.datastarty+level.mainsizey, len(level.items))):
1548 cache = level.items[i]
1552 cache = level.items[i]
1549 if i == level.cury:
1553 if i == level.cury:
1550 style = self.style_numberhere
1554 style = self.style_numberhere
1551 else:
1555 else:
1552 style = self.style_number
1556 style = self.style_number
1553
1557
1554 posy = self._headerlines+1+i-level.datastarty
1558 posy = self._headerlines+1+i-level.datastarty
1555 posx = begx-level.datastartx
1559 posx = begx-level.datastartx
1556
1560
1557 scr.move(posy, 0)
1561 scr.move(posy, 0)
1558 scr.addstr(" %*d%s" % (level.numbersizex, i, " !"[cache.marked]), self.getstyle(style))
1562 scr.addstr(" %*d%s" % (level.numbersizex, i, " !"[cache.marked]), self.getstyle(style))
1559 scr.addstr(self.headersepchar, self.getstyle(self.style_sep))
1563 scr.addstr(self.headersepchar, self.getstyle(self.style_sep))
1560
1564
1561 for attrname in level.displayattrs:
1565 for attrname in level.displayattrs:
1562 cwidth = level.colwidths[attrname]
1566 cwidth = level.colwidths[attrname]
1563 try:
1567 try:
1564 (align, length, parts) = level.displayrows[i-level.datastarty][attrname]
1568 (align, length, parts) = level.displayrows[i-level.datastarty][attrname]
1565 except KeyError:
1569 except KeyError:
1566 align = 2
1570 align = 2
1567 style = astyle.style_nodata
1571 style = astyle.style_nodata
1568 if i == level.cury:
1572 if i == level.cury:
1569 style = self.getstylehere(style)
1573 style = self.getstylehere(style)
1570 padstyle = self.style_datapad
1574 padstyle = self.style_datapad
1571 sepstyle = self.style_sep
1575 sepstyle = self.style_sep
1572 if i == level.cury:
1576 if i == level.cury:
1573 padstyle = self.getstylehere(padstyle)
1577 padstyle = self.getstylehere(padstyle)
1574 sepstyle = self.getstylehere(sepstyle)
1578 sepstyle = self.getstylehere(sepstyle)
1575 if align == 2:
1579 if align == 2:
1576 posx += self.addchr(posy, posx, begx, self.scrsizex, self.nodatachar, cwidth, style)
1580 posx += self.addchr(posy, posx, begx, self.scrsizex, self.nodatachar, cwidth, style)
1577 else:
1581 else:
1578 if align == 1:
1582 if align == 1:
1579 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, cwidth-length, padstyle)
1583 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, cwidth-length, padstyle)
1580 elif align == 0:
1584 elif align == 0:
1581 pad1 = (cwidth-length)//2
1585 pad1 = (cwidth-length)//2
1582 pad2 = cwidth-length-len(pad1)
1586 pad2 = cwidth-length-len(pad1)
1583 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, pad1, padstyle)
1587 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, pad1, padstyle)
1584 for (style, text) in parts:
1588 for (style, text) in parts:
1585 if i == level.cury:
1589 if i == level.cury:
1586 style = self.getstylehere(style)
1590 style = self.getstylehere(style)
1587 posx += self.addstr(posy, posx, begx, self.scrsizex, text, style)
1591 posx += self.addstr(posy, posx, begx, self.scrsizex, text, style)
1588 if posx >= self.scrsizex:
1592 if posx >= self.scrsizex:
1589 break
1593 break
1590 if align == -1:
1594 if align == -1:
1591 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, cwidth-length, padstyle)
1595 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, cwidth-length, padstyle)
1592 elif align == 0:
1596 elif align == 0:
1593 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, pad2, padstyle)
1597 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, pad2, padstyle)
1594 posx += self.addstr(posy, posx, begx, self.scrsizex, self.datasepchar, sepstyle)
1598 posx += self.addstr(posy, posx, begx, self.scrsizex, self.datasepchar, sepstyle)
1595 else:
1599 else:
1596 scr.clrtoeol()
1600 scr.clrtoeol()
1597
1601
1598 # Add blank row headers for the rest of the screen
1602 # Add blank row headers for the rest of the screen
1599 for posy in xrange(posy+1, self.scrsizey-2):
1603 for posy in xrange(posy+1, self.scrsizey-2):
1600 scr.addstr(posy, 0, " " * (level.numbersizex+2), self.getstyle(self.style_colheader))
1604 scr.addstr(posy, 0, " " * (level.numbersizex+2), self.getstyle(self.style_colheader))
1601 scr.clrtoeol()
1605 scr.clrtoeol()
1602
1606
1603 posy = self.scrsizey-footery
1607 posy = self.scrsizey-footery
1604 # Display footer
1608 # Display footer
1605 scr.addstr(posy, 0, " "*self.scrsizex, self.getstyle(self.style_footer))
1609 scr.addstr(posy, 0, " "*self.scrsizex, self.getstyle(self.style_footer))
1606
1610
1607 if level.exhausted:
1611 if level.exhausted:
1608 flag = ""
1612 flag = ""
1609 else:
1613 else:
1610 flag = "+"
1614 flag = "+"
1611
1615
1612 endx = self.scrsizex-len(helpmsg)-1
1616 endx = self.scrsizex-len(helpmsg)-1
1613 scr.addstr(posy, endx, helpmsg, self.getstyle(self.style_footer))
1617 scr.addstr(posy, endx, helpmsg, self.getstyle(self.style_footer))
1614
1618
1615 posx = 0
1619 posx = 0
1616 msg = " %d%s objects (%d marked): " % (len(level.items), flag, level.marked)
1620 msg = " %d%s objects (%d marked): " % (len(level.items), flag, level.marked)
1617 posx += self.addstr(posy, posx, 0, endx, msg, self.style_footer)
1621 posx += self.addstr(posy, posx, 0, endx, msg, self.style_footer)
1618 try:
1622 try:
1619 item = level.items[level.cury].item
1623 item = level.items[level.cury].item
1620 except IndexError: # empty
1624 except IndexError: # empty
1621 pass
1625 pass
1622 else:
1626 else:
1623 for (nostyle, text) in ipipe.xrepr(item, "footer"):
1627 for (nostyle, text) in ipipe.xrepr(item, "footer"):
1624 if not isinstance(nostyle, int):
1628 if not isinstance(nostyle, int):
1625 posx += self.addstr(posy, posx, 0, endx, text, self.style_footer)
1629 posx += self.addstr(posy, posx, 0, endx, text, self.style_footer)
1626 if posx >= endx:
1630 if posx >= endx:
1627 break
1631 break
1628
1632
1629 attrstyle = [(astyle.style_default, "no attribute")]
1633 attrstyle = [(astyle.style_default, "no attribute")]
1630 attr = level.displayattr[1]
1634 attr = level.displayattr[1]
1631 if attr is not ipipe.noitem and not isinstance(attr, ipipe.SelfDescriptor):
1635 if attr is not ipipe.noitem and not isinstance(attr, ipipe.SelfDescriptor):
1632 posx += self.addstr(posy, posx, 0, endx, " | ", self.style_footer)
1636 posx += self.addstr(posy, posx, 0, endx, " | ", self.style_footer)
1633 posx += self.addstr(posy, posx, 0, endx, attr.name(), self.style_footer)
1637 posx += self.addstr(posy, posx, 0, endx, attr.name(), self.style_footer)
1634 posx += self.addstr(posy, posx, 0, endx, ": ", self.style_footer)
1638 posx += self.addstr(posy, posx, 0, endx, ": ", self.style_footer)
1635 try:
1639 try:
1636 value = attr.value(item)
1640 value = attr.value(item)
1637 except (SystemExit, KeyboardInterrupt):
1641 except (SystemExit, KeyboardInterrupt):
1638 raise
1642 raise
1639 except Exception, exc:
1643 except Exception, exc:
1640 value = exc
1644 value = exc
1641 if value is not ipipe.noitem:
1645 if value is not ipipe.noitem:
1642 attrstyle = ipipe.xrepr(value, "footer")
1646 attrstyle = ipipe.xrepr(value, "footer")
1643 for (nostyle, text) in attrstyle:
1647 for (nostyle, text) in attrstyle:
1644 if not isinstance(nostyle, int):
1648 if not isinstance(nostyle, int):
1645 posx += self.addstr(posy, posx, 0, endx, text, self.style_footer)
1649 posx += self.addstr(posy, posx, 0, endx, text, self.style_footer)
1646 if posx >= endx:
1650 if posx >= endx:
1647 break
1651 break
1648
1652
1649 try:
1653 try:
1650 # Display input prompt
1654 # Display input prompt
1651 if self.mode in self.prompts:
1655 if self.mode in self.prompts:
1652 history = self.prompts[self.mode]
1656 history = self.prompts[self.mode]
1653 posx = 0
1657 posx = 0
1654 posy = self.scrsizey-1
1658 posy = self.scrsizey-1
1655 posx += self.addstr(posy, posx, 0, endx, history.prompt, astyle.style_default)
1659 posx += self.addstr(posy, posx, 0, endx, history.prompt, astyle.style_default)
1656 posx += self.addstr(posy, posx, 0, endx, " [", astyle.style_default)
1660 posx += self.addstr(posy, posx, 0, endx, " [", astyle.style_default)
1657 if history.cury==-1:
1661 if history.cury==-1:
1658 text = "new"
1662 text = "new"
1659 else:
1663 else:
1660 text = str(history.cury+1)
1664 text = str(history.cury+1)
1661 posx += self.addstr(posy, posx, 0, endx, text, astyle.style_type_number)
1665 posx += self.addstr(posy, posx, 0, endx, text, astyle.style_type_number)
1662 if history.history:
1666 if history.history:
1663 posx += self.addstr(posy, posx, 0, endx, "/", astyle.style_default)
1667 posx += self.addstr(posy, posx, 0, endx, "/", astyle.style_default)
1664 posx += self.addstr(posy, posx, 0, endx, str(len(history.history)), astyle.style_type_number)
1668 posx += self.addstr(posy, posx, 0, endx, str(len(history.history)), astyle.style_type_number)
1665 posx += self.addstr(posy, posx, 0, endx, "]: ", astyle.style_default)
1669 posx += self.addstr(posy, posx, 0, endx, "]: ", astyle.style_default)
1666 inputstartx = posx
1670 inputstartx = posx
1667 posx += self.addstr(posy, posx, 0, endx, history.input, astyle.style_default)
1671 posx += self.addstr(posy, posx, 0, endx, history.input, astyle.style_default)
1668 # Display report
1672 # Display report
1669 else:
1673 else:
1670 if self._report is not None:
1674 if self._report is not None:
1671 if isinstance(self._report, Exception):
1675 if isinstance(self._report, Exception):
1672 style = self.getstyle(astyle.style_error)
1676 style = self.getstyle(astyle.style_error)
1673 if self._report.__class__.__module__ == "exceptions":
1677 if self._report.__class__.__module__ == "exceptions":
1674 msg = "%s: %s" % \
1678 msg = "%s: %s" % \
1675 (self._report.__class__.__name__, self._report)
1679 (self._report.__class__.__name__, self._report)
1676 else:
1680 else:
1677 msg = "%s.%s: %s" % \
1681 msg = "%s.%s: %s" % \
1678 (self._report.__class__.__module__,
1682 (self._report.__class__.__module__,
1679 self._report.__class__.__name__, self._report)
1683 self._report.__class__.__name__, self._report)
1680 else:
1684 else:
1681 style = self.getstyle(self.style_report)
1685 style = self.getstyle(self.style_report)
1682 msg = self._report
1686 msg = self._report
1683 scr.addstr(self.scrsizey-1, 0, msg[:self.scrsizex], style)
1687 scr.addstr(self.scrsizey-1, 0, msg[:self.scrsizex], style)
1684 self._report = None
1688 self._report = None
1685 else:
1689 else:
1686 scr.move(self.scrsizey-1, 0)
1690 scr.move(self.scrsizey-1, 0)
1687 except curses.error:
1691 except curses.error:
1688 # Protect against errors from writing to the last line
1692 # Protect against errors from writing to the last line
1689 pass
1693 pass
1690 scr.clrtoeol()
1694 scr.clrtoeol()
1691
1695
1692 # Position cursor
1696 # Position cursor
1693 if self.mode in self.prompts:
1697 if self.mode in self.prompts:
1694 history = self.prompts[self.mode]
1698 history = self.prompts[self.mode]
1695 scr.move(self.scrsizey-1, inputstartx+history.curx)
1699 scr.move(self.scrsizey-1, inputstartx+history.curx)
1696 else:
1700 else:
1697 scr.move(
1701 scr.move(
1698 1+self._headerlines+level.cury-level.datastarty,
1702 1+self._headerlines+level.cury-level.datastarty,
1699 level.numbersizex+3+level.curx-level.datastartx
1703 level.numbersizex+3+level.curx-level.datastartx
1700 )
1704 )
1701 scr.refresh()
1705 scr.refresh()
1702
1706
1703 # Check keyboard
1707 # Check keyboard
1704 while True:
1708 while True:
1705 c = scr.getch()
1709 c = scr.getch()
1706 if self.resized:
1710 if self.resized:
1707 size = fcntl.ioctl(0, tty.TIOCGWINSZ, "12345678")
1711 size = fcntl.ioctl(0, tty.TIOCGWINSZ, "12345678")
1708 size = struct.unpack("4H", size)
1712 size = struct.unpack("4H", size)
1709 oldsize = scr.getmaxyx()
1713 oldsize = scr.getmaxyx()
1710 scr.erase()
1714 scr.erase()
1711 curses.resize_term(size[0], size[1])
1715 curses.resize_term(size[0], size[1])
1712 newsize = scr.getmaxyx()
1716 newsize = scr.getmaxyx()
1713 scr.erase()
1717 scr.erase()
1714 for l in self.levels:
1718 for l in self.levels:
1715 l.mainsizey += newsize[0]-oldsize[0]
1719 l.mainsizey += newsize[0]-oldsize[0]
1716 l.moveto(l.curx, l.cury, refresh=True)
1720 l.moveto(l.curx, l.cury, refresh=True)
1717 scr.refresh()
1721 scr.refresh()
1718 self.resized = False
1722 self.resized = False
1719 break # Redisplay
1723 break # Redisplay
1720 if self.mode in self.prompts:
1724 if self.mode in self.prompts:
1721 if self.prompts[self.mode].handlekey(self, c):
1725 if self.prompts[self.mode].handlekey(self, c):
1722 break # Redisplay
1726 break # Redisplay
1723 else:
1727 else:
1724 # if no key is pressed slow down and beep again
1728 # if no key is pressed slow down and beep again
1725 if c == -1:
1729 if c == -1:
1726 self.stepx = 1.
1730 self.stepx = 1.
1727 self.stepy = 1.
1731 self.stepy = 1.
1728 self._dobeep = True
1732 self._dobeep = True
1729 else:
1733 else:
1730 # if a different key was pressed slow down and beep too
1734 # if a different key was pressed slow down and beep too
1731 if c != lastc:
1735 if c != lastc:
1732 lastc = c
1736 lastc = c
1733 self.stepx = 1.
1737 self.stepx = 1.
1734 self.stepy = 1.
1738 self.stepy = 1.
1735 self._dobeep = True
1739 self._dobeep = True
1736 cmdname = self.keymap.get(c, None)
1740 cmdname = self.keymap.get(c, None)
1737 if cmdname is None:
1741 if cmdname is None:
1738 self.report(
1742 self.report(
1739 UnassignedKeyError("Unassigned key %s" %
1743 UnassignedKeyError("Unassigned key %s" %
1740 self.keylabel(c)))
1744 self.keylabel(c)))
1741 else:
1745 else:
1742 cmdfunc = getattr(self, "cmd_%s" % cmdname, None)
1746 cmdfunc = getattr(self, "cmd_%s" % cmdname, None)
1743 if cmdfunc is None:
1747 if cmdfunc is None:
1744 self.report(
1748 self.report(
1745 UnknownCommandError("Unknown command %r" %
1749 UnknownCommandError("Unknown command %r" %
1746 (cmdname,)))
1750 (cmdname,)))
1747 elif cmdfunc():
1751 elif cmdfunc():
1748 returnvalue = self.returnvalue
1752 returnvalue = self.returnvalue
1749 self.returnvalue = None
1753 self.returnvalue = None
1750 return returnvalue
1754 return returnvalue
1751 self.stepx = self.nextstepx(self.stepx)
1755 self.stepx = self.nextstepx(self.stepx)
1752 self.stepy = self.nextstepy(self.stepy)
1756 self.stepy = self.nextstepy(self.stepy)
1753 curses.flushinp() # get rid of type ahead
1757 curses.flushinp() # get rid of type ahead
1754 break # Redisplay
1758 break # Redisplay
1755 self.scr = None
1759 self.scr = None
1756
1760
1757 def display(self):
1761 def display(self):
1758 if hasattr(curses, "resize_term"):
1762 if hasattr(curses, "resize_term"):
1759 oldhandler = signal.signal(signal.SIGWINCH, self.sigwinchhandler)
1763 oldhandler = signal.signal(signal.SIGWINCH, self.sigwinchhandler)
1760 try:
1764 try:
1761 return curses.wrapper(self._dodisplay)
1765 return curses.wrapper(self._dodisplay)
1762 finally:
1766 finally:
1763 signal.signal(signal.SIGWINCH, oldhandler)
1767 signal.signal(signal.SIGWINCH, oldhandler)
1764 else:
1768 else:
1765 return curses.wrapper(self._dodisplay)
1769 return curses.wrapper(self._dodisplay)
@@ -1,1125 +1,1129 b''
1 # -*- coding: iso-8859-1 -*-
1 # -*- coding: iso-8859-1 -*-
2
2
3 import ipipe, os, webbrowser, urllib
3 import ipipe, os, webbrowser, urllib
4 from IPython import ipapi
4 from IPython import ipapi
5 import wx
5 import wx
6 import wx.grid, wx.html
6 import wx.grid, wx.html
7
7
8 try:
8 try:
9 sorted
9 sorted
10 except NameError:
10 except NameError:
11 from ipipe import sorted
11 from ipipe import sorted
12 try:
12 try:
13 set
13 set
14 except:
14 except:
15 from sets import Set as set
15 from sets import Set as set
16
16
17
17
18 __all__ = ["igrid"]
18 __all__ = ["igrid"]
19
19
20
20
21 help = """
21 help = """
22 <?xml version='1.0' encoding='iso-8859-1'?>
22 <?xml version='1.0' encoding='iso-8859-1'?>
23 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
23 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
24 <html>
24 <html>
25 <head>
25 <head>
26 <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
26 <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
27 <link rel="stylesheet" href="igrid_help.css" type="text/css" />
27 <link rel="stylesheet" href="igrid_help.css" type="text/css" />
28 <title>igrid help</title>
28 <title>igrid help</title>
29 </head>
29 </head>
30 <body>
30 <body>
31 <h1>igrid help</h1>
31 <h1>igrid help</h1>
32
32
33
33
34 <h2>Commands</h2>
34 <h2>Commands</h2>
35
35
36
36
37 <h3>pick (P)</h3>
37 <h3>pick (P)</h3>
38 <p>Pick the whole row (object is available as "_")</p>
38 <p>Pick the whole row (object is available as "_")</p>
39
39
40 <h3>pickattr (Shift-P)</h3>
40 <h3>pickattr (Shift-P)</h3>
41 <p>Pick the attribute under the cursor</p>
41 <p>Pick the attribute under the cursor</p>
42
42
43 <h3>pickallattrs (Shift-C)</h3>
43 <h3>pickallattrs (Shift-C)</h3>
44 <p>Pick the complete column under the cursor (i.e. the attribute under the
44 <p>Pick the complete column under the cursor (i.e. the attribute under the
45 cursor) from all currently fetched objects. These attributes will be returned
45 cursor) from all currently fetched objects. These attributes will be returned
46 as a list.</p>
46 as a list.</p>
47
47
48 <h3>pickinput (I)</h3>
48 <h3>pickinput (I)</h3>
49 <p>Pick the current row as next input line in IPython. Additionally the row is stored as "_"</p>
49 <p>Pick the current row as next input line in IPython. Additionally the row is stored as "_"</p>
50
50
51 <h3>pickinputattr (Shift-I)</h3>
51 <h3>pickinputattr (Shift-I)</h3>
52 <p>Pick the attribute under the cursor as next input line in IPython. Additionally the row is stored as "_"</p>
52 <p>Pick the attribute under the cursor as next input line in IPython. Additionally the row is stored as "_"</p>
53
53
54 <h3>enter (E)</h3>
54 <h3>enter (E)</h3>
55 <p>Enter the object under the cursor. (what this mean depends on the object
55 <p>Enter the object under the cursor. (what this mean depends on the object
56 itself, i.e. how it implements iteration). This opens a new browser 'level'.</p>
56 itself, i.e. how it implements iteration). This opens a new browser 'level'.</p>
57
57
58 <h3>enterattr (Shift-E)</h3>
58 <h3>enterattr (Shift-E)</h3>
59 <p>Enter the attribute under the cursor.</p>
59 <p>Enter the attribute under the cursor.</p>
60
60
61 <h3>detail (D)</h3>
61 <h3>detail (D)</h3>
62 <p>Show a detail view of the object under the cursor. This shows the name,
62 <p>Show a detail view of the object under the cursor. This shows the name,
63 type, doc string and value of the object attributes (and it might show more
63 type, doc string and value of the object attributes (and it might show more
64 attributes than in the list view, depending on the object).</p>
64 attributes than in the list view, depending on the object).</p>
65
65
66 <h3>detailattr (Shift-D)</h3>
66 <h3>detailattr (Shift-D)</h3>
67 <p>Show a detail view of the attribute under the cursor.</p>
67 <p>Show a detail view of the attribute under the cursor.</p>
68
68
69 <h3>pickrows (M)</h3>
69 <h3>pickrows (M)</h3>
70 <p>Pick multiple selected rows (M)</p>
70 <p>Pick multiple selected rows (M)</p>
71
71
72 <h3>pickrowsattr (CTRL-M)</h3>
72 <h3>pickrowsattr (CTRL-M)</h3>
73 <p>From multiple selected rows pick the cells matching the attribute the cursor is in (CTRL-M)</p>
73 <p>From multiple selected rows pick the cells matching the attribute the cursor is in (CTRL-M)</p>
74
74
75 <h3>find (CTRL-F)</h3>
75 <h3>find (CTRL-F)</h3>
76 <p>Find text</p>
76 <p>Find text</p>
77
77
78 <h3>find_expression (CTRL-Shift-F)</h3>
78 <h3>find_expression (CTRL-Shift-F)</h3>
79 <p>Find entries matching an expression</p>
79 <p>Find entries matching an expression</p>
80
80
81 <h3>find_next (F3)</h3>
81 <h3>find_next (F3)</h3>
82 <p>Find next occurrence</p>
82 <p>Find next occurrence</p>
83
83
84 <h3>find_previous (Shift-F3)</h3>
84 <h3>find_previous (Shift-F3)</h3>
85 <p>Find previous occurrence</p>
85 <p>Find previous occurrence</p>
86
86
87 <h3>sortattrasc (V)</h3>
87 <h3>sortattrasc (V)</h3>
88 <p>Sort the objects (in ascending order) using the attribute under the cursor as the sort key.</p>
88 <p>Sort the objects (in ascending order) using the attribute under the cursor as the sort key.</p>
89
89
90 <h3>sortattrdesc (Shift-V)</h3>
90 <h3>sortattrdesc (Shift-V)</h3>
91 <p>Sort the objects (in descending order) using the attribute under the cursor as the sort key.</p>
91 <p>Sort the objects (in descending order) using the attribute under the cursor as the sort key.</p>
92
92
93 <h3>refresh_once (R, F5)</h3>
93 <h3>refresh_once (R, F5)</h3>
94 <p>Refreshes the display by restarting the iterator</p>
94 <p>Refreshes the display by restarting the iterator</p>
95
95
96 <h3>refresh_every_second</h3>
96 <h3>refresh_every_second</h3>
97 <p>Refreshes the display by restarting the iterator every second until stopped by stop_refresh.</p>
97 <p>Refreshes the display by restarting the iterator every second until stopped by stop_refresh.</p>
98
98
99 <h3>refresh_interval</h3>
99 <h3>refresh_interval</h3>
100 <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>
100 <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>
101
101
102 <h3>stop_refresh</h3>
102 <h3>stop_refresh</h3>
103 <p>Stops all refresh timers.</p>
103 <p>Stops all refresh timers.</p>
104
104
105 <h3>leave (Backspace, DEL, X)</h3>
105 <h3>leave (Backspace, DEL, X)</h3>
106 <p>Close current tab (and all the tabs to the right of the current one).</h3>
106 <p>Close current tab (and all the tabs to the right of the current one).</h3>
107
107
108 <h3>quit (ESC,Q)</h3>
108 <h3>quit (ESC,Q)</h3>
109 <p>Quit igrid and return to the IPython prompt.</p>
109 <p>Quit igrid and return to the IPython prompt.</p>
110
110
111
111
112 <h2>Navigation</h2>
112 <h2>Navigation</h2>
113
113
114
114
115 <h3>Jump to the last column of the current row (END, CTRL-E, CTRL-Right)</h3>
115 <h3>Jump to the last column of the current row (END, CTRL-E, CTRL-Right)</h3>
116
116
117 <h3>Jump to the first column of the current row (HOME, CTRL-A, CTRL-Left)</h3>
117 <h3>Jump to the first column of the current row (HOME, CTRL-A, CTRL-Left)</h3>
118
118
119 <h3>Move the cursor one column to the left (&lt;)</h3>
119 <h3>Move the cursor one column to the left (&lt;)</h3>
120
120
121 <h3>Move the cursor one column to the right (&gt;)</h3>
121 <h3>Move the cursor one column to the right (&gt;)</h3>
122
122
123 <h3>Jump to the first row in the current column (CTRL-Up)</h3>
123 <h3>Jump to the first row in the current column (CTRL-Up)</h3>
124
124
125 <h3>Jump to the last row in the current column (CTRL-Down)</h3>
125 <h3>Jump to the last row in the current column (CTRL-Down)</h3>
126
126
127 </body>
127 </body>
128 </html>
128 </html>
129
129
130 """
130 """
131
131
132
132
133 class IGridRenderer(wx.grid.PyGridCellRenderer):
133 class IGridRenderer(wx.grid.PyGridCellRenderer):
134 """
134 """
135 This is a custom renderer for our IGridGrid
135 This is a custom renderer for our IGridGrid
136 """
136 """
137 def __init__(self, table):
137 def __init__(self, table):
138 self.maxchars = 200
138 self.maxchars = 200
139 self.table = table
139 self.table = table
140 self.colormap = (
140 self.colormap = (
141 ( 0, 0, 0),
141 ( 0, 0, 0),
142 (174, 0, 0),
142 (174, 0, 0),
143 ( 0, 174, 0),
143 ( 0, 174, 0),
144 (174, 174, 0),
144 (174, 174, 0),
145 ( 0, 0, 174),
145 ( 0, 0, 174),
146 (174, 0, 174),
146 (174, 0, 174),
147 ( 0, 174, 174),
147 ( 0, 174, 174),
148 ( 64, 64, 64)
148 ( 64, 64, 64)
149 )
149 )
150
150
151 wx.grid.PyGridCellRenderer.__init__(self)
151 wx.grid.PyGridCellRenderer.__init__(self)
152
152
153 def _getvalue(self, row, col):
153 def _getvalue(self, row, col):
154 try:
154 try:
155 value = self.table._displayattrs[col].value(self.table.items[row])
155 value = self.table._displayattrs[col].value(self.table.items[row])
156 (align, width, text) = ipipe.xformat(value, "cell", self.maxchars)
156 (align, width, text) = ipipe.xformat(value, "cell", self.maxchars)
157 except Exception, exc:
157 except Exception, exc:
158 (align, width, text) = ipipe.xformat(exc, "cell", self.maxchars)
158 (align, width, text) = ipipe.xformat(exc, "cell", self.maxchars)
159 return (align, text)
159 return (align, text)
160
160
161 def GetBestSize(self, grid, attr, dc, row, col):
161 def GetBestSize(self, grid, attr, dc, row, col):
162 text = grid.GetCellValue(row, col)
162 text = grid.GetCellValue(row, col)
163 (align, text) = self._getvalue(row, col)
163 (align, text) = self._getvalue(row, col)
164 dc.SetFont(attr.GetFont())
164 dc.SetFont(attr.GetFont())
165 (w, h) = dc.GetTextExtent(str(text))
165 (w, h) = dc.GetTextExtent(str(text))
166 return wx.Size(min(w+2, 600), h+2) # add border
166 return wx.Size(min(w+2, 600), h+2) # add border
167
167
168 def Draw(self, grid, attr, dc, rect, row, col, isSelected):
168 def Draw(self, grid, attr, dc, rect, row, col, isSelected):
169 """
169 """
170 Takes care of drawing everything in the cell; aligns the text
170 Takes care of drawing everything in the cell; aligns the text
171 """
171 """
172 text = grid.GetCellValue(row, col)
172 text = grid.GetCellValue(row, col)
173 (align, text) = self._getvalue(row, col)
173 (align, text) = self._getvalue(row, col)
174 if isSelected:
174 if isSelected:
175 bg = grid.GetSelectionBackground()
175 bg = grid.GetSelectionBackground()
176 else:
176 else:
177 bg = ["white", (240, 240, 240)][row%2]
177 bg = ["white", (240, 240, 240)][row%2]
178 dc.SetTextBackground(bg)
178 dc.SetTextBackground(bg)
179 dc.SetBrush(wx.Brush(bg, wx.SOLID))
179 dc.SetBrush(wx.Brush(bg, wx.SOLID))
180 dc.SetPen(wx.TRANSPARENT_PEN)
180 dc.SetPen(wx.TRANSPARENT_PEN)
181 dc.SetFont(attr.GetFont())
181 dc.SetFont(attr.GetFont())
182 dc.DrawRectangleRect(rect)
182 dc.DrawRectangleRect(rect)
183 dc.SetClippingRect(rect)
183 dc.SetClippingRect(rect)
184 # Format the text
184 # Format the text
185 if align == -1: # left alignment
185 if align == -1: # left alignment
186 (width, height) = dc.GetTextExtent(str(text))
186 (width, height) = dc.GetTextExtent(str(text))
187 x = rect[0]+1
187 x = rect[0]+1
188 y = rect[1]+0.5*(rect[3]-height)
188 y = rect[1]+0.5*(rect[3]-height)
189
189
190 for (style, part) in text:
190 for (style, part) in text:
191 if isSelected:
191 if isSelected:
192 fg = grid.GetSelectionForeground()
192 fg = grid.GetSelectionForeground()
193 else:
193 else:
194 fg = self.colormap[style.fg]
194 fg = self.colormap[style.fg]
195 dc.SetTextForeground(fg)
195 dc.SetTextForeground(fg)
196 (w, h) = dc.GetTextExtent(part)
196 (w, h) = dc.GetTextExtent(part)
197 dc.DrawText(part, x, y)
197 dc.DrawText(part, x, y)
198 x += w
198 x += w
199 elif align == 0: # center alignment
199 elif align == 0: # center alignment
200 (width, height) = dc.GetTextExtent(str(text))
200 (width, height) = dc.GetTextExtent(str(text))
201 x = rect[0]+0.5*(rect[2]-width)
201 x = rect[0]+0.5*(rect[2]-width)
202 y = rect[1]+0.5*(rect[3]-height)
202 y = rect[1]+0.5*(rect[3]-height)
203 for (style, part) in text:
203 for (style, part) in text:
204 if isSelected:
204 if isSelected:
205 fg = grid.GetSelectionForeground()
205 fg = grid.GetSelectionForeground()
206 else:
206 else:
207 fg = self.colormap[style.fg]
207 fg = self.colormap[style.fg]
208 dc.SetTextForeground(fg)
208 dc.SetTextForeground(fg)
209 (w, h) = dc.GetTextExtent(part)
209 (w, h) = dc.GetTextExtent(part)
210 dc.DrawText(part, x, y)
210 dc.DrawText(part, x, y)
211 x += w
211 x += w
212 else: # right alignment
212 else: # right alignment
213 (width, height) = dc.GetTextExtent(str(text))
213 (width, height) = dc.GetTextExtent(str(text))
214 x = rect[0]+rect[2]-1
214 x = rect[0]+rect[2]-1
215 y = rect[1]+0.5*(rect[3]-height)
215 y = rect[1]+0.5*(rect[3]-height)
216 for (style, part) in reversed(text):
216 for (style, part) in reversed(text):
217 (w, h) = dc.GetTextExtent(part)
217 (w, h) = dc.GetTextExtent(part)
218 x -= w
218 x -= w
219 if isSelected:
219 if isSelected:
220 fg = grid.GetSelectionForeground()
220 fg = grid.GetSelectionForeground()
221 else:
221 else:
222 fg = self.colormap[style.fg]
222 fg = self.colormap[style.fg]
223 dc.SetTextForeground(fg)
223 dc.SetTextForeground(fg)
224 dc.DrawText(part, x, y)
224 dc.DrawText(part, x, y)
225 dc.DestroyClippingRegion()
225 dc.DestroyClippingRegion()
226
226
227 def Clone(self):
227 def Clone(self):
228 return IGridRenderer(self.table)
228 return IGridRenderer(self.table)
229
229
230
230
231 class IGridTable(wx.grid.PyGridTableBase):
231 class IGridTable(wx.grid.PyGridTableBase):
232 # The data table for the ``IGridGrid``. Some dirty tricks were used here:
232 # The data table for the ``IGridGrid``. Some dirty tricks were used here:
233 # ``GetValue()`` does not get any values (or at least it does not return
233 # ``GetValue()`` does not get any values (or at least it does not return
234 # anything, accessing the values is done by the renderer)
234 # anything, accessing the values is done by the renderer)
235 # but rather tries to fetch the objects which were requested into the table.
235 # but rather tries to fetch the objects which were requested into the table.
236 # General behaviour is: Fetch the first X objects. If the user scrolls down
236 # General behaviour is: Fetch the first X objects. If the user scrolls down
237 # to the last object another bunch of X objects is fetched (if possible)
237 # to the last object another bunch of X objects is fetched (if possible)
238 def __init__(self, input, fontsize, *attrs):
238 def __init__(self, input, fontsize, *attrs):
239 wx.grid.PyGridTableBase.__init__(self)
239 wx.grid.PyGridTableBase.__init__(self)
240 self.input = input
240 self.input = input
241 self.iterator = ipipe.xiter(input)
241 self.iterator = ipipe.xiter(input)
242 self.items = []
242 self.items = []
243 self.attrs = [ipipe.upgradexattr(attr) for attr in attrs]
243 self.attrs = [ipipe.upgradexattr(attr) for attr in attrs]
244 self._displayattrs = self.attrs[:]
244 self._displayattrs = self.attrs[:]
245 self._displayattrset = set(self.attrs)
245 self._displayattrset = set(self.attrs)
246 self.fontsize = fontsize
246 self.fontsize = fontsize
247 self._fetch(1)
247 self._fetch(1)
248 self.timer = wx.Timer()
248 self.timer = wx.Timer()
249 self.timer.Bind(wx.EVT_TIMER, self.refresh_content)
249 self.timer.Bind(wx.EVT_TIMER, self.refresh_content)
250
250
251 def GetAttr(self, *args):
251 def GetAttr(self, *args):
252 attr = wx.grid.GridCellAttr()
252 attr = wx.grid.GridCellAttr()
253 attr.SetFont(wx.Font(self.fontsize, wx.TELETYPE, wx.NORMAL, wx.NORMAL))
253 attr.SetFont(wx.Font(self.fontsize, wx.TELETYPE, wx.NORMAL, wx.NORMAL))
254 return attr
254 return attr
255
255
256 def GetNumberRows(self):
256 def GetNumberRows(self):
257 return len(self.items)
257 return len(self.items)
258
258
259 def GetNumberCols(self):
259 def GetNumberCols(self):
260 return len(self._displayattrs)
260 return len(self._displayattrs)
261
261
262 def GetColLabelValue(self, col):
262 def GetColLabelValue(self, col):
263 if col < len(self._displayattrs):
263 if col < len(self._displayattrs):
264 return self._displayattrs[col].name()
264 return self._displayattrs[col].name()
265 else:
265 else:
266 return ""
266 return ""
267
267
268 def GetRowLabelValue(self, row):
268 def GetRowLabelValue(self, row):
269 return str(row)
269 return str(row)
270
270
271 def IsEmptyCell(self, row, col):
271 def IsEmptyCell(self, row, col):
272 return False
272 return False
273
273
274 def _append(self, item):
274 def _append(self, item):
275 self.items.append(item)
275 self.items.append(item)
276 # Nothing to do if the set of attributes has been fixed by the user
276 # Nothing to do if the set of attributes has been fixed by the user
277 if not self.attrs:
277 if not self.attrs:
278 for attr in ipipe.xattrs(item):
278 for attr in ipipe.xattrs(item):
279 attr = ipipe.upgradexattr(attr)
279 attr = ipipe.upgradexattr(attr)
280 if attr not in self._displayattrset:
280 if attr not in self._displayattrset:
281 self._displayattrs.append(attr)
281 self._displayattrs.append(attr)
282 self._displayattrset.add(attr)
282 self._displayattrset.add(attr)
283
283
284 def _fetch(self, count):
284 def _fetch(self, count):
285 # Try to fill ``self.items`` with at least ``count`` objects.
285 # Try to fill ``self.items`` with at least ``count`` objects.
286 have = len(self.items)
286 have = len(self.items)
287 while self.iterator is not None and have < count:
287 while self.iterator is not None and have < count:
288 try:
288 try:
289 item = self.iterator.next()
289 item = self.iterator.next()
290 except StopIteration:
290 except StopIteration:
291 self.iterator = None
291 self.iterator = None
292 break
292 break
293 except (KeyboardInterrupt, SystemExit):
293 except (KeyboardInterrupt, SystemExit):
294 raise
294 raise
295 except Exception, exc:
295 except Exception, exc:
296 have += 1
296 have += 1
297 self._append(exc)
297 self._append(exc)
298 self.iterator = None
298 self.iterator = None
299 break
299 break
300 else:
300 else:
301 have += 1
301 have += 1
302 self._append(item)
302 self._append(item)
303
303
304 def GetValue(self, row, col):
304 def GetValue(self, row, col):
305 # some kind of dummy-function: does not return anything but "";
305 # some kind of dummy-function: does not return anything but "";
306 # (The value isn't use anyway)
306 # (The value isn't use anyway)
307 # its main task is to trigger the fetch of new objects
307 # its main task is to trigger the fetch of new objects
308 sizing_needed = False
308 sizing_needed = False
309 had_cols = len(self._displayattrs)
309 had_cols = len(self._displayattrs)
310 had_rows = len(self.items)
310 had_rows = len(self.items)
311 if row == had_rows - 1 and self.iterator is not None:
311 if row == had_rows - 1 and self.iterator is not None:
312 self._fetch(row + 20)
312 self._fetch(row + 20)
313 sizing_needed = True
313 sizing_needed = True
314 have_rows = len(self.items)
314 have_rows = len(self.items)
315 have_cols = len(self._displayattrs)
315 have_cols = len(self._displayattrs)
316 if have_rows > had_rows:
316 if have_rows > had_rows:
317 msg = wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_NOTIFY_ROWS_APPENDED, have_rows - had_rows)
317 msg = wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_NOTIFY_ROWS_APPENDED, have_rows - had_rows)
318 self.GetView().ProcessTableMessage(msg)
318 self.GetView().ProcessTableMessage(msg)
319 sizing_needed = True
319 sizing_needed = True
320 if row >= have_rows:
320 if row >= have_rows:
321 return ""
321 return ""
322 if have_cols != had_cols:
322 if have_cols != had_cols:
323 msg = wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_NOTIFY_COLS_APPENDED, have_cols - had_cols)
323 msg = wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_NOTIFY_COLS_APPENDED, have_cols - had_cols)
324 self.GetView().ProcessTableMessage(msg)
324 self.GetView().ProcessTableMessage(msg)
325 sizing_needed = True
325 sizing_needed = True
326 if sizing_needed:
326 if sizing_needed:
327 self.GetView().AutoSizeColumns(False)
327 self.GetView().AutoSizeColumns(False)
328 return ""
328 return ""
329
329
330 def SetValue(self, row, col, value):
330 def SetValue(self, row, col, value):
331 pass
331 pass
332
332
333 def refresh_content(self, event):
333 def refresh_content(self, event):
334 msg = wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_NOTIFY_ROWS_DELETED, 0, self.GetNumberRows())
334 msg = wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_NOTIFY_ROWS_DELETED, 0, self.GetNumberRows())
335 self.GetView().ProcessTableMessage(msg)
335 self.GetView().ProcessTableMessage(msg)
336 self.iterator = ipipe.xiter(self.input)
336 self.iterator = ipipe.xiter(self.input)
337 self.items = []
337 self.items = []
338 self.attrs = [] # _append will calculate new displayattrs
338 self.attrs = [] # _append will calculate new displayattrs
339 self._fetch(1) # fetch one...
339 self._fetch(1) # fetch one...
340 if self.items:
340 if self.items:
341 msg = wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_NOTIFY_ROWS_APPENDED, 1)
341 msg = wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_NOTIFY_ROWS_APPENDED, 1)
342 self.GetView().ProcessTableMessage(msg)
342 self.GetView().ProcessTableMessage(msg)
343 self.GetValue(0, 0) # and trigger "fetch next 20"
343 self.GetValue(0, 0) # and trigger "fetch next 20"
344 item = self.items[0]
344 item = self.items[0]
345 self.GetView().AutoSizeColumns(False)
345 self.GetView().AutoSizeColumns(False)
346 panel = self.GetView().GetParent()
346 panel = self.GetView().GetParent()
347 nb = panel.GetParent()
347 nb = panel.GetParent()
348 current = nb.GetSelection()
348 current = nb.GetSelection()
349 if nb.GetPage(current) == panel:
349 if nb.GetPage(current) == panel:
350 self.GetView().set_footer(item)
350 self.GetView().set_footer(item)
351
351
352 class IGridGrid(wx.grid.Grid):
352 class IGridGrid(wx.grid.Grid):
353 # The actual grid
353 # The actual grid
354 # all methods for selecting/sorting/picking/... data are implemented here
354 # all methods for selecting/sorting/picking/... data are implemented here
355 def __init__(self, panel, input, *attrs):
355 def __init__(self, panel, input, *attrs):
356 wx.grid.Grid.__init__(self, panel)
356 wx.grid.Grid.__init__(self, panel)
357 fontsize = 9
357 fontsize = 9
358 self.input = input
358 self.input = input
359 self.table = IGridTable(self.input, fontsize, *attrs)
359 self.table = IGridTable(self.input, fontsize, *attrs)
360 self.SetTable(self.table, True)
360 self.SetTable(self.table, True)
361 self.SetSelectionMode(wx.grid.Grid.wxGridSelectRows)
361 self.SetSelectionMode(wx.grid.Grid.wxGridSelectRows)
362 self.SetDefaultRenderer(IGridRenderer(self.table))
362 self.SetDefaultRenderer(IGridRenderer(self.table))
363 self.EnableEditing(False)
363 self.EnableEditing(False)
364 self.Bind(wx.EVT_KEY_DOWN, self.key_pressed)
364 self.Bind(wx.EVT_KEY_DOWN, self.key_pressed)
365 self.Bind(wx.grid.EVT_GRID_CELL_LEFT_DCLICK, self.cell_doubleclicked)
365 self.Bind(wx.grid.EVT_GRID_CELL_LEFT_DCLICK, self.cell_doubleclicked)
366 self.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, self.cell_leftclicked)
366 self.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, self.cell_leftclicked)
367 self.Bind(wx.grid.EVT_GRID_LABEL_LEFT_DCLICK, self.label_doubleclicked)
367 self.Bind(wx.grid.EVT_GRID_LABEL_LEFT_DCLICK, self.label_doubleclicked)
368 self.Bind(wx.grid.EVT_GRID_LABEL_LEFT_CLICK, self.on_label_leftclick)
368 self.Bind(wx.grid.EVT_GRID_LABEL_LEFT_CLICK, self.on_label_leftclick)
369 self.Bind(wx.grid.EVT_GRID_RANGE_SELECT, self._on_selected_range)
369 self.Bind(wx.grid.EVT_GRID_RANGE_SELECT, self._on_selected_range)
370 self.Bind(wx.grid.EVT_GRID_SELECT_CELL, self._on_selected_cell)
370 self.Bind(wx.grid.EVT_GRID_SELECT_CELL, self._on_selected_cell)
371 self.current_selection = set()
371 self.current_selection = set()
372 self.maxchars = 200
372 self.maxchars = 200
373
373
374 def on_label_leftclick(self, event):
374 def on_label_leftclick(self, event):
375 event.Skip()
375 event.Skip()
376
376
377 def error_output(self, text):
377 def error_output(self, text):
378 wx.Bell()
378 wx.Bell()
379 frame = self.GetParent().GetParent().GetParent()
379 frame = self.GetParent().GetParent().GetParent()
380 frame.SetStatusText(str(text))
380 frame.SetStatusText(str(text))
381
381
382 def _on_selected_range(self, event):
382 def _on_selected_range(self, event):
383 # Internal update to the selection tracking lists
383 # Internal update to the selection tracking lists
384 if event.Selecting():
384 if event.Selecting():
385 # adding to the list...
385 # adding to the list...
386 self.current_selection.update(xrange(event.GetTopRow(), event.GetBottomRow()+1))
386 self.current_selection.update(xrange(event.GetTopRow(), event.GetBottomRow()+1))
387 else:
387 else:
388 # removal from list
388 # removal from list
389 for index in xrange(event.GetTopRow(), event.GetBottomRow()+1):
389 for index in xrange(event.GetTopRow(), event.GetBottomRow()+1):
390 self.current_selection.discard(index)
390 self.current_selection.discard(index)
391 event.Skip()
391 event.Skip()
392
392
393 def _on_selected_cell(self, event):
393 def _on_selected_cell(self, event):
394 # Internal update to the selection tracking list
394 # Internal update to the selection tracking list
395 self.current_selection = set([event.GetRow()])
395 self.current_selection = set([event.GetRow()])
396 event.Skip()
396 event.Skip()
397
397
398 def sort(self, key, reverse=False):
398 def sort(self, key, reverse=False):
399 """
399 """
400 Sort the current list of items using the key function ``key``. If
400 Sort the current list of items using the key function ``key``. If
401 ``reverse`` is true the sort order is reversed.
401 ``reverse`` is true the sort order is reversed.
402 """
402 """
403 row = self.GetGridCursorRow()
403 row = self.GetGridCursorRow()
404 col = self.GetGridCursorCol()
404 col = self.GetGridCursorCol()
405 curitem = self.table.items[row] # Remember where the cursor is now
405 curitem = self.table.items[row] # Remember where the cursor is now
406 # Sort items
406 # Sort items
407 def realkey(item):
407 def realkey(item):
408 try:
408 try:
409 return key(item)
409 return key(item)
410 except (KeyboardInterrupt, SystemExit):
410 except (KeyboardInterrupt, SystemExit):
411 raise
411 raise
412 except Exception:
412 except Exception:
413 return None
413 return None
414 try:
414 try:
415 self.table.items = ipipe.deque(sorted(self.table.items, key=realkey, reverse=reverse))
415 self.table.items = ipipe.deque(sorted(self.table.items, key=realkey, reverse=reverse))
416 except TypeError, exc:
416 except TypeError, exc:
417 self.error_output("Exception encountered: %s" % exc)
417 self.error_output("Exception encountered: %s" % exc)
418 return
418 return
419 # Find out where the object under the cursor went
419 # Find out where the object under the cursor went
420 for (i, item) in enumerate(self.table.items):
420 for (i, item) in enumerate(self.table.items):
421 if item is curitem:
421 if item is curitem:
422 self.SetGridCursor(i,col)
422 self.SetGridCursor(i,col)
423 self.MakeCellVisible(i,col)
423 self.MakeCellVisible(i,col)
424 self.Refresh()
424 self.Refresh()
425
425
426 def sortattrasc(self):
426 def sortattrasc(self):
427 """
427 """
428 Sort in ascending order; sorting criteria is the current attribute
428 Sort in ascending order; sorting criteria is the current attribute
429 """
429 """
430 col = self.GetGridCursorCol()
430 col = self.GetGridCursorCol()
431 attr = self.table._displayattrs[col]
431 attr = self.table._displayattrs[col]
432 frame = self.GetParent().GetParent().GetParent()
432 frame = self.GetParent().GetParent().GetParent()
433 if attr is ipipe.noitem:
433 if attr is ipipe.noitem:
434 self.error_output("no column under cursor")
434 self.error_output("no column under cursor")
435 return
435 return
436 frame.SetStatusText("sort by %s (ascending)" % attr.name())
436 frame.SetStatusText("sort by %s (ascending)" % attr.name())
437 def key(item):
437 def key(item):
438 try:
438 try:
439 return attr.value(item)
439 return attr.value(item)
440 except (KeyboardInterrupt, SystemExit):
440 except (KeyboardInterrupt, SystemExit):
441 raise
441 raise
442 except Exception:
442 except Exception:
443 return None
443 return None
444 self.sort(key)
444 self.sort(key)
445
445
446 def sortattrdesc(self):
446 def sortattrdesc(self):
447 """
447 """
448 Sort in descending order; sorting criteria is the current attribute
448 Sort in descending order; sorting criteria is the current attribute
449 """
449 """
450 col = self.GetGridCursorCol()
450 col = self.GetGridCursorCol()
451 attr = self.table._displayattrs[col]
451 attr = self.table._displayattrs[col]
452 frame = self.GetParent().GetParent().GetParent()
452 frame = self.GetParent().GetParent().GetParent()
453 if attr is ipipe.noitem:
453 if attr is ipipe.noitem:
454 self.error_output("no column under cursor")
454 self.error_output("no column under cursor")
455 return
455 return
456 frame.SetStatusText("sort by %s (descending)" % attr.name())
456 frame.SetStatusText("sort by %s (descending)" % attr.name())
457 def key(item):
457 def key(item):
458 try:
458 try:
459 return attr.value(item)
459 return attr.value(item)
460 except (KeyboardInterrupt, SystemExit):
460 except (KeyboardInterrupt, SystemExit):
461 raise
461 raise
462 except Exception:
462 except Exception:
463 return None
463 return None
464 self.sort(key, reverse=True)
464 self.sort(key, reverse=True)
465
465
466 def label_doubleclicked(self, event):
466 def label_doubleclicked(self, event):
467 row = event.GetRow()
467 row = event.GetRow()
468 col = event.GetCol()
468 col = event.GetCol()
469 if col == -1:
469 if col == -1:
470 self.enter(row)
470 self.enter(row)
471
471
472 def _getvalue(self, row, col):
472 def _getvalue(self, row, col):
473 """
473 """
474 Gets the text which is displayed at ``(row, col)``
474 Gets the text which is displayed at ``(row, col)``
475 """
475 """
476 try:
476 try:
477 value = self.table._displayattrs[col].value(self.table.items[row])
477 value = self.table._displayattrs[col].value(self.table.items[row])
478 (align, width, text) = ipipe.xformat(value, "cell", self.maxchars)
478 (align, width, text) = ipipe.xformat(value, "cell", self.maxchars)
479 except IndexError:
479 except IndexError:
480 raise IndexError
480 raise IndexError
481 except Exception, exc:
481 except Exception, exc:
482 (align, width, text) = ipipe.xformat(exc, "cell", self.maxchars)
482 (align, width, text) = ipipe.xformat(exc, "cell", self.maxchars)
483 return text
483 return text
484
484
485 def searchexpression(self, searchexp, startrow=None, search_forward=True ):
485 def searchexpression(self, searchexp, startrow=None, search_forward=True ):
486 """
486 """
487 Find by expression
487 Find by expression
488 """
488 """
489 frame = self.GetParent().GetParent().GetParent()
489 frame = self.GetParent().GetParent().GetParent()
490 if searchexp:
490 if searchexp:
491 if search_forward:
491 if search_forward:
492 if not startrow:
492 if not startrow:
493 row = self.GetGridCursorRow()+1
493 row = self.GetGridCursorRow()+1
494 else:
494 else:
495 row = startrow + 1
495 row = startrow + 1
496 while True:
496 while True:
497 try:
497 try:
498 foo = self.table.GetValue(row, 0)
498 foo = self.table.GetValue(row, 0)
499 item = self.table.items[row]
499 item = self.table.items[row]
500 try:
500 try:
501 globals = ipipe.getglobals(None)
501 globals = ipipe.getglobals(None)
502 if eval(searchexp, globals, ipipe.AttrNamespace(item)):
502 if eval(searchexp, globals, ipipe.AttrNamespace(item)):
503 self.SetGridCursor(row, 0) # found something
503 self.SetGridCursor(row, 0) # found something
504 self.MakeCellVisible(row, 0)
504 self.MakeCellVisible(row, 0)
505 break
505 break
506 except (KeyboardInterrupt, SystemExit):
506 except (KeyboardInterrupt, SystemExit):
507 raise
507 raise
508 except Exception, exc:
508 except Exception, exc:
509 frame.SetStatusText(str(exc))
509 frame.SetStatusText(str(exc))
510 wx.Bell()
510 wx.Bell()
511 break # break on error
511 break # break on error
512 except IndexError:
512 except IndexError:
513 return
513 return
514 row += 1
514 row += 1
515 else:
515 else:
516 if not startrow:
516 if not startrow:
517 row = self.GetGridCursorRow() - 1
517 row = self.GetGridCursorRow() - 1
518 else:
518 else:
519 row = startrow - 1
519 row = startrow - 1
520 while True:
520 while True:
521 try:
521 try:
522 foo = self.table.GetValue(row, 0)
522 foo = self.table.GetValue(row, 0)
523 item = self.table.items[row]
523 item = self.table.items[row]
524 try:
524 try:
525 globals = ipipe.getglobals(None)
525 globals = ipipe.getglobals(None)
526 if eval(searchexp, globals, ipipe.AttrNamespace(item)):
526 if eval(searchexp, globals, ipipe.AttrNamespace(item)):
527 self.SetGridCursor(row, 0) # found something
527 self.SetGridCursor(row, 0) # found something
528 self.MakeCellVisible(row, 0)
528 self.MakeCellVisible(row, 0)
529 break
529 break
530 except (KeyboardInterrupt, SystemExit):
530 except (KeyboardInterrupt, SystemExit):
531 raise
531 raise
532 except Exception, exc:
532 except Exception, exc:
533 frame.SetStatusText(str(exc))
533 frame.SetStatusText(str(exc))
534 wx.Bell()
534 wx.Bell()
535 break # break on error
535 break # break on error
536 except IndexError:
536 except IndexError:
537 return
537 return
538 row -= 1
538 row -= 1
539
539
540
540
541 def search(self, searchtext, startrow=None, startcol=None, search_forward=True):
541 def search(self, searchtext, startrow=None, startcol=None, search_forward=True):
542 """
542 """
543 search for ``searchtext``, starting in ``(startrow, startcol)``;
543 search for ``searchtext``, starting in ``(startrow, startcol)``;
544 if ``search_forward`` is true the direction is "forward"
544 if ``search_forward`` is true the direction is "forward"
545 """
545 """
546 searchtext = searchtext.lower()
546 searchtext = searchtext.lower()
547 if search_forward:
547 if search_forward:
548 if startrow is not None and startcol is not None:
548 if startrow is not None and startcol is not None:
549 row = startrow
549 row = startrow
550 else:
550 else:
551 startcol = self.GetGridCursorCol() + 1
551 startcol = self.GetGridCursorCol() + 1
552 row = self.GetGridCursorRow()
552 row = self.GetGridCursorRow()
553 if startcol >= self.GetNumberCols():
553 if startcol >= self.GetNumberCols():
554 startcol = 0
554 startcol = 0
555 row += 1
555 row += 1
556 while True:
556 while True:
557 for col in xrange(startcol, self.table.GetNumberCols()):
557 for col in xrange(startcol, self.table.GetNumberCols()):
558 try:
558 try:
559 foo = self.table.GetValue(row, col)
559 foo = self.table.GetValue(row, col)
560 text = self._getvalue(row, col)
560 text = self._getvalue(row, col)
561 if searchtext in text.string().lower():
561 if searchtext in text.string().lower():
562 self.SetGridCursor(row, col)
562 self.SetGridCursor(row, col)
563 self.MakeCellVisible(row, col)
563 self.MakeCellVisible(row, col)
564 return
564 return
565 except IndexError:
565 except IndexError:
566 return
566 return
567 startcol = 0
567 startcol = 0
568 row += 1
568 row += 1
569 else:
569 else:
570 if startrow is not None and startcol is not None:
570 if startrow is not None and startcol is not None:
571 row = startrow
571 row = startrow
572 else:
572 else:
573 startcol = self.GetGridCursorCol() - 1
573 startcol = self.GetGridCursorCol() - 1
574 row = self.GetGridCursorRow()
574 row = self.GetGridCursorRow()
575 if startcol < 0:
575 if startcol < 0:
576 startcol = self.GetNumberCols() - 1
576 startcol = self.GetNumberCols() - 1
577 row -= 1
577 row -= 1
578 while True:
578 while True:
579 for col in xrange(startcol, -1, -1):
579 for col in xrange(startcol, -1, -1):
580 try:
580 try:
581 foo = self.table.GetValue(row, col)
581 foo = self.table.GetValue(row, col)
582 text = self._getvalue(row, col)
582 text = self._getvalue(row, col)
583 if searchtext in text.string().lower():
583 if searchtext in text.string().lower():
584 self.SetGridCursor(row, col)
584 self.SetGridCursor(row, col)
585 self.MakeCellVisible(row, col)
585 self.MakeCellVisible(row, col)
586 return
586 return
587 except IndexError:
587 except IndexError:
588 return
588 return
589 startcol = self.table.GetNumberCols()-1
589 startcol = self.table.GetNumberCols()-1
590 row -= 1
590 row -= 1
591
591
592 def key_pressed(self, event):
592 def key_pressed(self, event):
593 """
593 """
594 Maps pressed keys to functions
594 Maps pressed keys to functions
595 """
595 """
596 frame = self.GetParent().GetParent().GetParent()
596 frame = self.GetParent().GetParent().GetParent()
597 frame.SetStatusText("")
597 frame.SetStatusText("")
598 sh = event.ShiftDown()
598 sh = event.ShiftDown()
599 ctrl = event.ControlDown()
599 ctrl = event.ControlDown()
600
600
601 keycode = event.GetKeyCode()
601 keycode = event.GetKeyCode()
602 if keycode == ord("P"):
602 if keycode == ord("P"):
603 row = self.GetGridCursorRow()
603 row = self.GetGridCursorRow()
604 if sh:
604 if sh:
605 col = self.GetGridCursorCol()
605 col = self.GetGridCursorCol()
606 self.pickattr(row, col)
606 self.pickattr(row, col)
607 else:
607 else:
608 self.pick(row)
608 self.pick(row)
609 elif keycode == ord("M"):
609 elif keycode == ord("M"):
610 if ctrl:
610 if ctrl:
611 col = self.GetGridCursorCol()
611 col = self.GetGridCursorCol()
612 self.pickrowsattr(sorted(self.current_selection), col)
612 self.pickrowsattr(sorted(self.current_selection), col)
613 else:
613 else:
614 self.pickrows(sorted(self.current_selection))
614 self.pickrows(sorted(self.current_selection))
615 elif keycode in (wx.WXK_BACK, wx.WXK_DELETE, ord("X")) and not (ctrl or sh):
615 elif keycode in (wx.WXK_BACK, wx.WXK_DELETE, ord("X")) and not (ctrl or sh):
616 self.delete_current_notebook()
616 self.delete_current_notebook()
617 elif keycode in (ord("E"), ord("\r")):
617 elif keycode in (ord("E"), ord("\r")):
618 row = self.GetGridCursorRow()
618 row = self.GetGridCursorRow()
619 if sh:
619 if sh:
620 col = self.GetGridCursorCol()
620 col = self.GetGridCursorCol()
621 self.enterattr(row, col)
621 self.enterattr(row, col)
622 else:
622 else:
623 self.enter(row)
623 self.enter(row)
624 elif keycode == ord("E") and ctrl:
624 elif keycode == ord("E") and ctrl:
625 row = self.GetGridCursorRow()
625 row = self.GetGridCursorRow()
626 self.SetGridCursor(row, self.GetNumberCols()-1)
626 self.SetGridCursor(row, self.GetNumberCols()-1)
627 elif keycode == wx.WXK_HOME or (keycode == ord("A") and ctrl):
627 elif keycode == wx.WXK_HOME or (keycode == ord("A") and ctrl):
628 row = self.GetGridCursorRow()
628 row = self.GetGridCursorRow()
629 self.SetGridCursor(row, 0)
629 self.SetGridCursor(row, 0)
630 elif keycode == ord("C") and sh:
630 elif keycode == ord("C") and sh:
631 col = self.GetGridCursorCol()
631 col = self.GetGridCursorCol()
632 attr = self.table._displayattrs[col]
632 attr = self.table._displayattrs[col]
633 result = []
633 result = []
634 for i in xrange(self.GetNumberRows()):
634 for i in xrange(self.GetNumberRows()):
635 result.append(self.table._displayattrs[col].value(self.table.items[i]))
635 result.append(self.table._displayattrs[col].value(self.table.items[i]))
636 self.quit(result)
636 self.quit(result)
637 elif keycode in (wx.WXK_ESCAPE, ord("Q")) and not (ctrl or sh):
637 elif keycode in (wx.WXK_ESCAPE, ord("Q")) and not (ctrl or sh):
638 self.quit()
638 self.quit()
639 elif keycode == ord("<"):
639 elif keycode == ord("<"):
640 row = self.GetGridCursorRow()
640 row = self.GetGridCursorRow()
641 col = self.GetGridCursorCol()
641 col = self.GetGridCursorCol()
642 if not event.ShiftDown():
642 if not event.ShiftDown():
643 newcol = col - 1
643 newcol = col - 1
644 if newcol >= 0:
644 if newcol >= 0:
645 self.SetGridCursor(row, col - 1)
645 self.SetGridCursor(row, col - 1)
646 else:
646 else:
647 newcol = col + 1
647 newcol = col + 1
648 if newcol < self.GetNumberCols():
648 if newcol < self.GetNumberCols():
649 self.SetGridCursor(row, col + 1)
649 self.SetGridCursor(row, col + 1)
650 elif keycode == ord("D"):
650 elif keycode == ord("D"):
651 col = self.GetGridCursorCol()
651 col = self.GetGridCursorCol()
652 row = self.GetGridCursorRow()
652 row = self.GetGridCursorRow()
653 if not sh:
653 if not sh:
654 self.detail(row, col)
654 self.detail(row, col)
655 else:
655 else:
656 self.detail_attr(row, col)
656 self.detail_attr(row, col)
657 elif keycode == ord("F") and ctrl:
657 elif keycode == ord("F") and ctrl:
658 if sh:
658 if sh:
659 frame.enter_searchexpression(event)
659 frame.enter_searchexpression(event)
660 else:
660 else:
661 frame.enter_searchtext(event)
661 frame.enter_searchtext(event)
662 elif keycode == wx.WXK_F3:
662 elif keycode == wx.WXK_F3:
663 if sh:
663 if sh:
664 frame.find_previous(event)
664 frame.find_previous(event)
665 else:
665 else:
666 frame.find_next(event)
666 frame.find_next(event)
667 elif keycode == ord("V"):
667 elif keycode == ord("V"):
668 if sh:
668 if sh:
669 self.sortattrdesc()
669 self.sortattrdesc()
670 else:
670 else:
671 self.sortattrasc()
671 self.sortattrasc()
672 elif keycode == wx.WXK_DOWN:
672 elif keycode == wx.WXK_DOWN:
673 row = self.GetGridCursorRow()
673 row = self.GetGridCursorRow()
674 try:
674 try:
675 item = self.table.items[row+1]
675 item = self.table.items[row+1]
676 except IndexError:
676 except IndexError:
677 item = self.table.items[row]
677 item = self.table.items[row]
678 self.set_footer(item)
678 self.set_footer(item)
679 event.Skip()
679 event.Skip()
680 elif keycode == wx.WXK_UP:
680 elif keycode == wx.WXK_UP:
681 row = self.GetGridCursorRow()
681 row = self.GetGridCursorRow()
682 if row >= 1:
682 if row >= 1:
683 item = self.table.items[row-1]
683 item = self.table.items[row-1]
684 else:
684 else:
685 item = self.table.items[row]
685 item = self.table.items[row]
686 self.set_footer(item)
686 self.set_footer(item)
687 event.Skip()
687 event.Skip()
688 elif keycode == wx.WXK_RIGHT:
688 elif keycode == wx.WXK_RIGHT:
689 row = self.GetGridCursorRow()
689 row = self.GetGridCursorRow()
690 item = self.table.items[row]
690 item = self.table.items[row]
691 self.set_footer(item)
691 self.set_footer(item)
692 event.Skip()
692 event.Skip()
693 elif keycode == wx.WXK_LEFT:
693 elif keycode == wx.WXK_LEFT:
694 row = self.GetGridCursorRow()
694 row = self.GetGridCursorRow()
695 item = self.table.items[row]
695 item = self.table.items[row]
696 self.set_footer(item)
696 self.set_footer(item)
697 event.Skip()
697 event.Skip()
698 elif keycode == ord("R") or keycode == wx.WXK_F5:
698 elif keycode == ord("R") or keycode == wx.WXK_F5:
699 self.table.refresh_content(event)
699 self.table.refresh_content(event)
700 elif keycode == ord("I"):
700 elif keycode == ord("I"):
701 row = self.GetGridCursorRow()
701 row = self.GetGridCursorRow()
702 if not sh:
702 if not sh:
703 self.pickinput(row)
703 self.pickinput(row)
704 else:
704 else:
705 col = self.GetGridCursorCol()
705 col = self.GetGridCursorCol()
706 self.pickinputattr(row, col)
706 self.pickinputattr(row, col)
707 else:
707 else:
708 event.Skip()
708 event.Skip()
709
709
710 def delete_current_notebook(self):
710 def delete_current_notebook(self):
711 """
711 """
712 deletes the current notebook tab
712 deletes the current notebook tab
713 """
713 """
714 panel = self.GetParent()
714 panel = self.GetParent()
715 nb = panel.GetParent()
715 nb = panel.GetParent()
716 current = nb.GetSelection()
716 current = nb.GetSelection()
717 count = nb.GetPageCount()
717 count = nb.GetPageCount()
718 if count > 1:
718 if count > 1:
719 for i in xrange(count-1, current-1, -1):
719 for i in xrange(count-1, current-1, -1):
720 nb.DeletePage(i)
720 nb.DeletePage(i)
721 nb.GetCurrentPage().grid.SetFocus()
721 nb.GetCurrentPage().grid.SetFocus()
722 else:
722 else:
723 frame = nb.GetParent()
723 frame = nb.GetParent()
724 frame.SetStatusText("This is the last level!")
724 frame.SetStatusText("This is the last level!")
725
725
726 def _doenter(self, value, *attrs):
726 def _doenter(self, value, *attrs):
727 """
727 """
728 "enter" a special item resulting in a new notebook tab
728 "enter" a special item resulting in a new notebook tab
729 """
729 """
730 panel = self.GetParent()
730 panel = self.GetParent()
731 nb = panel.GetParent()
731 nb = panel.GetParent()
732 frame = nb.GetParent()
732 frame = nb.GetParent()
733 current = nb.GetSelection()
733 current = nb.GetSelection()
734 count = nb.GetPageCount()
734 count = nb.GetPageCount()
735 try: # if we want to enter something non-iterable, e.g. a function
735 try: # if we want to enter something non-iterable, e.g. a function
736 if current + 1 == count and value is not self.input: # we have an event in the last tab
736 if current + 1 == count and value is not self.input: # we have an event in the last tab
737 frame._add_notebook(value, *attrs)
737 frame._add_notebook(value, *attrs)
738 elif value != self.input: # we have to delete all tabs newer than [panel] first
738 elif value != self.input: # we have to delete all tabs newer than [panel] first
739 for i in xrange(count-1, current, -1): # some tabs don't close if we don't close in *reverse* order
739 for i in xrange(count-1, current, -1): # some tabs don't close if we don't close in *reverse* order
740 nb.DeletePage(i)
740 nb.DeletePage(i)
741 frame._add_notebook(value)
741 frame._add_notebook(value)
742 except TypeError, exc:
742 except TypeError, exc:
743 if exc.__class__.__module__ == "exceptions":
743 if exc.__class__.__module__ == "exceptions":
744 msg = "%s: %s" % (exc.__class__.__name__, exc)
744 msg = "%s: %s" % (exc.__class__.__name__, exc)
745 else:
745 else:
746 msg = "%s.%s: %s" % (exc.__class__.__module__, exc.__class__.__name__, exc)
746 msg = "%s.%s: %s" % (exc.__class__.__module__, exc.__class__.__name__, exc)
747 frame.SetStatusText(msg)
747 frame.SetStatusText(msg)
748
748
749 def enterattr(self, row, col):
749 def enterattr(self, row, col):
750 try:
750 try:
751 attr = self.table._displayattrs[col]
751 attr = self.table._displayattrs[col]
752 value = attr.value(self.table.items[row])
752 value = attr.value(self.table.items[row])
753 except Exception, exc:
753 except Exception, exc:
754 self.error_output(str(exc))
754 self.error_output(str(exc))
755 else:
755 else:
756 self._doenter(value)
756 self._doenter(value)
757
757
758 def set_footer(self, item):
758 def set_footer(self, item):
759 frame = self.GetParent().GetParent().GetParent()
759 frame = self.GetParent().GetParent().GetParent()
760 frame.SetStatusText(" ".join([str(text) for (style, text) in ipipe.xformat(item, "footer", 20)[2]]), 0)
760 frame.SetStatusText(" ".join([str(text) for (style, text) in ipipe.xformat(item, "footer", 20)[2]]), 0)
761
761
762 def enter(self, row):
762 def enter(self, row):
763 try:
763 try:
764 value = self.table.items[row]
764 value = self.table.items[row]
765 except Exception, exc:
765 except Exception, exc:
766 self.error_output(str(exc))
766 self.error_output(str(exc))
767 else:
767 else:
768 self._doenter(value)
768 self._doenter(value)
769
769
770 def detail(self, row, col):
770 def detail(self, row, col):
771 """
771 """
772 shows a detail-view of the current cell
772 shows a detail-view of the current cell
773 """
773 """
774 try:
774 try:
775 attr = self.table._displayattrs[col]
775 attr = self.table._displayattrs[col]
776 item = self.table.items[row]
776 item = self.table.items[row]
777 except Exception, exc:
777 except Exception, exc:
778 self.error_output(str(exc))
778 self.error_output(str(exc))
779 else:
779 else:
780 attrs = [ipipe.AttributeDetail(item, attr) for attr in ipipe.xattrs(item, "detail")]
780 attrs = [ipipe.AttributeDetail(item, attr) for attr in ipipe.xattrs(item, "detail")]
781 self._doenter(attrs)
781 self._doenter(attrs)
782
782
783 def detail_attr(self, row, col):
783 def detail_attr(self, row, col):
784 try:
784 try:
785 attr = self.table._displayattrs[col]
785 attr = self.table._displayattrs[col]
786 item = attr.value(self.table.items[row])
786 item = attr.value(self.table.items[row])
787 except Exception, exc:
787 except Exception, exc:
788 self.error_output(str(exc))
788 self.error_output(str(exc))
789 else:
789 else:
790 attrs = [ipipe.AttributeDetail(item, attr) for attr in ipipe.xattrs(item, "detail")]
790 attrs = [ipipe.AttributeDetail(item, attr) for attr in ipipe.xattrs(item, "detail")]
791 self._doenter(attrs)
791 self._doenter(attrs)
792
792
793 def quit(self, result=None):
793 def quit(self, result=None):
794 """
794 """
795 quit
795 quit
796 """
796 """
797 frame = self.GetParent().GetParent().GetParent()
797 frame = self.GetParent().GetParent().GetParent()
798 if frame.helpdialog:
798 if frame.helpdialog:
799 frame.helpdialog.Destroy()
799 frame.helpdialog.Destroy()
800 app = frame.parent
800 app = frame.parent
801 if app is not None:
801 if app is not None:
802 app.result = result
802 app.result = result
803 frame.Close()
803 frame.Close()
804 frame.Destroy()
804 frame.Destroy()
805
805
806 def cell_doubleclicked(self, event):
806 def cell_doubleclicked(self, event):
807 self.enterattr(event.GetRow(), event.GetCol())
807 self.enterattr(event.GetRow(), event.GetCol())
808 event.Skip()
808 event.Skip()
809
809
810 def cell_leftclicked(self, event):
810 def cell_leftclicked(self, event):
811 row = event.GetRow()
811 row = event.GetRow()
812 item = self.table.items[row]
812 item = self.table.items[row]
813 self.set_footer(item)
813 self.set_footer(item)
814 event.Skip()
814 event.Skip()
815
815
816 def pick(self, row):
816 def pick(self, row):
817 """
817 """
818 pick a single row and return to the IPython prompt
818 pick a single row and return to the IPython prompt
819 """
819 """
820 try:
820 try:
821 value = self.table.items[row]
821 value = self.table.items[row]
822 except Exception, exc:
822 except Exception, exc:
823 self.error_output(str(exc))
823 self.error_output(str(exc))
824 else:
824 else:
825 self.quit(value)
825 self.quit(value)
826
826
827 def pickinput(self, row):
827 def pickinput(self, row):
828 try:
828 try:
829 value = self.table.items[row]
829 value = self.table.items[row]
830 except Exception, exc:
830 except Exception, exc:
831 self.error_output(str(exc))
831 self.error_output(str(exc))
832 else:
832 else:
833 api = ipapi.get()
833 api = ipapi.get()
834 api.set_next_input(str(value))
834 api.set_next_input(str(value))
835 self.quit(value)
835 self.quit(value)
836
836
837 def pickinputattr(self, row, col):
837 def pickinputattr(self, row, col):
838 try:
838 try:
839 attr = self.table._displayattrs[col]
839 attr = self.table._displayattrs[col]
840 value = attr.value(self.table.items[row])
840 value = attr.value(self.table.items[row])
841 except Exception, exc:
841 except Exception, exc:
842 self.error_output(str(exc))
842 self.error_output(str(exc))
843 else:
843 else:
844 api = ipapi.get()
844 api = ipapi.get()
845 api.set_next_input(str(value))
845 api.set_next_input(str(value))
846 self.quit(value)
846 self.quit(value)
847
847
848 def pickrows(self, rows):
848 def pickrows(self, rows):
849 """
849 """
850 pick multiple rows and return to the IPython prompt
850 pick multiple rows and return to the IPython prompt
851 """
851 """
852 try:
852 try:
853 value = [self.table.items[row] for row in rows]
853 value = [self.table.items[row] for row in rows]
854 except Exception, exc:
854 except Exception, exc:
855 self.error_output(str(exc))
855 self.error_output(str(exc))
856 else:
856 else:
857 self.quit(value)
857 self.quit(value)
858
858
859 def pickrowsattr(self, rows, col):
859 def pickrowsattr(self, rows, col):
860 """"
860 """"
861 pick one column from multiple rows
861 pick one column from multiple rows
862 """
862 """
863 values = []
863 values = []
864 try:
864 try:
865 attr = self.table._displayattrs[col]
865 attr = self.table._displayattrs[col]
866 for row in rows:
866 for row in rows:
867 try:
867 try:
868 values.append(attr.value(self.table.items[row]))
868 values.append(attr.value(self.table.items[row]))
869 except (SystemExit, KeyboardInterrupt):
869 except (SystemExit, KeyboardInterrupt):
870 raise
870 raise
871 except Exception:
871 except Exception:
872 raise #pass
872 raise #pass
873 except Exception, exc:
873 except Exception, exc:
874 self.error_output(str(exc))
874 self.error_output(str(exc))
875 else:
875 else:
876 self.quit(values)
876 self.quit(values)
877
877
878 def pickattr(self, row, col):
878 def pickattr(self, row, col):
879 try:
879 try:
880 attr = self.table._displayattrs[col]
880 attr = self.table._displayattrs[col]
881 value = attr.value(self.table.items[row])
881 value = attr.value(self.table.items[row])
882 except Exception, exc:
882 except Exception, exc:
883 self.error_output(str(exc))
883 self.error_output(str(exc))
884 else:
884 else:
885 self.quit(value)
885 self.quit(value)
886
886
887
887
888 class IGridPanel(wx.Panel):
888 class IGridPanel(wx.Panel):
889 # Each IGridPanel contains an IGridGrid
889 # Each IGridPanel contains an IGridGrid
890 def __init__(self, parent, input, *attrs):
890 def __init__(self, parent, input, *attrs):
891 wx.Panel.__init__(self, parent, -1)
891 wx.Panel.__init__(self, parent, -1)
892 self.grid = IGridGrid(self, input, *attrs)
892 self.grid = IGridGrid(self, input, *attrs)
893 self.grid.FitInside()
893 self.grid.FitInside()
894 sizer = wx.BoxSizer(wx.VERTICAL)
894 sizer = wx.BoxSizer(wx.VERTICAL)
895 sizer.Add(self.grid, proportion=1, flag=wx.EXPAND | wx.ALL, border=10)
895 sizer.Add(self.grid, proportion=1, flag=wx.EXPAND | wx.ALL, border=10)
896 self.SetSizer(sizer)
896 self.SetSizer(sizer)
897 sizer.Fit(self)
897 sizer.Fit(self)
898 sizer.SetSizeHints(self)
898 sizer.SetSizeHints(self)
899
899
900
900
901 class IGridHTMLHelp(wx.Frame):
901 class IGridHTMLHelp(wx.Frame):
902 def __init__(self, parent, title, size):
902 def __init__(self, parent, title, size):
903 wx.Frame.__init__(self, parent, -1, title, size=size)
903 wx.Frame.__init__(self, parent, -1, title, size=size)
904 html = wx.html.HtmlWindow(self)
904 html = wx.html.HtmlWindow(self)
905 if "gtk2" in wx.PlatformInfo:
905 if "gtk2" in wx.PlatformInfo:
906 html.SetStandardFonts()
906 html.SetStandardFonts()
907 html.SetPage(help)
907 html.SetPage(help)
908
908
909
909
910 class IGridFrame(wx.Frame):
910 class IGridFrame(wx.Frame):
911 maxtitlelen = 30
911 maxtitlelen = 30
912
912
913 def __init__(self, parent, input):
913 def __init__(self, parent, input):
914 title = " ".join([str(text) for (style, text) in ipipe.xformat(input, "header", 20)[2]])
914 title = " ".join([str(text) for (style, text) in ipipe.xformat(input, "header", 20)[2]])
915 wx.Frame.__init__(self, None, title=title, size=(640, 480))
915 wx.Frame.__init__(self, None, title=title, size=(640, 480))
916 self.menubar = wx.MenuBar()
916 self.menubar = wx.MenuBar()
917 self.menucounter = 100
917 self.menucounter = 100
918 self.m_help = wx.Menu()
918 self.m_help = wx.Menu()
919 self.m_search = wx.Menu()
919 self.m_search = wx.Menu()
920 self.m_sort = wx.Menu()
920 self.m_sort = wx.Menu()
921 self.m_refresh = wx.Menu()
921 self.m_refresh = wx.Menu()
922 self.notebook = wx.Notebook(self, -1, style=0)
922 self.notebook = wx.Notebook(self, -1, style=0)
923 self.statusbar = self.CreateStatusBar(1, wx.ST_SIZEGRIP)
923 self.statusbar = self.CreateStatusBar(1, wx.ST_SIZEGRIP)
924 self.statusbar.SetFieldsCount(2)
924 self.statusbar.SetFieldsCount(2)
925 self.SetStatusWidths([-1, 200])
925 self.SetStatusWidths([-1, 200])
926 self.parent = parent
926 self.parent = parent
927 self._add_notebook(input)
927 self._add_notebook(input)
928 self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
928 self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
929 self.makemenu(self.m_sort, "&Sort (asc)\tV", "Sort ascending", self.sortasc)
929 self.makemenu(self.m_sort, "&Sort (asc)\tV", "Sort ascending", self.sortasc)
930 self.makemenu(self.m_sort, "Sort (&desc)\tShift-V", "Sort descending", self.sortdesc)
930 self.makemenu(self.m_sort, "Sort (&desc)\tShift-V", "Sort descending", self.sortdesc)
931 self.makemenu(self.m_help, "&Help\tF1", "Help", self.display_help)
931 self.makemenu(self.m_help, "&Help\tF1", "Help", self.display_help)
932 # self.makemenu(self.m_help, "&Show help in browser", "Show help in browser", self.display_help_in_browser)
932 # self.makemenu(self.m_help, "&Show help in browser", "Show help in browser", self.display_help_in_browser)
933 self.makemenu(self.m_search, "&Find text\tCTRL-F", "Find text", self.enter_searchtext)
933 self.makemenu(self.m_search, "&Find text\tCTRL-F", "Find text", self.enter_searchtext)
934 self.makemenu(self.m_search, "Find by &expression\tCTRL-Shift-F", "Find by expression", self.enter_searchexpression)
934 self.makemenu(self.m_search, "Find by &expression\tCTRL-Shift-F", "Find by expression", self.enter_searchexpression)
935 self.makemenu(self.m_search, "Find &next\tF3", "Find next", self.find_next)
935 self.makemenu(self.m_search, "Find &next\tF3", "Find next", self.find_next)
936 self.makemenu(self.m_search, "Find &previous\tShift-F3", "Find previous", self.find_previous)
936 self.makemenu(self.m_search, "Find &previous\tShift-F3", "Find previous", self.find_previous)
937 self.makemenu(self.m_refresh, "&Refresh once \tF5", "Refresh once", self.refresh_once)
937 self.makemenu(self.m_refresh, "&Refresh once \tF5", "Refresh once", self.refresh_once)
938 self.makemenu(self.m_refresh, "Refresh every &1s", "Refresh every second", self.refresh_every_second)
938 self.makemenu(self.m_refresh, "Refresh every &1s", "Refresh every second", self.refresh_every_second)
939 self.makemenu(self.m_refresh, "Refresh every &X seconds", "Refresh every X seconds", self.refresh_interval)
939 self.makemenu(self.m_refresh, "Refresh every &X seconds", "Refresh every X seconds", self.refresh_interval)
940 self.makemenu(self.m_refresh, "&Stop all refresh timers", "Stop refresh timers", self.stop_refresh)
940 self.makemenu(self.m_refresh, "&Stop all refresh timers", "Stop refresh timers", self.stop_refresh)
941 self.menubar.Append(self.m_search, "&Find")
941 self.menubar.Append(self.m_search, "&Find")
942 self.menubar.Append(self.m_sort, "&Sort")
942 self.menubar.Append(self.m_sort, "&Sort")
943 self.menubar.Append(self.m_refresh, "&Refresh")
943 self.menubar.Append(self.m_refresh, "&Refresh")
944 self.menubar.Append(self.m_help, "&Help")
944 self.menubar.Append(self.m_help, "&Help")
945 self.SetMenuBar(self.menubar)
945 self.SetMenuBar(self.menubar)
946 self.searchtext = ""
946 self.searchtext = ""
947 self.searchexpression = ""
947 self.searchexpression = ""
948 self.helpdialog = None
948 self.helpdialog = None
949 self.refresh_interval = 1000
949 self.refresh_interval = 1000
950 self.SetStatusText("Refreshing inactive", 1)
950 self.SetStatusText("Refreshing inactive", 1)
951
951
952 def refresh_once(self, event):
952 def refresh_once(self, event):
953 table = self.notebook.GetPage(self.notebook.GetSelection()).grid.table
953 table = self.notebook.GetPage(self.notebook.GetSelection()).grid.table
954 table.refresh_content(event)
954 table.refresh_content(event)
955
955
956 def refresh_interval(self, event):
956 def refresh_interval(self, event):
957 table = self.notebook.GetPage(self.notebook.GetSelection()).grid.table
957 table = self.notebook.GetPage(self.notebook.GetSelection()).grid.table
958 dlg = wx.TextEntryDialog(self, "Enter refresh interval (milliseconds):", "Refresh timer:", defaultValue=str(self.refresh_interval))
958 dlg = wx.TextEntryDialog(self, "Enter refresh interval (milliseconds):", "Refresh timer:", defaultValue=str(self.refresh_interval))
959 if dlg.ShowModal() == wx.ID_OK:
959 if dlg.ShowModal() == wx.ID_OK:
960 try:
960 try:
961 milliseconds = int(dlg.GetValue())
961 milliseconds = int(dlg.GetValue())
962 except ValueError, exc:
962 except ValueError, exc:
963 self.SetStatusText(str(exc))
963 self.SetStatusText(str(exc))
964 else:
964 else:
965 table.timer.Start(milliseconds=milliseconds, oneShot=False)
965 table.timer.Start(milliseconds=milliseconds, oneShot=False)
966 self.SetStatusText("Refresh timer set to %s ms" % milliseconds)
966 self.SetStatusText("Refresh timer set to %s ms" % milliseconds)
967 self.SetStatusText("Refresh interval: %s ms" % milliseconds, 1)
967 self.SetStatusText("Refresh interval: %s ms" % milliseconds, 1)
968 self.refresh_interval = milliseconds
968 self.refresh_interval = milliseconds
969 dlg.Destroy()
969 dlg.Destroy()
970
970
971 def stop_refresh(self, event):
971 def stop_refresh(self, event):
972 for i in xrange(self.notebook.GetPageCount()):
972 for i in xrange(self.notebook.GetPageCount()):
973 nb = self.notebook.GetPage(i)
973 nb = self.notebook.GetPage(i)
974 nb.grid.table.timer.Stop()
974 nb.grid.table.timer.Stop()
975 self.SetStatusText("Refreshing inactive", 1)
975 self.SetStatusText("Refreshing inactive", 1)
976
976
977 def refresh_every_second(self, event):
977 def refresh_every_second(self, event):
978 table = self.notebook.GetPage(self.notebook.GetSelection()).grid.table
978 table = self.notebook.GetPage(self.notebook.GetSelection()).grid.table
979 table.timer.Start(milliseconds=1000, oneShot=False)
979 table.timer.Start(milliseconds=1000, oneShot=False)
980 self.SetStatusText("Refresh interval: 1000 ms", 1)
980 self.SetStatusText("Refresh interval: 1000 ms", 1)
981
981
982 def sortasc(self, event):
982 def sortasc(self, event):
983 grid = self.notebook.GetPage(self.notebook.GetSelection()).grid
983 grid = self.notebook.GetPage(self.notebook.GetSelection()).grid
984 grid.sortattrasc()
984 grid.sortattrasc()
985
985
986 def sortdesc(self, event):
986 def sortdesc(self, event):
987 grid = self.notebook.GetPage(self.notebook.GetSelection()).grid
987 grid = self.notebook.GetPage(self.notebook.GetSelection()).grid
988 grid.sortattrdesc()
988 grid.sortattrdesc()
989
989
990 def find_previous(self, event):
990 def find_previous(self, event):
991 """
991 """
992 find previous occurrences
992 find previous occurrences
993 """
993 """
994 grid = self.notebook.GetPage(self.notebook.GetSelection()).grid
994 grid = self.notebook.GetPage(self.notebook.GetSelection()).grid
995 if self.searchtext:
995 if self.searchtext:
996 row = grid.GetGridCursorRow()
996 row = grid.GetGridCursorRow()
997 col = grid.GetGridCursorCol()
997 col = grid.GetGridCursorCol()
998 self.SetStatusText('Search mode: text; looking for %s' % self.searchtext)
998 self.SetStatusText('Search mode: text; looking for %s' % self.searchtext)
999 if col-1 >= 0:
999 if col-1 >= 0:
1000 grid.search(self.searchtext, row, col-1, False)
1000 grid.search(self.searchtext, row, col-1, False)
1001 else:
1001 else:
1002 grid.search(self.searchtext, row-1, grid.table.GetNumberCols()-1, False)
1002 grid.search(self.searchtext, row-1, grid.table.GetNumberCols()-1, False)
1003 elif self.searchexpression:
1003 elif self.searchexpression:
1004 self.SetStatusText("Search mode: expression; looking for %s" % repr(self.searchexpression)[2:-1])
1004 self.SetStatusText("Search mode: expression; looking for %s" % repr(self.searchexpression)[2:-1])
1005 grid.searchexpression(searchexp=self.searchexpression, search_forward=False)
1005 grid.searchexpression(searchexp=self.searchexpression, search_forward=False)
1006 else:
1006 else:
1007 self.SetStatusText("No search yet: please enter search-text or -expression")
1007 self.SetStatusText("No search yet: please enter search-text or -expression")
1008
1008
1009 def find_next(self, event):
1009 def find_next(self, event):
1010 """
1010 """
1011 find the next occurrence
1011 find the next occurrence
1012 """
1012 """
1013 grid = self.notebook.GetPage(self.notebook.GetSelection()).grid
1013 grid = self.notebook.GetPage(self.notebook.GetSelection()).grid
1014 if self.searchtext != "":
1014 if self.searchtext != "":
1015 row = grid.GetGridCursorRow()
1015 row = grid.GetGridCursorRow()
1016 col = grid.GetGridCursorCol()
1016 col = grid.GetGridCursorCol()
1017 self.SetStatusText('Search mode: text; looking for %s' % self.searchtext)
1017 self.SetStatusText('Search mode: text; looking for %s' % self.searchtext)
1018 if col+1 < grid.table.GetNumberCols():
1018 if col+1 < grid.table.GetNumberCols():
1019 grid.search(self.searchtext, row, col+1)
1019 grid.search(self.searchtext, row, col+1)
1020 else:
1020 else:
1021 grid.search(self.searchtext, row+1, 0)
1021 grid.search(self.searchtext, row+1, 0)
1022 elif self.searchexpression != "":
1022 elif self.searchexpression != "":
1023 self.SetStatusText('Search mode: expression; looking for %s' % repr(self.searchexpression)[2:-1])
1023 self.SetStatusText('Search mode: expression; looking for %s' % repr(self.searchexpression)[2:-1])
1024 grid.searchexpression(searchexp=self.searchexpression)
1024 grid.searchexpression(searchexp=self.searchexpression)
1025 else:
1025 else:
1026 self.SetStatusText("No search yet: please enter search-text or -expression")
1026 self.SetStatusText("No search yet: please enter search-text or -expression")
1027
1027
1028 def display_help(self, event):
1028 def display_help(self, event):
1029 """
1029 """
1030 Display a help dialog
1030 Display a help dialog
1031 """
1031 """
1032 if self.helpdialog:
1032 if self.helpdialog:
1033 self.helpdialog.Destroy()
1033 self.helpdialog.Destroy()
1034 self.helpdialog = IGridHTMLHelp(None, title="Help", size=wx.Size(600,400))
1034 self.helpdialog = IGridHTMLHelp(None, title="Help", size=wx.Size(600,400))
1035 self.helpdialog.Show()
1035 self.helpdialog.Show()
1036
1036
1037 def display_help_in_browser(self, event):
1037 def display_help_in_browser(self, event):
1038 """
1038 """
1039 Show the help-HTML in a browser (as a ``HtmlWindow`` does not understand
1039 Show the help-HTML in a browser (as a ``HtmlWindow`` does not understand
1040 CSS this looks better)
1040 CSS this looks better)
1041 """
1041 """
1042 filename = urllib.pathname2url(os.path.abspath(os.path.join(os.path.dirname(__file__), "igrid_help.html")))
1042 filename = urllib.pathname2url(os.path.abspath(os.path.join(os.path.dirname(__file__), "igrid_help.html")))
1043 if not filename.startswith("file"):
1043 if not filename.startswith("file"):
1044 filename = "file:" + filename
1044 filename = "file:" + filename
1045 webbrowser.open(filename, new=1, autoraise=True)
1045 webbrowser.open(filename, new=1, autoraise=True)
1046
1046
1047 def enter_searchexpression(self, event):
1047 def enter_searchexpression(self, event):
1048 dlg = wx.TextEntryDialog(self, "Find:", "Find matching expression:", defaultValue=self.searchexpression)
1048 dlg = wx.TextEntryDialog(self, "Find:", "Find matching expression:", defaultValue=self.searchexpression)
1049 if dlg.ShowModal() == wx.ID_OK:
1049 if dlg.ShowModal() == wx.ID_OK:
1050 self.searchexpression = dlg.GetValue()
1050 self.searchexpression = dlg.GetValue()
1051 self.searchtext = ""
1051 self.searchtext = ""
1052 self.SetStatusText('Search mode: expression; looking for %s' % repr(self.searchexpression)[2:-1])
1052 self.SetStatusText('Search mode: expression; looking for %s' % repr(self.searchexpression)[2:-1])
1053 self.notebook.GetPage(self.notebook.GetSelection()).grid.searchexpression(self.searchexpression)
1053 self.notebook.GetPage(self.notebook.GetSelection()).grid.searchexpression(self.searchexpression)
1054 dlg.Destroy()
1054 dlg.Destroy()
1055
1055
1056 def makemenu(self, menu, label, help, cmd):
1056 def makemenu(self, menu, label, help, cmd):
1057 menu.Append(self.menucounter, label, help)
1057 menu.Append(self.menucounter, label, help)
1058 self.Bind(wx.EVT_MENU, cmd, id=self.menucounter)
1058 self.Bind(wx.EVT_MENU, cmd, id=self.menucounter)
1059 self.menucounter += 1
1059 self.menucounter += 1
1060
1060
1061 def _add_notebook(self, input, *attrs):
1061 def _add_notebook(self, input, *attrs):
1062 # Adds another notebook which has the starting object ``input``
1062 # Adds another notebook which has the starting object ``input``
1063 panel = IGridPanel(self.notebook, input, *attrs)
1063 panel = IGridPanel(self.notebook, input, *attrs)
1064 text = str(ipipe.xformat(input, "header", self.maxtitlelen)[2])
1064 text = str(ipipe.xformat(input, "header", self.maxtitlelen)[2])
1065 if len(text) >= self.maxtitlelen:
1065 if len(text) >= self.maxtitlelen:
1066 text = text[:self.maxtitlelen].rstrip(".") + "..."
1066 text = text[:self.maxtitlelen].rstrip(".") + "..."
1067 self.notebook.AddPage(panel, text, True)
1067 self.notebook.AddPage(panel, text, True)
1068 panel.grid.SetFocus()
1068 panel.grid.SetFocus()
1069 self.Layout()
1069 self.Layout()
1070
1070
1071 def OnCloseWindow(self, event):
1071 def OnCloseWindow(self, event):
1072 self.Destroy()
1072 self.Destroy()
1073
1073
1074 def enter_searchtext(self, event):
1074 def enter_searchtext(self, event):
1075 # Displays a dialog asking for the searchtext
1075 # Displays a dialog asking for the searchtext
1076 dlg = wx.TextEntryDialog(self, "Find:", "Find in list", defaultValue=self.searchtext)
1076 dlg = wx.TextEntryDialog(self, "Find:", "Find in list", defaultValue=self.searchtext)
1077 if dlg.ShowModal() == wx.ID_OK:
1077 if dlg.ShowModal() == wx.ID_OK:
1078 self.searchtext = dlg.GetValue()
1078 self.searchtext = dlg.GetValue()
1079 self.searchexpression = ""
1079 self.searchexpression = ""
1080 self.SetStatusText('Search mode: text; looking for %s' % self.searchtext)
1080 self.SetStatusText('Search mode: text; looking for %s' % self.searchtext)
1081 self.notebook.GetPage(self.notebook.GetSelection()).grid.search(self.searchtext)
1081 self.notebook.GetPage(self.notebook.GetSelection()).grid.search(self.searchtext)
1082 dlg.Destroy()
1082 dlg.Destroy()
1083
1083
1084
1084
1085 class App(wx.App):
1085 class App(wx.App):
1086 def __init__(self, input):
1086 def __init__(self, input):
1087 self.input = input
1087 self.input = input
1088 self.result = None # Result to be returned to IPython. Set by quit().
1088 self.result = None # Result to be returned to IPython. Set by quit().
1089 wx.App.__init__(self)
1089 wx.App.__init__(self)
1090
1090
1091 def OnInit(self):
1091 def OnInit(self):
1092 frame = IGridFrame(self, self.input)
1092 frame = IGridFrame(self, self.input)
1093 frame.Show()
1093 frame.Show()
1094 self.SetTopWindow(frame)
1094 self.SetTopWindow(frame)
1095 frame.Raise()
1095 frame.Raise()
1096 return True
1096 return True
1097
1097
1098
1098
1099 class igrid(ipipe.Display):
1099 class igrid(ipipe.Display):
1100 """
1100 """
1101 This is a wx-based display object that can be used instead of ``ibrowse``
1101 This is a wx-based display object that can be used instead of ``ibrowse``
1102 (which is curses-based) or ``idump`` (which simply does a print).
1102 (which is curses-based) or ``idump`` (which simply does a print).
1103 """
1103 """
1104
1105 def __init__(self, input=None):
1106 self.input = input
1107
1104 if wx.VERSION < (2, 7):
1108 if wx.VERSION < (2, 7):
1105 def display(self):
1109 def display(self):
1106 try:
1110 try:
1107 # Try to create a "standalone" from. If this works we're probably
1111 # Try to create a "standalone" from. If this works we're probably
1108 # running with -wthread.
1112 # running with -wthread.
1109 # Note that this sets the parent of the frame to None, but we can't
1113 # Note that this sets the parent of the frame to None, but we can't
1110 # pass a result object back to the shell anyway.
1114 # pass a result object back to the shell anyway.
1111 frame = IGridFrame(None, self.input)
1115 frame = IGridFrame(None, self.input)
1112 frame.Show()
1116 frame.Show()
1113 frame.Raise()
1117 frame.Raise()
1114 except wx.PyNoAppError:
1118 except wx.PyNoAppError:
1115 # There's no wx application yet => create one.
1119 # There's no wx application yet => create one.
1116 app = App(self.input)
1120 app = App(self.input)
1117 app.MainLoop()
1121 app.MainLoop()
1118 return app.result
1122 return app.result
1119 else:
1123 else:
1120 # With wx 2.7 it gets simpler.
1124 # With wx 2.7 it gets simpler.
1121 def display(self):
1125 def display(self):
1122 app = App(self.input)
1126 app = App(self.input)
1123 app.MainLoop()
1127 app.MainLoop()
1124 return app.result
1128 return app.result
1125
1129
General Comments 0
You need to be logged in to leave comments. Login now