Package diffpy :: Package pdfgui :: Package gui :: Module fittree
[hide private]
[frames] | no frames]

Source Code for Module diffpy.pdfgui.gui.fittree

  1  ############################################################################## 
  2  # 
  3  # PDFgui            by DANSE Diffraction group 
  4  #                   Simon J. L. Billinge 
  5  #                   (c) 2006 trustees of the Michigan State University. 
  6  #                   All rights reserved. 
  7  # 
  8  # File coded by:    Chris Farrow 
  9  # 
 10  # See AUTHORS.txt for a list of people who contributed. 
 11  # See LICENSE.txt for license information. 
 12  # 
 13  ############################################################################## 
 14   
 15  """This module contains the FitTree object designed for use in PDFgui. 
 16   
 17  Classes: 
 18      FitTree         --  A tree specific to orgainizing data for pdffit 
 19   
 20  Exceptions: 
 21      FitTreeError    --  Exception for errors with FitTree operations. 
 22  """ 
 23   
 24  import wx 
 25  import sys, os, re, cPickle 
 26   
 27  from diffpy.pdfgui.gui.pdfguiglobals import iconpath 
 28  from diffpy.pdfgui.control.pdfguicontrol import pdfguicontrol 
 29  from diffpy.pdfgui.control.calculation import Calculation 
 30  from diffpy.pdfgui.control.fitting import Fitting 
 31  from diffpy.pdfgui.control.controlerrors import ControlError 
 32   
