Source code for diffpy.pdfgui.gui.mainframe

#!/usr/bin/env python
# -*- coding: ISO-8859-1 -*-
##############################################################################
#
# PDFgui            by DANSE Diffraction group
#                   Simon J. L. Billinge
#                   (c) 2006 trustees of the Michigan State University.
#                   All rights reserved.
#
# File coded by:    Chris Farrow
#
# See AUTHORS.txt for a list of people who contributed.
# See LICENSE.txt for license information.
#
##############################################################################

# generated by wxGlade 0.4 on Thu Feb 23 15:06:06 2006
"""This module contains the main window of PDFgui."""

import os.path
from configparser import ConfigParser

import wx
import wx.aui
import wx.lib.newevent

from diffpy.pdfgui.control import structureviewer
from diffpy.pdfgui.control.controlerrors import ControlError, ControlFileError
from diffpy.pdfgui.control.pdfguicontrol import pdfguicontrol
from diffpy.pdfgui.gui import pdfguiglobals
from diffpy.pdfgui.gui.aboutdialog import DialogAbout
from diffpy.pdfgui.gui.adddatapanel import AddDataPanel
from diffpy.pdfgui.gui.addphasepanel import AddPhasePanel
from diffpy.pdfgui.gui.blankpanel import BlankPanel
from diffpy.pdfgui.gui.calculationpanel import CalculationPanel
from diffpy.pdfgui.gui.datasetpanel import DataSetPanel
from diffpy.pdfgui.gui.dopingseriespanel import DopingSeriesPanel
from diffpy.pdfgui.gui.errorreportdialog import USERSMAILINGLIST, ErrorReportDialog
from diffpy.pdfgui.gui.errorwrapper import catchObjectErrors
from diffpy.pdfgui.gui.fitnotebookpanel import FitNotebookPanel
from diffpy.pdfgui.gui.fittree import FitTree, FitTreeError
from diffpy.pdfgui.gui.journalpanel import JournalPanel
from diffpy.pdfgui.gui.outputpanel import OutputPanel
from diffpy.pdfgui.gui.pdfguiglobals import docMainFile, iconpath
from diffpy.pdfgui.gui.phasenotebookpanel import PhaseNotebookPanel
from diffpy.pdfgui.gui.plotpanel import PlotPanel
from diffpy.pdfgui.gui.preferencespanel import PreferencesPanel
from diffpy.pdfgui.gui.rseriespanel import RSeriesPanel
from diffpy.pdfgui.gui.temperatureseriespanel import TemperatureSeriesPanel
from diffpy.pdfgui.gui.welcomepanel import WelcomePanel

(PDFCustomEvent, EVT_PDFCUSTOM) = wx.lib.newevent.NewEvent()

# WARNING - This file cannot be maintained with wxglade any longer. Do not make
# modifications with wxglade!!!

# README - Note that wx.TreeCtrl.GetSelections works differently in MSW than it
# does in GTK. In GTK, it returns a list of nodes as they appear in the tree.
# In MSW, it returns the list of nodes in some other order.  This can lead to
# trouble if the order of selected nodes is important to a method.
# wx.TreeControl does not create an event in windows.  Node deselection does
# not create an event on windows. There is no workaround for this.  Node
# selection vetoing does not work on windows.  Finally, changing the tree
# selection sends two selection events on windows. One for an empty selection
# and one with the new selections.


