Instead of editing nested textual expressions, edit the whole expression.
[l3full.git] / l3gui / misc.py
blobba970542461ebf2fc4c768a5221b750dc393fcb3
2 # Author: Michael H. Hohn (mhhohn@lbl.gov)
4 # Copyright (c) 2006, The Regents of the University of California
5 #
6 # See legal.txt and license.txt
9 """
10 Miscellaneous functions and classes.
12 """
13 import exceptions, thread, sys, os
14 from copy import copy, deepcopy
15 import gobject, gtk
16 try:
17 import gnomecanvas as canvas
18 from gnomecanvas import MOVETO_OPEN, MOVETO, LINETO, CURVETO
19 except:
20 # Try backported version used in sparx.
21 import canvas
22 from canvas import MOVETO_OPEN, MOVETO, LINETO, CURVETO
23 from math import *
25 from l3lang import utils, ast, interp, view
26 import l3lang.globals as G
28 #* event separation via thread locks
30 # wrapping whole event handlers, as in
31 # self._root_group.connect("event",
32 # lambda *a: misc.single_event(self.drag_event, *a))
33 # really throws off the handling.
35 # This single-event restriction must be used locally to block specific
36 # other events when a significant (event-affecting) state modification
37 # is about to happen.
39 # See l3Nested.drag_event for an example.
41 _the_lck = thread.allocate_lock()
42 def single_event(func, *args):
43 if _the_lck.acquire(0):
44 try:
45 func(*args)
46 finally:
47 _the_lck.release()
48 else:
49 pass
52 #* data / file content -> l3 conversion
53 #** file type dispatcher
54 def file2ast(fname, shape = 'list'):
55 ''' Produce a raw astType for the content of file `fname`.
57 The `shape` argument is 'list' or 'square'; the 'list' form will
58 not change the "natural" shape of the data whereas 'square'
59 adjusts the shape for better space utilization.
61 The returned astType is not .setup().
63 For unrecognized file types, `fname` is returned.
64 '''
65 assert isinstance(fname, ast.String), "Expecting file name."
67 # File exists?
68 if not os.path.exists(fname):
69 return fname
71 # File type check by extension.
72 (root, ext) = os.path.splitext(fname)
74 _identity = lambda aa: aa
75 return {
76 '.hdf' : _file2ast_hdf,
77 '.png' : png2pixbuf, # todo: handle files
78 '' : _identity,
79 }[ext](fname, shape = shape)
81 #** hdf file handler
82 def _file2ast_hdf(fname, shape = 'list'):
83 '''Convert an hdf5 file containing a list of images.
85 The contents are converted to numpy arrays and a new l3 list
86 returned; editing the new list will not affect the original.
87 '''
88 import l3lang.external.hdf_mixins as hm
89 if not hm.l3_have_pytables:
90 G.logger.message("pytables is not available. Unable to load hdf file.")
91 return fname
93 import tables
94 fi = tables.openFile(fname)
95 num_img = fi.l3_len()
97 # For the file to be closed here, the l3_getimg() arrays must be
98 # copied as numpy arrays.
100 vals = [ (ii, fi.l3_getimg(ii)[:,:]) for ii in range(0, num_img) ]
102 # Produce list of lists for viewing, if requested.
103 M = len(vals)
104 if (M > 3) and shape == 'square':
105 cols = int(floor(sqrt(M)))
106 vals = [tuple(vals[ii : ii + cols]) for ii in range(0, M, cols)]
108 # Form the ast
109 rv = ast.val2ast(vals)
111 fi.close()
112 return rv
116 #** array to image conversions
117 #*** numpy array to pixbuf
118 def arr2pix(num_arr):
119 ''' Convert a 2D float array to a greyscale pixmap. '''
120 import numpy as N
121 assert len(num_arr.shape) == 2, "Expected 2D array."
123 # Scale float array to 8-bits.
124 rows, cols = num_arr.shape
125 bottom = N.min(num_arr)
126 top = N.max(num_arr)
127 scale = (top - bottom)
128 norm_arr = ((num_arr - bottom) / scale * 255).astype(N.uint8)\
129 .reshape(rows*cols)
131 # Use 3 8-bit arrays as (rgb) values.
132 t3 = N.outer(norm_arr, N.array([1,1,1], 'b'))
133 t4 = N.reshape(t3, (rows, cols, 3))
134 pix_arr = N.array(t4, dtype=N.uint8) #
136 # Get the pixbuf.
137 # array input indexing is row, pixel, [r,g,b {,a} ]
138 pixbuf = gtk.gdk.pixbuf_new_from_data(
139 pix_arr.tostring(order='a'),
140 gtk.gdk.COLORSPACE_RGB,
141 False,
143 cols,
144 rows,
145 3*cols)
147 return pixbuf
150 #*** emdata conversions
151 def emdata2pixbuf(emd):
152 from EMAN2 import EMNumPy, EMData
153 import numpy as N
154 import gtk
156 assert isinstance(emd, EMData), "Expected EMdata instance"
157 # eman -> numeric -> gtk
158 num_arr = EMNumPy.em2numpy(emd)
159 return arr2pix(num_arr)
161 #*** png conversion
162 def png2pixbuf(png_file, shape = 'list'):
163 import matplotlib.image as mi
164 import numpy as N
165 if isinstance(png_file, ast.String):
166 png_file = png_file.py_string()
167 img_arr = mi.imread(png_file)
168 rows, cols, bytes = img_arr.shape
170 # Scale from [0,1] to [0,255]
171 img_arr = N.array(255 * img_arr, dtype=N.uint8)
173 # Get the pixbuf.
174 # array input indexing is row, pixel, [r,g,b {,a} ]
175 pixbuf = gtk.gdk.pixbuf_new_from_data(
176 img_arr.tostring(order='a'),
177 gtk.gdk.COLORSPACE_RGB,
178 True,
180 cols,
181 rows,
182 bytes * cols)
184 return pixbuf
185 ## misc.png2pixbuf = png2pixbuf
186 # # # Convert.
187 # # pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB,
188 # # has_alpha = True,
189 # # bits_per_sample = 8,
190 # # width = cols,
191 # # height = rows)
193 # # array = pixbuf_get_pixels_array(pixbuf)
194 # # array[:,:,:] = image_array
197 #* utility
198 def escape_markup(text):
199 '''Change html-like markup in `text` so that GnomeCanvasText ignores it.
200 See http://developer.gnome.org/doc/API/2.0/pango/PangoMarkupFormat.html
202 return text.replace("<", "&lt;")
205 def triple_quote(text):
206 return '"""%s"""' % text
208 class DisplayError(exceptions.Exception):
209 def __init__(self, e_args=None):
210 self.e_args = e_args
211 def __str__(self):
212 return repr(self.e_args)
215 def flush_events():
216 # Update bounding boxes, among others.
217 while gtk.events_pending():
218 gtk.main_iteration()
220 def python_main_loop():
221 try:
222 while 1:
223 gtk.main_iteration_do()
224 except:
225 pdb.pm()
227 def destroy_if_last(obj):
228 if 0:
229 rc = sys.getrefcount(obj)
230 print "refcount: ", rc
231 print "wiping ", obj
232 # Ignore local obj, getrefcount arg
233 if rc > 5:
234 pdb.set_trace()
235 obj.destroy()
237 def print_(strng):
238 print strng
239 sys.stdout.flush()
241 def warn_(strng):
242 print >> sys.stderr, "Warning: ", strng
243 sys.stderr.flush()
245 def slot_available(w_, old_entry):
246 if w_.fluid_ref(insert_l3_tree = True):
247 if not isinstance(old_entry, ast.aNone):
248 raise DisplayError("Insertion in occupied slot.")
251 def canvas_item_get_bounds_world(item):
252 i2w = item.get_property("parent").i2w
253 x1, y1, x2, y2 = item.get_bounds()
254 u1, v1 = i2w(x1, y1)
255 u2, v2 = i2w(x2, y2)
256 return u1, v1, u2, v2
259 #* comment -> node connection
260 def associate_comments(w_, tree, src_string):
261 from l3lang import reader, interp, ast
262 tm_play_env = interp.get_child_env(w_.state_.def_env,
263 w_.state_.storage)
264 for nd, comment in reader.parse_comments(tree, src_string):
265 ctree = w_.deco_table_[nd._id] = ast.Comment(comment)
266 ctree.setup(ast.empty_parent(),
267 tm_play_env,
268 w_.state_.storage)
272 #* affine rotation
273 def affine_rotate(theta):
274 # Return rotation tuple for theta degrees.
275 s = sin (theta * pi / 180.0)
276 c = cos (theta * pi / 180.0)
277 return (c, s, -s, c, 0, 0)
279 #* rounded corner rectangle
280 # Rectangle w/ rounded corners, leaving the bezier control points on
281 # the tangent lines.
282 # Note: using one definition, say on [0,1]x[0,1] and scaling will also
283 # scale the rounded edges, but these must have absolute size. [ie. the
284 # rounding size must be independent of the rectangle size.]
285 def path_rectangle_rounded(el, er,
286 et, eb, # edge positions
287 cv, ch, # corner point indentation
288 sv, sh, # spline control point indentation
290 assert (el < er)
291 assert (et < eb)
293 # Suppress rounded corner for extreme cases.
294 if not ((er - el) > 2*ch):
295 ch = (er - el) / 3.0
297 if not ((eb - et) > 2*cv):
298 cv = (eb - et) / 3.0
300 path = [(MOVETO, el, et + cv),
302 (LINETO, el, eb - cv),
303 (CURVETO , # bottom left
304 el , eb - sv,
305 el + sh , eb,
306 el + ch , eb,
309 (LINETO, er - ch, eb), # bottom right
310 (CURVETO ,
311 er - sh , eb,
312 er , eb - sv,
313 er , eb - cv,
316 (LINETO, er, et + cv),
317 (CURVETO , # top right
318 er , et + sv ,
319 er - sh , et,
320 er - ch , et,
323 (LINETO, el + ch, et),
324 (CURVETO , # top left
325 el + sh , et,
326 el , et + sv,
327 el , et + cv,
330 return path
332 #* file selection popup
333 def file_selection(call_w_selection):
335 # Let the user select a filename NAME, and call
336 # call_w_selection(NAME)
338 fs = gtk.FileSelection("Select file")
340 def wrap(widget):
341 fname = fs.get_filename()
342 print ("Selected filename: %s\n" % fname)
343 call_w_selection(fname)
345 fs.ok_button.connect( "clicked", wrap)
346 fs.ok_button.connect_after( "clicked",
347 lambda *a: fs.destroy())
348 fs.cancel_button.connect_after( "clicked",
349 lambda *a: fs.destroy())
350 fs.show_all()
355 #* input validation
356 def validate_input(text):
357 # Check the input text; use interactive widget if fixing is
358 # required.
360 pass
362 #* selection handling
363 #** Selectable interface
364 class Selectable:
365 def __init__(self):
366 raise Exception("Interface only.")
368 def plain_view(self):
369 raise DisplayError("interface only")
371 def highlight(self):
372 raise DisplayError("interface only")
374 def l3tree(self):
375 raise DisplayError("interface only")
379 #** selector
380 class Selector:
381 """ Item selection handler.
383 This class handles the selection data, holding zero or more
384 selected L3 display types.
386 Typically, Button-1 selects one item, clearing the existing selection.
387 Ctrl-Button-1 selects one item, adding it to the existing
388 selection.
391 def __init__(self, w_):
392 self._current_node_l = [] # Selectable list
393 self.w_ = w_
395 def have_selection(self):
396 return len(self._current_node_l) > 0
397 Selector.have_selection = have_selection
399 def unselect(self):
400 if self.have_selection():
401 [foo.plain_view() for foo in self._current_node_l]
402 self._current_node_l = []
403 Selector.unselect = unselect
406 #*** Single-selection interface
407 def set_selection(self, item):
408 ''' Clear existing selection and add item. '''
409 assert isinstance(item, Selectable)
410 # Adjust existing node.
411 if self.have_selection():
412 [foo.plain_view() for foo in self._current_node_l]
414 # Set up new selection.
415 self._current_node_l = [item]
416 item.highlight()
418 # Highlight associated tree also.
419 source_id_l = item._l3tree.getthe('value_source_tree_id')
420 if source_id_l:
421 for source_id in source_id_l:
422 linked_pic = utils.key_of(source_id, item._canvas._nodes)
423 if linked_pic:
424 self.add_to_selection(linked_pic)
425 Selector.set_selection = set_selection
427 def get_selection(self):
428 if self.have_selection():
429 return self._current_node_l[-1]
430 else:
431 return None
432 Selector.get_selection = get_selection
435 #*** Multi-selection interface
436 def add_to_selection(self, item):
437 """ Add item to selection if it is not there. """
438 assert isinstance(item, Selectable)
439 if self.have_selection():
440 if item not in self._current_node_l:
441 self._current_node_l.append(item)
442 item.highlight()
443 else:
444 self._current_node_l = [item]
445 item.highlight()
446 Selector.add_to_selection = add_to_selection
448 def remove(self, item):
449 """ Remove item from selection. """
450 assert isinstance(item, Selectable)
451 if self.have_selection():
452 if item in self._current_node_l:
453 self._current_node_l.remove(item)
454 item.plain_view()
455 Selector.remove = remove
457 def toggle_selection(self, item):
459 Add item to selection if not there; remove it if present already.
461 assert isinstance(item, Selectable)
462 if self.have_selection():
463 if item not in self._current_node_l:
464 self._current_node_l.append(item)
465 item.highlight()
466 else:
467 self._current_node_l.remove(item)
468 item.plain_view()
469 else:
470 self._current_node_l = [item]
471 item.highlight()
472 Selector.toggle_selection = toggle_selection
474 def get_selection_list(self):
477 return copy(self._current_node_l)
478 Selector.get_selection_list = get_selection_list
481 #** copy selection to clipboard
483 # Avoid
484 # GtkClipboard prematurely finalized
485 # problems
486 gtk_primary_clipboard = gtk.Clipboard(selection='PRIMARY')
487 gtk_clipboard_clipboard = gtk.Clipboard(selection='CLIPBOARD')
489 def copy_selection(self):
490 from l3lang import reader, interp, ast
491 if not self.have_selection():
492 return
493 s_tree = self.get_selection().l3tree()
495 # Get the pretty-printed string and verify the tree.
496 comm_dct = self.w_.deco_table_
498 pp_string = s_tree.get_infix_string(self.w_.pprint_width,
499 comm_dct)
500 pp_tree = reader.parse(pp_string) ## .single_program()
501 pp_env = interp.get_child_env(self.w_.state_.def_env,
502 self.w_.state_.storage)
503 pp_tree.setup(ast.empty_parent(), pp_env, self.w_.state_.storage)
504 associate_comments(self.w_, pp_tree, pp_string)
505 diff = s_tree.tree_difference(comm_dct, pp_tree.body(), comm_dct)
506 if diff == '':
507 gtk_primary_clipboard.set_text(pp_string)
508 gtk_clipboard_clipboard.set_text(pp_string)
509 else:
510 G.logger.warn("Unable to copy tree:\n%s\n" % diff)
511 Selector.copy_selection = copy_selection
514 #* Property getting / setting
515 def _pickle_these(*args): ### test only
516 for data in args:
517 utils.file_pickle( data )
519 def _get_props(item, prop_list, *properties):
520 # Return a (property, value) list
521 # When property is of the form "*-set", it is queried and only if
522 # true is the following property used.
523 # See also _set_props()
525 skip = 0
526 for pp in properties:
527 if skip:
528 skip -= 1
529 continue
531 if len(pp) > 4 and pp[-4:None] == "-set":
532 if not item.get_property(pp):
533 skip += 1
534 continue
535 try:
536 prop = item.get_property(pp)
537 except:
538 print "_get_props Failed for item ", item,
539 print ", property: ", pp
540 continue
542 # Retrieve non-pickling properties as text.
543 # [ first needed for .set_property("anchor", 'GTK_ANCHOR_NORTH_WEST')]
544 if pp in ["anchor"]:
545 prop = prop.value_name
547 prop_list.append( (pp, prop) )
548 return prop_list
550 def _set_props(item, prop_list):
551 # Update the ITEM's properties.
553 # prop_list: (property, value) list
555 # See also _get_props().
557 for (prop, value) in prop_list:
558 try:
559 item.set_property(prop, value)
560 except Exception, foo:
561 print foo, prop, value
563 #* unique dictionary insertion
564 def unique_set(dic, key, val):
565 if dic.has_key(key):
566 raise KeyError("Key `%s` exists in dictionary." % key)
567 dic[key] = val
568 return dic
569 ## misc.unique_set = unique_set
572 #* safe deletion
573 def _safed(obj):
574 if obj is None: return
575 obj.destroy()
577 #* gtk object proxy
578 # Only dereference the gtk object while it is alive.
579 class GtkObjProxy:
580 def __init__(self, obj):
581 self._obj = obj
582 self._live = True
583 obj.connect("destroy", self._destroyed)
585 def __getattr__(self, nm):
586 if self._live:
587 return self._obj.__getattribute__(nm)
588 else:
589 raise Exception("Accessing dead gtk object.")
591 def _destroyed(self, widget):
592 self._live = False
595 #* main I/O entry points.
597 #** load script
598 def load_script(w_, filename):
599 from l3lang import reader, interp, ast
600 # Tree setup.
601 text = "".join(open(filename, "r").readlines())
602 tm_tree = reader.parse(text)
603 tm_play_env = interp.get_child_env(w_.state_.def_env,
604 w_.state_.storage)
605 tm_tree.setup(ast.empty_parent(), tm_play_env, w_.state_.storage)
606 tm_tree.set_outl_edges(w_, None)
608 # Comment association.
609 associate_comments(w_, tm_tree, text)
611 # Node.
612 tm_tree_l3pic = w_.canvas.view.start_add_l3tree(tm_tree)
613 return None
615 #** load library
616 def load_library(w_, filename, maxdepth = 1):
617 ''' Load library entries from file, display without Program.'''
618 # See also add_l3tree_clipboard
619 from l3lang import reader, interp, ast
621 # Tree setup.
622 text = "".join(open(filename, "r").readlines())
623 tm_tree = reader.parse(text)
624 tm_play_env = interp.get_child_env(w_.state_.def_env,
625 w_.state_.storage)
626 tm_tree.setup(ast.empty_parent(), tm_play_env, w_.state_.storage)
627 tm_tree.set_outl_edges(w_, None)
629 # Comment association.
630 associate_comments(w_, tm_tree, text)
632 # Display independent nodes.
633 shift_y = 0
634 for tree in [ee for ee in tm_tree.entries()]: # copy for iteration
635 # Independent nodes
636 tree.detach_from_parent_rec(w_.state_.storage)
638 # Limit tree display depth.
639 if isinstance(tree, (ast.cls_viewList, ast.Program)):
640 tree.outl_flat_display(maxdepth)
642 # Node.
643 l3pic = w_.lib_canvas.view.start_add_l3tree(tree)
645 # Position.
646 flush_events()
647 pl, pt, pr, pb = l3pic.get_bounds_world()
648 l3pic.move(0.0, shift_y)
649 shift_y += pb - pt + w_.cp_.marker_padding
651 return None
654 #** save
655 def save_state(w_, filename):
656 # Faster.
657 return utils.file_cPickle( get_current_state(w_), filename = filename)
658 # Better debug info.
659 # # return utils.file_pickle( get_current_state(w_), filename = filename)
661 def get_current_state(w_):
662 return utils.Shared(
663 ### selection saving must be via REFERENCE -- maybe use _id
664 ### tags later.
665 ### selection = w_.selector.get_selection().get_state(),
667 # Data.
668 cp_ = w_.cp_,
669 state_ = w_.state_,
670 deco_table_ = w_.deco_table_,
672 # Instances.
673 canvas_view = w_.canvas.view.get_state(),
674 lib_canvas_view = w_.lib_canvas.view.get_state(),
678 #** load
679 def load_state(w_, filename):
680 data = utils.file_unpickle(filename)
681 state_ = data.state_
684 # Clear existing canvas content first.
686 w_.canvas.view.clear_state( )
687 w_.lib_canvas.view.clear_state( )
689 # Restore previous data structures.
690 w_.cp_.pull(data.cp_)
691 w_.state_.pull(state_)
692 w_.deco_table_ = data.deco_table_
694 # Restore non-persistent environment.
695 interp.setup_envs(state_.storage, state_.root_env, state_.def_env)
697 # And rebuild the canvas' display and data.
698 w_.canvas.view.set_state ( data.canvas_view )
699 w_.lib_canvas.view.set_state ( data.lib_canvas_view )
701 ### selection saving...
702 # if data.selection != None:
703 # selection = w_.selector.get_selection().get_state(),
704 # w_.selector.set_selection (data.selection)
706 # Ugly hack to force update
707 for canv in [w_.canvas.view, w_.lib_canvas.view]:
708 canv._pixpu = (1.00001 * canv._pixpu)
709 canv.set_pixels_per_unit(canv._pixpu)
710 # Restore missing graph edges.
711 # w_
713 # add_l3tree handles
714 # _canvas
715 # _parent
716 # _marker
717 # _container
719 # Ignored for now:
720 # _tag_state
725 #** print PS
726 def print_display(w_):
727 # Produce a postscript dump of the text of both canvases on stdout.
729 # A full "screenshot" including all graphics requires more
730 # adjustments. In particular:
731 # -- for 'show', the PS position is (left, baseline);
732 # the gc (gnome canvas) position is (center, center)
733 # -- stroke width has to be included
734 # -- coloring
736 print """
737 % Right Canvas State
738 % Change orientation, (0,0) in upper left
739 0 11 72 mul translate
741 1 4 div dup scale % adjust to fit page
743 /fscale 10 def
744 /Courier findfont fscale 1.7 mul scalefont setfont
747 % Change orientation, y increases down.
748 % Scale so 1 unit == font height + depth
749 /fntunit % x y : x y
751 fscale mul exch
752 fscale mul exch
753 } def
755 /gcmoveto % x y : --
756 { fntunit -1 mul moveto } def
758 /gclineto % x y : --
759 { fntunit -1 mul lineto } def
761 /gcshow % string : --
762 { % move point from (left, baseline) to (left, top)
763 0 fscale 1 mul neg rmoveto
764 show
765 } def
767 # left canvas: w_.lib_canvas.view.get_state(),
769 w_.with_fluids(lambda : w_.canvas.view.get_state(),
770 dump_ps = True)
773 #* stdin/out/error handling
774 class StdInOutErr:
775 pass
777 def __init__(self, w_):
778 self.w_ = w_
779 self._stack = []
780 self._sys = (sys.stdin, sys.stdout, sys.stderr)
781 StdInOutErr.__init__ = __init__
784 def std_connection(self):
785 # Reset to defaults.
786 (sys.stdin, sys.stdout, sys.stderr) = self._sys
787 StdInOutErr.std_connection = std_connection
790 def push(self):
791 # Store stdin/out/err. Used by other instances before they grab
792 # stdin/out/err.
793 self._stack.append( (sys.stdin, sys.stdout, sys.stderr) )
794 StdInOutErr.push = push
797 def pop(self):
798 # Restore stdin/out/err. Used by other instances to release
799 # stdin/out/err.
800 (sys.stdin, sys.stdout, sys.stderr) = self._stack.pop()
801 StdInOutErr.pop = pop
803 #* application-level glue functions / classes
804 #** Console visibility
805 def show_hide_console(w_):
806 bot = w_.vpane.get_property("max-position")
807 prop = w_.consoles.proportion_y
808 if bot == w_.vpane.get_property("position"):
809 # make it visible
810 top = w_.vpane.get_property("min-position")
811 w_.vpane.set_property("position", prop * top + (1 - prop) * bot)
812 else:
813 # hide it.
814 w_.vpane.set_property("position", bot)
816 def show_program_only(w_):
817 # Hide vertical pane.
818 w_.vpane.set_property("position",
819 w_.vpane.get_property("max-position"))
820 # Hide library canvas.
821 w_.hpane.set_property("position",
822 w_.hpane.get_property("min-position"))
825 def show_consoles(w_):
826 top = w_.vpane.get_property("min-position")
827 bot = w_.vpane.get_property("max-position")
828 pos = w_.vpane.get_property("position")
829 prop = w_.consoles.proportion_y
830 min_pos = prop * top + (1 - prop) * bot
831 if pos > min_pos:
832 # enlarge visible area
833 w_.vpane.set_property("position", min_pos)
835 #** Custom notebook for consoles
836 class ConsoleNtbk(gtk.Notebook):
837 pass
839 def __init__(self, w_):
840 gtk.Notebook.__init__(self)
841 self.w_ = w_
842 self.set_tab_pos(gtk.POS_LEFT)
843 self._entries = {} # (name -> widget) dict
844 ConsoleNtbk.__init__ = __init__
846 def add_page(self, widget, name):
847 # Add and track `widget` under label `name`.
848 assert not self._entries.has_key(name), "Page already exists."
849 label = gtk.Label(name)
850 label.set_padding(2, 2)
851 self.append_page(widget, label)
852 self._entries[name] = widget
853 ConsoleNtbk.add_page = add_page
855 def page_index(self, name):
856 assert self._entries.has_key(name)
857 idx = self.page_num(self._entries[name])
858 assert idx != -1 # Invalid _entries.
859 return idx
860 ConsoleNtbk.page_index = page_index
862 def destroy_page(self, name):
863 idx = self.page_index(name)
864 self.remove_page(idx)
865 self._entries[name].destroy()
866 del self._entries[name]
867 ConsoleNtbk.destroy_page = destroy_page
870 #** l3 console with notebook / canvas / l3List connections
871 def add_l3appconsole(w_):
872 from l3lang import reader
873 from l3gui.cruft.pylab import l3Console
874 from l3gui import widgets, misc
875 # Introduce a new Program.
876 storage = w_.state_.storage
877 def_env = w_.state_.def_env
878 program = reader.empty_program().setup(ast.empty_parent(),
879 def_env,
880 storage)[0]
881 program.set_outl_edges(w_, None)
882 # no comments: associate_comments(w_, program, text)
883 program_l3pic = w_.canvas.view.start_add_l3tree(program)
885 # Set up the console.
886 name_s = "l3 Console"
887 console = l3Console( w_, locals(),
888 quit_handler =
889 lambda *args: (w_.notebk_consoles.destroy_page(name_s),
890 w_.stdinouterr.pop()
892 w_.notebk_consoles.add_page(console, name_s)
893 w_.stdinouterr.push()
894 console.std_to_widget()
895 console.write("==== l3 Console; exit with end-of-file (ctrl-d) ====\n",
896 style='error')
897 console.banner()
899 # Connect Program and console.
900 console.associate_alist(program_l3pic._alist, def_env, storage)
902 # Display the console.
903 misc.show_consoles(w_)
904 cs = w_.notebk_consoles
905 cs.set_current_page(cs.page_index(name_s))
906 console.grab_focus()
909 #** terminal console with notebook / canvas / l3List connections
910 def new_termconsole(w_):
912 Start a new Program, and connect it to the main terminal.
913 Terminal history accumulates as entries into the Program.
915 from l3lang import reader
916 # Introduce a new Program.
917 storage = w_.state_.storage
918 def_env = w_.state_.def_env
919 program = reader.empty_program().setup(ast.empty_parent(),
920 def_env,
921 storage)[0]
922 program.set_outl_edges(w_, None)
923 # no comments: associate_comments(w_, program, text)
924 program_l3pic = w_.canvas.view.start_add_l3tree(program)
926 # Set up the console.
927 from l3lang import repl
928 console, interpreter = repl.get_console_interpreter_pair(def_env, storage)
929 def_env.import_external('console', console)
930 def_env.import_external('interp', interpreter)
932 # Connect Program and console.
933 disp_alist = program_l3pic._alist
935 # Display the console.
936 w_.stdinouterr.push()
937 w_.stdinouterr.std_connection()
939 def root_display(cons_prog):
940 body = cons_prog.body()
942 # Ignore empty programs.
943 if isinstance(body, ast.aNone):
944 storage.remove(cons_prog._id)
945 return
947 # Extract body from Program.
948 cons_prog._primary[0].detach_child(body._id, storage)
950 # Delete Program(). This avoids leaving unpickleable objects
951 # in the storage.
952 storage.remove(cons_prog._id)
954 # Display node.
955 body.set_outl_edges(w_, None)
956 body_l3pic = w_.canvas.view.start_add_l3tree(body)
957 disp_alist.append(body_l3pic)
960 console.interact(display_via = root_display,
961 do_event = flush_events,
962 associate_comments =
963 (lambda tree, src: associate_comments(w_, tree, src)),
967 #* Visual interpretation tracing
968 class DisplayInterp:
969 ''' Indicate progress by selecting the node about to be interpreted.
971 pass
973 def __init__(self, w_, l3pic):
974 self.w_ = w_
975 self._active = True
976 self._l3pic = l3pic # The (l3Base) display.
977 DisplayInterp.__init__ = __init__
979 def __call__(self, l3tree, env, storage):
980 # todo: the context of the call could be shown to indicate loop
981 # progress. Or display an iteration count as part of the
982 # highlight.
984 # Tree still visible?
985 if self._active:
986 if hasattr(self._l3pic, '_root_group'):
987 self.w_.selector.set_selection(self._l3pic)
988 gtk.main_iteration(block = False)
989 DisplayInterp.__call__ = __call__
991 def __getstate__(self):
992 # A DisplayInterp must be an in-memory only attachment.
993 return {'_active' : False}
994 DisplayInterp.__getstate__ = __getstate__
996 def __setstate__(self, stuff):
997 self.__dict__.update(stuff)
998 DisplayInterp.__setstate__ = __setstate__
1000 def __deepcopy__(self, memo):
1001 return DisplayInterp(self.w_, self._l3pic)
1002 DisplayInterp.__deepcopy__ = __deepcopy__
1004 #* Copy node and deco_table entries
1005 def node_copy(w_, orig, ):
1006 node, nid = deepcopy(orig).\
1007 setup(ast.empty_parent(),
1008 w_.state_.def_env,
1009 w_.state_.storage)
1011 # Copy relevant table entries.
1012 oit = orig.top_down()
1013 nit = node.top_down()
1014 dt = w_.deco_table_
1015 try:
1016 while 1:
1017 id_o = oit.next()._id
1018 if dt.has_key(id_o):
1019 # Copy node's comment.
1020 comm = deepcopy(dt[id_o])
1021 comm.setup(ast.empty_parent(),
1022 w_.state_.def_env,
1023 w_.state_.storage)
1024 dt[nit.next()._id] = comm
1025 else:
1026 nit.next()
1027 except StopIteration:
1028 pass
1030 # Set outline edges.
1031 node.set_outl_edges(w_, None)
1032 return node, nid