CMS 3D CMS Logo

VispaWidget.py
Go to the documentation of this file.
1 import math
2 
3 from PyQt4.QtCore import *
4 from PyQt4.QtGui import *
5 from PyQt4.QtSvg import QSvgRenderer
6 
7 import logging
8 
9 from Vispa.Gui.ZoomableWidget import ZoomableWidget
10 from Vispa.Gui.VispaWidgetOwner import VispaWidgetOwner
11 
13  """ TextField for VispaWidget.
14 
15  Text and title shown in VispaWidget are TextField object.
16  """
17 
18  WIDTH = 100
19  HEIGHT = 0 # If set to zero, it will be automatically set to the font's height in setFont().
20 
21  FONT_SIZE = 12
22 
23  def __init__(self):
24  self._text = ''
25  self._textShort = ''
26  self._font = None #needed for autosizeFont()
27  self._fontSize = self.FONT_SIZE
28  self._fontSizeHasChanged = True
29  self._penColor = QColor(Qt.black)
30  self._minFontSize = 1
31  self._maxFontSize = 30
32  self._outputFlags = Qt.AlignLeft
33 
34  self._defaultWidth = self.WIDTH
35  self._defaultHeight = self.HEIGHT
36  self._width = self._defaultWidth
37  self._height = self._defaultHeight
38 
39  self._deletableFalg = True
40  self._autosizeFontFlag = False
42  self._autoscaleFlag = False
44 
45  self._xPos = 0
46  self._yPos = 0
47 
48  def setText(self, text):
49  """ Sets text.
50  """
51  self._text = text
52  self._textShort = ''
53 
54  def setAutosizeFont(self, auto):
55  """ Sets autosizeFontFlag.
56 
57  If flag is True and text does not fit in its given area the font size will be reduced to fit.
58  """
59  self._autosizeFontFlag = bool(auto)
60  if not self.empty():
61  self.calculateDimensions()
62 
63  def setAutotruncate(self, auto):
64  """ Sets autoTruncateTextFlag.
65 
66  If flag is True the text will be truncated if it is too long to fit in given space.
67  """
68  self._autotruncateTextFlag = bool(auto)
69  if not self.empty():
70  self.calculateDimensions()
71 
72  def setAutoscale(self, auto, keepAspectRatio):
73  """ Sets autoscale and autoscalKeepAspectRatio flags.
74 
75  If autoscale flag is True the needed space is increased until text fits.
76  If keepAspectRatio flag is False the aspet ratio may change depending on output flags.
77  See setOutputFlags().
78  """
79  self._autoscaleFlag = auto
80  self._autoscaleKeepAspectRatioFlag = keepAspectRatio
81 
82  def setFont(self, qfont):
83  """ Sets font and if default height is not yet set default height will be set to font height.
84  """
85  #self._font = QFont(qfont)
86  self._font = qfont
87  self._fontSizeHasChanged = True
88 
89  def font(self):
90  return self._font
91 
92  def setPenColor(self, color):
93  self._penColor = color
94 
95  def penColor(self):
96  return self._penColor
97 
98  def getFontHeight(self, fm=None):
99  """ Calculates font height for given font metrics object.
100 
101  If no font metrics object is given one will be created for the current TextField font.
102  """
103  if fm == None:
104  fm = QFontMetrics(self._font)
105  height = fm.height()
106  return height
107 
108  def setDefaultFontSize(self, fontSize):
109  """ Sets preferred font size.
110  """
111  self._defaultFontSize = fontSize
112 
113  def setFontSizeRange(self, minFontSize, maxFontSize):
114  """ Sets min and max font point size for autosize font capability.
115 
116  See setAutosizeFont().
117  """
118  self._minFontSize = minFontSize
119  self._maxFontSize = maxFontSize
120 
121  def getDrawRect(self, scale=1):
122  """ Returns QRect.
123 
124  Width will be equal to getWidth() and height equal to getHeight().
125  """
126  return QRect(self._xPos, self._yPos, math.ceil(self._width * scale), math.ceil(self._height * scale))
127  #return QRectF(self._xPos, self._yPos, self._width, self._height)
128 
129  def setDefaultWidth(self, width):
130  """ Sets preferred width for text output.
131  """
132  self._defaultWidth = width
133 
134  def setDefaultHeight(self, height):
135  """ Sets preferred height for text output.
136  """
137  self._defaultHeight = height
138 
140  """ Calculates the space (width and height) needed to display text.
141 
142  Depending on the flags set the size will be greater than the default size,
143  or the font size will be adjusted,
144  or the text will be truncated.
145 
146  See setAutosizeFont(), setAutotruncate(), setAutoscale(), setDefaultWidth(), setDefaultHeight().
147  """
148  #self._width = self._defaultWidth
149  #self._height = self._defaultHeight
150 
151  if self._fontSizeHasChanged and (not self._autosizeFontFlag or self._autoscaleFlag):
152  self._font.setPointSize(self.getFontSize())
153  if self._defaultHeight == 0:
154  self.setDefaultHeight(self.getFontHeight())
155  self._fontSizeHasChanged = False
156 
157  if self._autoscaleFlag:
158  self.autoscale()
159 
160  elif self._autosizeFontFlag:
161  self.autosizeFont()
162 
163  if self._autotruncateTextFlag:
164  self.truncate()
165 
166  def getWidth(self):
167  """ Returns width calculated by calculateDimensions().
168  """
169  return self._width
170 
171  def getHeight(self):
172  """ Returns height calculated by calculateDimensions().
173  """
174  #logging.debug(self.__class__.__name__ + ": getHeight() "+ str(self._height) + " " + self.text())
175  return self._height
176 
177  def setOutputFlags(self, flags):
178  """ Set Qt output flags for drawing text.
179  """
180  self._outputFlags = flags
181 
182  def getOutputFlags(self):
183  """ Returns set output flags.
184  """
185  return self._outputFlags
186 
187  def truncated(self):
188  """ Returns True if text was truncated.
189  """
190  if self._textShort != '':
191  return True
192  return False
193 
194  def getFontSize(self):
195  """ Returns the font size the text will be drawn in.
196  """
197  if self._autoscaleFlag:
198  return self._defaultFontSize
199  return self._fontSize
200 
201  def text(self):
202  """ Returns text.
203  """
204  return self._text
205 
206  def getTextShort(self):
207  """ Returns short version of text if it was truncated.
208  """
209  return self._textShort
210 
211  def getOutputText(self):
212  """ Evaluates whether the string was truncated or not.
213 
214  If truncated it returns short version, else the whole text.
215  """
216  if self.truncated():
217  return self._textShort
218  return self._text
219 
220  def empty(self):
221  """ Returns True if no text or empty string is set.
222  """
223  if self._text == '' or self._text == None:
224  return True
225  return False
226 
227  def autoscale(self):
228  """ Adjusts values for getWidth() and getHeight() so whole text fits in.
229  """
230  #logging.debug("%s: autoscale() - %s" % (self.__class__.__name__, self._text))
231  fm = QFontMetrics(self._font)
232  self.ranbefore=True
233  self._width = 1
234  self._height = 1
235  widthFits = heightFits = False
236 
237  if not self._autoscaleKeepAspectRatioFlag:
238  # test for replacing else part in while-loop (2009-02-23)
239  neededRect = fm.boundingRect(0, 0, self._defaultWidth*100, self._defaultHeight*100, self._outputFlags, self._text)
240  self._width = neededRect.width()
241  self._height = neededRect.height()
242  return
243 
244  while not widthFits or not heightFits:
246  self._width += 1
247  self._height = 1.0 * self._width * (self._defaultHeight + 1) / self._defaultWidth
248  # 'defaultHeight' +1 prevents factor 0 --> infinite loop
249  else:
250  if not widthFits:
251  self._width += 1
252  if not heightFits:
253  self._height += 1
254  neededRect = fm.boundingRect(0, 0, self._width, self._height, self._outputFlags, self._text)
255  if neededRect.width() <= self._width:
256  widthFits = True
257  self._width += 1 # prevent slightly too small width (maybe due to rounding while zoooming)
258  if neededRect.height() <= self._height:
259  heightFits = True
260  #logging.debug(self.__class__.__name__ +": autoscale() - (width, height) = ("+ str(self._width) +", "+ str(self._height) +")")
261 
262  def autosizeFont(self):
263  """ Decreases font so text fits in given widht and height.
264  """
265  if self._font == None:
266  logging.error("TextField.autosizeFont() - ERROR: 'font' not set, can't calculate font size")
267  return
268 
269  drawRect = self.getDrawRect()
270  font = self._font
271  decSize = 0
272  for size in range(self._minFontSize + 1, self._maxFontSize + 1):
273  font.setPointSizeF(size + 0.1 * decSize)
274  fm = QFontMetricsF(font)
275  neededRect = fm.boundingRect(drawRect, self._outputFlags, self._text)
276  if neededRect.width() > drawRect.width() or neededRect.height() > drawRect.height():
277  size -= 1
278  break
279 
280  for decSize in range(0, 10):
281  font.setPointSizeF(size + 0.1 * decSize)
282  fm = QFontMetricsF(font)
283  neededRect = fm.boundingRect(drawRect, self._outputFlags, self._text)
284  if neededRect.width() > drawRect.width() or neededRect.height() > drawRect.height():
285  decSize -= 1
286  break
287 
288  self._fontSize = size + 0.1 * decSize
289  #print "determineTextFieldSize(", self._fontSize, ")"
290 
291  def truncate(self):
292  """ Truncates text if it does not fit in given space.
293  """
294  #logging.debug(self.__class__.__name__ + ": truncate()")
295  text = QString(self._text)
296  short = QString()
297  drawRect = QRectF(self.getDrawRect())
298  font = self._font
299  fm = QFontMetricsF(font)
300  counter = 0
301  patterns = text.split(QRegExp('\\b'))
302 
303  for pattern in patterns:
304  short.append(pattern)
305  neededRect = fm.boundingRect(drawRect, self._outputFlags, short)
306 
307  if neededRect.width() > drawRect.width() or neededRect.height() > drawRect.height():
308  break
309  counter += len(pattern)
310 
311  if counter < len(text):
312  self._textShort = text.left(counter)
313  self._textShort = text.left(counter).append("...")
314  #print "truncate() - short: ", self._textShort
315 
316  def paint(self, painter, xPos, yPos, scale=1):
317  """ Draws text on given painter at given position.
318 
319  If scale is given the text will be scaled accordingly.
320  """
321  self._xPos = xPos
322  self._yPos = yPos
323  drawRect = self.getDrawRect(scale)
324  painter.setBrush(Qt.NoBrush)
325  painter.setPen(QPen(self._penColor))
326  self._font.setPointSize(max(self.getFontSize() * scale, 1))
327  painter.setFont(self._font)
328  painter.drawText(drawRect, self.getOutputFlags(), self.getOutputText())
329 
330  ## debug
331  #painter.drawRect(drawRect)
332  #print " drawRect ", drawRect
333  #print " text", self.getOutputText()
334  #print "drawRect(width, height) = ", drawRect.width(), ",", drawRect.height(), ")"
335 
336 
338  """ Class supporting random shapes, title and text field.
339 
340  Title and text field content are stored in TextField objects.
341  You can influence the behavior of the widget by severals flags.
342  If you want the widget's size to fit the content size (increases / decreases widget) use enableAutosizing().
343  You can also chose the decrease the text field's font size, if text does not fit in. See setTextFieldAutosizeFont().
344  Additionally the text can be truncated instead using setTextFieldAutotruncateText().
345  """
346 
347  WIDTH = 100
348  HEIGHT = 80
349  MINIMUM_WIDTH = 30
350  MINIMUM_HEIGHT = 0
351  ROUNDRECT_RADIUS = 30
352  BACKGROUND_SHAPE = 'RECT'
353 
354  TOP_MARGIN = 5
355  LEFT_MARGIN = 5
356  BOTTOM_MARGIN = 5
357  RIGHT_MARGIN = 5
358  HORIZONTAL_INNER_MARGIN = 5
359  VERTICAL_INNTER_MARGIN = 5
360 
361  #PEN_COLOR = QColor('darkolivegreen')
362  #FILL_COLOR1 = QColor('yellowgreen')
363  #FILL_COLOR2 = QColor('darkkhaki')
364 
365  #PEN_COLOR = QColor(0, 116, 217) # strong blue, end wprkshop
366  #FILL_COLOR1 = QColor(65, 146, 217)
367  #FILL_COLOR2 = QColor(122, 186, 242)
368 
369  #PEN_COLOR = QColor(102, 133, 176) # gentle blue
370  #FILL_COLOR1 = QColor(128, 186, 224)
371  #FILL_COLOR2 = QColor(188, 215, 241)
372 
373  #PEN_COLOR = QColor(115, 115, 115) # grey
374  #FILL_COLOR1 = QColor(166, 166, 166)
375  #FILL_COLOR2 = QColor(217, 217, 217)
376 
377  PEN_COLOR = QColor(128, 186, 224) # gentle blue right
378  FILL_COLOR1 = QColor(188, 215, 241)
379  FILL_COLOR2 = QColor(242, 230, 242)
380  TITLE_COLOR = QColor(Qt.white)
381 
382  #PEN_COLOR = QColor(100, 133, 156)
383  #FILL_COLOR1 = QColor(116, 155, 181)
384  #FILL_COLOR2 = QColor(124, 166, 194)
385 
386  SELECT_COLOR = QColor('darkblue')
387 
388  SELECTABLE_FLAG = True
389  FOCUSPOLICY = Qt.StrongFocus
390  SELECTED_FRAME_WIDTH = 2 # Width in pixels of colored (SELECT_CORLOR) frame, when widget is in focus
391 
392  AUTOPOSITIONIZE_WHEN_ZOOMING_FLAG = True
393 
394  TITLEFIELD_FONTSIZE = 12
395  COLOR_HEADER_BACKGROUND_FLAG = True
396  USE_BACKGROUND_GRADIENT_FLAG = True
397 
398  TEXTFIELD_FONTSIZE = 12
399  TEXTFIELD_FONTSIZE_MIN = 2
400  TEXTFIELD_FONTSIZE_MAX = 20
401  TEXTFIELD_FLAGS = Qt.TextWordWrap
402  TEXTFIELD_AUTOSIZE_FONT_FLAG = False
403  TEXTFIELD_AUTOTRUNCATE_TEXT_FLAG = False
404 
405  AUTOSIZE = False
406  AUTOSIZE_KEEP_ASPECT_RATIO = True
407 
408  ARROW_SHAPE = None
409  ARROW_SHAPE_TOP = 0
410  ARROW_SHAPE_LEFT = 1
411  ARROW_SHAPE_BOTTOM = 2
412  ARROW_SHAPE_RIGHT = 3
413  ARROW_SIZE = 30
414 
415  def __init__(self, parent=None):
416  """ Constructor
417  """
418  #print "VispaWidget.__init__()"
419  self._autosizeFlag = False
421  self._titleField = None
424 
425  self._scale = 1
426  self._scaleWidth = 1
427  self._scaleHeight = 1
428 
429  self.framePenColor = None
430  self.fillColor1 = None
431  self.fillColor2 = None
432 
433  self._textField = None
434  self._selectableFlag = False
435  self._selectedFlag = False
436  self._deletableFlag = True
437  self._dragableFlag = True
438  self._dragMouseXrel = 0
439  self._dragMouseYrel = 0
440 
441  self._distances = None
448 
451  self._arrowShape = None
452 
453  self._unzoomedPositionX = 0 # These values are set by the overridden move() function.
454  self._unzoomedPositionY = 0 # With these values the widgets position can be scaled when zooming.
456 
457  self._bodyWidget = None
458  self._image = None
459 
460  ZoomableWidget.__init__(self, parent)
461 
462  self.setColors(self.PEN_COLOR, self.FILL_COLOR1, self.FILL_COLOR2)
463  self.setSelectable(self.SELECTABLE_FLAG)
464  self.setShape(self.BACKGROUND_SHAPE)
465  self.setArrowShape(self.ARROW_SHAPE)
466  self.enableAutopositionizeWhenZooming(self.AUTOPOSITIONIZE_WHEN_ZOOMING_FLAG)
467  self.enableAutosizing(self.AUTOSIZE, self.AUTOSIZE_KEEP_ASPECT_RATIO)
468  self.enableColorHeaderBackground(self.COLOR_HEADER_BACKGROUND_FLAG)
469  self.enableBackgroundGradient(self.USE_BACKGROUND_GRADIENT_FLAG)
470  self.setMinimumSize(self.MINIMUM_WIDTH, self.MINIMUM_HEIGHT)
471 
472  self.noRearangeContent(False)
474  self._previusDragPosition = self.pos()
475 
476  def unzoomedX(self):
477  """ Returns x coordinate the widget would have if zoom was set to 100%.
478  """
479  #logging.debug(self.__class__.__name__ +": unzoomedX() "+ str(self._unzoomedPositionY))
480  return int(self._unzoomedPositionX)
481 
482  def unzoomedY(self):
483  """ Returns x coordinate the widget would have if zoom was set to 100%.
484  """
485  #logging.debug(self.__class__.__name__ +": unzoomedY() "+ str(self._unzoomedPositionY))
486  return int(self._unzoomedPositionY)
487 
488  def scale(self):
489  """ Return scale factor of widget.
490  """
491  return self._scale
492 
493  def setZoom(self, zoom):
494  """ Sets widget's zoom.
495  """
496  ZoomableWidget.setZoom(self, zoom)
497 
498  self._scale = self.zoom() * 0.01
499  ZoomableWidget.resize(self, self.width(), self.height())
500 
502  self.move(self._unzoomedPositionX * self.scale(), self._unzoomedPositionY * self.scale())
503 
504  def penColor(self):
505  """ Returns pen color for this widget.
506  """
507  return self.framePenColor
508 
510  """ If True the position of this widget will be corrected according to it unzoomed position.
511 
512  Prevents unexpected moving when zoom is enabled due to rounding errors.
513  """
515 
516  def setDragable(self, dragable, recursive=False):
517  """ If True the widget can be dragged using a pointing device.
518 
519  If recursive is True also dragablitiy of all children will be set.
520  """
521  self._dragableFlag = dragable
522  if recursive:
523  for child in self.children():
524  if isinstance(child, VispaWidget):
525  child.setDragable(dragable, True)
526 
527  def isDragable(self):
528  return self._dragableFlag
529 
530  def setColors(self, penColor, fillColor1, fillColor2):
531  """ Sets colors of this widget.
532 
533  The pen color will be used for drawing the widget's frame.
534  Fill color 1 and 2 will be used for background color gradient.
535  """
536  self.framePenColor = penColor
537  self.fillColor1 = fillColor1
538  self.fillColor2 = fillColor2
539 
540  def setShape(self, shape):
541  """ Sets shape of this widget.
542 
543  Right now supported 'RECT' (default), 'ROUNDRECT', 'CIRCLE'.
544  """
545  self._backgroundShape = shape
546 
547  def setArrowShape(self, direction):
548  """ In addition to normal shape this gives whole widget the shape of an arrow.
549 
550  Possible values for direction are
551  None
552  ARROW_SHAPE_TOP
553  ARROW_SHAPE_LEFT
554  ARROW_SHAPE_BOTTOM
555  ARROW_SHAPE_RIGHT
556  """
557  self._arrowShape = direction
558 
559  def enableAutosizing(self, auto, keepAspectRatio=True):
560  """ Sets flag for auto resizing this widget.
561 
562  If auto is True the size of this widget will be adjusted to widget size.
563  If keepAspectRatio is False the aspect ratio may change according to widget content.
564  """
565  if self._autosizeKeepAspectRatioFlag != auto or self._autosizeKeepAspectRatioFlag != keepAspectRatio:
566  changed = True
567  else:
568  changed = False
569 
570  self._autosizeFlag = auto
571  self._autosizeKeepAspectRatioFlag = keepAspectRatio
572  if self.titleIsSet():
573  self._titleField.setAutoscale(auto, keepAspectRatio)
574  if self.textFieldIsSet():
575  self._textField.setAutoscale(auto, keepAspectRatio)
576 
577  if changed:
579  self.update()
580 
581  def autosizeEnabled(self):
582  """ Returns True if auto resizing is enabled.
583 
584  See enableAutosizing().
585  """
586  return self._autosizeFlag
587 
588  def setSelectable(self, selectable):
589  """ Makes widget selectable if True.
590  """
591  self._selectableFlag = bool(selectable)
592 
593  if self._selectableFlag:
594  self.setFocusPolicy(self.FOCUSPOLICY)
595  else:
596  self.setFocusPolicy(Qt.NoFocus)
597 
598  def isSelectable(self):
599  """ Returns True if widget can be selected.
600  """
601  return self._selectableFlag
602 
603  def setDeletable(self, deleteable):
604  self._deletableFlag = deleteable
605 
606  def isDeletable(self):
607  return self._deletableFlag
608 
609  def enableColorHeaderBackground(self, enable=True):
610  """ If set to True the background of the header is painted using pen color.
611 
612  See setColors().
613  """
614  self._colorHeaderBackgroundFlag = enable
615 
617  return self._colorHeaderBackgroundFlag
618 
619  def enableBackgroundGradient(self, enable=True):
620  """ If set to True the background color is painted using a QLinearGradient with the two fill colors.
621 
622  See setColors().
623  """
624  self._backgroundGradientEnabledFlag = enable
625 
628 
629  def select(self, sel=True, multiSelect=False):
630  """ Marks this widget as selected and informs parent if parent is VispaWidetOwner.
631 
632  If multiSelect is True e.g. due to a pressed Ctrl button while clicking (see mousePressEvent()),
633  the VispaWidgetOwner will also be informed and might not deselect all widgets.
634  """
635  if not self.isSelectable():
636  return
637 
638  if sel != self._selectedFlag:
639  self.update()
640 
641  self._selectedFlag = sel
642 
643  # TODO: raise in front of other widget, not in front of line
644 # if sel:
645 # self.raise_()
646 
647  if (multiSelect or self.isSelected()) and isinstance(self.parent(), VispaWidgetOwner):
648  self.parent().widgetSelected(self, multiSelect)
649 
650  def mouseDoubleClickEvent(self, event):
651  if isinstance(self.parent(), VispaWidgetOwner):
652  self.parent().widgetDoubleClicked(self)
653 
654  def isSelected(self):
655  """ Returns True if widget is selected.
656  """
657  if not self.isSelectable():
658  return False
659 
660  return self._selectedFlag
661 
662  def _initTitleField(self):
663  """ Initializes title field.
664 
665  Sets default flags for title field.
666  """
667  if self._titleField == None:
668  self._titleField = TextField()
669  self._titleField.setDefaultWidth(self.getDistance('titleFieldWidth', 1, True))
670  self._titleField.setDefaultFontSize(self.TITLEFIELD_FONTSIZE)
671  self._titleField.setAutotruncate(False)
672  #self._titleField.setAutoscale(self._autosizeFlag, self._autosizeKeepAspectRatioFlag)
673  self.titleField().setAutoscale(True, False)
674  self._titleField.setPenColor(self.TITLE_COLOR)
675 
676  def titleIsSet(self):
677  """ Returns True if test field has been set to a non empty string.
678  """
679  if self._titleField != None and not self._titleField.empty():
680  return True
681  return False
682 
683  def setTitle(self, title):
684  """ Sets title text.
685  """
686  self._initTitleField()
687  self._titleField.setFont(self.font())
688  self._titleField.setText(title)
690  self.update()
691 
692  def titleField(self):
693  return self._titleField
694 
695  def title(self):
696  if self.titleIsSet():
697  return self._titleField.text()
698  return None
699 
700  def _initTextField(self):
701  if self._textField == None:
702  self._textField = TextField()
703  self._textField.setFontSizeRange(self.TEXTFIELD_FONTSIZE_MIN, self.TEXTFIELD_FONTSIZE_MAX)
704  self._textField.setDefaultWidth(self.getDistance('textFieldWidth', 1, True))
705  self._textField.setDefaultHeight(self.getDistance('textFieldHeight', 1, True))
706  self._textField.setDefaultFontSize(self.TEXTFIELD_FONTSIZE)
707  self._textField.setAutosizeFont(self.TEXTFIELD_AUTOSIZE_FONT_FLAG)
708  self._textField.setAutotruncate(self.TEXTFIELD_AUTOTRUNCATE_TEXT_FLAG)
709  self._textField.setOutputFlags(self.TEXTFIELD_FLAGS)
710  self._textField.setAutoscale(self._autosizeFlag, self._autosizeKeepAspectRatioFlag)
711 
712  def textFieldIsSet(self):
713  """ Returns True if text field text has been set to an non empty string.
714  """
715  if self._textField != None and not self._textField.empty():
716  return True
717  return False
718 
719  def setText(self, text):
720  """ Sets text for text field.
721  """
722  #logging.debug(self.__class__.__name__ +": setText() - %s (%s)" % (str(text), str(type(text))))
723  self._initTextField()
724  self._textField.setFont(self.font())
725  self._textField.setText(text)
726 
728 
729  self.setToolTip(self._textField.text())
730  self.update()
731 
732  def textField(self):
733  """ Returns TextField object belonging to text field.
734  """
735  return self._textField
736 
737  def text(self):
738  """ Returns text of text field.
739  """
740  if self.textFieldIsSet():
741  return self._textField.text()
742  return None
743 
744  def setTextFieldAutosizeFont(self, auto):
745  """ Sets auto resize flag of text field.
746  """
747  self._initTextField()
748  self._textField.setAutosizeFont(auto)
750 
752  """ Sets auto truncate flag of text field.
753  """
754  self._initTextField()
755  self._textField.setAutotruncate(auto)
757 
758  def setMaximumSize(self, *attr):
759  QWidget.setMaximumSize(self, *attr)
761 
762  def setMinimumSize(self, *attr):
763  QWidget.setMinimumSize(self, *attr)
765 
766  def sizeHint(self):
767  """ Calculates needed space for widget content.
768  """
769  #if not self._autosizeFlag:
770  # return QSize(self.WIDTH, self.HEIGHT)
771 
772  self._scaleWidth = 1 # for getDistance()
773  self._scaleHeight = 1
774 
775  neededWidth = self.getDistance('leftMargin', 1) + self.getDistance('rightMargin', 1)
776  neededHeight = self.getDistance('topMargin', 1) + self.getDistance('bottomMargin', 1)
777 
778  titleFieldWidth = 0
779  titleFieldHeight = 0
780  titleIsSet = self.titleIsSet()
781  if titleIsSet:
782  titleFieldWidth = self.getDistance('titleFieldWidth', 1)
783  titleFieldHeight += self.getDistance('titleFieldHeight', 1)
784 
785  textFieldWidth = 0
786  textFieldHeight = 0
787  if self.textFieldIsSet():
788  textFieldWidth = self._textField.getWidth()
789  textFieldHeight += self._textField.getHeight()
790  bodyWidgetWidth = 0
791  bodyWidgetHeight = 0
792  if self._bodyWidget:
793  if self._bodyWidget.parent() != self:
794  self._bodyWidget = None
795  else:
796  sh = self._bodyWidget.sizeHint()
797  bodyWidgetWidth = sh.width()
798  bodyWidgetHeight = sh.height()
799 
800  imageSizeF = self.imageSizeF()
801  bodyWidth = max(textFieldWidth, bodyWidgetWidth, imageSizeF.width())
802  bodyHeight = max(textFieldHeight, bodyWidgetHeight, imageSizeF.height())
803 
804  if titleIsSet and bodyHeight != 0:
805  # gap between title and text
806  neededHeight += self.getDistance('bottomMargin', 1)
807 
808  neededWidth += max(bodyWidth, titleFieldWidth)
809  neededHeight += titleFieldHeight + bodyHeight
810 
811  # evaluate maximum size
812  maxWidth = self.maximumSize().width()
813  maxHeight = self.maximumSize().height()
814 
815  maxScaleWidth = min(1.0, 1.0 * maxWidth/neededWidth)
816  maxScaleHeight = min(1.0, 1.0 * maxHeight/neededHeight)
817  if maxScaleWidth != 1.0 or maxScaleHeight != 1.0:
818  # this is not limited by keepAspectRationFlag
819  # as it is about absolute sizes here
820  # ratio is evaluated in autosize()
821  scale = min(maxScaleWidth, maxScaleHeight)
822  neededWidth *= scale
823  neededHeight *= scale
824 
825  return QSize(max(self.minimumSize().width(), neededWidth), max(self.minimumSize().height(), neededHeight))
826 
827  def resize(self, width, height):
828  self.WIDTH = width / self.zoomFactor()
829  self.HEIGHT = height / self.zoomFactor()
830  ZoomableWidget.resize(self, self.width(), self.height())
831 
832  def autosize(self, skipSizeHint=False):
833  """ Calculates scale factors and resizes widget accordingly.
834 
835  Calculates by which factor width and height of this widget should be scaled
836  depending on the content of the widget.
837  If skipSizeHint is True only the resize part is performed.
838  """
839  #logging.debug(self.__class__.__name__ +": autosize()")
840  if not skipSizeHint:
841  neededSpace = self.sizeHint()
842  neededWidth = neededSpace.width()
843  neededHeight = neededSpace.height()
844 
845  self._scaleWidth = 1.0 * neededWidth / self.WIDTH
846  self._scaleHeight = 1.0 * neededHeight / self.HEIGHT
847 
849  self._scaleWidth = min(self._scaleWidth, self._scaleHeight)
850  self._scaleHeight = self._scaleWidth
851 
852  if self._bodyWidget:
853  self._bodyWidget.move(self.getDistance("contentStartX"), self.getDistance("contentStartY"))
854 
855  ZoomableWidget.resize(self, self.width(), self.height())
856  self.update()
857  # update() is sometimes required
858  # if content has changed but size did not.
859  # user can expect repainting after calling autosize()? TODO: think about this
860 
861  def rearangeContent(self):
862  """ Checks which components have to be regarded for size calculation.
863 
864  If you want to make sure this function is called next time a distance is requested use call scheduleRearangeContent().
865  This function is stronger than autosize, as it also recalculates dimensions of children (e.g. text field).
866  If content did not change autosize is probably the better approach.
867  """
868  self._rearangeContentFlag = False
869  # does not work with box decay tree (needs correct sizes before on screen)
870  #if not self.isVisible():
871  # return
872 
873  #logging.debug(self.__class__.__name__ +": rearangeContent() ")
874 
875  if self.textFieldIsSet():
876  self._textField.setDefaultWidth(self.getDistance('textFieldWidth', 1, True))
877  self._textField.setDefaultHeight(self.getDistance('textFieldHeight', 1, True))
878  self._textField.calculateDimensions()
879  if self.titleIsSet():
880  self._titleField.setDefaultWidth(self.getDistance('titleFieldWidth', 1, True))
881  self._titleField.setDefaultHeight(self.getDistance('titleFieldHeight', 1, True))
882  self._titleField.calculateDimensions()
883 
885  if self._autosizeFlag:
886  self.autosize()
887 
889  """ Sets distancesHaveToBeRecalculatedFlag to True.
890 
891  Next time defineDistances() is called distances are recalculated even if zoom has not changed.
892  """
894 
895  def noRearangeContent(self,no=True):
896  """ Flag disables any rearanging.
897  """
898  self._noRearangeContentFlag=no
899 
901  """ Makes sure rearangeContent() will be called next time a distance is requested.
902 
903  See rearangeContent().
904  """
905  self._rearangeContentFlag = True
906 
907  def showEvent(self, event):
908  """ Calls rearangeContent() if needed.
909  """
910  # hasattr for some reason important
911  # sometimes a show event seems to occur before the constructor is called
912 # if hasattr(self, "_rearangeContentFlag") and hasattr(self, "_noRearangeContentFlag") and \
913 # self._rearangeContentFlag and not self._noRearangeContentFlag:
914 
915  if self._rearangeContentFlag and not self._noRearangeContentFlag:
916  self.rearangeContent()
917  ZoomableWidget.showEvent(self, event)
918 
919  def distances(self):
920  """ Returns dictionary containing distances as defined in defineDistances().
921  """
922  return self._distances
923 
924  def defineDistances(self, keepDefaultRatio=False):
925  """ Defines supported distances.
926 
927  The distances are needed for drawing widget content and are scaled according to current scale / zoom factors.
928  The distances are stored in an dictionary (see distances()).
929  """
930  if self._rearangeContentFlag and not self._noRearangeContentFlag:# and self.isVisible():
931  self.rearangeContent()
932 
933  scale = 1.0 # remove if works without
934  if keepDefaultRatio:
935  scaleWidth = 1.0
936  scaleHeight = 1.0
937  else:
938  scaleWidth = self._scaleWidth
939  scaleHeight = self._scaleHeight
940 
941  if not self._distancesHaveToBeRecalculatedFlag and \
942  scaleWidth == self._distancesLastScaleWidth and \
943  scaleHeight == self._distancesLastScaleHeight:
944  return False
945 
946  #logging.debug(self.__class__.__name__ +": defineDistances() - scale = '"+ str(scale) +"'")
947 
948  self._distancesLastScale = scale
949  self._distancesLastScaleWidth = scaleWidth
950  self._distancesLastScaleHeight = scaleHeight
952 
953  self._distances = dict()
954  self._distances['width'] = self.WIDTH * scale * scaleWidth
955  self._distances['height'] = self.HEIGHT * scale * scaleHeight
956 
957  self._distances['frameTop'] = 1
958  self._distances['frameLeft'] = 1
959  self._distances['frameBottom'] = self._distances['height'] - 1
960  self._distances['frameRight'] = self._distances['width'] - 1
961 
962  if self._arrowShape == self.ARROW_SHAPE_TOP:
963  self._distances['frameTop'] = self.ARROW_SIZE * scale
964  self._distances['height'] += self._distances['frameTop']
965  self._distances['frameBottom'] = self._distances['height'] -1
966  elif self._arrowShape == self.ARROW_SHAPE_RIGHT:
967  self._distances['frameRight'] = self._distances['width']
968  self._distances['width'] += self.ARROW_SIZE * scale
969  elif self._arrowShape == self.ARROW_SHAPE_BOTTOM:
970  self._distances['frameBottom'] = self._distances['height']
971  self._distances['height'] += self.ARROW_SIZE * scale
972  elif self._arrowShape == self.ARROW_SHAPE_LEFT:
973  self._distances['frameLeft'] = self.ARROW_SIZE * scale
974  self._distances['width'] += self._distances['frameLeft']
975  self._distances['frameRight'] = self._distances['width'] -1
976 
977  self._distances['topMargin'] = self.TOP_MARGIN * scale
978  self._distances['leftMargin'] = self.LEFT_MARGIN * scale
979  self._distances['bottomMargin'] = self.BOTTOM_MARGIN * scale
980  self._distances['rightMargin'] = self.RIGHT_MARGIN * scale
981 
982  self._distances['horizontalInnerMargin'] = self.HORIZONTAL_INNER_MARGIN * scale
983  self._distances['verticalInnerMargin'] = self.VERTICAL_INNTER_MARGIN * scale
984 
985  self._distances['titleFieldX'] = self._distances['frameLeft'] + self._distances['leftMargin']
986  self._distances['titleFieldY'] = self._distances['frameTop'] + self._distances['topMargin']
987  if self.titleIsSet():
988  self._distances['titleFieldWidth'] = self._titleField.getWidth() * scale
989  self._distances['titleFieldHeight'] = self._titleField.getHeight() * scale
990  self._distances['titleFieldBottom'] = self._distances['titleFieldY'] + self._distances['titleFieldHeight']
991  else:
992  self._distances['titleFieldHeight'] = 0
993  self._distances['titleFieldBottom'] = self._distances['frameTop']
994  self._distances['titleFieldWidth'] = self._distances['width'] - self._distances['leftMargin'] - self._distances['rightMargin']
995 
996  self._distances['contentStartX'] = self._distances['frameLeft'] + self._distances['leftMargin']
997  self._distances['contentStartY'] = self._distances['titleFieldBottom'] + self._distances['topMargin']
998 
999  self._distances['textFieldX'] = self._distances['contentStartX']
1000  self._distances['textFieldY'] = self._distances['contentStartY']
1001  if self.textFieldIsSet():
1002  self._distances['textFieldWidth'] = self._textField.getWidth() * scale
1003  self._distances['textFieldHeight'] = self._textField.getHeight() * scale
1004  else:
1005  self._distances['textFieldWidth'] = self._distances['width'] - self._distances['textFieldX'] - self._distances['rightMargin']
1006  self._distances['textFieldHeight'] = self._distances['height'] - self._distances['textFieldY'] - self._distances['bottomMargin']
1007  self._distances['textFieldRight'] = self._distances['textFieldX'] + self._distances['textFieldWidth']
1008 
1009  return True # indicates changes for overridden function of sub classes
1010 
1011  def getDistance(self, name, scale=None, keepDefaultRatio=False):
1012  """ Gets the length of the element called 'name'.
1013  """
1014  self.defineDistances(keepDefaultRatio)
1015  if scale == None:
1016  scale = self._scale
1017  elif scale == 1:
1018  scale = 1.0
1019 
1020  if name in self._distances:
1021  #logging.debug(self.__class__.__name__ +": getdistance() - name = '"+ name +"' - "+ str(self._distances[name]))
1022  return self._distances[name] * scale
1023  else:
1024  logging.warning(self.__class__.__name__ +": getdistance() - Unknown distance '"+ name +"'")
1025  return 0
1026 
1027  def width(self):
1028  """ Returns width of this widget.
1029  """
1030  #if self.parent() and self.parent().layout():
1031  # return QWidget.width(self)
1032  return self.getDistance('width')
1033 
1034  def height(self):
1035  """ Returns height of this widget.
1036  """
1037  # TODO: implement this more flexible regarding different QSizePolicies (also width())
1038  #if self.parent() and self.parent().layout():
1039  # return QWidget.height(self)
1040  return self.getDistance('height')
1041 
1042  def isTitlePoint(self, point):
1043  """ Returns True if this point is part of the tile field, otherwise False is returned.
1044  """
1045  if not self.titleIsSet():
1046  return False
1047  if point.y() >= self.getDistance("titleFieldY") and point.y() <= self.getDistance("titleFieldBottom"):
1048  return True
1049  return False
1050 
1051  def defineRectBackgroundShape(self, painter):
1052  """ Draws background for rectangular shape.
1053  """
1054  l = self.getDistance('frameLeft')
1055  t = self.getDistance('frameTop')
1056  r = self.getDistance('frameRight')
1057  b = self.getDistance('frameBottom')
1058  myRect = QRectF(l, t, r - l , b - t)
1059  self._backgroundShapePath = QPainterPath()
1060  self._backgroundShapePath.addRect(myRect)
1061 
1062  def defineCircleBackgroundShape(self, painter):
1063  """ Draws background for circular shape.
1064  """
1065  w = self.width()
1066  h = self.height()
1067  r = min(w, h) - 3 # radius
1068 
1069  self._backgroundShapePath = QPainterPath()
1070  self._backgroundShapePath.addEllipse(0.5 * (w -r), 0.5 * (h -r), r, r)
1071 
1073  """ Draws background for rectangular shape with rounded corners.
1074  """
1075  r = (self.ROUNDRECT_RADIUS) * self._scale
1076  w = self.width()# - 2
1077  h = self.height()# - 2
1078 
1079  w = self.getDistance("frameRight")
1080  h = self.getDistance("frameBottom")
1081  t = self.getDistance("frameTop")
1082  l = self.getDistance("frameLeft")
1083 
1084  # Prevent nasty lines when box too small
1085  f = 0.8
1086  r = min(r, f * h, f * w)
1087 
1088  self._backgroundShapePath = QPainterPath()
1089  self._backgroundShapePath.moveTo(w, r + t)
1090  self._backgroundShapePath.arcTo(w - r, t, r, r, 0, 90)
1091  self._backgroundShapePath.lineTo(r + l, t)
1092  self._backgroundShapePath.arcTo(l, t, r, r, 90, 90)
1093  self._backgroundShapePath.lineTo(l, h - r)
1094  self._backgroundShapePath.arcTo(l, h - r, r, r, 180, 90)
1095  self._backgroundShapePath.lineTo(w - r, h)
1096  self._backgroundShapePath.arcTo(w - r, h - r, r, r, 270, 90)
1097  self._backgroundShapePath.closeSubpath()
1098  self._backgroundShapePath = self._backgroundShapePath.simplified()
1099 
1101  if not hasattr(self._backgroundShapePath, "united"):
1102  logging.warning(self.__class__.__name__ +": defineArrowBackgroundShape() - Upgrade your Qt version at least to 4.3 to use this feature. Aborting...")
1103  return
1104 
1105  #logging.debug(self.__class__.__name__ +":defineArrowBackgroundShape()")
1106 
1107  offset = 0
1108  if self._backgroundShape == "ROUNDRECT":
1109  offset = (self.ROUNDRECT_RADIUS) * self._scale * 0.2
1110  p = self._backgroundShapePath.toFillPolygon()
1111  #print "background shape", [p[i] for i in range(len(p))]
1112  arrowPath = None
1113  if self._arrowShape == self.ARROW_SHAPE_TOP:
1114  arrowPath = QPainterPath()
1115  arrowPath.moveTo(self.getDistance('frameLeft'), self.getDistance('frameTop') + offset)
1116  arrowPath.lineTo(0.5 * self.width(), 1)
1117  arrowPath.lineTo(self.getDistance('frameRight'), self.getDistance('frameTop') + offset)
1118  arrowPath.closeSubpath()
1119  elif self._arrowShape == self.ARROW_SHAPE_RIGHT:
1120  arrowPath = QPainterPath()
1121  arrowPath.moveTo(self.getDistance('frameRight') - offset, self.getDistance('frameTop'))
1122  arrowPath.lineTo(self.width(), 0.5 * self.height())
1123  arrowPath.lineTo(self.getDistance('frameRight') - offset, self.getDistance('frameBottom'))
1124  arrowPath.closeSubpath()
1125  elif self._arrowShape == self.ARROW_SHAPE_BOTTOM:
1126  arrowPath = QPainterPath()
1127  arrowPath.moveTo(self.getDistance('frameLeft'), self.getDistance('frameBottom') - offset)
1128  arrowPath.lineTo(0.5 * self.width(), self.height())
1129  arrowPath.lineTo(self.getDistance('frameRight'), self.getDistance('frameBottom') - offset)
1130  arrowPath.closeSubpath()
1131  elif self._arrowShape == self.ARROW_SHAPE_LEFT:
1132  arrowPath = QPainterPath()
1133  arrowPath.moveTo(self.getDistance('frameLeft') + offset, self.getDistance('frameTop'))
1134  arrowPath.lineTo(1, 0.5 * self.height())
1135  arrowPath.lineTo(self.getDistance('frameLeft') + offset, self.getDistance('frameBottom'))
1136  arrowPath.closeSubpath()
1137 
1138  if arrowPath:
1139  self._backgroundShapePath = arrowPath.united(self._backgroundShapePath).simplified()
1140 
1141  def drawHeaderBackground(self, painter):
1142  """ Color background of title in frame pen color.
1143  """
1144  if not self._colorHeaderBackgroundFlag or self._backgroundShapePath == None:
1145  # Cannot color background
1146  return
1147 
1148  if hasattr(self._backgroundShapePath, "intersected"):
1149  # Available since Qt 4.3
1150  topRectPath = QPainterPath()
1151  topRectPath.addRect(QRectF(0, 0, self.getDistance('width'), self.getDistance('titleFieldBottom')))
1152  headerPath = topRectPath.intersected(self._backgroundShapePath)
1153  painter.setPen(QColor(self.framePenColor))
1154  painter.setBrush(self.framePenColor)
1155  painter.drawPath(headerPath)
1156  return
1157 
1158  # Fallback for Qt versions prior to 4.3
1159 
1160  backgroundShapePolygon = self._backgroundShapePath.toFillPolygon()
1161  headerPolygonPoints = []
1162  i = 0
1163  headerBottom = 0
1164  nearlyBottom = 0
1165  if self.isSelected():
1166  selectedWidth = (self.SELECTED_FRAME_WIDTH +4) * self.scale()
1167  else:
1168  selectedWidth = 0
1169  while i < backgroundShapePolygon.count():
1170  thisP = backgroundShapePolygon.value(i)
1171  i += 1
1172  # selectedWidth prevents horizontal line in SELECT_COLOR
1173  if thisP.y() <= self.getDistance('titleFieldBottom') - selectedWidth:
1174  if thisP.y() > headerBottom:
1175  headerBottom = thisP.y()
1176  elif thisP.y() > nearlyBottom:
1177  nearlyBottom = thisP.y()
1178  headerPolygonPoints.append(thisP)
1179  headerPolygon = QPolygonF(headerPolygonPoints)
1180 
1181  painter.setPen(Qt.NoPen)
1182  painter.setBrush(self.framePenColor)
1183  titleBgPath = QPainterPath()
1184  titleBgPath.addPolygon(headerPolygon)
1185  painter.drawPath(titleBgPath)
1186 
1187  headerBottom = nearlyBottom # test whether second lowest header point works better
1188  if (self._backgroundShape == 'ROUNDRECT' or self._backgroundShape == 'RECT') and headerBottom < self.getDistance('titleFieldBottom'):
1189  #if (headerBottom) < self.getDistance('titleFieldBottom'): # This condition unfortunately does not work correctly on round shapes.
1190  # backgroundShapePolygon does not have a sufficient number of points at the straight lines on the side, so this is a work around.
1191  # This is not a clean solution as most functions should be independent from chosen backgroundShape.
1192  xBorder = 0
1193  if self.isSelected():
1194  # do not paint on frame
1195  if headerBottom == 0:
1196  headerBottom = self.SELECTED_FRAME_WIDTH - 2
1197  xBorder = self.SELECTED_FRAME_WIDTH - 2
1198  #painter.setPen(Qt.NoPen)
1199  painter.setPen(self.framePenColor)
1200  painter.drawRect(QRectF(xBorder, headerBottom, self.width() - 2 * xBorder - 2, self.getDistance('titleFieldBottom') - headerBottom))
1201 
1202  def drawTitle(self, painter):
1203  """ Tells TextField object of title to draw title on widget.
1204  """
1205  if not self.titleIsSet():
1206  return
1207  self.drawHeaderBackground(painter)
1208 
1209  painter.setPen(QPen())
1210  self._titleField.paint(painter, self.getDistance('titleFieldX'), self.getDistance('titleFieldY'), self._scale)
1211 
1212  def drawTextField(self, painter):
1213  """ Tells TextField object of text field to draw title on widget.
1214  """
1215  if not self.textFieldIsSet():
1216  return
1217 
1218  painter.setPen(QPen())
1219  self._textField.paint(painter, self.getDistance('textFieldX'), self.getDistance('textFieldY'), self._scale)
1220 
1221  def drawBody(self, painter):
1222  """ This function calls drawTextField() and drawImage().
1223 
1224  Inheriting classes should overwrite this function if they wish to draw different things or alter the order of drawing.
1225  """
1226  self.drawTextField(painter)
1227  self.drawImage(painter)
1228 
1229  def contentRect(self):
1230  frame_width = 2
1231  if self.isSelected():
1232  frame_width = self.SELECTED_FRAME_WIDTH
1233  return QRect(frame_width, self.getDistance("titleFieldBottom"),
1234  self.width() - 2* frame_width -1,
1235  self.height() - self.getDistance("titleFieldBottom") - frame_width -1)
1236 
1237  def paintEvent(self, event):
1238  """ Reacts on paint event and calls paint() method.
1239  """
1240  #logging.debug(self.__class__.__name__ +": paintEvent()")
1241  painter = QPainter(self)
1242  if isinstance(self.parent(), VispaWidget):
1243  painter.setClipRegion(event.region().intersected(QRegion(self.parent().contentRect().translated(- self.pos()))))
1244  else:
1245  painter.setClipRegion(event.region())
1246 
1247  if self.zoom() > 30:
1248  painter.setRenderHint(QPainter.Antialiasing)
1249  else:
1250  painter.setRenderHint(QPainter.Antialiasing, False)
1251 
1252  self.paint(painter)
1253  ZoomableWidget.paintEvent(self, event)
1254 
1255  def paint(self, painter, event=None):
1256  """ Takes care of painting widget content on given painter.
1257  """
1258  if not self._backgroundGradientEnabledFlag or painter.redirected(painter.device()):
1259  # TODO: find condition which fits QPixmap but not QPicture (pdf export)
1260  # e.q. QPixmap.grabWidget()
1261  backgroundBrush = self.fillColor1
1262  else:
1263  backgroundBrush = QLinearGradient(0, self.getDistance('titleFieldBottom'), 0, self.height())
1264  backgroundBrush.setColorAt(0, self.fillColor1)
1265  backgroundBrush.setColorAt(1, self.fillColor2)
1266 
1267  painter.setPen(self.framePenColor)
1268  painter.pen().setJoinStyle(Qt.RoundJoin)
1269  painter.setBrush(backgroundBrush)
1270 
1271  if self._backgroundShape == 'CIRCLE':
1272  self.defineCircleBackgroundShape(painter)
1273  elif self._backgroundShape == 'ROUNDRECT':
1274  self.defineRoundRectBackgroundShape(painter)
1275  else:
1276  self.defineRectBackgroundShape(painter)
1277 
1278  if self._arrowShape != None:
1280 
1281  painter.drawPath(self._backgroundShapePath)
1282 
1283  self.drawTitle(painter)
1284  self.drawBody(painter)
1285 
1286  if self.isSelected():
1287  # color frame
1288  framePen = QPen(self.SELECT_COLOR)
1289  framePen.setWidth(self.SELECTED_FRAME_WIDTH)
1290  painter.setPen(framePen)
1291  painter.setBrush(Qt.NoBrush)
1292  painter.drawPath(self._backgroundShapePath)
1293 
1294  def setDragReferencePoint(self, pos):
1295  self._dragMouseXrel = pos.x()
1296  self._dragMouseYrel = pos.y()
1297 
1299  return QPoint(self._dragMouseXrel, self._dragMouseYrel)
1300 
1302  self._dragMouseXrel = 0
1303  self._dragMouseYrel = 0
1304 
1305  def mousePressEvent(self, event):
1306  """ Register mouse offset for dragging and calls select().
1307  """
1308  parentIsVispaWidgetOwner = isinstance(self.parent(), VispaWidgetOwner)
1309  if event.modifiers() == Qt.ControlModifier:
1310  # allow deselect of individual widgets in selection
1311  self.select(not self.isSelected(), True)
1312  elif parentIsVispaWidgetOwner and not self.isSelected():
1313  self.select(True)
1314 
1315  if not self._dragableFlag:
1316  return
1317  self._dragMouseXrel = event.x()
1318  self._dragMouseYrel = event.y()
1319 
1320  if parentIsVispaWidgetOwner:
1321  self.parent().initWidgetMovement(self)
1322 
1323  def mouseMoveEvent(self, event):
1324  """ Call dragWidget().
1325  """
1326  #logging.debug("%s: mouseMoveEvent()" % self.__class__.__name__)
1327  if bool(event.buttons() & Qt.LeftButton):
1328  self.dragWidget(self.mapToParent(event.pos()))
1329  return
1330 
1331  def mouseReleaseEvent(self, event):
1332  #logging.debug("%s: mouseReleaeEvent()" % self.__class__.__name__)
1333  if self._dragMouseXrel != 0 and self._dragMouseYrel != 0:
1334  self.resetMouseDragOffset()
1335  self.emit(SIGNAL("dragFinished"))
1336 
1337  def keyPressEvent(self, event):
1338  """ Calls delete() method if backspace or delete key is pressed when widget has focus.
1339  """
1340  if event.key() == Qt.Key_Backspace or event.key() == Qt.Key_Delete:
1341  if hasattr(self.parent(), "multiSelectEnabled") and self.parent().multiSelectEnabled() and \
1342  hasattr(self.parent(), "selectedWidgets") and len(self.parent().selectedWidgets()) > 1:
1343  # let parent handle button event if multi-select is enabled
1344  self.parent().setFocus(Qt.OtherFocusReason)
1345  QCoreApplication.instance().sendEvent(self.parent(), event)
1346  else:
1347  self.emit(SIGNAL("deleteButtonPressed"))
1348  self.delete()
1349  else:
1350  # let parent handle all other events
1351  self.parent().setFocus(Qt.OtherFocusReason)
1352  QCoreApplication.instance().sendEvent(self.parent(), event)
1353 
1354  def delete(self):
1355  """ Deletes this widget.
1356  """
1357  if not self.isDeletable():
1358  logging.warning(self.__class__.__name__ +": delete() - Tried to remove undeletable widget. Aborting...")
1359  return False
1360 
1361  if isinstance(self.parent(), VispaWidgetOwner):
1362  self.parent().widgetAboutToDelete(self)
1363 
1364  self.deleteLater()
1365  self.emit(SIGNAL("widgetDeleted"))
1366  return True
1367 
1368  def setPreviousDragPosition(self, position):
1369  self._previusDragPosition = position
1370 
1372  """ Returns position from before previous drag operation.
1373 
1374  E.g. used for undo function.
1375  """
1376  #print "VispaWidget.previousDragPosition()", self._previusDragPosition
1377  return self._previusDragPosition
1378 
1379  def dragWidget(self, pPos):
1380  """ Perform dragging when user moves cursor while left mouse button is pressed.
1381  """
1382  if not self._dragableFlag:
1383  return
1384 
1385  self._previusDragPosition = self.pos()
1386  #pPos = self.mapToParent(event.pos())
1387  self.move(max(0,pPos.x() - self._dragMouseXrel), max(0,pPos.y() - self._dragMouseYrel))
1388 
1389  # Tell parent, a widget moved to trigger file modification.
1390  if isinstance(self.parent(), VispaWidgetOwner):
1391  self.parent().widgetDragged(self)
1392 
1393  def move(self, *target):
1394  """ Move widgt to new position.
1395 
1396  You can either give x and y coordinates as parameters or a QPosition object.
1397  """
1398  if len(target) == 1:
1399  # Got point as argument
1400  targetX = target[0].x()
1401  targetY = target[0].y()
1402  else:
1403  # Got x and y as arguments
1404  targetX = target[0]
1405  targetY = target[1]
1406 
1407  self._unzoomedPositionX = 1.0 * targetX / self.scale()
1408  self._unzoomedPositionY = 1.0 * targetY / self.scale()
1409  # In self.setZoome() the widgets position can be set with these values
1410 
1411  QWidget.move(self, targetX, targetY)
1412 
1413  #import traceback
1414  #traceback.print_stack()
1415 
1416  def setImage(self, image):
1417  """ The given image will be shown in the centre of the widget.
1418 
1419  Currently supported image types are QPixmap and QSvgRenderer.
1420  """
1421  self._image = image
1422 
1423  def drawImage(self, painter):
1424  """ Draws image onto the widget's centre. See setImage().
1425  """
1426  if not self._image:
1427  return
1428 
1429  if isinstance(self._image, QSvgRenderer):
1430  #rect = self.imageRectF(self._image.defaultSize().width() * self.scale(), self._image.defaultSize().height() * self.scale())
1431  self._image.render(painter, self.imageRectF())
1432  elif isinstance(self._image, QPixmap):
1433  #rect = self.imageRectF(self._image.width() * self.scale(), self._image.height() * self.scale())
1434  painter.drawPixmap(self.imageRectF(), self._image, QRectF(self._image.rect()))
1435 
1436  # debug
1437  #painter.drawRect(self.imageRectF())
1438 
1439  def imageSizeF(self):
1440  """ Returns QSizeF object representing the unzoomed size of the image. See setImage().
1441  """
1442  if not self._image:
1443  return QSizeF(0, 0)
1444  if isinstance(self._image, QSvgRenderer):
1445  return QSizeF(self._image.defaultSize())
1446  if isinstance(self._image, QPixmap):
1447  return QSizeF(self._image.size())
1448  logging.warning(self.__class__.__name__ +": imageSizeF() - Unknown image type.")
1449  return QSizeF(0, 0)
1450 
1451  def imageRectF(self, width=None, height=None):
1452  """ Returns draw area as QRectF for drawImage.
1453  """
1454  if not width or not height:
1455  size = self.imageSizeF() * self.scale()
1456  width = size.width()
1457  height = size.height()
1458 
1459  if width > self.width() or height > self.height():
1460  widthScale = 1.0 * self.width() / width
1461  heightScale = 1.0 *self.height() / height
1462  scale = min(widthScale, heightScale)
1463  width *= scale
1464  height *= scale
1465 
1466  rect = QRectF((self.width() - width) * 0.5, (self.height() - height + self.getDistance("titleFieldBottom")) * 0.5, width, height)
1467  #print "rect ", rect
1468  return rect
1469 
1470  def boundingRect(self):
1471  return QRect(self.x(), self.y(), self.width(), self.height())
1472 
1473  def setBodyWidget(self, widget):
1474  """ Accepts any QWidget and displays into the body section.
1475 
1476  The body section is below the header.
1477  """
1478  self._bodyWidget = widget
1479  self._bodyWidget.setParent(self)
1480  if self.isVisible():
1481  self._bodyWidget.setVisible(True)
1483 
1484  def bodyWidget(self):
1485  """ Returns body widget if there is one or None otherwise.
1486  """
1487  return self._bodyWidget
def setAutotruncate(self, auto)
Definition: VispaWidget.py:63
def autosize(self, skipSizeHint=False)
Definition: VispaWidget.py:832
def paint(self, painter, event=None)
def setDefaultWidth(self, width)
Definition: VispaWidget.py:129
def setAutosizeFont(self, auto)
Definition: VispaWidget.py:54
def defineRoundRectBackgroundShape(self, painter)
def getDrawRect(self, scale=1)
Definition: VispaWidget.py:121
def getDistance(self, name, scale=None, keepDefaultRatio=False)
def setPenColor(self, color)
Definition: VispaWidget.py:92
def setPreviousDragPosition(self, position)
def enableColorHeaderBackground(self, enable=True)
Definition: VispaWidget.py:609
def enableAutopositionizeWhenZooming(self, auto)
Definition: VispaWidget.py:509
def defineCircleBackgroundShape(self, painter)
def setDefaultFontSize(self, fontSize)
Definition: VispaWidget.py:108
def enableBackgroundGradient(self, enable=True)
Definition: VispaWidget.py:619
def setTextFieldAutotruncateText(self, auto)
Definition: VispaWidget.py:751
def setAutoscale(self, auto, keepAspectRatio)
Definition: VispaWidget.py:72
def paint(self, painter, xPos, yPos, scale=1)
Definition: VispaWidget.py:316
def setSelectable(self, selectable)
Definition: VispaWidget.py:588
def defineDistances(self, keepDefaultRatio=False)
Definition: VispaWidget.py:924
T min(T a, T b)
Definition: MathUtil.h:58
def imageRectF(self, width=None, height=None)
def getFontHeight(self, fm=None)
Definition: VispaWidget.py:98
def setArrowShape(self, direction)
Definition: VispaWidget.py:547
def setDeletable(self, deleteable)
Definition: VispaWidget.py:603
def mouseDoubleClickEvent(self, event)
Definition: VispaWidget.py:650
def setDragable(self, dragable, recursive=False)
Definition: VispaWidget.py:516
def drawTextField(self, painter)
def noRearangeContent(self, no=True)
Definition: VispaWidget.py:895
def resize(self, width, height)
Definition: VispaWidget.py:827
def select(self, sel=True, multiSelect=False)
Definition: VispaWidget.py:629
def setDefaultHeight(self, height)
Definition: VispaWidget.py:134
def setTextFieldAutosizeFont(self, auto)
Definition: VispaWidget.py:744
def enableAutosizing(self, auto, keepAspectRatio=True)
Definition: VispaWidget.py:559
def defineRectBackgroundShape(self, painter)
def drawHeaderBackground(self, painter)
def setColors(self, penColor, fillColor1, fillColor2)
Definition: VispaWidget.py:530
def __init__(self, parent=None)
Definition: VispaWidget.py:415
def setFontSizeRange(self, minFontSize, maxFontSize)
Definition: VispaWidget.py:113
def setOutputFlags(self, flags)
Definition: VispaWidget.py:177