X-Git-Url: http://git.tremily.us/?p=hooke.git;a=blobdiff_plain;f=hooke%2Fui%2Fgui%2F__init__.py;h=59ea1b7a1c45254ab281808fd362f480752abff5;hp=08ee6e302a27a4aa6cf64fab48e8dbbff88a29d9;hb=76d5792c61dfb616ddadf058890258d5b32b33be;hpb=1c8117db8ebbbd928e00a98a6547171b485f0245 diff --git a/hooke/ui/gui/__init__.py b/hooke/ui/gui/__init__.py index 08ee6e3..59ea1b7 100644 --- a/hooke/ui/gui/__init__.py +++ b/hooke/ui/gui/__init__.py @@ -1,968 +1,138 @@ -# Copyright - -"""Defines :class:`GUI` providing a wxWidgets interface to Hooke. -""" - -WX_GOOD=['2.8'] - -import wxversion -wxversion.select(WX_GOOD) - -import copy -import os -import os.path -import platform -import shutil -import time - -import wx.html -import wx.aui as aui -import wx.lib.evtmgr as evtmgr - - -# wxPropertyGrid included in wxPython >= 2.9.1, until then, see -# http://wxpropgrid.sourceforge.net/cgi-bin/index?page=download -# until then, we'll avoid it because of the *nix build problems. -#import wx.propgrid as wxpg - -from matplotlib.ticker import FuncFormatter - -from ...command import CommandExit, Exit, Success, Failure, Command, Argument -from ...config import Setting -from ...interaction import Request, BooleanRequest, ReloadUserInterfaceConfig -from ...ui import UserInterface, CommandMessage -from .dialog.selection import Selection as SelectionDialog -from .dialog.save_file import select_save_file -from . import menu as menu -from . import navbar as navbar -from . import panel as panel -from . import prettyformat as prettyformat -from . import statusbar as statusbar - - -class HookeFrame (wx.Frame): - """The main Hooke-interface window. - - - """ - def __init__(self, gui, commands, inqueue, outqueue, *args, **kwargs): - super(HookeFrame, self).__init__(*args, **kwargs) - self.gui = gui - self.commands = commands - self.inqueue = inqueue - self.outqueue = outqueue - self._perspectives = {} # {name: perspective_str} - self._c = {} - - self.SetIcon(wx.Icon(self.gui.config['icon image'], wx.BITMAP_TYPE_ICO)) - - # setup frame manager - self._c['manager'] = aui.AuiManager() - self._c['manager'].SetManagedWindow(self) - - # set the gradient and drag styles - self._c['manager'].GetArtProvider().SetMetric( - aui.AUI_DOCKART_GRADIENT_TYPE, aui.AUI_GRADIENT_NONE) - self._c['manager'].SetFlags( - self._c['manager'].GetFlags() ^ aui.AUI_MGR_TRANSPARENT_DRAG) - - # Min size for the frame itself isn't completely done. See - # the end of FrameManager::Update() for the test code. For - # now, just hard code a frame minimum size. - self.SetMinSize(wx.Size(500, 500)) - - self._setup_panels() - self._setup_toolbars() - self._c['manager'].Update() # commit pending changes - - # Create the menubar after the panes so that the default - # perspective is created with all panes open - self._c['menu bar'] = menu.HookeMenuBar( - parent=self, - callbacks={ - 'close': self._on_close, - 'about': self._on_about, - 'view_panel': self._on_panel_visibility, - 'save_perspective': self._on_save_perspective, - 'delete_perspective': self._on_delete_perspective, - 'select_perspective': self._on_select_perspective, - }) - self.SetMenuBar(self._c['menu bar']) - - self._c['status bar'] = statusbar.StatusBar( - parent=self, - style=wx.ST_SIZEGRIP) - self.SetStatusBar(self._c['status bar']) - - self._setup_perspectives() - self._bind_events() - - name = self.gui.config['active perspective'] - return # TODO: cleanup - self.playlists = self._c['playlists'].Playlists - self._displayed_plot = None - #load default list, if possible - self.do_loadlist(self.GetStringFromConfig('core', 'preferences', 'playlist')) - - - # GUI maintenance - - def _setup_panels(self): - client_size = self.GetClientSize() - for label,p,style in [ -# ('folders', wx.GenericDirCtrl( -# parent=self, -# dir=self.gui.config['folders-workdir'], -# size=(200, 250), -# style=wx.DIRCTRL_SHOW_FILTERS, -# filter=self.gui.config['folders-filters'], -# defaultFilter=int(self.gui.config['folders-filter-index'])), 'left'), #HACK: config should convert -# ('playlists', panel.PANELS['playlist']( -# callbacks={}, -# config=self.gui.config, -# parent=self, -# style=wx.WANTS_CHARS|wx.NO_BORDER, -# # WANTS_CHARS so the panel doesn't eat the Return key. -# size=(160, 200)), 'left'), -# ('note', panel.note.Note( -# parent=self -# style=wx.WANTS_CHARS|wx.NO_BORDER, -# size=(160, 200)), 'left'), -# ('notebook', Notebook( -# parent=self, -# pos=wx.Point(client_size.x, client_size.y), -# size=wx.Size(430, 200), -# style=aui.AUI_NB_DEFAULT_STYLE -# | aui.AUI_NB_TAB_EXTERNAL_MOVE | wx.NO_BORDER), 'center'), - ('commands', panel.PANELS['commands']( - commands=self.commands, - selected=self.gui.config['selected command'], - callbacks={ - 'execute': self.execute_command, - 'select_plugin': self.select_plugin, - 'select_command': self.select_command, -# 'selection_changed': self.panelProperties.select(self, method, command), #SelectedTreeItem = selected_item, - }, - parent=self, - style=wx.WANTS_CHARS|wx.NO_BORDER, - # WANTS_CHARS so the panel doesn't eat the Return key. -# size=(160, 200) - ), 'center'), - #('properties', panel.propertyeditor.PropertyEditor(self),'right'), -# ('assistant', wx.TextCtrl( -# parent=self, -# pos=wx.Point(0, 0), -# size=wx.Size(150, 90), -# style=wx.NO_BORDER|wx.TE_MULTILINE), 'right'), - ('output', panel.PANELS['output']( - buffer_lines=5, - parent=self, - pos=wx.Point(0, 0), - size=wx.Size(150, 90), - style=wx.TE_READONLY|wx.NO_BORDER|wx.TE_MULTILINE), - 'bottom'), -# ('results', panel.results.Results(self), 'bottom'), - ]: - self._add_panel(label, p, style) - #self._c['assistant'].SetEditable(False) - - def _add_panel(self, label, panel, style): - self._c[label] = panel - cap_label = label.capitalize() - info = aui.AuiPaneInfo().Name(cap_label).Caption(cap_label) - info.PaneBorder(False).CloseButton(True).MaximizeButton(False) - if style == 'top': - info.Top() - elif style == 'center': - info.CenterPane() - elif style == 'left': - info.Left() - elif style == 'right': - info.Right() - else: - assert style == 'bottom', style - info.Bottom() - self._c['manager'].AddPane(panel, info) - - def _setup_toolbars(self): - self._c['navigation bar'] = navbar.NavBar( - callbacks={ - 'next': self._next_curve, - 'previous': self._previous_curve, - }, - parent=self, - style=wx.TB_FLAT | wx.TB_NODIVIDER) - self._c['manager'].AddPane( - self._c['navigation bar'], - aui.AuiPaneInfo().Name('Navigation').Caption('Navigation' - ).ToolbarPane().Top().Layer(1).Row(1).LeftDockable(False - ).RightDockable(False)) - - def _bind_events(self): - # TODO: figure out if we can use the eventManager for menu - # ranges and events of 'self' without raising an assertion - # fail error. - self.Bind(wx.EVT_ERASE_BACKGROUND, self._on_erase_background) - self.Bind(wx.EVT_SIZE, self._on_size) - self.Bind(wx.EVT_CLOSE, self._on_close) - self.Bind(aui.EVT_AUI_PANE_CLOSE, self.OnPaneClose) - self.Bind(aui.EVT_AUINOTEBOOK_PAGE_CLOSE, self._on_notebook_page_close) - - return # TODO: cleanup - for value in self._c['menu bar']._c['view']._c.values(): - self.Bind(wx.EVT_MENU_RANGE, self._on_view, value) - - self.Bind(wx.EVT_MENU, self._on_save_perspective, - self._c['menu bar']._c['perspective']._c['save']) - self.Bind(wx.EVT_MENU, self._on_delete_perspective, - self._c['menu bar']._c['perspective']._c['delete']) - - treeCtrl = self._c['folders'].GetTreeCtrl() - treeCtrl.Bind(wx.EVT_LEFT_DCLICK, self._on_dir_ctrl_left_double_click) - - # TODO: playlist callbacks - return # TODO: cleanup - evtmgr.eventManager.Register(self.OnUpdateNote, wx.EVT_BUTTON, self.panelNote.UpdateButton) - #property editor - self.panelProperties.pg.Bind(wxpg.EVT_PG_CHANGED, self.OnPropGridChanged) - #results panel - self.panelResults.results_list.OnCheckItem = self.OnResultsCheck - - def _on_about(self, *args): - dialog = wx.MessageDialog( - parent=self, - message=self.gui._splash_text(), - caption='About Hooke', - style=wx.OK|wx.ICON_INFORMATION) - dialog.ShowModal() - dialog.Destroy() - - def _on_close(self, *args): - # apply changes - self.gui.config['main height'] = str(self.GetSize().GetHeight()) - self.gui.config['main left'] = str(self.GetPosition()[0]) - self.gui.config['main top'] = str(self.GetPosition()[1]) - self.gui.config['main width'] = str(self.GetSize().GetWidth()) - # push changes back to Hooke.config? - self._c['manager'].UnInit() - del self._c['manager'] - self.Destroy() - - - - # Command handling - - def _command_by_name(self, name): - cs = [c for c in self.commands if c.name == name] - if len(cs) == 0: - raise KeyError(name) - elif len(cs) > 1: - raise Exception('Multiple commands named "%s"' % name) - return cs[0] - - def execute_command(self, _class=None, method=None, - command=None, args=None): - self.inqueue.put(CommandMessage(command, args)) - results = [] - while True: - msg = self.outqueue.get() - results.append(msg) - if isinstance(msg, Exit): - self._on_close() - break - elif isinstance(msg, CommandExit): - # TODO: display command complete - break - elif isinstance(msg, ReloadUserInterfaceConfig): - self.gui.reload_config(msg.config) - continue - elif isinstance(msg, Request): - h = handler.HANDLERS[msg.type] - h.run(self, msg) # TODO: pause for response? - continue - pp = getattr( - self, '_postprocess_%s' % command.name.replace(' ', '_'), - self._postprocess_text) - pp(command=command, results=results) - return results - - def _handle_request(self, msg): - """Repeatedly try to get a response to `msg`. - """ - if prompt == None: - raise NotImplementedError('_%s_request_prompt' % msg.type) - prompt_string = prompt(msg) - parser = getattr(self, '_%s_request_parser' % msg.type, None) - if parser == None: - raise NotImplementedError('_%s_request_parser' % msg.type) - error = None - while True: - if error != None: - self.cmd.stdout.write(''.join([ - error.__class__.__name__, ': ', str(error), '\n'])) - self.cmd.stdout.write(prompt_string) - value = parser(msg, self.cmd.stdin.readline()) - try: - response = msg.response(value) - break - except ValueError, error: - continue - self.inqueue.put(response) - - - - # Command-specific postprocessing - - def _postprocess_text(self, command, results): - """Print the string representation of the results to the Results window. - - This is similar to :class:`~hooke.ui.commandline.DoCommand`'s - approach, except that :class:`~hooke.ui.commandline.DoCommand` - doesn't print some internally handled messages - (e.g. :class:`~hooke.interaction.ReloadUserInterfaceConfig`). - """ - for result in results: - if isinstance(result, CommandExit): - self._c['output'].write(result.__class__.__name__+'\n') - self._c['output'].write(str(result).rstrip()+'\n') - - def _postprocess_get_curve(self, command, results): - """Update `self` to show the curve. - """ - if not isinstance(results[-1], Success): - return # error executing 'get curve' - assert len(results) == 2, results - curve = results[0] - print curve - - selected_item = self._c['playlists']._c['tree'].GetSelection() - if self._c['playlists']._c['tree'].ItemHasChildren(selected_item): - #GetFirstChild returns a tuple - #we only need the first element - next_item = self._c['playlists']._c['tree'].GetFirstChild(selected_item)[0] - else: - next_item = self._c['playlists']._c['tree'].GetNextSibling(selected_item) - if not next_item.IsOk(): - parent_item = self._c['playlists']._c['tree'].GetItemParent(selected_item) - #GetFirstChild returns a tuple - #we only need the first element - next_item = self._c['playlists']._c['tree'].GetFirstChild(parent_item)[0] - self._c['playlists']._c['tree'].SelectItem(next_item, True) - if not self._c['playlists']._c['tree'].ItemHasChildren(selected_item): - playlist = self.GetActivePlaylist() - if playlist.count > 1: - playlist.next() - self._c['status bar'].set_playlist(playlist) - self.UpdateNote() - self.UpdatePlot() - - - - # TODO: cruft - - def _GetActiveFileIndex(self): - lib.playlist.Playlist = self.GetActivePlaylist() - #get the selected item from the tree - selected_item = self._c['playlists']._c['tree'].GetSelection() - #test if a playlist or a curve was double-clicked - if self._c['playlists']._c['tree'].ItemHasChildren(selected_item): - return -1 - else: - count = 0 - selected_item = self._c['playlists']._c['tree'].GetPrevSibling(selected_item) - while selected_item.IsOk(): - count += 1 - selected_item = self._c['playlists']._c['tree'].GetPrevSibling(selected_item) - return count - - def _GetPlaylistTab(self, name): - for index, page in enumerate(self._c['notebook']._tabs._pages): - if page.caption == name: - return index - return -1 - - def select_plugin(self, _class=None, method=None, plugin=None): - for option in config[section]: - properties.append([option, config[section][option]]) - - def AddPlaylistFromFiles(self, files=[], name='Untitled'): - if files: - playlist = lib.playlist.Playlist(self, self.drivers) - for item in files: - playlist.add_curve(item) - if playlist.count > 0: - playlist.name = self._GetUniquePlaylistName(name) - playlist.reset() - self.AddTayliss(playlist) - - def AppliesPlotmanipulator(self, name): - ''' - Returns True if the plotmanipulator 'name' is applied, False otherwise - name does not contain 'plotmanip_', just the name of the plotmanipulator (e.g. 'flatten') - ''' - return self.GetBoolFromConfig('core', 'plotmanipulators', name) - - def ApplyPlotmanipulators(self, plot, plot_file): - ''' - Apply all active plotmanipulators. - ''' - if plot is not None and plot_file is not None: - manipulated_plot = copy.deepcopy(plot) - for plotmanipulator in self.plotmanipulators: - if self.GetBoolFromConfig('core', 'plotmanipulators', plotmanipulator.name): - manipulated_plot = plotmanipulator.method(manipulated_plot, plot_file) - return manipulated_plot - - def GetActiveFigure(self): - playlist_name = self.GetActivePlaylistName() - figure = self.playlists[playlist_name].figure - if figure is not None: - return figure - return None - - def GetActiveFile(self): - playlist = self.GetActivePlaylist() - if playlist is not None: - return playlist.get_active_file() - return None - - def GetActivePlot(self): - playlist = self.GetActivePlaylist() - if playlist is not None: - return playlist.get_active_file().plot - return None - - def GetDisplayedPlot(self): - plot = copy.deepcopy(self.displayed_plot) - #plot.curves = [] - #plot.curves = copy.deepcopy(plot.curves) - return plot - - def GetDisplayedPlotCorrected(self): - plot = copy.deepcopy(self.displayed_plot) - plot.curves = [] - plot.curves = copy.deepcopy(plot.corrected_curves) - return plot - - def GetDisplayedPlotRaw(self): - plot = copy.deepcopy(self.displayed_plot) - plot.curves = [] - plot.curves = copy.deepcopy(plot.raw_curves) - return plot - - def GetDockArt(self): - return self._c['manager'].GetArtProvider() - - def GetPlotmanipulator(self, name): - ''' - Returns a plot manipulator function from its name - ''' - for plotmanipulator in self.plotmanipulators: - if plotmanipulator.name == name: - return plotmanipulator - return None - - def HasPlotmanipulator(self, name): - ''' - returns True if the plotmanipulator 'name' is loaded, False otherwise - ''' - for plotmanipulator in self.plotmanipulators: - if plotmanipulator.command == name: - return True - return False - - - def _on_dir_ctrl_left_double_click(self, event): - file_path = self.panelFolders.GetPath() - if os.path.isfile(file_path): - if file_path.endswith('.hkp'): - self.do_loadlist(file_path) - event.Skip() - - def _on_erase_background(self, event): - event.Skip() - - def _on_notebook_page_close(self, event): - ctrl = event.GetEventObject() - playlist_name = ctrl.GetPageText(ctrl._curpage) - self.DeleteFromPlaylists(playlist_name) - - def OnPaneClose(self, event): - event.Skip() - - def OnPropGridChanged (self, event): - prop = event.GetProperty() - if prop: - item_section = self.panelProperties.SelectedTreeItem - item_plugin = self._c['commands']._c['tree'].GetItemParent(item_section) - plugin = self._c['commands']._c['tree'].GetItemText(item_plugin) - config = self.gui.config[plugin] - property_section = self._c['commands']._c['tree'].GetItemText(item_section) - property_key = prop.GetName() - property_value = prop.GetDisplayedString() - - config[property_section][property_key]['value'] = property_value - - def OnResultsCheck(self, index, flag): - results = self.GetActivePlot().results - if results.has_key(self.results_str): - results[self.results_str].results[index].visible = flag - results[self.results_str].update() - self.UpdatePlot() - - - def _on_size(self, event): - event.Skip() - - def OnUpdateNote(self, event): - ''' - Saves the note to the active file. - ''' - active_file = self.GetActiveFile() - active_file.note = self.panelNote.Editor.GetValue() - - def UpdateNote(self): - #update the note for the active file - active_file = self.GetActiveFile() - if active_file is not None: - self.panelNote.Editor.SetValue(active_file.note) - - def UpdatePlaylistsTreeSelection(self): - playlist = self.GetActivePlaylist() - if playlist is not None: - if playlist.index >= 0: - self._c['status bar'].set_playlist(playlist) - self.UpdateNote() - self.UpdatePlot() - - def UpdatePlot(self, plot=None): - - def add_to_plot(curve, set_scale=True): - if curve.visible and curve.x and curve.y: - #get the index of the subplot to use as destination - destination = (curve.destination.column - 1) * number_of_rows + curve.destination.row - 1 - #set all parameters for the plot - axes_list[destination].set_title(curve.title) - if set_scale: - axes_list[destination].set_xlabel(curve.prefix.x + curve.units.x) - axes_list[destination].set_ylabel(curve.prefix.y + curve.units.y) - #set the formatting details for the scale - formatter_x = lib.curve.PrefixFormatter(curve.decimals.x, curve.prefix.x, use_zero) - formatter_y = lib.curve.PrefixFormatter(curve.decimals.y, curve.prefix.y, use_zero) - axes_list[destination].xaxis.set_major_formatter(formatter_x) - axes_list[destination].yaxis.set_major_formatter(formatter_y) - if curve.style == 'plot': - axes_list[destination].plot(curve.x, curve.y, color=curve.color, label=curve.label, lw=curve.linewidth, zorder=1) - if curve.style == 'scatter': - axes_list[destination].scatter(curve.x, curve.y, color=curve.color, label=curve.label, s=curve.size, zorder=2) - #add the legend if necessary - if curve.legend: - axes_list[destination].legend() - - if plot is None: - active_file = self.GetActiveFile() - if not active_file.driver: - #the first time we identify a file, the following need to be set - active_file.identify(self.drivers) - for curve in active_file.plot.curves: - curve.decimals.x = self.GetIntFromConfig('core', 'preferences', 'x_decimals') - curve.decimals.y = self.GetIntFromConfig('core', 'preferences', 'y_decimals') - curve.legend = self.GetBoolFromConfig('core', 'preferences', 'legend') - curve.prefix.x = self.GetStringFromConfig('core', 'preferences', 'x_prefix') - curve.prefix.y = self.GetStringFromConfig('core', 'preferences', 'y_prefix') - if active_file.driver is None: - self.AppendToOutput('Invalid file: ' + active_file.filename) - return - self.displayed_plot = copy.deepcopy(active_file.plot) - #add raw curves to plot - self.displayed_plot.raw_curves = copy.deepcopy(self.displayed_plot.curves) - #apply all active plotmanipulators - self.displayed_plot = self.ApplyPlotmanipulators(self.displayed_plot, active_file) - #add corrected curves to plot - self.displayed_plot.corrected_curves = copy.deepcopy(self.displayed_plot.curves) - else: - active_file = None - self.displayed_plot = copy.deepcopy(plot) - - figure = self.GetActiveFigure() - figure.clear() - - #use '0' instead of e.g. '0.00' for scales - use_zero = self.GetBoolFromConfig('core', 'preferences', 'use_zero') - #optionally remove the extension from the title of the plot - hide_curve_extension = self.GetBoolFromConfig('core', 'preferences', 'hide_curve_extension') - if hide_curve_extension: - title = lh.remove_extension(self.displayed_plot.title) - else: - title = self.displayed_plot.title - figure.suptitle(title, fontsize=14) - #create the list of all axes necessary (rows and columns) - axes_list =[] - number_of_columns = max([curve.destination.column for curve in self.displayed_plot.curves]) - number_of_rows = max([curve.destination.row for curve in self.displayed_plot.curves]) - for index in range(number_of_rows * number_of_columns): - axes_list.append(figure.add_subplot(number_of_rows, number_of_columns, index + 1)) - - #add all curves to the corresponding plots - for curve in self.displayed_plot.curves: - add_to_plot(curve) - - #make sure the titles of 'subplots' do not overlap with the axis labels of the 'main plot' - figure.subplots_adjust(hspace=0.3) - - #display results - self.panelResults.ClearResults() - if self.displayed_plot.results.has_key(self.results_str): - for curve in self.displayed_plot.results[self.results_str].results: - add_to_plot(curve, set_scale=False) - self.panelResults.DisplayResults(self.displayed_plot.results[self.results_str]) - else: - self.panelResults.ClearResults() - #refresh the plot - figure.canvas.draw() - - def _on_curve_select(self, playlist, curve): - #create the plot tab and add playlist to the dictionary - plotPanel = panel.plot.PlotPanel(self, ID_FirstPlot + len(self.playlists)) - notebook_tab = self._c['notebook'].AddPage(plotPanel, playlist.name, True) - #tab_index = self._c['notebook'].GetSelection() - playlist.figure = plotPanel.get_figure() - self.playlists[playlist.name] = playlist - #self.playlists[playlist.name] = [playlist, figure] - self._c['status bar'].set_playlist(playlist) - self.UpdateNote() - self.UpdatePlot() - - - def _on_playlist_left_doubleclick(self): - index = self._c['notebook'].GetSelection() - current_playlist = self._c['notebook'].GetPageText(index) - if current_playlist != playlist_name: - index = self._GetPlaylistTab(playlist_name) - self._c['notebook'].SetSelection(index) - self._c['status bar'].set_playlist(playlist) - self.UpdateNote() - self.UpdatePlot() - - def _on_playlist_delete(self, playlist): - notebook = self.Parent.plotNotebook - index = self.Parent._GetPlaylistTab(playlist.name) - notebook.SetSelection(index) - notebook.DeletePage(notebook.GetSelection()) - self.Parent.DeleteFromPlaylists(playlist_name) - - - - # Command panel interface - - def select_command(self, _class, method, command): - self.select_plugin(plugin=command.plugin) - plugin = self.GetItemText(selected_item) - if plugin != 'core': - doc_string = eval('plugins.' + plugin + '.' + plugin + 'Commands.__doc__') - else: - doc_string = 'The module "core" contains Hooke core functionality' - if doc_string is not None: - self.panelAssistant.ChangeValue(doc_string) - else: - self.panelAssistant.ChangeValue('') - panel.propertyeditor.PropertyEditor.Initialize(self.panelProperties, properties) - self.gui.config['selected command'] = command - - - - # Navbar interface - - def _next_curve(self, *args): - """Call the `next curve` command. - """ - results = self.execute_command( - command=self._command_by_name('next curve')) - if isinstance(results[-1], Success): - self.execute_command( - command=self._command_by_name('get curve')) - - def _previous_curve(self, *args): - """Call the `previous curve` command. - """ - self.execute_command( - command=self._command_by_name('previous curve')) - if isinstance(results[-1], Success): - self.execute_command( - command=self._command_by_name('get curve')) - - - - # Panel display handling - - def _on_panel_visibility(self, _class, method, panel_name, visible): - pane = self._c['manager'].GetPane(panel_name) - print visible - pane.Show(visible) - #if we don't do the following, the Folders pane does not resize properly on hide/show - if pane.caption == 'Folders' and pane.IsShown() and pane.IsDocked(): - #folders_size = pane.GetSize() - self.panelFolders.Fit() - self._c['manager'].Update() - - def _setup_perspectives(self): - """Add perspectives to menubar and _perspectives. - """ - self._perspectives = { - 'Default': self._c['manager'].SavePerspective(), - } - path = self.gui.config['perspective path'] - if os.path.isdir(path): - files = sorted(os.listdir(path)) - for fname in files: - name, extension = os.path.splitext(fname) - if extension != self.gui.config['perspective extension']: - continue - fpath = os.path.join(path, fname) - if not os.path.isfile(fpath): - continue - perspective = None - with open(fpath, 'rU') as f: - perspective = f.readline() - if perspective: - self._perspectives[name] = perspective - - selected_perspective = self.gui.config['active perspective'] - if not self._perspectives.has_key(selected_perspective): - self.gui.config['active perspective'] = 'Default' # TODO: push to engine's Hooke - - self._restore_perspective(selected_perspective) - self._update_perspective_menu() - - def _update_perspective_menu(self): - self._c['menu bar']._c['perspective'].update( - sorted(self._perspectives.keys()), - self.gui.config['active perspective']) - - def _save_perspective(self, perspective, perspective_dir, name, - extension=None): - path = os.path.join(perspective_dir, name) - if extension != None: - path += extension - if not os.path.isdir(perspective_dir): - os.makedirs(perspective_dir) - with open(path, 'w') as f: - f.write(perspective) - self._perspectives[name] = perspective - self._restore_perspective(name) - self._update_perspective_menu() - - def _delete_perspectives(self, perspective_dir, names, - extension=None): - print 'pop', names - for name in names: - path = os.path.join(perspective_dir, name) - if extension != None: - path += extension - os.remove(path) - del(self._perspectives[name]) - self._update_perspective_menu() - if self.gui.config['active perspective'] in names: - self._restore_perspective('Default') - # TODO: does this bug still apply? - # Unfortunately, there is a bug in wxWidgets for win32 (Ticket #3258 - # http://trac.wxwidgets.org/ticket/3258 - # ) that makes the radio item indicator in the menu disappear. - # The code should be fine once this issue is fixed. - - def _restore_perspective(self, name): - if name != self.gui.config['active perspective']: - print 'restoring perspective:', name - self.gui.config['active perspective'] = name # TODO: push to engine's Hooke - self._c['manager'].LoadPerspective(self._perspectives[name]) - self._c['manager'].Update() - for pane in self._c['manager'].GetAllPanes(): - if pane.name in self._c['menu bar']._c['view']._c.keys(): - pane.Check(pane.window.IsShown()) - - def _on_save_perspective(self, *args): - perspective = self._c['manager'].SavePerspective() - name = self.gui.config['active perspective'] - if name == 'Default': - name = 'New perspective' - name = select_save_file( - directory=self.gui.config['perspective path'], - name=name, - extension=self.gui.config['perspective extension'], - parent=self, - message='Enter a name for the new perspective:', - caption='Save perspective') - if name == None: - return - self._save_perspective( - perspective, self.gui.config['perspective path'], name=name, - extension=self.gui.config['perspective extension']) - - def _on_delete_perspective(self, *args, **kwargs): - options = sorted([p for p in self._perspectives.keys() - if p != 'Default']) - dialog = SelectionDialog( - options=options, - message="\nPlease check the perspectives\n\nyou want to delete and click 'Delete'.\n", - button_id=wx.ID_DELETE, - selection_style='multiple', - parent=self, - title='Delete perspective(s)', - style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER) - dialog.CenterOnScreen() - dialog.ShowModal() - names = [options[i] for i in dialog.selected] - dialog.Destroy() - self._delete_perspectives( - self.gui.config['perspective path'], names=names, - extension=self.gui.config['perspective extension']) - - def _on_select_perspective(self, _class, method, name): - self._restore_perspective(name) - - - -class HookeApp (wx.App): - """A :class:`wx.App` wrapper around :class:`HookeFrame`. - - Tosses up a splash screen and then loads :class:`HookeFrame` in - its own window. - """ - def __init__(self, gui, commands, inqueue, outqueue, *args, **kwargs): - self.gui = gui - self.commands = commands - self.inqueue = inqueue - self.outqueue = outqueue - super(HookeApp, self).__init__(*args, **kwargs) - - def OnInit(self): - self.SetAppName('Hooke') - self.SetVendorName('') - self._setup_splash_screen() - - height = int(self.gui.config['main height']) # HACK: config should convert - width = int(self.gui.config['main width']) - top = int(self.gui.config['main top']) - left = int(self.gui.config['main left']) - - # Sometimes, the ini file gets confused and sets 'left' and - # 'top' to large negative numbers. Here we catch and fix - # this. Keep small negative numbers, the user might want - # those. - if left < -width: - left = 0 - if top < -height: - top = 0 - - self._c = { - 'frame': HookeFrame( - self.gui, self.commands, self.inqueue, self.outqueue, - parent=None, title='Hooke', - pos=(left, top), size=(width, height), - style=wx.DEFAULT_FRAME_STYLE|wx.SUNKEN_BORDER|wx.CLIP_CHILDREN), - } - self._c['frame'].Show(True) - self.SetTopWindow(self._c['frame']) - return True - - def _setup_splash_screen(self): - if self.gui.config['show splash screen'] == 'True': # HACK: config should decode - print 'splash', self.gui.config['show splash screen'] - path = self.gui.config['splash screen image'] - if os.path.isfile(path): - duration = int(self.gui.config['splash screen duration']) # HACK: config should decode types - wx.SplashScreen( - bitmap=wx.Image(path).ConvertToBitmap(), - splashStyle=wx.SPLASH_CENTRE_ON_SCREEN|wx.SPLASH_TIMEOUT, - milliseconds=duration, - parent=None) - wx.Yield() - # For some reason splashDuration and sleep do not - # correspond to each other at least not on Windows. - # Maybe it's because duration is in milliseconds and - # sleep in seconds. Thus we need to increase the - # sleep time a bit. A factor of 1.2 seems to work. - sleepFactor = 1.2 - time.sleep(sleepFactor * duration / 1000) - - -class GUI (UserInterface): - """wxWindows graphical user interface. - """ - def __init__(self): - super(GUI, self).__init__(name='gui') - - def default_settings(self): - """Return a list of :class:`hooke.config.Setting`\s for any - configurable UI settings. - - The suggested section setting is:: - - Setting(section=self.setting_section, help=self.__doc__) - """ - return [ - Setting(section=self.setting_section, help=self.__doc__), - Setting(section=self.setting_section, option='icon image', - value=os.path.join('doc', 'img', 'microscope.ico'), - help='Path to the hooke icon image.'), - Setting(section=self.setting_section, option='show splash screen', - value=True, - help='Enable/disable the splash screen'), - Setting(section=self.setting_section, option='splash screen image', - value=os.path.join('doc', 'img', 'hooke.jpg'), - help='Path to the Hooke splash screen image.'), - Setting(section=self.setting_section, option='splash screen duration', - value=1000, - help='Duration of the splash screen in milliseconds.'), - Setting(section=self.setting_section, option='perspective path', - value=os.path.join('resources', 'gui', 'perspective'), - help='Directory containing perspective files.'), # TODO: allow colon separated list, like $PATH. - Setting(section=self.setting_section, option='perspective extension', - value='.txt', - help='Extension for perspective files.'), - Setting(section=self.setting_section, option='hide extensions', - value=False, - help='Hide file extensions when displaying names.'), - Setting(section=self.setting_section, option='folders-workdir', - value='.', - help='This should probably go...'), - Setting(section=self.setting_section, option='folders-filters', - value='.', - help='This should probably go...'), - Setting(section=self.setting_section, option='active perspective', - value='Default', - help='Name of active perspective file (or "Default").'), - Setting(section=self.setting_section, option='folders-filter-index', - value='0', - help='This should probably go...'), - Setting(section=self.setting_section, option='main height', - value=500, - help='Height of main window in pixels.'), - Setting(section=self.setting_section, option='main width', - value=500, - help='Width of main window in pixels.'), - Setting(section=self.setting_section, option='main top', - value=0, - help='Pixels from screen top to top of main window.'), - Setting(section=self.setting_section, option='main left', - value=0, - help='Pixels from screen left to left of main window.'), - Setting(section=self.setting_section, option='selected command', - value='load playlist', - help='Name of the initially selected command.'), - ] - - def _app(self, commands, ui_to_command_queue, command_to_ui_queue): - redirect = True - if __debug__: - redirect=False - app = HookeApp(gui=self, - commands=commands, - inqueue=ui_to_command_queue, - outqueue=command_to_ui_queue, - redirect=redirect) - return app - - def run(self, commands, ui_to_command_queue, command_to_ui_queue): - app = self._app(commands, ui_to_command_queue, command_to_ui_queue) - app.MainLoop() +# Copyright (C) 2010-2012 W. Trevor King +# +# This file is part of Hooke. +# +# Hooke is free software: you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) any +# later version. +# +# Hooke is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Hooke. If not, see . + +"""Defines :class:`GUI` providing a wxWidgets interface to Hooke. + +We only define the :class:`UserInterface` here, to minimize required +dependencies for users who may not wish to use this interface. The +bulk of the interface is defined in :mod:`interface`. +""" + +import os.path as _os_path + +from ...ui import UserInterface as _UserInterface +from ...config import Setting as _Setting +try: + import wxversion as _wxversion +except ImportError, e: + _wxversion = None + _wxversion_error = e +else: + WX_GOOD=['2.9'] + _wxversion.select(WX_GOOD) + try: + from .interface import HookeApp as _HookeApp + except _wxversion.VersionError, e: + _wxversion = None + _wxversion_error = e + +class GUI (_UserInterface): + """wxWindows graphical user interface. + """ + def __init__(self): + super(GUI, self).__init__(name='gui') + + def default_settings(self): + """Return a list of :class:`hooke.config.Setting`\s for any + configurable UI settings. + + The suggested section setting is:: + + Setting(section=self.setting_section, help=self.__doc__) + """ + return [ + _Setting(section=self.setting_section, help=self.__doc__), + _Setting(section=self.setting_section, option='icon image', + value=_os_path.join('doc', 'img', 'microscope.ico'), + type='file', + help='Path to the hooke icon image.'), + _Setting(section=self.setting_section, option='show splash screen', + value=True, type='bool', + help='Enable/disable the splash screen'), + _Setting(section=self.setting_section, option='splash screen image', + value=_os_path.join('doc', 'img', 'hooke.jpg'), + type='file', + help='Path to the Hooke splash screen image.'), + _Setting(section=self.setting_section, + option='splash screen duration', + value=1000, type='int', + help='Duration of the splash screen in milliseconds.'), + _Setting(section=self.setting_section, option='perspective path', + value=_os_path.join('resources', 'gui', 'perspective'), + help='Directory containing perspective files.'), # TODO: allow colon separated list, like $PATH. + _Setting(section=self.setting_section, option='perspective extension', + value='.txt', + help='Extension for perspective files.'), + _Setting(section=self.setting_section, option='hide extensions', + value=False, type='bool', + help='Hide file extensions when displaying names.'), + _Setting(section=self.setting_section, option='plot legend', + value=True, type='bool', + help='Enable/disable the plot legend.'), + _Setting(section=self.setting_section, option='plot SI format', + value='True', type='bool', + help='Enable/disable SI plot axes numbering.'), + _Setting(section=self.setting_section, option='plot decimals', + value=2, type='int', + help=('Number of decimal places to show if "plot SI ' + 'format" is enabled.')), + _Setting(section=self.setting_section, option='folders-workdir', + value='.', type='path', + help='This should probably go...'), + _Setting(section=self.setting_section, option='folders-filters', + value='.', type='path', + help='This should probably go...'), + _Setting(section=self.setting_section, option='active perspective', + value='Default', + help='Name of active perspective file (or "Default").'), + _Setting(section=self.setting_section, + option='folders-filter-index', + value=0, type='int', + help='This should probably go...'), + _Setting(section=self.setting_section, option='main height', + value=450, type='int', + help='Height of main window in pixels.'), + _Setting(section=self.setting_section, option='main width', + value=800, type='int', + help='Width of main window in pixels.'), + _Setting(section=self.setting_section, option='main top', + value=0, type='int', + help='Pixels from screen top to top of main window.'), + _Setting(section=self.setting_section, option='main left', + value=0, type='int', + help='Pixels from screen left to left of main window.'), + _Setting(section=self.setting_section, option='selected command', + value='load playlist', + help='Name of the initially selected command.'), + ] + + def _app(self, commands, ui_to_command_queue, command_to_ui_queue): + if _wxversion is None: + raise _wxversion_error + redirect = True + if __debug__: + redirect=False + app = _HookeApp(gui=self, + commands=commands, + inqueue=ui_to_command_queue, + outqueue=command_to_ui_queue, + redirect=redirect) + return app + + def run(self, commands, ui_to_command_queue, command_to_ui_queue): + app = self._app(commands, ui_to_command_queue, command_to_ui_queue) + app.MainLoop()