| # changes by dscherer@cmu.edu | 
 | #   - OutputWindow and OnDemandOutputWindow have been hastily | 
 | #     extended to provide readline() support, an "iomark" separate | 
 | #     from the "insert" cursor, and scrolling to clear the window. | 
 | #     These changes are used by the ExecBinding module to provide | 
 | #     standard input and output for user programs.  Many of the new | 
 | #     features are very similar to features of PyShell, which is a | 
 | #     subclass of OutputWindow.  Someone should make some sense of | 
 | #     this. | 
 |  | 
 | from Tkinter import * | 
 | from EditorWindow import EditorWindow | 
 | import re | 
 | import tkMessageBox | 
 |  | 
 | from UndoDelegator import UndoDelegator | 
 |  | 
 | class OutputUndoDelegator(UndoDelegator): | 
 |     reading = 0 | 
 |     # Forbid insert/delete before the I/O mark, in the blank lines after | 
 |     #   the output, or *anywhere* if we are not presently doing user input | 
 |     def insert(self, index, chars, tags=None): | 
 |         try: | 
 |             if (self.delegate.compare(index, "<", "iomark") or | 
 |                 self.delegate.compare(index, ">", "endmark") or | 
 |                 (index!="iomark" and not self.reading)): | 
 |                 self.delegate.bell() | 
 |                 return | 
 |         except TclError: | 
 |             pass | 
 |         UndoDelegator.insert(self, index, chars, tags) | 
 |     def delete(self, index1, index2=None): | 
 |         try: | 
 |             if (self.delegate.compare(index1, "<", "iomark") or | 
 |                 self.delegate.compare(index1, ">", "endmark") or | 
 |                 (index2 and self.delegate.compare(index2, ">=", "endmark")) or | 
 |                 not self.reading): | 
 |                 self.delegate.bell() | 
 |                 return | 
 |         except TclError: | 
 |             pass | 
 |         UndoDelegator.delete(self, index1, index2) | 
 |  | 
 | class OutputWindow(EditorWindow): | 
 |     """An editor window that can serve as an input and output file. | 
 |        The input support has been rather hastily hacked in, and should | 
 |        not be trusted. | 
 |     """ | 
 |  | 
 |     UndoDelegator = OutputUndoDelegator | 
 |     source_window = None | 
 |  | 
 |     def __init__(self, *args, **keywords): | 
 |         if keywords.has_key('source_window'): | 
 |             self.source_window = keywords['source_window'] | 
 |         apply(EditorWindow.__init__, (self,) + args) | 
 |         self.text.bind("<<goto-file-line>>", self.goto_file_line) | 
 |         self.text.bind("<<newline-and-indent>>", self.enter_callback) | 
 |         self.text.mark_set("iomark","1.0") | 
 |         self.text.mark_gravity("iomark", LEFT) | 
 |         self.text.mark_set("endmark","1.0") | 
 |  | 
 |     # Customize EditorWindow | 
 |  | 
 |     def ispythonsource(self, filename): | 
 |         # No colorization needed | 
 |         return 0 | 
 |  | 
 |     def short_title(self): | 
 |         return "Output" | 
 |  | 
 |     def long_title(self): | 
 |         return "" | 
 |  | 
 |     def maybesave(self): | 
 |         # Override base class method -- don't ask any questions | 
 |         if self.get_saved(): | 
 |             return "yes" | 
 |         else: | 
 |             return "no" | 
 |  | 
 |     # Act as input file - incomplete | 
 |  | 
 |     def set_line_and_column(self, event=None): | 
 |         index = self.text.index(INSERT) | 
 |         if (self.text.compare(index, ">", "endmark")): | 
 |           self.text.mark_set("insert", "endmark") | 
 |         self.text.see("insert") | 
 |         EditorWindow.set_line_and_column(self) | 
 |  | 
 |     reading = 0 | 
 |     canceled = 0 | 
 |     endoffile = 0 | 
 |  | 
 |     def readline(self): | 
 |         save = self.reading | 
 |         try: | 
 |             self.reading = self.undo.reading = 1 | 
 |             self.text.mark_set("insert", "iomark") | 
 |             self.text.see("insert") | 
 |             self.top.mainloop() | 
 |         finally: | 
 |             self.reading = self.undo.reading = save | 
 |         line = self.text.get("input", "iomark") | 
 |         if self.canceled: | 
 |             self.canceled = 0 | 
 |             raise KeyboardInterrupt | 
 |         if self.endoffile: | 
 |             self.endoffile = 0 | 
 |             return "" | 
 |         return line or '\n' | 
 |  | 
 |     def close(self): | 
 |         self.interrupt() | 
 |         return EditorWindow.close(self) | 
 |  | 
 |     def interrupt(self): | 
 |         if self.reading: | 
 |             self.endoffile = 1 | 
 |             self.top.quit() | 
 |  | 
 |     def enter_callback(self, event): | 
 |         if self.reading and self.text.compare("insert", ">=", "iomark"): | 
 |             self.text.mark_set("input", "iomark") | 
 |             self.text.mark_set("iomark", "insert") | 
 |             self.write('\n',"iomark") | 
 |             self.text.tag_add("stdin", "input", "iomark") | 
 |             self.text.update_idletasks() | 
 |             self.top.quit() # Break out of recursive mainloop() in raw_input() | 
 |  | 
 |         return "break" | 
 |  | 
 |     # Act as output file | 
 |  | 
 |     def write(self, s, tags=(), mark="iomark"): | 
 |         self.text.mark_gravity(mark, RIGHT) | 
 |         self.text.insert(mark, str(s), tags) | 
 |         self.text.mark_gravity(mark, LEFT) | 
 |         self.text.see(mark) | 
 |         self.text.update() | 
 |  | 
 |     def writelines(self, l): | 
 |         map(self.write, l) | 
 |  | 
 |     def flush(self): | 
 |         pass | 
 |  | 
 |     # Our own right-button menu | 
 |  | 
 |     rmenu_specs = [ | 
 |         ("Go to file/line", "<<goto-file-line>>"), | 
 |     ] | 
 |  | 
 |     file_line_pats = [ | 
 |         r'file "([^"]*)", line (\d+)', | 
 |         r'([^\s]+)\((\d+)\)', | 
 |         r'([^\s]+):\s*(\d+):', | 
 |     ] | 
 |  | 
 |     file_line_progs = None | 
 |  | 
 |     def goto_file_line(self, event=None): | 
 |         if self.file_line_progs is None: | 
 |             l = [] | 
 |             for pat in self.file_line_pats: | 
 |                 l.append(re.compile(pat, re.IGNORECASE)) | 
 |             self.file_line_progs = l | 
 |         # x, y = self.event.x, self.event.y | 
 |         # self.text.mark_set("insert", "@%d,%d" % (x, y)) | 
 |         line = self.text.get("insert linestart", "insert lineend") | 
 |         result = self._file_line_helper(line) | 
 |         if not result: | 
 |             # Try the previous line.  This is handy e.g. in tracebacks, | 
 |             # where you tend to right-click on the displayed source line | 
 |             line = self.text.get("insert -1line linestart", | 
 |                                  "insert -1line lineend") | 
 |             result = self._file_line_helper(line) | 
 |             if not result: | 
 |                 tkMessageBox.showerror( | 
 |                     "No special line", | 
 |                     "The line you point at doesn't look like " | 
 |                     "a valid file name followed by a line number.", | 
 |                     master=self.text) | 
 |                 return | 
 |         filename, lineno = result | 
 |         edit = self.untitled(filename) or self.flist.open(filename) | 
 |         edit.gotoline(lineno) | 
 |         edit.wakeup() | 
 |  | 
 |     def untitled(self, filename): | 
 |         if filename!='Untitled' or not self.source_window or self.source_window.io.filename: | 
 |             return None | 
 |         return self.source_window | 
 |  | 
 |     def _file_line_helper(self, line): | 
 |         for prog in self.file_line_progs: | 
 |             m = prog.search(line) | 
 |             if m: | 
 |                 break | 
 |         else: | 
 |             return None | 
 |         filename, lineno = m.group(1, 2) | 
 |         if not self.untitled(filename): | 
 |             try: | 
 |                 f = open(filename, "r") | 
 |                 f.close() | 
 |             except IOError: | 
 |                 return None | 
 |         try: | 
 |             return filename, int(lineno) | 
 |         except TypeError: | 
 |             return None | 
 |  | 
 | # This classes now used by ExecBinding.py: | 
 |  | 
 | class OnDemandOutputWindow: | 
 |     source_window = None | 
 |  | 
 |     tagdefs = { | 
 |         # XXX Should use IdlePrefs.ColorPrefs | 
 |         "stdin":   {"foreground": "black"}, | 
 |         "stdout":  {"foreground": "blue"}, | 
 |         "stderr":  {"foreground": "red"}, | 
 |     }    | 
 |      | 
 |     def __init__(self, flist): | 
 |         self.flist = flist | 
 |         self.owin = None | 
 |         self.title = "Output" | 
 |         self.close_hook = None | 
 |         self.old_close = None | 
 |  | 
 |     def owclose(self): | 
 |         if self.close_hook: | 
 |             self.close_hook() | 
 |         if self.old_close: | 
 |             self.old_close() | 
 |  | 
 |     def set_title(self, title): | 
 |         self.title = title | 
 |         if self.owin and self.owin.text: | 
 |           self.owin.saved_change_hook() | 
 |  | 
 |     def write(self, s, tags=(), mark="iomark"): | 
 |         if not self.owin or not self.owin.text: | 
 |             self.setup() | 
 |         self.owin.write(s, tags, mark) | 
 |  | 
 |     def readline(self): | 
 |         if not self.owin or not self.owin.text: | 
 |             self.setup() | 
 |         return self.owin.readline() | 
 |  | 
 |     def scroll_clear(self): | 
 |         if self.owin and self.owin.text: | 
 |            lineno = self.owin.getlineno("endmark") | 
 |            self.owin.text.mark_set("insert","endmark") | 
 |            self.owin.text.yview(float(lineno)) | 
 |            self.owin.wakeup() | 
 |      | 
 |     def setup(self): | 
 |         self.owin = owin = OutputWindow(self.flist, source_window = self.source_window) | 
 |         owin.short_title = lambda self=self: self.title | 
 |         text = owin.text | 
 |  | 
 |         self.old_close = owin.close_hook | 
 |         owin.close_hook = self.owclose | 
 |  | 
 |         # xxx Bad hack: 50 blank lines at the bottom so that | 
 |         #     we can scroll the top of the window to the output | 
 |         #     cursor in scroll_clear().  There must be a better way... | 
 |         owin.text.mark_gravity('endmark', LEFT) | 
 |         owin.text.insert('iomark', '\n'*50) | 
 |         owin.text.mark_gravity('endmark', RIGHT) | 
 |          | 
 |         for tag, cnf in self.tagdefs.items(): | 
 |             if cnf: | 
 |                 apply(text.tag_configure, (tag,), cnf) | 
 |         text.tag_raise('sel') |