--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/E5Gui/E5MapWidget.py Tue Mar 04 19:52:06 2014 +0100 @@ -0,0 +1,290 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2014 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a base class for showing a document map. +""" + +from PyQt4.QtCore import Qt, QSize, QRect +from PyQt4.QtGui import QWidget, QAbstractScrollArea, QColor, QBrush, QPainter + + +class E5MapWidget(QWidget): + """ + Class implementing a base class for showing a document map. + """ + def __init__(self, parent=None): + """ + Constructor + + @param parent reference to the parent widget (QWidget) + """ + super().__init__(parent) + self.setAttribute(Qt.WA_OpaquePaintEvent) + + self.__width = 12 + self.__lineBorder = 2 + self.__lineHeight = 2 + self.__backgroundColor = QColor(Qt.lightGray).lighter(120) + self.__sliderBorderColor = QColor(Qt.black) + self.__sliderBackgroundColor = QColor(Qt.white) + + self.__master = None + self.__enabled = False + + if parent is not None and isinstance(parent, QAbstractScrollArea): + self.setMaster(parent) + + def __updateMasterViewportWidth(self): + """ + Private method to update the master's viewport width. + """ + if self.__master: + if self.__enabled: + width = self.__width + else: + width = 0 + self.__master.setViewportMargins(0, 0, width, 0) + + def setMaster(self, master): + """ + Public method to set the map master widget. + + @param master map master widget (QAbstractScrollArea) + """ + self.__master = master + self.__master.verticalScrollBar().valueChanged.connect(self.repaint) + self.__updateMasterViewportWidth() + + def setWidth(self, width): + """ + Public method to set the widget width. + + @param width widget width (integer) + """ + self.__width = width + + def width(self): + """ + Public method to get the widget's width. + + @return widget width (integer) + """ + return self.__width + + def setLineDimensions(self, border, height): + """ + Public method to set the line (indicator) dimensions. + + @param border border width on each side in x-direction (integer) + @param height height of the line in pixels (integer) + """ + self.__lineBorder = border + self.__lineHeight = max(2, height) # min height is 2 pixels + + def lineDimensions(self): + """ + Public method to get the line (indicator) dimensions. + + @return tuple with border width (integer) and line height (integer) + """ + return self.__lineBorder, self.__lineHeight + + def setEnabled(self, enable): + """ + Public method to set the enabled state. + + @param enable flag indicating the enabled state (boolean) + """ + self.__enabled = enable + self.setVisible(enable) + self.__updateMasterViewportWidth() + + def isEnabled(self): + """ + Public method to check the enabled state. + + @return flag indicating the enabled state (boolean) + """ + return self.__enabled + + def setBackgroundColor(self, color): + """ + Public method to set the widget background color. + + @param color color for the background (QColor) + """ + self.__backgroundColor = color + + def backgroundColor(self): + """ + Public method to get the background color. + + @return background color (QColor) + """ + return QColor(self.__backgroundColor) + + def setSliderColors(self, border, background): + """ + Public method to set the slider colors. + + @param border border color (QColor) + @param background background color (QColor) + """ + self.__sliderBorderColor = border + self.__sliderBackgroundColor = background + + def sliderColors(self): + """ + Public method to get the slider colors. + + @return tuple with the slider's border color (QColor) and + background color (QColor) + """ + return (QColor(self.__sliderBorderColor), + QColor(self.__sliderBackgroundColor)) + + def sizeHint(self): + """ + Public method to give an indication about the preferred size. + + @return preferred size (QSize) + """ + return QSize(self.__width, 0) + + def paintEvent(self, event): + """ + Protected method to handle a paint event. + + @param event paint event (QPaintEvent) + """ + # step 1: fill the whole painting area + painter = QPainter(self) + painter.fillRect(event.rect(), self.__backgroundColor) + + # step 2: paint the indicators + self._paintIt(painter) + + # step 3: paint the slider + if self.__master: + penColor = self.__sliderBorderColor + penColor.setAlphaF(0.8) + painter.setPen(penColor) + brushColor = self.__sliderBackgroundColor + brushColor.setAlphaF(0.5) + painter.setBrush(QBrush(brushColor)) + painter.drawRect(self.__generateSliderRange( + self.__master.verticalScrollBar())) + + def _paintIt(self, painter): + """ + Protected method for painting the widget's indicators. + + Note: This method should be implemented by subclasses. + + @param painter reference to the painter object (QPainter) + """ + pass + + def mousePressEvent(self, event): + """ + Protected method to handle a mouse button press. + + @param event mouse event (QMouseEvent) + """ + if event.button() == Qt.LeftButton and self.__master: + vsb = self.__master.verticalScrollBar() + value = self.position2Value(event.pos().y() - 1) + vsb.setValue(value - 0.5 * vsb.pageStep()) # center on page + + def calculateGeometry(self): + """ + Public method to recalculate the map widget's geometry. + """ + if self.__master: + cr = self.__master.contentsRect() + vsb = self.__master.verticalScrollBar() + if vsb.isVisible(): + vsbw = vsb.contentsRect().width() + else: + vsbw = 0 + left, top, right, bottom = self.__master.getContentsMargins() + if right > vsbw: + vsbw = 0 + self.setGeometry(QRect(cr.right() - self.__width - vsbw, cr.top(), + self.__width, cr.height())) + + def scaleFactor(self, slider=False): + """ + Public method to determine the scrollbar's scale factor. + + @param slider flag indicating to calculate the result for the slider + (boolean) + @return scale factor (float) + """ + if self.__master: + delta = 0 if slider else 2 + vsb = self.__master.verticalScrollBar() + posHeight = vsb.height() - delta - 1 + valHeight = vsb.maximum() - vsb.minimum() + vsb.pageStep() + return posHeight / valHeight + else: + return 1.0 + + def value2Position(self, value, slider=False): + """ + Public method to convert a scrollbar value into a position. + + @param value value to convert (integer) + @param slider flag indicating to calculate the result for the slider + (boolean) + @return position (integer) + """ + if self.__master: + offset = 0 if slider else 1 + vsb = self.__master.verticalScrollBar() + return (value - vsb.minimum()) * self.scaleFactor(slider) + offset + else: + return value + + def position2Value(self, position, slider=False): + """ + Public method to convert a position into a scrollbar value. + + @param position scrollbar position to convert (integer) + @param slider flag indicating to calculate the result for the slider + (boolean) + @return scrollbar value (integer) + """ + if self.__master: + offset = 0 if slider else 1 + vsb = self.__master.verticalScrollBar() + return vsb.minimum() + max( + 0, (position - offset) / self.scaleFactor(slider)) + else: + return position + + def generateIndicatorRect(self, position): + """ + Public method to generate an indicator rectangle. + + @param position indicator position (integer) + @return indicator rectangle (QRect) + """ + return QRect(self.__lineBorder, position - self.__lineHeight // 2, + self.__width - self.__lineBorder, self.__lineHeight) + + def __generateSliderRange(self, scrollbar): + """ + Private method to generate the slider rectangle. + + @param scrollbar reference to the vertical scrollbar (QScrollBar) + @return slider rectangle (QRect) + """ + pos1 = self.value2Position(scrollbar.value(), slider=True) + pos2 = self.value2Position(scrollbar.value() + scrollbar.pageStep(), + slider=True) + return QRect(1, pos1, self.__width - 2, pos2 - pos1 + 1) + # TODO: check slider appearance and adjust to self.__width