33 -class FitTree(wx.TreeCtrl):
34 """TreeCtrl designed to organize pdffit fits. 35 36 The root of the tree is hidden. Below that there are several levels 37 which are diagrammed below. 38 39 _ fit (*) 40 | 41 |____ phase (5) 42 |____ datset (*) 43 |____ calculation (*) 44 45 Fits are at the top level. Under fits there are phases, datasets, and 46 calculations (in that order). 47 48 It is required that the data for each node is a dictionary. In the 'type' 49 entry of this dictionary is the node type (fit, phase, dataset, 50 calculation). Fit items also have a 'cdata' entry in their tree item 51 dictionary. This is the control center data associated with this node's 52 branch. 53 54 Data members: 55 control -- The pdfguicontrol object that interfaces between the tree 56 and the pdffit2 engine. The tree is a mirror of the internal 57 structure of the control. 58 59 """ 60
61 - def __init__(self, parent, id=-1, pos=wx.DefaultPosition, 62 size=wx.DefaultSize, 63 style=wx.TR_HAS_BUTTONS|wx.TR_HIDE_ROOT|wx.TR_MULTIPLE, 64 validator=wx.DefaultValidator, name="FitTree"):
65 wx.TreeCtrl.__init__(self, parent, id, pos, size, style) 66 67 # Define the control 68 # This is set by the mainFrame 69 # self.control = pdfguicontrol() 70 71 # Define bitmaps 72 datasetbmp = wx.Bitmap(iconpath("datasetitem.png")) 73 phasebmp = wx.Bitmap(iconpath("phaseitem.png")) 74 fitbmp = wx.Bitmap(iconpath("fititem.png")) 75 calcbmp = wx.Bitmap(iconpath("calculationitem.png")) 76 isz = (16,16) 77 il = wx.ImageList(isz[0], isz[1]) 78 self.fitbmid = il.Add(fitbmp) 79 self.dtsbmid = il.Add(datasetbmp) 80 self.phabmid = il.Add(phasebmp) 81 self.clcbmid = il.Add(calcbmp) 82 self.SetImageList(il) 83 self.treeImageList = il 84 85 86 return
87
88 - def InitializeTree(self):
89 """This initializes the tree by adding a root node.""" 90 self.root = self.AddRoot("The Root Item") 91 self.SetNodeType(self.root, "root") 92 # Testing code 93 #fit1 = self.AddFit() 94 #self.AddPhase(fit1, "Phase 1") 95 #self.AddPhase(fit1, "Phase 2") 96 #self.AddDataSet(fit1, "Data 1") 97 #self.AddCalc(fit1, "Calc 1") 98 #self.Expand(fit1) 99 return
100
101 - def GetTreeItemDict(self, node):
102 """Get the data dictionary of the node.""" 103 return self.GetPyData(node)
104
105 - def GetFitRoot(self, node):
106 """Return the id of the fit in which the passed node resides.""" 107 if not node: return 108 fitId = node 109 nextId = self.GetItemParent(node) 110 while nextId != self.root: 111 fitId = nextId 112 nextId = self.GetItemParent(nextId) 113 return fitId
114
115 - def GetChildren(self, node):
116 """Get the ids of the children of a given node.""" 117 cookie = 0 118 ids = [] 119 (child, cookie) = self.GetFirstChild(node) 120 while child.IsOk(): 121 ids.append(child) 122 (child, cookie) = self.GetNextChild(node, cookie) 123 124 return ids
125
126 - def GetSiblings(self, node):
127 """Get the ids of the siblings of a given node.""" 128 parent = self.GetItemParent(node) 129 ids = self.GetChildren(parent) 130 ids.remove(node) 131 return ids
132
133 - def GetAllType(self, node):
134 """Get the id of each item in the tree of the same type as node.""" 135 nodetype = self.GetNodeType(node) 136 fits = self.GetChildren(self.root) 137 if nodetype == 'fit': 138 return fits 139 else: 140 sametype = [] 141 for fit in fits: 142 children = self.GetChildren(fit) 143 sametype.extend( [child for child in children if 144 self.GetNodeType(child) == nodetype] ) 145 return sametype
146
147 - def GetPhases(self, node):
148 """Get a list of phase in branch. 149 150 node is either the fit-root or a node in the fit-branch of interest. 151 """ 152 nodes = self.GetChildren(self.GetFitRoot(node)) 153 ids = [id for id in nodes if self.GetNodeType(id) == 'phase'] 154 return ids
155
156 - def GetDataSets(self, node):
157 """Get a list of datasets in branch. 158 159 node is either the fit-root or a node in the fit-branch of interest. 160 """ 161 nodes = self.GetChildren(self.GetFitRoot(node)) 162 ids = [id for id in nodes if self.GetNodeType(id) == 'dataset'] 163 return ids
164
165 - def GetCalculations(self, node):
166 """Get a list of calculations in branch. 167 168 node is either the fit-root or a node in the fit-branch of interest. 169 """ 170 nodes = self.GetChildren(self.GetFitRoot(node)) 171 ids = [id for id in nodes if self.GetNodeType(id) == 'calculation'] 172 return ids
173
174 - def GetNodeType(self, node):
175 """Get the node type. 176 177 This is the "type" entry in the data dictionary of the node. 178 """ 179 if not node: return 180 return self.GetTreeItemDict(node)['type']
181
182 - def SetNodeType(self, node, type):
183 """Set the node type of a node.""" 184 if not node: return 185 datadict = self.GetTreeItemDict(node) 186 if not datadict: 187 self.SetPyData(node, {'type': type}) 188 else: 189 self.GetTreeItemDict(node)['type'] = type 190 return
191
192 - def GetBranchName(self, node):
193 """Get the name of the branch in which node resides.""" 194 fp = self.GetFitRoot(node) 195 return self.GetItemText(fp)
196
197 - def GetLastPhase(self, node):
198 """Get the last phase child of the parent node. 199 200 This method is helpful in placing datasets and phases into the fit tree. 201 This method depends on the fact that phases are placed before datasets 202 in the fit tree. 203 """ 204 siblings = self.GetChildren(node) 205 lastphase = None 206 for sib in siblings: 207 if self.GetNodeType(sib) == "dataset": break 208 elif self.GetNodeType(sib) == "calculation": break 209 else: lastphase = sib 210 return lastphase
211
212 - def GetLastDataSet(self, node):
213 """Get the last dataset child of the fit node. 214 215 If there is no last dataset node, this may return the last phase node. 216 The purpose of getting this node is to know where to place another node, 217 so the actual node type is not important. 218 """ 219 siblings = self.GetChildren(node) 220 lastdata = None 221 for sib in siblings: 222 if self.GetNodeType(sib) == "calculation": break 223 else: lastdata = sib 224 return lastdata
225
226 - def GetNumPhases(self, node):
227 """Get the number of phases in a branch. 228 229 node -- A node in the branch, or the root of the branch. 230 """ 231 parent = self.GetFitRoot(node) 232 family = self.GetChildren(parent) 233 phases = [item for item in family if self.GetNodeType(item) == 'phase'] 234 return len(phases)
235
236 - def GetNumDataSets(self, node):
237 """Get the number of datasets in a branch. 238 239 node -- A node in the branch, or the root of the branch. 240 """ 241 parent = self.GetFitRoot(node) 242 family = self.GetChildren(parent) 243 phases = [item for item in family if self.GetNodeType(item) == 'dataset'] 244 return len(phases)
245
246 - def GetPositionInSubtree(self, node):
247 """Get the index if the node in its subtree. 248 249 For fits the position is absolute within the tree. For phases, datasets, 250 and calculations, the location is taken to be in reference to the other 251 nodes of its type. This is designed to be compatible with the control 252 center. 253 """ 254 parent = self.GetItemParent(node) 255 brood = self.GetChildren(parent) 256 pos = 0 257 for sib in brood: 258 if sib == node: 259 break 260 else: pos += 1 261 nodetype = self.GetNodeType(node) 262 if nodetype == 'dataset': 263 pos -= self.GetNumPhases(node) 264 if nodetype == 'calculation': 265 pos -= self.GetNumPhases(node) + self.GetNumDataSets(node) 266 return pos
267
268 - def SetControlData(self, node, data):
269 """Set the control center data associated with the node. 270 271 This need only be called for 'fit' nodes. 272 This is the "cdata" entry in the data dictionary of the 273 node. It holds the object with which the right panel interfaces. For 274 example, for a 'phase' node, it contains a Structure object. 275 """ 276 nodetype = self.GetNodeType(node) 277 if nodetype != 'fit': 278 message = 'Node type %s does not hold its own data' % nodetype 279 raise FitTreeError, message 280 281 self.GetTreeItemDict(node)['cdata'] = data 282 return
283
284 - def GetControlData(self, node):
285 """Get the control center data associated with a node. 286 287 NOTE: The fit-root of a node holds this data. This method makes it 288 convenient to retrieve it. 289 """ 290 nodetype = self.GetNodeType(node) 291 parent = self.GetFitRoot(node) 292 pdata = self.GetTreeItemDict(parent)['cdata'] 293 if nodetype == 'fit': 294 return pdata 295 elif nodetype == 'phase': 296 pos = self.GetPositionInSubtree(node) 297 return pdata.getStructure(pos) 298 elif nodetype == 'dataset': 299 pos = self.GetPositionInSubtree(node) 300 return pdata.getDataSet(pos) 301 elif nodetype == 'calculation': 302 pos = self.GetPositionInSubtree(node) 303 return pdata.getCalculation(pos) 304 else: 305 message = "Node of type %s does not exist" % nodetype 306 raise FitTreeError, message 307 return
308
309 - def AddFit(self, fitname = "Fit 1", cdata = None, paste = False):
310 """Append a new fit tree to the end of the current fits. 311 312 fitname -- The name of the fit. This is incremented if it already 313 exists. 314 cdata -- Control data for the node. If cdata is None (default), 315 then the control is asked to create new data. 316 paste -- Whether or not the cdata is being pasted from another 317 node (default False). 318 319 Returns the id of the new node. 320 """ 321 # Name the fit, but check to not duplicate names. 322 fits = self.GetChildren(self.root) 323 names = map(self.GetItemText, fits) 324 fitname = incrementName(fitname, names) 325 326 newfit = self.AppendItem(self.root, fitname) 327 self.SetNodeType(newfit, 'fit') 328 self.SetItemImage(newfit, self.fitbmid, wx.TreeItemIcon_Normal) 329 pos = self.GetPositionInSubtree(newfit) 330 331 try: 332 # Set the node data for the new node 333 if cdata is None: 334 cdata = self.control.newFitting(fitname, pos) 335 elif paste: 336 cdata = self.control.paste(cdata, None, fitname, pos) 337 self.SetControlData(newfit, cdata) 338 return newfit 339 except: 340 self.Delete(newfit) 341 raise 342 return
343
344 - def AddPhase(self, node, label, insertafter=None, filename=None, 345 makedata = True, cdata=None):
346 """Add a new blank Phase to the tree as a child of node. 347 348 node -- The parent 'fit' node. 349 label -- The name of the new node. 350 insertafter -- The node after which to insert the new phase. If 351 insertafter is None (default) the new phase is 352 appended to the end of the phases in the subtree of 353 the parent node. 354 filename -- The file from which to load the structure. If this is 355 None (default), a new structure is created. 356 makedata -- Tells whether the control needs to make data for the 357 node (default True). 358 cdata -- Control data for the node. If cdata is None (default), 359 then it is assumed that the node already has data in the 360 control. See ExtendProjectTree and __InsertBranch for 361 examples of how this is used. 362 363 Phases are always placed before DataSets. 364 365 Raises: 366 FitTreeError if node is not a "fit" node. 367 FitTreeError if insertafter is not a "phase" node. 368 369 Returns the id of the new node. 370 """ 371 # Check to make sure the new phase is a child of a fit or calculation 372 nodetype = self.GetNodeType(node) 373 if nodetype != "fit": 374 message = "Can only add a phase as a child of a fit." 375 raise FitTreeError, message 376 377 if insertafter is not None: 378 afttype = self.GetNodeType(insertafter) 379 if afttype != "phase": 380 insertafter = None 381 382 if insertafter: 383 newphase = self.InsertItem(node, insertafter, label) 384 else: 385 lastphase = self.GetLastPhase(node) 386 if lastphase: 387 # Put the new phase after the last 388 newphase = self.InsertItem(node, lastphase, label) 389 else: 390 newphase = self.PrependItem(node, label) 391 392 self.SetNodeType(newphase, "phase") 393 self.SetItemImage(newphase, self.phabmid, wx.TreeItemIcon_Normal) 394 395 # Set the control data to the new phase 396 pdata = self.GetControlData(node) 397 pos = self.GetPositionInSubtree(newphase) 398 399 # Try to get/make the node data from the control. If it doesn't work, 400 # then delete the new node. 401 try: 402 if makedata: 403 if filename is None: 404 self.control.newStructure(pdata, label, pos) 405 else: 406 self.control.loadStructure(pdata, filename, label, pos) 407 408 elif cdata is not None: 409 self.control.paste(cdata, pdata, label, pos) 410 return newphase 411 except: 412 self.Delete(newphase) 413 raise 414 return
415
416 - def AddDataSet(self, node, label, insertafter=None, filename=None, 417 makedata=True, cdata=None):
418 """Add a new DataSet to the tree as a child of fit. 419 420 node -- The parent node of the dataset. Must be 'fit' type. 421 label -- The label of the new node. 422 insertafter -- The node after which to insert the new dataset. If 423 insertafter is None (default) the new dataset is 424 appended to the end of the datasets in the subtree of 425 the parent node. 426 filename -- The name of the file from which to load the data. 427 makedata -- Tells whether the control needs to make data for the 428 node (default True). If True, cdata is ignored. 429 cdata -- Control data for the node. If False cdata is None 430 (default), then it is assumed that the node already has 431 data in the control. See ExtendProjectTree and 432 __InsertBranch for examples of how this is used. 433 434 DataSets are always placed after Phases. 435 436 Raises: 437 FitTreeError if node is not a "fit" node. 438 FitTreeError if insertafter is not a "dataset" node. 439 440 Returns the id of the new node. 441 """ 442 # Check to make sure the new dataset is a child of a fit 443 nodetype = self.GetNodeType(node) 444 if nodetype != "fit": 445 message = "Can only add a data set as a child of a fit." 446 raise FitTreeError, message 447 448 if insertafter is not None: 449 afttype = self.GetNodeType(node) 450 if afttype != "dataset": 451 insertafter = None 452 453 if insertafter: 454 newset = self.InsertItem(node, insertafter, label) 455 else: 456 lastset = self.GetLastDataSet(node) 457 if lastset: 458 newset = self.InsertItem(node, lastset, label) 459 else: 460 newset = self.PrependItem(node, label) 461 462 self.SetNodeType(newset, "dataset") 463 self.SetItemImage(newset, self.dtsbmid, wx.TreeItemIcon_Normal) 464 # Attach the control center data to the new dataset 465 pos = self.GetPositionInSubtree(newset) 466 pdata = self.GetControlData(node) 467 468 try: 469 if makedata: 470 if filename is not None: 471 self.control.loadDataset(pdata, filename, label, pos) 472 else: 473 raise FitTreeError, "Cannot load a dataset without a name!" 474 elif cdata is not None: 475 self.control.paste(cdata, pdata, label, pos) 476 return newset 477 except: 478 self.Delete(newset) 479 raise 480 return
481
482 - def AddCalc(self, node, label, insertafter=None, makedata=True, cdata=None):
483 """Add a new DataSet to the tree as a child of fit. 484 485 node -- The parent node of the calculation. Must be 'fit' type. 486 label -- The label of the new node. 487 insertafter -- The node after which to insert the new calculation. If 488 insertafter is None (default) the new calculation is 489 appended to the end of the calculation in the subtree of 490 the parent node. 491 makedata -- Tells whether the control needs to make data for the 492 node (default True). If True, cdata is ignored. 493 cdata -- Control data for the node. If False cdata is None 494 (default), then it is assumed that the node already has 495 data in the control. See ExtendProjectTree and 496 __InsertBranch for examples of how this is used. 497 498 Calculations are always placed after datasets. 499 500 Raises: 501 FitTreeError if node is not a "fit" node. 502 FitTreeError if insertafter is not a "calculation" node. 503 504 Returns the id of the new node. 505 """ 506 # Check to make sure the new calculation is a child of a fit 507 nodetype = self.GetNodeType(node) 508 if nodetype != "fit": 509 message = "Can only add a calculation as a child of a fit." 510 raise FitTreeError, message 511 512 if insertafter is not None: 513 afttype = self.GetNodeType(node) 514 if afttype != "calculation": 515 insertafter = None 516 517 sibs = self.GetCalculations(node) 518 names = map(self.GetItemText, sibs) 519 label = incrementName(label, names) 520 521 if insertafter: 522 newcalc = self.InsertItem(node, insertafter, label) 523 else: 524 newcalc = self.AppendItem(node, label) 525 526 self.SetNodeType(newcalc, "calculation") 527 self.SetItemImage(newcalc, self.clcbmid, wx.TreeItemIcon_Normal) 528 # Attach the control center data to the new datacalc 529 pos = self.GetPositionInSubtree(newcalc) 530 pdata = self.GetControlData(node) 531 532 try: 533 if makedata: 534 self.control.newCalculation(pdata, label, pos) 535 elif cdata is not None: 536 self.control.paste(cdata, pdata, label, pos) 537 return newcalc 538 except: 539 self.Delete(newcalc) 540 raise 541 return
542
543 - def CopyBranch(self, startnode):
544 """Make a copy of a tree branch. 545 546 The branch is held in the system clipboard so it can be used in another 547 instance of the fittree. 548 """ 549 nodetype = self.GetNodeType(startnode) 550 cdata = self.control.copy(self.GetControlData(startnode)) 551 if isinstance(cdata, Fitting): 552 cdata = cdata.stripped() 553 cdata.type = nodetype 554 cdatastring = cPickle.dumps(cdata) 555 cdatastring = "pdfgui_cliboard=" + cdatastring 556 textdata = wx.PyTextDataObject(cdatastring) 557 if not wx.TheClipboard.IsOpened(): 558 opened = wx.TheClipboard.Open() 559 if not opened: raise FitTreeError, "Cannot open the clipboard." 560 wx.TheClipboard.SetData(textdata) 561 wx.TheClipboard.Close() 562 return
563
564 - def GetClipboard(self):
565 """Get the clipboard data. 566 567 Returns the controldata in the clipboard, or None if the clipboard is 568 empty or contains the wrong type of data. 569 """ 570 # Check to see if data is present 571 if not wx.TheClipboard.IsSupported(wx.DataFormat(wx.DF_TEXT)): 572 return None 573 574 textdata = wx.PyTextDataObject() 575 if not wx.TheClipboard.IsOpened(): 576 opened = wx.TheClipboard.Open() 577 if not opened: return None 578 success = wx.TheClipboard.GetData(textdata) 579 wx.TheClipboard.Close() 580 if not success: return None 581 cdatastring = textdata.GetText() 582 583 cdata = None 584 if cdatastring[:16] == "pdfgui_cliboard=": 585 cdatastring = cdatastring[16:] 586 try: 587 cdata = cPickle.loads(str(cdatastring)) 588 except: 589 pass 590 591 #try: 592 # cdata = cPickle.loads(str(cdatastring)) 593 #except (cPickle.UnpicklingError, UnicodeEncodeError, EOFError): 594 # cdata = None 595 return cdata
596
597 - def PasteBranch(self, entrypoint = None):
598 """Paste the branch from the clipboard into tree at the given node. 599 600 A certain type of branch can only be copied to specific places. 601 602 fit - A fit can be pasted to anywhere. This does not overwrite 603 an existing node, but simply inserts the fit into the 604 last available slot. 605 phase - A phase can be pasted from anywhere. If pasted from a 606 fit, it is placed at the end of the phase section of 607 that node. If inserted from a dataset or a calculation, 608 it is placed at the end of the phase section. 609 dataset - A dataset can be pasted from anywhere. If pasted from a 610 fit, the dataset is appended at the end of the other 611 datasets. If pasted from a dataset, the pasted set is 612 inserted right after that one. If pasted from a phase, 613 it is placed at the beginning of the dataset section. If 614 pasted from a calculation, it is placed at the end of 615 the dataset section. 616 calculation - A calculation can be pasted to anywhere, but it appears 617 at the end of the calculation section of the tree. If 618 pasted from a calculation node, it is inserted after 619 that node. 620 621 Raises: 622 FitTreeError if the entrypoint and branch type are incompatible. 623 """ 624 cdata = self.GetClipboard() 625 if cdata is None: 626 message = "There is no branch to paste!" 627 raise FitTreeError, message 628 629 # Now we have the cdata, we must put it into the tree 630 branchtype = cdata.type 631 insertafter = None 632 prepend = False 633 entrytype = None 634 if entrypoint: 635 entrytype = self.GetNodeType(entrypoint) 636 # Check to see what we are trying to paste, and where. 637 638 if branchtype == "fit": 639 # Paste after the selected fit containing the selection, or 640 # after the last fit if a calculation is selected. If nothing is 641 # selected, just paste it! 642 643 entrytype = None 644 if entrypoint: 645 entrypoint = self.GetFitRoot(entrypoint) 646 entrytype = self.GetNodeType(entrypoint) 647 648 if entrytype is None: 649 entrypoint = self.root 650 insertafter = None 651 elif entrytype == "fit": 652 insertafter = entrypoint 653 entrypoint = self.root 654 else: # Just in case 655 raise FitTreeError, "Cannot paste a fit branch here." 656 657 if branchtype == "phase": 658 # Paste after selected phase, or append to the end of the phase 659 # section of a fit. 660 661 if entrytype == "phase": 662 # The entry is to be a sibling. 663 insertafter = entrypoint 664 entrypoint = self.GetItemParent(entrypoint) 665 elif entrytype in ("dataset", "calculation"): 666 # Paste to the end of the phases, if they exist. 667 entrypoint = self.GetItemParent(entrypoint) 668 insertafter = self.GetLastPhase(entrypoint) 669 if not insertafter: 670 # Put the branch at the beginning of the phases 671 prepend = True 672 elif entrytype == "fit": 673 # Get the last phase in the phase section, which may not 674 # exist. 675 insertafter = self.GetLastPhase(entrypoint) 676 if not insertafter: 677 # Put the branch at the beginning of the phases 678 prepend = True 679 else: # Just in case 680 raise FitTreeError, "Cannot paste a phase branch here." 681 682 if branchtype == "dataset": 683 # Paste after a selected dataset, or into a selected fit. 684 685 if entrytype == "dataset": 686 # The entry is to be a sibling. 687 insertafter = entrypoint 688 entrypoint = self.GetItemParent(entrypoint) 689 elif entrytype == "phase": 690 # The entry goes to the end of the phases, which must exist. 691 entrypoint = self.GetItemParent(entrypoint) 692 insertafter = self.GetLastPhase(entrypoint) 693 elif entrytype == "calculation": 694 # The entry goes to the end of the datasets. 695 entrypoint = self.GetItemParent(entrypoint) 696 insertafter = self.GetLastDataSet(entrypoint) 697 elif entrytype == "fit": 698 insertafter = self.GetLastDataSet(entrypoint) 699 # The entrypoint is ok. The branch is appended to the end of 700 # the calculations. 701 pass 702 else: 703 raise FitTreeError, "Cannot paste a data set branch here." 704 705 if branchtype == "calculation": 706 # Paste after the selected calculation or after the calculations. 707 708 if entrytype == "calculation": 709 # The entry is to be a sibling. 710 insertafter = entrypoint 711 entrypoint = self.GetItemParent(entrypoint) 712 elif entrytype in ("phase", "dataset"): 713 entrypoint = self.GetItemParent(entrypoint) 714 insertafter = self.GetLastDataSet(entrypoint) 715 elif entrytype == "fit": 716 insertafter = self.GetLastDataSet(entrypoint) 717 else: # Just in case 718 raise FitTreeError, "Cannot paste a calculation branch here." 719 720 721 # Now set the name of the item to be inserted. 722 label = self.__copyLabel(cdata.name, entrypoint) 723 724 # Now we have a label. We must insert the item into the tree. 725 newnode = self.__InsertBranch(cdata, entrypoint, label, insertafter, 726 prepend) 727 728 return newnode
729
730 - def __copyLabel(self, oldlabel, entrypoint):
731 """Make a new label that is appropriate for a new node.""" 732 # Append "_copy" to the end of the label, unless it already has that. 733 # In that case, just add a number to indicate which copy it is. 734 siblings = self.GetChildren(entrypoint) 735 labels = map(self.GetItemText, siblings) 736 match = "_copy\d*$" 737 label = re.sub(match, '', oldlabel) 738 label += "_copy" 739 label = incrementName(label, labels) 740 return label
741
742 - def __InsertBranch(self, cdata, entrypoint, label, insertafter = None, 743 prepend = False):
744 """Instert control data into the tree. 745 746 cdata -- The control data that goes with the branch 747 entrypoint -- The subbranch (fit root) to paste into 748 label -- The label of the new node 749 insertafter -- A node after which to insert. If insertafter is None 750 (default), then the new node will be pasted after the 751 last node of the same type. 752 prepend -- Prepend to the beginning of the node group (default 753 False). insertafter takes prescedent over prepend. 754 755 Returns the newly inserted node. 756 """ 757 if cdata is None: 758 message = "There is no branch to paste!" 759 raise FitTreeError, message 760 761 branchtype = cdata.type 762 #cdata.name = label 763 if branchtype == 'fit': 764 cdata.name = label 765 newnode = self.ExtendProjectTree([cdata.organization()], 766 clear=False, paste=True) 767 elif branchtype == 'phase': 768 newnode = self.AddPhase(entrypoint, label, insertafter=insertafter, 769 makedata=False, cdata=cdata) 770 elif branchtype == 'dataset': 771 newnode = self.AddDataSet(entrypoint, label, insertafter=insertafter, 772 makedata=False, cdata=cdata) 773 elif branchtype == 'calculation': 774 newnode = self.AddCalc(entrypoint, label, insertafter=insertafter, 775 makedata=False, cdata=cdata) 776 else: 777 raise FitTreeError, "Unrecognized node type: %s" % branchtype 778 779 return newnode
780
781 - def DeleteBranches(self, selections):
782 """Remove the subtree starting from the selected node(s).""" 783 # Get a list of branch heads 784 branchset = [node for node in selections if self.GetNodeType(node) ==\ 785 "fit"] 786 787 # Get their children 788 childset = [] 789 for node in branchset: 790 childset.extend(self.GetChildren(node)) 791 792 # Collect all nodes, removing any children of branch nodes. 793 nodeset = [node for node in selections if node not in childset] 794 795 for node in nodeset: 796 cdata = self.GetControlData(node) 797 self.control.remove(cdata) 798 self.Delete(node) 799 return nodeset
800
801 - def SelectAll(self):
802 """Select all nodes.""" 803 self.UnselectAll() 804 fits = self.GetChildren(self.root) 805 for fit in fits: 806 children = self.GetChildren(fit) 807 self.SelectItem(fit) 808 for child in children: 809 self.SelectItem(child) 810 return
811
812 - def SelectAllType(self, node = None):
813 """Select all nodes of same type as passed node. 814 815 node -- Node whose type to select. If node is None (default), then 816 all fit nodes will be selected. 817 """ 818 self.UnselectAll() 819 if node is None: 820 # Get the first fit node 821 fits = self.GetChildren(self.root) 822 if not fits: return 823 node = fits[0] 824 typelist = self.GetAllType(node) 825 for item in typelist: 826 self.SelectItem(item) 827 return
828
829 - def ExtendProjectTree(self, treelist, clear=True, paste=False):
830 """Extend the project tree from the treelist. 831 832 treelist -- A list of control data returned by 833 Oraganizer.organization() 834 clear -- Clear the tree before adding new nodes (default True) 835 paste -- Whether or not the cdata is being pasted from another 836 node (default False). 837 838 The treelist here is of the type returned from pdfguicontrol.load. 839 It is a list of fit lists with the following format. 840 node[0] -- fit object 841 node[1] -- list of (name, dataset) tuples 842 node[2] -- list of (name, phase) tuples 843 node[3] -- list of (name, calculation) tuples 844 845 Note that node[1] should be empty if the node is a calculation. 846 847 Returns the last insterted fit or calculation node 848 """ 849 # Clean slate 850 if clear: 851 self.DeleteAllItems() 852 self.InitializeTree() 853 roots = [] 854 855 # Return if the treelist is empty 856 if not treelist: return 857 858 # Build the tree 859 for item in treelist: 860 broot = item[0] 861 name = broot.name 862 node = self.AddFit(name, cdata = broot, paste = paste) 863 864 if node is None: 865 message = "Cannot insert data. Malformed tree list." 866 raise FitTreeError, message 867 868 roots.append(node) 869 # Build the rest of the tree. Note that we don't want to create new 870 # data, but we don't pass the cdata since it is already included in 871 # the fit root. 872 phases = item[2] 873 for (name, phase) in phases: 874 self.AddPhase(node, name, makedata = False) 875 dsets = item[1] 876 for (name, set) in dsets: 877 self.AddDataSet(node, name, makedata = False) 878 calcs = item[3] 879 for (name, calc) in calcs: 880 self.AddCalc(node, name, makedata = False) 881 882 for item in roots: 883 self.Expand(item) 884 return node
885 886 # End class FitTree 887 888 889 # Exceptions
890 -class FitTreeError(ControlError):
891 - def __init__(self, *args):
892 ControlError.__init__(self, *args) 893 return
894 # End class FitTreeError 895 896 897 # Utility functions
898 -def incrementName(name, namelist, start = 1):
899 """Increment the name by assigning the lowest number to the end such 900 that the name does not appear in the namelist. 901 """ 902 newname = name 903 match = "\d+$" 904 counter = start 905 while newname in namelist: 906 newname = re.sub(match, '', name) 907 counter += 1 908 newname = "%s%i" % (newname, counter) 909 return newname
910 911 __id__ = "$Id: fittree.py 2980 2009-04-02 00:14:33Z juhas $" 912