#!/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<s 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 onPasteLink(self, event):
"""Paste a copied fit and link it to the original.
This should only be called on a 'fit' node, and only if the
original 'fit' node, or at least one with the same name as the
original still exists in the tree.
"""
selections = self.treeCtrlMain.GetSelections()
cdata = self.treeCtrlMain.GetClipboard()
fitname = cdata.name
fits = self.treeCtrlMain.GetChildren(self.treeCtrlMain.root)
fitnames = set(map(self.treeCtrlMain.GetItemText, fits))
if fitname not in fitnames:
return
ep = None
if selections:
ep = selections[0]
newnode = self.treeCtrlMain.PasteBranch(ep)
# Now link the fit
newfit = self.treeCtrlMain.GetControlData(newnode)
oldparnames = set(cdata.parameters.keys())
for parname, par in newfit.parameters.items():
if parname in oldparnames:
parval = "=%s:%s" % (fitname, parname)
par.setInitial(parval)
# Make the node ready for editing
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