CMS 3D CMS Logo

 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Pages
LineDecayView.py
Go to the documentation of this file.
1 import sys
2 import math
3 
4 from PyQt4.QtCore import Qt, QPoint, QPointF, QRect, QSize, SIGNAL, QCoreApplication, QMimeData, QRectF
5 from PyQt4.QtGui import QWidget, QPainter, QPolygon, QColor, QPen, QPalette, QPainterPath, QFont, QFontMetrics, QApplication, QDrag, QPixmap,QSizePolicy,QMessageBox, QTransform, QBrush
6 
7 import logging
8 
9 from Vispa.Main.Application import Application
10 from Vispa.Main.Exceptions import exception_traceback
11 from Vispa.Gui.ConnectableWidgetOwner import ConnectableWidgetOwner
12 from Vispa.Gui.VispaWidget import VispaWidget
13 from Vispa.Gui.WidgetContainer import WidgetContainer
14 from Vispa.Gui.Zoomable import Zoomable
15 from Vispa.Share.ObjectHolder import ObjectHolder
16 from Vispa.Share.ThreadChain import ThreadChain
17 from Vispa.Views.AbstractView import AbstractView
18 from Vispa.Views.WidgetView import WidgetView
19 
20 
21 try:
22  from pxl.algorithms import *
23  import_autolayout_error=None
24 except Exception as e:
25  import_autolayout_error=(str(e),exception_traceback())
26 
28 
29  LABEL = "&Line Decay View"
30 
31  DECAY_OBJECT_MIME_TYPE = "text/x-decay-object"
32  WARNING_ABOVE = 1000
33 
34  def __init__(self, parent=None):
35  WidgetView.__init__(self, parent)
36  self.setAutoFillBackground(True)
37  self.setPalette(QPalette(Qt.white))
38  self.setAcceptDrops(True)
39  self._tabController = None
40  self._allNodes = {}
41  self._nodeVector = None
42  self._operationId = 0
43  self._editable=False
44  self._noDecorationsMode=False
46 
48  self.connect(self._crateDecayObjectsDecaysThreadChain, SIGNAL("finishedThreadChain"), self.createDecayObjectsThreadChainFinished)
49 
50  def noDecorationsMode(self):
51  return self._noDecorationsMode
52 
53  def operationId(self):
54  return self._operationId
55 
57  return self.DECAY_OBJECT_MIME_TYPE
58 
59  def setDataObjects(self, objects):
60  """ Overwrite WidgetView's function.
61 
62  Do not clear content. This will be done by updateContent() only if it is necessary.
63  """
64  AbstractView.setDataObjects(self, objects)
65  self.clear() # NO!
66 
67  def cancel(self):
68  """ Stop all running operations.
69  """
70  self._operationId += 1
71 
72  def updateContent(self):
73  logging.debug(self.__class__.__name__ +": updateContent()")
74  self.cancel()
75  self._updatingFlag+=1
76  operationId = self._operationId
77  numObjects=self.numberDataObjectChildren()
78  self._noDecorationsMode=numObjects>abs(self.WARNING_ABOVE)
79  if self.WARNING_ABOVE>0 and numObjects>self.WARNING_ABOVE:
80  result=QCoreApplication.instance().showMessageBox("You are about to display more than "+str(numObjects)+" (>"+str(self.WARNING_ABOVE)+") objects. This may take some time. Labels will not be displayed.",
81  "Would you like to continue?",
82  QMessageBox.Yes | QMessageBox.No,
83  QMessageBox.Yes, [("Yes (remember my decision)",QMessageBox.YesRole)])
84  if result == QMessageBox.No:
85  self._updatingFlag -=1
86  return False
87  if result == 0:
89  existingWidgets = []
90  for object in self.applyFilter(self.dataObjects()):
91  if object == None:
92  # usually this should not happen, just in case dataAccessor misbehaves
93  continue
94  if self.dataAccessor().isContainer(object):
95  # event or event view
96  eventWidget = self.createLineDecayContainer(object)
97  self._topLevelContainer=eventWidget
98  existingWidgets += [eventWidget]
99  else:
100  # particle
101  particleWidget = ParticleWidget(self, ParticleWidget.NONE, "", "")
102  particleWidget.setMinimumSize(DecayLine.DEFAULT_LENGTH, 40)
103  particleWidget.setColors(QColor('white'), QColor('white'), QColor('white'))
104  particleWidget.setSelectable(True)
105  particleWidget.setObject(object)
106  decayLine = DecayLine(particleWidget, QPoint(5, 20), QPoint(particleWidget.width() -5, 20))
107  if self.dataAccessor():
108  decayLine.setLabel(self.dataAccessor().label(object))
109  decayLine.setColor(self.dataAccessor().color(object))
110  decayLine.setLineStyle(self.dataAccessor().lineStyle(object))
111  else:
112  decayLine.setLabel("Particle")
113 
114  particleWidget.setDecayObject(decayLine)
115  existingWidgets += [particleWidget]
116 
117  for child in self.children():
118  if operationId != self.operationId():
119  self._updatingFlag -=1
120  return False
121  if hasattr(child, "object") and not child in existingWidgets:
122  # remove widgets of objects which no longer exist
123  child.setDeletable(True)
124  child.delete()
125  elif hasattr(child, "createDecayObjectsFromPxlObject"):
126  # every cycle of this loop takes long time
127  # so process window events
128  #if not Application.NO_PROCESS_EVENTS:
129  # QCoreApplication.instance().processEvents()
130  #self._crateDecayObjectsDecaysThreadChain.addCommand(child.createDecayObjectsFromPxlObject)
131  child.createDecayObjectsFromPxlObject(operationId)
132  child.setDeletable(False)
133  child.setDragable(False)
134 
136 
137  self._updatingFlag-=1
138  return True
139 
141  #logging.debug(self.__class__.__name__ +": createDecayObjectsThreadChainFinished()")
142  if not self._editable:
143  self.autolayout()
144 
145  def autolayout(self):
146  #logging.debug(self.__class__.__name__ +": autolayout()")
147  if import_autolayout_error!=None:
148  logging.error(__name__ + ": Could not import autolayout algorithm: "+import_autolayout_error[1])
149  QCoreApplication.instance().errorMessage("Could not import autolayout algorithm (see logfile for details):\n"+import_autolayout_error[0])
150  return
151  margin = 10 * self.zoomFactor()
152  x = margin
153  y = margin
154  for child in self.children():
155  if isinstance(child, QWidget):
156  if isinstance(child, LineDecayContainer):
157  child.autolayout()
158  child.move(x, y)
159  y += child.height() + margin
160 
162  #logging.debug(self.__class__.__name__ +": childFinishedAutolayouting()")
163  self.autosizeScrollArea()
164  #for child in self.children():
165  # if isinstance(child, LineDecayContainer):
166  # child.autosize()
167 
168  def lineDecayContainer(self, object):
169  """ Returns the widget component representing the given pxl object.
170  """
171  if not object:
172  return None
173  objectId = self.dataAccessor().id(object)
174 
175  for child in self.children():
176  if isinstance(child, LineDecayContainer):
177  if self.dataAccessor().id(child.object()) == objectId:
178  return child
179  subChild = child.childContainer(objectId)
180  if subChild:
181  return subChild
182  return None
183 
184  def createLineDecayContainer(self, object, objectMother=None):
185  if not object:
186  return None
187 
188  existingWidget = self.lineDecayContainer(object)
189  if existingWidget:
190  # It may happen that an container exists but at the wrong hierarchical level.
191  # e.g. if in TreeView after an event view the superior event is selected.
192 
193  if objectMother:
194  parentWidget = self.lineDecayContainer(objectMother)
195  else:
196  parentWidget = self
197  oldParent = existingWidget.parent()
198 
199  if parentWidget != oldParent:
200  self.disconnect(existingWidget, SIGNAL("finishedAutolayouting"), oldParent.childFinishedAutolayouting)
201  existingWidget.setParent(parentWidget)
202  self.connect(existingWidget, SIGNAL("finishedAutolayouting"), parentWidget.childFinishedAutolayouting)
203  existingWidget.setVisible(True)
204 
205  return existingWidget
206 
207  if objectMother:
208  parentWidget = self.lineDecayContainer(objectMother)
209  if not parentWidget:
210  logging.warning(self.__class__.__name__ +": createLineDecayContainer() - Cannot add child object to given object. Object does not belong to this "+ self.__class__.__name__ +".")
211  return
212 
213  lineDecayView = parentWidget.createChildContainer(object)
214  else:
215  # parentWidget == self:
216  lineDecayView = LineDecayContainer(self)
217  self.connect(lineDecayView, SIGNAL("finishedAutolayouting"), self.childFinishedAutolayouting)
218  lineDecayView.setPxlObject(object)
219 
220  self.connect(lineDecayView, SIGNAL("selected"), self.onSelected)
221  return lineDecayView
222 
223 # Apparently setTabController() is unnecessary, remove if sure about that (2010-06-29)
224 # def setTabController(self, controller):
225 # """ Sets tab controller.
226 # """
227 # WidgetView.setTabController(self, controller)
228 # self.connect(self, SIGNAL("selected"), controller.onSelected)
229 
230  def tabController(self):
231  """ Return tab controller.
232 
233  See setTabController()
234  """
235  parent=self
236  while parent!=None:
237  if hasattr(parent,"controller"):
238  return parent.controller()
239  parent=parent.parent()
240  return None
241 
242  def topLevelContainer(self):
243  return self._topLevelContainer
244 
245  def onSelected(self, object):
246  """ When item is selected in SubView forward signal.
247  """
248  #logging.debug(self.__class__.__name__ + ": onSelected()")
249  self.emit(SIGNAL("selected"), object)
250 
251  def setEditable(self, editable):
252  self._editable=editable
253  for child in self.children():
254  if isinstance(child, LineDecayContainer):
255  child.setEditable(editable)
256 
257  def editable(self):
258  return self._editable
259 
260  def scrollBarValueChanged(self, event):
261  for child in self.children():
262  if isinstance(child, LineDecayContainer):
263  child.scheduleUpdateVisibleList(True)
264 
265 
267  """ Represents an Event or EventView
268  """
269  # inherited properties
270  AUTOSIZE = True
271  AUTOSIZE_KEEP_ASPECT_RATIO = False
272  AUTOLAYOUT_CHILDREN_ENABLED = True
273  AUTOSIZE_ADJUST_CONTAINER_POSITION = False
274  WIDTH = 300
275  HEIGHT = 300
276 
277  def __init__(self, parent):
278  logging.debug(self.__class__.__name__ +": __init__()")
281  ObjectHolder.__init__(self)
282  WidgetContainer.__init__(self, parent)
283  self.setEditable(parent.editable())
284  self.setSelectable(True)
285 
286  self._selectedList = []
287  self._visibleList = []
288  self._hoveredObject = None
289  self.setMouseTracking(True) # receive mouse events even if no button is pressed
290  self._titleClicked = False
291 
292  self._pxlObject = None
294  self._particlesDict = {}
296  self.setTitle(" ") # calculate correct height
297  self.show()
298  self.connect(self._threadChain, SIGNAL("finishedThreadChain"), self.autolayoutThreadFinished)
299  if isinstance(parent, LineDecayContainer):
300  self.connect(self, SIGNAL("finishedAutolayouting"), parent.childFinishedAutolayouting)
301 
302  def noDecorationsMode(self):
303  return self.parent().noDecorationsMode()
304 
305  def applyFilter(self,objects):
306  """ Redirect filtering to parent.
307  """
308  return self.parent().applyFilter(objects)
309 
310  def dataObjects(self):
311  """ Do not filter widget but rather the pxl objects
312  """
313  return self._dataObjects
314 
315  def setEditable(self, editable):
316  self._editable = editable
317  self.setAcceptDrops(editable)
318  # make event views freely moveable
319  self.enableAutolayoutChildren(not editable)
320  for child in self.children():
321  if isinstance(child, LineDecayContainer):
322  child.setEditable(editable)
323 
324  def editable(self):
325  return self._editable
326 
327  def setAcceptDrops(self, accept):
328  """ Sets whether this view accepts drops and forwards the information to sub LineDecayContainers.
329  """
330  WidgetContainer.setAcceptDrops(self, accept)
331  for child in self.children():
332  if isinstance(child, LineDecayContainer):
333  child.setAcceptDrops(accept)
334 
335  def setDataAccessor(self, accessor):
336  """ Sets the DataAccessor from which the lines are created.
337 
338  You need to call updateContent() in order to make the changes visible.
339  """
340  if not isinstance(accessor, BasicDataAccessor):
341  raise TypeError(__name__ + " requires data accessor of type BasicDataAccessor.")
342  if not isinstance(accessor, RelativeDataAccessor):
343  raise TypeError(__name__ + " requires data accessor of type RelativeDataAccessor.")
344  if not isinstance(accessor, ParticleDataAccessor):
345  raise TypeError(__name__ + " requires data accessor of type ParticleDataAccessor.")
346  AbstractView.setDataAccessor(self, accessor)
347 
348  def sizeHint(self):
349  """ If there is only one container in parent increase it so it fills whole space.
350  """
351  if not self._editable:
352  return WidgetContainer.sizeHint(self)
353 
354  minWidth = 0
355  minHeight = 0
356  if not self.collapsed() and self.parent().dataObjectsCount() == 1:
357  margin = 10
358  minWidth = (self.parent().width() - 2 * margin) / self.zoomFactor()
359  minHeight = (self.parent().height() - 2 * margin) / self.zoomFactor()
360 
361  increaseSize = 0
362  if not self.collapsed():
363  # make it easier to drop particles into container
364  increaseSize = 30
365 
366  sizeHint = WidgetContainer.sizeHint(self)
367  return QSize(max(sizeHint.width() + increaseSize, minWidth), max(sizeHint.height() + increaseSize, minHeight))
368 
369  def childrenRect(self):
370  """ Overwrites QWidget's method and includes DecayObjects in addition to QWidgets.
371  """
372  minX = 0
373  minY = 0
374  maxX = 0
375  maxY = 0
376  if not self.collapsed():
377  first = True
378  for object in self.dataObjects():
379  if not isinstance(object, DecayNode):
380  continue
381  if first or minX > object.x():
382  minX = object.x()
383  if first or minY > object.y():
384  minY = object.y()
385  if first or maxX < object.x():
386  maxX = object.x()
387  if first or maxY < object.y():
388  maxY = object.y()
389  first = False
390  minX *= self.zoomFactor()
391  minY *= self.zoomFactor()
392  maxX = (maxX * self.zoomFactor() + self.getDistance("rightMargin"))
393  maxY = (maxY * self.zoomFactor() + self.getDistance("bottomMargin"))
394  return QRect(minX, minY, maxX - minX, maxY - minY).united(WidgetContainer.childrenRect(self))
395 
396  def children(self):
397  return WidgetContainer.children(self) + [node for node in self.dataObjects() if isinstance(node, DecayNode)]
398 
399  def deselectAllWidgets(self, exception=None):
400  """ Overwrite VispaWidgetOwner's method so in addition deselectAllObjects() is called.
401  """
402  self.deselectAllObjects()
403  WidgetContainer.deselectAllWidgets(self, exception)
404 
405  def setZoom(self, zoom):
406  """ Sets zoom of this widget and of it's children.
407  """
408  WidgetContainer.setZoom(self, zoom)
409 
410  for child in self.dataObjects():
411  if isinstance(child, Zoomable):
412  child.setZoom(zoom)
413 
415  self.update()
416 
417 # def dataObjectsNodePriority(self):
418 # return [obj for obj in self.dataObjects() if isinstance(obj, DecayNode)] + [obj for obj in self.dataObjects() if not isinstance(obj, DecayNode)]
419 
420  def setPxlObject(self, object):
421  self._pxlObject = object
422  if self.dataAccessor():
423  label = self.dataAccessor().label(object)
424  if label:
425  self.setTitle(label)
426 
427  def object(self):
428  return self._pxlObject
429 
430  def tabController(self):
431  """ Return tab controller.
432 
433  See setTabController()
434  """
435  return self.parent().tabController()
436 
437  def dataAccessor(self):
438  return self.parent().dataAccessor()
439 
440  def childContainer(self, objectId):
441  """ Returns the widget component representing the given pxl object.
442  """
443  if not objectId:
444  return None
445 
446  for child in self.children():
447  if isinstance(child, LineDecayContainer):
448  if self.dataAccessor().id(child.object()) == objectId:
449  return child
450  subChild = child.childContainer(object)
451  if subChild:
452  return subChild
453  return None
454 
455  def createChildContainer(self, object, pos=None):
456  lineDecayView = LineDecayContainer(self)
457  lineDecayView.setPxlObject(object)
458 
459  if not pos:
460  margin = 10 * self.zoomFactor()
461  pos = QPoint(margin, margin)
462  else:
463  pos -= QPoint(0.5* lineDecayView.width(), 0.5* lineDecayView.height())
464 
465  lineDecayView.move(pos)
466  return lineDecayView
467 
468  def dragEnterEvent(self, event):
469  """ Accepts drag enter event if module is dragged.
470  """
471  #logging.debug(self.__class__.__name__ + ": dragEnterEvent()")
472  if not self.tabController():
473  return
474  if event.mimeData().hasFormat(LineDecayView.DECAY_OBJECT_MIME_TYPE):
475  event.acceptProposedAction()
476 
477  def createObject(self,dropType,pos):
478  newObject = None
479  if dropType == "Node":
480  newObject = self.addDecayNode(pos)
481  self.select(newObject)
482  self.setFocus()
483  elif dropType == "EventView":
484  newObject = self.createChildContainer(self.object().createEventView(), pos)
485  newObject.select(True)
486  newObject.setFocus()
487  # connect selected signal to parent
488  parent=self
489  while hasattr(parent,"parent"):
490  if hasattr(parent,"onSelected"):
491  self.connect(newObject, SIGNAL("selected"), parent.onSelected)
492  break
493  parent=parent.parent()
494  else:
495  # assume dropType is the generic name or type of a particle
496  newObject = self.addParticleByType(dropType, pos)
497  self.select(newObject)
498  self.setFocus()
499 
500  if newObject:
501  self.update(newObject.boundingRect())
502  if self._editable:
503  self.autosize() # instead of full autolayout
504  if self.tabController():
505  self.tabController().setModified()
506  if hasattr(self.tabController(),"updateTreeView"):
507  self.tabController().updateTreeView()
508  return newObject
509 
510  def dropEvent(self, event):
511  """ Handle drop of module.
512  """
513  logging.debug(self.__class__.__name__ + ": dropEvent()")
514  if not self.tabController():
515  return
516 
517  if event.mimeData().hasFormat(LineDecayView.DECAY_OBJECT_MIME_TYPE):
518  dropType = str(event.mimeData().data(LineDecayView.DECAY_OBJECT_MIME_TYPE))
519  pos = event.pos() / self.zoomFactor()
520  if self.createObject(dropType, pos):
521  event.acceptProposedAction()
522 
523  def addParticleByType(self, particleType=None, pos=None):
524  """ This function asks the data accessor to create a new particle and makes sure it gets the properties (name, pdg id, charge) of the desired type.
525 
526  particleType may either be None or a string specifying the object type.
527  pos is in 100% coordinates.
528  """
529 
530  if not particleType:
531  # this is nothing
532  return None
533  dataAccessor = self.dataAccessor()
534 
535  newParticle = dataAccessor.createParticle()
536  #newParticle.setName(particleType) # use this name to find id
537  categoryName = "Object info"
538  dataAccessor.setProperty(newParticle, "Name", particleType, categoryName) # use this name to find id
539  particleId = self.dataAccessor().particleId(newParticle)
540  if particleId != None:
541  #newParticle.setPdgNumber(particleId)
542  dataAccessor.setProperty(newParticle, "PdgNumber", particleId, categoryName)
543  particleName = self.dataAccessor().defaultName(newParticle)
544  if particleName:
545  # normalize name using id
546  #newParticle.setName(particleName)
547  dataAccessor.setProperty(newParticle, "Name", particleName, categoryName)
548  #newParticle.setCharge(self.dataAccessor().charge(newParticle))
549  dataAccessor.setProperty(newParticle, "Charge", dataAccessor.charge(newParticle), categoryName) #TODO: check whether this is necessary
550  self._pxlObject.setObject(newParticle)
551 
552  return self.addDecayLine(newParticle, pos)
553 
554  def addDecayLine(self, object, pos=None):
555  """ This function accepts a data object (e.g. a pxl object), creates a DecayLine for it and adds the latter to this container's objects list.
556 
557  """
558  if not object:
559  return None
560 
561  if not pos:
562  pos = QPoint(10, 10)
563  else:
564  pos -= QPoint(0.5 * DecayLine.DEFAULT_LENGTH, 0)
565 
566  # id() is slow, so ids of existing objects are stored
567  if self.dataAccessor():
568  dataObjectId = self.dataAccessor().id(object)
569  if dataObjectId in self._existingObjectIds:
570  # maybe here should the existing object be returned
571  return None
572  self._existingObjectIds.append(str(dataObjectId))
573 
574  # map pxl relations to gui components
575  # find potentially, already existing DecayNodes to be used by the DecayLine that will be created below
576  motherNode = None
577  daughterNode = None
578  if self.dataAccessor():
579  for daughter in self.dataAccessor().daughterRelations(object):
580  if daughter in self._particlesDict.keys():
581  daughterDecayObject = self._particlesDict[daughter]
582  if not daughterNode:
583  daughterNode = daughterDecayObject.motherNode()
584  elif daughterDecayObject.motherNode() != daughterNode and \
585  daughterDecayObject.motherNode().unite(daughterNode):
586  # already found one daughter
587  # need to make sure all of the other relevant nodes are united
588  self.removeObject(daughterNode)
589  daughterNode = daughterDecayObject.motherNode()
590 
591  for mother in self.dataAccessor().motherRelations(object):
592  if mother in self._particlesDict.keys():
593  motherDecayObject = self._particlesDict[mother]
594  if not motherNode:
595  motherNode = motherDecayObject.daughterNode()
596  elif motherDecayObject.daughterNode() != motherNode and \
597  motherDecayObject.daughterNode().unite(motherNode):
598  # already found one mother
599  # need to make sure all of the other relevant nodes are united
600  self.removeObject(motherNode)
601  motherNode = motherDecayObject.daughterNode()
602  if not motherNode:
603  motherNode = QPoint(pos)
604  if not daughterNode:
605  daughterNode = QPoint(pos.x() + DecayLine.DEFAULT_LENGTH, pos.y())
606 
607  # create DecayLine
608  newDecayLine = DecayLine(self, motherNode, daughterNode)
609  newDecayLine.setObject(object)
610  if self.dataAccessor():
611  newDecayLine.setLabel(self.dataAccessor().label(object))
612  newDecayLine.setColor(self.dataAccessor().color(object))
613  newDecayLine.setLineStyle(self.dataAccessor().lineStyle(object))
614  self._particlesDict[object] = newDecayLine
615 
616  self.appendObject(newDecayLine)
618  return newDecayLine
619 
620  def addDecayNode(self, pos):
621  newDecayNode = DecayNode(self, pos)
623  return self.appendObject(newDecayNode)
624 
625  def operationId(self):
626  return self.parent().operationId()
627 
628  def createDecayObjectsFromPxlObject(self, operationId):
629  """ Creates DecayObjects for all particles in the set pxl object.
630 
631  In addition this function is called on all child LineDecayContainers.
632  """
633  #logging.debug(self.__class__.__name__ +": createDecayObjectsFromPxlObject()")
634  if self._pxlObject and self.dataAccessor():
635  for childObject in self.applyFilter(self.dataAccessor().children(self._pxlObject)):
636  if operationId != self.operationId():
637  return False
638  if self.dataAccessor().isContainer(childObject):
639  self.createChildContainer(childObject)
640  else:
641  self.addDecayLine(childObject)
642 
643  for child in self.children():
644  #if not Application.NO_PROCESS_EVENTS:
645  # QCoreApplication.instance().processEvents()
646  if operationId != self.operationId():
647  return False
648  if isinstance(child, LineDecayContainer):
649  if not child.createDecayObjectsFromPxlObject(operationId):
650  return False
651  return True
652 
653  def decayObject(self, pxlObject):
654  """ Returns the DecayObject which represents the given pxlObject or None if there is no such one.
655 
656  This function is to slow for massive usage with many dataObjects as it loops over all dataObjects.
657  """
658  for decayObject in self.dataObjects():
659  if decayObject.object() == pxlObject:
660  return decayObject
661  return None
662 
663  def select(self, decayObject):
664  if type(decayObject) == type(True):
665  WidgetContainer.select(self, decayObject)
666  elif not decayObject in self._selectedList:
667  #if type(decayObject) != type(True) and not decayObject in self._selectedList:
668  try:
669  # widgetSelected() usually expects VispaWidget as argument
670  # needed to prevent multiple selects in different containers
671  # in case of problems, replace decayObect by None and emit selected signal (see below)
672  self.parent().widgetSelected(decayObject)
673 
674  if decayObject in self.dataObjects():
675  self.dataObjects().remove(decayObject)
676  self.dataObjects().insert(0, decayObject)
677  self._selectedList.append(decayObject)
678  self.update(decayObject.boundingRect())
679  except ValueError:
680  logging.error(self.__class__.__name__ +": select() - Tried to remove non data object from data objects list. This is not supposed to happen. Check it.")
681 
682  # no need to emit selected signal as long as
683  # widgetSelected(decayObject) works (see above)
684  #self.emit(SIGNAL("selected"), decayObject.object())
685 
687  for object in self._selectedList:
688  self.update(object.boundingRect())
689  self._selectedList = []
690 
691  def objectMoved(self, decayObject, oldBoundingRect=None):
692  boundingRect = decayObject.boundingRect()
693  if oldBoundingRect:
694  self.update(boundingRect.unite(oldBoundingRect))
695  else:
696  self.update(boundingRect)
697 
698  # update visiblity list
699  objectIsVisible = False
700  if self.visibleRegion().intersects(boundingRect):
701  objectIsVisible = True
702  if decayObject in self._visibleList:
703  if not objectIsVisible:
704  self._visibleList.remove(decayObject)
705  elif objectIsVisible:
706  self._visibleList.append(decayObject)
707 
708  def scheduleUpdateVisibleList(self, update=True):
709  self._updateVisibleListFlag = update
710  for child in self.children():
711  if isinstance(child, LineDecayContainer):
712  child.scheduleUpdateVisibleList(update)
713 
714  def updateVisibleList(self, force=False):
715  if not self._updateVisibleListFlag and not force:
716  return
717 
718  #logging.debug("%s: updateVisibleList()" % self.__class__.__name__)
719 
720  region = self.visibleRegion()
721  self._visibleList = []
722  for decayObject in reversed(self.dataObjects()):
723  #for decayObject in oldVisibleList + self.dataObjects():
724  if region.intersects(decayObject.boundingRect()):
725  self._visibleList.append(decayObject)
726 
727  self._updateVisibleListFlag = False
728 
729  def showEvent(self, event):
731 
732  def paint(self, painter):
733  WidgetContainer.paint(self, painter)
734 
735  if self.collapsed():
736  # don't paint if container is collapsed
737  return
738 
739  generalPaintMode = 0x0
740  if self.noDecorationsMode():
741  generalPaintMode = DecayObject.PAINT_MODE_NO_DECORATIONS
742 
743  #if self.dataObjectsCount() > 50:
744  # painter.setRenderHint(QPainter.Antialiasing, False)
745 
746  self.updateVisibleList()
747  decayNodes = []
748  painterClipRegion = self.visibleRegion()
749  for decayObject in reversed(self._visibleList):
750  if isinstance(decayObject, DecayLine):
751  self.paintDecayObject(painter, decayObject, generalPaintMode)
752  if isinstance(decayObject, DecayNode):
753  # paint nodes after lines, so that they appear above the lines
754  decayNodes.append(decayObject)
755 
756  for decayObject in decayNodes:
757  self.paintDecayObject(painter, decayObject, generalPaintMode)
758 
759  def paintDecayObject(self, painter, decayObject, generalPaintMode):
760  paintMode = generalPaintMode
761  if decayObject in self._selectedList:
762  paintMode |= DecayObject.PAINT_MODE_SELECTED
763  if decayObject == self._hoveredObject:
764  paintMode |= DecayObject.PAINT_MODE_HOVERED
765  decayObject.paint(painter, paintMode)
766 
767  def mousePressEvent(self, event):
768  if self.isTitlePoint(event.pos()):
769  WidgetContainer.mousePressEvent(self, event)
770  self._titleClicked = True
771  return
772  self.select(False)
773  self._titleClicked = False
774  self.deselectAllWidgets()
775 
776  toSelectObject = None
777  for object in self.dataObjects():
778  if object.containsPoint(event.pos()):
779  if isinstance(object, DecayNode):
780  # prefere nodes over other DecayObjects (especially DecayLines)
781  toSelectObject = object
782  break
783  elif not toSelectObject:
784  toSelectObject = object
785  if toSelectObject:
786  toSelectObject.select(event.pos())
787  self.select(toSelectObject)
788  return # select 1 object at most
789  WidgetContainer.mousePressEvent(self, event)
790 
791  def mouseMoveEvent(self, event):
792  if self._titleClicked:
793  if self._editable:
794  WidgetContainer.mouseMoveEvent(self, event)
795  return
796  elif self.menu():
797  if self.isTitlePoint(event.pos()):
798  self.positionizeMenuWidget()
799  self.showMenu()
800  else:
801  self.menu().hide()
802 
803  if not bool(event.buttons()):
804  # no button pressed -> hovering
805  to_hover_object = None
806  if not self._visibleList:
807  self._visibleList = self.dataObjects()
808 
809  for object in self._visibleList:
810  if object.containsPoint(event.pos()):
811  if isinstance(object, DecayNode):
812  # prefere nodes over other DecayObjects (especially DecayLines)
813  to_hover_object = object
814  break
815  elif not to_hover_object:
816  to_hover_object = object
817  if to_hover_object:
818  if to_hover_object != self._hoveredObject:
819  previously_hovered_object = self._hoveredObject
820  self._hoveredObject = to_hover_object
821  self.update(self._hoveredObject.boundingRect())
822  if previously_hovered_object:
823  # make sure hovered mode is removed if hovered object changed
824  self.update(previously_hovered_object.boundingRect())
825  elif self._hoveredObject:
826  self.update(self._hoveredObject.boundingRect())
827  self._hoveredObject = None
828 
829  elif len(self._selectedList) > 0 and event.buttons() & Qt.LeftButton and self._editable:
830  # selection
831  self._selectedList[0].move(event.pos())
832 
833  def mouseReleaseEvent(self, event):
834  """ Join nodes if they belong to objects with relations.
835  """
836  if len(self._selectedList) > 0 and isinstance(self._selectedList[0], DecayNode):
837  # unite DecayNodes
838  selectedObject = self._selectedList[0]
839  dataObjects = self.dataObjects()[:]
840  for obj in dataObjects:
841  if obj != self._selectedList[0] and isinstance(obj, DecayNode) and obj.containsPoint(event.pos()):
842  hasRelations=self.dataAccessor().hasRelations(self.object())
843  for decayLine in self._selectedList[0].dataObjects()+obj.dataObjects():
844  hasRelations=hasRelations and self.dataAccessor().hasRelations(decayLine.object())
845  if not hasRelations:
846  continue
847  # selectedObject.unite(obj) # this may lead to image errors, so do it the other way
848  obj.unite(self._selectedList[0])
849  self.removeObject(selectedObject)
850  self._selectedList.append(obj)
851  if self.tabController():
852  self.tabController().setModified()
853 
854 
855  def keyPressEvent(self, event):
856  """ Calls delete() method if backspace or delete key is pressed when widget has focus.
857  """
858  if (event.key() == Qt.Key_Backspace or event.key() == Qt.Key_Delete) and self._editable:
859  controller=self.tabController()
860  if controller:
861  controller.tab().propertyView().clear()
862 
863  if self.isSelected():
864  self.delete()
865  elif len(self._selectedList) > 0:
866  self.removeObject(self._selectedList[0])
867 
868  if controller:
869  controller.setModified()
870  if hasattr(controller,"updateTreeView"):
871  controller.updateTreeView()
872  #controller.autolayout()
873 
874  def delete(self):
875  parent = self.parent()
876  if WidgetContainer.delete(self) and hasattr(parent, "object"):
877  parent.object().removeObject(self.object())
878  self._pxlObject = None
879 
880  def removeObject(self, decayObject):
881  if not decayObject in self.dataObjects():
882  return
883  self.update(decayObject.boundingRect())
884  if decayObject.delete():
885  if decayObject in self._selectedList:
886  self._selectedList.remove(decayObject)
887  if decayObject.object():
888  if self.dataAccessor():
889  id = self.dataAccessor().id(decayObject.object())
890  if id in self._existingObjectIds:
891  self._existingObjectIds.remove(id)
892  self._particlesDict.pop(decayObject.object(), None)
893  self._pxlObject.removeObject(decayObject.object())
894  ObjectHolder.removeObject(self, decayObject)
896  #self.tabController().updateContent() # not here, can create infinite loop
897 
898  def autolayout(self):
899  logging.debug(self.__class__.__name__ +": autolayout() - %s" % str(self.title()))
900  if self._threadChain.isRunning():
901  logging.info(self.__class__.__name__ +": autolayout() - Autolayout thread is already running. Aborting...")
902  return
903 
905  try:
906  self._nodeVector = NodeVector()
907  self._allNodes = {}
908  for object in self.dataObjects():
909  if isinstance(object, DecayLine):
910  # make sure both nodes of particle are stored in self._nodeVector
911  # and set relations between these nodes
912  if not object.motherNode() in self._allNodes.keys():
913  # create new node
914  motherNode = Node()
915  motherNode.position = Vector2(0, 0)
916  motherNode.isVertex = False
917  self._allNodes[object.motherNode()] = motherNode
918  newMother = True
919  else:
920  # use same node again, if it was already created for another particle
921  motherNode = self._allNodes[object.motherNode()]
922  newMother = False
923  if not object.daughterNode() in self._allNodes.keys():
924  daughterNode = Node()
925  daughterNode.position = Vector2(0, 0)
926  daughterNode.isVertex = False
927  self._allNodes[object.daughterNode()] = daughterNode
928  newDaughter = True
929  else:
930  daughterNode = self._allNodes[object.daughterNode()]
931  newDaughter = False
932 
933  # the important step: set mother and daughter relations
934  # between both nodes of _one_ particle
935  motherNode.children.append(daughterNode)
936  daughterNode.mothers.append(motherNode)
937 
938  # only append nodes if they were newly created
939  if newMother:
940  self._nodeVector.append(motherNode)
941  if newDaughter:
942  self._nodeVector.append(daughterNode)
943 
944  nodeVectorSize = self._nodeVector.size()
945  if nodeVectorSize > 1:
946  adhoc = nodeVectorSize > 40
947  autolayouter = AutoLayout()
948  #logging.debug(self.__class__.__name__ +": calling pxl::AutoLayout.init() with "+str(self._nodeVector.size())+" Particles")
949  autolayouter.init(self._nodeVector)
950  logging.debug(self.__class__.__name__ +": calling pxl::AutoLayout.layout(%s) with %d Particles" % (str(adhoc), nodeVectorSize))
951  if adhoc:
952  autolayouter.layout(False)
953  else:
954  autolayouter.layout(True)
955  self.autolayoutThreadFinished(None)
956  except Exception:
957  logging.error(__name__ + ": Pxl Autolayout not found: " + exception_traceback())
958  return
959 
960  for child in self.children():
961  if isinstance(child, LineDecayContainer):
962  self._autolayoutingChildren.append(child)
963  child.autolayout()
964 
965  def autolayoutThreadFinished(self, result):
966  logging.debug(self.__class__.__name__ +" autolayoutThreadFinished() - %s" % str(self.title()))
967  if self._threadChain.isRunning():
968  logging.info(self.__class__.__name__ +": autolayoutThreadFinished() - Thread is still running. Aborting...")
969  return
970  minOrphanY = 0
971  maxNonOrphanY = 0
972  firstMinOrphanY = True
973  firstMaxNonOrphanY = True
974  minY = 0
975  maxY = 0
976  for i in range(len(self._nodeVector)):
977  if i == 0 or self._nodeVector[i].position.y < minY:
978  minY = self._nodeVector[i].position.y
979  if i == 0 or self._nodeVector[i].position.y > maxY:
980  maxY = self._nodeVector[i].position.y
981 
982  if (len(self._nodeVector[i].mothers) == 1 and len(self._nodeVector[i].children) == 0) or (len(self._nodeVector[i].mothers) == 0 and len(self._nodeVector[i].children) == 1):
983  # orphan particles:
984  # nodes have exactly one relation
985  # eighter node is mother or daughter (left and right side of particle)
986  if firstMinOrphanY or self._nodeVector[i].position.y > minOrphanY:
987  minOrphanY = self._nodeVector[i].position.y
988  firstMinOrphanY = False
989  else:
990  # non orphans
991  if firstMaxNonOrphanY or self._nodeVector[i].position.y < maxNonOrphanY:
992  maxNonOrphanY = self._nodeVector[i].position.y
993  firstMaxNonOrphanY = False
994 
995  xOffset = -30
996  yOffset = -minY + (self.getDistance("titleFieldBottom") + 4* self.getDistance("topMargin")) / self.zoomFactor()
997 
998  for decayNode in self.dataObjects():
999  if isinstance(decayNode, DecayNode) and decayNode in self._allNodes.keys():
1000  decayNode.setPosition(QPoint(self._allNodes[decayNode].position.x + xOffset, self._allNodes[decayNode].position.y + yOffset))
1001 
1002  # arrange potential sub-views under decay tree
1003  self._subWidgetStartY = (maxY - minY) + 1.0* (self.getDistance("titleFieldBottom") + self.getDistance("topMargin") + self.getDistance("topMargin")) / self.zoomFactor()
1004  if self.dataObjectsCount() > 0:
1005  # add some pixels for last particle
1006  self._subWidgetStartY += 20
1007 
1008  if len(self._autolayoutingChildren) == 0:
1009  self.autolayoutPostprocess()
1010 
1011  def contentStartY(self):
1012  return self._subWidgetStartY * self.zoomFactor()
1013 
1015  logging.debug(self.__class__.__name__ +": childFinishedAutolayouting() - %s" % str(self.title()))
1016  if self.sender() and isinstance(self.sender(), LineDecayContainer):
1017  child = self.sender()
1018  if child in self._autolayoutingChildren:
1019  self._autolayoutingChildren.remove(child)
1020 
1021  if len(self._autolayoutingChildren) == 0 and not self._threadChain.isRunning():
1022  # wait until all children are done
1023  self.autolayoutPostprocess()
1024 
1026  if not self.autolayoutChildrenEnabled():
1027  # autosize() calls autolayoutChildren() anyway
1028  # but: even if in edit-mode and user shall be able to freely move event views
1029  # on manually calling autolayout children should be autolayouted as well
1030  # this function positionizes children, it does not call pxl autolayouter on children
1031  self.autolayoutChildren()
1032  self.autosize()
1033  self.emit(SIGNAL("finishedAutolayouting"))
1034 
1035 
1037  CONTAINS_AREA_SIZE = 4
1038  PAINT_MODE_SELECTED = 0x1
1039  PAINT_MODE_HOVERED = 0x2
1040  PAINT_MODE_NO_DECORATIONS = 0x4
1041 
1042  def __init__(self, parent=None):
1043  Zoomable.__init__(self)
1044  self._parent = parent
1045  if isinstance(self._parent, Zoomable):
1046  self.setZoom(self._parent.zoom())
1047 
1048  def parent(self):
1049  return self._parent
1050 
1051  def paint(self, painter, paintMode=0x0):
1052  raise NotImplementedError
1053 
1054  def boundingRect(self):
1055  raise NotImplementedError
1056 
1057  def containsPoint(self, pos):
1058  raise NotImplementedError
1059 
1060  def select(self, pos=None, selected=True):
1061  raise NotImplementedError
1062 
1063  def containsAreaSquareRect(self, position):
1064  return QRect( (position - QPoint(self.CONTAINS_AREA_SIZE, self.CONTAINS_AREA_SIZE)*0.5 ) * self.zoomFactor(), QSize(self.CONTAINS_AREA_SIZE, self.CONTAINS_AREA_SIZE) * self.zoomFactor() + QSize(1, 1))
1065 
1066  def move(self, pos):
1067  raise NotImplementedError
1068 
1069  def delete(self):
1070  pass
1071 
1072  def object(self):
1073  return None
1074 
1075 
1077  CONTAINS_AREA_SIZE = 8
1078  TYPE_MOTHER = 0
1079  TYPE_DAUGHTER = 1
1080 
1081  def __init__(self, parent, position):
1082  DecayObject.__init__(self, parent)
1083  ObjectHolder.__init__(self)
1084  self.setExclusiveMode(True)
1085  self._position = QPoint(position) # copy
1086  self._dragMouseRel = QPoint(0, 0)
1087 
1088  def delete(self):
1089  if self.parent().dataAccessor():
1090  for decayObject in self.dataObjects():
1091  for decayObject2 in self.dataObjects():
1092  if decayObject2.object() in self.parent().dataAccessor().motherRelations(decayObject.object()):
1093  decayObject.object().unlinkMother(decayObject2.object())
1094  if decayObject2.object() in self.parent().dataAccessor().daughterRelations(decayObject.object()):
1095  decayObject.object().unlinkDaughter(decayObject2.object())
1096  return True
1097 
1098  def position(self, zoomed=False):
1099  if zoomed:
1100  return QPointF(self._position.x() * self.zoomFactor(), self._position.y() * self.zoomFactor())
1101  return self._position
1102 
1103  def setPosition(self, pos):
1104  self._position = pos
1105 
1106  def x(self):
1107  return self._position.x()
1108 
1109  def y(self):
1110  return self._position.y()
1111 
1112  def paint(self, painter, paintMode=0x0):
1113  if paintMode & DecayObject.PAINT_MODE_SELECTED:
1114  penColor = QColor(Qt.blue)
1115  #elif paintMode & DecayObject.PAINT_MODE_HOVERED:
1116  # penColor = QColor(Qt.green)
1117  else:
1118  penColor = QColor(Qt.blue).lighter(140)
1119  if paintMode & DecayObject.PAINT_MODE_HOVERED:
1120  penColor = penColor.lighter(120)
1121 
1122  painter.setPen(QPen(penColor, 1 * self.zoomFactor(), Qt.SolidLine))
1123  painter.setBrush(penColor)
1124  #if paintMode & DecayObject.PAINT_MODE_HOVERED or paintMode & DecayObject.PAINT_MODE_SELECTED:
1125  painter.drawEllipse(self._position * self.zoomFactor(), self.CONTAINS_AREA_SIZE * 0.4 * self.zoomFactor(), self.CONTAINS_AREA_SIZE * 0.4 * self.zoomFactor())
1126  #painter.drawRect(self.boundingRect())
1127 
1128  def boundingRect(self):
1129  return self.containsAreaSquareRect(self._position)
1130 
1131  def containsPoint(self, pos):
1132  return self.boundingRect().contains(pos)
1133 
1134  def select(self, pos=None, selected=True):
1135  if pos:
1136  self._dragMouseRel = self._position - pos / self.zoomFactor()
1137 
1138  def move(self, *arg):
1139 
1140  if len(arg) == 1:
1141  pos = arg[0]
1142  if len(arg) > 1:
1143  pos = QPoint(arg[0], arg[1])
1144 
1145  if self.parent():
1146  oldBoundingRect = self.boundingRect()
1147  oldBoundingRects = {}
1148  for object in self.dataObjects():
1149  oldBoundingRects[object] = object.boundingRect()
1150 
1151  self._position = pos / self.zoomFactor() + self._dragMouseRel
1152 
1153  if self.parent():
1154  for object in self.dataObjects():
1155  self.parent().objectMoved(object, oldBoundingRects[object])
1156  self.parent().objectMoved(self, oldBoundingRect)
1157 
1158  def unite(self, node):
1159  #logging.debug(self.__class__.__name__ +": unite()")
1160  if node == self:
1161  return False
1162 
1163  useDataAccessor = False
1164  if self.parent().dataAccessor():
1165  useDataAccessor = True
1166 
1167  oldDecayObjects = self.dataObjects()[:]
1168  newDecayObjects = node.dataObjects()[:]
1169  for newDecayObject in newDecayObjects:
1170  self.appendObject(newDecayObject)
1171  self.parent().update(newDecayObject.boundingRect()) # old bounding rect
1172  nodeType = newDecayObject.replaceNode(node, self)
1173  self.parent().update(newDecayObject.boundingRect()) # new bounding rect
1174  if useDataAccessor:
1175  for oldDecayObject in oldDecayObjects:
1176  if nodeType == DecayNode.TYPE_MOTHER and oldDecayObject.nodeType(self) == DecayNode.TYPE_DAUGHTER:
1177  #newDecayObject.object().linkMother(oldDecayObject.object())
1178  self.parent().dataAccessor().linkMother(newDecayObject.object(), oldDecayObject.object())
1179  if nodeType == DecayNode.TYPE_DAUGHTER and oldDecayObject.nodeType(self) == DecayNode.TYPE_MOTHER:
1180  #newDecayObject.object().linkDaughter(oldDecayObject.object())
1181  self.parent().dataAccessor().linkDaughter(newDecayObject.object(), oldDecayObject.object())
1182  return True
1183 
1184 
1186 
1187  # new properties
1188  LINE_WIDTH = 2
1189  DEFAULT_LENGTH = 70
1190  LABEL_OFFSET = 7
1191 
1192  ARROW_LENGTH = 14 # length of two small arrow lines (painted when selected or hovered)
1193  ARROW_WIDTH = 8 # vertical distance of two arrow lines from normal line
1194 
1195  HUNDREDEIGHTY_OVER_PI = 180 / math.pi
1196 
1197  def __init__(self, parent, startPointOrNode, endPointOrNode):
1198  self._color = QColor(176, 179, 177)
1199  self._lineStyle = Qt.SolidLine
1200  self._label = None
1201  self._labelFont = None
1202  self._showLabel = True
1203  self._labelMatrix = None
1206  self._boundingRect = None
1208  self._forwardDirection = True
1210  self._transform = None
1211  self._pxlObject = None
1212 
1213  DecayObject.__init__(self, parent)
1214 
1215  if isinstance(parent, LineDecayContainer):
1216  self._selfContained = False
1217  if isinstance(startPointOrNode, QPoint):
1218  # startPoint
1219  self._startNode = parent.addDecayNode(startPointOrNode)
1220  else:
1221  # startNode
1222  self._startNode = startPointOrNode
1223  if isinstance(endPointOrNode, QPoint):
1224  # endPoint
1225  self._endNode = parent.addDecayNode(endPointOrNode)
1226  else:
1227  # endNode
1228  self._endNode = endPointOrNode
1229  else:
1230  # make it possible to use DecayLine outside LineDecayContainer
1231  self._selfContained = True
1232  self._startNode = DecayNode(parent, startPointOrNode)
1233  self._endNode = DecayNode(parent, endPointOrNode)
1234 
1235  self._startNode.appendObject(self)
1236  self._endNode.appendObject(self)
1237 
1238  def setZoom(self, zoom):
1239  DecayObject.setZoom(self, zoom)
1240  if self._labelFont:
1241  self._labelFont.setPointSize(12 * self.zoomFactor())
1242 
1243  def delete(self):
1244  self._startNode.removeObject(self)
1245  self._endNode.removeObject(self)
1246  self.parent().removeObject(self._startNode)
1247  self.parent().removeObject(self._endNode)
1248 
1249  # remove this DecayObject's pxl particle from parent's pxl event / eventview
1250  self.parent().object().removeObject(self.object())
1251  return True
1252 
1253  def motherNode(self):
1254  return self._startNode
1255 
1256  def daughterNode(self):
1257  return self._endNode
1258 
1259  def setObject(self, object):
1260  self._pxlObject = object
1261  self._recalculateBoundingRect = True
1262 
1263  def object(self):
1264  return self._pxlObject
1265 
1266  def qtLineStyle(self):
1267  if not self.parent().dataAccessor():
1268  return Qt.SolidLine
1269  if self._lineStyle == self.parent().dataAccessor().LINE_STYLE_DASH:
1270  return Qt.DashLine
1271  elif self._lineStyle == self.parent().dataAccessor().LINE_STYLE_SOLID:
1272  return Qt.SolidLine
1273  return None
1274 
1275  def setLineStyle(self, style):
1276  self._lineStyle = style
1277  self._recalculateBoundingRect = True
1278 
1279  def setColor(self, color):
1280  self._color = color
1281  self._recalculateBoundingRect = True
1282 
1283  def setLabel(self, label):
1284  self._label = label
1285  if not self._labelFont:
1286  self._labelFont = QFont()
1287  self._labelFont.setPointSize(12 * self.zoomFactor())
1288 
1289  def setShowLabel(self, show):
1290  self._showLabel = show
1291 
1292  def nodeType(self, node):
1293  if self._startNode == node:
1294  return DecayNode.TYPE_MOTHER
1295  if self._endNode == node:
1296  return DecayNode.TYPE_DAUGHTER
1297  return None
1298 
1299  def replaceNode(self, oldNode, newNode):
1300  if self._startNode == oldNode:
1301  self._startNode.removeObject(self)
1302  self._startNode = newNode
1303  self._startNode.appendObject(self)
1304  if self._endNode == oldNode:
1305  self._endNode.removeObject(self)
1306  self._endNode = newNode
1307  self._endNode.appendObject(self)
1308  self._recalculateBoundingRect = True
1309  return self.nodeType(newNode)
1310 
1311  def lineWidth(self):
1312  return self.LINE_WIDTH * self.zoomFactor()
1313 
1314  def dataAccessor(self):
1315  if self.parent():
1316  return self.parent().dataAccessor()
1317  return None
1318 
1319  def extendedSize(self):
1320  """ Returns True if instead of simple line a spiral or a sinus function is plotted.
1321  """
1322  if not self.parent().dataAccessor():
1323  return False
1324  return self._lineStyle == self.parent().dataAccessor().LINE_STYLE_SPIRAL or self._lineStyle == self.parent().dataAccessor().LINE_STYLE_WAVE or self._lineStyle == self.parent().dataAccessor().LINE_VERTEX
1325 
1326  def transform(self):
1327  """ Returns QTransform that sets the origin to the start point and rotates by the slope angle.
1328 
1329  Used to change coordinates of painter in paint().
1330  """
1331 
1332  if not self._recalculateTransform and self._transform:
1333  return self._transform
1334 
1335  z = self.zoomFactor()
1336  if self._startNode.x() < self._endNode.x():
1337  self._forwardDirection = True
1338  xNull = self._startNode.x() * z
1339  yNull = self._startNode.y() * z
1340  else:
1341  self._forwardDirection = False
1342  xNull = self._endNode.x() * z
1343  yNull = self._endNode.y() * z
1344 
1345  slope = self.slope()
1346  angle = math.atan(slope)
1347  angleDegree = angle * self.HUNDREDEIGHTY_OVER_PI
1348 
1349  self._transform = QTransform()
1350  self._transform.translate(xNull, yNull) # rotate around start point
1351  self._transform.rotate(angleDegree)
1352  return self._transform
1353 
1354  def paint(self, painter, paintMode=0x0):
1355  if paintMode & DecayObject.PAINT_MODE_SELECTED:
1356  penColor = QColor(Qt.blue)
1357  else:
1358  penColor = self._color
1359  if paintMode & DecayObject.PAINT_MODE_HOVERED:
1360  penColor = penColor.lighter(80)
1361 
1362  showDirectionArrow = paintMode & DecayObject.PAINT_MODE_HOVERED or paintMode & DecayObject.PAINT_MODE_SELECTED
1363  extendedSize = self.extendedSize()
1364 
1365  z = self.zoomFactor()
1366  l = self.length(zoomed = True)
1367 
1368  # transform coordinates to make following calculations easier
1369  painter.setTransform(self.transform())
1370 
1371  if extendedSize:
1372  painter.setPen(QPen(penColor, 0.5*self.lineWidth(), Qt.SolidLine))
1373  # spiral or wave line
1374  ## l = (n + 1/2) * r * 2 * math.pi
1375  designRadius = 1.2 * z
1376  # n: number of spirals
1377  n = max(int(1.0 * l / (2 * math.pi * designRadius)), 4)
1378  # r: radius of cycloide wheel
1379  r = 1.0 * l / (2 * math.pi * (n + 0.5))
1380  # a: cycloide is trace of point with radius a
1381  a = 3.5 * r
1382 
1383  if self.parent().dataAccessor() and self._lineStyle == self.parent().dataAccessor().LINE_STYLE_SPIRAL:
1384  path = QPainterPath()
1385  # draw spiral using a cycloide
1386  # coordinates of center of wheel
1387  xM = a
1388  yM = 0
1389  while xM < l:
1390  phase = 1.0 * (xM-a)/r
1391  x = xM - a * math.cos(phase)
1392  y = yM - a * math.sin(phase)
1393  if x > l:
1394  x = l
1395  #path.lineTo(QPointF(x, y))
1396  break
1397  path.lineTo(QPointF(x, y))
1398  xM += 0.2 * r / z
1399  painter.drawPath(path)
1400  elif self.parent().dataAccessor() and self._lineStyle == self.parent().dataAccessor().LINE_STYLE_WAVE:
1401  path = QPainterPath()
1402  x = a
1403  while x < l - 0.5*a:
1404  y = a * math.cos(x/r)
1405  path.lineTo(QPointF(x, y))
1406  x += 0.2 * r / z
1407  painter.drawPath(path)
1408  elif self.parent().dataAccessor() and self._lineStyle == self.parent().dataAccessor().LINE_VERTEX:
1409  painter.setBrush(QBrush(penColor, Qt.SolidPattern))
1410  painter.drawEllipse(QPointF(l/2.,0.),l/2.,a)
1411 
1412  else:
1413  painter.setPen(QPen(penColor, self.lineWidth(), self.qtLineStyle()))
1414  painter.drawLine(QPoint(0,0), QPoint(l, 0))
1415 
1416  if not paintMode & DecayObject.PAINT_MODE_NO_DECORATIONS:
1417  self.drawText(painter, paintMode)
1418 
1419  # paint arrow lines
1420  if showDirectionArrow:
1421  painter.setPen(QPen(penColor, 0.8*self.lineWidth(), Qt.SolidLine, Qt.RoundCap))
1422  self.drawArrow(painter, paintMode)
1423 
1424  ## DEBUG
1425  #painter.setPen(QPen(penColor, 1, Qt.SolidLine))
1426  #painter.drawRect(self.labelBoundingRect())
1427  #painter.drawRect(self.arrowBoundingRect())
1428 
1429  painter.resetTransform()
1430  #painter.drawRect(self.boundingRect())
1431 
1432  if self._selfContained:
1433  self._startNode.paint(painter, paintMode)
1434  self._endNode.paint(painter, paintMode)
1435 
1436  # simple paint method for debugging
1437 # def paint(self, painter, paintMode=0x0):
1438 # if paintMode & DecayObject.PAINT_MODE_SELECTED:
1439 # penColor = QColor(Qt.blue)
1440 # else:
1441 # penColor = self._color
1442 # if paintMode & DecayObject.PAINT_MODE_HOVERED:
1443 # penColor = penColor.lighter(150)
1444 #
1445 # painter.setPen(QPen(penColor, self.lineWidth(), Qt.SolidLine))
1446 # painter.drawLine(self._startNode.position() * self.zoomFactor(), self._endNode.position() * self.zoomFactor())
1447 #
1448 # if not paintMode & DecayObject.PAINT_MODE_NO_DECORATIONS:
1449 # self.drawText(painter, paintMode)
1450 #
1451 # if self._selfContained:
1452 # self._startNode.paint(painter, paintMode)
1453 # self._endNode.paint(painter, paintMode)
1454 #
1455 # #painter.drawRect(self.boundingRect())
1456 
1457  def drawText(self, painter, paintMode=0x0):
1458  """ Draws self._label on given painter.
1459 
1460  Expects coordinates of painter transformed as returned by transform()
1461  """
1462  if not self._showLabel or not self._label:
1463  return
1464 
1465  if paintMode & DecayObject.PAINT_MODE_SELECTED:
1466  textColor = QColor(Qt.blue)
1467  elif paintMode & DecayObject.PAINT_MODE_HOVERED:
1468  textColor = QColor(Qt.gray)
1469  else:
1470  textColor = QColor(Qt.black)
1471 
1472  path = QPainterPath()
1473  path.addText(QPointF(self.labelBoundingRect().bottomLeft()), self._labelFont, self._label)
1474  painter.fillPath(path, textColor)
1475 
1476  def drawArrow(self, painter, paintMode=0x0):
1477  # make sure to stay within bounding rect, therefore add / substract arrowPixelOffset
1478  arrowPixelOffset = 0.7* self.lineWidth()
1479  arrowBoundingRect = self.arrowBoundingRect()
1480  arrowBoundingRectLeft = arrowBoundingRect.left() + arrowPixelOffset
1481  arrowBoundingRectRight = arrowBoundingRect.right() - arrowPixelOffset
1482  arrowBoundingRectTop = arrowBoundingRect.top() + arrowPixelOffset
1483  arrowBoundingRectBottom = arrowBoundingRect.bottom() - arrowPixelOffset
1484  arrowBoundingRectVerticalCenter = arrowBoundingRect.center().y()
1485  if self._forwardDirection:
1486  painter.drawLine(arrowBoundingRectLeft, arrowBoundingRectTop, arrowBoundingRectRight, arrowBoundingRectVerticalCenter)
1487  painter.drawLine(arrowBoundingRectLeft, arrowBoundingRectBottom, arrowBoundingRectRight, arrowBoundingRectVerticalCenter)
1488 
1489  else:
1490  painter.drawLine(arrowBoundingRectLeft, arrowBoundingRectVerticalCenter, arrowBoundingRectRight, arrowBoundingRectTop)
1491  painter.drawLine(arrowBoundingRectLeft, arrowBoundingRectVerticalCenter, arrowBoundingRectRight, arrowBoundingRectBottom)
1492 
1493 
1494  def labelBoundingRect(self, forceRecalculation=False):
1495  if not self._label or not self._labelFont:
1496  return QRect()
1497 
1498  if not self._labelBoundingRect or forceRecalculation:
1499  label_offset = self.LABEL_OFFSET
1500  if self.extendedSize():
1501  label_offset += 2
1502 
1503  fm = QFontMetrics(self._labelFont)
1504  self._labelBoundingRect = fm.boundingRect(self._label)
1505 
1506  labelWidth = self._labelBoundingRect.width()
1507  offset = QPointF(0.5 * (self.length(zoomed=True) - labelWidth), - label_offset * self.zoomFactor())
1508  self._labelBoundingRect.translate(offset.x(), offset.y())
1509 
1510  return self._labelBoundingRect
1511 
1512  def arrowBoundingRect(self, forceRecalculation=False):
1513  if not self._arrowBoundingRect or forceRecalculation:
1514  zoomFactor = self.zoomFactor()
1515  l = self.length(zoomed = True)
1516  arrowLength = self.ARROW_LENGTH * zoomFactor
1517  arrowWidth = self.ARROW_WIDTH * zoomFactor
1518  horizontalOffset = self.CONTAINS_AREA_SIZE * 0.4 * self.zoomFactor() # offset from end of line
1519  if self._forwardDirection:
1520  self._arrowBoundingRect = QRect(l-arrowLength - horizontalOffset, -arrowWidth, arrowLength, 2*arrowWidth)
1521  else:
1522  self._arrowBoundingRect = QRect(horizontalOffset, -arrowWidth, arrowLength, 2*arrowWidth)
1523 
1524  return self._arrowBoundingRect
1525 
1526  def boundingRect(self):
1527  if not self._recalculateBoundingRect and self._boundingRect:
1528  return self._boundingRect
1529 
1530  self._recalculateTransform = True
1531  contains_area_size = self.CONTAINS_AREA_SIZE
1532  if self.extendedSize():
1533  contains_area_size += 4
1534 
1535  zoomFactor = self.zoomFactor()
1536  offset = contains_area_size * zoomFactor
1537  startPoint = self._startNode.position() * zoomFactor
1538  endPoint = self._endNode.position() * zoomFactor
1539 
1540  topLeft = QPoint(min(startPoint.x(), endPoint.x()) - offset, min(startPoint.y(), endPoint.y()) - offset)
1541  bottomRight = QPoint(max(startPoint.x(), endPoint.x()) + offset, max(startPoint.y(), endPoint.y()) + offset)
1542 
1543  rect = QRect(topLeft, bottomRight)
1544 
1545  # increase rect for label and arrow (shows when selected or hovered
1546  rect = rect.united(self.transform().mapRect(self.arrowBoundingRect(True)))
1547  if self._label and self._labelFont:
1548  rect = rect.united(self.transform().mapRect(self.labelBoundingRect(True)))
1549 
1550  self._boundingRect = rect
1551  return self._boundingRect
1552 
1553  def slope(self):
1554  deltaX = self._endNode.x() - self._startNode.x()
1555  if deltaX == 0:
1556  return sys.maxsize
1557 
1558  return 1.0 * (self._endNode.y() - self._startNode.y()) / deltaX
1559 
1560  def length(self, zoomed=False):
1561  l = math.sqrt((self._endNode.x() - self._startNode.x())**2 + (self._endNode.y() - self._startNode.y())**2)
1562  if zoomed:
1563  return self.zoomFactor() * l
1564  return l
1565 
1566  def containsPoint(self, pos):
1567  pos = pos / self.zoomFactor()
1568 
1569  # label
1570  if self._label and self._labelFont and self.labelBoundingRect().contains(self.transform().inverted()[0].map(pos)):
1571  return True
1572 
1573  # line
1574  line_width = self.LINE_WIDTH + 1
1575  if self.extendedSize():
1576  line_width += 6
1577 
1578  if self._endNode.position().x() == self._startNode.position().x():
1579  # vertical
1580  if abs(pos.x() - self._endNode.position().x()) < line_width:
1581  return True
1582  return False
1583 
1584  if pos.x() < (min(self._startNode.position().x(), self._endNode.position().x()) - line_width) or pos.x() > (max(self._startNode.position().x(), self._endNode.position().x()) + line_width):
1585  return False
1586 
1587  slope = self.slope()
1588  deltaY = slope * (pos.x() - self._startNode.position().x()) + self._startNode.position().y() - pos.y()
1589  if abs(deltaY) < 0.5* line_width * max(1, abs(slope)):
1590  return True
1591  return False
1592 
1593  def select(self, pos=None, selected=True):
1594  if not pos:
1595  pos = (self._startNode.position() + self._endNode.position()) * 0.5
1596  self._startNode.select(pos)
1597  self._endNode.select(pos)
1598  self._recalculateBoundingRect = True
1599 
1600  def move(self, pos):
1601  self._startNode.move(pos)
1602  self._endNode.move(pos)
1603  self._recalculateBoundingRect = True
1604 
1606 
1607  # inherited
1608  MINIMUM_WIDTH = 30
1609  MINIMUM_HEIGHT = 0
1610 
1611  # new
1612  NONE = 0
1613  LEPTON = 1
1614  QUARK = 2
1615  BOSON = 3
1616  HIGGS = 4
1617 
1618  def __init__(self, parent, type, name, dragData=None):
1620  self._mimeDataType = None
1621  self._toolBoxContainer = None
1622  self._decayObect = None
1623  self._object = None
1624 
1625  VispaWidget.__init__(self, parent)
1626  self.enableAutosizing(True, False)
1627  self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1628  self.setSelectable(False)
1629  self.setDragable(False)
1630  self.setShape("ROUNDRECT")
1631  self.setText(name)
1632  self.textField().setPenColor(QColor("white"))
1633  self.show()
1634  if dragData:
1635  self._dragData = dragData
1636  else:
1637  self._dragData = name
1638 
1639  self.setMimeDataType(LineDecayView.DECAY_OBJECT_MIME_TYPE)
1640 
1641  if type == self.LEPTON:
1642  self.setColors(QColor(117, 57, 18), QColor(180, 88, 28), QColor(244, 119, 38))
1643  elif type == self.QUARK:
1644  self.setColors(QColor(19, 56, 0), QColor(27, 79, 27), QColor(57, 129, 51))
1645  elif type == self.BOSON:
1646  self.setColors(QColor(64, 0, 0), QColor(127, 0, 0), QColor(191, 0, 0))
1647  elif type == self.HIGGS:
1648  self.setColors(QColor(28, 63, 253), QColor(27, 118, 255), QColor(21, 169, 250))
1649 
1650  if hasattr(parent,"particleDoubleClicked"):
1651  self.connect(self,SIGNAL("mouseDoubleClicked"),parent.particleDoubleClicked)
1652  if hasattr(parent,"particleRightClicked"):
1653  self.connect(self,SIGNAL("mouseRightPressed"),parent.particleRightClicked)
1654 
1655  def setMimeDataType(self, type):
1656  self._mimeDataType = type
1657 
1658  def dragData(self):
1659  return self._dragData
1660 
1661  def mouseDoubleClickEvent(self, event):
1662  self.emit(SIGNAL("mouseDoubleClicked"), self)
1663 
1664  def mousePressEvent(self, event):
1665  if event.button() == Qt.LeftButton:
1666  self._dragStartPosition = QPoint(event.pos()) # copy, does not work without on SL 4.0
1667  if isinstance(self.parent(), WidgetView):
1668  self.parent().widgetSelected(self)
1669  if event.button()==Qt.RightButton:
1670  self.emit(SIGNAL("mouseRightPressed"), event.globalPos(), self)
1671 
1672  def mouseMoveEvent(self, event):
1673  if not (event.buttons() & Qt.LeftButton):
1674  return
1675  if (event.pos() - self._dragStartPosition).manhattanLength() < QApplication.startDragDistance():
1676  return
1677 
1678  drag = QDrag(self)
1679  mimeData = QMimeData()
1680  mimeData.setData(self._mimeDataType, self._dragData)
1681  drag.setMimeData(mimeData)
1682  drag.setPixmap(QPixmap.grabWidget(self))
1683  drag.setHotSpot(QPoint(drag.pixmap().width()/2, drag.pixmap().height()/2))
1684  drag.exec_()
1685 
1686  def setDecayObject(self, decayObject):
1687  """ Will be painted in content area.
1688  """
1689  self._decayObect = decayObject
1690 
1691  def setObject(self, object):
1692  """ The particle widget can optionally carry a real physics object, e.g. pxl particle.
1693 
1694  Required for example if widget's parent is a WidgetView that should react on clicks.
1695  """
1696  self._object = object
1697 
1698  def object(self):
1699  return self._object
1700 
1701  def paint(self, painter):
1702  VispaWidget.paint(self, painter)
1703  if self._decayObect:
1704  paintMode = 0
1705  if self.isSelected():
1706  paintMode |= DecayObject.PAINT_MODE_SELECTED
1707  self._decayObect.paint(painter, paintMode)
1708 
1709  def dataAccessor(self):
1710  """ Return None for decay object.
1711  """
1712  return None
1713 
1714 
bool contains(EventRange const &lh, EventID const &rh)
Definition: EventRange.cc:38
_lineStyle
l = (n + 1/2) * r * 2 * math.pi
void clear(CLHEP::HepGenMatrix &m)
Helper function: Reset all elements of a matrix to 0.
Definition: matutil.cc:167
Abs< T >::type abs(const T &t)
Definition: Abs.h:22
T min(T a, T b)
Definition: MathUtil.h:58
bool insert(Storage &iStorage, ItemType *iItem, const IdTag &iIdTag)
Definition: HCMethods.h:49
char data[epos_bytes_allocation]
Definition: EPOS_Wrapper.h:82
#define update(a, b)
edm::TrieNode< PDet > Node