CMS 3D CMS Logo

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