5 from PyQt4.QtSvg
import QSvgRenderer
13 """ TextField for VispaWidget.
15 Text and title shown in VispaWidget are TextField object.
55 """ Sets autosizeFontFlag.
57 If flag is True and text does not fit in its given area the font size will be reduced to fit.
64 """ Sets autoTruncateTextFlag.
66 If flag is True the text will be truncated if it is too long to fit in given space.
73 """ Sets autoscale and autoscalKeepAspectRatio flags.
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.
83 """ Sets font and if default height is not yet set default height will be set to font height.
99 """ Calculates font height for given font metrics object.
101 If no font metrics object is given one will be created for the current TextField font.
104 fm = QFontMetrics(self.
_font)
109 """ Sets preferred font size.
114 """ Sets min and max font point size for autosize font capability.
116 See setAutosizeFont().
124 Width will be equal to getWidth() and height equal to getHeight().
130 """ Sets preferred width for text output.
135 """ Sets preferred height for text output.
140 """ Calculates the space (width and height) needed to display text.
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.
146 See setAutosizeFont(), setAutotruncate(), setAutoscale(), setDefaultWidth(), setDefaultHeight().
167 """ Returns width calculated by calculateDimensions().
172 """ Returns height calculated by calculateDimensions().
178 """ Set Qt output flags for drawing text.
183 """ Returns set output flags.
188 """ Returns True if text was truncated.
195 """ Returns the font size the text will be drawn in.
207 """ Returns short version of text if it was truncated.
212 """ Evaluates whether the string was truncated or not.
214 If truncated it returns short version, else the whole text.
221 """ Returns True if no text or empty string is set.
228 """ Adjusts values for getWidth() and getHeight() so whole text fits in.
231 fm = QFontMetrics(self.
_font)
235 widthFits = heightFits =
False
240 self.
_width = neededRect.width()
241 self.
_height = neededRect.height()
244 while not widthFits
or not heightFits:
255 if neededRect.width() <= self.
_width:
258 if neededRect.height() <= self.
_height:
263 """ Decreases font so text fits in given widht and height.
265 if self.
_font ==
None:
266 logging.error(
"TextField.autosizeFont() - ERROR: 'font' not set, can't calculate font size")
273 font.setPointSizeF(size + 0.1 * decSize)
274 fm = QFontMetricsF(font)
276 if neededRect.width() > drawRect.width()
or neededRect.height() > drawRect.height():
280 for decSize
in range(0, 10):
281 font.setPointSizeF(size + 0.1 * decSize)
282 fm = QFontMetricsF(font)
284 if neededRect.width() > drawRect.width()
or neededRect.height() > drawRect.height():
292 """ Truncates text if it does not fit in given space.
295 text = QString(self.
_text)
299 fm = QFontMetricsF(font)
301 patterns = text.split(QRegExp(
'\\b'))
303 for pattern
in patterns:
304 short.append(pattern)
305 neededRect = fm.boundingRect(drawRect, self.
_outputFlags, short)
307 if neededRect.width() > drawRect.width()
or neededRect.height() > drawRect.height():
309 counter += len(pattern)
311 if counter < len(text):
316 def paint(self, painter, xPos, yPos, scale=1):
317 """ Draws text on given painter at given position.
319 If scale is given the text will be scaled accordingly.
324 painter.setBrush(Qt.NoBrush)
327 painter.setFont(self.
_font)
338 """ Class supporting random shapes, title and text field.
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().
351 ROUNDRECT_RADIUS = 30
352 BACKGROUND_SHAPE =
'RECT'
358 HORIZONTAL_INNER_MARGIN = 5
359 VERTICAL_INNTER_MARGIN = 5
377 PEN_COLOR = QColor(128, 186, 224)
378 FILL_COLOR1 = QColor(188, 215, 241)
379 FILL_COLOR2 = QColor(242, 230, 242)
380 TITLE_COLOR = QColor(Qt.white)
386 SELECT_COLOR = QColor(
'darkblue')
388 SELECTABLE_FLAG =
True
389 FOCUSPOLICY = Qt.StrongFocus
390 SELECTED_FRAME_WIDTH = 2
392 AUTOPOSITIONIZE_WHEN_ZOOMING_FLAG =
True
394 TITLEFIELD_FONTSIZE = 12
395 COLOR_HEADER_BACKGROUND_FLAG =
True
396 USE_BACKGROUND_GRADIENT_FLAG =
True
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
406 AUTOSIZE_KEEP_ASPECT_RATIO =
True
411 ARROW_SHAPE_BOTTOM = 2
412 ARROW_SHAPE_RIGHT = 3
460 ZoomableWidget.__init__(self, parent)
477 """ Returns x coordinate the widget would have if zoom was set to 100%.
483 """ Returns x coordinate the widget would have if zoom was set to 100%.
489 """ Return scale factor of widget.
494 """ Sets widget's zoom.
496 ZoomableWidget.setZoom(self, zoom)
499 ZoomableWidget.resize(self, self.
width(), self.
height())
505 """ Returns pen color for this widget.
510 """ If True the position of this widget will be corrected according to it unzoomed position.
512 Prevents unexpected moving when zoom is enabled due to rounding errors.
517 """ If True the widget can be dragged using a pointing device.
519 If recursive is True also dragablitiy of all children will be set.
523 for child
in self.children():
524 if isinstance(child, VispaWidget):
525 child.setDragable(dragable,
True)
531 """ Sets colors of this widget.
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.
541 """ Sets shape of this widget.
543 Right now supported 'RECT' (default), 'ROUNDRECT', 'CIRCLE'.
548 """ In addition to normal shape this gives whole widget the shape of an arrow.
550 Possible values for direction are
560 """ Sets flag for auto resizing this widget.
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.
573 self._titleField.setAutoscale(auto, keepAspectRatio)
575 self._textField.setAutoscale(auto, keepAspectRatio)
582 """ Returns True if auto resizing is enabled.
584 See enableAutosizing().
589 """ Makes widget selectable if True.
596 self.setFocusPolicy(Qt.NoFocus)
599 """ Returns True if widget can be selected.
610 """ If set to True the background of the header is painted using pen color.
620 """ If set to True the background color is painted using a QLinearGradient with the two fill colors.
629 def select(self, sel=True, multiSelect=False):
630 """ Marks this widget as selected and informs parent if parent is VispaWidetOwner.
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.
647 if (multiSelect
or self.
isSelected())
and isinstance(self.parent(), VispaWidgetOwner):
648 self.parent().widgetSelected(self, multiSelect)
651 if isinstance(self.parent(), VispaWidgetOwner):
652 self.parent().widgetDoubleClicked(self)
655 """ Returns True if widget is selected.
663 """ Initializes title field.
665 Sets default flags for title field.
669 self._titleField.setDefaultWidth(self.
getDistance(
'titleFieldWidth', 1,
True))
671 self._titleField.setAutotruncate(
False)
677 """ Returns True if test field has been set to a non empty string.
679 if self.
_titleField !=
None and not self._titleField.empty():
687 self._titleField.setFont(self.font())
688 self._titleField.setText(title)
697 return self._titleField.text()
704 self._textField.setDefaultWidth(self.
getDistance(
'textFieldWidth', 1,
True))
705 self._textField.setDefaultHeight(self.
getDistance(
'textFieldHeight', 1,
True))
713 """ Returns True if text field text has been set to an non empty string.
715 if self.
_textField !=
None and not self._textField.empty():
720 """ Sets text for text field.
724 self._textField.setFont(self.font())
725 self._textField.setText(text)
729 self.setToolTip(self._textField.text())
733 """ Returns TextField object belonging to text field.
738 """ Returns text of text field.
741 return self._textField.text()
745 """ Sets auto resize flag of text field.
748 self._textField.setAutosizeFont(auto)
752 """ Sets auto truncate flag of text field.
755 self._textField.setAutotruncate(auto)
759 QWidget.setMaximumSize(self, *attr)
763 QWidget.setMinimumSize(self, *attr)
767 """ Calculates needed space for widget content.
782 titleFieldWidth = self.
getDistance(
'titleFieldWidth', 1)
783 titleFieldHeight += self.
getDistance(
'titleFieldHeight', 1)
788 textFieldWidth = self._textField.getWidth()
789 textFieldHeight += self._textField.getHeight()
793 if self._bodyWidget.parent() != self:
796 sh = self._bodyWidget.sizeHint()
797 bodyWidgetWidth = sh.width()
798 bodyWidgetHeight = sh.height()
801 bodyWidth =
max(textFieldWidth, bodyWidgetWidth, imageSizeF.width())
802 bodyHeight =
max(textFieldHeight, bodyWidgetHeight, imageSizeF.height())
804 if titleIsSet
and bodyHeight != 0:
806 neededHeight += self.
getDistance(
'bottomMargin', 1)
808 neededWidth +=
max(bodyWidth, titleFieldWidth)
809 neededHeight += titleFieldHeight + bodyHeight
812 maxWidth = self.maximumSize().
width()
813 maxHeight = self.maximumSize().
height()
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:
821 scale =
min(maxScaleWidth, maxScaleHeight)
823 neededHeight *= scale
825 return QSize(
max(self.minimumSize().
width(), neededWidth),
max(self.minimumSize().
height(), neededHeight))
830 ZoomableWidget.resize(self, self.
width(), self.
height())
833 """ Calculates scale factors and resizes widget accordingly.
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.
842 neededWidth = neededSpace.width()
843 neededHeight = neededSpace.height()
855 ZoomableWidget.resize(self, self.
width(), self.
height())
862 """ Checks which components have to be regarded for size calculation.
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.
876 self._textField.setDefaultWidth(self.
getDistance(
'textFieldWidth', 1,
True))
877 self._textField.setDefaultHeight(self.
getDistance(
'textFieldHeight', 1,
True))
878 self._textField.calculateDimensions()
880 self._titleField.setDefaultWidth(self.
getDistance(
'titleFieldWidth', 1,
True))
881 self._titleField.setDefaultHeight(self.
getDistance(
'titleFieldHeight', 1,
True))
882 self._titleField.calculateDimensions()
889 """ Sets distancesHaveToBeRecalculatedFlag to True.
891 Next time defineDistances() is called distances are recalculated even if zoom has not changed.
896 """ Flag disables any rearanging.
901 """ Makes sure rearangeContent() will be called next time a distance is requested.
903 See rearangeContent().
908 """ Calls rearangeContent() if needed.
917 ZoomableWidget.showEvent(self, event)
920 """ Returns dictionary containing distances as defined in defineDistances().
925 """ Defines supported distances.
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()).
988 self.
_distances[
'titleFieldWidth'] = self._titleField.getWidth() * scale
989 self.
_distances[
'titleFieldHeight'] = self._titleField.getHeight() * scale
1002 self.
_distances[
'textFieldWidth'] = self._textField.getWidth() * scale
1003 self.
_distances[
'textFieldHeight'] = self._textField.getHeight() * scale
1012 """ Gets the length of the element called 'name'.
1024 logging.warning(self.__class__.__name__ +
": getdistance() - Unknown distance '"+ name +
"'")
1028 """ Returns width of this widget.
1035 """ Returns height of this widget.
1043 """ Returns True if this point is part of the tile field, otherwise False is returned.
1052 """ Draws background for rectangular shape.
1058 myRect = QRectF(l, t, r - l , b - t)
1060 self._backgroundShapePath.addRect(myRect)
1063 """ Draws background for circular shape.
1070 self._backgroundShapePath.addEllipse(0.5 * (w -r), 0.5 * (h -r), r, r)
1073 """ Draws background for rectangular shape with rounded corners.
1086 r =
min(r, f * h, f * w)
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()
1102 logging.warning(self.__class__.__name__ +
": defineArrowBackgroundShape() - Upgrade your Qt version at least to 4.3 to use this feature. Aborting...")
1110 p = self._backgroundShapePath.toFillPolygon()
1114 arrowPath = QPainterPath()
1116 arrowPath.lineTo(0.5 * self.
width(), 1)
1118 arrowPath.closeSubpath()
1120 arrowPath = QPainterPath()
1122 arrowPath.lineTo(self.
width(), 0.5 * self.
height())
1124 arrowPath.closeSubpath()
1126 arrowPath = QPainterPath()
1128 arrowPath.lineTo(0.5 * self.
width(), self.
height())
1130 arrowPath.closeSubpath()
1132 arrowPath = QPainterPath()
1134 arrowPath.lineTo(1, 0.5 * self.
height())
1136 arrowPath.closeSubpath()
1142 """ Color background of title in frame pen color.
1150 topRectPath = QPainterPath()
1155 painter.drawPath(headerPath)
1160 backgroundShapePolygon = self._backgroundShapePath.toFillPolygon()
1161 headerPolygonPoints = []
1169 while i < backgroundShapePolygon.count():
1170 thisP = backgroundShapePolygon.value(i)
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)
1181 painter.setPen(Qt.NoPen)
1183 titleBgPath = QPainterPath()
1184 titleBgPath.addPolygon(headerPolygon)
1185 painter.drawPath(titleBgPath)
1187 headerBottom = nearlyBottom
1195 if headerBottom == 0:
1200 painter.drawRect(QRectF(xBorder, headerBottom, self.
width() - 2 * xBorder - 2, self.
getDistance(
'titleFieldBottom') - headerBottom))
1203 """ Tells TextField object of title to draw title on widget.
1209 painter.setPen(QPen())
1213 """ Tells TextField object of text field to draw title on widget.
1218 painter.setPen(QPen())
1222 """ This function calls drawTextField() and drawImage().
1224 Inheriting classes should overwrite this function if they wish to draw different things or alter the order of drawing.
1233 return QRect(frame_width, self.
getDistance(
"titleFieldBottom"),
1234 self.
width() - 2* frame_width -1,
1238 """ Reacts on paint event and calls paint() method.
1241 painter = QPainter(self)
1242 if isinstance(self.parent(), VispaWidget):
1243 painter.setClipRegion(event.region().intersected(QRegion(self.parent().
contentRect().translated(- self.pos()))))
1245 painter.setClipRegion(event.region())
1247 if self.
zoom() > 30:
1248 painter.setRenderHint(QPainter.Antialiasing)
1250 painter.setRenderHint(QPainter.Antialiasing,
False)
1253 ZoomableWidget.paintEvent(self, event)
1256 """ Takes care of painting widget content on given painter.
1263 backgroundBrush = QLinearGradient(0, self.
getDistance(
'titleFieldBottom'), 0, self.
height())
1264 backgroundBrush.setColorAt(0, self.
fillColor1)
1265 backgroundBrush.setColorAt(1, self.
fillColor2)
1268 painter.pen().setJoinStyle(Qt.RoundJoin)
1269 painter.setBrush(backgroundBrush)
1290 painter.setPen(framePen)
1291 painter.setBrush(Qt.NoBrush)
1306 """ Register mouse offset for dragging and calls select().
1308 parentIsVispaWidgetOwner = isinstance(self.parent(), VispaWidgetOwner)
1309 if event.modifiers() == Qt.ControlModifier:
1312 elif parentIsVispaWidgetOwner
and not self.
isSelected():
1320 if parentIsVispaWidgetOwner:
1321 self.parent().initWidgetMovement(self)
1324 """ Call dragWidget().
1327 if bool(event.buttons() & Qt.LeftButton):
1328 self.
dragWidget(self.mapToParent(event.pos()))
1335 self.emit(SIGNAL(
"dragFinished"))
1338 """ Calls delete() method if backspace or delete key is pressed when widget has focus.
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:
1344 self.parent().setFocus(Qt.OtherFocusReason)
1345 QCoreApplication.instance().sendEvent(self.parent(), event)
1347 self.emit(SIGNAL(
"deleteButtonPressed"))
1351 self.parent().setFocus(Qt.OtherFocusReason)
1352 QCoreApplication.instance().sendEvent(self.parent(), event)
1355 """ Deletes this widget.
1358 logging.warning(self.__class__.__name__ +
": delete() - Tried to remove undeletable widget. Aborting...")
1361 if isinstance(self.parent(), VispaWidgetOwner):
1362 self.parent().widgetAboutToDelete(self)
1365 self.emit(SIGNAL(
"widgetDeleted"))
1372 """ Returns position from before previous drag operation.
1374 E.g. used for undo function.
1380 """ Perform dragging when user moves cursor while left mouse button is pressed.
1390 if isinstance(self.parent(), VispaWidgetOwner):
1391 self.parent().widgetDragged(self)
1394 """ Move widgt to new position.
1396 You can either give x and y coordinates as parameters or a QPosition object.
1398 if len(target) == 1:
1400 targetX = target[0].
x()
1401 targetY = target[0].
y()
1411 QWidget.move(self, targetX, targetY)
1417 """ The given image will be shown in the centre of the widget.
1419 Currently supported image types are QPixmap and QSvgRenderer.
1424 """ Draws image onto the widget's centre. See setImage().
1429 if isinstance(self.
_image, QSvgRenderer):
1431 self._image.render(painter, self.
imageRectF())
1432 elif isinstance(self.
_image, QPixmap):
1434 painter.drawPixmap(self.
imageRectF(), self.
_image, QRectF(self._image.rect()))
1440 """ Returns QSizeF object representing the unzoomed size of the image. See setImage().
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.")
1452 """ Returns draw area as QRectF for drawImage.
1454 if not width
or not height:
1456 width = size.width()
1457 height = size.height()
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)
1466 rect = QRectF((self.
width() - width) * 0.5, (self.
height() - height + self.
getDistance(
"titleFieldBottom")) * 0.5, width, height)
1471 return QRect(self.x(), self.y(), self.
width(), self.
height())
1474 """ Accepts any QWidget and displays into the body section.
1476 The body section is below the header.
1479 self._bodyWidget.setParent(self)
1480 if self.isVisible():
1481 self._bodyWidget.setVisible(
True)
1485 """ Returns body widget if there is one or None otherwise.
_autoscaleKeepAspectRatioFlag
const T & max(const T &a, const T &b)