CMS 3D CMS Logo

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