def __init__(self, streams_to_watch={}, *args, **kwargs): GladeAppWindow.__init__(self, gladeFile()) ## Forward injected to imported Plide modules PlideHelp.define_injected(injected) PlideTab.define_injected(injected) PyPLearnOptionsDialog.define_injected(injected, gladeFile) ## Initialize Members self.untitled_counter = 1 self.work_requests = {} # Request ids to expdir mapping self.all_plearn_classes = injected.getAllClassnames() ## Initialize Display self.setup_statusbar() self.log_filters = [ re.compile("WARNING.*Scintilla.*PosChanged.*deprecated") ] self.log_clear() self.log_hide() welcome_text = kwargs.get( "welcome_text", "<b>Welcome to Plide %s!</b>" % self.PlideVersion) self.status_display(welcome_text, has_markup=True) self.setup_stdouterr_redirect(streams_to_watch) ## Set up help system injected.helpResourcesPath(helpResourcesPath()) self.help_viewer = PlideHelp(self) self.help_viewer.display_page("index.html") self.help_close() ## Prepare the work queue self.work_queue = PLearnWorkQueue()
def __init__(self, streams_to_watch={}, *args, **kwargs): GladeAppWindow.__init__(self, gladeFile()) ## Forward injected to imported Plide modules PlideHelp.define_injected(injected) PlideTab.define_injected(injected) PyPLearnOptionsDialog.define_injected(injected, gladeFile) ## Initialize Members self.untitled_counter = 1 self.work_requests = {} # Request ids to expdir mapping self.all_plearn_classes = injected.getAllClassnames() ## Initialize Display self.setup_statusbar() self.log_filters = [re.compile("WARNING.*Scintilla.*PosChanged.*deprecated")] self.log_clear() self.log_hide() welcome_text = kwargs.get("welcome_text", "<b>Welcome to Plide %s!</b>" % self.PlideVersion) self.status_display(welcome_text, has_markup=True) self.setup_stdouterr_redirect(streams_to_watch) ## Set up help system injected.helpResourcesPath(helpResourcesPath()) self.help_viewer = PlideHelp(self) self.help_viewer.display_page("index.html") self.help_close() ## Prepare the work queue self.work_queue = PLearnWorkQueue()
class PlideMain(GladeAppWindow): PlideVersion = "0.01" def __init__(self, streams_to_watch={}, *args, **kwargs): GladeAppWindow.__init__(self, gladeFile()) ## Forward injected to imported Plide modules PlideHelp.define_injected(injected) PlideTab.define_injected(injected) PyPLearnOptionsDialog.define_injected(injected, gladeFile) ## Initialize Members self.untitled_counter = 1 self.work_requests = {} # Request ids to expdir mapping self.all_plearn_classes = injected.getAllClassnames() ## Initialize Display self.setup_statusbar() self.log_filters = [ re.compile("WARNING.*Scintilla.*PosChanged.*deprecated") ] self.log_clear() self.log_hide() welcome_text = kwargs.get( "welcome_text", "<b>Welcome to Plide %s!</b>" % self.PlideVersion) self.status_display(welcome_text, has_markup=True) self.setup_stdouterr_redirect(streams_to_watch) ## Set up help system injected.helpResourcesPath(helpResourcesPath()) self.help_viewer = PlideHelp(self) self.help_viewer.display_page("index.html") self.help_close() ## Prepare the work queue self.work_queue = PLearnWorkQueue() def quit(self): ## Minor hack: the main-thread loop is terminated by receiving a ## 'script' whose contents is Quit(). First close all tabs and ## ensure that we stop the process if some tabs won't be closed. n = self.w_plide_notebook.get_n_pages() for i in range(n - 1, -1, -1): tab = self.get_nth_tab(i) if not tab.close_tab(): return True # Stop close process if cannot close tab print >> raw_stderr, "Quit message received" raw_stderr.flush() self.work_queue.post_work_request("Quit()", "", "") GladeAppWindow.quit(self) def help_close(self): """Close the help pane. """ self.w_help_frame.hide() self.w_help_frame.set_no_show_all(True) def help_show(self): """Open the help pane. Bring up context-sensitive help if there is a valid context in the current tab. """ self.w_help_frame.set_no_show_all(False) self.w_help_frame.show() curtab = self.get_current_tab() if curtab: help_context = curtab.get_help_candidate() if help_context: self.help_viewer.display_page(help_context) def setup_stdouterr_redirect(self, streams_to_watch={}): """Redirect standard output and error to be sent to the log pane instead of the console. """ ## Redirect standard output and standard error to display to the ## log pane area. Keep around old stdout/stderr in order to ## display debugging messages. They are called, respectively, ## raw_stdout and raw_stderr (Python file objects) global raw_stdout, raw_stderr old_stdout_fd = os.dup(sys.stdout.fileno()) old_stderr_fd = os.dup(sys.stderr.fileno()) raw_stdout = os.fdopen(old_stdout_fd, 'w') raw_stderr = os.fdopen(old_stderr_fd, 'w') print >> sys.stderr, "Original stderr" print >> raw_stderr, "Redirected stderr" sys.stderr.flush() raw_stderr.flush() (self.stdout_read, self.stdout_write) = os.pipe() (self.stderr_read, self.stderr_write) = os.pipe() os.dup2(self.stdout_write, sys.stdout.fileno()) os.dup2(self.stderr_write, sys.stderr.fileno()) out_flags = fcntl.fcntl(self.stdout_read, fcntl.F_GETFL) err_flags = fcntl.fcntl(self.stderr_read, fcntl.F_GETFL) fcntl.fcntl(self.stdout_read, fcntl.F_SETFL, out_flags | os.O_NONBLOCK) fcntl.fcntl(self.stderr_read, fcntl.F_SETFL, err_flags | os.O_NONBLOCK) streams_to_watch.update({ self.stdout_read: 'stdout', self.stderr_read: 'stderr' }) def callback(fd, cb_condition): # print >>raw_stderr, "log_updater: got stuff on fd", fd, \ # "with condition", cb_condition raw_stderr.flush() data = os.read(fd, 65536) kind = streams_to_watch.get(fd, '') self.log_display(data, kind) return True # Ensure it's called again! for s in streams_to_watch: gobject.io_add_watch(s, gobject.IO_IN, callback) def setup_statusbar(self): """Arrange the status bar area to contain both a status line and a progress bar. """ ## The GTK StatusBar widget is a pain to use. Our statusbar is an ## hbox packed at the bottom of main_vbox, and containing: ## ## - A frame with a status label ## - A progress bar self.w_statusframe = gtk.Frame() self.w_statusframe.set_shadow_type(gtk.SHADOW_IN) self.status_display(' ') # Establish proper height self.w_statusframe.show() self.w_progressbar = gtk.ProgressBar() self.w_progressbar.set_ellipsize(pango.ELLIPSIZE_END) self.w_progressbar.show() ## This gives a list of the "order" in which progress bars should ## be allocated as well as whether each one has been allocated self.all_progressbars = [(self.w_progressbar, False)] self.w_statusbar = gtk.Table(rows=1, columns=4) self.w_statusbar.attach(self.w_statusframe, left_attach=0, right_attach=3, top_attach=0, bottom_attach=1, xoptions=gtk.EXPAND | gtk.FILL, yoptions=gtk.EXPAND | gtk.FILL, xpadding=0, ypadding=0) self.w_statusbar.attach(self.w_progressbar, left_attach=3, right_attach=4, top_attach=0, bottom_attach=1, xoptions=gtk.EXPAND | gtk.FILL, yoptions=gtk.EXPAND | gtk.FILL, xpadding=2, ypadding=2) self.w_statusbar.show() self.w_main_vbox.pack_start(self.w_statusbar, expand=False, fill=False) def log_clear(self): """The log is a TreeView widget that contains 3 columns: (1) number, (2) kind, (3) log entry itself. This sets up a new log or clears an existing log. """ self.log_liststore = gtk.ListStore(int, str, str, int) ## Create individual columns self.log_columns = [ gtk.TreeViewColumn(x) for x in ['No.', 'Kind', 'Message'] ] ## Create cell renderers for displaying the contents self.log_cells = [ gtk.CellRendererText() for i in xrange(len(self.log_columns)) ] for i, (column, cell) in enumerate(zip(self.log_columns, self.log_cells)): column.pack_start(cell, True) column.add_attribute(cell, 'text', i) column.set_sort_column_id(i) ## Last column (message) is a bit tricky: we either set the ## attribute 'text' or 'markup' depending on the setting of the ## fourth model column def message_format_func(treeviewcolumn, cell, model, iter): message = model.get(iter, 2)[0] markup = model.get(iter, 3)[0] cell.set_property('family-set', True) if markup: cell.set_property('markup', message) cell.set_property('family', 'Helvetica') else: cell.set_property('text', message) cell.set_property('family', 'Monospace') self.log_columns[2].set_cell_data_func(self.log_cells[2], message_format_func) ## Create the TreeView using underlying liststore model and append columns ## and make it searchable on the message self.log_treeview = gtk.TreeView(self.log_liststore) for column in self.log_columns: self.log_treeview.append_column(column) self.log_treeview.set_search_column(2) self.log_treeview.set_rules_hint(True) container_remove_children(self.w_plearn_log_scroller) self.w_plearn_log_scroller.add(self.log_treeview) self.log_treeview.show_all() ## Reset the next log entry self.log_next_number = 1 def log_hide(self): self.w_log_messages_menuitem.set_active(False) self.w_plearn_log_scroller.hide() self.w_plearn_log_scroller.set_no_show_all(True) def log_show(self): self.w_log_messages_menuitem.set_active(True) self.w_plearn_log_scroller.set_no_show_all(False) self.w_plearn_log_scroller.show() def log_display(self, message, kind="", has_markup=False, log_clear=False): """Append the given message to the log area of the main window. Thread-safe. """ if log_clear: self.log_clear() ## If 'data' matches any regular expression in log_filter, skip ## this message. This is mostly a hack to get around displaying ## known warnings from the Scintilla editor for regex in self.log_filters: if regex.search(message): return row = [ self.log_next_number, kind.rstrip(), message.rstrip(), has_markup ] self.log_show() self.log_liststore.append(row) self.log_next_number += 1 def status_display(self, message, has_markup=False): """Display the given message in the status bar area of the main window. Thread-safe. """ label = gtk.Label(message) if has_markup: label.set_markup(message) label.set_line_wrap(False) label.set_alignment(0, 0) label.set_padding(2, 2) label.set_single_line_mode(True) label.set_ellipsize(pango.ELLIPSIZE_MIDDLE) label.show() container_remove_children(self.w_statusframe) self.w_statusframe.add(label) def cursor_hourglass(self, unsensitize=True): """Make the cursor an hourglass for the main window. In addition, if the argument unsensitize is True, most entry will be disabled for the window. """ self.w_root.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH)) if unsensitize: self.w_root.set_sensitive(False) def cursor_normal(self, sensitize=True): """Take back the cursor to normal form for the main window. """ self.w_root.window.set_cursor(None) # Set back to parent window cursor if sensitize: self.w_root.set_sensitive(True) def get_nth_tab(self, n): """Return the PlideTab object corresponding to the n-th tab. Return None if there is no such PlideTab. """ notebook_page = self.w_plide_notebook.get_nth_page(n) if notebook_page: return notebook_page.plide_tab_object else: return None def get_current_tab(self): """Return the PlideTab object that's currently selected in the notebook. Return None if none... """ cur_page = self.w_plide_notebook.get_current_page() if cur_page >= 0: return self.w_plide_notebook.get_nth_page( cur_page).plide_tab_object else: return None def get_current_tab_directory(self): """Return the directory associated with the current PlideTab, or '.' if there is no current tab. """ cur_tab = self.get_current_tab() dir = None if cur_tab is not None: dir = cur_tab.get_directory() return dir or '.' ##### Progress Bar Handling ########################################### def reset_progress(self): """Bring back the all progress bars to an 'available' state, erase their containing text, and bring the fraction to zero. """ for i, (pb, alloc) in enumerate(self.all_progressbars): pb.set_text('') pb.set_fraction(0.0) self.all_progressbars[i] = (pb, False) def allocate_progress(self): """Return the ID of an available progress bar, or -1 if none is available. """ for i, (pb, alloc) in enumerate(self.all_progressbars): if not alloc: self.all_progressbars[i] = (pb, True) return i return -1 def release_progress(self, progress_id): """Release an available progress bar and make it available for other uses. """ (pb, alloc) = self.all_progressbars[progress_id] self.all_progressbars[progress_id] = (pb, False) def get_progress_from_id(self, progress_id): """Return the gtk.ProgressBar widget corresponding to its id. """ return self.all_progressbars[progress_id][0] ##### Callbacks ####################################################### ## General def on_plide_top_delete_event(self, widget, event): return self.quit() def on_quit_activate(self, widget): self.quit() ## File Menu def on_new_activate(self, widget): self.add_untitled_tab(".pyplearn") def on_new_pyplearn_script_activate(self, widget): self.add_untitled_tab(".pyplearn") def on_new_py_script_activate(self, widget): self.add_untitled_tab(".py") def on_new_plearn_script_activate(self, widget): self.add_untitled_tab(".plearn") def on_new_text_file_activate(self, widget): self.add_untitled_tab("") def on_open_activate(self, widget): self.open_file() def on_save_activate(self, widget): if self.get_current_tab(): self.get_current_tab().on_save_activate() def on_save_as_activate(self, widget): if self.get_current_tab(): self.get_current_tab().save_as_file() def on_close_activate(self, widget): if self.get_current_tab(): self.get_current_tab().close_tab(widget) def on_browse_expdir_activate(self, widget): self.open_file(action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER) ## Edit menu def on_undo_activate(self, widget): self.get_current_tab().on_undo_activate() def on_redo_activate(self, widget): self.get_current_tab().on_redo_activate() def on_cut_activate(self, widget): self.get_current_tab().on_cut_activate() def on_copy_activate(self, widget): self.get_current_tab().on_copy_activate() def on_paste_activate(self, widget): self.get_current_tab().on_paste_activate() ## View menu def on_log_messages_toggled(self, menuitem): if menuitem.get_active(): self.log_show() else: self.log_hide() ## Help menu def on_about_activate(self, widget): version = injected.versionString().replace("(", "\n(") MessageBox("PLearn Integrated Development Environment Version " + self.PlideVersion, "Running on " + version + "\n" +\ "Copyright (c) 2006 by Nicolas Chapados", title = "About Plide") ## Toolbar def on_toolbutton_new_pyplearn_clicked(self, widget): self.add_untitled_tab(".pyplearn") def on_toolbutton_open_clicked(self, widget): self.open_file() def on_toolbutton_options_clicked(self, widget): """Display dialog box for establishing script options. """ tab = self.get_current_tab() if tab is not None: script = tab.get_text() name = tab.get_basename() script_dir = tab.get_directory() while True: # Loop to handle script reload options_holder = tab.get_options_holder() if not options_holder: ## When executing for the first time, run the script if self.pyplearn_parse(name, script) is None: return # Syntax errors in script options_holder = PyPLearnOptionsHolder( name, script, script_dir) tab.set_options_holder(options_holder) options_dialog = PyPLearnOptionsDialog(options_holder) result = options_dialog.run() if result == gtk.RESPONSE_OK: options_dialog.update_options_holder() options_dialog.destroy() if result == gtk.RESPONSE_REJECT: tab.set_options_holder(None) else: break def on_toolbutton_execute_clicked(self, widget): """Launch the execution of the pyplearn script, only if it's indeed such a script. """ tab = self.get_current_tab() if tab is not None: if type(tab) == PlideTabPyPLearn: script_name = tab.get_basename() script_code = tab.get_text() launch_dir = tab.get_directory() options_holder = tab.get_options_holder() if options_holder is not None: launch_dir = options_holder.launch_directory self.pyplearn_executor(script_name, script_code, launch_dir, options_holder) ### Help-related def on_help_activate(self, widget): self.help_show() def on_help_close_clicked(self, widget): self.help_close() ##### Tab Handling #################################################### def add_untitled_tab(self, extension): self.add_intelligent_tab("untitled%d%s" % (self.untitled_counter, extension), is_new=True) self.untitled_counter += 1 def add_intelligent_tab(self, filename, is_new=False): """Create a new tab 'intelligently' depending on the filename type or its extension. (If the file is new, rely on its extension only). """ filename = filename.rstrip(os.path.sep) extension = os.path.splitext(filename)[1] new_tab = None if extension == ".pyplearn" or extension == ".py": new_tab = PlideTabPyPLearn(self.w_plide_notebook, filename, is_new, self.all_plearn_classes) elif extension == ".pmat": new_tab = PlideTabPMat(self.w_plide_notebook, filename) elif os.path.isdir(filename): new_tab = PlideTabExpdir(self.w_plide_notebook, filename) elif os.path.exists(filename): new_tab = PlideTabFile(self.w_plide_notebook, filename, is_new) elif is_new: new_tab = PlideTabFile(self.w_plide_notebook, filename, is_new) else: MessageBox("File '%s' is of unrecognized type" % filename, type=gtk.MESSAGE_ERROR) self.status_display('') # Clear the status def open_file(self, action=gtk.FILE_CHOOSER_ACTION_OPEN): """Display a file chooser dialog and add a new tab based on selected file. """ chooser = gtk.FileChooserDialog( title="Open", action=action, buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK)) # Add file filters filter = gtk.FileFilter() filter.set_name("All files") filter.add_pattern("*") chooser.add_filter(filter) filter = gtk.FileFilter() filter.set_name("Experiment scripts") filter.add_pattern("*.plearn") filter.add_pattern("*.pyplearn") chooser.add_filter(filter) filter = gtk.FileFilter() filter.set_name("Data") filter.add_pattern("*.pmat") filter.add_pattern(".amat") chooser.add_filter(filter) chooser.set_default_response(gtk.RESPONSE_OK) chooser.set_current_folder(self.get_current_tab_directory()) response = chooser.run() ## Add all files selected by the user if response == gtk.RESPONSE_OK: filenames = chooser.get_filenames() for f in filenames: self.add_intelligent_tab(f, is_new=False) chooser.destroy() ##### PLearn execution ############################################ def pyplearn_executor(self, script_name, script_code, launch_directory, options_holder): """Execute a pyplearn script within an options context. Operations are as follows: 1. We execute the script one more time with the more recent text (may have changed since last time options were set) 2. We set the options corresponding each scoped object in their own class 3. We parse the manual command-line arguments 4. We transform the script to a .plearn 5. We grab a hold of the soon-to-be-created expdir 6. We hand this script off to PLearn for execution """ script_env = self.pyplearn_parse(script_name, script_code) if script_env is not None: if options_holder: options_holder.pyplearn_actualize() else: ## FIXME: Generate a brand-new expdir (minor hack) plargs.parse(["expdir=" + generateExpdir()]) expdir = plargs.expdir plearn_script = eval('str(PyPLearnScript( main() ))', script_env) message = 'Launching script <b>%s</b> in directory <b>%s</b>' % \ (script_name, launch_directory) self.status_display(message, has_markup=True) self.log_display(message, has_markup=True, log_clear=True) if self.w_dump_plearn_to_log.get_active(): self.log_display("Expdir is: %s\n.plearn is:\n%s" % (expdir, plearn_script)) request_id = self.work_queue.post_work_request( plearn_script, launch_directory, "pyplearn") print >> sys.stderr, "Caller executing request_id", request_id sys.stderr.flush() self.work_requests[request_id] = os.path.join( launch_directory, expdir) self.add_plearn_results_monitor(script_name, request_id) def add_plearn_results_monitor(self, script_name, request_id, interval=100): """Add a monitor callback to check for availability of PLearn results every 'interval' milliseconds. """ def callback(): completion_result = \ self.work_queue.work_request_completed(request_id) if completion_result is not None: gtk.threads_enter() # This is not a GTK+ callback self.reset_progress() (result_code, result_details) = completion_result ## If result_code is "", it means everything is OK. if result_code == "": message = "<b>%s</b> completed successfully" % script_name self.status_display(message, has_markup=True) self.log_display(message, has_markup=True) else: status_msg = "<b>%s</b> terminated due to errors" % script_name self.status_display(status_msg, has_markup=True) message = ('A fatal error of kind "%s" was encountered during ' +\ 'execution of script "%s".') % (result_code, script_name) details = "Details:\n" + result_details self.log_display(message + "\n" + details, has_markup=False) MessageBox(message, details, type=gtk.MESSAGE_ERROR) ## Done: don't call again, and add a new tab corresponding ## to the expdir. Check for expdir existence first, since ## some experiments don't necessarily leave an expdir around if os.path.isdir(self.work_requests[request_id]): self.add_intelligent_tab(self.work_requests[request_id]) gtk.threads_leave() return False else: ## Call again later return True ## Add the callback to the GTK list of callback callback_id = gobject.timeout_add(interval, callback) ##### PyPLearn Parse ############################################## def pyplearn_parse(self, script_name, script_code): """Ensure that a pyplearn script parses without error. If an error is encountered, a backtrace is emitted to the log aread and a message box is popped to indicate the error. Return None in this case. Return the script execution environment if no error is encountered. """ ## Implementation note: start by compiling the code to catch syntax ## errors in the script. Then execute with an 'exec' statement and ## separately catch execution errors. compiled_code = None try: self.cursor_hourglass() compiled_code = compile(script_code + '\n', script_name, 'exec') except ValueError: pass except SyntaxError, e: self.cursor_normal() (exc_type, exc_value, tb) = sys.exc_info() self.status_display( "Syntax error in script <b>%s</b>" % script_name, True) self.log_display(''.join( traceback.format_exception_only(exc_type, exc_value))) MessageBox('Syntax error in script "%s".' % script_name, "Python message: %s\nSee the log area for the detailed traceback." % \ str(exc_value), title = "PyPLearn Script Error", type=gtk.MESSAGE_ERROR) return None if compiled_code: script_env = {} try: exec compiled_code in script_env except: self.cursor_normal() (exc_type, exc_value, tb) = sys.exc_info() self.log_display(''.join(traceback.format_tb(tb))) self.status_display( "Exception during execution of script <b>%s</b>." % script_name, True) MessageBox('Script "%s" raised exception "%s: %s".' \ % (script_name, str(exc_type), str(exc_value)), "See the log area for the detailed traceback.", title = "PyPLearn Script Error", type=gtk.MESSAGE_ERROR) return None else: self.cursor_normal() self.status_display( "Script <b>%s</b> parsed successfully." % script_name, True) return script_env self.cursor_normal()
class PlideMain(GladeAppWindow): PlideVersion = "0.01" def __init__(self, streams_to_watch={}, *args, **kwargs): GladeAppWindow.__init__(self, gladeFile()) ## Forward injected to imported Plide modules PlideHelp.define_injected(injected) PlideTab.define_injected(injected) PyPLearnOptionsDialog.define_injected(injected, gladeFile) ## Initialize Members self.untitled_counter = 1 self.work_requests = {} # Request ids to expdir mapping self.all_plearn_classes = injected.getAllClassnames() ## Initialize Display self.setup_statusbar() self.log_filters = [re.compile("WARNING.*Scintilla.*PosChanged.*deprecated")] self.log_clear() self.log_hide() welcome_text = kwargs.get("welcome_text", "<b>Welcome to Plide %s!</b>" % self.PlideVersion) self.status_display(welcome_text, has_markup=True) self.setup_stdouterr_redirect(streams_to_watch) ## Set up help system injected.helpResourcesPath(helpResourcesPath()) self.help_viewer = PlideHelp(self) self.help_viewer.display_page("index.html") self.help_close() ## Prepare the work queue self.work_queue = PLearnWorkQueue() def quit(self): ## Minor hack: the main-thread loop is terminated by receiving a ## 'script' whose contents is Quit(). First close all tabs and ## ensure that we stop the process if some tabs won't be closed. n = self.w_plide_notebook.get_n_pages() for i in range(n - 1, -1, -1): tab = self.get_nth_tab(i) if not tab.close_tab(): return True # Stop close process if cannot close tab print >> raw_stderr, "Quit message received" raw_stderr.flush() self.work_queue.post_work_request("Quit()", "", "") GladeAppWindow.quit(self) def help_close(self): """Close the help pane. """ self.w_help_frame.hide() self.w_help_frame.set_no_show_all(True) def help_show(self): """Open the help pane. Bring up context-sensitive help if there is a valid context in the current tab. """ self.w_help_frame.set_no_show_all(False) self.w_help_frame.show() curtab = self.get_current_tab() if curtab: help_context = curtab.get_help_candidate() if help_context: self.help_viewer.display_page(help_context) def setup_stdouterr_redirect(self, streams_to_watch={}): """Redirect standard output and error to be sent to the log pane instead of the console. """ ## Redirect standard output and standard error to display to the ## log pane area. Keep around old stdout/stderr in order to ## display debugging messages. They are called, respectively, ## raw_stdout and raw_stderr (Python file objects) global raw_stdout, raw_stderr old_stdout_fd = os.dup(sys.stdout.fileno()) old_stderr_fd = os.dup(sys.stderr.fileno()) raw_stdout = os.fdopen(old_stdout_fd, "w") raw_stderr = os.fdopen(old_stderr_fd, "w") print >>sys.stderr, "Original stderr" print >> raw_stderr, "Redirected stderr" sys.stderr.flush() raw_stderr.flush() (self.stdout_read, self.stdout_write) = os.pipe() (self.stderr_read, self.stderr_write) = os.pipe() os.dup2(self.stdout_write, sys.stdout.fileno()) os.dup2(self.stderr_write, sys.stderr.fileno()) out_flags = fcntl.fcntl(self.stdout_read, fcntl.F_GETFL) err_flags = fcntl.fcntl(self.stderr_read, fcntl.F_GETFL) fcntl.fcntl(self.stdout_read, fcntl.F_SETFL, out_flags | os.O_NONBLOCK) fcntl.fcntl(self.stderr_read, fcntl.F_SETFL, err_flags | os.O_NONBLOCK) streams_to_watch.update({self.stdout_read: "stdout", self.stderr_read: "stderr"}) def callback(fd, cb_condition): # print >>raw_stderr, "log_updater: got stuff on fd", fd, \ # "with condition", cb_condition raw_stderr.flush() data = os.read(fd, 65536) kind = streams_to_watch.get(fd, "") self.log_display(data, kind) return True # Ensure it's called again! for s in streams_to_watch: gobject.io_add_watch(s, gobject.IO_IN, callback) def setup_statusbar(self): """Arrange the status bar area to contain both a status line and a progress bar. """ ## The GTK StatusBar widget is a pain to use. Our statusbar is an ## hbox packed at the bottom of main_vbox, and containing: ## ## - A frame with a status label ## - A progress bar self.w_statusframe = gtk.Frame() self.w_statusframe.set_shadow_type(gtk.SHADOW_IN) self.status_display(" ") # Establish proper height self.w_statusframe.show() self.w_progressbar = gtk.ProgressBar() self.w_progressbar.set_ellipsize(pango.ELLIPSIZE_END) self.w_progressbar.show() ## This gives a list of the "order" in which progress bars should ## be allocated as well as whether each one has been allocated self.all_progressbars = [(self.w_progressbar, False)] self.w_statusbar = gtk.Table(rows=1, columns=4) self.w_statusbar.attach( self.w_statusframe, left_attach=0, right_attach=3, top_attach=0, bottom_attach=1, xoptions=gtk.EXPAND | gtk.FILL, yoptions=gtk.EXPAND | gtk.FILL, xpadding=0, ypadding=0, ) self.w_statusbar.attach( self.w_progressbar, left_attach=3, right_attach=4, top_attach=0, bottom_attach=1, xoptions=gtk.EXPAND | gtk.FILL, yoptions=gtk.EXPAND | gtk.FILL, xpadding=2, ypadding=2, ) self.w_statusbar.show() self.w_main_vbox.pack_start(self.w_statusbar, expand=False, fill=False) def log_clear(self): """The log is a TreeView widget that contains 3 columns: (1) number, (2) kind, (3) log entry itself. This sets up a new log or clears an existing log. """ self.log_liststore = gtk.ListStore(int, str, str, int) ## Create individual columns self.log_columns = [gtk.TreeViewColumn(x) for x in ["No.", "Kind", "Message"]] ## Create cell renderers for displaying the contents self.log_cells = [gtk.CellRendererText() for i in xrange(len(self.log_columns))] for i, (column, cell) in enumerate(zip(self.log_columns, self.log_cells)): column.pack_start(cell, True) column.add_attribute(cell, "text", i) column.set_sort_column_id(i) ## Last column (message) is a bit tricky: we either set the ## attribute 'text' or 'markup' depending on the setting of the ## fourth model column def message_format_func(treeviewcolumn, cell, model, iter): message = model.get(iter, 2)[0] markup = model.get(iter, 3)[0] cell.set_property("family-set", True) if markup: cell.set_property("markup", message) cell.set_property("family", "Helvetica") else: cell.set_property("text", message) cell.set_property("family", "Monospace") self.log_columns[2].set_cell_data_func(self.log_cells[2], message_format_func) ## Create the TreeView using underlying liststore model and append columns ## and make it searchable on the message self.log_treeview = gtk.TreeView(self.log_liststore) for column in self.log_columns: self.log_treeview.append_column(column) self.log_treeview.set_search_column(2) self.log_treeview.set_rules_hint(True) container_remove_children(self.w_plearn_log_scroller) self.w_plearn_log_scroller.add(self.log_treeview) self.log_treeview.show_all() ## Reset the next log entry self.log_next_number = 1 def log_hide(self): self.w_log_messages_menuitem.set_active(False) self.w_plearn_log_scroller.hide() self.w_plearn_log_scroller.set_no_show_all(True) def log_show(self): self.w_log_messages_menuitem.set_active(True) self.w_plearn_log_scroller.set_no_show_all(False) self.w_plearn_log_scroller.show() def log_display(self, message, kind="", has_markup=False, log_clear=False): """Append the given message to the log area of the main window. Thread-safe. """ if log_clear: self.log_clear() ## If 'data' matches any regular expression in log_filter, skip ## this message. This is mostly a hack to get around displaying ## known warnings from the Scintilla editor for regex in self.log_filters: if regex.search(message): return row = [self.log_next_number, kind.rstrip(), message.rstrip(), has_markup] self.log_show() self.log_liststore.append(row) self.log_next_number += 1 def status_display(self, message, has_markup=False): """Display the given message in the status bar area of the main window. Thread-safe. """ label = gtk.Label(message) if has_markup: label.set_markup(message) label.set_line_wrap(False) label.set_alignment(0, 0) label.set_padding(2, 2) label.set_single_line_mode(True) label.set_ellipsize(pango.ELLIPSIZE_MIDDLE) label.show() container_remove_children(self.w_statusframe) self.w_statusframe.add(label) def cursor_hourglass(self, unsensitize=True): """Make the cursor an hourglass for the main window. In addition, if the argument unsensitize is True, most entry will be disabled for the window. """ self.w_root.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH)) if unsensitize: self.w_root.set_sensitive(False) def cursor_normal(self, sensitize=True): """Take back the cursor to normal form for the main window. """ self.w_root.window.set_cursor(None) # Set back to parent window cursor if sensitize: self.w_root.set_sensitive(True) def get_nth_tab(self, n): """Return the PlideTab object corresponding to the n-th tab. Return None if there is no such PlideTab. """ notebook_page = self.w_plide_notebook.get_nth_page(n) if notebook_page: return notebook_page.plide_tab_object else: return None def get_current_tab(self): """Return the PlideTab object that's currently selected in the notebook. Return None if none... """ cur_page = self.w_plide_notebook.get_current_page() if cur_page >= 0: return self.w_plide_notebook.get_nth_page(cur_page).plide_tab_object else: return None def get_current_tab_directory(self): """Return the directory associated with the current PlideTab, or '.' if there is no current tab. """ cur_tab = self.get_current_tab() dir = None if cur_tab is not None: dir = cur_tab.get_directory() return dir or "." ##### Progress Bar Handling ########################################### def reset_progress(self): """Bring back the all progress bars to an 'available' state, erase their containing text, and bring the fraction to zero. """ for i, (pb, alloc) in enumerate(self.all_progressbars): pb.set_text("") pb.set_fraction(0.0) self.all_progressbars[i] = (pb, False) def allocate_progress(self): """Return the ID of an available progress bar, or -1 if none is available. """ for i, (pb, alloc) in enumerate(self.all_progressbars): if not alloc: self.all_progressbars[i] = (pb, True) return i return -1 def release_progress(self, progress_id): """Release an available progress bar and make it available for other uses. """ (pb, alloc) = self.all_progressbars[progress_id] self.all_progressbars[progress_id] = (pb, False) def get_progress_from_id(self, progress_id): """Return the gtk.ProgressBar widget corresponding to its id. """ return self.all_progressbars[progress_id][0] ##### Callbacks ####################################################### ## General def on_plide_top_delete_event(self, widget, event): return self.quit() def on_quit_activate(self, widget): self.quit() ## File Menu def on_new_activate(self, widget): self.add_untitled_tab(".pyplearn") def on_new_pyplearn_script_activate(self, widget): self.add_untitled_tab(".pyplearn") def on_new_py_script_activate(self, widget): self.add_untitled_tab(".py") def on_new_plearn_script_activate(self, widget): self.add_untitled_tab(".plearn") def on_new_text_file_activate(self, widget): self.add_untitled_tab("") def on_open_activate(self, widget): self.open_file() def on_save_activate(self, widget): if self.get_current_tab(): self.get_current_tab().on_save_activate() def on_save_as_activate(self, widget): if self.get_current_tab(): self.get_current_tab().save_as_file() def on_close_activate(self, widget): if self.get_current_tab(): self.get_current_tab().close_tab(widget) def on_browse_expdir_activate(self, widget): self.open_file(action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER) ## Edit menu def on_undo_activate(self, widget): self.get_current_tab().on_undo_activate() def on_redo_activate(self, widget): self.get_current_tab().on_redo_activate() def on_cut_activate(self, widget): self.get_current_tab().on_cut_activate() def on_copy_activate(self, widget): self.get_current_tab().on_copy_activate() def on_paste_activate(self, widget): self.get_current_tab().on_paste_activate() ## View menu def on_log_messages_toggled(self, menuitem): if menuitem.get_active(): self.log_show() else: self.log_hide() ## Help menu def on_about_activate(self, widget): version = injected.versionString().replace("(", "\n(") MessageBox( "PLearn Integrated Development Environment Version " + self.PlideVersion, "Running on " + version + "\n" + "Copyright (c) 2006 by Nicolas Chapados", title="About Plide", ) ## Toolbar def on_toolbutton_new_pyplearn_clicked(self, widget): self.add_untitled_tab(".pyplearn") def on_toolbutton_open_clicked(self, widget): self.open_file() def on_toolbutton_options_clicked(self, widget): """Display dialog box for establishing script options. """ tab = self.get_current_tab() if tab is not None: script = tab.get_text() name = tab.get_basename() script_dir = tab.get_directory() while True: # Loop to handle script reload options_holder = tab.get_options_holder() if not options_holder: ## When executing for the first time, run the script if self.pyplearn_parse(name, script) is None: return # Syntax errors in script options_holder = PyPLearnOptionsHolder(name, script, script_dir) tab.set_options_holder(options_holder) options_dialog = PyPLearnOptionsDialog(options_holder) result = options_dialog.run() if result == gtk.RESPONSE_OK: options_dialog.update_options_holder() options_dialog.destroy() if result == gtk.RESPONSE_REJECT: tab.set_options_holder(None) else: break def on_toolbutton_execute_clicked(self, widget): """Launch the execution of the pyplearn script, only if it's indeed such a script. """ tab = self.get_current_tab() if tab is not None: if type(tab) == PlideTabPyPLearn: script_name = tab.get_basename() script_code = tab.get_text() launch_dir = tab.get_directory() options_holder = tab.get_options_holder() if options_holder is not None: launch_dir = options_holder.launch_directory self.pyplearn_executor(script_name, script_code, launch_dir, options_holder) ### Help-related def on_help_activate(self, widget): self.help_show() def on_help_close_clicked(self, widget): self.help_close() ##### Tab Handling #################################################### def add_untitled_tab(self, extension): self.add_intelligent_tab("untitled%d%s" % (self.untitled_counter, extension), is_new=True) self.untitled_counter += 1 def add_intelligent_tab(self, filename, is_new=False): """Create a new tab 'intelligently' depending on the filename type or its extension. (If the file is new, rely on its extension only). """ filename = filename.rstrip(os.path.sep) extension = os.path.splitext(filename)[1] new_tab = None if extension == ".pyplearn" or extension == ".py": new_tab = PlideTabPyPLearn(self.w_plide_notebook, filename, is_new, self.all_plearn_classes) elif extension == ".pmat": new_tab = PlideTabPMat(self.w_plide_notebook, filename) elif os.path.isdir(filename): new_tab = PlideTabExpdir(self.w_plide_notebook, filename) elif os.path.exists(filename): new_tab = PlideTabFile(self.w_plide_notebook, filename, is_new) elif is_new: new_tab = PlideTabFile(self.w_plide_notebook, filename, is_new) else: MessageBox("File '%s' is of unrecognized type" % filename, type=gtk.MESSAGE_ERROR) self.status_display("") # Clear the status def open_file(self, action=gtk.FILE_CHOOSER_ACTION_OPEN): """Display a file chooser dialog and add a new tab based on selected file. """ chooser = gtk.FileChooserDialog( title="Open", action=action, buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK), ) # Add file filters filter = gtk.FileFilter() filter.set_name("All files") filter.add_pattern("*") chooser.add_filter(filter) filter = gtk.FileFilter() filter.set_name("Experiment scripts") filter.add_pattern("*.plearn") filter.add_pattern("*.pyplearn") chooser.add_filter(filter) filter = gtk.FileFilter() filter.set_name("Data") filter.add_pattern("*.pmat") filter.add_pattern(".amat") chooser.add_filter(filter) chooser.set_default_response(gtk.RESPONSE_OK) chooser.set_current_folder(self.get_current_tab_directory()) response = chooser.run() ## Add all files selected by the user if response == gtk.RESPONSE_OK: filenames = chooser.get_filenames() for f in filenames: self.add_intelligent_tab(f, is_new=False) chooser.destroy() ##### PLearn execution ############################################ def pyplearn_executor(self, script_name, script_code, launch_directory, options_holder): """Execute a pyplearn script within an options context. Operations are as follows: 1. We execute the script one more time with the more recent text (may have changed since last time options were set) 2. We set the options corresponding each scoped object in their own class 3. We parse the manual command-line arguments 4. We transform the script to a .plearn 5. We grab a hold of the soon-to-be-created expdir 6. We hand this script off to PLearn for execution """ script_env = self.pyplearn_parse(script_name, script_code) if script_env is not None: if options_holder: options_holder.pyplearn_actualize() else: ## FIXME: Generate a brand-new expdir (minor hack) plargs.parse(["expdir=" + generateExpdir()]) expdir = plargs.expdir plearn_script = eval("str(PyPLearnScript( main() ))", script_env) message = "Launching script <b>%s</b> in directory <b>%s</b>" % (script_name, launch_directory) self.status_display(message, has_markup=True) self.log_display(message, has_markup=True, log_clear=True) if self.w_dump_plearn_to_log.get_active(): self.log_display("Expdir is: %s\n.plearn is:\n%s" % (expdir, plearn_script)) request_id = self.work_queue.post_work_request(plearn_script, launch_directory, "pyplearn") print >>sys.stderr, "Caller executing request_id", request_id sys.stderr.flush() self.work_requests[request_id] = os.path.join(launch_directory, expdir) self.add_plearn_results_monitor(script_name, request_id) def add_plearn_results_monitor(self, script_name, request_id, interval=100): """Add a monitor callback to check for availability of PLearn results every 'interval' milliseconds. """ def callback(): completion_result = self.work_queue.work_request_completed(request_id) if completion_result is not None: gtk.threads_enter() # This is not a GTK+ callback self.reset_progress() (result_code, result_details) = completion_result ## If result_code is "", it means everything is OK. if result_code == "": message = "<b>%s</b> completed successfully" % script_name self.status_display(message, has_markup=True) self.log_display(message, has_markup=True) else: status_msg = "<b>%s</b> terminated due to errors" % script_name self.status_display(status_msg, has_markup=True) message = ('A fatal error of kind "%s" was encountered during ' + 'execution of script "%s".') % ( result_code, script_name, ) details = "Details:\n" + result_details self.log_display(message + "\n" + details, has_markup=False) MessageBox(message, details, type=gtk.MESSAGE_ERROR) ## Done: don't call again, and add a new tab corresponding ## to the expdir. Check for expdir existence first, since ## some experiments don't necessarily leave an expdir around if os.path.isdir(self.work_requests[request_id]): self.add_intelligent_tab(self.work_requests[request_id]) gtk.threads_leave() return False else: ## Call again later return True ## Add the callback to the GTK list of callback callback_id = gobject.timeout_add(interval, callback) ##### PyPLearn Parse ############################################## def pyplearn_parse(self, script_name, script_code): """Ensure that a pyplearn script parses without error. If an error is encountered, a backtrace is emitted to the log aread and a message box is popped to indicate the error. Return None in this case. Return the script execution environment if no error is encountered. """ ## Implementation note: start by compiling the code to catch syntax ## errors in the script. Then execute with an 'exec' statement and ## separately catch execution errors. compiled_code = None try: self.cursor_hourglass() compiled_code = compile(script_code + "\n", script_name, "exec") except ValueError: pass except SyntaxError, e: self.cursor_normal() (exc_type, exc_value, tb) = sys.exc_info() self.status_display("Syntax error in script <b>%s</b>" % script_name, True) self.log_display("".join(traceback.format_exception_only(exc_type, exc_value))) MessageBox( 'Syntax error in script "%s".' % script_name, "Python message: %s\nSee the log area for the detailed traceback." % str(exc_value), title="PyPLearn Script Error", type=gtk.MESSAGE_ERROR, ) return None if compiled_code: script_env = {} try: exec compiled_code in script_env except: self.cursor_normal() (exc_type, exc_value, tb) = sys.exc_info() self.log_display("".join(traceback.format_tb(tb))) self.status_display("Exception during execution of script <b>%s</b>." % script_name, True) MessageBox( 'Script "%s" raised exception "%s: %s".' % (script_name, str(exc_type), str(exc_value)), "See the log area for the detailed traceback.", title="PyPLearn Script Error", type=gtk.MESSAGE_ERROR, ) return None else: self.cursor_normal() self.status_display("Script <b>%s</b> parsed successfully." % script_name, True) return script_env self.cursor_normal()