[docs] class MainFrame(wx.Frame): """The left pane is a FitTree (from fittree.py), the right is a dynamic panel, accessed via the data member rightPanel, which can hold one of any number of panels. The panels that can appear in the right pane must be derived from PDFPanel (in pdfpanel.py) and are defined in the dynamicPanels dictionary, which is defined in __customProperties. A panel is placed in the right pane by passing its dynamicPanels dictionary key to the switchRightPanel method. This method takes care of displaying the panel, giving the data it needs, and calling its refresh() method. ** NODE TYPES ** The FitTree is essential to the functionality of the Gui. The tree contains one of five types of items: "fit" -- This represents a fit that is to be run by pdffit. "dataset" -- This represents a data for a fit. "phase" -- This represents the theoretical phases needed for a dataset or a calculation. "calculation" -- This represents a calculation which is to be made from using a configured fit. Depending upon what type of item is selected in the tree, the right pane will display the properties and configuration of that item (if in "fitting mode", see below.) More on these item types is given in the documentation for the FitTree in fittree.py. See r ** MODES ** The program has various modes of operation. "fitting" -- In this mode the right pane changes depending upon what type of item is selected in the FitTree. When the fitting button is pressed, the program is in "fitting" mode. "addingdata" -- This mode is for adding data. "addingphase" -- This mode is for adding the phase "config" -- This mode is used for preferences and structure viewer configuration. "rseries" -- The mode used when configuring an r-series macro. "tseries" -- The mode used when configuring a temperature series macro. "dseries" -- The mode used when configuring a doping series macro. The mode determines how the tree and other widgets react to user interaction. The mode of the program is changed with the method setMode. This method outright enables or disables certain widgets that should not be used when in certain modes. ** DATA MEMBERS ** dynamicPanels -- The dictionary of right panels. This is used to change the right panel in the method switchRightPanel. The panels held by the dynamicPanels dictionary are listed below by their dictionary keys: * Miscellaneous panels: "blank" -- A blank panel "rseries" -- The r-series macro panel "tseries" -- The temperature series macro panel "dseries" -- The doping series macro panel "welcome" -- A welcome panel * 'fitting' mode panels "fit" -- The panel for 'fit' nodes "phase" -- The panel for 'phase' nodes "dataset" -- The panel for 'dataset' nodes "calculation" -- The panel for 'calculation' nodes * Panels specific to other program modes "adddata" -- The panel used in 'addingdata' mode "addphase" -- The panel used in 'addingphase' mode * Panels for future implementation "configuration" -- Another 'config' mode panel rightPanel -- The current right panel. fullpath -- The full path to the most recently accessed project file. workpath -- The full path to the working directory. This is modified whenever something is loaded or saved to file. It is preserved in the current session and across new projects. cP -- A python SafeConfigurationParser object. This is in charge of storing configuration information about the most recently used files list. It is also used by addphasepanel and adddatapanel to store their respective fullpath variables. The code that handles the MRU files interacts directly with cP. mode -- The current mode of the program. This is modified using the setMode method. See the MODES section above. name -- The name of the program as defined in pdfguiglobals. control -- The pdfguicontrol object needed for interfacing with the engine pdffit2 code. isAltered -- A Boolean flag that indicates when the program has been altered. This should be changed with the method needsSave so that the save menu item and toolbar button can be updated accordingly. runningDict -- A dictionary of running fits and calculations indexed by name. This dictionary is used to change the status colors of running fits and to keep the user from editing a running fit. quitting -- A boolean that is set when the program is quitting. This flag tells the error handlers to ignore any errors that take place during shutdown. """ def __init__(self, *args, **kwds): kwds["style"] = wx.DEFAULT_FRAME_STYLE wx.Frame.__init__(self, *args, **kwds) self.SetMinSize((700, 500)) self.auiManager = wx.aui.AuiManager(self) self.treeCtrlMain = FitTree( self, -1, style=wx.TR_HAS_BUTTONS | wx.TR_NO_LINES | wx.TR_EDIT_LABELS | wx.TR_MULTIPLE | wx.TR_HIDE_ROOT | wx.TR_MULTIPLE | wx.TR_DEFAULT_STYLE | wx.SUNKEN_BORDER, ) self.plotPanel = PlotPanel(self, -1) self.outputPanel = OutputPanel(self, -1) self.journalPanel = JournalPanel(self, -1) self.panelDynamic = BlankPanel(self, -1) self.__customProperties() self.Bind(wx.EVT_TREE_SEL_CHANGING, self.onTreeSelChanging, self.treeCtrlMain) self.Bind(wx.EVT_TREE_SEL_CHANGED, self.onTreeSelChanged, self.treeCtrlMain) self.Bind(wx.EVT_TREE_END_LABEL_EDIT, self.onEndLabelEdit, self.treeCtrlMain) self.Bind(wx.EVT_TREE_BEGIN_LABEL_EDIT, self.onBeginLabelEdit, self.treeCtrlMain) self.__customBindings() self.__cmdLineLoad() self.updateTitle() self.auiManager.Update() self.switchRightPanel("welcome") return # USER CONFIGURATION CODE ################################################# def __cmdLineLoad(self): """Open file loaded from the command line. This opens a file without any checking for existing projects. This should only be called after all initializations. It will open a file whose name is specified in pdfguiglobals.cmdargs. """ if pdfguiglobals.cmdargs: filename = pdfguiglobals.cmdargs[0] fullpath = os.path.abspath(filename) treelist = self.control.load(fullpath) self.treeCtrlMain.ExtendProjectTree(treelist) self.fullpath = fullpath self.workpath = os.path.dirname(fullpath) self.fileHistory.AddFileToHistory(fullpath) self.plotPanel.refresh() return def __defineLocalIds(self): """Several user functions are duplicated many times throughout the gui. This occurs mostly between the main menu, the right-click menu, and the many buttons in the gui. This method defines local Ids that can be used for all of these. """ # Functions that modify the tree. # These are used in the fitting right-click menu and the main menu. self.newFitId = wx.NewIdRef() # New Fit self.newCalcId = wx.NewIdRef() # New Calculation self.newPhaseId = wx.NewIdRef() # New Phase self.newDataId = wx.NewIdRef() # New Data Set self.deleteId = wx.ID_DELETE # Delete tree item self.copyId = wx.ID_COPY # Copy a tree item self.pasteId = wx.ID_PASTE # Paste a tree item into tree self.pasteLinkId = wx.NewIdRef() # Paste and link a fit node # Misc. functions, these are exclusive to the main menu. self.newId = wx.ID_NEW # Start a new Project self.openId = wx.ID_OPEN # Open a project self.recentId = None # Open a recent project (set later) self.saveId = wx.ID_SAVE # Save the project self.saveAsId = wx.ID_SAVEAS # Save the project as... self.quitId = wx.ID_CLOSE # Quit the program self.runFitId = wx.NewIdRef() # Run a fit self.stopFitId = wx.NewIdRef() # Stop a fit self.quickPlotId = wx.NewIdRef() # Quick plot a fit self.exportFitPDFId = wx.NewIdRef() # Save a fit PDF self.exportFitStruId = wx.NewIdRef() # Save a fit structure self.exportNewStruId = wx.NewIdRef() # Export a 'new' structure self.plotIStructId = wx.NewIdRef() # Plot initial structure self.plotFStructId = wx.NewIdRef() # Plot final structure self.printBLId = wx.NewIdRef() # Print the bond lengths of a structure self.printBAId = wx.NewIdRef() # Print the bond angles of a structure self.exportResId = wx.NewIdRef() # Save the results file self.runCalcId = wx.NewIdRef() # Run a calculation self.exportCalcPDFId = wx.NewIdRef() # Save a calculated PDF return def __customProperties(self): """Custom Properties go here.""" # Set some visual stuff icon = wx.Icon(iconpath("pdfgui.ico"), wx.BITMAP_TYPE_ANY) self.SetIcon(icon) # The panel should know its name self.name = pdfguiglobals.name # The fit tree needs a copy of the control, as # most interactions with the control happen there. self.control = pdfguicontrol(self) self.control.startQueue() self.treeCtrlMain.control = self.control # Constants needed for communication with the control self.ERROR = 1 self.UPDATE = 1 << 1 self.OUTPUT = 1 << 2 self.PLOTNOW = 1 << 3 # Needed for the error checker so it doesn't throw errors at quit time self.quitting = False # Wrap the events to use an event handler self.mainFrame = self # needed by error wrapper catchObjectErrors(self) # Needed for loading and saving self.fullpath = "" self.workpath = os.path.abspath(".") # The dictionary of running fits/calculations self.runningDict = {} # The configuration parser for getting configuration data. # self.cP = Configparser() # Long try this to avoid DuplicateSectionError and ParsingError self.cP = ConfigParser(strict=False, allow_no_value=True, interpolation=None) # Set the program mode self.mode = "fitting" # This is the dictionary of right panels. For simplicity the five panels # corresponding to the five tree item types are given the name of the # data type (fit, dataset, phase, calculation). This allows for # automatic switching of panels. self.dynamicPanels = { "blank": self.panelDynamic, "welcome": WelcomePanel(self, -1), "fit": FitNotebookPanel(self, -1), "phase": PhaseNotebookPanel(self, -1), "dataset": DataSetPanel(self, -1), "calculation": CalculationPanel(self, -1), "adddata": AddDataPanel(self, -1), "addphase": AddPhasePanel(self, -1), "preferences": PreferencesPanel(self, -1), "rseries": RSeriesPanel(self, -1), "tseries": TemperatureSeriesPanel(self, -1), "dseries": DopingSeriesPanel(self, -1), } # Prepare the right pane. Display the welcome screen. self.rightPanel = self.panelDynamic for key in self.dynamicPanels: self.auiManager.AddPane( self.dynamicPanels[key], wx.aui.AuiPaneInfo() .Name(key) .CenterPane() .BestSize(wx.Size(400, 380)) .MinSize(wx.Size(190, 200)) .Hide(), ) self.dynamicPanels[key].mainFrame = self self.dynamicPanels[key].treeCtrlMain = self.treeCtrlMain self.dynamicPanels[key].cP = self.cP self.dynamicPanels[key].key = key self.dynamicPanels[key].Enable(False) # Do the same for the plotPanel and journalPanel self.plotPanel.mainFrame = self self.plotPanel.treeCtrlMain = self.treeCtrlMain self.plotPanel.cP = self.cP self.plotPanel.Enable(False) self.journalPanel.mainFrame = self self.journalPanel.treeCtrlMain = self.treeCtrlMain self.journalPanel.cP = self.cP # Position other panels. Note that currently MinimizeButton does not do # anything. It is to be implemented in future versions of wx.aui self.auiManager.AddPane( self.outputPanel, wx.aui.AuiPaneInfo() .Name("outputPanel") .Caption("PDFfit2 Output") .Bottom() .TopDockable() .BottomDockable() .LeftDockable() .RightDockable() .MinimizeButton() .BestSize(wx.Size(400, 40)) .MinSize(wx.Size(200, 40)), ) self.auiManager.AddPane( self.treeCtrlMain, wx.aui.AuiPaneInfo() .Name("treeCtrlMain") .Caption("Fit Tree") .Left() .TopDockable() .BottomDockable() .LeftDockable() .RightDockable() .MinimizeButton() .BestSize(wx.Size(200, 100)) .MinSize(wx.Size(200, 40)), ) self.auiManager.AddPane( self.plotPanel, wx.aui.AuiPaneInfo() .Name("plotPanel") .Caption("Plot Control") .Left() .TopDockable() .BottomDockable() .LeftDockable() .RightDockable() .MinimizeButton() .BestSize(wx.Size(200, 250)) .MinSize(wx.Size(200, 150)), ) self.auiManager.AddPane( self.journalPanel, wx.aui.AuiPaneInfo() .Name("journalPanel") .Caption("Project Journal") .TopDockable() .BottomDockable() .LeftDockable() .RightDockable() .MinimizeButton() .Hide() .BestSize(wx.Size(450, 450)) .MinSize(wx.Size(200, 200)) .FloatingSize(wx.Size(450, 450)) .Float(), ) # Continue with initialization self.__defineLocalIds() # Ids for menu items self.__setupMainMenu() # Make the main menu self.__setupToolBar() # Make the toolbar self.treeCtrlMain.InitializeTree() # Initialize the tree # Load the configuration self.loadConfiguration() # Set the state of the program self.needsSave(False) return def __setupMainMenu(self): """This sets up the menu in the main frame.""" self.menulength = 8 self.menuBar = wx.MenuBar() self.SetMenuBar(self.menuBar) # File Menu self.fileMenu = wx.Menu() self.newItem = wx.MenuItem(self.fileMenu, self.newId, "&New Project\tCtrl+n", "", wx.ITEM_NORMAL) self.fileMenu.Append(self.newItem) self.openItem = wx.MenuItem(self.fileMenu, self.openId, "&Open Project\tCtrl+o", "", wx.ITEM_NORMAL) self.fileMenu.Append(self.openItem) self.recentMenu = wx.Menu() msub = self.fileMenu.AppendSubMenu(self.recentMenu, "&Recent Files") self.recentId = msub.Id self.fileMenu.AppendSeparator() self.saveItem = wx.MenuItem(self.fileMenu, self.saveId, "&Save Project\tCtrl+s", "", wx.ITEM_NORMAL) self.fileMenu.Append(self.saveItem) self.saveAsItem = wx.MenuItem( self.fileMenu, self.saveAsId, "Save Project &as\tCtrl+Shift+s", "", wx.ITEM_NORMAL, ) self.fileMenu.Append(self.saveAsItem) self.fileMenu.AppendSeparator() self.quitItem = wx.MenuItem(self.fileMenu, self.quitId, "&Quit\tCtrl+q", "", wx.ITEM_NORMAL) self.fileMenu.Append(self.quitItem) self.menuBar.Append(self.fileMenu, "&File") # End File Menu # Edit Menu self.editMenu = wx.Menu() self.delItem = wx.MenuItem(self.editMenu, self.deleteId, "&Delete Item(s)\tCtrl+X", "", wx.ITEM_NORMAL) self.editMenu.Append(self.delItem) self.copyItem = wx.MenuItem(self.editMenu, self.copyId, "&Copy Item\tCtrl+C", "", wx.ITEM_NORMAL) self.editMenu.Append(self.copyItem) self.pasteItem = wx.MenuItem(self.editMenu, self.pasteId, "&Paste Item\tCtrl+V", "", wx.ITEM_NORMAL) self.editMenu.Append(self.pasteItem) self.pasteLinkItem = wx.MenuItem(self.editMenu, self.pasteLinkId, "Paste &Linked Fit", "", wx.ITEM_NORMAL) self.editMenu.Append(self.pasteLinkItem) self.editMenu.AppendSeparator() self.prefItem = wx.MenuItem(self.editMenu, wx.NewIdRef(), "&Preferences", "", wx.ITEM_NORMAL) self.editMenu.Append(self.prefItem) self.menuBar.Append(self.editMenu, "&Edit") # End Edit Menu # View Menu self.viewMenu = wx.Menu() self.defaultLayoutItem = wx.MenuItem( self.editMenu, wx.NewIdRef(), "Default Window Layout", "", wx.ITEM_NORMAL ) self.viewMenu.Append(self.defaultLayoutItem) self.viewMenu.AppendSeparator() # These items are context sensitive. self.showFitItem = wx.MenuItem(self.viewMenu, wx.NewIdRef(), "Show Fit Tree", "", wx.ITEM_NORMAL) self.viewMenu.Append(self.showFitItem) self.showPlotItem = wx.MenuItem(self.viewMenu, wx.NewIdRef(), "Show Plot Control", "", wx.ITEM_NORMAL) self.viewMenu.Append(self.showPlotItem) self.showOutputItem = wx.MenuItem(self.viewMenu, wx.NewIdRef(), "Show Output", "", wx.ITEM_NORMAL) self.viewMenu.Append(self.showOutputItem) self.showJournalItem = wx.MenuItem( self.viewMenu, wx.NewIdRef(), "Show Journal\tCtrl+j", "", wx.ITEM_NORMAL ) self.viewMenu.Append(self.showJournalItem) self.menuBar.Append(self.viewMenu, "&View") # Fits Menu self.fitsMenu = wx.Menu() self.newFitItem = wx.MenuItem(self.fitsMenu, self.newFitId, "&New Fit\tCtrl+t", "", wx.ITEM_NORMAL) self.fitsMenu.Append(self.newFitItem) self.fitsMenu.AppendSeparator() self.runFitItem = wx.MenuItem(self.fitsMenu, self.runFitId, "&Run Selected Fits", "", wx.ITEM_NORMAL) self.fitsMenu.Append(self.runFitItem) self.stopFitItem = wx.MenuItem(self.fitsMenu, self.stopFitId, "&Stop Fitting", "", wx.ITEM_NORMAL) self.fitsMenu.Append(self.stopFitItem) self.fitsMenu.AppendSeparator() self.expResItem = wx.MenuItem(self.fitsMenu, self.exportResId, "Export Resu&lts File", "", wx.ITEM_NORMAL) self.fitsMenu.Append(self.expResItem) self.fitsMenu.AppendSeparator() # Macros sub-menu self.macrosMenu = wx.Menu() self.rseriesItem = wx.MenuItem(self.macrosMenu, wx.NewIdRef(), "r-Series", "", wx.ITEM_NORMAL) self.macrosMenu.Append(self.rseriesItem) self.tseriesItem = wx.MenuItem(self.macrosMenu, wx.NewIdRef(), "Temperature Series", "", wx.ITEM_NORMAL) self.macrosMenu.Append(self.tseriesItem) self.dseriesItem = wx.MenuItem(self.macrosMenu, wx.NewIdRef(), "Doping Series", "", wx.ITEM_NORMAL) self.macrosMenu.Append(self.dseriesItem) self.fitsMenu.AppendSubMenu(self.macrosMenu, "Macros") self.menuBar.Append(self.fitsMenu, "Fi&ts") # End Fits Menu # Phases Menu self.phasesMenu = wx.Menu() self.newPhaseItem = wx.MenuItem(self.phasesMenu, self.newPhaseId, "&New Phase\tCtrl+p", "", wx.ITEM_NORMAL) self.phasesMenu.Append(self.newPhaseItem) self.phasesMenu.AppendSeparator() self.printBLItem = wx.MenuItem( self.phasesMenu, self.printBLId, "Calculate bond lengths", "", wx.ITEM_NORMAL, ) self.phasesMenu.Append(self.printBLItem) self.printBAItem = wx.MenuItem( self.phasesMenu, self.printBAId, "Calculate bond angles", "", wx.ITEM_NORMAL ) self.phasesMenu.Append(self.printBAItem) self.phasesMenu.AppendSeparator() self.expNewPhaseItem = wx.MenuItem( self.phasesMenu, self.exportNewStruId, "Export &Selected Phase", "", wx.ITEM_NORMAL, ) self.phasesMenu.Append(self.expNewPhaseItem) self.expStruItem = wx.MenuItem( self.fitsMenu, self.exportFitStruId, "&Export Fit Structure", "", wx.ITEM_NORMAL, ) self.phasesMenu.Append(self.expStruItem) self.phasesMenu.AppendSeparator() self.plotIStructItem = wx.MenuItem( self.phasesMenu, self.plotIStructId, "&Plot Initial Structure", "", wx.ITEM_NORMAL, ) self.phasesMenu.Append(self.plotIStructItem) self.plotFStructItem = wx.MenuItem( self.phasesMenu, self.plotFStructId, "&Plot Final Structure", "", wx.ITEM_NORMAL, ) self.phasesMenu.Append(self.plotFStructItem) self.menuBar.Append(self.phasesMenu, "&Phases") # End Phases Menu # Data Menu self.dataMenu = wx.Menu() self.newDataItem = wx.MenuItem(self.dataMenu, self.newDataId, "&New Data Set\tCtrl+d", "", wx.ITEM_NORMAL) self.dataMenu.Append(self.newDataItem) self.dataMenu.AppendSeparator() self.expFitPDFItem = wx.MenuItem(self.fitsMenu, self.exportFitPDFId, "&Export Fit PDF", "", wx.ITEM_NORMAL) self.dataMenu.Append(self.expFitPDFItem) self.menuBar.Append(self.dataMenu, "&Data") # End Data Menu # Calculations Menu self.calcMenu = wx.Menu() self.newCalcItem = wx.MenuItem( self.calcMenu, self.newCalcId, "&New Calculation\tCtrl+l", "", wx.ITEM_NORMAL, ) self.calcMenu.Append(self.newCalcItem) self.calcMenu.AppendSeparator() self.runCalcItem = wx.MenuItem( self.calcMenu, self.runCalcId, "&Run Selected Calculation", "", wx.ITEM_NORMAL, ) self.calcMenu.Append(self.runCalcItem) self.calcMenu.AppendSeparator() self.expCalcPDFItem = wx.MenuItem( self.calcMenu, self.exportCalcPDFId, "&Export Selected Calculation", "", wx.ITEM_NORMAL, ) self.calcMenu.Append(self.expCalcPDFItem) self.menuBar.Append(self.calcMenu, "Ca&lculations") # End Calculations Menu # Help Menu self.helpMenu = wx.Menu() self.docItem = wx.MenuItem(self.helpMenu, wx.NewIdRef(), "&Documentation\tF1", "", wx.ITEM_NORMAL) self.helpMenu.Append(self.docItem) self.requestItem = wx.MenuItem( self.helpMenu, wx.NewIdRef(), "Request a Feature / Report a Bug", "", wx.ITEM_NORMAL, ) self.helpMenu.Append(self.requestItem) self.communityItem = wx.MenuItem(self.helpMenu, wx.NewIdRef(), "PDFgui Community", "", wx.ITEM_NORMAL) self.helpMenu.Append(self.communityItem) self.aboutItem = wx.MenuItem(self.helpMenu, wx.NewIdRef(), "&About", "", wx.ITEM_NORMAL) self.helpMenu.Append(self.aboutItem) self.menuBar.Append(self.helpMenu, "&Help") # End Help Menu # For managing MRUs self.fileHistory = wx.FileHistory(pdfguiglobals.MAXMRU) self.fileHistory.UseMenu(self.recentMenu) return def __setupToolBar(self): """This sets up the tool bar in the parent window.""" self.toolBar = self.CreateToolBar() size = (16, 16) bitmap = wx.ArtProvider.GetBitmap(wx.ART_NEW, wx.ART_TOOLBAR, size) self.toolBar.AddTool( self.newId, "New Project", bitmap, wx.NullBitmap, wx.ITEM_NORMAL, "Start a new project", ) bitmap = wx.ArtProvider.GetBitmap(wx.ART_FILE_OPEN, wx.ART_TOOLBAR, size) self.toolBar.AddTool( self.openId, "Open Project", bitmap, wx.NullBitmap, wx.ITEM_NORMAL, "Open an existing project", ) bitmap = wx.ArtProvider.GetBitmap(wx.ART_FILE_SAVE, wx.ART_TOOLBAR, size) self.toolBar.AddTool( self.saveId, "Save Project", bitmap, wx.NullBitmap, wx.ITEM_NORMAL, "Save this project", ) self.toolBar.AddSeparator() # This fixes the shadowing problem on Windows. # The bitmap has a white transparency color (mask) maskcolor = wx.Colour(red=255, green=255, blue=255) bitmap = wx.Bitmap(iconpath("run.png")) bitmap.SetSize(size) mask = wx.Mask(bitmap, maskcolor) bitmap.SetMask(mask) self.toolBar.AddTool( self.runFitId, "Start", bitmap, wx.NullBitmap, wx.ITEM_NORMAL, "Start a fit or calculation", ) bitmap = wx.Bitmap(iconpath("stop.png")) bitmap.SetSize(size) mask = wx.Mask(bitmap, maskcolor) bitmap.SetMask(mask) self.toolBar.AddTool( self.stopFitId, "Stop", bitmap, wx.NullBitmap, wx.ITEM_NORMAL, "Stop running fits or calculations", ) self.toolBar.AddSeparator() bitmap = wx.Bitmap(iconpath("datasetitem.png")) bitmap.SetSize(size) self.toolBar.AddTool( self.quickPlotId, "Quick plot", bitmap, wx.NullBitmap, wx.ITEM_NORMAL, "Plot PDF or structure", ) self.toolBar.Realize() return def __customBindings(self): """Custom user bindings go here. These bindings are not present in wxglade. """ # Allow a general right-click to work on the tree self.treeCtrlMain.Bind(wx.EVT_RIGHT_DOWN, self.onRightClick) # Double-click select all type on tree # FIXME - this doesn't work, I suspect the problem is with the tree # selection code. # self.treeCtrlMain.Bind(wx.EVT_LEFT_DCLICK, self.onDoubleClick2) # self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.onDoubleClick, self.treeCtrlMain) # Middle-click quickplot self.Bind(wx.EVT_TREE_ITEM_MIDDLE_CLICK, self.onMiddleClick, self.treeCtrlMain) # Catch key events for the tree self.treeCtrlMain.Bind(wx.EVT_KEY_DOWN, self.onKey) # Catch the close event self.Bind(wx.EVT_CLOSE, self.onQuit) # Use the custom event to pop up error messages self.Bind(EVT_PDFCUSTOM, self.onCustom) # Do bindings for menu items self.__menuBindings() self.__fittingRightMenuBindings() return def __menuBindings(self): """Setup bindings for the main menu and toolbar. Since all toolbar functions use the same Ids as main menu items, the toolbar events do not need their own bindings. """ # File Menu self.Bind(wx.EVT_MENU, self.onNew, id=self.newId) self.Bind(wx.EVT_MENU, self.onOpen, id=self.openId) self.Bind(wx.EVT_MENU, self.onSave, id=self.saveId) self.Bind(wx.EVT_MENU, self.onSaveAs, id=self.saveAsId) self.Bind(wx.EVT_MENU, self.onQuit, id=self.quitId) # For recent items self.Bind(wx.EVT_MENU_RANGE, self.onMRUFile, id=wx.ID_FILE1, id2=wx.ID_FILE5) # Edit Menu self.Bind(wx.EVT_MENU, self.onDelete, id=self.deleteId) self.Bind(wx.EVT_MENU, self.onCopy, id=self.copyId) self.Bind(wx.EVT_MENU, self.onPaste, id=self.pasteId) self.Bind(wx.EVT_MENU, self.onPasteLink, id=self.pasteLinkId) self.Bind(wx.EVT_MENU, self.onPreferences, self.prefItem) # View menu self.Bind(wx.EVT_MENU, self.onDefaultLayout, self.defaultLayoutItem) self.Bind(wx.EVT_MENU, self.onShowFit, self.showFitItem) self.Bind(wx.EVT_MENU, self.onShowPlot, self.showPlotItem) self.Bind(wx.EVT_MENU, self.onShowOutput, self.showOutputItem) self.Bind(wx.EVT_MENU, self.onShowJournal, self.showJournalItem) # Fits Menu self.Bind(wx.EVT_MENU, self.onNewFit, id=self.newFitId) self.Bind(wx.EVT_MENU, self.onRun, id=self.runFitId) self.Bind(wx.EVT_MENU, self.onStop, id=self.stopFitId) self.Bind(wx.EVT_MENU, self.onExportRes, id=self.exportResId) self.Bind(wx.EVT_MENU, self.onRSeries, self.rseriesItem) self.Bind(wx.EVT_MENU, self.onTSeries, self.tseriesItem) self.Bind(wx.EVT_MENU, self.onDSeries, self.dseriesItem) # Macros are inserted individually # Phases Menu self.Bind(wx.EVT_MENU, self.onInsPhase, id=self.newPhaseId) self.Bind(wx.EVT_MENU, self.onPrintBL, id=self.printBLId) self.Bind(wx.EVT_MENU, self.onPrintBA, id=self.printBAId) self.Bind(wx.EVT_MENU, self.onExportNewStruct, id=self.exportNewStruId) self.Bind(wx.EVT_MENU, self.onExportStruct, id=self.exportFitStruId) self.Bind(wx.EVT_MENU, self.onPlotIStruct, id=self.plotIStructId) self.Bind(wx.EVT_MENU, self.onPlotFStruct, id=self.plotFStructId) # Data Menu self.Bind(wx.EVT_MENU, self.onInsData, id=self.newDataId) self.Bind(wx.EVT_MENU, self.onExportPDF, id=self.exportFitPDFId) # Calculations Menu self.Bind(wx.EVT_MENU, self.onInsCalc, id=self.newCalcId) self.Bind(wx.EVT_MENU, self.onRun, id=self.runCalcId) self.Bind(wx.EVT_MENU, self.onSaveCalc, id=self.exportCalcPDFId) # Help Menu self.Bind(wx.EVT_MENU, self.onDocumentation, self.docItem) self.Bind(wx.EVT_MENU, self.onAbout, self.aboutItem) self.Bind(wx.EVT_MENU, self.onRequest, self.requestItem) self.Bind(wx.EVT_MENU, self.onCommunity, self.communityItem) # The generic menu-check. self.Bind(wx.EVT_MENU_OPEN, self.onMainMenu) # Toolbar events that have no menu item self.Bind(wx.EVT_MENU, self.onQuickPlot, id=self.quickPlotId) return def __fittingRightMenuBindings(self): """Bindings for the fitting-mode right-click menu.""" self.Bind(wx.EVT_MENU, self.onNewFit, id=self.newFitId) self.Bind(wx.EVT_MENU, self.onCopy, id=self.copyId) self.Bind(wx.EVT_MENU, self.onPaste, id=self.pasteId) self.Bind(wx.EVT_MENU, self.onPasteLink, id=self.pasteLinkId) self.Bind(wx.EVT_MENU, self.onInsPhase, id=self.newPhaseId) self.Bind(wx.EVT_MENU, self.onInsData, id=self.newDataId) self.Bind(wx.EVT_MENU, self.onInsCalc, id=self.newCalcId) self.Bind(wx.EVT_MENU, self.onDelete, id=self.deleteId) return # UTILITY FUNCTIONS ######################################################
[docs] def switchRightPanel(self, paneltype): """Switch the panel which is visible in the right hand side. This sets any panel-specific data and calls the refresh() method of the new rightPanel. All right panels must be derived from wxPanel and PDFPanel (in pdfpanel module). Inputs: paneltype -- The code used in self.dynamicPanels that indicates the panel to be displayed. If paneltype is None, the blank panel is displayed. """ self.rightPanel.Enable(False) self.plotPanel.Enable(False) for key in self.dynamicPanels: self.auiManager.GetPane(key).Hide() # Why doesn't this work? # key = self.rightPanel.key # self.auiManager.GetPane(key).Hide() if paneltype is None: paneltype = "blank" self.rightPanel = self.dynamicPanels[paneltype] self.rightPanel.Enable(True) self.setPanelSpecificData(paneltype) self.rightPanel.refresh() paneinfo = self.auiManager.GetPane(paneltype) paneinfo.Show() self.auiManager.Update() selections = self.treeCtrlMain.GetSelections() if len(selections) == 1: self.plotPanel.Enable(True) return
[docs] def setPanelSpecificData(self, paneltype): """This method sets the panel specific data for the right panel. This method gets panel-specific data and sends it to the rightPanel. The different types of data assignment are listed below. "fit" type: * Give the fit object to the panel "phase" type: * initialize constraints dictionary and configuration and results * Structure objects. "dataset" type: * initialize configuration, constraints, and results objects "calculation" type: * Give the calculation object to the panel "rseries" type: * Give the fit object to the panel "tseries" type: * Give the fit object to the panel "dseries" type: * Give the fit object to the panel """ selections = self.treeCtrlMain.GetSelections() if len(selections) == 1: node = selections[0] dataobject = self.treeCtrlMain.GetControlData(node) if paneltype == "phase": self.rightPanel.configuration = dataobject.initial self.rightPanel.constraints = dataobject.constraints self.rightPanel.results = dataobject.refined elif paneltype == "dataset": self.rightPanel.configuration = dataobject self.rightPanel.constraints = dataobject.constraints self.rightPanel.results = dataobject.refined elif paneltype == "fit": dataobject.updateParameters() self.rightPanel.fit = dataobject elif paneltype == "calculation": self.rightPanel.calculation = dataobject elif paneltype == "rseries": self.rightPanel.fit = dataobject elif paneltype == "tseries": self.rightPanel.fit = dataobject elif paneltype == "dseries": self.rightPanel.fit = dataobject return
[docs] def setMode(self, mode): """Set the mode of the program. This method takes care of any widget properties that must change when the mode is changed. If the mode is changing due to the change in the right panel, always call setMode before switchRightPanel. "fitting" mode: * treeCtrlMain is enabled * plotPanel panel is enabled * toolBar is enabled * menuBar is enabled "addingdata" mode: "addingphase" mode: "config" mode: * treeCtrlMain is disabled * plotPanel panel is disabled * toolBar is disabled * menuBar is disabled "rseries" mode: "tseries" mode: "dseries" mode: * treeCtrlMain is enabled * plotPanel panel is disabled * toolBar is disabled * menuBar is disabled """ self.mode = mode if mode == "fitting": self.treeCtrlMain.Enable(True) self.plotPanel.Enable(True) self.toolBar.Enable(True) for i in range(self.menulength): self.menuBar.EnableTop(i, True) elif mode in ["addingdata", "addingphase", "config"]: self.treeCtrlMain.Enable(False) self.plotPanel.Enable(False) self.toolBar.Enable(False) for i in range(self.menulength): self.menuBar.EnableTop(i, False) elif mode in ["rseries", "tseries", "dseries"]: self.treeCtrlMain.Enable(True) self.plotPanel.Enable(False) self.toolBar.Enable(False) for i in range(self.menulength): self.menuBar.EnableTop(i, False) return
[docs] def loadConfiguration(self): """Load the configuration from file. The MRU list is handled by the local member fileHistory, which is a wxFileHistory object. """ # Get MRU information localpath = os.path.expanduser(pdfguiglobals.configfilename) if os.path.exists(localpath): self.cP.read(localpath) for i in range(pdfguiglobals.MAXMRU, 0, -1): if self.cP.has_option("MRU", str(i)): filename = self.cP.get("MRU", str(i)) if filename: self.fileHistory.AddFileToHistory(filename) # Import perspective from last session if self.cP.has_section("PERSPECTIVE"): if self.cP.has_option("PERSPECTIVE", "last"): perspective = self.cP.get("PERSPECTIVE", "last") self.auiManager.LoadPerspective(perspective) else: from diffpy.pdfgui.gui.windowperspective import default self.auiManager.LoadPerspective(default) # Load the window dimensions w = 800 h = 600 if self.cP.has_option("SIZE", "width"): w = self.cP.get("SIZE", "width") w = int(w) if self.cP.has_option("SIZE", "height"): h = self.cP.get("SIZE", "height") h = int(h) self.SetSize((w, h)) # Load structure viewer information and put this in the configure panel viewerconfig = {} if self.cP.has_section("STRUCTUREVIEWER"): viewerconfig = dict(self.cP.items("STRUCTUREVIEWER")) viewer = structureviewer.getStructureViewer() viewer.setConfig(viewerconfig) return
[docs] def updateConfiguration(self): """Update the configuration information. This updates the 'MRU' section of the configuration. """ # Most recently used list if not self.cP.has_section("MRU"): self.cP.add_section("MRU") for i in range(self.fileHistory.GetCount()): item = self.fileHistory.GetHistoryFile(i) self.cP.set("MRU", str(i + 1), item) # Window size if not self.cP.has_section("SIZE"): self.cP.add_section("SIZE") w, h = self.GetSize() self.cP.set("SIZE", "width", str(w)) self.cP.set("SIZE", "height", str(h)) # Frame layout if not self.cP.has_section("PERSPECTIVE"): self.cP.add_section("PERSPECTIVE") perspective = self.auiManager.SavePerspective() self.cP.set("PERSPECTIVE", "last", perspective) # Set the structure viewer information if not self.cP.has_section("STRUCTUREVIEWER"): self.cP.add_section("STRUCTUREVIEWER") viewer = structureviewer.getStructureViewer() viewerconfig = viewer.getConfig() for key, value in viewerconfig.items(): self.cP.set("STRUCTUREVIEWER", key, value) return
[docs] def writeConfiguration(self): """Write the program configuration to file.""" filename = os.path.expanduser(pdfguiglobals.configfilename) oflags = os.O_CREAT | os.O_WRONLY try: with os.fdopen(os.open(filename, oflags, 0o600), "w") as outfile: self.cP.write(outfile) except IOError: emsg = "Cannot write configuration file %r" % filename raise ControlFileError(emsg) return
[docs] def checkForSave(self): """Pop up a dialog if the project needs to be saved. returns: wx.ID_YES if the user chose to save the project. wx.ID_NO if the user chose not to save the project. wx.ID_CANCEL if they changed their mind about their action. """ code = wx.ID_NO # disable when requested in dbopts if pdfguiglobals.dbopts.noconfirm: return code if pdfguiglobals.isAltered: d = wx.MessageDialog( self, "Would you like to save this session?", "Save?", wx.YES_NO | wx.CANCEL, ) code = d.ShowModal() if code == wx.ID_YES: code = self.onSave(None) d.Destroy() return code
[docs] def updateTitle(self): """Update the title according to the name of the current file.""" shorttitle = os.path.basename(self.fullpath) udirnamed = "~" + os.path.sep udir = os.path.expanduser(udirnamed) if shorttitle: namedpath = self.fullpath if namedpath.startswith(udir): namedpath = namedpath.replace(udir, udirnamed) fulltitle = "%s (%s) - %s" % (shorttitle, namedpath, self.name) else: fulltitle = self.name self.SetTitle(fulltitle) return
# MAIN PANEL EVENT CODE #######################################################
[docs] def onMainMenu(self, event): """Prepare the main menu whenever it is activated.""" self.disableMainMenuItems() return
[docs] def makeTreeSelection(self, node): """Manually select a node of the tree and update according to selection. This makes sure that the node is visible after selection. If node is None, this does nothing. """ if node is None: return self.treeCtrlMain.SelectItem(node) # Make sure that the node is visible. self.treeCtrlMain.SetFocus() self.treeCtrlMain.EnsureVisible(node) self.treeCtrlMain.ScrollTo(node) self.treeSelectionUpdate(node) # The right-panel probably stole focus, but we want it back. self.treeCtrlMain.SetFocus() return
[docs] def treeSelectionUpdate(self, node): """Update the widgets based on a tree selection. "fitting" mode: * Right panel changes depending upon the type of item selected from the tree. "rseries", "tseries", "dseries" mode: * The behavior is defined in the associated panel """ selections = self.treeCtrlMain.GetSelections() # "fitting" mode if self.mode == "fitting": # This doesn't work on Windows. self.plotPanel.Enable(True) if len(selections) == 0: self.switchRightPanel("blank") self.plotPanel.Enable(False) # return elif len(selections) == 1: self.rightPanel.Enable() selectiontype = self.treeCtrlMain.GetNodeType(selections[0]) self.switchRightPanel(selectiontype) else: self.rightPanel.Enable(False) self.plotPanel.Enable(True) # Don't let the user edit the right panel of a running fit. fp = self.treeCtrlMain.GetFitRoot(node) if fp: name = self.treeCtrlMain.GetItemText(fp) if name in self.runningDict: self.rightPanel.Enable(False) elif self.mode in ["rseries", "tseries", "dseries"]: self.rightPanel.treeSelectionUpdate(node) # Update the plotPanel self.plotPanel.refresh() # update the toolbar and menu self.updateToolbar() if self.runningDict: self.disableMainMenuItems() return
[docs] def onTreeSelChanged(self, event): """Set the click behavior for each mode.""" node = event.GetItem() self.treeSelectionUpdate(node) return
[docs] def onTreeSelChanging(self, event): # wxGlade: MainPanel.<event_handler> """Set the click behavior for each mode. Note that this doesn't work on Windows. Be sure to build in redundancy so that the program behaves as if this does not even get called. If the Windows bug does not get fixed, this method will probably be eliminated. "addingdata" mode: * can select nothing "addingphase" mode: * can select nothing "config" mode: * can select nothing "rseries" mode: * can only select fit items "tseries" mode: * can only select fit items "dseries" mode: * can only select fit items """ # THIS DOESN'T WORK ON WINDOWS! node = event.GetItem() if not node: return if self.mode in ["addingdata", "addingphase", "config"]: event.Veto() elif self.mode in ["rseries", "tseries", "dseries"]: nodetype = self.treeCtrlMain.GetNodeType(node) if nodetype != "fit": event.Veto() return
[docs] def onBeginLabelEdit(self, event): # wxGlade: MainPanel.<event_handler> """Veto editing of some items and in some modes. The following editing attempts are Veto()'d * Editing any item in "addingdata", "addingphase", or "config" mode. """ nodetype = self.treeCtrlMain.GetNodeType(event.GetItem()) # silence the pyflakes syntax checker assert nodetype or True if self.mode != "fitting": event.Veto() return
[docs] def onEndLabelEdit(self, event): # wxGlade: MainPanel.<event_handler> """Allow only certain types of renaming. The following rename attempts are Veto()'d * Giving a node the same name as a sibling. Cousins can share names. * Giving a node the name ''. Everything needs a name. """ label = event.GetLabel() # No '' if label.strip() == "": event.Veto() return # No sibling's sharing the same name. (Sorry, George Foreman.) node = event.GetItem() siblings = [self.treeCtrlMain.GetItemText(id) for id in self.treeCtrlMain.GetSiblings(node)] if label in siblings: event.Veto() return # Notify the control of the rename cdata = self.treeCtrlMain.GetControlData(node) self.control.rename(cdata, label) self.needsSave() return
[docs] def onRightClick(self, event): # wxGlade: MainPanel.<event_handler> """Bring up the right-click menu. This menu can give a different menu depending upon which mode the program is in. It can even give a different menu depending upon what part of the tree is selected. "fitting" mode The menu appears for right-clicks both on and off of tree items. The menu has the following items: "New Fit" -- Append a new fit from anywhere on the tree. "Copy" -- Copy a fit, phase, dataset, or calc branch "Paste" -- Paste a copied branch into the tree. Branches of a given type can only be pasted in certain places. "Delete" -- Delete a branch from the tree. "Insert Phase" -- Insert a phase in the phase section of a fit or calculation branch. "Insert Data Set" -- Insert a dataset in the dataset section of a fit branch. "Insert Calculation"-- Insert a calculation in the calculation section of a fit. The menu appears for right-clicks both on and off of tree items. The menu has the following items: "Select All" -- The tree automatically selects all items of the type the user clicked on. """ # This menu is disabled the if item is part of a running fit. selections = self.treeCtrlMain.GetSelections() if selections: node = self.treeCtrlMain.GetFitRoot(selections[0]) nodetype = self.treeCtrlMain.GetNodeType(node) # silence the pyflakes syntax checker assert nodetype or True if node in self.runningDict.values(): return if self.mode == "fitting": # The menu Ids are defined in __defineLocalIds. menu = wx.Menu() menu.Append(self.newFitId, "New Fit") menu.AppendSeparator() menu.Append(self.copyId, "Copy") menu.Append(self.pasteId, "Paste") menu.Append(self.pasteLinkId, "Paste Linked Fit") menu.Append(self.deleteId, "Delete") menu.AppendSeparator() menu.Append(self.newPhaseId, "Insert Phase") menu.Append(self.newDataId, "Insert Data Set") menu.Append(self.newCalcId, "Insert Calculation") # Get the item we've right clicked on itemtype = None x = event.GetX() y = event.GetY() node, flags = self.treeCtrlMain.HitTest((x, y)) if flags in [ wx.TREE_HITTEST_ABOVE, wx.TREE_HITTEST_BELOW, wx.TREE_HITTEST_NOWHERE, ]: # The hit is not on an item. self.treeCtrlMain.UnselectAll() self.switchRightPanel("blank") selections = [] # Select the item with a right click, but don't add it to an # existing selection. elif node not in selections: self.treeCtrlMain.UnselectAll() self.treeCtrlMain.SelectItem(node) selections = [node] itemtype = self.treeCtrlMain.GetNodeType(node) # silence the pyflakes syntax checker assert itemtype or True # Enable/Disable certain entries based upon where we clicked. self.disableSharedMenuItems(menu) else: return # Bring up the popup menu. Must have the coordinates of the right-click # event that summoned the menu. # This is to position the menu correctly on a floating frame. # wx.treeCtrlMain.GetPosition() will return (0,0) if the frame is # not docked. This is a bit of a hack, since pane.floating_pos is not # designed to be a public attribute. pane = self.auiManager.GetPane("treeCtrlMain") (x0, y0) = self.treeCtrlMain.GetPosition() if pane.IsFloating(): (x0, y0) = self.ScreenToClient(pane.floating_pos) self.PopupMenu(menu, (x0 + x, y0 + y)) menu.Destroy() return
[docs] def onMiddleClick(self, event): """Quickplot on item middle click.""" node = event.GetItem() self.treeCtrlMain.UnselectAll() self.treeCtrlMain.SelectItem(node) self.onQuickPlot(None) return
[docs] def onDoubleClick2(self, event): """Select-all type on item double click.""" x = event.GetX() y = event.GetY() node, flags = self.treeCtrlMain.HitTest((x, y)) if flags not in [ wx.TREE_HITTEST_ABOVE, wx.TREE_HITTEST_BELOW, wx.TREE_HITTEST_NOWHERE, ]: if self.mode == "fitting": wx.CallAfter(self.treeCtrlMain.SelectAllType, node) wx.CallAfter(self.treeSelectionUpdate, node) return
[docs] def onDoubleClick(self, event): """Select-all type on item double click.""" node = event.GetItem() if self.mode == "fitting": wx.CallAfter(self.treeCtrlMain.SelectAllType, node) wx.CallAfter(self.treeSelectionUpdate, node) return
[docs] def onKey(self, event): """Catch key events in the panel.""" # See if the tree is in focus. If not, pass the event on key = event.GetKeyCode() selections = self.treeCtrlMain.GetSelections() node = None if selections: node = selections[0] # Shift+Ctrl+A # "fitting" mode -- Select all nodes of a given type if event.ShiftDown() and event.ControlDown() and key == 65: if self.mode == "fitting": self.treeCtrlMain.SelectAllType(node) self.treeSelectionUpdate(node) # Ctrl+A # "fitting" mode -- Select all nodes elif event.ControlDown() and key == 65: if self.mode == "fitting": self.treeCtrlMain.SelectAll() self.treeSelectionUpdate(node) # XXX - removed - Without undo functionality, this is too dangerous. # Delete # "fitting" mode -- Delete selected noded # elif key == 127: # if self.mode == "fitting": # self.onDelete(None) # Tab # Move to the right panel elif key == 9: self.rightPanel.SetFocus() else: event.Skip() return
# MENU RELATED FUNCTIONS ##################################################
[docs] def updateToolbar(self): """Update the toolbar based upon the status of the program.""" self.toolBar.EnableTool(self.saveId, (pdfguiglobals.isAltered and self.runningDict == {})) itemtype = None selections = self.treeCtrlMain.GetSelections() if selections: itemtype = self.treeCtrlMain.GetNodeType(selections[0]) # This is redundant, but easy to maintain if self.mode == "fitting": # Quickplot if len(selections) == 1 and itemtype and itemtype != "fit": self.toolBar.EnableTool(self.quickPlotId, True) else: self.toolBar.EnableTool(self.quickPlotId, False) # Fit run/stop if not self.runningDict: # No fit is running self.toolBar.EnableTool(self.stopFitId, False) # We can run a fit if there are any selections if selections: self.toolBar.EnableTool(self.runFitId, True) else: self.toolBar.EnableTool(self.runFitId, False) else: self.toolBar.EnableTool(self.stopFitId, True) self.toolBar.EnableTool(self.runFitId, False) else: # Quickplot self.toolBar.EnableTool(self.quickPlotId, False) # Fit run/stop self.toolBar.EnableTool(self.stopFitId, False) self.toolBar.EnableTool(self.runFitId, False) return
[docs] def needsSave(self, altered=True): """Tell the gui that the program needs to be saved. This changes the state of the save menu and tool bar items. altered -- Whether or not the program needs saving (default True). """ if not self.quitting: pdfguiglobals.isAltered = altered self.updateToolbar() return
[docs] def disableSharedMenuItems(self, menu): """Disable some menu items based upon what is selected in the tree. menu -- The menu which to apply the changes. Note that this method is meant to disable only the shared menu items, that is, those that use the same menu item Ids. These are defined in the top part of __defineLocalIds(). Putting this logic into a single method makes it easier to make changes with the menus. If a specific menu needs additional logic, put that in a separate method. """ # Start by refreshing the shared items. We don't need things to become # perpetually disabled. All things are enabled by default. It is up to # the logic below to disable them. menu.Enable(self.newFitId, True) menu.Enable(self.newPhaseId, True) menu.Enable(self.newDataId, True) menu.Enable(self.newCalcId, True) menu.Enable(self.deleteId, True) menu.Enable(self.copyId, True) menu.Enable(self.pasteId, True) menu.Enable(self.pasteLinkId, True) # Get the selections off the tree selections = self.treeCtrlMain.GetSelections() node = None if selections: node = selections[0] noPhases = False if node: phases = self.treeCtrlMain.GetPhases(node) # No insert calculation if there are no phases if len(phases) == 0: noPhases = True # No insert calculation if there are no phases if noPhases: menu.Enable(self.newCalcId, False) # Change the paste text and enable or disable the paste function # based upon where we are in the tree or whether or not we have # something in the clipboard pastename = "" clipbranchtype = None cdata = self.treeCtrlMain.GetClipboard() # No paste if nothing in the clipboard if cdata is None: menu.Enable(self.pasteId, False) menu.Enable(self.pasteLinkId, False) else: clipbranchtype = cdata.type if clipbranchtype == "fit": pastename = "Fit" # Check to see if the linking fit is still in the tree. If it is # not, we disable pasteLink fitname = cdata.name fits = self.treeCtrlMain.GetChildren(self.treeCtrlMain.root) fitnames = set(map(self.treeCtrlMain.GetItemText, fits)) if fitname not in fitnames: menu.Enable(self.pasteLinkId, False) # pasteLink only if there's a fit in the clipboard elif clipbranchtype == "phase": pastename = "Phase" menu.Enable(self.pasteLinkId, False) elif clipbranchtype == "dataset": pastename = "Data Set" menu.Enable(self.pasteLinkId, False) elif clipbranchtype == "calculation": pastename = "Calculation" menu.Enable(self.pasteLinkId, False) pastetext = "&Paste %s\tCtrl+V" % pastename menu.SetLabel(self.pasteId, pastetext) # Disable certain entries based upon where we clicked. # No copy, paste, or insert on multiple items. if len(selections) > 1: menu.Enable(self.copyId, False) menu.Enable(self.pasteId, False) menu.Enable(self.pasteLinkId, False) menu.Enable(self.newDataId, False) menu.Enable(self.newPhaseId, False) menu.Enable(self.newCalcId, False) # Disallow paste of fit if no items selected elif not selections: menu.Enable(self.copyId, False) menu.Enable(self.deleteId, False) menu.Enable(self.newDataId, False) menu.Enable(self.newPhaseId, False) menu.Enable(self.newCalcId, False) if clipbranchtype != "fit": menu.Enable(self.pasteId, False) menu.Enable(self.pasteLinkId, False) return
[docs] def disableMainMenuItems(self): """Disable main menu items.""" menu = self.menuBar # First disable the shared items self.disableSharedMenuItems(menu) # Enable everything that can be disabled # Menus menu.EnableTop(1, True) menu.EnableTop(2, True) menu.EnableTop(3, True) menu.EnableTop(4, True) menu.EnableTop(5, True) menu.EnableTop(6, True) menu.EnableTop(7, True) # Menu Items menu.Enable(self.runFitId, True) menu.Enable(self.stopFitId, True) menu.Enable(self.exportFitPDFId, True) menu.Enable(self.exportFitStruId, True) menu.Enable(self.exportResId, True) menu.Enable(self.runCalcId, True) menu.Enable(self.exportCalcPDFId, True) menu.Enable(self.printBLId, True) menu.Enable(self.printBAId, True) menu.Enable(self.exportNewStruId, True) menu.Enable(self.plotIStructId, True) menu.Enable(self.plotFStructId, True) menu.Enable(self.aboutItem.GetId(), True) # Reset the save menus so that they can be disabled if a fit is running. menu.Enable(self.saveId, pdfguiglobals.isAltered) menu.Enable(self.saveAsId, True) menu.Enable(self.openId, True) menu.Enable(self.recentId, True) # Now disable the non-shared menu items selections = self.treeCtrlMain.GetSelections() numsel = len(selections) node = None if selections: node = selections[0] itemtype = self.treeCtrlMain.GetNodeType(node) else: itemtype = None # MODE if self.mode != "fitting": menu.Enable(self.deleteId, False) menu.Enable(self.copyId, False) menu.Enable(self.pasteId, False) menu.Enable(self.runFitId, False) menu.Enable(self.runCalcId, False) menu.Enable(self.newFitId, False) menu.Enable(self.newCalcId, False) menu.Enable(self.newPhaseId, False) menu.Enable(self.newDataId, False) # FIT if itemtype != "fit": menu.Enable(self.runFitId, False) menu.Enable(self.exportResId, False) elif numsel > 1: menu.Enable(self.exportResId, False) else: cdata = self.treeCtrlMain.GetControlData(node) if not cdata.res: menu.Enable(self.exportResId, False) # CALCULATION if itemtype != "calculation": menu.Enable(self.runCalcId, False) menu.Enable(self.exportCalcPDFId, False) elif numsel > 1: menu.Enable(self.exportCalcPDFId, False) else: cdata = self.treeCtrlMain.GetControlData(node) if not cdata.Gcalc: menu.Enable(self.exportCalcPDFId, False) # PHASE if itemtype != "phase": menu.Enable(self.exportNewStruId, False) menu.Enable(self.exportFitStruId, False) menu.Enable(self.printBLId, False) menu.Enable(self.printBAId, False) menu.Enable(self.plotIStructId, False) menu.Enable(self.plotFStructId, False) elif numsel > 1: menu.Enable(self.plotIStructId, False) menu.Enable(self.plotFStructId, False) menu.Enable(self.printBLId, False) menu.Enable(self.printBAId, False) menu.Enable(self.exportFitStruId, False) menu.Enable(self.exportNewStruId, False) else: menu.Enable(self.plotIStructId, True) cdata = self.treeCtrlMain.GetControlData(node) if not cdata.refined: menu.Enable(self.exportFitStruId, False) menu.Enable(self.plotFStructId, False) # DATASET if itemtype != "dataset": menu.Enable(self.exportFitPDFId, False) elif numsel > 1: menu.Enable(self.exportFitPDFId, False) else: cdata = self.treeCtrlMain.GetControlData(node) if not cdata.Gcalc: menu.Enable(self.exportFitPDFId, False) # Check the run/stop status. if self.runningDict: menu.Enable(self.newId, False) menu.Enable(self.runCalcId, False) menu.Enable(self.runFitId, False) menu.Enable(self.saveAsId, False) menu.Enable(self.saveId, False) menu.Enable(self.openId, False) menu.Enable(self.recentId, False) # Disallow certain things during a running fit pnode = self.treeCtrlMain.GetFitRoot(node) if pnode in self.runningDict.values(): menu.EnableTop(1, False) menu.EnableTop(3, False) menu.EnableTop(4, False) menu.EnableTop(5, False) menu.EnableTop(6, False) else: menu.Enable(self.stopFitId, False) # Show/Hide fitTree if self.auiManager.GetPane("treeCtrlMain").IsShown(): self.showFitItem.SetItemLabel("Hide Fit Tree") else: self.showFitItem.SetItemLabel("Show Fit Tree") # Show/Hide plotPanel if self.auiManager.GetPane("plotPanel").IsShown(): self.showPlotItem.SetItemLabel("Hide Plot Control") else: self.showPlotItem.SetItemLabel("Show Plot Control") # Show/Hide outputPanel if self.auiManager.GetPane("outputPanel").IsShown(): self.showOutputItem.SetItemLabel("Hide Output") else: self.showOutputItem.SetItemLabel("Show Output") # Show/Hide journalPanel if self.auiManager.GetPane("journalPanel").IsShown(): self.showJournalItem.SetItemLabel("Hide Journal\tCtrl+j") else: self.showJournalItem.SetItemLabel("Show Journal\tCtrl+j") return
# Shared menu items # The bulk of the code for these methods is in the FitTree class.
[docs] def onNewFit(self, event): """Start a new fit tree. A fit is given the name "Fit n", where n is the smallest positive integer such that the name is not already taken. """ newfit = self.treeCtrlMain.AddFit() # Select the fit item so that the name can be edited self.treeCtrlMain.Expand(newfit) self.treeCtrlMain.EditLabel(newfit) self.treeCtrlMain.SelectItem(newfit) self.treeCtrlMain.EnsureVisible(newfit) self.needsSave() return
[docs] def onCopy(self, event): """Copy the subtree of the current selected item into the clipboard.""" selections = self.treeCtrlMain.GetSelections() if len(selections) == 1: self.treeCtrlMain.CopyBranch(selections[0]) return
[docs] def onPaste(self, event): """Paste the subtree from the clipboard into the tree.""" selections = self.treeCtrlMain.GetSelections() ep = None if selections: ep = selections[0] try: newnode = self.treeCtrlMain.PasteBranch(ep) except FitTreeError: return self.treeCtrlMain.Expand(newnode) self.treeCtrlMain.EditLabel(newnode) self.treeCtrlMain.SelectItem(newnode) self.treeCtrlMain.EnsureVisible(newnode) self.needsSave() return
[docs] def onInsData(self, event): """Insert a new dataset item. This opens up the new data set panel from adddatapanel.py. That panel is in charge of inserting a new phase. See the module for details. """ selections = self.treeCtrlMain.GetSelections() if len(selections) == 1: self.setMode("addingdata") self.switchRightPanel("adddata") self.needsSave() return
[docs] def onInsPhase(self, event): """Insert a new phase item. This opens up the new phase panel from addphasepanel.py. That panel is in charge of inserting a new phase. See the module for details. """ selections = self.treeCtrlMain.GetSelections() if len(selections) == 1: self.setMode("addingphase") self.switchRightPanel("addphase") self.needsSave() return
[docs] def onInsCalc(self, event): """Insert a new calculation item. A calculation is given the name "Calculation n", where n is the smallest positive integer such that the name is not already taken. """ selections = self.treeCtrlMain.GetSelections() if len(selections) == 1: node = selections[0] fitroot = self.treeCtrlMain.GetFitRoot(node) newcalc = self.treeCtrlMain.AddCalc(fitroot, "Calculation 1", insertafter=node) # Select the calculation item so that the name can be edited self.treeCtrlMain.UnselectAll() self.treeCtrlMain.EditLabel(newcalc) self.treeCtrlMain.SelectItem(newcalc) self.treeCtrlMain.EnsureVisible(newcalc) self.needsSave() return
[docs] def onDelete(self, event): """Remove the subtrees starting from the selected nodes.""" selections = self.treeCtrlMain.GetSelections() # find a node to choose after deletion fitroot = None roots = self.treeCtrlMain.GetChildren(self.treeCtrlMain.root) delroots = list(map(self.treeCtrlMain.GetFitRoot, selections)) # Find the fit node above the first removed node. for root in roots: if root in selections: break fitroot = root if root in delroots: break # Delete! if selections: self.treeCtrlMain.DeleteBranches(selections) self.needsSave() # Select the fit root it it exists. If not, select the first fit node. # If that does not exist, then go blank. if fitroot: self.treeCtrlMain.SelectItem(fitroot) else: fits = self.treeCtrlMain.GetChildren(self.treeCtrlMain.root) if fits: self.treeCtrlMain.SelectItem(fits[0]) else: self.switchRightPanel("blank") return
# Main menu items
[docs] def onRun(self, event): """Run the selected fits/calculations and disable their tree entries. This also runs calculations that are children of a running fit. """ # Make sure that the tree is focused. This will trigger the KILL_FOCUS # events of the other panels. self.treeCtrlMain.SetFocus() selections = self.treeCtrlMain.GetSelections() # Get the calculation nodes and fit parent nodes from the selections. nodes = [ self.treeCtrlMain.GetFitRoot(sel) for sel in selections if self.treeCtrlMain.GetNodeType(sel) != "calculation" ] nodes.extend([sel for sel in selections if self.treeCtrlMain.GetNodeType(sel) == "calculation"]) # Add calculation nodes that are children of fit nodes, and order them # as if walking down the fit tree allnodes = [] for node in nodes: if node not in allnodes: allnodes.append(node) if self.treeCtrlMain.GetNodeType(node) == "fit": allnodes.extend(self.treeCtrlMain.GetChildren(node)) # Disable the current panel if self.rightPanel.key != "calculation": self.rightPanel.Enable(False) # Change the color of the fitting nodes depending upon their status. See # updateFittingStatus for the color scheme. Create a dictionary of fits # for ease of use. for sel in allnodes: if self.treeCtrlMain.GetNodeType(sel) == "fit": self.treeCtrlMain.SetItemBackgroundColour(sel, wx.LIGHT_GREY) name = self.treeCtrlMain.GetItemText(sel) self.runningDict[name] = sel self.needsSave() IDlist = list(map(self.treeCtrlMain.GetControlData, allnodes)) self.control.start(IDlist) return
[docs] def onStop(self, event): """Stop all fits. This removes all items from the runningDict and changes the status colors back to wxWHITE. """ self.control.stop() self.needsSave() return
[docs] def onPreferences(self, event): """Switch the right panel to the 'preferences' panel. The 'preferences' panel uses the 'config' mode. """ self.setMode("config") self.switchRightPanel("preferences") return
[docs] def onDefaultLayout(self, event): """Place the fit tree and plot panel in default locations.""" from diffpy.pdfgui.gui.windowperspective import default self.auiManager.LoadPerspective(default) self.auiManager.Update() return
[docs] def onShowFit(self, event): """Make sure the fit tree is visible.""" if self.auiManager.GetPane("treeCtrlMain").IsShown(): self.auiManager.GetPane("treeCtrlMain").Hide() else: self.auiManager.GetPane("treeCtrlMain").Show() self.auiManager.Update() return
[docs] def onShowPlot(self, event): """Make sure the plot panel is visible.""" if self.auiManager.GetPane("plotPanel").IsShown(): self.auiManager.GetPane("plotPanel").Hide() else: self.auiManager.GetPane("plotPanel").Show() self.auiManager.Update() return
[docs] def onShowOutput(self, event): """Make sure the output panel is visible.""" if self.auiManager.GetPane("outputPanel").IsShown(): self.auiManager.GetPane("outputPanel").Hide() else: self.auiManager.GetPane("outputPanel").Show() self.auiManager.Update() return
[docs] def onShowJournal(self, event): """Bring up or hide the journal window.""" if self.auiManager.GetPane("journalPanel").IsShown(): self.auiManager.GetPane("journalPanel").Hide() else: self.auiManager.GetPane("journalPanel").Show() self.journalPanel.refresh() self.journalPanel.SetFocus() self.auiManager.Update() return
[docs] def onPlotIStruct(self, event): """Plots the phase structure. Opens Atomeye and plots the structure. """ return self._plotStruct("initial")
[docs] def onPlotFStruct(self, event): """Plots the phase structure. Opens Atomeye and plots the structure. """ return self._plotStruct("refined")
def _plotStruct(self, stype): """Helper for onPlotFStruct and onPlotIStruct.""" selections = self.treeCtrlMain.GetSelections() if selections: node = selections[0] itemtype = self.treeCtrlMain.GetNodeType(node) if itemtype == "phase": # panel = self.dynamicPanels['phase'] cdata = self.treeCtrlMain.GetControlData(node) stru = getattr(cdata, stype) viewer = structureviewer.getStructureViewer() viewer.plot(stru) return
[docs] def onPrintBL(self, event): """Print the bond lengths of a selected structure to the output panel.""" from diffpy.pdfgui.gui.bondlengthdialog import BondLengthDialog selections = self.treeCtrlMain.GetSelections() if selections: node = selections[0] itemtype = self.treeCtrlMain.GetNodeType(node) if itemtype == "phase": # panel = self.dynamicPanels['phase'] cdata = self.treeCtrlMain.GetControlData(node) S = cdata.refined if not S: S = cdata.initial dlg = BondLengthDialog(self) dlg.setStructure(S) if dlg.ShowModal() == wx.ID_OK: fitroot = self.treeCtrlMain.GetFitRoot(node) fitting = self.treeCtrlMain.GetControlData(fitroot) self.control.redirectStdout() # Figure out what to calculate. If the upper and lower bound # is too small, it is assumed that a single distance is # intended to be calculated. a = dlg.a b = dlg.b ea = dlg.ea eb = dlg.eb lb = min(dlg.lb, dlg.ub) ub = max(dlg.lb, dlg.ub) if lb == ub == 0: fitting.outputBondLengthAtoms(S, a, b) else: fitting.outputBondLengthTypes(S, ea, eb, lb, ub) self.updateOutput() dlg.Destroy() return
[docs] def onPrintBA(self, event): """Print the bond angles of a selected structure to the output panel.""" from diffpy.pdfgui.gui.bondangledialog import BondAngleDialog selections = self.treeCtrlMain.GetSelections() if selections: node = selections[0] itemtype = self.treeCtrlMain.GetNodeType(node) if itemtype == "phase": # panel = self.dynamicPanels['phase'] cdata = self.treeCtrlMain.GetControlData(node) S = cdata.refined if not S: S = cdata.initial dlg = BondAngleDialog(self) dlg.setStructure(S) if dlg.ShowModal() == wx.ID_OK: fitroot = self.treeCtrlMain.GetFitRoot(node) fitting = self.treeCtrlMain.GetControlData(fitroot) self.control.redirectStdout() fitting.outputBondAngle(S, dlg.a, dlg.b, dlg.c) self.updateOutput() dlg.Destroy()
[docs] def onQuickPlot(self, event): """Quickly plot information for the selected node.""" selections = self.treeCtrlMain.GetSelections() if len(selections) != 1: return node = selections[0] refs = [self.treeCtrlMain.GetControlData(node)] nodetype = self.treeCtrlMain.GetNodeType(selections[0]) if nodetype == "dataset": xval = "r" # For quick plotting, keep this order. Gdiff must be the last. yvals = ["Gtrunc", "Gcalc", "Gdiff"] soffset = self.plotPanel.offsetTextCtrl.GetValue() offset = 0 if soffset: offset = float(self.plotPanel.offsetTextCtrl.GetValue()) self.control.plot(xval, yvals, refs, shift=offset) elif nodetype == "calculation": xval = "r" yvals = ["Gcalc"] self.control.plot(xval, yvals, refs, shift=0) elif nodetype == "phase": cdata = self.treeCtrlMain.GetControlData(node) if cdata.refined: self.onPlotFStruct(event) else: self.onPlotIStruct(event) return
[docs] def onAbout(self, event): dlg = DialogAbout(self) # dlg.CenterOnScreen() dlg.ShowModal() dlg.Destroy() return
[docs] def onRequest(self, event): dlg = ErrorReportDialog(self) # dlg.errorReport = True dlg.ShowModal() dlg.Destroy() return
[docs] def onCommunity(self, event): """Open the browser and go to the diffpy-users Google Group.""" import webbrowser try: webbrowser.open(USERSMAILINGLIST) except Exception as e: errorinfo = 'Failed to open "%s"' % e raise ControlError(errorinfo) return
[docs] def onNew(self, event): """Create a new project.""" retval = self.checkForSave() if retval != wx.ID_CANCEL: self.control.stop() self.control.close() self.treeCtrlMain.DeleteAllItems() self.treeCtrlMain.InitializeTree() self.switchRightPanel("welcome") self.plotPanel.refresh() self.needsSave(False) self.fullpath = "" self.outputPanel.clearText() self.journalPanel.refresh() self.updateTitle() return
[docs] def onOpen(self, event): """Open a file dialog so an existing project can be opened.""" retval = self.checkForSave() if retval != wx.ID_CANCEL: dir, filename = os.path.split(self.fullpath) if not dir: dir = self.workpath matchstring = "PDFgui project files (*.ddp)|*.ddp;*.ddp3" d = wx.FileDialog(None, "Choose a file", dir, "", matchstring) if d.ShowModal() == wx.ID_OK: fullpath = d.GetPath() # Load this file into the control center. self.control.stop() self.control.close() treelist = self.control.load(fullpath) self.treeCtrlMain.ExtendProjectTree(treelist) self.setMode("fitting") self.switchRightPanel("welcome") self.fullpath = fullpath self.workpath = os.path.dirname(fullpath) self.fileHistory.AddFileToHistory(fullpath) self.needsSave(False) self.outputPanel.clearText() self.journalPanel.refresh() self.updateTitle() d.Destroy() return
[docs] def onSave(self, event): """Save the project to a predetermined location.""" # Make sure that the tree is focused. This will trigger the KILL_FOCUS # events of the other panels. self.treeCtrlMain.SetFocus() code = wx.ID_OK if self.fullpath: self.control.save(self.fullpath) self.fileHistory.AddFileToHistory(self.fullpath) self.needsSave(False) pass else: code = self.onSaveAs(event) return code
[docs] def onSaveAs(self, event): """Open a save dialog so the current project can be saved.""" # Make sure that the tree is focused. This will trigger the KILL_FOCUS # events of the other panels. self.treeCtrlMain.SetFocus() matchstring = "PDFgui project files (*.ddp3)|*.ddp3|All Files|*" dir, filename = os.path.split(self.fullpath) if not dir: dir = self.workpath d = wx.FileDialog( None, "Save as...", dir, filename or "project.ddp3", matchstring, wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT, ) code = d.ShowModal() if code == wx.ID_OK: self.fullpath = d.GetPath() if len(self.fullpath) < 5 or self.fullpath[-5:] != ".ddp3": self.fullpath += ".ddp3" self.workpath = os.path.dirname(self.fullpath) self.fileHistory.AddFileToHistory(self.fullpath) # Save the file self.control.save(self.fullpath) self.needsSave(False) self.updateTitle() d.Destroy() return code
[docs] def onQuit(self, event): """Shut down gracefully.""" # Make sure that we have focus. self.SetFocus() retval = self.checkForSave() if retval != wx.ID_CANCEL: self.quitting = True self.updateConfiguration() self.writeConfiguration() self.control.exit() self.auiManager.UnInit() self.Destroy() return
[docs] def onMRUFile(self, event): """Open a recently used file.""" index = event.GetId() - wx.ID_FILE1 filename = self.fileHistory.GetHistoryFile(index) retval = self.checkForSave() if retval != wx.ID_CANCEL: self.control.stop() self.control.close() try: treelist = self.control.load(filename) self.treeCtrlMain.ExtendProjectTree(treelist) self.setMode("fitting") self.switchRightPanel("welcome") self.needsSave(False) self.outputPanel.clearText() self.journalPanel.refresh() self.fullpath = filename self.workpath = os.path.dirname(self.fullpath) self.fileHistory.AddFileToHistory(self.fullpath) self.updateTitle() except ControlError as e: self.fileHistory.RemoveFileFromHistory(index) self.updateConfiguration() self.writeConfiguration() raise e return
[docs] def onExportRes(self, event): """Export the results file for the selected calculation.""" selections = self.treeCtrlMain.GetSelections() if not selections: return node = selections[0] nodetype = self.treeCtrlMain.GetNodeType(node) if nodetype != "fit": return cdata = self.treeCtrlMain.GetControlData(node) name = self.treeCtrlMain.GetItemText(node) basename = ".".join(name.split(".")[:-1]) or name matchstring = "PDFgui results files (*.res)|*.res|All Files|*" d = wx.FileDialog( None, "Save as...", self.workpath, basename, matchstring, wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT, ) if d.ShowModal() == wx.ID_OK: path = d.GetPath() self.workpath, savename = os.path.split(path) # Add the right extension if it doesn't already have it. if len(savename) < 3 or savename[-3:] != "res": savename += ".res" path = os.path.join(self.workpath, savename) outfile = open(path, "w") outfile.write(cdata.res) outfile.close() d.Destroy() return
[docs] def onRSeries(self, event): """Open up the r-series panel.""" self.setMode("rseries") self.switchRightPanel("rseries") return
[docs] def onTSeries(self, event): """Open up the temperature series panel.""" self.setMode("tseries") self.switchRightPanel("tseries") return
[docs] def onDSeries(self, event): """Open up the doping series panel.""" self.setMode("dseries") self.switchRightPanel("dseries") return
[docs] def onExportNewStruct(self, event): """Export a structure that was created from scratch.""" extlist = ["stru", "cif", "pdb", "xyz", "xyz", ""] fmtlist = ["pdffit", "cif", "pdb", "xyz", "rawxyz", "xcfg"] selections = self.treeCtrlMain.GetSelections() if not selections: return node = selections[0] nodetype = self.treeCtrlMain.GetNodeType(node) if nodetype != "phase": return cdata = self.treeCtrlMain.GetControlData(node) # branchname = self.treeCtrlMain.GetBranchName(node) name = self.treeCtrlMain.GetItemText(node) basename = ".".join(name.split(".")[:-1]) or name matchstring = ( "PDFfit structure file (*.stru)|*.stru|" "Crystallographic Information File (*.cif)|*.cif|" "Protein Data Bank file (*.pdb)|*.pdb|" "Labeled coordinate file (*.xyz)|*.xyz|" "Raw coordinate file (*.xyz)|*.xyz|" "AtomEye configuration file|*" ) d = wx.FileDialog( None, "Save as...", self.workpath, basename, matchstring, wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT, ) if d.ShowModal() == wx.ID_OK: i = d.GetFilterIndex() path = d.GetPath() self.workpath, savename = os.path.split(path) # Add the right extension if the file needs it. if len(savename) < 3 or (extlist[i] and savename[-3:] != extlist[i][-3:]): savename += ".%s" % extlist[i] path = os.path.join(self.workpath, savename) text = cdata.initial.writeStr(fmtlist[i]) outfile = open(path, "w") outfile.write(text) outfile.close() d.Destroy() return
[docs] def onExportStruct(self, event): """Export a fit structure.""" extlist = ["stru", "cif", "pdb", "xyz", "xyz", ""] fmtlist = ["pdffit", "cif", "pdb", "xyz", "rawxyz", "xcfg"] selections = self.treeCtrlMain.GetSelections() if not selections: return node = selections[0] nodetype = self.treeCtrlMain.GetNodeType(node) if nodetype != "phase": return cdata = self.treeCtrlMain.GetControlData(node) # branchname = self.treeCtrlMain.GetBranchName(node) name = self.treeCtrlMain.GetItemText(node) basename = ".".join(name.split(".")[:-1]) or name matchstring = ( "PDFfit structure file (*.stru)|*.stru|" "Crystallographic Information File (*.cif)|*.cif|" "Protein Data Bank file (*.pdb)|*.pdb|" "Labeled coordinate file (*.xyz)|*.xyz|" "Raw coordinate file (*.xyz)|*.xyz|" "AtomEye configuration file|*" ) d = wx.FileDialog( None, "Save as...", self.workpath, basename, matchstring, wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT, ) if d.ShowModal() == wx.ID_OK: i = d.GetFilterIndex() path = d.GetPath() self.workpath, savename = os.path.split(path) # Add the right extension if the file needs it. if len(savename) < 3 or (extlist[i] and savename[-3:] != extlist[i][-3:]): savename += ".%s" % extlist[i] path = os.path.join(self.workpath, savename) text = cdata.refined.writeStr(fmtlist[i]) outfile = open(path, "w") outfile.write(text) outfile.close() d.Destroy() return
[docs] def onExportPDF(self, event): """Export a fit PDF.""" selections = self.treeCtrlMain.GetSelections() if not selections: return node = selections[0] nodetype = self.treeCtrlMain.GetNodeType(node) if nodetype != "dataset": return cdata = self.treeCtrlMain.GetControlData(node) # branchname = self.treeCtrlMain.GetBranchName(node) name = self.treeCtrlMain.GetItemText(node) basename = ".".join(name.split(".")[:-1]) or name matchstring = "PDF fit data file (*.fgr)|*.fgr|All Files|*" d = wx.FileDialog( None, "Save as...", self.workpath, basename, matchstring, wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT, ) if d.ShowModal() == wx.ID_OK: path = d.GetPath() self.workpath, savename = os.path.split(path) # Add the right extension if it doesn't already have it. if len(savename) < 3 or savename[-3:] != "fgr": savename += ".fgr" path = os.path.join(self.workpath, savename) cdata.writeCalc(path) d.Destroy() return
[docs] def onSaveCalc(self, event): """Export a calculated PDF.""" selections = self.treeCtrlMain.GetSelections() if not selections: return node = selections[0] nodetype = self.treeCtrlMain.GetNodeType(node) if nodetype != "calculation": return cdata = self.treeCtrlMain.GetControlData(node) name = self.treeCtrlMain.GetItemText(node) basename = ".".join(name.split(".")[:-1]) or name matchstring = "PDF calculated data file (*.cgr)|*.cgr|All Files|*" d = wx.FileDialog( None, "Save as...", self.workpath, basename, matchstring, wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT, ) if d.ShowModal() == wx.ID_OK: path = d.GetPath() self.workpath, savename = os.path.split(path) # Add the right extension if it doesn't already have it. if len(savename) < 3 or savename[-3:] != "cgr": savename += ".cgr" path = os.path.join(self.workpath, savename) cdata.write(path) d.Destroy() return
[docs] def onDocumentation(self, event): """Show information about the documentation.""" import webbrowser webbrowser.open(docMainFile) return
# MISC INTERACTION ITEMS
[docs] def showMessage(self, info, title="PDF Control Error"): """ShowMessage(self, info) --> tell user about an exception and so on. title -- window title info -- message """ dlg = wx.MessageDialog(self, info, title, wx.OK | wx.ICON_ERROR) dlg.ShowModal() dlg.Destroy() return
# control items
[docs] def lock(self): if not wx.IsMainThread(): wx.MutexGuiEnter()
[docs] def unlock(self): if not wx.IsMainThread(): wx.MutexGuiLeave()
[docs] def postEvent(self, type, info): """This method is called by the control. Whenever the control needs to communicate directly with the gui it can call this method. The event is processed by onCustom and then handled by the gui on its own terms. """ event = PDFCustomEvent() event.type = type event.info = info wx.PostEvent(self, event) return
[docs] def onCustom(self, event): """This handles the custom events sent by the control.""" if event.type == self.ERROR: self.showMessage(event.info) elif event.type == self.UPDATE: # job is a fitting or a calculation job = event.info self.updateFittingStatus(job) elif event.type == self.OUTPUT: self.updateOutput() elif event.type == self.PLOTNOW: # job is a fitting or a calculation with a new data to plot. job = event.info for plot in self.control.plots: plot.notify(job) return
[docs] def updateFittingStatus(self, job): """Update the fitting status. This will alter the local member runningDict so that the running items cannot be altered. The following status ids are defined in the fitting module. Some are given a color which gives the user an indication of the fit status. job is actually a fitting or a calculation object. Fit status INITIALIZED -- 'LIGHT GRAY' CONNECTED -- 'GREEN' CONFIGURED -- 'GREEN' DONE -- 'WHITE' JOB Status VOID QUEUED RUNNING PAUSED """ from diffpy.pdfgui.control.fitting import Fitting if isinstance(job, Fitting): name = job.name fitStatus = job.fitStatus jobStatus = job.jobStatus try: node = self.runningDict[name] except KeyError: return if jobStatus == Fitting.RUNNING: if fitStatus == Fitting.INITIALIZED: self.treeCtrlMain.SetItemBackgroundColour(node, wx.LIGHT_GREY) elif fitStatus in (Fitting.CONNECTED, Fitting.CONFIGURED): self.treeCtrlMain.SetItemBackgroundColour(node, wx.GREEN) elif jobStatus == Fitting.VOID: self.treeCtrlMain.SetItemBackgroundColour(node, wx.WHITE) selections = self.treeCtrlMain.GetSelections() if len(selections) == 1: # Enable whatever panel is currently being viewed. self.rightPanel.Enable() if node == selections[0]: self.rightPanel.refresh() self.runningDict.pop(name, None) self.needsSave() # Update the menus and toolbars whenever an event is posted. self.disableMainMenuItems() self.updateToolbar() return
[docs] def updateOutput(self): """Update text in outputPanel with text in stdout.""" self.outputPanel.updateText(self.control.getEngineOutput()) return
# end of class MainPanel