#!/usr/bin/env python
##############################################################################
#
# 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.
#
##############################################################################
"""This module contains the FitTree object designed for use in PDFgui.
Classes:
FitTree -- A tree specific to orgainizing data for pdffit
Exceptions:
FitTreeError -- Exception for errors with FitTree operations.
"""
import base64
import pickle
import re
import wx
from diffpy.pdfgui.control.controlerrors import ControlError
from diffpy.pdfgui.control.fitting import Fitting
from diffpy.pdfgui.gui.pdfguiglobals import iconpath
from diffpy.pdfgui.utils import safeCPickleDumps
[docs]
class FitTree(wx.TreeCtrl):
"""TreeCtrl designed to organize pdffit fits.
The root of the tree is hidden. Below that there are several levels
which are diagrammed below.
_ fit (*)
|
|____ phase (5)
|____ dataset (*)
|____ calculation (*)
Fits are at the top level. Under fits there are phases, datasets, and
calculations (in that order).
It is required that the data for each node is a dictionary. In the 'type'
entry of this dictionary is the node type (fit, phase, dataset,
calculation). Fit items also have a 'cdata' entry in their tree item
dictionary. This is the control center data associated with this node's
branch.
Data members:
control -- The pdfguicontrol object that interfaces between the tree
and the pdffit2 engine. The tree is a mirror of the internal
structure of the control.
"""
def __init__(
self,
parent,
id=-1,
pos=wx.DefaultPosition,
size=wx.DefaultSize,
style=wx.TR_HAS_BUTTONS | wx.TR_HIDE_ROOT | wx.TR_MULTIPLE,
validator=wx.DefaultValidator,
name="FitTree",
):
wx.TreeCtrl.__init__(self, parent, id, pos, size, style)
# Define the control
# This is set by the mainFrame
# self.control = pdfguicontrol()
# Define bitmaps
datasetbmp = wx.Bitmap(iconpath("datasetitem.png"))
phasebmp = wx.Bitmap(iconpath("phaseitem.png"))
fitbmp = wx.Bitmap(iconpath("fititem.png"))
calcbmp = wx.Bitmap(iconpath("calculationitem.png"))
isz = (16, 16)
il = wx.ImageList(isz[0], isz[1])
self.fitbmid = il.Add(fitbmp)
self.dtsbmid = il.Add(datasetbmp)
self.phabmid = il.Add(phasebmp)
self.clcbmid = il.Add(calcbmp)
self.SetImageList(il)
self.treeImageList = il
return
[docs]
def InitializeTree(self):
"""This initializes the tree by adding a root node."""
self.root = self.AddRoot("The Root Item")
self.SetNodeType(self.root, "root")
# Testing code
# fit1 = self.AddFit()
# self.AddPhase(fit1, "Phase 1")
# self.AddPhase(fit1, "Phase 2")
# self.AddDataSet(fit1, "Data 1")
# self.AddCalc(fit1, "Calc 1")
# self.Expand(fit1)
return
[docs]
def GetTreeItemDict(self, node):
"""Get the data dictionary of the node."""
return self.GetItemData(node)
[docs]
def GetFitRoot(self, node):
"""Return the id of the fit in which the passed node resides."""
if not node:
return
fitId = node
nextId = self.GetItemParent(node)
while nextId != self.root:
fitId = nextId
nextId = self.GetItemParent(nextId)
return fitId
[docs]
def GetChildren(self, node):
"""Get the ids of the children of a given node."""
cookie = 0
ids = []
(child, cookie) = self.GetFirstChild(node)
while child.IsOk():
ids.append(child)
(child, cookie) = self.GetNextChild(node, cookie)
return ids
[docs]
def GetSiblings(self, node):
"""Get the ids of the siblings of a given node."""
parent = self.GetItemParent(node)
ids = self.GetChildren(parent)
ids.remove(node)
return ids
[docs]
def GetAllType(self, node):
"""Get the id of each item in the tree of the same type as node."""
nodetype = self.GetNodeType(node)
fits = self.GetChildren(self.root)
if nodetype == "fit":
return fits
else:
sametype = []
for fit in fits:
children = self.GetChildren(fit)
sametype.extend([child for child in children if self.GetNodeType(child) == nodetype])
return sametype
[docs]
def GetPhases(self, node):
"""Get a list of phase in branch.
node is either the fit-root or a node in the fit-branch of
interest.
"""
nodes = self.GetChildren(self.GetFitRoot(node))
ids = [id for id in nodes if self.GetNodeType(id) == "phase"]
return ids
[docs]
def GetDataSets(self, node):
"""Get a list of datasets in branch.
node is either the fit-root or a node in the fit-branch of
interest.
"""
nodes = self.GetChildren(self.GetFitRoot(node))
ids = [id for id in nodes if self.GetNodeType(id) == "dataset"]
return ids
[docs]
def GetCalculations(self, node):
"""Get a list of calculations in branch.
node is either the fit-root or a node in the fit-branch of
interest.
"""
nodes = self.GetChildren(self.GetFitRoot(node))
ids = [id for id in nodes if self.GetNodeType(id) == "calculation"]
return ids
[docs]
def GetNodeType(self, node):
"""Get the node type.
This is the "type" entry in the data dictionary of the node.
"""
if not node:
return None
datadict = self.GetTreeItemDict(node)
if datadict is None:
return None
return datadict["type"]
[docs]
def SetNodeType(self, node, tp):
"""Set the node type of a node."""
if not node:
return
datadict = self.GetTreeItemDict(node)
if datadict is None:
datadict = {}
self.SetItemData(node, datadict)
datadict["type"] = tp
return
[docs]
def GetBranchName(self, node):
"""Get the name of the branch in which node resides."""
fp = self.GetFitRoot(node)
return self.GetItemText(fp)
[docs]
def GetLastPhase(self, node):
"""Get the last phase child of the parent node.
This method is helpful in placing datasets and phases into the
fit tree. This method depends on the fact that phases are placed
before datasets in the fit tree.
"""
siblings = self.GetChildren(node)
lastphase = None
for sib in siblings:
if self.GetNodeType(sib) == "dataset":
break
elif self.GetNodeType(sib) == "calculation":
break
else:
lastphase = sib
return lastphase
[docs]
def GetLastDataSet(self, node):
"""Get the last dataset child of the fit node.
If there is no last dataset node, this may return the last phase
node. The purpose of getting this node is to know where to place
another node, so the actual node type is not important.
"""
siblings = self.GetChildren(node)
lastdata = None
for sib in siblings:
if self.GetNodeType(sib) == "calculation":
break
else:
lastdata = sib
return lastdata
[docs]
def GetNumPhases(self, node):
"""Get the number of phases in a branch.
node -- A node in the branch, or the root of the branch.
"""
parent = self.GetFitRoot(node)
family = self.GetChildren(parent)
phases = [item for item in family if self.GetNodeType(item) == "phase"]
return len(phases)
[docs]
def GetNumDataSets(self, node):
"""Get the number of datasets in a branch.
node -- A node in the branch, or the root of the branch.
"""
parent = self.GetFitRoot(node)
family = self.GetChildren(parent)
phases = [item for item in family if self.GetNodeType(item) == "dataset"]
return len(phases)
[docs]
def GetPositionInSubtree(self, node):
"""Get the index if the node in its subtree.
For fits the position is absolute within the tree. For phases,
datasets, and calculations, the location is taken to be in
reference to the other nodes of its type. This is designed to be
compatible with the control center.
"""
parent = self.GetItemParent(node)
brood = self.GetChildren(parent)
pos = 0
for sib in brood:
if sib == node:
break
else:
pos += 1
nodetype = self.GetNodeType(node)
if nodetype == "dataset":
pos -= self.GetNumPhases(node)
if nodetype == "calculation":
pos -= self.GetNumPhases(node) + self.GetNumDataSets(node)
return pos
[docs]
def SetControlData(self, node, data):
"""Set the control center data associated with the node.
This need only be called for 'fit' nodes. This is the "cdata"
entry in the data dictionary of the node. It holds the object
with which the right panel interfaces. For example, for a
'phase' node, it contains a Structure object.
"""
nodetype = self.GetNodeType(node)
if nodetype != "fit":
message = "Node type %s does not hold its own data" % nodetype
raise FitTreeError(message)
self.GetTreeItemDict(node)["cdata"] = data
return
[docs]
def GetControlData(self, node):
"""Get the control center data associated with a node.
NOTE: The fit-root of a node holds this data. This method makes it
convenient to retrieve it.
"""
nodetype = self.GetNodeType(node)
parent = self.GetFitRoot(node)
pdata = self.GetTreeItemDict(parent)["cdata"]
if nodetype == "fit":
return pdata
elif nodetype == "phase":
pos = self.GetPositionInSubtree(node)
return pdata.getStructure(pos)
elif nodetype == "dataset":
pos = self.GetPositionInSubtree(node)
return pdata.getDataSet(pos)
elif nodetype == "calculation":
pos = self.GetPositionInSubtree(node)
return pdata.getCalculation(pos)
else:
message = "Node of type %s does not exist" % nodetype
raise FitTreeError(message)
return
[docs]
def AddFit(self, fitname="Fit 1", cdata=None, paste=False):
"""Append a new fit tree to the end of the current fits.
fitname -- The name of the fit. This is incremented if it already
exists.
cdata -- Control data for the node. If cdata is None (default),
then the control is asked to create new data.
paste -- Whether or not the cdata is being pasted from another
node (default False).
Returns the id of the new node.
"""
# Name the fit, but check to not duplicate names.
fits = self.GetChildren(self.root)
names = [self.GetItemText(f) for f in fits]
fitname = incrementName(fitname, names)
newfit = self.AppendItem(self.root, fitname)
self.SetNodeType(newfit, "fit")
self.SetItemImage(newfit, self.fitbmid, wx.TreeItemIcon_Normal)
pos = self.GetPositionInSubtree(newfit)
try:
# Set the node data for the new node
if cdata is None:
cdata = self.control.newFitting(fitname, pos)
elif paste:
cdata = self.control.paste(cdata, None, fitname, pos)
self.SetControlData(newfit, cdata)
return newfit
except Exception:
self.Delete(newfit)
raise
return
[docs]
def AddPhase(self, node, label, insertafter=None, filename=None, makedata=True, cdata=None):
"""Add a new blank Phase to the tree as a child of node.
node -- The parent 'fit' node.
label -- The name of the new node.
insertafter -- The node after which to insert the new phase. If
insertafter is None (default) the new phase is
appended to the end of the phases in the subtree of
the parent node.
filename -- The file from which to load the structure. If this is
None (default), a new structure is created.
makedata -- Tells whether the control needs to make data for the
node (default True).
cdata -- Control data for the node. If cdata is None (default),
then it is assumed that the node already has data in the
control. See ExtendProjectTree and __InsertBranch for
examples of how this is used.
Phases are always placed before DataSets.
Raises:
FitTreeError if node is not a "fit" node.
FitTreeError if insertafter is not a "phase" node.
Returns the id of the new node.
"""
# Check to make sure the new phase is a child of a fit or calculation
nodetype = self.GetNodeType(node)
if nodetype != "fit":
message = "Can only add a phase as a child of a fit."
raise FitTreeError(message)
if insertafter is not None:
afttype = self.GetNodeType(insertafter)
if afttype != "phase":
insertafter = None
if insertafter:
newphase = self.InsertItem(node, insertafter, label)
else:
lastphase = self.GetLastPhase(node)
if lastphase:
# Put the new phase after the last
newphase = self.InsertItem(node, lastphase, label)
else:
newphase = self.PrependItem(node, label)
self.SetNodeType(newphase, "phase")
self.SetItemImage(newphase, self.phabmid, wx.TreeItemIcon_Normal)
# Set the control data to the new phase
pdata = self.GetControlData(node)
pos = self.GetPositionInSubtree(newphase)
# Try to get/make the node data from the control. If it doesn't work,
# then delete the new node.
try:
if makedata:
if filename is None:
self.control.newStructure(pdata, label, pos)
else:
self.control.loadStructure(pdata, filename, label, pos)
elif cdata is not None:
self.control.paste(cdata, pdata, label, pos)
return newphase
except Exception:
self.Delete(newphase)
raise
return
[docs]
def AddDataSet(self, node, label, insertafter=None, filename=None, makedata=True, cdata=None):
"""Add a new DataSet to the tree as a child of fit.
node -- The parent node of the dataset. Must be 'fit' type.
label -- The label of the new node.
insertafter -- The node after which to insert the new dataset. If
insertafter is None (default) the new dataset is
appended to the end of the datasets in the subtree of
the parent node.
filename -- The name of the file from which to load the data.
makedata -- Tells whether the control needs to make data for the
node (default True). If True, cdata is ignored.
cdata -- Control data for the node. If False cdata is None
(default), then it is assumed that the node already has
data in the control. See ExtendProjectTree and
__InsertBranch for examples of how this is used.
DataSets are always placed after Phases.
Raises:
FitTreeError if node is not a "fit" node.
FitTreeError if insertafter is not a "dataset" node.
Returns the id of the new node.
"""
# Check to make sure the new dataset is a child of a fit
nodetype = self.GetNodeType(node)
if nodetype != "fit":
message = "Can only add a data set as a child of a fit."
raise FitTreeError(message)
if insertafter is not None:
afttype = self.GetNodeType(node)
if afttype != "dataset":
insertafter = None
if insertafter:
newset = self.InsertItem(node, insertafter, label)
else:
lastset = self.GetLastDataSet(node)
if lastset:
newset = self.InsertItem(node, lastset, label)
else:
newset = self.PrependItem(node, label)
self.SetNodeType(newset, "dataset")
self.SetItemImage(newset, self.dtsbmid, wx.TreeItemIcon_Normal)
# Attach the control center data to the new dataset
pos = self.GetPositionInSubtree(newset)
pdata = self.GetControlData(node)
try:
if makedata:
if filename is not None:
self.control.loadDataset(pdata, filename, label, pos)
else:
raise FitTreeError("Cannot load a dataset without a name!")
elif cdata is not None:
self.control.paste(cdata, pdata, label, pos)
return newset
except Exception:
self.Delete(newset)
raise
return
[docs]
def AddCalc(self, node, label, insertafter=None, makedata=True, cdata=None):
"""Add a new DataSet to the tree as a child of fit.
node -- The parent node of the calculation. Must be 'fit' type.
label -- The label of the new node.
insertafter -- The node after which to insert the new calculation. If
insertafter is None (default) the new calculation is
appended to the end of the calculation in the subtree of
the parent node.
makedata -- Tells whether the control needs to make data for the
node (default True). If True, cdata is ignored.
cdata -- Control data for the node. If False cdata is None
(default), then it is assumed that the node already has
data in the control. See ExtendProjectTree and
__InsertBranch for examples of how this is used.
Calculations are always placed after datasets.
Raises:
FitTreeError if node is not a "fit" node.
FitTreeError if insertafter is not a "calculation" node.
Returns the id of the new node.
"""
# Check to make sure the new calculation is a child of a fit
nodetype = self.GetNodeType(node)
if nodetype != "fit":
message = "Can only add a calculation as a child of a fit."
raise FitTreeError(message)
if insertafter is not None:
afttype = self.GetNodeType(node)
if afttype != "calculation":
insertafter = None
sibs = self.GetCalculations(node)
names = [self.GetItemText(sb) for sb in sibs]
label = incrementName(label, names)
if insertafter:
newcalc = self.InsertItem(node, insertafter, label)
else:
newcalc = self.AppendItem(node, label)
self.SetNodeType(newcalc, "calculation")
self.SetItemImage(newcalc, self.clcbmid, wx.TreeItemIcon_Normal)
# Attach the control center data to the new datacalc
pos = self.GetPositionInSubtree(newcalc)
pdata = self.GetControlData(node)
try:
if makedata:
self.control.newCalculation(pdata, label, pos)
elif cdata is not None:
self.control.paste(cdata, pdata, label, pos)
return newcalc
except Exception:
self.Delete(newcalc)
raise
return
[docs]
def CopyBranch(self, startnode):
"""Make a copy of a tree branch.
The branch is held in the system clipboard so it can be used in
another instance of the fittree.
"""
nodetype = self.GetNodeType(startnode)
cdata = self.control.copy(self.GetControlData(startnode))
if isinstance(cdata, Fitting):
cdata = cdata.stripped()
cdata.type = nodetype
cdatabytes = safeCPickleDumps(cdata)
cdatabytes = "pdfgui_cliboard=".encode() + cdatabytes
# wxpython only accepts str, use base64 to convert bytes to str
cdatastring = base64.b64encode(cdatabytes)
textdata = wx.TextDataObject(cdatastring)
if not wx.TheClipboard.IsOpened():
opened = wx.TheClipboard.Open()
if not opened:
raise FitTreeError("Cannot open the clipboard.")
wx.TheClipboard.SetData(textdata)
wx.TheClipboard.Close()
return
[docs]
def GetClipboard(self):
"""Get the clipboard data.
Returns the controldata in the clipboard, or None if the
clipboard is empty or contains the wrong type of data.
"""
# Check to see if data is present
if not wx.TheClipboard.IsSupported(wx.DataFormat(wx.DF_TEXT)):
return None
textdata = wx.TextDataObject()
if not wx.TheClipboard.IsOpened():
opened = wx.TheClipboard.Open()
if not opened:
return None
success = wx.TheClipboard.GetData(textdata)
wx.TheClipboard.Close()
if not success:
return None
cdatastring = textdata.GetText()
cdata = None
# use base64 to convert str back to bytes
try:
cdatabytes = base64.b64decode(cdatastring.encode())
if cdatabytes[:16] == "pdfgui_cliboard=".encode():
cdatabytes = cdatabytes[16:]
cdata = pickle.loads(cdatabytes, encoding="latin1")
except Exception:
pass
return cdata
[docs]
def PasteBranch(self, entrypoint=None):
"""Paste the branch from the clipboard into tree at the given node.
A certain type of branch can only be copied to specific places.
fit - A fit can be pasted to anywhere. This does not overwrite
an existing node, but simply inserts the fit into the
last available slot.
phase - A phase can be pasted from anywhere. If pasted from a
fit, it is placed at the end of the phase section of
that node. If inserted from a dataset or a calculation,
it is placed at the end of the phase section.
dataset - A dataset can be pasted from anywhere. If pasted from a
fit, the dataset is appended at the end of the other
datasets. If pasted from a dataset, the pasted set is
inserted right after that one. If pasted from a phase,
it is placed at the beginning of the dataset section. If
pasted from a calculation, it is placed at the end of
the dataset section.
calculation - A calculation can be pasted to anywhere, but it appears
at the end of the calculation section of the tree. If
pasted from a calculation node, it is inserted after
that node.
Raises:
FitTreeError if the entrypoint and branch type are incompatible.
"""
cdata = self.GetClipboard()
if cdata is None:
message = "There is no branch to paste!"
raise FitTreeError(message)
# Now we have the cdata, we must put it into the tree
branchtype = cdata.type
insertafter = None
prepend = False
entrytype = None
if entrypoint:
entrytype = self.GetNodeType(entrypoint)
# Check to see what we are trying to paste, and where.
if branchtype == "fit":
# Paste after the selected fit containing the selection, or
# after the last fit if a calculation is selected. If nothing is
# selected, just paste it!
entrytype = None
if entrypoint:
entrypoint = self.GetFitRoot(entrypoint)
entrytype = self.GetNodeType(entrypoint)
if entrytype is None:
entrypoint = self.root
insertafter = None
elif entrytype == "fit":
insertafter = entrypoint
entrypoint = self.root
else: # Just in case
raise FitTreeError("Cannot paste a fit branch here.")
if branchtype == "phase":
# Paste after selected phase, or append to the end of the phase
# section of a fit.
if entrytype == "phase":
# The entry is to be a sibling.
insertafter = entrypoint
entrypoint = self.GetItemParent(entrypoint)
elif entrytype in ("dataset", "calculation"):
# Paste to the end of the phases, if they exist.
entrypoint = self.GetItemParent(entrypoint)
insertafter = self.GetLastPhase(entrypoint)
if not insertafter:
# Put the branch at the beginning of the phases
prepend = True
elif entrytype == "fit":
# Get the last phase in the phase section, which may not
# exist.
insertafter = self.GetLastPhase(entrypoint)
if not insertafter:
# Put the branch at the beginning of the phases
prepend = True
else: # Just in case
raise FitTreeError("Cannot paste a phase branch here.")
if branchtype == "dataset":
# Paste after a selected dataset, or into a selected fit.
if entrytype == "dataset":
# The entry is to be a sibling.
insertafter = entrypoint
entrypoint = self.GetItemParent(entrypoint)
elif entrytype == "phase":
# The entry goes to the end of the phases, which must exist.
entrypoint = self.GetItemParent(entrypoint)
insertafter = self.GetLastPhase(entrypoint)
elif entrytype == "calculation":
# The entry goes to the end of the datasets.
entrypoint = self.GetItemParent(entrypoint)
insertafter = self.GetLastDataSet(entrypoint)
elif entrytype == "fit":
insertafter = self.GetLastDataSet(entrypoint)
# The entrypoint is ok. The branch is appended to the end of
# the calculations.
pass
else:
raise FitTreeError("Cannot paste a data set branch here.")
if branchtype == "calculation":
# Paste after the selected calculation or after the calculations.
if entrytype == "calculation":
# The entry is to be a sibling.
insertafter = entrypoint
entrypoint = self.GetItemParent(entrypoint)
elif entrytype in ("phase", "dataset"):
entrypoint = self.GetItemParent(entrypoint)
insertafter = self.GetLastDataSet(entrypoint)
elif entrytype == "fit":
insertafter = self.GetLastDataSet(entrypoint)
else: # Just in case
raise FitTreeError("Cannot paste a calculation branch here.")
# Now set the name of the item to be inserted.
label = self.__copyLabel(cdata.name, entrypoint)
# Now we have a label. We must insert the item into the tree.
newnode = self.__InsertBranch(cdata, entrypoint, label, insertafter, prepend)
return newnode
def __copyLabel(self, oldlabel, entrypoint):
"""Make a new label that is appropriate for a new node."""
# Append "_copy" to the end of the label, unless it already has that.
# In that case, just add a number to indicate which copy it is.
siblings = self.GetChildren(entrypoint)
labels = [self.GetItemText(sb) for sb in siblings]
match = r"_copy\d*$"
label = re.sub(match, "", oldlabel)
label += "_copy"
label = incrementName(label, labels)
return label
def __InsertBranch(self, cdata, entrypoint, label, insertafter=None, prepend=False):
"""Insert control data into the tree.
cdata -- The control data that goes with the branch
entrypoint -- The subbranch (fit root) to paste into
label -- The label of the new node
insertafter -- A node after which to insert. If insertafter is None
(default), then the new node will be pasted after the
last node of the same type.
prepend -- Prepend to the beginning of the node group (default
False). insertafter takes prescedent over prepend.
Returns the newly inserted node.
"""
if cdata is None:
message = "There is no branch to paste!"
raise FitTreeError(message)
branchtype = cdata.type
# cdata.name = label
if branchtype == "fit":
cdata.name = label
newnode = self.ExtendProjectTree([cdata.organization()], clear=False, paste=True)
elif branchtype == "phase":
newnode = self.AddPhase(entrypoint, label, insertafter=insertafter, makedata=False, cdata=cdata)
elif branchtype == "dataset":
newnode = self.AddDataSet(entrypoint, label, insertafter=insertafter, makedata=False, cdata=cdata)
elif branchtype == "calculation":
newnode = self.AddCalc(entrypoint, label, insertafter=insertafter, makedata=False, cdata=cdata)
else:
raise FitTreeError("Unrecognized node type: %s" % branchtype)
return newnode
[docs]
def DeleteBranches(self, selections):
"""Remove the subtree starting from the selected node(s)."""
# Get a list of branch heads
branchset = [node for node in selections if self.GetNodeType(node) == "fit"]
# Get their children
childset = []
for node in branchset:
childset.extend(self.GetChildren(node))
# Collect all nodes, removing any children of branch nodes.
nodeset = [node for node in selections if node not in childset]
for node in nodeset:
cdata = self.GetControlData(node)
self.control.remove(cdata)
self.Delete(node)
return nodeset
[docs]
def SelectAll(self):
"""Select all nodes."""
self.UnselectAll()
fits = self.GetChildren(self.root)
for fit in fits:
children = self.GetChildren(fit)
self.SelectItem(fit)
for child in children:
self.SelectItem(child)
return
[docs]
def SelectAllType(self, node=None):
"""Select all nodes of same type as passed node.
node -- Node whose type to select. If node is None (default), then
all fit nodes will be selected.
"""
self.UnselectAll()
if node is None:
# Get the first fit node
fits = self.GetChildren(self.root)
if not fits:
return
node = fits[0]
typelist = self.GetAllType(node)
for item in typelist:
self.SelectItem(item)
return
[docs]
def ExtendProjectTree(self, treelist, clear=True, paste=False):
"""Extend the project tree from the treelist.
treelist -- A list of control data returned by
Oraganizer.organization()
clear -- Clear the tree before adding new nodes (default True)
paste -- Whether or not the cdata is being pasted from another
node (default False).
The treelist here is of the type returned from pdfguicontrol.load.
It is a list of fit lists with the following format.
node[0] -- fit object
node[1] -- list of (name, dataset) tuples
node[2] -- list of (name, phase) tuples
node[3] -- list of (name, calculation) tuples
Note that node[1] should be empty if the node is a calculation.
Returns the last inserted fit or calculation node
"""
# Clean slate
if clear:
self.DeleteAllItems()
self.InitializeTree()
roots = []
# Return if the treelist is empty
if not treelist:
return
# Build the tree
for item in treelist:
broot = item[0]
name = broot.name
node = self.AddFit(name, cdata=broot, paste=paste)
if node is None:
message = "Cannot insert data. Malformed tree list."
raise FitTreeError(message)
roots.append(node)
# Build the rest of the tree. Note that we don't want to create new
# data, but we don't pass the cdata since it is already included in
# the fit root.
phases = item[2]
for name, phase in phases:
self.AddPhase(node, name, makedata=False)
dsets = item[1]
for name, set in dsets:
self.AddDataSet(node, name, makedata=False)
calcs = item[3]
for name, calc in calcs:
self.AddCalc(node, name, makedata=False)
for item in roots:
self.Expand(item)
return node
# End class FitTree
# Exceptions
[docs]
class FitTreeError(ControlError):
def __init__(self, *args):
ControlError.__init__(self, *args)
return
# End class FitTreeError
# Utility functions
[docs]
def incrementName(name, namelist, start=1):
"""Increment the name by assigning the lowest number to the end such that
the name does not appear in the namelist."""
newname = name
match = r"\d+$"
counter = start
while newname in namelist:
newname = re.sub(match, "", name)
counter += 1
newname = "%s%i" % (newname, counter)
return newname