class GtkApplication(application.Application): """ Application is a wrapper window for the ProjectTreeView which holds the information on the current project. It adds a menu bar and a toolbar as described in the two attributes window_actions and ui_string of this module. Furthermore, it provides basic functions to work with the project. """ def init(self): self.window = AppWindow() self._clipboard = gtk.Clipboard() # not implemented yet self._current_plot = None self.path.icon_dir = os.path.join(self.path.base_dir, 'Gtk','Icons') self.register_stock() self.tools = {} self.init_plugins() self.init_tools() def register_stock(self): """ Register png images from the GTK icon directory as stock icons. """ uihelper.register_stock_icons(self.path.icon_dir, prefix='sloppy-') # register stock items items = [('sloppy-rename', '_Rename', 0, 0, None)] aliases = [('sloppy-rename', 'gtk-edit')] gtk.stock_add(items) factory = gtk.IconFactory() factory.add_default() style = self.window.get_style() for new_stock, alias in aliases: icon_set = style.lookup_icon_set(alias) factory.add(new_stock, icon_set) # Plugin Handling ------------------------------------------------------ def init_plugins(self): for plugin in self.plugins.itervalues(): if hasattr(plugin, 'gtk_init'): print "GTK init the plugin." plugin.gtk_init(self) def register_actions(self, action_wrappers): " Helper function for plugins. " # create action group ag = gtk.ActionGroup("Plugin") for item in action_wrappers: ag.add_action(item.action) self.window.uimanager.insert_action_group(ag, -1) # construct plugin ui plugin_ui = '<popup name="popup_dataset">' for item in action_wrappers: plugin_ui += '<menuitem action="%s"/>' % item.name plugin_ui += '</popup>' # merge plugin ui merge_id = self.window.uimanager.add_ui_from_string(plugin_ui) def register_tool(self, klass, name=None): " Helper functions for plugins. " if name is None: name = klass.__name__ if self.tools.has_key(name): logger.error("Tool %s is already registered." % name) return self.tools[name] = klass # Tool Handling -------------------------------------------------------- def init_tools(self): # TODO: # we register these two tools here. # this should be done somewhere else. # one solution would be to source the corresponding # module from the application, like it is done with # the plugins self.tools['LayerTool'] = tools.LayerTool self.tools['LabelsTool'] = tools.LabelsTool self.tools_read_config() self.sig_connect("write-config", self.tools_write_config) def tools_read_config(self): # TODO: maybe put the toolbox into the application? toolbox = self.window.toolbox eToolbox = self.eConfig.find('Toolbox') if eToolbox is None: # basic setup (for now) book = dock.Dockbook() toolbox.dock.add(book) lt = tools.LabelsTool() book.add(lt) lt = tools.LayerTool() book.add(lt) return for eDockbook in eToolbox.findall('Dock/Dockbook'): book = dock.Dockbook() toolbox.dock.add(book) for eDockable in eDockbook.findall('Dockable'): try: tool = self.tools[eDockable.text]() book.add(tool) # TODO: size information is not used except: logger.error("Could not init tool dock '%s', unknown class." % eDockable.text) def tools_write_config(self, app): toolbox = self.window.toolbox eToolbox = app.eConfig.find("Toolbox") if eToolbox is None: eToolbox = SubElement(app.eConfig, "Toolbox") else: eToolbox.clear() # get information about dockables/dockbooks eDock = SubElement(eToolbox, "Dock") for dockbook in toolbox.dock.dockbooks: eDockbook = SubElement(eDock, "Dockbook") for dockable in dockbook.get_children(): eDockable = SubElement(eDockbook, "Dockable") width, height = dockable.size_request() eDockable.attrib['width'] = str(width) eDockable.attrib['height'] = str(height) eDockable.text = dockable.__class__.__name__ # ---------------------------------------------------------------------- # Project def set_project(self, project, confirm=True): """ Assign the given project to the Application. Returns the new current project. @param confirm: Ask user for permission to close the project (unless there were no changes). """ if self._project is not None: if self._project.journal.can_undo() and confirm is True: msg = \ """ You are about to close the Project. Do you want to save your changes ? """ dialog = gtk.MessageDialog(type = gtk.MESSAGE_QUESTION, message_format = msg) dialog.add_button("_Don't Save", gtk.RESPONSE_NO) btn_default = dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) dialog.add_button(gtk.STOCK_SAVE, gtk.RESPONSE_YES) btn_default.grab_focus() response = dialog.run() dialog.destroy() if response == gtk.RESPONSE_YES: # yes = yes, save the file before closing self.save_project() elif response == gtk.RESPONSE_NO: # no = no, proceed with closing pass else: # everything else -> abort action raise error.UserCancel # set new project application.Application.set_project(self, project) self.window.treeview.set_project(project) # assign project label to window title if project: title = project.filename or "<unnamed project>" else: title = "(no project)" self.window.set_title(os.path.basename(title)) if project is not None: project.journal.on_change = self.window._refresh_undo_redo self.window._refresh_undo_redo() self.window._refresh_recentfiles() return self._project def load_project(self, filename=None): """ Open a FileChooserDialog and let the user pick a new project to be loaded. The old project is replaced. """ if filename is None: # TODO # maybe we could have application.load_project # just request the file name and we simply # create a method for this dialog. # create chooser object chooser = gtk.FileChooserDialog( title="Open project", action=gtk.FILE_CHOOSER_ACTION_OPEN, buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK)) chooser.set_default_response(gtk.RESPONSE_OK) chooser.set_current_folder(os.path.abspath(self.path.current_dir)) chooser.set_select_multiple(False) filter = gtk.FileFilter() filter.set_name("All files") filter.add_pattern("*") chooser.add_filter(filter) filter = gtk.FileFilter() filter.set_name("Sloppyplot Project files") filter.add_pattern("*.spj") filter.add_pattern("*.SPJ") chooser.add_filter(filter) chooser.set_filter(filter) # default filter shortcut_folder = self.path.example_dir if os.path.exists(shortcut_folder): chooser.add_shortcut_folder( shortcut_folder ) response = chooser.run() if response == gtk.RESPONSE_OK: filename = chooser.get_filename() else: filename = None chooser.destroy() if filename is not None: application.Application.load_project(self, filename) def save_project_as(self, filename = None): """ Save project under another filename. """ pj = self._check_project() if not filename: # allow user to choose a filename chooser = gtk.FileChooserDialog( title="Save Project As", action=gtk.FILE_CHOOSER_ACTION_SAVE, buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, gtk.RESPONSE_OK)) chooser.set_default_response(gtk.RESPONSE_OK) chooser.set_current_folder(os.path.abspath(self.path.example_dir)) chooser.set_select_multiple(False) chooser.set_filename(os.path.abspath(pj.filename or "unnamed.spj")) major,minor,micro = gtk.pygtk_version if major > 1 and minor >= 8: chooser.set_do_overwrite_confirmation(True) filter = gtk.FileFilter() filter.set_name("All files") filter.add_pattern("*") chooser.add_filter(filter) chooser.set_filter(filter) # default filter shortcut_folder = self.path.example_dir if os.path.exists(shortcut_folder): chooser.add_shortcut_folder(shortcut_folder) response = chooser.run() try: if response == gtk.RESPONSE_OK: filename = chooser.get_filename() else: raise error.UserCancel finally: chooser.destroy() # add extension if not yet there if filename.lower().endswith('.spj') is False: filename = filename + ".spj" self._project.filename = filename self.window.set_title(os.path.basename(self._project.filename)) save_project(self._project) self._project.journal.clear() self.recent_files.insert(0, os.path.abspath(filename)) self.sig_emit('update-recent-files') def quit(self): """ Quit Application and gtk main loop. """ try: application.Application.quit(self) gtk.main_quit() except error.UserCancel: return # ---------------------------------------------------------------------- # Callbacks # # delete-event/destroy/quit application def _cb_quit_application(self, action): self.quit() def _cb_project_close(self,widget=None): self.set_project(None) def _cb_project_open(self,widget): self.load_project() def _cb_project_save(self,widget): self.save_project() def _cb_project_save_as(self,widget): self.save_project_as() def _cb_project_new(self,widget): self.new_project() #---------------------------------------------------------------------- def _cb_edit(self, action): plots, datasets = self.window.treeview.get_selected_plds() if len(plots) > 0: self.edit_layer(plots[0]) else: for dataset in datasets: self.edit_dataset(dataset) # --- VIEW --------------------------------------------------------------------- def edit_dataset(self, ds, undolist=[]): assert( isinstance(ds, Dataset) ) # reuse old DatasetWindow or create new one window = self.window.subwindow_match( (lambda win: isinstance(win, DatasetWindow) and (win.dataset == ds))) \ or \ self.window.subwindow_add( DatasetWindow(self._project, ds) ) window.present() def edit_layer(self, plot, layer=None, current_page=None): """ Edit the given layer of the given plot. If no layer is given, the method tries to edit the first Layer. If there is no Layer in the plot, an error will logged. TODO: current_page. """ if layer is None: if len(plot.layers) > 0: layer = plot.layers[0] else: logger.error("The plot to be edited has not even a single layer!") return win = LayerWindow(plot, layer, current_page=current_page) win.set_modal(True) win.present() #---------------------------------------------------------------------- def _cb_new_plot(self,widget): pj = self._check_project() plot = new_lineplot2d(key='empty plot') pj.add_plots([plot]) # --- PLOT --------------------------------------------------------------------- def _cb_plot(self,widget): self.plot_current_objects() def _cb_plot_gnuplot(self,widget): self.plot_current_objects('gnuplot/x11') def _cb_plot_matplotlib(self,widget): self.plot_current_objects('matplotlib') def plot(self,plot,backend_name='matplotlib'): logger.debug("Backend name is %s" % backend_name) if backend_name == 'gnuplot/x11': backend = self.project.request_backend('gnuplot/x11', plot=plot) backend.draw() return # TODO: open Gnuplot window # add_subwindow # match_subwindow # request_subwindow # evtl. schon als Toolbox ??? # window = self.window.subwindow_match( \ # (lambda win: isinstance(win, GnuplotWindow) and \ # win.project == self.project and \ # win.plot == plot) ) # if window is None: # window = GnuplotWindow(self, project=self.project, plot=plot) # self.window.subwindow_add( window ) elif backend_name == 'matplotlib': # # as widget # widget = self.window.find_plotwidget(project=self.project,plot=plot) # if widget is None: # widget = MatplotlibWidget(self, project=self.project, plot=plot) # self.window.add_plotwidget(widget) # widget.show() # as window window = self.window.subwindow_match( \ (lambda win: isinstance(win, mpl.MatplotlibWindow) \ and win.get_project() == self.project \ and win.get_plot() == plot) ) if window is None: window = mpl.MatplotlibWindow(project=self.project, plot=plot) ##window.set_transient_for(self.window) self.window.subwindow_add(window) # TESTING # Of course, the toolbox might decide to not use the backend, # e.g. if there is an 'auto' button and it is not pressed. So I will # need to change that then. window.connect("focus-in-event", (lambda a,b: self.window.toolbox.set_backend(window.get_backend()))) window.show() window.present() else: raise RuntimeError("Unknown backend %s" % backend_name) def plot_current_objects(self, backend_name='matplotlib', undolist=[]): (plots, datasets) = self.window.treeview.get_selected_plds() for plot in plots: self.plot(plot, backend_name) def on_action_export_via_gnuplot(self, action): plots = self.window.treeview.get_selected_plots() if len(plots) > 0: self.plot_postscript(self.project, plots[0]) def plot_postscript(app, project, plot): # # request filename # filename = PostscriptTerminal.build_filename('ps', project, plot) chooser = gtk.FileChooserDialog( title="PostScript Export", action=gtk.FILE_CHOOSER_ACTION_SAVE, buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, gtk.RESPONSE_OK)) chooser.set_default_response(gtk.RESPONSE_OK) chooser.set_select_multiple(False) chooser.set_current_folder(os.path.abspath(os.path.dirname(filename))) chooser.set_current_name(os.path.basename(filename)) filter = gtk.FileFilter() filter.set_name("All files") filter.add_pattern("*") chooser.add_filter(filter) filter = gtk.FileFilter() filter.set_name("Postscript (.ps; .eps)") filter.add_pattern("*.ps") filter.add_pattern("*.eps") chooser.add_filter(filter) chooser.set_filter(filter) # default filter response = chooser.run() try: if response == gtk.RESPONSE_OK: filename = chooser.get_filename() else: raise error.UserCancel finally: chooser.destroy() # # request export options # dialog = OptionsDialog(PostscriptTerminal(), title="Options Postscript Export", parent=app.window) #dialog.set_size_request(320,520) # determine requested postscript mode (ps or eps) from extension path, ext = os.path.splitext(filename) ext = ext.lower() if ext == '.eps': dialog.owner.mode = 'eps' elif ext == '.ps': dialog.owner.mode = 'landscape' try: result = dialog.run() if result == gtk.RESPONSE_ACCEPT: dialog.check_out() else: return terminal = dialog.owner finally: dialog.destroy() # # now check if mode and filename extension match # def fix_filename(filename, mode): msg = "The postscript mode you selected (%s) does not match the given filename extension (%s). Do you want to adjust the filename to match the mode? " % (mode, os.path.splitext(filename)[1]) dialog = gtk.MessageDialog(type = gtk.MESSAGE_QUESTION, message_format = msg) dialog.add_button("Keep Filename", gtk.RESPONSE_NO) btn_default = dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) dialog.add_button("Adjust Filename", gtk.RESPONSE_YES) btn_default.grab_focus() response = dialog.run() dialog.destroy() if response == gtk.RESPONSE_YES: # yes = yes, adjust filename if mode == '.eps': new_ext = '.eps' else: new_ext = '.ps' path, ext = os.path.splitext(filename) return path + new_ext elif response == gtk.RESPONSE_NO: # no = no, keep filename return filename else: # everything else -> abort action raise error.UserCancel if (terminal.mode == 'eps' and ext != '.eps') or \ (terminal.mode != 'eps' and ext != '.ps'): filename = fix_filename(filename, terminal.mode) # # construct backend for output # backend = globals.BackendRegistry['gnuplot']( project=project, plot=plot, filename=filename, terminal=terminal) try: backend.draw() finally: backend.disconnect() # --- DATASET HANDLING ------------------------------------------------- def _cb_import_dataset(self, action): pj = self._check_project() # allow user to choose files for import chooser = gtk.FileChooserDialog( title="Import Dataset from file", action=gtk.FILE_CHOOSER_ACTION_OPEN, buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK)) chooser.set_default_response(gtk.RESPONSE_OK) chooser.set_current_folder(os.path.abspath(self.path.current_dir)) chooser.set_select_multiple(True) filter_keys = {} # used for reference later on # add 'All Files' filter blurb_all_files = "All Files" filter = gtk.FileFilter() filter.set_name(blurb_all_files) filter.add_pattern("*") chooser.add_filter(filter) chooser.set_filter(filter) filter_keys[blurb_all_files] = 'auto' # default if nothing else specified # # create file filters # # Each item in importer_registry is a class derived from # dataio.Importer. By using IOTemplate objects we can # customize the default values for these templates. for (key, template) in globals.import_templates.iteritems(): ext_list = template.extensions.split(',') if len(ext_list) == 0: continue extensions = ';'.join(map(lambda ext: '*.'+ext, ext_list)) blurb = "%s (%s)" % (template.blurb, extensions) filter = gtk.FileFilter() filter.set_name(blurb) for ext in ext_list: filter.add_pattern("*."+ext.lower()) filter.add_pattern("*."+ext.upper()) chooser.add_filter(filter) filter_keys[blurb] = key # add shortcut folder to example path, if such exists shortcut_folder = self.path.data_dir if os.path.exists(shortcut_folder): chooser.add_shortcut_folder(shortcut_folder) # # prepare extra widget # # The custom widget `combobox` lets the user choose, # which ImporterTemplate is to be used. # model: key, blurb model = gtk.ListStore(str, str) # add 'Same as Filter' as first choice, then add all importers model.append( (None, "Auto") ) for key, template in globals.import_templates.iteritems(): model.append( (key, template.blurb) ) combobox = gtk.ComboBox(model) cell = gtk.CellRendererText() combobox.pack_start(cell, True) combobox.add_attribute(cell, 'text', 1) combobox.set_active(0) combobox.show() label = gtk.Label("Use Template: ") label.show() hbox = gtk.HBox() hbox.pack_end(combobox,False) hbox.pack_end(label,False) hbox.show() vbox = gtk.VBox() vbox.pack_start(hbox,False) #vbox.pack_start(pbar,False) vbox.show() chooser.set_extra_widget(vbox) # # run dialog # try: response = chooser.run() if response == gtk.RESPONSE_OK: filenames = chooser.get_filenames() if len(filenames) == 0: return template_key = model[combobox.get_active()][0] if template_key is None: # auto f = chooser.get_filter() template_key = filter_keys[f.get_name()] else: return finally: chooser.destroy() self.do_import(pj, filenames, template_key) def do_import(self, project, filenames, template_key=None): # try to determine template key if it is not given if template_key is None or template_key=='auto': matches = dataio.importer_template_from_filename(filenames[0]) if len(matches) > 0: template_key = matches[0] else: template_key = 'ASCII' # # Request import options # # Note that if 'skip_option' is set in the template, then # there will be no user options dialog. if globals.import_templates[template_key].skip_options is False: dialog = import_dialog.ImportOptions(template_key, previewfile=filenames[0]) try: result = dialog.run() if result == gtk.RESPONSE_ACCEPT: # save template as 'recently used' template = dataio.IOTemplate() template.defaults = dialog.importer.get_values(include=dialog.importer.public_props) template.blurb = "Recently used Template" template.importer_key = dialog.template.importer_key template.write_config = True template.immutable = True globals.import_templates['recently used'] = template else: return finally: dialog.destroy() else: template = template_key self.core.import_datasets(project, filenames, template) def _cb_new_dataset(self,widget): """ Create a new dataset and switch to its editing window. """ pj = self._check_project() ds = pj.new_dataset() self.edit_dataset(ds) def on_action_DatasetToPlot(self, action): pj = self._check_project() datasets = self.window.treeview.get_selected_datasets() self.core.create_plot_from_datasets(pj, datasets) def _cb_add_datasets_to_plot(self, action): pj = self._check_project() (plots, datasets) = self.window.treeview.get_selected_plds() if len(plots) == 1 and len(datasets) > 0: pj.add_datasets_to_plot(datasets, plots[0]) def _cb_delete(self, widget): pj = self._check_project() objects = self.window.treeview.get_selected_objects() pj.remove_objects(objects) # --- EDIT ------------------------------------------------------------- ### ### TODO: implement cut/copy/paste ### def _cb_edit_cut(self, widget): pass def _cb_edit_copy(self, widget): pass def _cb_edit_paste(self, widget): pass # --- UNDO/REDO -------------------------------------------------------- def _cb_undo(self, widget): pj = self._check_project() pj.undo() def _cb_redo(self, widget): pj = self._check_project() pj.redo() #---------------------------------------------------------------------- # MISC CALLBACKS def _cb_recent_files_clear(self, action): self.clear_recent_files() def on_action_Preferences(self, action): dlg = preferences.ConfigurationDialog() try: dlg.run() finally: dlg.destroy() def on_action_ViewMetadata(self, action): objects = self.window.treeview.get_selected_objects() if len(objects) == 1: obj = objects[0] # distinguish between old-style object # and new-style node. if isinstance(obj, Dataset): obj = obj.node_info dlg = PropertyBrowserDialog(obj) try: dlg.run() finally: dlg.destroy() #---------------------------------------------------------------------- # Simple user I/O, inherited from Base.Application # def ask_yes_no(self, msg): dialog = gtk.MessageDialog(parent=self.window, flags=0, type=gtk.MESSAGE_WARNING, buttons=gtk.BUTTONS_YES_NO, message_format=msg) result = dialog.run() dialog.destroy() return result == gtk.RESPONSE_YES def error_msg(self, msg): dialog = gtk.MessageDialog(type=gtk.MESSAGE_ERROR, buttons=gtk.BUTTONS_OK, message_format=unicode(msg)) dialog.run() dialog.destroy() def status_msg(self, msg): sb = self.window.statusbar context = sb.get_context_id("main") id = sb.push(context, msg) def remove_msg(statusbar, a_context, a_id): statusbar.remove(a_context, a_id) return False gobject.timeout_add(3000, remove_msg, sb, context, id) def progress(self, fraction): pb = self.window.progressbar if fraction == 0: pb.show() pb.set_fraction(0) elif fraction == -1: pb.hide() else: pb.set_fraction(fraction) # Calling main_iteration is definitely not the only way to # update the progressbar; see FAQ 23.20 for details. But # I refrain from using generators for the import function. while gtk.events_pending(): gtk.main_iteration()
class GtkApplication(application.Application): """ Application is a wrapper window for the ProjectTreeView which holds the information on the current project. It adds a menu bar and a toolbar as described in the two attributes window_actions and ui_string of this module. Furthermore, it provides basic functions to work with the project. """ active_backend = Instance(backend.Backend, required=False, init=None) selected_plots = List(Instance(Plot)) selected_datasets = List(Instance(Dataset)) def init(self): self.sig_register('begin-user-action') self.sig_register('cancel-user-action') self.sig_register('end-user-action') self.window = AppWindow() self._clipboard = gtk.Clipboard() # not implemented yet self._current_plot = None self.path.icon_dir = os.path.join(self.path.base_dir, 'Gtk','Icons') self.register_stock() self.popup_info = None # === Plugins === self.init_plugins() # === Tools === # TODO: The add_tool function is called if you add a Tool # from any Tool's popup menu. The popup # menu triggers the action, which emits the action's 'connect' # signal, which calls 'add_tool'. But there seems to be # no way of knowing which Tool triggered the action. So # currently I am using the application's popup_info field # to get that information ;-) # def add_tool(sender, toolklass): new_tool = toolklass() tool = globals.app.popup_info book = tool.dockbook book.add(new_tool) index = book.get_children().index(new_tool) book.set_current_page(index) def close_tool(sender): tool = globals.app.popup_info book = tool.dockbook book.remove(tool) # Create Actiongroup ag = gtk.ActionGroup("ToolConfig") for key, toolklass in toolbox.ToolRegistry.iteritems(): aw = uihelper.ActionWrapper(key, toolklass.label, stock_id=toolklass.icon_id) aw.connect(add_tool, toolklass) ag.add_action(aw.action) aw = uihelper.ActionWrapper('CloseTool', 'Close Tool', gtk.STOCK_CLOSE) aw.connect(close_tool) ag.add_action(aw.action) self.window.uimanager.insert_action_group(ag, -1) ui = '<popup name="popup_toolconfig">' ui += ' <menu action="AddTool">' for key, tool in toolbox.ToolRegistry.iteritems(): ui += '<menuitem action="%s"/>' % key ui += ' </menu>' ui += ' <menuitem action="CloseTool"/>' ui += '</popup>' # merge plugin ui merge_id = self.window.uimanager.add_ui_from_string(ui) self.window.sidepane.read_config(globals.app.eConfig, default=['ProjectExplorer', 'PropertyEditor']) def register_stock(self): """ Register png images from the GTK icon directory as stock icons. """ uihelper.register_stock_icons(self.path.icon_dir, prefix='sloppy-') # register stock items items = [('sloppy-rename', '_Rename', 0, 0, None)] aliases = [('sloppy-rename', 'gtk-edit')] gtk.stock_add(items) factory = gtk.IconFactory() factory.add_default() style = self.window.get_style() for new_stock, alias in aliases: icon_set = style.lookup_icon_set(alias) factory.add(new_stock, icon_set) # Plugin Handling ------------------------------------------------------ def init_plugins(self): for plugin in self.plugins.itervalues(): if hasattr(plugin, 'gtk_init'): plugin.gtk_init(self) def register_actions(self, action_wrappers): " Helper function for plugins. " # create action group ag = gtk.ActionGroup("Plugin") for item in action_wrappers: ag.add_action(item.action) self.window.uimanager.insert_action_group(ag, -1) # construct plugin ui plugin_ui = '<popup name="popup_dataset">' for item in action_wrappers: plugin_ui += '<menuitem action="%s"/>' % item.name plugin_ui += '</popup>' # merge plugin ui merge_id = self.window.uimanager.add_ui_from_string(plugin_ui) # ---------------------------------------------------------------------- # Project def set_project(self, project, confirm=True): """ Assign the given project to the Application. Returns the new current project. @param confirm: Ask user for permission to close the project (unless there were no changes). """ # class ProjectCheck(Check): # def check(self, value): # TODO: how can we access the old value in the check? DARN! # But maybe we can simply put this question into an action? # if self._project is not None: if self._project.journal.can_undo() and confirm is True: msg = \ """ You are about to close the Project. Do you want to save your changes ? """ dialog = gtk.MessageDialog(type = gtk.MESSAGE_QUESTION, message_format = msg) dialog.add_button("_Don't Save", gtk.RESPONSE_NO) btn_default = dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) dialog.add_button(gtk.STOCK_SAVE, gtk.RESPONSE_YES) btn_default.grab_focus() response = dialog.run() dialog.destroy() if response == gtk.RESPONSE_YES: # yes = yes, save the file before closing self.save_project() elif response == gtk.RESPONSE_NO: # no = no, proceed with closing pass else: # everything else -> abort action raise error.UserCancel # THIS WOULD BE CONNECTED TO THE update::project EVENT. # self.sig_connect('update::project', self.on_update_project) # def on_update_project(self, sender, project): # set new project application.Application.set_project(self, project) # assign project label to window title if project: title = project.filename or "<unnamed project>" else: title = "(no project)" self.window.set_title(os.path.basename(title)) if project is not None: project.journal.on_change = self.window._refresh_undo_redo self.window._refresh_undo_redo() self.window._refresh_recentfiles() return self._project def load_project(self, filename=None): """ Open a FileChooserDialog and let the user pick a new project to be loaded. The old project is replaced. """ if filename is None: # TODO # maybe we could have application.load_project # just request the file name and we simply # create a method for this dialog. # create chooser object chooser = gtk.FileChooserDialog( title="Open project", action=gtk.FILE_CHOOSER_ACTION_OPEN, buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK)) chooser.set_default_response(gtk.RESPONSE_OK) chooser.set_current_folder(os.path.abspath(self.path.current_dir)) chooser.set_select_multiple(False) filter = gtk.FileFilter() filter.set_name("All files") filter.add_pattern("*") chooser.add_filter(filter) filter = gtk.FileFilter() filter.set_name("Sloppyplot Project files") filter.add_pattern("*.spj") filter.add_pattern("*.SPJ") chooser.add_filter(filter) chooser.set_filter(filter) # default filter shortcut_folder = self.path.example_dir if os.path.exists(shortcut_folder): chooser.add_shortcut_folder( shortcut_folder ) response = chooser.run() if response == gtk.RESPONSE_OK: filename = chooser.get_filename() else: filename = None chooser.destroy() if filename is not None: application.Application.load_project(self, filename) def save_project_as(self, filename = None): """ Save project under another filename. """ pj = self._check_project() if not filename: # allow user to choose a filename chooser = gtk.FileChooserDialog( title="Save Project As", action=gtk.FILE_CHOOSER_ACTION_SAVE, buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, gtk.RESPONSE_OK)) chooser.set_default_response(gtk.RESPONSE_OK) chooser.set_current_folder(os.path.abspath(self.path.example_dir)) chooser.set_select_multiple(False) chooser.set_filename(os.path.abspath(pj.filename or "unnamed.spj")) major,minor,micro = gtk.pygtk_version if major > 1 and minor >= 8: chooser.set_do_overwrite_confirmation(True) filter = gtk.FileFilter() filter.set_name("All files") filter.add_pattern("*") chooser.add_filter(filter) chooser.set_filter(filter) # default filter shortcut_folder = self.path.example_dir if os.path.exists(shortcut_folder): chooser.add_shortcut_folder(shortcut_folder) response = chooser.run() try: if response == gtk.RESPONSE_OK: filename = chooser.get_filename() else: raise error.UserCancel finally: chooser.destroy() # add extension if not yet there if filename.lower().endswith('.spj') is False: filename = filename + ".spj" self._project.filename = filename self.window.set_title(os.path.basename(self._project.filename)) save_project(self._project) self._project.journal.clear() self.recent_files.insert(0, os.path.abspath(filename)) self.sig_emit('update-recent-files') def quit(self): """ Quit Application and gtk main loop. """ try: application.Application.quit(self) gtk.main_quit() except error.UserCancel: return # ---------------------------------------------------------------------- # Callbacks # # delete-event/destroy/quit application def _cb_quit_application(self, action): self.quit() def _cb_project_close(self,widget=None): self.set_project(None) def _cb_project_open(self,widget): self.load_project() def _cb_project_save(self,widget): self.save_project() def _cb_project_save_as(self,widget): self.save_project_as() def _cb_project_new(self,widget): self.new_project() #---------------------------------------------------------------------- def _cb_edit(self, action): plots, datasets = self.selected_plots, self.selected_datasets if len(plots) > 0: self.edit_layer(plots[0]) else: for dataset in datasets: self.edit_dataset(dataset) # --- VIEW --------------------------------------------------------------------- def edit_dataset(self, ds, undolist=[]): widget = self.window.find_dataset_widget(self.project, ds) if widget is None: widget = datawin.DatasetWidget(self.project, ds) self.window.add_basewidget(widget) widget.show() def edit_layer(self, plot, layer=None, current_page=None): """ Edit the given layer of the given plot. If no layer is given, the method tries to edit the first Layer. If there is no Layer in the plot, an error will logged. TODO: current_page. """ if layer is None: if len(plot.layers) > 0: layer = plot.layers[0] else: logger.error("The plot to be edited has not even a single layer!") return win = LayerWindow(plot, layer, current_page=current_page) win.set_modal(True) win.present() def edit_line(self, line): dialog = OptionsDialog(line) try: response = dialog.run() if response == gtk.RESPONSE_ACCEPT: ul = UndoList("Edit Line") dialog.check_out(undolist=ul) #uwrap.emit_last(self.backend, 'redraw', undolist=ul) self.project.journal.append(ul) return dialog.owner else: raise error.UserCancel finally: dialog.destroy() # win = gtk.Window() # self.factory = checkwidgets.DisplayFactory(line) # self.factory.add_keys(line._checks.keys()) # table = self.factory.create_table() # frame = uihelper.new_section("Line", table) # self.factory.check_in(line) #---------------------------------------------------------------------- def _cb_new_plot(self,widget): pj = self._check_project() plot = self.core.new_lineplot2d(key='empty plot') pj.add_plots([plot]) # --- PLOT --------------------------------------------------------------------- def _cb_plot(self,widget): self.plot_current_objects() def _cb_plot_gnuplot(self,widget): self.plot_current_objects('gnuplot/x11') def _cb_plot_matplotlib(self,widget): self.plot_current_objects('matplotlib') def plot(self,plot,backend_name='matplotlib'): logger.debug("Backend name is %s" % backend_name) if backend_name == 'gnuplot/x11': backend = self.project.request_backend('gnuplot/x11', plot=plot) backend.draw() return elif backend_name == 'matplotlib': # as widget widget = self.window.find_plot_widget(project=self.project, plot=plot) if widget is None: widget = mpl.MatplotlibWidget(project=self.project, plot=plot) self.window.add_basewidget(widget) widget.show() backend = widget.backend else: raise RuntimeError("Unknown backend %s" % backend_name) self.active_backend = backend def plot_current_objects(self, backend_name='matplotlib', undolist=[]): plots, datasets = self.selected_plots, self.selected_datasets for plot in plots: self.plot(plot, backend_name) def on_action_export_via_gnuplot(self, action): plots = self.selected_plots if len(plots) > 0: self.plot_postscript(self.project, plots[0]) def plot_postscript(app, project, plot): # # request filename # filename = PostscriptTerminal.build_filename('ps', project, plot) chooser = gtk.FileChooserDialog( title="PostScript Export", action=gtk.FILE_CHOOSER_ACTION_SAVE, buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, gtk.RESPONSE_OK)) chooser.set_default_response(gtk.RESPONSE_OK) chooser.set_select_multiple(False) chooser.set_current_folder(os.path.abspath(os.path.dirname(filename))) chooser.set_current_name(os.path.basename(filename)) filter = gtk.FileFilter() filter.set_name("All files") filter.add_pattern("*") chooser.add_filter(filter) filter = gtk.FileFilter() filter.set_name("Postscript (.ps; .eps)") filter.add_pattern("*.ps") filter.add_pattern("*.eps") chooser.add_filter(filter) chooser.set_filter(filter) # default filter response = chooser.run() try: if response == gtk.RESPONSE_OK: filename = chooser.get_filename() else: raise error.UserCancel finally: chooser.destroy() # # request export options # dialog = OptionsDialog(PostscriptTerminal(), title="Options Postscript Export", parent=app.window) #dialog.set_size_request(320,520) # determine requested postscript mode (ps or eps) from extension path, ext = os.path.splitext(filename) ext = ext.lower() if ext == '.eps': dialog.owner.mode = 'eps' elif ext == '.ps': dialog.owner.mode = 'landscape' try: result = dialog.run() if result == gtk.RESPONSE_ACCEPT: dialog.check_out() else: return terminal = dialog.owner finally: dialog.destroy() # # now check if mode and filename extension match # def fix_filename(filename, mode): msg = "The postscript mode you selected (%s) does not match the given filename extension (%s). Do you want to adjust the filename to match the mode? " % (mode, os.path.splitext(filename)[1]) dialog = gtk.MessageDialog(type = gtk.MESSAGE_QUESTION, message_format = msg) dialog.add_button("Keep Filename", gtk.RESPONSE_NO) btn_default = dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) dialog.add_button("Adjust Filename", gtk.RESPONSE_YES) btn_default.grab_focus() response = dialog.run() dialog.destroy() if response == gtk.RESPONSE_YES: # yes = yes, adjust filename if mode == '.eps': new_ext = '.eps' else: new_ext = '.ps' path, ext = os.path.splitext(filename) return path + new_ext elif response == gtk.RESPONSE_NO: # no = no, keep filename return filename else: # everything else -> abort action raise error.UserCancel if (terminal.mode == 'eps' and ext != '.eps') or \ (terminal.mode != 'eps' and ext != '.ps'): filename = fix_filename(filename, terminal.mode) # # construct backend for output # backend = globals.BackendRegistry['gnuplot']( project=project, plot=plot, filename=filename, terminal=terminal) try: backend.draw() finally: backend.disconnect() # --- DATASET HANDLING ------------------------------------------------- def _cb_import_dataset(self, action): pj = self._check_project() # allow user to choose files for import chooser = gtk.FileChooserDialog( title="Import Dataset from file", action=gtk.FILE_CHOOSER_ACTION_OPEN, buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK)) chooser.set_default_response(gtk.RESPONSE_OK) chooser.set_current_folder(os.path.abspath(self.path.current_dir)) chooser.set_select_multiple(True) filter_keys = {} # used for reference later on # add 'All Files' filter blurb_all_files = "All Files" filter = gtk.FileFilter() filter.set_name(blurb_all_files) filter.add_pattern("*") chooser.add_filter(filter) chooser.set_filter(filter) filter_keys[blurb_all_files] = 'auto' # default if nothing else specified # # create file filters # # Each item in importer_registry is a class derived from # dataio.Importer. By using IOTemplate objects we can # customize the default values for these templates. for (key, template) in globals.import_templates.iteritems(): ext_list = template.extensions.split(',') if len(ext_list) == 0: continue extensions = ';'.join(map(lambda ext: '*.'+ext, ext_list)) blurb = "%s (%s)" % (template.blurb, extensions) filter = gtk.FileFilter() filter.set_name(blurb) for ext in ext_list: filter.add_pattern("*."+ext.lower()) filter.add_pattern("*."+ext.upper()) chooser.add_filter(filter) filter_keys[blurb] = key # add shortcut folder to example path, if such exists shortcut_folder = self.path.data_dir if os.path.exists(shortcut_folder): chooser.add_shortcut_folder(shortcut_folder) # # prepare extra widget # # The custom widget `combobox` lets the user choose, # which ImporterTemplate is to be used. # model: key, blurb model = gtk.ListStore(str, str) # add 'Same as Filter' as first choice, then add all importers model.append( (None, "Auto") ) for key, template in globals.import_templates.iteritems(): model.append( (key, template.blurb) ) combobox = gtk.ComboBox(model) cell = gtk.CellRendererText() combobox.pack_start(cell, True) combobox.add_attribute(cell, 'text', 1) combobox.set_active(0) combobox.show() label = gtk.Label("Use Template: ") label.show() hbox = gtk.HBox() hbox.pack_end(combobox,False) hbox.pack_end(label,False) hbox.show() vbox = gtk.VBox() vbox.pack_start(hbox,False) #vbox.pack_start(pbar,False) vbox.show() chooser.set_extra_widget(vbox) # # run dialog # try: response = chooser.run() if response == gtk.RESPONSE_OK: filenames = chooser.get_filenames() if len(filenames) == 0: return template_key = model[combobox.get_active()][0] if template_key is None: # auto f = chooser.get_filter() template_key = filter_keys[f.get_name()] else: return finally: chooser.destroy() self.do_import(pj, filenames, template_key) def do_import(self, project, filenames, template_key=None): # try to determine template key if it is not given if template_key is None or template_key=='auto': matches = dataio.importer_template_from_filename(filenames[0]) if len(matches) > 0: template_key = matches[0] else: template_key = 'ASCII' # # Request import options # # Note that if 'skip_option' is set in the template, then # there will be no user options dialog. if globals.import_templates[template_key].skip_options is False: dialog = import_dialog.ImportOptions(template_key, previewfile=filenames[0]) try: result = dialog.run() if result == gtk.RESPONSE_ACCEPT: # save template as 'recently used' template = dataio.IOTemplate() template.defaults = values_as_dict(dialog.importer, dialog.importer.public_props) template.blurb = "Recently used Template" template.importer_key = dialog.template.importer_key template.write_config = True template.immutable = True globals.import_templates['recently used'] = template else: return finally: dialog.destroy() else: template = template_key self.core.import_datasets(project, filenames, template) def _cb_new_dataset(self,widget): """ Create a new dataset and switch to its editing window. """ pj = self._check_project() ds = pj.new_dataset() self.edit_dataset(ds) def on_action_DatasetToPlot(self, action): pj = self._check_project() self.core.create_plot_from_datasets(pj, self.selected_datasets) def _cb_add_datasets_to_plot(self, action): pj = self._check_project() plots, datasets = self.selected_plots, self.selected_datasets if len(plots) == 1 and len(datasets) > 0: pj.add_datasets_to_plot(datasets, plots[0]) def _cb_delete(self, widget): pj = self._check_project() objects = self.selected_datasets + self.selected_plots pj.remove_objects(objects) # --- EDIT ------------------------------------------------------------- ### ### TODO: implement cut/copy/paste ### def _cb_edit_cut(self, widget): pass def _cb_edit_copy(self, widget): pass def _cb_edit_paste(self, widget): pass # --- UNDO/REDO -------------------------------------------------------- def _cb_undo(self, widget): pj = self._check_project() pj.undo() def _cb_redo(self, widget): pj = self._check_project() pj.redo() #---------------------------------------------------------------------- # MISC CALLBACKS def _cb_recent_files_clear(self, action): self.clear_recent_files() def on_action_Preferences(self, action): dlg = preferences.ConfigurationDialog() try: dlg.run() finally: dlg.destroy() #---------------------------------------------------------------------- # Simple user I/O, inherited from Base.Application # def ask_yes_no(self, msg): dialog = gtk.MessageDialog(parent=self.window, flags=0, type=gtk.MESSAGE_WARNING, buttons=gtk.BUTTONS_YES_NO, message_format=msg) result = dialog.run() dialog.destroy() return result == gtk.RESPONSE_YES def error_msg(self, msg): dialog = gtk.MessageDialog(type=gtk.MESSAGE_ERROR, buttons=gtk.BUTTONS_OK, message_format=unicode(msg)) dialog.run() dialog.destroy() def status_msg(self, msg): sb = self.window.statusbar context = sb.get_context_id("main") id = sb.push(context, msg) def remove_msg(statusbar, a_context, a_id): statusbar.remove(a_context, a_id) return False gobject.timeout_add(3000, remove_msg, sb, context, id) def progress(self, fraction): pb = self.window.progressbar if fraction == 0: pb.show() pb.set_fraction(0) elif fraction == -1: pb.hide() else: pb.set_fraction(fraction) # Calling main_iteration is definitely not the only way to # update the progressbar; see FAQ 23.20 for details. But # I refrain from using generators for the import function. while gtk.events_pending(): gtk.main_iteration() def get_uistring(self, resource): """ Return ui string for the given resource. E.g. resource='appwindow' returns the contents of the file 'UI/appwindow.ui' (if available). """ path = os.path.join(self.path.internal_path, 'Gtk', 'UI', "%s.ui"%resource) fd = open(path, 'r') try: ui = fd.read() finally: fd.close() return ui