CMS 3D CMS Logo

BoxDecayView.py
Go to the documentation of this file.
1 import logging
2 
3 from PyQt4.QtCore import QCoreApplication,SIGNAL,Qt
4 from PyQt4.QtGui import QPalette,QMessageBox
5 
6 from Vispa.Main.Application import Application
7 from Vispa.Gui.ConnectableWidget import ConnectableWidget
8 from Vispa.Gui.WidgetContainer import WidgetContainer
9 from Vispa.Gui.VispaWidget import VispaWidget
10 from Vispa.Share.BasicDataAccessor import BasicDataAccessor
11 from Vispa.Share.BasicDataAccessor import BasicDataAccessorInterface
12 from Vispa.Share.RelativeDataAccessor import RelativeDataAccessor
13 from Vispa.Share.ParticleDataAccessor import ParticleDataAccessor
14 from Vispa.Gui.PortConnection import LinearPortConnection
15 from Vispa.Views.WidgetView import WidgetView
16 from Vispa.Main.Exceptions import exception_traceback
17 from Vispa.Share.ThreadChain import ThreadChain
18 
20  """Visualizes a decay tree using boxes to represent containers as well as their contents.
21 
22  Mother/daughter relations are represented by connection lines. The BoxDecayView is automatically filled using a DataAccessor.
23  """
24 
25  LABEL = "&Box Decay View"
26  UPDATE_EVERY = 20
27  NO_SORTING_ABOVE = 10000
28  WARNING_ABOVE = 500
29 
30  def __init__(self, parent=None):
31  logging.debug(__name__ + ": __init__")
32  WidgetView.__init__(self, parent)
33 
34  self._operationId = 0
38  self._leftMargin = ConnectableWidget.LEFT_MARGIN
39  self._topMargin = ConnectableWidget.TOP_MARGIN
41 
42  self.setPalette(QPalette(Qt.black, Qt.white))
43 
44  def setArrangeUsingRelations(self, set):
45  if self.WARNING_ABOVE>0:
46  if set:
47  self.WARNING_ABOVE=300
48  else:
49  self.WARNING_ABOVE=1000
51 
53  return self._arrangeUsingRelationsFlag
54 
55  def setSortBeforeArranging(self, set):
56  self._sortBeforeArranging = set
57 
60 
61  def setBoxContentScript(self, script):
62  self._boxContentScript = script
63 
64  def boxContentScript(self):
65  return self._boxContentScript
66 
67  def setDataAccessor(self, accessor):
68  """ Sets the DataAccessor from which the boxes are created.
69 
70  You need to call updateContent() in order to make the changes visible.
71  """
72  if not isinstance(accessor, BasicDataAccessor):
73  raise TypeError(__name__ + " requires data accessor of type BasicDataAccessor.")
74  if not isinstance(accessor, RelativeDataAccessor):
75  raise TypeError(__name__ + " requires data accessor of type RelativeDataAccessor.")
76  WidgetView.setDataAccessor(self, accessor)
77 
78  def cancel(self):
79  """ Stop all running operations.
80  """
81  self._operationId += 1
82 
84  numObjects=self.numberDataObjectChildren()
85  if self.WARNING_ABOVE>0 and numObjects>self.WARNING_ABOVE:
86  result=QCoreApplication.instance().showMessageBox("You are about to display more than "+str(numObjects)+" (>"+str(self.WARNING_ABOVE)+") objects. This may take some time. ",
87  "Would you like to continue?",
88  QMessageBox.Yes | QMessageBox.No,
89  QMessageBox.Yes, [("Yes (remember my decision)",QMessageBox.YesRole)])
90  if result == QMessageBox.No:
91  return False
92  if result == 0:
93  self.WARNING_ABOVE=0
94  return True
95 
96  def updateContent(self, overrideCheck=False):
97  """ Clear the BoxDecayView and refill it.
98  """
99  logging.debug(__name__ + ": updateContent")
100  self.cancel()
101  if self.dataAccessor() == None:
102  return False
103  self._updatingFlag+=1
104  self.clear()
105  if self.dataObject()==None:
106  self._updatingFlag-=1
107  return True
108  operationId = self._operationId
109  if not overrideCheck:
110  if not self.checkNumberOfObjects():
111  self._updatingFlag-=1
112  return False
113  objects = self.applyFilter(self.dataObjects())
114  if self._sortBeforeArranging and self.arrangeUsingRelations():
115  thread = ThreadChain(self._sortByRelations, objects)
116  while thread.isRunning():
117  if not Application.NO_PROCESS_EVENTS:
118  QCoreApplication.instance().processEvents()
119  objects=thread.returnValue()
120  if operationId != self._operationId:
121  self._updatingFlag -=1
122  return False
123  self.createBoxesRecursive(operationId, objects, self)
124  # arrange objects which are not within a container
125  BoxDecayContainer.autolayoutAlgorithm(self)
126  self._updatingFlag -=1
127  return operationId == self._operationId
128 
129  def createBox(self, widgetParent, container, title, text):
130  """ Create a WidgetContainer or ConnectableWidget and set its properties.
131  """
132  if container:
133  widget = BoxDecayContainer(widgetParent)
134  else:
135  widget = ConnectableWidget(widgetParent)
136  widget.noRearangeContent()
137  widget.setText(text)
138  widget.textField().setOutputFlags(Qt.AlignLeft)
139  widget.setShowPortNames(True)
140  widget.setDragable(False)
141  widget.setDeletable(False)
142  widget.enableAutosizing(True, False)
143 # widget.ROUNDRECT_RADIUS=0
144 # widget.setColors(Qt.black,Qt.white,Qt.white)
145  widget.setTitle(title)
146  return widget
147 
148  def createSourcePort(self, w, name, visible=True):
149  """ Create a source port and set its properties.
150  """
151  port = w.sourcePort(name)
152  if not port:
153  port = w.addSourcePort(name)
154  port.setDragable(False)
155  port.setSelectable(False)
156  if not visible:
157  port.HEIGHT = 0
158  port.show()
159  return port
160 
161  def createSinkPort(self, w, name, visible=True):
162  """ Create a sink port and set its properties.
163  """
164  port = w.sinkPort(name)
165  if not port:
166  port = w.addSinkPort(name)
167  port.setDragable(False)
168  port.setSelectable(False)
169  if not visible:
170  port.HEIGHT = 0
171  port.show()
172  return port
173 
174  def createConnection(self, w1, name1, w2, name2, color=None, portsVisible=True):
175  """ Create a connection widget between w1 and w2.
176  """
177  port1 = self.createSourcePort(w1, name1, portsVisible)
178  port2 = self.createSinkPort(w2, name2, portsVisible)
179  connection = LinearPortConnection(w1.parent(), port1, port2)
180  connection.setSelectable(False)
181  connection.setDeletable(False)
182 
183  if color:
184  connection.FILL_COLOR2 = color
185  return connection
186 
187  def createConnections(self, operationId, widgetParent):
188  """ Create connection lines between objects.
189 
190  In BoxDecayView default mother-daughter relations are vizualized by the connections.
191  """
192  for w1 in widgetParent.children():
193  # Process application event loop in order to accept user input during time consuming drawing operation
194  self._updateCounter+=1
195  if self._updateCounter>=self.UPDATE_EVERY:
196  self._updateCounter=0
197  if not Application.NO_PROCESS_EVENTS:
198  QCoreApplication.instance().processEvents()
199  # Abort drawing if operationId out of date
200  if operationId != self._operationId:
201  return None
202  if isinstance(w1, ConnectableWidget):
203  w1.setShowPortNames(False)
204  for daughter in self.dataAccessor().daughterRelations(w1.object):
205  if self.dataAccessor().isContainer(w1.object) or self.dataAccessor().isContainer(daughter):
206  continue
207  w2 = self.widgetByObject(daughter)
208  if w2:
209  connectionWidget = self.createConnection(w1, 'daughterRelations', w2, 'motherRelations', None, False)
210  connectionWidget.stackUnder(w2)
211  return True
212 
213  def createBoxesRecursive(self, operationId, objects, widgetParent, positionName="0"):
214  """ Creates a box from an object.
215 
216  All children of this object are created recursively.
217  """
218  #logging.debug(__name__ + ": createBoxesRecursive")
219  if self._sortBeforeArranging and self.arrangeUsingRelations():
220  thread = ThreadChain(self._sortByRelations, objects)
221  while thread.isRunning():
222  if not Application.NO_PROCESS_EVENTS:
223  QCoreApplication.instance().processEvents()
224  objects=thread.returnValue()
225 
226  i = 1
227  for object in objects:
228  if operationId != self._operationId:
229  return None
230  # Process application event loop in order to accept user input during time consuming drawing operation
231  self._updateCounter+=1
232  if self._updateCounter>=self.UPDATE_EVERY:
233  self._updateCounter=0
234  if not Application.NO_PROCESS_EVENTS:
235  QCoreApplication.instance().processEvents()
236  # create box
237  text = ""
238  if self._boxContentScript != "":
239  dataAccessorObject = BasicDataAccessorInterface(object, self.dataAccessor(), False)
240  try:
241  text = dataAccessorObject.runScript(self._boxContentScript).replace("None", "")
242  except Exception as e:
243  logging.info("Error in script: " + exception_traceback())
244  text = ""
245  widget = self.createBox(widgetParent, self.dataAccessor().isContainer(object), self.dataAccessor().label(object), text)
246  child_positionName = positionName + "-" + str(i)
247  self.addWidget(widget, object, child_positionName)
248  i += 1
249 
250  # create Connections
251  if not self.createConnections(operationId, widgetParent):
252  return None
253 
254  for widget in widgetParent.children():
255  # Abort drawing if operationId out of date
256  if operationId != self._operationId:
257  return None
258  # create children objects
259  if isinstance(widget, WidgetContainer):
260  if not self.createBoxesRecursive(operationId, self.applyFilter(self.dataAccessor().children(widget.object)), widget, positionName):
261  return None
262  if isinstance(widget, (WidgetContainer,ConnectableWidget)):
263  widget.noRearangeContent(False)
264 
265  for widget in widgetParent.children():
266  # Abort drawing if operationId out of date
267  if operationId != self._operationId:
268  return None
269  # draw box
270  if isinstance(widget, (WidgetContainer,ConnectableWidget)):
271  widget.show()
272  self.autosizeScrollArea()
273 
274  return True
275 
276  def _sortByRelations(self, objects):
277  """ Sort a list of objects by their mother/daughter relations.
278 
279  All daughter objects are put directly behind the mother object in the list.
280  This sorting algorithm is run before the display of objects with relations.
281  """
282  #logging.debug(__name__ + ": _sortByRelations")
283  if len(objects) == 0:
284  return ()
285  if len(objects) > self.NO_SORTING_ABOVE:
286  return objects
287  unsortedObjects = list(objects)
288  sortedObjects = []
289  for object in reversed(list(objects)):
290  globalMother=True
291  for mother in self.dataAccessor().allMotherRelations(object):
292  if mother in unsortedObjects:
293  globalMother=False
294  break
295  if object in unsortedObjects and globalMother:
296  unsortedObjects.remove(object)
297  sortedObjects.insert(0, object)
298  i = 0
299  for child in self.dataAccessor().allDaughterRelations(object):
300  if child in unsortedObjects:
301  i += 1
302  unsortedObjects.remove(child)
303  sortedObjects.insert(i, child)
304  sortedObjects += unsortedObjects
305  return tuple(sortedObjects)
306 
307  def closeEvent(self, event):
308  self.clear()
309  WidgetView.closeEvent(self, event)
310 
311  def contentStartX(self):
312  return 10 * self.zoomFactor()
313 
314  def contentStartY(self):
315  return 10 * self.zoomFactor()
316 
317  def expandAll(self):
318  for widget in self.widgets():
319  if isinstance(widget,WidgetContainer) and widget.collapsed():
320  widget.toggleCollapse()
321 
322  def collapseAll(self):
323  for widget in self.widgets():
324  if isinstance(widget,WidgetContainer) and not widget.collapsed():
325  widget.toggleCollapse()
326 
327  def expandToDepth(self,depth):
328  for widget in self.widgets():
329  if isinstance(widget,WidgetContainer):
330  mother=widget
331  d=0
332  while isinstance(mother.parent(),WidgetContainer):
333  mother=mother.parent()
334  d+=1
335  if not widget.collapsed() and d>=depth or\
336  widget.collapsed() and d<depth:
337  widget.toggleCollapse()
338 
339  def expandObject(self,object):
340  widget=self.widgetByObject(object)
341  if isinstance(widget,WidgetContainer) and widget.collapsed():
342  widget.toggleCollapse()
343 
344  def collapseObject(self,object):
345  widget=self.widgetByObject(object)
346  if isinstance(widget,WidgetContainer) and not widget.collapsed():
347  widget.toggleCollapse()
348 
349  def toggleCollapsed(self,object):
350  self.emit(SIGNAL("toggleCollapsed"), object)
351 
353  AUTOSIZE = True
354  AUTOSIZE_KEEP_ASPECT_RATIO = False
355  AUTOLAYOUT_CHILDREN_ENABLED = True
356 
357  def __init__(self, parent=None):
358  WidgetContainer.__init__(self, parent)
359 
360  def dataAccessor(self):
361  return self.parent().dataAccessor()
362 
363  def widgetByObject(self, mother):
364  return self.parent().widgetByObject(mother)
365 
367  return self.parent().arrangeUsingRelations()
368 
370  return self.parent().autosizeScrollArea()
371 
373  self.__class__.autolayoutAlgorithm(self)
374 
375  #@staticmethod
377  """ Arrange box position according to mother relations.
378  """
379  widgetParent = self.parent()
380  min_x = round(self.contentStartX())
381  min_y = round(self.contentStartY())
382  widgetBefore=None
383  leftMargin = VispaWidget.LEFT_MARGIN
384  topMargin = VispaWidget.TOP_MARGIN
385  for widget in self.children():
386  if isinstance(widget, VispaWidget) and hasattr(widget,"object"):
387  x = min_x
388  y = min_y
389  if self.arrangeUsingRelations():
390  for mother in self.dataAccessor().motherRelations(widget.object):
391  w = self.widgetByObject(mother)
392  if w:
393  # place daughter box on the right of the mother box
394  if x < w.x() + w.width():
395  x = w.x() + w.width() + leftMargin
396  # place right next to mother if its the first daughter
397  if w==widgetBefore:
398  y = w.y()
399  widget.move(x, y)
400  widgetBefore=widget
401  # remember the position below all other objects as min_y
402  min_y = y + widget.height() + widget.getDistance("topMargin")
403  self.autosizeScrollArea()
404  self.updateConnections()
405  return True
406  autolayoutAlgorithm = staticmethod(autolayoutAlgorithm)
407 
408  def toggleCollapse(self):
409  WidgetContainer.toggleCollapse(self)
410  self.toggleCollapsed(self.object)
411 
412  def toggleCollapsed(self,object):
413  self.parent().toggleCollapsed(object)
414 
def createConnections(self, operationId, widgetParent)
def numberDataObjectChildren(self, objects=None)
def replace(string, replacements)
def createBoxesRecursive(self, operationId, objects, widgetParent, positionName="0")
def __init__(self, parent=None)
Definition: BoxDecayView.py:30
char const * label
def createSinkPort(self, w, name, visible=True)
def createBox(self, widgetParent, container, title, text)
def addWidget(self, widget, object=None, positionName=0)
Definition: WidgetView.py:109
def widgetByObject(self, object)
Definition: WidgetView.py:52
def updateContent(self, overrideCheck=False)
Definition: BoxDecayView.py:96
def createConnection(self, w1, name1, w2, name2, color=None, portsVisible=True)
#define str(s)
def createSourcePort(self, w, name, visible=True)
How EventSelector::AcceptEvent() decides whether to accept an event for output otherwise it is excluding the probing of A single or multiple positive and the trigger will pass if any such matching triggers are PASS or EXCEPTION[A criterion thatmatches no triggers at all is detected and causes a throw.] A single negative with an expectation of appropriate bit checking in the decision and the trigger will pass if any such matching triggers are FAIL or EXCEPTION A wildcarded negative criterion that matches more than one trigger in the trigger list("!*","!HLTx*"if it matches 2 triggers or more) will accept the event if all the matching triggers are FAIL.It will reject the event if any of the triggers are PASS or EXCEPTION(this matches the behavior of"!*"before the partial wildcard feature was incorporated).Triggers which are in the READY state are completely ignored.(READY should never be returned since the trigger paths have been run