def paint(self, painter, option, index):
        """Performs custom painting of value of data in the model and decorations.

         Performs custom painting of value of data in the model at the specified index
         plus any decorations used in that column.

         Args:
            painter - QPainter
            option - QStyleOptionViewItem
            index - QModelIndex
        """
        xOffset = 0
        # First added for #15, the painting of custom amount information.  This can
        # be used as a pattern for painting any column of information.
        value_painter = self._get_value_painter(index)
        self._display_text = value_painter is None
        QStyledItemDelegate.paint(self, painter, option, index)
        if value_painter is not None:
            value_option = QStyleOptionViewItem(option)
            rect = value_option.rect
            rect = QRect(rect.left(), rect.top(), rect.width() - xOffset, rect.height())
            value_option.rect = rect
            value_painter.paint(painter, value_option, index)

        decorations = self._get_decorations(index, bool(option.state & QStyle.State_Selected))
        for dec in decorations:
            pixmap = dec.pixmap
            x = option.rect.right() - pixmap.width() - xOffset
            y = option.rect.center().y() - (pixmap.height() // 2)
            rect = QRect(x, y, pixmap.width(), pixmap.height())
            painter.drawPixmap(rect, pixmap)
            xOffset += pixmap.width()
示例#2
0
    def paint(self, painter, option, index):
        extra = index.data(Qt.UserRole + 1)
        if not extra:
            return QStyledItemDelegate.paint(self, painter, option, index)

        else:
            if option.state & QStyle.State_Selected:
                painter.fillRect(option.rect, option.palette.color(QPalette.Inactive, QPalette.Highlight))

            title = index.data()
            extra = " - {}".format(extra)
            painter.drawText(option.rect, Qt.AlignLeft, title)

            fm = QFontMetrics(option.font)
            w = fm.width(title)
            r = QRect(option.rect)
            r.setLeft(r.left() + w)
            painter.save()
            if option.state & QStyle.State_Selected:
                painter.setPen(QColor(S.highlightedTextLight))
            else:
                painter.setPen(QColor(S.textLight))

            painter.drawText(r, Qt.AlignLeft, extra)
            painter.restore()
示例#3
0
def applyMargin(rect, margin):
    result = QRect(rect)
    result.setLeft(result.left()+margin)
    result.setRight(result.right()-margin)
    result.setTop(result.top()+margin)
    result.setBottom(result.bottom()-margin)
    return result
示例#4
0
 def merge(self, pos, layer):
     # Determine the overlapping area
     area = QRect(pos, QSize(layer.width(), layer.height()))
     area &= QRect(0, 0, self.width(), self.height())
     for y in range(area.top(), area.bottom()+1):
         for x in range(area.left(), area.right()+1):
             cell = layer.cellAt(x - pos.x(), y - pos.y())
             if (not cell.isEmpty()):
                 self.setCell(x, y, cell)
 def scene_item_rects_updated(self, items):
     """The user moved or resized items in the scene
     """
     debug_print('GraphicsItemView.item_rects_updated')
     for index, item in zip(self.indexes_of_items(items), items):
         # item.sceneBoundingRect() is the items rects in the correct
         # coordinates system
         debug_print('Row [{0}] updated'.format(index.row()))
         rect = item.sceneBoundingRect()
         # Cumbersome conversion to ints
         rect = QRect(rect.left(), rect.top(), rect.width(), rect.height())
         self.model().setData(index, rect, RectRole)
示例#6
0
    def set_rect(self, new_rect):
        """Sets a new QRect in integer coordinates
        """

        # Cumbersome conversion to ints
        current = self.sceneBoundingRect()
        current = QRect(current.left(), current.top(),
                        current.width(), current.height())
        if current != new_rect:
            msg = 'Update rect for [{0}] from [{1}] to [{2}]'
            debug_print(msg.format(self, current, new_rect))
            self.prepareGeometryChange()

            # setrect() expects floating point rect
            self.setRect(QRectF(new_rect))
示例#7
0
    def paint(self, painter, rect, index):
        data_item = index.model().data_items[index.row()]

        if u'health' not in data_item or data_item[u'health'] == "updated":
            data_item[u'health'] = get_health(data_item['num_seeders'],
                                              data_item['num_leechers'],
                                              data_item['last_tracker_check'])
        health = data_item[u'health']

        # ----------------
        # |b---b|        |
        # |b|i|b| 0S 0L  |
        # |b---b|        |
        # ----------------

        r = rect

        # Indicator ellipse rectangle
        y = r.top() + (r.height() - self.indicator_side) / 2
        x = r.left() + self.indicator_border
        w = self.indicator_side
        h = self.indicator_side
        indicator_rect = QRect(x, y, w, h)

        # Paint indicator
        painter.save()
        painter.setBrush(QBrush(self.health_colors[health]))
        painter.setPen(QPen(self.health_colors[health], 0, Qt.SolidLine, Qt.RoundCap))
        painter.drawEllipse(indicator_rect)
        painter.restore()

        x = indicator_rect.left() + indicator_rect.width() + 2 * self.indicator_border
        y = r.top()
        w = r.width() - indicator_rect.width() - 2 * self.indicator_border
        h = r.height()
        text_box = QRect(x, y, w, h)

        # Paint status text, if necessary
        if health in [HEALTH_CHECKING, HEALTH_UNCHECKED, HEALTH_ERROR]:
            self.draw_text(painter, text_box, health)
        else:
            seeders = int(data_item[u'num_seeders'])
            leechers = int(data_item[u'num_leechers'])

            txt = u'S' + str(seeders) + u' L' + str(leechers)

            self.draw_text(painter, text_box, txt)
示例#8
0
    def computeDiffRegion(self, other):
        ret = QRegion()
        dx = other.x() - self.mX
        dy = other.y() - self.mY
        r = QRect(0, 0, self.width(), self.height())
        r &= QRect(dx, dy, other.width(), other.height())
        for y in range(r.top(), r.bottom()+1):
            for x in range(r.left(), r.right()+1):
                if (self.cellAt(x, y) != other.cellAt(x - dx, y - dy)):
                    rangeStart = x
                    while (x <= r.right() and self.cellAt(x, y) != other.cellAt(x - dx, y - dy)):
                        x += 1

                    rangeEnd = x
                    ret += QRect(rangeStart, y, rangeEnd - rangeStart, 1)

        return ret
示例#9
0
 def paint(self, painter, option, index):
     self.initStyleOption(option, index)
     # No idea why, but this cast is required if we want to have access to the V4 valuess
     option = QStyleOptionViewItem(option)
     if (index.column() == 1) and (option.state & QStyle.State_Selected):
         cboption = QStyleOptionComboBox()
         cboption.rect = option.rect
         # On OS X (with Qt4.6.0), adding State_Enabled to the flags causes the whole drawing to
         # fail (draw nothing), but it's an OS X only glitch. On Windows, it works alright.
         cboption.state |= QStyle.State_Enabled
         QApplication.style().drawComplexControl(QStyle.CC_ComboBox, cboption, painter)
         painter.setBrush(option.palette.text())
         rect = QRect(option.rect)
         rect.setLeft(rect.left()+4)
         painter.drawText(rect, Qt.AlignLeft, option.text)
     else:
         super().paint(painter, option, index)
 def scene_box_added(self, rect):
     """The user added a box
     """
     m = self.model()
     row = len(self._rows)
     if not m.insertRow(row):
         raise InselectError('Could not insert row')
     else:
         # Cumbersome conversion to ints
         rect = QRect(rect.left(), rect.top(), rect.width(), rect.height())
         if not m.setData(m.index(row, 0), rect, RectRole):
             raise InselectError('Could not set rect')
         else:
             # Select the new box
             self.scene.clearSelection()
             item = next(self.items_of_rows([row]))
             item.setSelected(True)
             item.update()
示例#11
0
 def __normalizeSelection(self, sel):
     """
     Private method to normalize the given selection.
     
     @param sel selection to be normalized (QRect)
     @return normalized selection (QRect)
     """
     rect = QRect(sel)
     if rect.width() <= 0:
         left = rect.left()
         width = rect.width()
         rect.setLeft(left + width - 1)
         rect.setRight(left)
     if rect.height() <= 0:
         top = rect.top()
         height = rect.height()
         rect.setTop(top + height - 1)
         rect.setBottom(top)
     return rect
示例#12
0
 def _rect_on_view_python(self, elem_geometry):
     """Python implementation for rect_on_view."""
     if elem_geometry is None:
         geometry = self._elem.geometry()
     else:
         geometry = elem_geometry
     frame = self._elem.webFrame()
     rect = QRect(geometry)
     while frame is not None:
         rect.translate(frame.geometry().topLeft())
         rect.translate(frame.scrollPosition() * -1)
         frame = frame.parentFrame()
     # We deliberately always adjust the zoom here, even with
     # adjust_zoom=False
     if elem_geometry is None:
         zoom = self._elem.webFrame().zoomFactor()
         if not config.get('ui', 'zoom-text-only'):
             rect.moveTo(rect.left() / zoom, rect.top() / zoom)
             rect.setWidth(rect.width() / zoom)
             rect.setHeight(rect.height() / zoom)
     return rect
示例#13
0
 def mouseMoveEvent(self, evt):
     """
     Protected method to handle mouse movements.
     
     @param evt mouse move event (QMouseEvent)
     """
     shouldShowHelp = not self.__helpTextRect.contains(evt.pos())
     if shouldShowHelp != self.__showHelp:
         self.__showHelp = shouldShowHelp
         self.update()
     
     if self.__mouseDown:
         if self.__newSelection:
             p = evt.pos()
             r = self.rect()
             self.__selection = self.__normalizeSelection(
                 QRect(self.__dragStartPoint,
                       self.__limitPointToRect(p, r)))
         elif self.__mouseOverHandle is None:
             # moving the whole selection
             r = self.rect().normalized()
             s = self.__selectionBeforeDrag.normalized()
             p = s.topLeft() + evt.pos() - self.__dragStartPoint
             r.setBottomRight(
                 r.bottomRight() - QPoint(s.width(), s.height()) +
                 QPoint(1, 1))
             if not r.isNull() and r.isValid():
                 self.__selection.moveTo(self.__limitPointToRect(p, r))
         else:
             # dragging a handle
             r = QRect(self.__selectionBeforeDrag)
             offset = evt.pos() - self.__dragStartPoint
             
             if self.__mouseOverHandle in \
                [self.__TLHandle, self.__THandle, self.__TRHandle]:
                 r.setTop(r.top() + offset.y())
             
             if self.__mouseOverHandle in \
                [self.__TLHandle, self.__LHandle, self.__BLHandle]:
                 r.setLeft(r.left() + offset.x())
             
             if self.__mouseOverHandle in \
                [self.__BLHandle, self.__BHandle, self.__BRHandle]:
                 r.setBottom(r.bottom() + offset.y())
             
             if self.__mouseOverHandle in \
                [self.__TRHandle, self.__RHandle, self.__BRHandle]:
                 r.setRight(r.right() + offset.x())
             
             r.setTopLeft(self.__limitPointToRect(r.topLeft(), self.rect()))
             r.setBottomRight(
                 self.__limitPointToRect(r.bottomRight(), self.rect()))
             self.__selection = self.__normalizeSelection(r)
         
         self.update()
     else:
         if self.__selection.isNull():
             return
         
         found = False
         for r in self.__handles:
             if r.contains(evt.pos()):
                 self.__mouseOverHandle = r
                 found = True
                 break
         
         if not found:
             self.__mouseOverHandle = None
             if self.__selection.contains(evt.pos()):
                 self.setCursor(Qt.OpenHandCursor)
             else:
                 self.setCursor(Qt.CrossCursor)
         else:
             if self.__mouseOverHandle in [self.__TLHandle,
                                           self.__BRHandle]:
                 self.setCursor(Qt.SizeFDiagCursor)
             elif self.__mouseOverHandle in [self.__TRHandle,
                                             self.__BLHandle]:
                 self.setCursor(Qt.SizeBDiagCursor)
             elif self.__mouseOverHandle in [self.__LHandle,
                                             self.__RHandle]:
                 self.setCursor(Qt.SizeHorCursor)
             elif self.__mouseOverHandle in [self.__THandle,
                                             self.__BHandle]:
                 self.setCursor(Qt.SizeVerCursor)
示例#14
0
    def updateBrush(self, cursorPos, _list = None):
        # get the current tile layer
        currentLayer = self.currentTileLayer()
        layerWidth = currentLayer.width()
        layerHeight = currentLayer.height()
        numTiles = layerWidth * layerHeight
        paintCorner = 0
        # if we are in vertex paint mode, the bottom right corner on the map will appear as an invalid tile offset...
        if (self.mBrushMode == BrushMode.PaintVertex):
            if (cursorPos.x() == layerWidth):
                cursorPos.setX(cursorPos.x() - 1)
                paintCorner |= 1

            if (cursorPos.y() == layerHeight):
                cursorPos.setY(cursorPos.y() - 1)
                paintCorner |= 2

        # if the cursor is outside of the map, bail out
        if (not currentLayer.bounds().contains(cursorPos)):
            return
        terrainTileset = None
        terrainId = -1
        if (self.mTerrain):
            terrainTileset = self.mTerrain.tileset()
            terrainId = self.mTerrain.id()

        # allocate a buffer to build the terrain tilemap (TODO: this could be retained per layer to save regular allocation)
        newTerrain = []
        for i in range(numTiles):
            newTerrain.append(0)
            
        # allocate a buffer of flags for each tile that may be considered (TODO: this could be retained per layer to save regular allocation)
        checked = array('B')
        for i in range(numTiles):
            checked.append(0)
        # create a consideration list, and push the start points
        transitionList = QList()
        initialTiles = 0
        if (_list):
            # if we were supplied a list of start points
            for p in _list:
                transitionList.append(p)
                initialTiles += 1

        else:
            transitionList.append(cursorPos)
            initialTiles = 1

        brushRect = QRect(cursorPos, cursorPos)
        # produce terrain with transitions using a simple, relative naive approach (considers each tile once, and doesn't allow re-consideration if selection was bad)
        while (not transitionList.isEmpty()):
            # get the next point in the consideration list
            p = transitionList.takeFirst()
            x = p.x()
            y = p.y()
            i = y*layerWidth + x
            # if we have already considered this point, skip to the next
            # TODO: we might want to allow re-consideration if prior tiles... but not for now, this would risk infinite loops
            if (checked[i]):
                continue
            tile = currentLayer.cellAt(p).tile
            currentTerrain = terrain(tile)
            # get the tileset for this tile
            tileset = None
            if (terrainTileset):
                # if we are painting a terrain, then we'll use the terrains tileset
                tileset = terrainTileset
            elif (tile):
                # if we're erasing terrain, use the individual tiles tileset (to search for transitions)
                tileset = tile.tileset()
            else:
                # no tile here and we're erasing terrain, not much we can do
                continue

            # calculate the ideal tile for this position
            preferredTerrain = 0xFFFFFFFF
            mask = 0
            if (initialTiles):
                # for the initial tiles, we will insert the selected terrain and add the surroundings for consideration
                if (self.mBrushMode == BrushMode.PaintTile):
                    # set the whole tile to the selected terrain
                    preferredTerrain = makeTerrain(terrainId)
                    mask = 0xFFFFFFFF
                else:
                    # Bail out if encountering a tile from a different tileset
                    if (tile and tile.tileset() != tileset):
                        continue
                    # calculate the corner mask
                    mask = 0xFF << (3 - paintCorner)*8
                    # mask in the selected terrain
                    preferredTerrain = (currentTerrain & ~mask) | (terrainId << (3 - paintCorner)*8)

                initialTiles -= 1
                # if there's nothing to paint... skip this tile
                if (preferredTerrain == currentTerrain and (not tile or tile.tileset() == tileset)):
                    continue
            else:
                # Bail out if encountering a tile from a different tileset
                if (tile and tile.tileset() != tileset):
                    continue
                # following tiles each need consideration against their surroundings
                preferredTerrain = currentTerrain
                mask = 0
                # depending which connections have been set, we update the preferred terrain of the tile accordingly
                if (y > 0 and checked[i - layerWidth]):
                    preferredTerrain = ((terrain(newTerrain[i - layerWidth]) << 16) | (preferredTerrain & 0x0000FFFF))&0xFFFFFFFF
                    mask |= 0xFFFF0000

                if (y < layerHeight - 1 and checked[i + layerWidth]):
                    preferredTerrain = ((terrain(newTerrain[i + layerWidth]) >> 16) | (preferredTerrain & 0xFFFF0000))&0xFFFFFFFF
                    mask |= 0x0000FFFF

                if (x > 0 and checked[i - 1]):
                    preferredTerrain = (((terrain(newTerrain[i - 1]) << 8) & 0xFF00FF00) | (preferredTerrain & 0x00FF00FF))&0xFFFFFFFF
                    mask |= 0xFF00FF00

                if (x < layerWidth - 1 and checked[i + 1]):
                    preferredTerrain = (((terrain(newTerrain[i + 1]) >> 8) & 0x00FF00FF) | (preferredTerrain & 0xFF00FF00))&0xFFFFFFFF
                    mask |= 0x00FF00FF

            # find the most appropriate tile in the tileset
            paste = None
            if (preferredTerrain != 0xFFFFFFFF):
                paste = findBestTile(tileset, preferredTerrain, mask)
                if (not paste):
                    continue

            # add tile to the brush
            newTerrain[i] = paste
            checked[i] = True
            # expand the brush rect to fit the edit set
            brushRect |= QRect(p, p)
            # consider surrounding tiles if terrain constraints were not satisfied
            if (y > 0 and not checked[i - layerWidth]):
                above = currentLayer.cellAt(x, y - 1).tile
                if (topEdge(paste) != bottomEdge(above)):
                    transitionList.append(QPoint(x, y - 1))

            if (y < layerHeight - 1 and not checked[i + layerWidth]):
                below = currentLayer.cellAt(x, y + 1).tile
                if (bottomEdge(paste) != topEdge(below)):
                    transitionList.append(QPoint(x, y + 1))

            if (x > 0 and not checked[i - 1]):
                left = currentLayer.cellAt(x - 1, y).tile
                if (leftEdge(paste) != rightEdge(left)):
                    transitionList.append(QPoint(x - 1, y))

            if (x < layerWidth - 1 and not checked[i + 1]):
                right = currentLayer.cellAt(x + 1, y).tile
                if (rightEdge(paste) != leftEdge(right)):
                    transitionList.append(QPoint(x + 1, y))

        # create a stamp for the terrain block
        stamp = TileLayer(QString(), brushRect.left(), brushRect.top(), brushRect.width(), brushRect.height())
        for y in range(brushRect.top(), brushRect.bottom()+1):
            for x in range(brushRect.left(), brushRect.right()+1):
                i = y*layerWidth + x
                if (not checked[i]):
                    continue
                tile = newTerrain[i]
                if (tile):
                    stamp.setCell(x - brushRect.left(), y - brushRect.top(), Cell(tile))
                else:
                    # TODO: we need to do something to erase tiles where checked[i] is True, and newTerrain[i] is NULL
                    # is there an eraser stamp? investigate how the eraser works...
                    pass

        # set the new tile layer as the brush
        self.brushItem().setTileLayer(stamp)
        del checked
        del newTerrain
        self.mPaintX = cursorPos.x()
        self.mPaintY = cursorPos.y()
        self.mOffsetX = cursorPos.x() - brushRect.left()
        self.mOffsetY = cursorPos.y() - brushRect.top()
示例#15
0
class BrushingModel(QObject):
    brushSizeChanged = pyqtSignal(int)
    brushColorChanged = pyqtSignal(QColor)
    brushStrokeAvailable = pyqtSignal(QPointF, object)
    drawnNumberChanged = pyqtSignal(int)

    minBrushSize = 1
    maxBrushSize = 61
    defaultBrushSize = 3
    defaultDrawnNumber = 1
    defaultColor = Qt.white
    erasingColor = Qt.black
    erasingNumber = 100

    def __init__(self, parent=None):
        QObject.__init__(self, parent=parent)
        self.sliceRect = None
        self.bb = QRect()  # bounding box enclosing the drawing
        self.brushSize = self.defaultBrushSize
        self.drawColor = self.defaultColor
        self._temp_color = None
        self._temp_number = None
        self.drawnNumber = self.defaultDrawnNumber

        self.pos = None
        self.erasing = False
        self._hasMoved = False

        self.drawOnto = None

        # an empty scene, where we add all drawn line segments
        # a QGraphicsLineItem, and which we can use to then
        # render to an image
        self.scene = QGraphicsScene()

    def toggleErase(self):
        self.erasing = not (self.erasing)
        if self.erasing:
            self.setErasing()
        else:
            self.disableErasing()

    def setErasing(self):
        self.erasing = True
        self._temp_color = self.drawColor
        self._temp_number = self.drawnNumber
        self.setBrushColor(self.erasingColor)
        self.brushColorChanged.emit(self.erasingColor)
        self.setDrawnNumber(self.erasingNumber)

    def disableErasing(self):
        self.erasing = False
        self.setBrushColor(self._temp_color)
        self.brushColorChanged.emit(self.drawColor)
        self.setDrawnNumber(self._temp_number)

    def setBrushSize(self, size):
        self.brushSize = size
        self.brushSizeChanged.emit(self.brushSize)

    def setDrawnNumber(self, num):
        self.drawnNumber = num
        self.drawnNumberChanged.emit(num)

    def getBrushSize(self):
        return self.brushSize

    def brushSmaller(self):
        b = self.brushSize
        if b > self.minBrushSize:
            self.setBrushSize(b - 1)

    def brushBigger(self):
        b = self.brushSize
        if self.brushSize < self.maxBrushSize:
            self.setBrushSize(b + 1)

    def setBrushColor(self, color):
        self.drawColor = QColor(color)
        self.brushColorChanged.emit(self.drawColor)

    def beginDrawing(self, pos, sliceRect):
        """

        pos -- QPointF-like
        """
        self.sliceRect = sliceRect
        self.scene.clear()
        self.bb = QRect()
        self.pos = QPointF(pos.x(), pos.y())
        self._hasMoved = False

    def endDrawing(self, pos):
        has_moved = self._hasMoved  # _hasMoved will change after calling moveTo
        if has_moved:
            self.moveTo(pos)
        else:
            assert self.pos == pos
            self.moveTo(QPointF(pos.x() + 0.0001, pos.y() + 0.0001))  # move a little

        # Qt seems to use strange rules for determining which pixels to set when rendering a brush stroke to a QImage.
        # We seem to get better results if we do the following:
        # 1) Slightly offset the source window because apparently there is a small shift in the data
        # 2) Render the scene to an image that is MUCH larger than the scene resolution (4x by 4x)
        # 3) Downsample each 4x4 patch from the large image back to a single pixel in the final image,
        #     applying some threshold to determine if the final pixel is on or off.

        tempi = QImage(
            QSize(4 * self.bb.width(), 4 * self.bb.height()), QImage.Format_ARGB32_Premultiplied
        )  # TODO: format
        tempi.fill(0)
        painter = QPainter(tempi)
        # Offset the source window.  At first I thought the right offset was 0.5, because
        #  that would seem to make sure points are rounded to pixel CENTERS, but
        #  experimentation indicates that 0.25 is slightly better for some reason...
        source_rect = QRectF(QPointF(self.bb.x() + 0.25, self.bb.y() + 0.25), QSizeF(self.bb.width(), self.bb.height()))
        target_rect = QRectF(QPointF(0, 0), QSizeF(4 * self.bb.width(), 4 * self.bb.height()))
        self.scene.render(painter, target=target_rect, source=source_rect)
        painter.end()

        # Now downsample: convert each 4x4 patch into a single pixel by summing and dividing
        ndarr = qimage2ndarray.rgb_view(tempi)[:, :, 0].astype(int)
        ndarr = ndarr.reshape((ndarr.shape[0],) + (ndarr.shape[1] // 4,) + (4,))
        ndarr = ndarr.sum(axis=-1)
        ndarr = ndarr.transpose()
        ndarr = ndarr.reshape((ndarr.shape[0],) + (ndarr.shape[1] // 4,) + (4,))
        ndarr = ndarr.sum(axis=-1)
        ndarr = ndarr.transpose()
        ndarr //= 4 * 4

        downsample_threshold = (7.0 / 16) * 255
        labels = numpy.where(ndarr >= downsample_threshold, numpy.uint8(self.drawnNumber), numpy.uint8(0))
        labels = labels.swapaxes(0, 1)
        assert labels.shape[0] == self.bb.width()
        assert labels.shape[1] == self.bb.height()

        ##
        ## ensure that at least one pixel is label when the brush size is 1
        ##
        ## this happens when the user just clicked without moving
        ## in that case the lineitem will be so tiny, that it won't be rendered
        ## into a single pixel by the code above
        if not has_moved and self.brushSize <= 1 and numpy.count_nonzero(labels) == 0:
            labels[labels.shape[0] // 2, labels.shape[1] // 2] = self.drawnNumber

        self.brushStrokeAvailable.emit(QPointF(self.bb.x(), self.bb.y()), labels)

    def dumpDraw(self, pos):
        res = self.endDrawing(pos)
        self.beginDrawing(pos, self.sliceRect)
        return res

    def moveTo(self, pos):
        # data coordinates
        oldX, oldY = self.pos.x(), self.pos.y()
        x, y = pos.x(), pos.y()

        line = QGraphicsLineItem(oldX, oldY, x, y)
        line.setPen(QPen(QBrush(Qt.white), self.brushSize, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
        self.scene.addItem(line)
        self._hasMoved = True

        # update bounding Box
        if not self.bb.isValid():
            self.bb = QRect(QPoint(oldX, oldY), QSize(1, 1))
        # grow bounding box
        self.bb.setLeft(min(self.bb.left(), max(0, x - self.brushSize // 2 - 1)))
        self.bb.setRight(max(self.bb.right(), min(self.sliceRect[0] - 1, x + self.brushSize // 2 + 1)))
        self.bb.setTop(min(self.bb.top(), max(0, y - self.brushSize // 2 - 1)))
        self.bb.setBottom(max(self.bb.bottom(), min(self.sliceRect[1] - 1, y + self.brushSize // 2 + 1)))

        # update/move position
        self.pos = pos
示例#16
0
文件: Gui.py 项目: PorterK/pyazo
class Gui(QWidget):
    def __init__(self):

        self.__eventHandlers = []

        #Initialize the QApp/QWidget things
        super().__init__()

        #Add a default rectangle
        self.__rectangle = QRect(0, 0, 0, 0)
        self.__relativeX = 0
        self.__relativeY = 0

        #Build the window in a method to keep the init clean
        self.buildWindow()

#Custom event handling
#Add events

    def on(self, eventName, handler):
        self.__eventHandlers.append([eventName, handler])

    #Fire events
    def __fire(self, eventName, *args):
        for event in self.__eventHandlers:
            if (event[0] == eventName):
                event[1](*args)

#Build the window

    def buildWindow(self):

        #Set the window title even though it will not be seen
        self.setWindowTitle('Pyazo')
        #Make the window transparent
        self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint)
        self.setAttribute(Qt.WA_TranslucentBackground)
        #Maximize the window
        self.resize(1920, 1080)
        #Enable mouse tracking
        self.setMouseTracking(True)
        #Render the window
        self.show()

#Paint things

    def paintEvent(self, event):
        qp = QPainter()
        qp.begin(self)
        #Paint the rectangle
        rectangleColor = QColor(200, 200, 200, 100)
        qp.setBrush(rectangleColor)
        qp.setPen(rectangleColor)
        qp.drawRect(self.__rectangle)
        qp.end()
#Handle the mouse events below
#press

    def mousePressEvent(self, event):
        # 'Mouse Click'
        #update reactangle coords
        self.__rectangle.setCoords(event.x(), event.y(), event.x(), event.y())

        self.__relativeX = event.x()
        self.__relativeY = event.y()
        #repaint
        self.repaint()
#release

    def mouseReleaseEvent(self, event):
        # 'Mouse Release'

        #Get the corners of our rectangle for use when actually taking the image from the screen
        x = self.__rectangle.left()
        y = self.__rectangle.top()

        width = self.__rectangle.width()
        height = self.__rectangle.height()

        self.__rectangle.setCoords(0, 0, 0, 0)
        self.update()

        #Hide the GUI after the button is released
        self.setVisible(False)
        #Fire our 'release' event, use the handler we defined, call it after we hide the GUI (so we don't get an image of the GUI)
        #Use a timer to create this effect, executing our handler in the QT event loop
        #also use a lambda function because singleShot requires anonymity
        QTimer.singleShot(0,
                          lambda: self.__fire('release', x, y, width, height))
#drag

    def mouseMoveEvent(self, event):
        if (event.buttons() == Qt.LeftButton):
            # 'Dragging'
            #update rectangle bottom left corner to the mouse pos
            if (event.x() > self.__relativeX):
                self.__rectangle.setRight(event.x())
                self.__rectangle.setLeft(self.__relativeX)
            elif (event.x() < self.__relativeX):
                self.__rectangle.setLeft(event.x())
                self.__rectangle.setRight(self.__relativeX)

            if (event.y() < self.__relativeY):
                self.__rectangle.setTop(event.y())
                self.__rectangle.setBottom(self.__relativeY)
            elif (event.y() > self.__relativeY):
                self.__rectangle.setBottom(event.y())
                self.__rectangle.setTop(self.__relativeY)
        #repaint
            self.repaint()
示例#17
0
class corkDelegate(QStyledItemDelegate):
    def __init__(self, parent=None):
        QStyledItemDelegate.__init__(self, parent)
        self.factor = settings.corkSizeFactor / 100.
        self.lastPos = None
        self.editing = None
        self.margin = 5

        self.bgColors = {}

    def newStyle(self):
        return settings.corkStyle == "new"

    def setCorkSizeFactor(self, v):
        self.factor = v / 100.

    def sizeHint(self, option, index):
        if self.newStyle():
            defaultSize = QSize(300, 210)
        else:
            defaultSize = QSize(300, 200)
        return defaultSize * self.factor

    def editorEvent(self, event, model, option, index):
        # We catch the mouse position in the widget to know which part to edit
        if type(event) == QMouseEvent:
            self.lastPos = event.pos()  # - option.rect.topLeft()
        return QStyledItemDelegate.editorEvent(self, event, model, option, index)

    def createEditor(self, parent, option, index):
        self.updateRects(option, index)

        bgColor = self.bgColors.get(index, "white")

        if self.mainLineRect.contains(self.lastPos):
            # One line summary
            self.editing = Outline.summarySentence
            edt = QLineEdit(parent)
            edt.setFocusPolicy(Qt.StrongFocus)
            edt.setFrame(False)
            f = QFont(option.font)
            if self.newStyle():
                f.setBold(True)
            else:
                f.setItalic(True)
                edt.setAlignment(Qt.AlignCenter)
            edt.setPlaceholderText(self.tr("One line summary"))
            edt.setFont(f)
            edt.setStyleSheet("background: {}; color: black;".format(bgColor))
            return edt

        elif self.titleRect.contains(self.lastPos):
            # Title
            self.editing = Outline.title
            edt = QLineEdit(parent)
            edt.setFocusPolicy(Qt.StrongFocus)
            edt.setFrame(False)
            f = QFont(option.font)
            if self.newStyle():
                f.setPointSize(f.pointSize() + 4)
            else:
                edt.setAlignment(Qt.AlignCenter)
            f.setBold(True)
            edt.setFont(f)
            edt.setStyleSheet("background: {}; color: black;".format(bgColor))
            # edt.setGeometry(self.titleRect)
            return edt

        else:  # self.mainTextRect.contains(self.lastPos):
            # Summary
            self.editing = Outline.summaryFull
            edt = QPlainTextEdit(parent)
            edt.setFocusPolicy(Qt.StrongFocus)
            edt.setFrameShape(QFrame.NoFrame)
            try:
                # QPlainTextEdit.setPlaceholderText was introduced in Qt 5.3
                edt.setPlaceholderText(self.tr("Full summary"))
            except AttributeError:
                pass
            edt.setStyleSheet("background: {}; color: black;".format(bgColor))
            return edt

    def updateEditorGeometry(self, editor, option, index):

        if self.editing == Outline.summarySentence:
            # One line summary
            editor.setGeometry(self.mainLineRect)

        elif self.editing == Outline.title:
            # Title
            editor.setGeometry(self.titleRect)

        elif self.editing == Outline.summaryFull:
            # Summary
            editor.setGeometry(self.mainTextRect)

    def setEditorData(self, editor, index):
        item = index.internalPointer()

        if self.editing == Outline.summarySentence:
            # One line summary
            editor.setText(item.data(Outline.summarySentence))

        elif self.editing == Outline.title:
            # Title
            editor.setText(index.data())

        elif self.editing == Outline.summaryFull:
            # Summary
            editor.setPlainText(item.data(Outline.summaryFull))

    def setModelData(self, editor, model, index):

        if self.editing == Outline.summarySentence:
            # One line summary
            model.setData(index.sibling(index.row(), Outline.summarySentence), editor.text())

        elif self.editing == Outline.title:
            # Title
            model.setData(index, editor.text(), Outline.title)

        elif self.editing == Outline.summaryFull:
            # Summary
            model.setData(index.sibling(index.row(), Outline.summaryFull), editor.toPlainText())

    def updateRects(self, option, index):
        if self.newStyle():
            self.updateRects_v2(option, index)
        else:
            self.updateRects_v1(option, index)

    def updateRects_v2(self, option, index):
        margin = self.margin * 2
        iconSize = max(24 * self.factor, 18)
        item = index.internalPointer()
        fm = QFontMetrics(option.font)
        h = fm.lineSpacing()

        self.itemRect = option.rect.adjusted(margin, margin, -margin, -margin)

        top = 15 * self.factor
        self.topRect = QRect(self.itemRect)
        self.topRect.setHeight(top)

        self.cardRect = QRect(self.itemRect.topLeft() + QPoint(0, top),
                         self.itemRect.bottomRight())
        self.iconRect = QRect(self.cardRect.topLeft() + QPoint(margin, margin),
                              QSize(iconSize, iconSize))
        self.labelRect = QRect(self.cardRect.topRight() - QPoint(margin + self.factor * 18, 1),
                               self.cardRect.topRight() + QPoint(- margin - self.factor * 4, self.factor * 24))
        self.titleRect = QRect(self.iconRect.topRight() + QPoint(margin, 0),
                               self.labelRect.bottomLeft() - QPoint(margin, margin))
        self.titleRect.setBottom(self.iconRect.bottom())
        self.mainRect = QRect(self.iconRect.bottomLeft() + QPoint(0, margin),
                              self.cardRect.bottomRight() - QPoint(margin, 2*margin))
        self.mainRect.setLeft(self.titleRect.left())
        self.mainLineRect = QRect(self.mainRect.topLeft(),
                                  self.mainRect.topRight() + QPoint(0, h))
        self.mainTextRect = QRect(self.mainLineRect.bottomLeft() + QPoint(0, margin),
                                  self.mainRect.bottomRight())
        if not item.data(Outline.summarySentence):
            self.mainTextRect.setTopLeft(self.mainLineRect.topLeft())

    def updateRects_v1(self, option, index):
        margin = self.margin
        iconSize = max(16 * self.factor, 12)
        item = index.internalPointer()
        self.itemRect = option.rect.adjusted(margin, margin, -margin, -margin)
        self.iconRect = QRect(self.itemRect.topLeft() + QPoint(margin, margin), QSize(iconSize, iconSize))
        self.labelRect = QRect(self.itemRect.topRight() - QPoint(iconSize + margin, 0),
                               self.itemRect.topRight() + QPoint(0, iconSize + 2 * margin))
        self.titleRect = QRect(self.iconRect.topRight() + QPoint(margin, 0),
                               self.labelRect.bottomLeft() - QPoint(margin, margin))
        self.bottomRect = QRect(QPoint(self.itemRect.x(), self.iconRect.bottom() + margin),
                                QPoint(self.itemRect.right(), self.itemRect.bottom()))
        self.topRect = QRect(self.itemRect.topLeft(), self.bottomRect.topRight())
        self.mainRect = self.bottomRect.adjusted(margin, margin, -margin, -margin)
        self.mainLineRect = QRect(self.mainRect.topLeft(),
                                  self.mainRect.topRight() + QPoint(0, iconSize))
        self.mainTextRect = QRect(self.mainLineRect.bottomLeft() + QPoint(0, margin),
                                  self.mainRect.bottomRight())
        if not item.data(Outline.summarySentence):
            self.mainTextRect.setTopLeft(self.mainLineRect.topLeft())
        if item.data(Outline.label) in ["", "0", 0]:
            self.titleRect.setBottomRight(self.labelRect.bottomRight() - QPoint(self.margin, self.margin))

    def paint(self, p, option, index):
        if self.newStyle():
            self.paint_v2(p, option, index)
        else:
            self.paint_v1(p, option, index)

    def paint_v2(self, p, option, index):
        # QStyledItemDelegate.paint(self, p, option, index)
        if not index.isValid():
            return

        item = index.internalPointer()
        self.updateRects(option, index)
        colors = outlineItemColors(item)

        style = qApp.style()

        def _rotate(angle, rect=self.mainRect):
            p.translate(rect.center())
            p.rotate(angle)
            p.translate(-rect.center())

        def drawRect(r):
            p.save()
            p.setBrush(Qt.gray)
            p.drawRect(r)
            p.restore()

        # Draw background
        cg = QPalette.ColorGroup(QPalette.Normal if option.state & QStyle.State_Enabled else QPalette.Disabled)
        if cg == QPalette.Normal and not option.state & QStyle.State_Active:
            cg = QPalette.Inactive

            # Selection
        if option.state & QStyle.State_Selected:
            p.save()
            p.setBrush(option.palette.brush(cg, QPalette.Highlight))
            p.setPen(Qt.NoPen)
            #p.drawRoundedRect(option.rect, 12, 12)
            p.drawRect(option.rect)
            p.restore()

            # Background
        p.save()
        if settings.viewSettings["Cork"]["Background"] != "Nothing":
            c = colors[settings.viewSettings["Cork"]["Background"]]
            if c == QColor(Qt.transparent):
                c = QColor(Qt.white)
            col = mixColors(c, QColor(Qt.white), .2)
            backgroundColor = col
            p.setBrush(col)
        else:
            p.setBrush(Qt.white)
            backgroundColor = QColor(Qt.white)

            # Cache background color
        self.bgColors[index] = backgroundColor.name()

        p.setPen(Qt.NoPen)
        p.drawRect(self.cardRect)
        if item.isFolder():
            itemPoly = QPolygonF([
                self.topRect.topLeft(),
                self.topRect.topLeft() + QPoint(self.topRect.width() * .35, 0),
                self.cardRect.topLeft() + QPoint(self.topRect.width() * .45, 0),
                self.cardRect.topRight(),
                self.cardRect.bottomRight(),
                self.cardRect.bottomLeft()
            ])
            p.drawPolygon(itemPoly)
        p.restore()

        # Label color
        if settings.viewSettings["Cork"]["Corner"] != "Nothing":
            p.save()
            color = colors[settings.viewSettings["Cork"]["Corner"]]
            p.setPen(Qt.NoPen)
            p.setBrush(color)
            p.drawRect(self.labelRect)
            w = self.labelRect.width()
            poly = QPolygonF([
                self.labelRect.bottomLeft() + QPointF(0, 1),
                self.labelRect.bottomLeft() + QPointF(0, w / 2),
                self.labelRect.bottomLeft() + QPointF(w / 2, 1),
                self.labelRect.bottomRight() + QPointF(1, w / 2),
                self.labelRect.bottomRight() + QPointF(1, 1),
            ])

            p.drawPolygon(poly)
            p.restore()

        if settings.viewSettings["Cork"]["Corner"] == "Nothing" or \
           color == Qt.transparent:
            # No corner, so title can be full width
            self.titleRect.setRight(self.mainRect.right())

        # Draw the icon
        iconRect = self.iconRect
        mode = QIcon.Normal
        if not option.state & style.State_Enabled:
            mode = QIcon.Disabled
        elif option.state & style.State_Selected:
            mode = QIcon.Selected
        # index.data(Qt.DecorationRole).paint(p, iconRect, option.decorationAlignment, mode)
        icon = index.data(Qt.DecorationRole).pixmap(iconRect.size())
        if settings.viewSettings["Cork"]["Icon"] != "Nothing":
            color = colors[settings.viewSettings["Cork"]["Icon"]]
            colorifyPixmap(icon, color)
        QIcon(icon).paint(p, iconRect, option.decorationAlignment, mode)

        # Draw title
        p.save()
        text = index.data()

        if text:
            p.setPen(Qt.black)
            textColor = QColor(Qt.black)
            if settings.viewSettings["Cork"]["Text"] != "Nothing":
                col = colors[settings.viewSettings["Cork"]["Text"]]
                if col == Qt.transparent:
                    col = Qt.black

                # If title setting is compile, we have to hack the color
                # Or we won't see anything in some themes
                if settings.viewSettings["Cork"]["Text"] == "Compile":
                    if item.compile() in [0, "0"]:
                        col = mixColors(QColor(Qt.black), backgroundColor)
                    else:
                        col = Qt.black
                textColor = col
                p.setPen(col)
            f = QFont(option.font)
            f.setPointSize(f.pointSize() + 4)
            f.setBold(True)
            p.setFont(f)
            fm = QFontMetrics(f)
            elidedText = fm.elidedText(text, Qt.ElideRight, self.titleRect.width())
            p.drawText(self.titleRect, Qt.AlignLeft | Qt.AlignVCenter, elidedText)
        p.restore()

            # One line summary background
        lineSummary = item.data(Outline.summarySentence)
        fullSummary = item.data(Outline.summaryFull)

            # Border
        if settings.viewSettings["Cork"]["Border"] != "Nothing":
            p.save()
            p.setBrush(Qt.NoBrush)
            pen = p.pen()
            pen.setWidth(2)
            col = colors[settings.viewSettings["Cork"]["Border"]]
            pen.setColor(col)
            p.setPen(pen)
            if item.isFolder():
                p.drawPolygon(itemPoly)
            else:
                p.drawRect(self.cardRect)
            p.restore()

        # Draw status
        status = item.data(Outline.status)
        if status:
            it = mainWindow().mdlStatus.item(int(status), 0)
            if it != None:
                p.save()
                p.setClipRegion(QRegion(self.cardRect))
                f = p.font()
                f.setPointSize(f.pointSize() + 12)
                f.setBold(True)
                p.setFont(f)
                p.setPen(QColor(Qt.red).lighter(170))
                _rotate(-35, rect=self.cardRect)
                p.drawText(self.cardRect, Qt.AlignCenter, it.text())
                p.restore()

                # Draw Summary
                # One line
        if lineSummary:
            p.save()
            f = QFont(option.font)
            f.setBold(True)
            p.setFont(f)
            p.setPen(textColor)
            fm = QFontMetrics(f)
            elidedText = fm.elidedText(lineSummary, Qt.ElideRight, self.mainLineRect.width())
            p.drawText(self.mainLineRect, Qt.AlignLeft | Qt.AlignVCenter, elidedText)
            p.restore()

            # Full summary
        if fullSummary:
            p.save()
            p.setFont(option.font)
            p.setPen(textColor)
            p.drawText(self.mainTextRect, Qt.TextWordWrap, fullSummary)
            p.restore()

    def paint_v1(self, p, option, index):
        # QStyledItemDelegate.paint(self, p, option, index)
        if not index.isValid():
            return

        item = index.internalPointer()
        self.updateRects(option, index)
        colors = outlineItemColors(item)

        style = qApp.style()

        def _rotate(angle):
            p.translate(self.mainRect.center())
            p.rotate(angle)
            p.translate(-self.mainRect.center())

        # Draw background
        cg = QPalette.ColorGroup(QPalette.Normal if option.state & QStyle.State_Enabled else QPalette.Disabled)
        if cg == QPalette.Normal and not option.state & QStyle.State_Active:
            cg = QPalette.Inactive

            # Selection
        if option.state & QStyle.State_Selected:
            p.save()
            p.setBrush(option.palette.brush(cg, QPalette.Highlight))
            p.setPen(Qt.NoPen)
            p.drawRoundedRect(option.rect, 12, 12)
            p.restore()

            # Stack
        if item.isFolder() and item.childCount() > 0:
            p.save()
            p.setBrush(Qt.white)
            for i in reversed(range(3)):
                p.drawRoundedRect(self.itemRect.adjusted(2 * i, 2 * i, -2 * i, 2 * i), 10, 10)

            p.restore()

            # Background
        itemRect = self.itemRect
        p.save()
        if settings.viewSettings["Cork"]["Background"] != "Nothing":
            c = colors[settings.viewSettings["Cork"]["Background"]]
            col = mixColors(c, QColor(Qt.white), .2)
            p.setBrush(col)
        else:
            p.setBrush(Qt.white)
        pen = p.pen()
        pen.setWidth(2)
        p.setPen(pen)
        p.drawRoundedRect(itemRect, 10, 10)
        p.restore()

        # Title bar
        topRect = self.topRect
        p.save()
        if item.isFolder():
            color = QColor(Qt.darkGreen)
        else:
            color = QColor(Qt.blue).lighter(175)
        p.setPen(Qt.NoPen)
        p.setBrush(color)
        p.setClipRegion(QRegion(topRect))
        p.drawRoundedRect(itemRect, 10, 10)
        # p.drawRect(topRect)
        p.restore()

        # Label color
        if settings.viewSettings["Cork"]["Corner"] != "Nothing":
            p.save()
            color = colors[settings.viewSettings["Cork"]["Corner"]]
            p.setPen(Qt.NoPen)
            p.setBrush(color)
            p.setClipRegion(QRegion(self.labelRect))
            p.drawRoundedRect(itemRect, 10, 10)
            # p.drawRect(topRect)
            p.restore()
            if color != Qt.transparent:
                p.drawLine(self.labelRect.topLeft(), self.labelRect.bottomLeft())

            # One line summary background
        lineSummary = item.data(Outline.summarySentence)
        fullSummary = item.data(Outline.summaryFull)
        if lineSummary or not fullSummary:
            m = self.margin
            r = self.mainLineRect.adjusted(-m, -m, m, m / 2)
            p.save()
            p.setPen(Qt.NoPen)
            p.setBrush(QColor("#EEE"))
            p.drawRect(r)
            p.restore()

            # Border
        p.save()
        p.setBrush(Qt.NoBrush)
        pen = p.pen()
        pen.setWidth(2)
        if settings.viewSettings["Cork"]["Border"] != "Nothing":
            col = colors[settings.viewSettings["Cork"]["Border"]]
            if col == Qt.transparent:
                col = Qt.black
            pen.setColor(col)
        p.setPen(pen)
        p.drawRoundedRect(itemRect, 10, 10)
        p.restore()

        # Draw the icon
        iconRect = self.iconRect
        mode = QIcon.Normal
        if not option.state & style.State_Enabled:
            mode = QIcon.Disabled
        elif option.state & style.State_Selected:
            mode = QIcon.Selected
        # index.data(Qt.DecorationRole).paint(p, iconRect, option.decorationAlignment, mode)
        icon = index.data(Qt.DecorationRole).pixmap(iconRect.size())
        if settings.viewSettings["Cork"]["Icon"] != "Nothing":
            color = colors[settings.viewSettings["Cork"]["Icon"]]
            colorifyPixmap(icon, color)
        QIcon(icon).paint(p, iconRect, option.decorationAlignment, mode)

        # Draw title
        p.save()
        text = index.data()
        titleRect = self.titleRect
        if text:
            if settings.viewSettings["Cork"]["Text"] != "Nothing":
                col = colors[settings.viewSettings["Cork"]["Text"]]
                if col == Qt.transparent:
                    col = Qt.black
                p.setPen(col)
            f = QFont(option.font)
            # f.setPointSize(f.pointSize() + 1)
            f.setBold(True)
            p.setFont(f)
            fm = QFontMetrics(f)
            elidedText = fm.elidedText(text, Qt.ElideRight, titleRect.width())
            p.drawText(titleRect, Qt.AlignCenter, elidedText)
        p.restore()

        # Draw the line
        bottomRect = self.bottomRect
        p.save()
        # p.drawLine(itemRect.x(), iconRect.bottom() + margin,
        # itemRect.right(), iconRect.bottom() + margin)
        p.drawLine(bottomRect.topLeft(), bottomRect.topRight())
        p.restore()

        # Lines
        if True:
            p.save()
            p.setPen(QColor("#EEE"))
            fm = QFontMetrics(option.font)
            h = fm.lineSpacing()
            l = self.mainTextRect.topLeft() + QPoint(0, h)
            while self.mainTextRect.contains(l):
                p.drawLine(l, QPoint(self.mainTextRect.right(), l.y()))
                l.setY(l.y() + h)
            p.restore()

        # Draw status
        mainRect = self.mainRect
        status = item.data(Outline.status)
        if status:
            it = mainWindow().mdlStatus.item(int(status), 0)
            if it != None:
                p.save()
                p.setClipRegion(QRegion(mainRect))
                f = p.font()
                f.setPointSize(f.pointSize() + 12)
                f.setBold(True)
                p.setFont(f)
                p.setPen(QColor(Qt.red).lighter(175))
                _rotate(-35)
                p.drawText(mainRect, Qt.AlignCenter, it.text())
                p.restore()

                # Draw Summary
                # One line
        if lineSummary:
            p.save()
            f = QFont(option.font)
            f.setItalic(True)
            p.setFont(f)
            fm = QFontMetrics(f)
            elidedText = fm.elidedText(lineSummary, Qt.ElideRight, self.mainLineRect.width())
            p.drawText(self.mainLineRect, Qt.AlignCenter, elidedText)
            p.restore()

            # Full summary
        if fullSummary:
            p.setFont(option.font)
            p.drawText(self.mainTextRect, Qt.TextWordWrap, fullSummary)
示例#18
0
    def paint_smallicon_view(self, painter: QPainter) -> None:
        self.thumbnail_rect = QRect(0, 0, self.tile_rect.height(), self.tile_rect.height())

        font = self.style.font
        fm = self.style.fm

        painter.setFont(font)
        self.paint_thumbnail(painter)

        if self.zoom_index in [0, 1]:
            text_option = QTextOption(Qt.AlignLeft | Qt.AlignVCenter)
            text_option.setWrapMode(QTextOption.NoWrap)
            text_rect = QRect(QPoint(self.tile_rect.height() + 4,
                                     0),
                              QPoint(self.tile_rect.width(),
                                     self.tile_rect.height()))
            text = self.fileinfo.basename()
            text = fm.elidedText(text, Qt.ElideRight, text_rect.width())
            painter.drawText(QRectF(text_rect),
                             text,
                             text_option)
        elif self.zoom_index in [2]:
            text_rect = QRect(QPoint(self.tile_rect.height() + 4,
                                     0),
                              QPoint(self.tile_rect.width() - 80,
                                     self.tile_rect.height()))
            text = self.fileinfo.basename()
            text = fm.elidedText(text, Qt.ElideRight, text_rect.width())
            text_option = QTextOption(Qt.AlignLeft | Qt.AlignVCenter)
            text_option.setWrapMode(QTextOption.NoWrap)
            painter.drawText(QRectF(text_rect), text, text_option)

            text_rect = QRect(QPoint(self.tile_rect.width() - 80,
                                     0),
                              QPoint(self.tile_rect.width(),
                                     self.tile_rect.height()))
            text = bytefmt.humanize(self.fileinfo.size())
            text = fm.elidedText(text, Qt.ElideRight, text_rect.width())
            text_option = QTextOption(Qt.AlignRight | Qt.AlignVCenter)

            text_option.setWrapMode(QTextOption.NoWrap)
            painter.setPen(QColor(96, 96, 96))
            painter.drawText(QRectF(text_rect), text, text_option)
        else:
            top_left_text, top_right_text, bottom_left_text, bottom_right = self.make_text()

            row1_rect = QRect(QPoint(self.tile_rect.left() + self.tile_rect.height() + 8,
                                     self.tile_rect.top()),
                              QPoint(self.tile_rect.right() - 8,
                                     self.tile_rect.top() + self.tile_rect.height() / 2))
            row2_rect = QRect(QPoint(self.tile_rect.left() + self.tile_rect.height() + 8,
                                     self.tile_rect.top() + self.tile_rect.height() / 2),
                              QPoint(self.tile_rect.right() - 8,
                                     self.tile_rect.bottom()))

            # row 1
            text_rect = QRect(QPoint(row1_rect.left(),
                                     row1_rect.top()),
                              QPoint(row1_rect.right() - 80,
                                     row1_rect.bottom()))
            text = self.fileinfo.basename()
            text = fm.elidedText(text, Qt.ElideRight, text_rect.width())
            text_option = QTextOption(Qt.AlignLeft | Qt.AlignVCenter)
            text_option.setWrapMode(QTextOption.NoWrap)
            painter.drawText(QRectF(text_rect), text, text_option)

            text_rect = QRect(QPoint(row1_rect.left() - 80,
                                     row1_rect.top()),
                              QPoint(row1_rect.right(),
                                     row1_rect.bottom()))
            text = bytefmt.humanize(self.fileinfo.size())
            text = fm.elidedText(text, Qt.ElideRight, text_rect.width())
            text_option = QTextOption(Qt.AlignRight | Qt.AlignVCenter)

            text_option.setWrapMode(QTextOption.NoWrap)
            painter.setPen(QColor(96, 96, 96))
            painter.drawText(QRectF(text_rect), text, text_option)

            # row 2
            text_rect = QRect(QPoint(row2_rect.left(),
                                     row2_rect.top()),
                              QPoint(row2_rect.right() - 80,
                                     row2_rect.bottom()))
            text = bottom_left_text
            text = fm.elidedText(text, Qt.ElideRight, text_rect.width())
            text_option = QTextOption(Qt.AlignLeft | Qt.AlignVCenter)
            text_option.setWrapMode(QTextOption.NoWrap)
            painter.drawText(QRectF(text_rect), text, text_option)

            text_rect = QRect(QPoint(row2_rect.left() - 80,
                                     row2_rect.top()),
                              QPoint(row2_rect.right(),
                                     row2_rect.bottom()))
            text = top_left_text
            text = fm.elidedText(text, Qt.ElideRight, text_rect.width())
            text_option = QTextOption(Qt.AlignRight | Qt.AlignVCenter)

            text_option.setWrapMode(QTextOption.NoWrap)
            painter.setPen(QColor(96, 96, 96))
            painter.drawText(QRectF(text_rect), text, text_option)
示例#19
0
    def paint(self, painter, option, index):

        item = index.internalPointer()
        colors = outlineItemColors(item)

        style = qApp.style()

        opt = QStyleOptionViewItem(option)
        self.initStyleOption(opt, index)

        iconRect = style.subElementRect(style.SE_ItemViewItemDecoration, opt)
        textRect = style.subElementRect(style.SE_ItemViewItemText, opt)

        # Background
        style.drawPrimitive(style.PE_PanelItemViewItem, opt, painter)

        if settings.viewSettings["Tree"]["Background"] != "Nothing" and not opt.state & QStyle.State_Selected:

            col = colors[settings.viewSettings["Tree"]["Background"]]

            if col != QColor(Qt.transparent):
                col2 = QColor(S.window)
                if opt.state & QStyle.State_Selected:
                    col2 = opt.palette.brush(QPalette.Normal, QPalette.Highlight).color()
                col = mixColors(col, col2, .2)

            painter.save()
            painter.setBrush(col)
            painter.setPen(Qt.NoPen)

            rect = opt.rect
            if self._view:
                r2 = self._view.visualRect(index)
                rect = self._view.viewport().rect()
                rect.setLeft(r2.left())
                rect.setTop(r2.top())
                rect.setBottom(r2.bottom())

            painter.drawRoundedRect(rect, 5, 5)
            painter.restore()

        # Icon
        mode = QIcon.Normal
        if not opt.state & QStyle.State_Enabled:
            mode = QIcon.Disabled
        elif opt.state & QStyle.State_Selected:
            mode = QIcon.Selected
        state = QIcon.On if opt.state & QStyle.State_Open else QIcon.Off
        icon = opt.icon.pixmap(iconRect.size(), mode=mode, state=state)
        if opt.icon and settings.viewSettings["Tree"]["Icon"] != "Nothing":
            color = colors[settings.viewSettings["Tree"]["Icon"]]
            colorifyPixmap(icon, color)
        opt.icon = QIcon(icon)
        opt.icon.paint(painter, iconRect, opt.decorationAlignment, mode, state)

        # Text
        if opt.text:
            painter.save()
            textColor = QColor(S.text)
            if option.state & QStyle.State_Selected:
                col = QColor(S.highlightedText)
                textColor = col
                painter.setPen(col)
            if settings.viewSettings["Tree"]["Text"] != "Nothing":
                col = colors[settings.viewSettings["Tree"]["Text"]]
                if col == Qt.transparent:
                    col = textColor
                # If text color is Compile and item is selected, we have
                # to change the color
                if settings.viewSettings["Outline"]["Text"] == "Compile" and \
                   not item.compile():
                    col = mixColors(textColor, QColor(S.window))
                painter.setPen(col)
            f = QFont(opt.font)
            painter.setFont(f)
            fm = QFontMetrics(f)
            elidedText = fm.elidedText(opt.text, Qt.ElideRight, textRect.width())
            painter.drawText(textRect, Qt.AlignLeft | Qt.AlignVCenter, elidedText)

            extraText = ""
            if item.isFolder() and settings.viewSettings["Tree"]["InfoFolder"] != "Nothing":
                if settings.viewSettings["Tree"]["InfoFolder"] == "Count":
                    extraText = item.childCount()
                    extraText = " [{}]".format(extraText)
                elif settings.viewSettings["Tree"]["InfoFolder"] == "WC":
                    extraText = item.wordCount()
                    extraText = " ({})".format(extraText)
                elif settings.viewSettings["Tree"]["InfoFolder"] == "Progress":
                    extraText = int(toFloat(item.data(Outline.goalPercentage)) * 100)
                    if extraText:
                        extraText = " ({}%)".format(extraText)
                elif settings.viewSettings["Tree"]["InfoFolder"] == "Summary":
                    extraText = item.data(Outline.summarySentence)
                    if extraText:
                        extraText = " - {}".format(extraText)

            if item.isText() and settings.viewSettings["Tree"]["InfoText"] != "Nothing":
                if settings.viewSettings["Tree"]["InfoText"] == "WC":
                    extraText = item.wordCount()
                    extraText = " ({})".format(extraText)
                elif settings.viewSettings["Tree"]["InfoText"] == "Progress":
                    extraText = int(toFloat(item.data(Outline.goalPercentage)) * 100)
                    if extraText:
                        extraText = " ({}%)".format(extraText)
                elif settings.viewSettings["Tree"]["InfoText"] == "Summary":
                    extraText = item.data(Outline.summarySentence)
                    if extraText:
                        extraText = " - {}".format(extraText)


            if extraText:
                r = QRect(textRect)
                r.setLeft(r.left() + fm.width(opt.text + " "))

                painter.save()
                f = painter.font()
                f.setWeight(QFont.Normal)
                painter.setFont(f)
                if option.state & QStyle.State_Selected:
                    col = QColor(S.highlightedTextLight)
                else:
                    col = QColor(S.textLight)
                painter.setPen(col)
                painter.drawText(r, Qt.AlignLeft | Qt.AlignVCenter, extraText)
                painter.restore()

            painter.restore()
示例#20
0
    def rect_on_view(self, *, elem_geometry=None, adjust_zoom=True,
                     no_js=False):
        """Get the geometry of the element relative to the webview.

        Uses the getClientRects() JavaScript method to obtain the collection of
        rectangles containing the element and returns the first rectangle which
        is large enough (larger than 1px times 1px). If all rectangles returned
        by getClientRects() are too small, falls back to elem.rect_on_view().

        Skipping of small rectangles is due to <a> elements containing other
        elements with "display:block" style, see
        https://github.com/The-Compiler/qutebrowser/issues/1298

        Args:
            elem_geometry: The geometry of the element, or None.
                           Calling QWebElement::geometry is rather expensive so
                           we want to avoid doing it twice.
            adjust_zoom: Whether to adjust the element position based on the
                         current zoom level.
            no_js: Fall back to the Python implementation
        """
        self._check_vanished()

        # First try getting the element rect via JS, as that's usually more
        # accurate
        if elem_geometry is None and not no_js:
            rects = self._elem.evaluateJavaScript("this.getClientRects()")
            text = utils.compact_text(self._elem.toOuterXml(), 500)
            log.hints.vdebug("Client rectangles of element '{}': {}".format(
                text, rects))
            for i in range(int(rects.get("length", 0))):
                rect = rects[str(i)]
                width = rect.get("width", 0)
                height = rect.get("height", 0)
                if width > 1 and height > 1:
                    # fix coordinates according to zoom level
                    zoom = self._elem.webFrame().zoomFactor()
                    if not config.get('ui', 'zoom-text-only') and adjust_zoom:
                        rect["left"] *= zoom
                        rect["top"] *= zoom
                        width *= zoom
                        height *= zoom
                    rect = QRect(rect["left"], rect["top"], width, height)
                    frame = self._elem.webFrame()
                    while frame is not None:
                        # Translate to parent frames' position (scroll position
                        # is taken care of inside getClientRects)
                        rect.translate(frame.geometry().topLeft())
                        frame = frame.parentFrame()
                    return rect

        # No suitable rects found via JS, try via the QWebElement API
        if elem_geometry is None:
            geometry = self._elem.geometry()
        else:
            geometry = elem_geometry
        frame = self._elem.webFrame()
        rect = QRect(geometry)
        while frame is not None:
            rect.translate(frame.geometry().topLeft())
            rect.translate(frame.scrollPosition() * -1)
            frame = frame.parentFrame()
        # We deliberately always adjust the zoom here, even with
        # adjust_zoom=False
        if elem_geometry is None:
            zoom = self._elem.webFrame().zoomFactor()
            if not config.get('ui', 'zoom-text-only'):
                rect.moveTo(rect.left() / zoom, rect.top() / zoom)
                rect.setWidth(rect.width() / zoom)
                rect.setHeight(rect.height() / zoom)
        return rect
class ViewFisheye(QWidget):

    # sample selection
    SelectionType = Enum('SelectType', 'Exact Closest Rect')
    SelectionMode = Enum('SelectMode', 'Select Add Remove')
    SelectionRectMin = 10   # pixels, width and height, scales as photo scales
    SampleRadius = 10       # pixels, scales as photo scales
    SelectedPixelBox = 64   # pixels, width and height

    def __init__(self, parent):
        super().__init__()

        # members
        self.parent = parent
        self.myPhoto = QImage()
        self.myPhotoPixels = np.zeros(shape=(1, 1, 4))
        self.myPhotoPath = ""
        self.myPhotoTime = datetime(1,1,1)
        self.myPhotoSrcRect = QRect()
        self.myPhotoDestRect = QRect()
        self.myPhotoRadius = 0
        self.myPhotoRotation = 0
        self.rawAvailable = False
        self.coordsMouse = (0, 0)
        self.viewCenter = (0, 0)
        self.dragSelectRect = QRect(0, 0, 0, 0)
        self.sunPosition = (0, 0)        # (azimuth (theta), altitude (phi)(90-zenith))
        self.sunPositionVisible = (0,0)  # point (x,y) of sun location rendered on screen (scaled)
        self.sunPathPoints = []          # [(azimuth (theta), altitude (phi)(90-zenith), datetime)]
        self.compassTicks = []           # [[x1, y1, x2, y2, x1lbl, y1lbl, angle]]
        self.lensIdealRadii = []         # list of radii for ideal lens latitudes to draw
        self.lensRealRadii = []          # list of radii for real/warped lens latitudes to draw
        self.samplePoints = []           # (x,y) coords of all samples on the photo rendered on screen (scaled)
        self.sampleAreaVisible = []      # area of 4 points for each sample rendered on screen (scaled)
        self.samplePointsInFile = []     # points (x,y) of all samples in the photo on file
        self.samplesSelected = []        # indices of selected samples
        self.skyCover = common.SkyCover.UNK

        # members - preloaded graphics
        self.painter = QPainter()
        self.mask = QImage()
        self.pathSun = QPainterPath()
        self.penText = QPen(Qt.white, 1, Qt.SolidLine)
        self.penLens = QPen(Qt.magenta, 1, Qt.SolidLine)
        self.penSun = QPen(QColor(255, 165, 0), 2, Qt.SolidLine)
        self.penSelected = []  # list of pens, one for each sampling pattern location
        self.penSelectRect = QPen(Qt.white, 1, Qt.DashLine)
        self.penShadowText = QPen(Qt.black, 1, Qt.SolidLine)
        self.penShadowSun = QPen(Qt.black, 2, Qt.SolidLine)
        self.penShadowSelected = QPen(Qt.black, 3, Qt.SolidLine)
        self.brushGrid = QBrush(Qt.white, Qt.SolidPattern)
        self.fontFixed = QFont('Courier New', 8)
        self.fontScaled = QFont('Courier New', 8)
        self.fontMetrics = QFontMetrics(self.fontScaled)
        self.iconWarning = self.style().standardIcon(QStyle.SP_MessageBoxWarning).pixmap(ViewFisheye.SelectedPixelBox / 2)

    def dataLoaded(self):
        # Note - this function only runs once the data directory has been loaded
        self.setMouseTracking(True)
        color = QColor(255, 255, 255)
        self.samplesSelected.clear()
        self.samplePoints.clear()
        self.sampleAreaVisible.clear()
        self.samplePointsInFile.clear()
        self.penSelected.clear()
        for t, p in common.SamplingPattern:
            self.samplePoints.append((0, 0))  # these will need to be recomputed as photo scales
            self.samplePointsInFile.append((0, 0))  # these only need to be computed once per photo
            self.sampleAreaVisible.append([])
            color.setHsv(t, int(utility.normalize(p, 0, 90) * 127 + 128), 255)
            self.penSelected.append(QPen(color, 3, Qt.SolidLine))

    def setPhoto(self, path, exif=None):
        # if photo is valid
        if path is not None and os.path.exists(path):
            self.myPhotoPath = path
            self.myPhoto = QImage(path)
            self.myPhotoSrcRect = QRect(0, 0, self.myPhoto.width(), self.myPhoto.height())
            self.myPhotoDestRect = QRect(0, 0, self.width(), self.height())
            self.rawAvailable = utility_data.isHDRRawAvailable(path)
            if exif is not None:
                self.myPhotoTime = datetime.strptime(str(exif["EXIF DateTimeOriginal"]), '%Y:%m:%d %H:%M:%S')
            else:
                self.myPhotoTime = utility_data.imageEXIFDateTime(path)

            # cache each sample's coordinate in the photo
            # note: technically doesn't need to be recalculated if all photos have same resolution!
            self.samplePointsInFile = utility_data.computePointsInImage(path, common.SamplingPattern)

            # keep a copy the image's pixels in memory (used later for exporting, etc.)
            ptr = self.myPhoto.bits()
            ptr.setsize(self.myPhoto.byteCount())
            pixbgr = np.asarray(ptr).reshape(self.myPhoto.height(), self.myPhoto.width(), 4)
            # HACKAROONIE: byte order is not the same as image format, so swapped it around :/
            # TODO: should handle this better
            self.myPhotoPixels = np.copy(pixbgr)
            red = np.copy(self.myPhotoPixels[:, :, 0])
            self.myPhotoPixels[:, :, 0] = self.myPhotoPixels[:, :, 2]
            self.myPhotoPixels[:, :, 2] = red
            # rgba = self.myPhoto.pixelColor(center[0], center[1])
            # print((rgba.red(), rgba.green(), rgba.blue()))
            # rgba = pixrgb[center[1], center[0]]
            # print(rgba)

        # photo is null or missing
        else:
            self.myPhoto = QImage()
            self.myPhotoPixels = np.zeros(shape=(1,1,4))
            self.myPhotoPath = ""
            self.myPhotoTime = datetime(1, 1, 1)
            self.myPhotoSrcRect = QRect()
            self.myPhotoDestRect = QRect()
            self.rawAvailable = False

        # precompute as much as we can before any drawing
        self.computeBounds()

    def setSunPath(self, sunpath):
        self.sunPathPoints = sunpath

    def setSunPosition(self, pos):
        self.sunPosition = pos

    def setSkycover(self, sc):
        self.skyCover = sc

    def getSamplePatternRGB(self, index):
        if index < 0 or index >= len(common.SamplingPattern):
            return (0,0,0)
        color = self.penSelected[index].color()
        return (color.red(), color.green(), color.blue())

    def resetRotation(self, angles=0):
        self.myPhotoRotation = angles

    def selectSamples(self, message="none"):
        # nothing to do if no photo loaded
        if self.myPhoto.isNull():
            return

        # handle selection message
        if message == "none":
            self.samplesSelected.clear()
        elif message == "all":
            self.samplesSelected[:] = [i for i in range(0, len(common.SamplingPattern))]
        elif message == "inverse":
            allidx = set([i for i in range(0, len(common.SamplingPattern))])
            selidx = set(self.samplesSelected)
            self.samplesSelected[:] = list(allidx - selidx)

        # remove samples in circumsolar avoidance region if necessary
        sunAvoid = common.AppSettings["AvoidSunAngle"]
        if sunAvoid > 0:
            sunAvoidRads = math.radians(common.AppSettings["AvoidSunAngle"])
            sunPosRads = (math.radians(self.sunPosition[0]), math.radians(self.sunPosition[1]))
            self.samplesSelected[:] = [idx for idx in self.samplesSelected if utility_angles.CentralAngle(sunPosRads, common.SamplingPatternRads[idx], inRadians=True) > sunAvoidRads]

        # update
        self.repaint()
        self.parent.graphSamples(self.samplesSelected)

    def mouseMoveEvent(self, event):
        # nothing to do if no photo loaded
        if self.myPhoto.isNull():
            return

        # detect primary mouse button drag for sample selection
        if event.buttons() == Qt.LeftButton:
            # update drag selection bounds
            self.dragSelectRect.setWidth(event.x() - self.dragSelectRect.x())
            self.dragSelectRect.setHeight(event.y() - self.dragSelectRect.y())

        # detect middle mouse button drag for image rotation
        elif (event.buttons() == Qt.MidButton):
            old = (self.coordsMouse[0] - self.viewCenter[0], self.coordsMouse[1] - self.viewCenter[1])
            new = (event.x() - self.viewCenter[0], event.y() - self.viewCenter[1])
            # clockwise drag decreases rotation
            if old[1]*new[0] < old[0]*new[1]:
                self.myPhotoRotation -= 1
            # counter-clockwise drag increases rotation
            else:
                self.myPhotoRotation += 1
            # rotation
            if self.myPhotoRotation >= 0:
                self.myPhotoRotation %= 360
            else:
                self.myPhotoRotation %= -360

        # lastly, cache mouse coordinates and update
        self.coordsMouse = (event.x(), event.y())
        self.repaint()

    def mousePressEvent(self, event):
        # nothing to do if no photo loaded
        if self.myPhoto.isNull():
            return
        # we only care about a left click for point and drag selection
        # right click is for context menu - handled elsewhere
        # middle click is for rotation - handled elsewhere
        if event.buttons() != Qt.LeftButton:
            return

        # start logging drag selection (whether user drags or not)
        self.dragSelectRect.setX(event.x())
        self.dragSelectRect.setY(event.y())
        self.dragSelectRect.setWidth(0)
        self.dragSelectRect.setHeight(0)

    def mouseReleaseEvent(self, event):
        # nothing to do if no photo loaded
        if self.myPhoto.isNull():
            return

        # detect primary mouse button release for stopping sample selection
        if event.button() == Qt.LeftButton:
            # read modifier keys for user desired selection mode
            mode = ViewFisheye.SelectionMode.Select
            if event.modifiers() == Qt.ControlModifier:
                mode = ViewFisheye.SelectionMode.Add
            elif event.modifiers() == Qt.ShiftModifier:
                mode = ViewFisheye.SelectionMode.Remove

            # unflip coordinates of rect so that width and height are always positive
            r = self.dragSelectRect
            r = utility.rectForwardFacing([r.x(), r.y(), r.right(), r.bottom()])
            self.dragSelectRect.setCoords(r[0], r[1], r[2], r[3])

            # select samples
            prevSelected = list(self.samplesSelected)
            if self.dragSelectRect.width() < ViewFisheye.SelectionRectMin and self.dragSelectRect.height() < ViewFisheye.SelectionRectMin:
               self.computeSelectedSamples(ViewFisheye.SelectionType.Closest, mode)
            else:
                self.computeSelectedSamples(ViewFisheye.SelectionType.Rect, mode)

            # reset drag selection
            self.dragSelectRect.setX(event.x())
            self.dragSelectRect.setY(event.y())
            self.dragSelectRect.setWidth(0)
            self.dragSelectRect.setHeight(0)

            # update
            self.repaint()
            if self.samplesSelected != prevSelected:
                self.parent.graphSamples(self.samplesSelected)

    def wheelEvent(self, event):
        # nothing to do if no photo loaded
        if self.myPhoto.isNull():
            return

        self.parent.timeChangeWheelEvent(event)

    def leaveEvent(self, event):
        self.coordsMouse = (-1, -1)
        self.repaint()

    def resizeEvent(self, event):
        self.computeBounds()

    def contextMenuEvent(self, event):
        # nothing to do if no photo loaded
        if self.myPhoto.isNull():
            return

        self.parent.triggerContextMenu(self, event)

    def computeSelectedSamples(self, type, mode):
        px = 0
        py = 0
        x1 = 0
        y1 = 0
        x2 = 0
        y2 = 0

        # in select mode, clear current selection
        if mode == ViewFisheye.SelectionMode.Select:
            self.samplesSelected = []

        # these are the samples we will be adding or removing
        sampleAdjustments = []

        # which single sample did user select by point
        if type == ViewFisheye.SelectionType.Exact:
            px = self.coordsMouse[0]
            py = self.coordsMouse[1]
            for i in range(0, len(self.samplePoints)):
                x, y = self.samplePoints[i]
                x1 = x - ViewFisheye.SampleRadius
                y1 = y - ViewFisheye.SampleRadius
                x2 = x + ViewFisheye.SampleRadius
                y2 = y + ViewFisheye.SampleRadius
                if px >= x1 and px <= x2 and py >= y1 and py <= y2:
                    sampleAdjustments.append(i)
                    break
        # which single sample is the closest to the mouse coordinate
        elif type == ViewFisheye.SelectionType.Closest:
            px = self.coordsMouse[0]
            py = self.coordsMouse[1]
            dist = math.sqrt((py-self.viewCenter[1])*(py-self.viewCenter[1]) + (px-self.viewCenter[0])*(px-self.viewCenter[0]))
            if dist <= self.myPhotoRadius:
                close = math.inf
                closest = -1
                for i in range(0, len(self.samplePoints)):
                    x, y = self.samplePoints[i]
                    dist = math.sqrt((y-py)*(y-py) + (x-px)*(x-px))
                    if dist < close:
                        close = dist
                        closest = i
                if closest >= 0:
                    sampleAdjustments.append(closest)
        # which samples are in the drag selection rect
        elif type == ViewFisheye.SelectionType.Rect:
            x1 = self.dragSelectRect.x()
            y1 = self.dragSelectRect.y()
            x2 = self.dragSelectRect.x() + self.dragSelectRect.width()
            y2 = self.dragSelectRect.y() + self.dragSelectRect.height()
            for i in range(0, len(self.samplePoints)):
                x, y = self.samplePoints[i]
                if x >= x1 and x <= x2 and y >= y1 and y <= y2:
                    sampleAdjustments.append(i)

        # remove samples in circumsolar avoidance region
        sunAvoid = common.AppSettings["AvoidSunAngle"]
        if sunAvoid > 0:
            sunAvoidRads = math.radians(common.AppSettings["AvoidSunAngle"])
            sunPosRads = (math.radians(self.sunPosition[0]), math.radians(self.sunPosition[1]))
            sampleAdjustments[:] = [idx for idx in sampleAdjustments if utility_angles.CentralAngle(sunPosRads, common.SamplingPatternRads[idx], inRadians=True) > sunAvoidRads]

        # no changes to be made
        if len(sampleAdjustments) <= 0:
            return

        # finally modify sample selection and return difference
        if mode == ViewFisheye.SelectionMode.Select or mode == ViewFisheye.SelectionMode.Add:
            for i in range(0, len(sampleAdjustments)):
                if sampleAdjustments[i] not in self.samplesSelected:  # don't readd existing indices
                    self.samplesSelected.append(sampleAdjustments[i])
        elif mode == ViewFisheye.SelectionMode.Remove:
            for i in range(0, len(sampleAdjustments)):
                try:
                    self.samplesSelected.remove(sampleAdjustments[i])
                except:
                    pass # ignore trying to remove indices that aren't currently selected

        # sort selection for easier searching later
        self.samplesSelected.sort()

    def computeBounds(self):
        if self.myPhoto.isNull():
            self.myPhotoDestRect = QRect(0, 0, self.width(), self.height())
            self.viewCenter = (self.width() / 2, self.height() / 2)
            self.myPhotoRadius = 0
            self.myPhotoDiameter = 0
            for i in range(0, len(common.SamplingPattern)):
                self.samplePoints[i] = (0, 0)
                self.sampleAreaVisible[i] = []
            return

        # scale photo destination rect to fit photo on screen
        # scale by the scaling factor that requires the most scaling ( - 2 to fit in border )
        wRatio = self.width() / self.myPhoto.width()
        hRatio = self.height() / self.myPhoto.height()
        if wRatio <= hRatio:
            self.myPhotoDestRect.setWidth(self.myPhotoSrcRect.width() * wRatio - 2)
            self.myPhotoDestRect.setHeight(self.myPhotoSrcRect.height() * wRatio - 2)
        else:
            self.myPhotoDestRect.setWidth(self.myPhotoSrcRect.width() * hRatio - 2)
            self.myPhotoDestRect.setHeight(self.myPhotoSrcRect.height() * hRatio - 2)

        # center the photo dest rect
        self.myPhotoDestRect.moveTo(self.width() / 2 - self.myPhotoDestRect.width() / 2,
                                    self.height() / 2 - self.myPhotoDestRect.height() / 2)

        # NOTE - THESE ARE THE MOST IMPORTANT COMPUTATIONS FROM WHICH EVERYTHING ELSE IS PLOTTED
        self.viewCenter = (self.width() / 2, self.height() / 2)
        self.myPhotoRadius = self.myPhotoDestRect.height() / 2
        self.myPhotoDiameter = self.myPhotoRadius * 2
        self.myPhotoTopLeft = ((self.viewCenter[0] - self.myPhotoRadius), (self.viewCenter[1] - self.myPhotoRadius))

        # compute new scaled font size
        self.fontScaled = QFont('Courier New', self.myPhotoRadius * (1/(101-common.AppSettings["HUDTextScale"])))
        self.fontMetrics = QFontMetrics(self.fontScaled)

        # compute sampling pattern collision bounds
        ViewFisheye.SampleRadius = self.myPhotoRadius / 50
        hFOV = common.DataConfig["RadianceFOV"] / 2
        for i in range(0, len(common.SamplingPattern)):
            # compute sample bounds
            u, v = utility_angles.SkyCoord2FisheyeUV(common.SamplingPattern[i][0], common.SamplingPattern[i][1])
            x = self.myPhotoTopLeft[0] + (u * self.myPhotoDiameter)
            y = self.myPhotoTopLeft[1] + (v * self.myPhotoDiameter)
            self.samplePoints[i] = (x, y)
            # compute sampling pattern actual sampling areas (projected differential angle area)
            p1 = utility_angles.SkyCoord2FisheyeUV(common.SamplingPattern[i][0] - hFOV, common.SamplingPattern[i][1] - hFOV)
            p2 = utility_angles.SkyCoord2FisheyeUV(common.SamplingPattern[i][0] - hFOV, common.SamplingPattern[i][1] + hFOV)
            p3 = utility_angles.SkyCoord2FisheyeUV(common.SamplingPattern[i][0] + hFOV, common.SamplingPattern[i][1] + hFOV)
            p4 = utility_angles.SkyCoord2FisheyeUV(common.SamplingPattern[i][0] + hFOV, common.SamplingPattern[i][1] - hFOV)
            p1 = QPoint(self.myPhotoTopLeft[0] + (p1[0] * self.myPhotoDiameter), self.myPhotoTopLeft[1] + (p1[1] * self.myPhotoDiameter))
            p2 = QPoint(self.myPhotoTopLeft[0] + (p2[0] * self.myPhotoDiameter), self.myPhotoTopLeft[1] + (p2[1] * self.myPhotoDiameter))
            p3 = QPoint(self.myPhotoTopLeft[0] + (p3[0] * self.myPhotoDiameter), self.myPhotoTopLeft[1] + (p3[1] * self.myPhotoDiameter))
            p4 = QPoint(self.myPhotoTopLeft[0] + (p4[0] * self.myPhotoDiameter), self.myPhotoTopLeft[1] + (p4[1] * self.myPhotoDiameter))
            self.sampleAreaVisible[i] = [p1, p2, p3, p4]

        # compute compass lines
        self.compassTicks.clear()
        tickLength = self.myPhotoRadius / 90
        for angle in range(0, 360, 10):
            theta = 360 - ((angle + 270) % 360)  # angles eastward from North, North facing down
            rads = theta * math.pi / 180.0
            cx1 = (math.cos(rads) * (self.myPhotoRadius - tickLength)) + self.viewCenter[0]
            cy1 = (math.sin(rads) * (self.myPhotoRadius - tickLength)) + self.viewCenter[1]
            cx2 = (math.cos(rads) * self.myPhotoRadius) + self.viewCenter[0]
            cy2 = (math.sin(rads) * self.myPhotoRadius) + self.viewCenter[1]
            lx1 = (math.cos(rads) * (self.myPhotoRadius - tickLength*4)) + self.viewCenter[0] - self.fontMetrics.width(str(angle))/2
            ly1 = (math.sin(rads) * (self.myPhotoRadius - tickLength*4)) + self.viewCenter[1] - self.fontMetrics.height()/2
            self.compassTicks.append([cx1, cy1, cx2, cy2, lx1, ly1, angle])  # x1, y1, x2, y2, x1lbl, y1lbl, angle

        # compute new grid for debugging coordinates
        griddivs = 5
        gridwidth = int(round(self.myPhotoDiameter / griddivs))
        self.gridpoints = []
        self.gridUVs = []
        self.gridskycoords = []
        for r in range(1, griddivs):
            for c in range(1, griddivs):
                point = (self.myPhotoTopLeft[0] + (c * gridwidth), self.myPhotoTopLeft[1] + (r * gridwidth))
                self.gridpoints.append(point)
                u = (point[0] - self.myPhotoTopLeft[0]) / self.myPhotoDiameter
                v = (point[1] - self.myPhotoTopLeft[1]) / self.myPhotoDiameter
                self.gridUVs.append((u, v))
                t, p = utility_angles.FisheyeUV2SkyCoord(u, v)
                self.gridskycoords.append((t, p))

        # compute lens (ideal and actual) radii for drawn latitude ellipses along zenith
        self.lensIdealRadii.clear()
        self.lensRealRadii.clear()
        for alt in common.SamplingPatternAlts:
            # ideal lens
            u, v = utility_angles.SkyCoord2FisheyeUV(90, alt, lenswarp=False)
            x = self.myPhotoTopLeft[0] + (u * self.myPhotoDiameter)
            r = x - self.viewCenter[0]
            self.lensIdealRadii.append((r, alt))  # (radius, altitude)
            # warped lens
            u, v = utility_angles.SkyCoord2FisheyeUV(90, alt)
            x = self.myPhotoTopLeft[0] + (u * self.myPhotoDiameter)
            r = x - self.viewCenter[0]
            self.lensRealRadii.append((r, alt))   # (radius, altitude)

        # compute sun path screen points
        self.pathSun = QPainterPath()
        if len(self.sunPathPoints) > 0:
            azi, alt, dt = self.sunPathPoints[0]
            u, v = utility_angles.SkyCoord2FisheyeUV(azi, alt)
            x = self.myPhotoTopLeft[0] + (u * self.myPhotoDiameter)
            y = self.myPhotoTopLeft[1] + (v * self.myPhotoDiameter)
            self.pathSun.moveTo(x, y)
            for i in range(1, len(self.sunPathPoints)):
                azi, alt, dt = self.sunPathPoints[i]
                u, v = utility_angles.SkyCoord2FisheyeUV(azi, alt)
                x = self.myPhotoTopLeft[0] + (u * self.myPhotoDiameter)
                y = self.myPhotoTopLeft[1] + (v * self.myPhotoDiameter)
                self.pathSun.lineTo(x, y)

        # compute sun position screen point
        u, v = utility_angles.SkyCoord2FisheyeUV(self.sunPosition[0], self.sunPosition[1])
        x = self.myPhotoTopLeft[0] + (u * self.myPhotoDiameter)
        y = self.myPhotoTopLeft[1] + (v * self.myPhotoDiameter)
        self.sunPositionVisible = (x, y)

        # compute new mask
        self.mask = QPixmap(self.width(), self.height()).toImage()

    def paintEvent(self, event):
        super().paintEvent(event)
        painter = QPainter()
        painter.begin(self)

        # background
        brushBG = QBrush(Qt.black, Qt.SolidPattern)
        if not common.AppSettings["ShowMask"]:
            brushBG.setColor(Qt.darkGray)
            brushBG.setStyle(Qt.Dense1Pattern)
            painter.setBackground(Qt.gray)
        else:
            brushBG.setColor(Qt.black)
            brushBG.setStyle(Qt.SolidPattern)
            painter.setBackground(Qt.black)
        painter.setBackgroundMode(Qt.OpaqueMode)
        painter.setBrush(brushBG)
        painter.setPen(Qt.NoPen)
        painter.drawRect(0, 0, self.width(), self.height())

        # draw photo
        if not self.myPhoto.isNull():
            # rotate and draw photo as specified by user
            transform = QTransform()
            transform.translate(self.myPhotoDestRect.center().x(), self.myPhotoDestRect.center().y())
            transform.rotate(-self.myPhotoRotation)
            transform.translate(-self.myPhotoDestRect.center().x(), -self.myPhotoDestRect.center().y())
            painter.setTransform(transform)
            painter.drawImage(self.myPhotoDestRect, self.myPhoto, self.myPhotoSrcRect) # draw it
            painter.resetTransform()

            # useful local vars
            centerPoint = QPoint(self.viewCenter[0], self.viewCenter[1])
            destRect = QRect(0, 0, self.myPhotoDestRect.width(), self.myPhotoDestRect.height())
            fontWidth = self.fontMetrics.width("X")

            # mask
            if common.AppSettings["ShowMask"]:
                maskPainter = QPainter()
                maskPainter.begin(self.mask)
                maskPainter.setBrush(QBrush(Qt.magenta, Qt.SolidPattern))
                maskPainter.drawEllipse(self.viewCenter[0] - self.myPhotoRadius, self.viewCenter[1] - self.myPhotoRadius, self.myPhotoDiameter, self.myPhotoDiameter)
                maskPainter.end()
                painter.setCompositionMode(QPainter.CompositionMode_DestinationIn)
                painter.drawImage(0, 0, self.mask)
                painter.setCompositionMode(QPainter.CompositionMode_SourceOver)

            # HUD
            if common.AppSettings["ShowHUD"]:
                painter.setBackgroundMode(Qt.TransparentMode)
                #painter.setBackground(Qt.black)
                painter.setBrush(Qt.NoBrush)
                painter.setFont(self.fontScaled)

                # draw UV grid
                if common.AppSettings["ShowUVGrid"]:
                    painter.setPen(self.penText)
                    # box
                    tl = self.myPhotoTopLeft
                    tr = (self.viewCenter[0] + self.myPhotoRadius, self.viewCenter[1] - self.myPhotoRadius)
                    bl = (self.viewCenter[0] - self.myPhotoRadius, self.viewCenter[1] + self.myPhotoRadius)
                    br = (self.viewCenter[0] + self.myPhotoRadius, self.viewCenter[1] + self.myPhotoRadius)
                    painter.drawLine(tl[0], tl[1], tr[0], tr[1])
                    painter.drawLine(bl[0], bl[1], br[0], br[1])
                    painter.drawLine(tl[0], tl[1], bl[0], bl[1])
                    painter.drawLine(tr[0], tr[1], br[0], br[1])
                    # crosshairs
                    painter.drawLine(tl[0], self.viewCenter[1], tr[0], self.viewCenter[1])
                    painter.drawLine(self.viewCenter[0], tr[1], self.viewCenter[0], br[1])
                    # labels
                    destRect.setCoords(tl[0] + 4, tl[1] + 4, self.width(), self.height())
                    painter.drawText(destRect, Qt.AlignTop | Qt.AlignLeft, "0")
                    destRect.setCoords(tr[0] - (fontWidth+4), tr[1] + 4, self.width(), self.height())
                    painter.drawText(destRect, Qt.AlignTop | Qt.AlignLeft, "1")
                    destRect.setCoords(bl[0] + 3, bl[1] - (self.fontMetrics.height()+3), self.width(), self.height())
                    painter.drawText(destRect, Qt.AlignTop | Qt.AlignLeft, "1")
                    destRect.setCoords(br[0] - (fontWidth+3), br[1] - (self.fontMetrics.height()+3), self.width(), self.height())
                    painter.drawText(destRect, Qt.AlignTop | Qt.AlignLeft, "1")
                    # grid coordinates
                    gpntrad = self.myPhotoRadius * 0.005
                    painter.setPen(self.penText)
                    painter.setBrush(self.brushGrid)
                    painter.setFont(self.fontScaled)
                    for i in range(0, len(self.gridpoints)):
                        point = self.gridpoints[i]
                        u, v = self.gridUVs[i]
                        t, p = self.gridskycoords[i]
                        painter.drawEllipse(QPoint(point[0], point[1]), gpntrad, gpntrad)
                        destRect.setCoords(point[0]+fontWidth/2, point[1]-self.fontMetrics.height(), self.width(), self.height())
                        textuv = "{0:.1f}u, {1:.1f}v".format(round(u,1), round(v,1))
                        painter.drawText(destRect, Qt.AlignTop | Qt.AlignLeft, textuv)
                        destRect.setCoords(point[0]+fontWidth/2, point[1], self.width(), self.height())
                        textuv = "{0:d}°, {1:d}°".format(int(round(t)), int(round(p)))
                        painter.drawText(destRect, Qt.AlignTop | Qt.AlignLeft, textuv)
                    painter.setBrush(Qt.NoBrush)

                # draw lens warp
                if common.AppSettings["ShowLensWarp"]:
                    # ideal lens longitudes along azimuth
                    painter.setPen(self.penText)
                    for i in range(0, int(len(self.compassTicks)/2), 3):
                        p1 = QPoint(self.compassTicks[i][2], self.compassTicks[i][3])
                        p2 = QPoint(self.compassTicks[i+18][2], self.compassTicks[i+18][3])  # tick opposite 180 degrees
                        painter.drawLine(p1, p2)
                    # ideal lens latitudes along zenith
                    for r, alt in self.lensIdealRadii:
                        painter.drawEllipse(centerPoint, r, r)
                    # actual/warped lens latitudes along zenith
                    painter.setPen(self.penLens)
                    for r, alt in self.lensRealRadii:
                        painter.drawEllipse(centerPoint, r, r)
                        destRect.setCoords(self.viewCenter[0] + r + 3, self.viewCenter[1] - (self.fontMetrics.height() + 3), self.width(), self.height())
                        painter.drawText(destRect, Qt.AlignTop | Qt.AlignLeft, "{0:d}°".format(int(alt)))

                # draw compass
                if common.AppSettings["ShowCompass"]:
                    # compass ticks text shadows
                    if common.AppSettings["ShowShadows"]:
                        painter.setPen(self.penShadowText)
                        for tick in self.compassTicks:
                            destRect.setCoords(tick[4] + 1, tick[5] + 1, self.width(), self.height())
                            painter.drawText(destRect, Qt.AlignTop | Qt.AlignLeft, str(tick[6])+"°")
                    # compass ticks text
                    painter.setPen(self.penText)
                    for tick in self.compassTicks:
                        painter.drawLine(tick[0], tick[1], tick[2], tick[3])
                        destRect.setCoords(tick[4], tick[5], self.width(), self.height())
                        painter.drawText(destRect, Qt.AlignTop | Qt.AlignLeft, str(tick[6])+"°")
                    # photo radius
                    #painter.drawEllipse(self.viewCenter[0] - self.myPhotoRadius, self.viewCenter[1] - self.myPhotoRadius, self.myPhotoDiameter, self.myPhotoDiameter)
                    painter.drawEllipse(centerPoint, self.myPhotoRadius, self.myPhotoRadius)
                    # cardinal directions
                    destRect.setCoords(self.viewCenter[0] - self.myPhotoRadius - (fontWidth+4), self.viewCenter[1] - self.fontMetrics.height()/2, self.width(), self.height())
                    painter.drawText(destRect, Qt.AlignTop | Qt.AlignLeft, "W")
                    destRect.setCoords(self.viewCenter[0] + self.myPhotoRadius + 4, self.viewCenter[1] - self.fontMetrics.height()/2, self.width(), self.height())
                    painter.drawText(destRect, Qt.AlignTop | Qt.AlignLeft, "E")
                    destRect.setCoords(self.viewCenter[0] - fontWidth/2, self.viewCenter[1] - self.myPhotoRadius - (self.fontMetrics.height()+3), self.width(), self.height())
                    painter.drawText(destRect, Qt.AlignTop | Qt.AlignLeft, "S")
                    destRect.setCoords(self.viewCenter[0] - fontWidth/2, self.viewCenter[1] + self.myPhotoRadius + 3, self.width(), self.height())
                    painter.drawText(destRect, Qt.AlignTop | Qt.AlignLeft, "N")

                # draw sampling pattern
                if common.AppSettings["ShowSamples"]:
                    painter.setPen(self.penText)
                    for i, points in enumerate(self.sampleAreaVisible):
                        painter.drawLine(QLine(points[0], points[1]))
                        painter.drawLine(QLine(points[1], points[2]))
                        painter.drawLine(QLine(points[2], points[3]))
                        painter.drawLine(QLine(points[3], points[0]))
                    for i in range(0, len(self.samplePoints)):
                        p = self.samplePoints[i]
                        painter.drawEllipse(QPoint(p[0],p[1]), ViewFisheye.SampleRadius, ViewFisheye.SampleRadius)
                        painter.drawText(p[0] + ViewFisheye.SampleRadius, p[1], str(i))

                # draw sun path
                if common.AppSettings["ShowSunPath"]:
                    sunradius = self.myPhotoRadius * 0.1
                    # shadows
                    painter.setPen(self.penShadowSun)
                    if common.AppSettings["ShowShadows"]:
                        painter.drawEllipse(QPoint(self.sunPositionVisible[0]+1, self.sunPositionVisible[1]+1), sunradius, sunradius)
                        self.pathSun.translate(1.0, 1.0)
                        painter.drawPath(self.pathSun)
                        self.pathSun.translate(-1.0, -1.0)
                        for i in range(0, self.pathSun.elementCount()):
                            e = self.pathSun.elementAt(i)
                            destRect.setCoords(e.x, e.y + self.fontMetrics.height()/2 + 1, self.width(), self.height())
                            painter.drawText(destRect, Qt.AlignTop | Qt.AlignLeft, str(self.sunPathPoints[i][2].hour))
                    # sun, path, hours
                    painter.setPen(self.penSun)
                    painter.drawEllipse(QPoint(self.sunPositionVisible[0], self.sunPositionVisible[1]), sunradius, sunradius)
                    painter.drawPath(self.pathSun)
                    for i in range(0, self.pathSun.elementCount()):
                        e = self.pathSun.elementAt(i)
                        destRect.setCoords(e.x, e.y + self.fontMetrics.height() / 2, self.width(), self.height())
                        painter.drawText(destRect, Qt.AlignTop | Qt.AlignLeft, str(self.sunPathPoints[i][2].hour))

                # draw selected samples (ALWAYS)
                r = QRect()
                # shadows
                if common.AppSettings["ShowShadows"]:
                    painter.setPen(self.penShadowSelected)
                    for i in self.samplesSelected:
                        x, y = self.samplePoints[i]
                        painter.drawEllipse(QPoint(x+1, y+1), ViewFisheye.SampleRadius, ViewFisheye.SampleRadius)
                # samples
                for i in self.samplesSelected:
                    painter.setPen(self.penSelected[i])
                    x, y = self.samplePoints[i]
                    painter.drawEllipse(QPoint(x, y), ViewFisheye.SampleRadius, ViewFisheye.SampleRadius)

                # draw user's selection bounds
                if (abs(self.dragSelectRect.right()-self.dragSelectRect.left()) >= ViewFisheye.SelectionRectMin and
                    abs(self.dragSelectRect.bottom()-self.dragSelectRect.top()) >= ViewFisheye.SelectionRectMin):
                    painter.setPen(self.penSelectRect)
                    painter.drawRect(self.dragSelectRect)

                # draw timestamp
                painter.setPen(self.penText)
                painter.setFont(self.fontFixed)
                destRect.setCoords(10, 10, self.width() / 2, 50)
                painter.drawText(destRect, Qt.AlignTop | Qt.AlignLeft, str(self.myPhotoTime))
                # draw sky cover assessment
                destRect.setCoords(10, 25, self.width(), self.height())
                painter.drawText(destRect, Qt.AlignTop | Qt.AlignLeft, self.skyCover.name + "/" + common.SkyCoverDesc[self.skyCover])
                # draw photo rotation
                if self.myPhotoRotation != 0:
                    destRect.setCoords(10, self.height()-25, self.width(), self.height())
                    painter.drawText(destRect, Qt.AlignTop | Qt.AlignLeft, "Rotation: " + str(self.myPhotoRotation) + "°")

                # where is the mouse relative to the center?
                # this is used as an optimization to only display information when mouse is in fisheye portion
                dx = self.coordsMouse[0] - self.viewCenter[0]
                dy = self.coordsMouse[1] - self.viewCenter[1]
                distance = math.sqrt((dx * dx) + (dy * dy))  # distance from mouse to view center

                # coordinates we are interested in
                #self.coordsMouse    # x,y of this widget
                coordsxy = (-1, -1)  # x,y over photo as scaled/rendered on this widget
                coordsXY = (-1, -1)  # x,y over actual original photo on disk
                coordsUV = (-1, -1)  # u,v coords of fisheye portion of photo w/ 0,0 top left and 1,1 bottom right
                coordsTP = (-1, -1)  # theta,phi polar coordinates
                # text
                textxy = "-1, -1 xy"
                textXY = "-1, -1 xy"
                textUV = "-1, -1 uv"
                textTP = "-1, -1 θφ"
                textPX = "0 0 0 px"

                # compute all relevant information only when mouse is within fisheye portion of photo
                if distance < self.myPhotoRadius:
                    coordsxy = (self.coordsMouse[0] - self.myPhotoDestRect.x(),
                                self.coordsMouse[1] - self.myPhotoDestRect.y())
                    coordsXY = (int(coordsxy[0] / self.myPhotoDestRect.width() * self.myPhoto.width()),
                                int(coordsxy[1] / self.myPhotoDestRect.height() * self.myPhoto.height()))
                    coordsUV = ((self.coordsMouse[0] - self.myPhotoTopLeft[0]) / self.myPhotoDiameter,
                                (self.coordsMouse[1] - self.myPhotoTopLeft[1]) / self.myPhotoDiameter)
                    coordsTP = utility_angles.FisheyeUV2SkyCoord(coordsUV[0], coordsUV[1])
                    # text
                    textxy = str(coordsxy[0]) + ", " + str(coordsxy[1]) + " xy"
                    textXY = str(coordsXY[0]) + ", " + str(coordsXY[1]) + " xy"
                    textUV = "{:.2f}".format(coordsUV[0]) + ", " + "{:.2f}".format(coordsUV[1]) + " uv"
                    textTP = "{:.2f}".format(coordsTP[0]) + ", " + "{:.2f}".format(coordsTP[1]) + " θφ"

                # pixels colors
                pixreg = common.AppSettings["PixelRegion"]
                colorsRegion = np.zeros((pixreg, pixreg, 4))
                colorFinal = colorsRegion[0,0]  # RGBA of pixel under mouse of photo on disk
                # colorFinal = self.myPhoto.pixelColor(coordsXY[0], coordsXY[1])
                if distance < self.myPhotoRadius:
                    halfdim = int(pixreg / 2)
                    rstart = coordsXY[1]-halfdim
                    rstop = coordsXY[1]+halfdim+1
                    cstart = coordsXY[0]-halfdim
                    cstop = coordsXY[0]+halfdim+1
                    if (rstart >= 0 and rstop<=self.myPhotoPixels.shape[0] and
                        cstart >= 0 and cstop<=self.myPhotoPixels.shape[1]):
                        colorsRegion = self.myPhotoPixels[rstart:rstop, cstart:cstop]
                        colorFinal = colorsRegion[halfdim, halfdim]
                        if pixreg > 1:  # with pixel weighting
                            colorFinal = utility_data.collectPixels([coordsXY], [pixreg], pixels=self.myPhotoPixels, weighting=common.PixelWeighting(common.AppSettings["PixelWeighting"]))[0]
                    textPX = str(colorFinal[0]) + " " + str(colorFinal[1]) + " " + str(colorFinal[2]) + " px"

                # draw HUD text strings
                # x,y coords
                destRect.setCoords(0, 0, self.width() - 10, self.height()- 124)
                painter.drawText(destRect, Qt.AlignBottom | Qt.AlignRight, textxy)
                # X,Y coords
                destRect.setCoords(0, 0, self.width() - 10, self.height() - 114)
                painter.drawText(destRect, Qt.AlignBottom | Qt.AlignRight, textXY)
                # u,v coords
                destRect.setCoords(0, 0, self.width() - 10, self.height() - 104)
                painter.drawText(destRect, Qt.AlignBottom | Qt.AlignRight, textUV)
                # t,p coords
                destRect.setCoords(0, 0, self.width() - 10, self.height() - 94)
                painter.drawText(destRect, Qt.AlignBottom | Qt.AlignRight, textTP)
                # pixel color
                destRect.setCoords(0, 0, self.width() - 10, self.height() - 84)
                painter.drawText(destRect, Qt.AlignBottom | Qt.AlignRight, textPX)

                # compute pixel visualization coordinates
                circleX = self.width() - 10 - ViewFisheye.SelectedPixelBox - 10 - ViewFisheye.SelectedPixelBox - 10 - ViewFisheye.SelectedPixelBox
                circleY = self.height() - 10 - ViewFisheye.SelectedPixelBox
                pixelsX = self.width() - 10 - ViewFisheye.SelectedPixelBox - 10 - ViewFisheye.SelectedPixelBox
                pixelsY = self.height() - 10 - ViewFisheye.SelectedPixelBox
                pixelsWeightedX = self.width() - ViewFisheye.SelectedPixelBox - 10
                pixelsWeightedY = self.height() - 10 - ViewFisheye.SelectedPixelBox

                # draw pixel visualization - fills
                pixreg = common.AppSettings["PixelRegion"]
                if distance < self.myPhotoRadius:
                    painter.setPen(Qt.NoPen)
                    # pixel region
                    pixdim = ViewFisheye.SelectedPixelBox / pixreg
                    for row in range(0, pixreg):
                        for col in range(0, pixreg):
                            color = colorsRegion[row, col]
                            color = QColor(color[0], color[1], color[2])
                            painter.setBrush(QBrush(color, Qt.SolidPattern))
                            painter.drawRect(pixelsX + (col * pixdim), pixelsY + (row * pixdim), math.ceil(pixdim), math.ceil(pixdim))
                    # final pixel color
                    color = QColor(colorFinal[0], colorFinal[1], colorFinal[2])
                    painter.setBrush(QBrush(color, Qt.SolidPattern))
                    cx = circleX + (coordsUV[0] * ViewFisheye.SelectedPixelBox)
                    cy = circleY + (coordsUV[1] * ViewFisheye.SelectedPixelBox)
                    painter.drawEllipse(cx - 5, cy - 5, 10, 10)
                    painter.drawRect(pixelsWeightedX, pixelsWeightedY, ViewFisheye.SelectedPixelBox, ViewFisheye.SelectedPixelBox)

                # draw pixel visualization - outlines
                painter.setPen(self.penText)
                painter.setBrush(Qt.NoBrush)
                painter.drawEllipse(circleX, circleY, ViewFisheye.SelectedPixelBox, ViewFisheye.SelectedPixelBox)
                painter.drawRect(pixelsX, pixelsY, ViewFisheye.SelectedPixelBox, ViewFisheye.SelectedPixelBox)
                painter.drawRect(pixelsWeightedX, pixelsWeightedY, ViewFisheye.SelectedPixelBox, ViewFisheye.SelectedPixelBox)

                # raw data missing indicator
                # if (not self.rawAvailable):
                #     painter.drawPixmap(pixelX + ViewFisheye.SelectedPixelBox / 2,
                #                        pixelY + ViewFisheye.SelectedPixelBox / 2,
                #                        self.iconWarning)

        # end draw
        painter.end()
示例#22
0
class ResizeHelper(QWidget):
    offsetChanged = pyqtSignal(QPoint)
    offsetXChanged = pyqtSignal(int)
    offsetYChanged = pyqtSignal(int)
    offsetBoundsChanged = pyqtSignal(QRect)

    def __init__(self, parent = None):
        super().__init__(parent)
        
        self.mMouseAnchorPoint = QPoint()
        self.mOffset = QPoint()
        self.mOldSize = QSize()
        self.mDragging = False
        self.mOffsetBounds = QRect()
        self.mScale = 0.0
        self.mNewSize = QSize()
        self.mOrigOffset = QPoint()
        
        self.setMinimumSize(20, 20)
        self.setOldSize(QSize(1, 1))

    def oldSize(self):
        return self.mOldSize

    def newSize(self):
        return self.mNewSize

    def offset(self):
        return self.mOffset

    def offsetBounds(self):
        return self.mOffsetBounds

    def setOldSize(self, size):
        self.mOldSize = size
        self.recalculateMinMaxOffset()
        self.recalculateScale()

    def setNewSize(self, size):
        self.mNewSize = size
        self.recalculateMinMaxOffset()
        self.recalculateScale()

    def setOffset(self, offset):
        # Clamp the offset within the offset bounds
        newOffset = QPoint(min(self.mOffsetBounds.right(),
                            max(self.mOffsetBounds.left(), offset.x())),
                                min(self.mOffsetBounds.bottom(),
                                    max(self.mOffsetBounds.top(), offset.y())))
        if (self.mOffset != newOffset):
            xChanged = self.mOffset.x() != newOffset.x()
            yChanged = self.mOffset.y() != newOffset.y()
            self.mOffset = newOffset
            if (xChanged):
                self.offsetXChanged.emit(self.mOffset.x())
            if (yChanged):
                self.offsetYChanged.emit(self.mOffset.y())
            self.offsetChanged.emit(self.mOffset)
            self.update()

    ## Method to set only the X offset, provided for convenience. */
    def setOffsetX(self, x):
        self.setOffset(QPoint(x, self.mOffset.y()))

    ## Method to set only the Y offset, provided for convenience. */
    def setOffsetY(self, y):
        self.setOffset(QPoint(self.mOffset.x(), y))

    ## Method to set only new width, provided for convenience. */
    def setNewWidth(self, width):
        self.mNewSize.setWidth(width)
        self.recalculateMinMaxOffset()
        self.recalculateScale()

    ## Method to set only new height, provided for convenience. */
    def setNewHeight(self, height):
        self.mNewSize.setHeight(height)
        self.recalculateMinMaxOffset()
        self.recalculateScale()

    def paintEvent(self, event):
        _size = self.size() - QSize(2, 2)
        if (_size.isEmpty()):
            return
        origX = (_size.width() - self.mNewSize.width() * self.mScale) / 2 + 0.5
        origY = (_size.height() - self.mNewSize.height() * self.mScale) / 2 + 0.5
        oldRect = QRect(self.mOffset, self.mOldSize)
        painter = QPainter(self)
        painter.translate(origX, origY)
        painter.scale(self.mScale, self.mScale)
        pen = QPen(Qt.black)
        pen.setCosmetic(True)
        painter.setPen(pen)
        painter.drawRect(QRect(QPoint(0, 0), self.mNewSize))
        pen.setColor(Qt.white)
        painter.setPen(pen)
        painter.setBrush(Qt.white)
        painter.setOpacity(0.5)
        painter.drawRect(oldRect)
        pen.setColor(Qt.black)
        pen.setStyle(Qt.DashLine)
        painter.setOpacity(1.0)
        painter.setBrush(Qt.NoBrush)
        painter.setPen(pen)
        painter.drawRect(oldRect)
        painter.end()

    def mousePressEvent(self, event):
        self.mMouseAnchorPoint = event.pos()
        self.mOrigOffset = self.mOffset
        self.mDragging = event.button() == Qt.LeftButton

    def mouseMoveEvent(self, event):
        if (not self.mDragging):
            return
        pos = event.pos()
        if (pos != self.mMouseAnchorPoint):
            self.setOffset(self.mOrigOffset + (pos - self.mMouseAnchorPoint) / self.mScale)
            self.offsetChanged.emit(self.mOffset)

    def resizeEvent(self, event):
        self.recalculateScale()

    def recalculateScale(self):
        _size = self.size() - QSize(2, 2)
        if (_size.isEmpty()):
            return
        if self.mOldSize.width() < self.mNewSize.width():
            width = self.mNewSize.width()
        else:
            width = 2 * self.mOldSize.width() - self.mNewSize.width()
        if self.mOldSize.height() < self.mNewSize.height():
            height = self.mNewSize.height()
        else:
            height = 2 * self.mOldSize.height() - self.mNewSize.height()

        # Pick the smallest scale
        scaleW = _size.width() / width
        scaleH = _size.height() / height
        if scaleW < scaleH:
            self.mScale = scaleW
        else:
            self.mScale = scaleH

        self.update()

    def recalculateMinMaxOffset(self):
        offsetBounds = self.mOffsetBounds
        if (self.mOldSize.width() <= self.mNewSize.width()):
            offsetBounds.setLeft(0)
            offsetBounds.setRight(self.mNewSize.width() - self.mOldSize.width())
        else:
            offsetBounds.setLeft(self.mNewSize.width() - self.mOldSize.width())
            offsetBounds.setRight(0)

        if (self.mOldSize.height() <= self.mNewSize.height()):
            offsetBounds.setTop(0)
            offsetBounds.setBottom(self.mNewSize.height() - self.mOldSize.height())
        else:
            offsetBounds.setTop(self.mNewSize.height() - self.mOldSize.height())
            offsetBounds.setBottom(0)

        if (self.mOffsetBounds != offsetBounds):
            self.mOffsetBounds = offsetBounds
            self.offsetBoundsChanged.emit(self.mOffsetBounds)
示例#23
0
class SlippyMap(QObject):

    updated = pyqtSignal(QRect)

    def __init__(self, parent=None):
        super(SlippyMap, self).__init__(parent)

        self._offset = QPoint()
        self._tilesRect = QRect()
        self._tilePixmaps = {} # Point(x, y) to QPixmap mapping
        self._manager = QNetworkAccessManager()
        self._url = QUrl()
        # public vars
        self.width = 400
        self.height = 300
        self.zoom = 15
        self.latitude = 59.9138204
        self.longitude = 10.7387413

        self._emptyTile = QPixmap(TDIM, TDIM)
        self._emptyTile.fill(Qt.lightGray)

        cache = QNetworkDiskCache()
        cache.setCacheDirectory(
            QStandardPaths.writableLocation(QStandardPaths.CacheLocation))
        self._manager.setCache(cache)
        self._manager.finished.connect(self.handleNetworkData)

    def invalidate(self):
        if self.width <= 0 or self.height <= 0:
            return

        ct = tileForCoordinate(self.latitude, self.longitude, self.zoom)
        tx = ct.x()
        ty = ct.y()

        # top-left corner of the center tile
        xp = int(self.width / 2 - (tx - math.floor(tx)) * TDIM)
        yp = int(self.height / 2 - (ty - math.floor(ty)) * TDIM)

        # first tile vertical and horizontal
        xa = (xp + TDIM - 1) / TDIM
        ya = (yp + TDIM - 1) / TDIM
        xs = int(tx) - xa
        ys = int(ty) - ya

        # offset for top-left tile
        self._offset = QPoint(xp - xa * TDIM, yp - ya * TDIM)

        # last tile vertical and horizontal
        xe = int(tx) + (self.width - xp - 1) / TDIM
        ye = int(ty) + (self.height - yp - 1) / TDIM

        # build a rect
        self._tilesRect = QRect(xs, ys, xe - xs + 1, ye - ys + 1)

        if self._url.isEmpty():
            self.download()

        self.updated.emit(QRect(0, 0, self.width, self.height))

    def render(self, p, rect):
        for x in range(self._tilesRect.width()):
            for y in range(self._tilesRect.height()):
                tp = Point(x + self._tilesRect.left(), y + self._tilesRect.top())
                box = self.tileRect(tp)
                if rect.intersects(box):
                    p.drawPixmap(box, self._tilePixmaps.get(tp, self._emptyTile))
   
    def pan(self, delta):
        dx = QPointF(delta) / float(TDIM)
        center = tileForCoordinate(self.latitude, self.longitude, self.zoom) - dx
        self.latitude = latitudeFromTile(center.y(), self.zoom)
        self.longitude = longitudeFromTile(center.x(), self.zoom)
        self.invalidate()

    # slots
    def handleNetworkData(self, reply):
        img = QImage()
        tp = Point(reply.request().attribute(QNetworkRequest.User))
        url = reply.url()
        if not reply.error():
            if img.load(reply, None):
                self._tilePixmaps[tp] = QPixmap.fromImage(img)
        reply.deleteLater()
        self.updated.emit(self.tileRect(tp))

        # purge unused tiles
        bound = self._tilesRect.adjusted(-2, -2, 2, 2)
        for tp in list(self._tilePixmaps.keys()):
            if not bound.contains(tp):
                del self._tilePixmaps[tp]
        self.download()

    def download(self):
        grab = None
        for x in range(self._tilesRect.width()):
            for y in range(self._tilesRect.height()):
                tp = Point(self._tilesRect.topLeft() + QPoint(x, y))
                if tp not in self._tilePixmaps:
                    grab = QPoint(tp)
                    break

        if grab is None:
            self._url = QUrl()
            return

        path = 'http://tile.openstreetmap.org/%d/%d/%d.png' % (self.zoom, grab.x(), grab.y())
        self._url = QUrl(path)
        request = QNetworkRequest()
        request.setUrl(self._url)
        request.setRawHeader(b'User-Agent', b'Nokia (PyQt) Graphics Dojo 1.0')
        request.setAttribute(QNetworkRequest.User, grab)
        self._manager.get(request)

    def tileRect(self, tp):
        t = tp - self._tilesRect.topLeft()
        x = t.x() * TDIM + self._offset.x()
        y = t.y() * TDIM + self._offset.y()

        return QRect(x, y, TDIM, TDIM)
示例#24
0
	def createTabPane ( self, paneClassName, title = None, nOverrideDockDirection = -1,
	                    bLoadLayoutPersonalization = False ):
		pane = None
		isToolAlreadyCreated = False

		from candy_editor.qt.controls.EditorCommon.IEditor import getIEditor
		classDesc = getIEditor ().getClassFactory ().findClass ( paneClassName )
		if classDesc is None or classDesc.systemClassID () != ESystemClassID.ESYSTEM_CLASS_VIEWPANE:
			qWarning ( "CTabPaneManager::createTabPane return None %s pane class not found." % paneClassName )
			return None

		viewPaneClass = classDesc

		if viewPaneClass.singlePane ():
			for i in range ( 0, len ( self.panes ) ):
				pane = self.panes[ i ]
				if pane.class_ == paneClassName and pane.viewCreated:
					isToolAlreadyCreated = True
					break

		if not isToolAlreadyCreated:
			# print ( "CTabPaneManager.createTabPane create QTabPane for", paneClassName )
			pane = QTabPane ()
			pane.setParent ( self.parent )
			pane.class_ = paneClassName
			self.panes.append ( pane )

		dockDir = EDockingDirection.DOCK_FLOAT

		contentWidget = None

		if not isToolAlreadyCreated:
			contentWidget = self.createPaneContents ( pane )
			if contentWidget != None:
				if title == None:
					title = contentWidget.getPaneTitle ()
				dockDir = contentWidget.getDockingDirection ()
			elif title == None:
				title = viewPaneClass.getPaneTitle ()

			pane.title = title
			pane.setObjectName ( self.createObjectName ( title ) )
			pane.setWindowTitle ( title )
			pane.category = viewPaneClass.category ()

			if contentWidget:
				contentWidget.initialize ()
				if bLoadLayoutPersonalization:
					contentWidget.loadLayoutPersonalization ()
		else:
			contentWidget = pane.pane
			if contentWidget != None:
				dockDir = contentWidget.getDockingDirection ()

		bDockDirection = False

		from qt.controls.QToolWindowManager import QToolWindowAreaReference, QToolWindowAreaTarget
		toolAreaTarget = QToolWindowAreaTarget ( QToolWindowAreaReference.Floating )

		if nOverrideDockDirection != -1:
			dockDir = nOverrideDockDirection

		if dockDir == EDockingDirection.DOCK_DEFAULT:
			toolAreaTarget = QToolWindowAreaTarget ( QToolWindowAreaReference.Floating )
		elif dockDir == EDockingDirection.DOCK_TOP:
			toolAreaTarget = QToolWindowAreaTarget ( QToolWindowAreaReference.HSplitTop )
			bDockDirection = True
		elif dockDir == EDockingDirection.DOCK_BOTTOM:
			toolAreaTarget = QToolWindowAreaTarget ( QToolWindowAreaReference.HSplitBottom )
			bDockDirection = True
		elif dockDir == EDockingDirection.DOCK_LEFT:
			toolAreaTarget = QToolWindowAreaTarget ( QToolWindowAreaReference.VSplitLeft )
			bDockDirection = True
		elif dockDir == EDockingDirection.DOCK_RIGHT:
			toolAreaTarget = QToolWindowAreaTarget ( QToolWindowAreaReference.VSplitRight )
			bDockDirection = True
		elif dockDir == EDockingDirection.DOCK_FLOAT:
			toolAreaTarget = QToolWindowAreaTarget ( QToolWindowAreaReference.Floating )

		if bDockDirection:
			referencePane = self.findTabPaneByTitle ( "Perspective" ) # FIXME: No Perspective Pane
			if referencePane != None:
				toolArea = self.getToolManager ().areaOf ( referencePane )
				if toolArea != None:
					toolAreaTarget = QToolWindowAreaTarget.createByArea ( toolArea, toolAreaTarget.reference )
				else:
					toolAreaTarget = QToolWindowAreaTarget ( QToolWindowAreaReference.Floating )

		paneRect = QRect ( 0, 0, 800, 600 )
		if contentWidget != None:
			paneRect = contentWidget.getPaneRect ()
		if pane.title in self.panesHistory:
			paneHistory = self.panesHistory[ pane.title ]
			paneRect = paneHistory.rect

		maxRc = qApp.desktop ().screenGeometry ()

		minimumSize = QSize ()
		if contentWidget != None:
			minimumSize = contentWidget.getMinSize ()

		paneRect = paneRect.intersected ( maxRc )

		if paneRect.width () < 10:
			paneRect.setRight ( paneRect.left () + 10 )
		if paneRect.height () < 10:
			paneRect.setBottom ( paneRect.top () + 10 )

		pane.defaultSize = QSize ( paneRect.width (), paneRect.height () )
		pane.minimumSize = minimumSize

		toolPath = { }
		spawnLocationMap = { }  # GetIEditor()->GetPersonalizationManager()->GetState("SpawnLocation")

		if not self.bLayoutLoaded:
			toolAreaTarget = QToolWindowAreaTarget ( QToolWindowAreaReference.Hidden )
		else:
			if spawnLocationMap.setdefault ( paneClassName, False ):
				toolPath = spawnLocationMap[ paneClassName ]
				if not toolPath.setdefault ( "geometry", False ):
					t = self.getToolManager ().targetFromPath ( toolPath[ "path" ] )
					if t.reference != QToolWindowAreaReference.Floating:
						toolAreaTarget = t

		self.getToolManager ().addToolWindowTarget ( pane, toolAreaTarget )

		if toolAreaTarget.reference == QToolWindowAreaReference.Floating:
			alreadyPlaced = False
			if toolPath.setdefault ( "geometry", False ):
				alreadyPlaced = pane.window ().restoreGeometry ( toolPath[ "geometry" ] )
			if not alreadyPlaced:
				w = pane
				while not w.isWindow ():
					w = w.parentWidget ()
				i = 0
				w.move ( QPoint ( 32, 32 ) * ( i + 1 ) )
				i = ( i + 1 ) % 10
			self.onTabPaneMoved ( pane, True )

		self.toolsDirty = True

		if pane.pane != None:
			# from qt.controls.QToolWindowManager.QtViewPane import IPane
			pane.pane.signalPaneCreated.emit ( pane.pane )

		return pane
示例#25
0
class SlippyMap(QObject):
    def __init__(self, parent: QObject = None) -> None:
        super().__init__(parent)

        self.width: int = 400
        self.height: int = 300
        self.zoom: int = 15
        self.latitude: float = 59.9138204
        self.longitude: float = 10.7387413

        self.m_offset = QPoint()
        self.m_tilesRect = QRect()

        self.m_emptyTile = QPixmap(tdim, tdim)
        self.m_emptyTile.fill(Qt.lightGray)

        self.m_tilePixmaps: typing.Dict[QPointH, QPixmap] = dict()
        self.m_manager = QNetworkAccessManager()
        self.m_url = QUrl()

        cache = QNetworkDiskCache()
        cache.setCacheDirectory(
            QStandardPaths.writableLocation(QStandardPaths.CacheLocation)
        )
        self.m_manager.setCache(cache)
        self.m_manager.finished.connect(self.handleNetworkData)

    def invalidate(self) -> None:
        if self.width <= 0 or self.height <= 0:
            return

        ct = tileForCoordinate(self.latitude, self.longitude, self.zoom)
        tx = ct.x()
        ty = ct.y()

        # top-left corner of the center tile
        xp = self.width / 2 - (tx - math.floor(tx)) * tdim
        yp = self.height / 2 - (ty - math.floor(ty)) * tdim

        xa = (xp + tdim - 1) / tdim
        ya = (yp + tdim - 1) / tdim
        xs = int(tx) - xa
        ys = int(ty) - ya

        # offset for top-left tile
        self.m_offset = QPoint(xp - xa * tdim, yp - ya * tdim)

        # last tile vertical and horizontal
        xe = int(tx) + (self.width - xp - 1) / tdim
        ye = int(ty) + (self.height - yp - 1) / tdim

        # build a rect
        self.m_tilesRect = QRect(xs, ys, xe - xs + 1, ye - ys + 1)

        if self.m_url.isEmpty():
            self.download()

        self.updated.emit(QRect(0, 0, self.width, self.height))

    def render(self, painter: QPainter, rect: QRect) -> None:
        for x in range(self.m_tilesRect.width() + 1):
            for y in range(self.m_tilesRect.height() + 1):
                tp = QPoint(x + self.m_tilesRect.left(), y + self.m_tilesRect.top())
                box = self.tileRect(tp)
                if rect.intersects(box):
                    painter.drawPixmap(
                        box, self.m_tilePixmaps.get(QPointH(tp), self.m_emptyTile)
                    )

    def pan(self, delta: QPoint) -> None:
        dx = QPointF(delta) / float(tdim)
        center = tileForCoordinate(self.latitude, self.longitude, self.zoom) - dx
        self.latitude = latitudeFromTile(center.y(), self.zoom)
        self.longitude = longitudeFromTile(center.x(), self.zoom)
        self.invalidate()

    updated = pyqtSignal(QRect)

    @pyqtSlot(QNetworkReply)
    def handleNetworkData(self, reply: QNetworkReply) -> None:
        img = QImage()
        tp = reply.request().attribute(QNetworkRequest.User)
        if not reply.error():
            if not img.load(reply, ""):
                img = QImage()
        reply.deleteLater()
        self.m_tilePixmaps[QPointH(tp)] = (
            self.m_emptyTile if img.isNull() else QPixmap.fromImage(img)
        )
        self.updated.emit(self.tileRect(tp))

        # purge unused spaces
        bound = self.m_tilesRect.adjusted(-2, -2, 2, 2)
        self.m_tilePixmaps = {
            tp: pixmap
            for tp, pixmap in self.m_tilePixmaps.items()
            if bound.contains(tp)
        }

        self.download()

    @pyqtSlot()
    def download(self):
        grab = QPoint(0, 0)
        for x in range(self.m_tilesRect.width() + 1):
            for y in range(self.m_tilesRect.height() + 1):
                tp = self.m_tilesRect.topLeft() + QPoint(x, y)
                if QPointH(tp) not in self.m_tilePixmaps:
                    grab = tp
                    break

        if grab == QPoint(0, 0):
            self.m_url = QUrl()
            return
        path = "http://tile.openstreetmap.org/%d/%d/%d.png"
        self.m_url = QUrl(path % (self.zoom, grab.x(), grab.y()))
        request = QNetworkRequest()
        request.setUrl(self.m_url)
        request.setRawHeader(b"User-Agent", b"The Qt Company (Qt) Graphics Dojo 1.0")
        request.setAttribute(QNetworkRequest.User, grab)
        self.m_manager.get(request)

    def tileRect(self, tp: QPoint):
        t = tp - self.m_tilesRect.topLeft()
        x = t.x() * tdim + self.m_offset.x()
        y = t.y() * tdim + self.m_offset.y()
        return QRect(x, y, tdim, tdim)
示例#26
0
class SlippyMap(QObject):

    updated = pyqtSignal(QRect)

    def __init__(self, parent=None):
        super(SlippyMap, self).__init__(parent)

        self._offset = QPoint()
        self._tilesRect = QRect()
        self._tilePixmaps = {}  # Point(x, y) to QPixmap mapping
        self._manager = QNetworkAccessManager()
        self._url = QUrl()
        # public vars
        self.width = 400
        self.height = 300
        self.zoom = 15
        self.latitude = 59.9138204
        self.longitude = 10.7387413

        self._emptyTile = QPixmap(TDIM, TDIM)
        self._emptyTile.fill(Qt.lightGray)

        cache = QNetworkDiskCache()
        cache.setCacheDirectory(
            QStandardPaths.writableLocation(QStandardPaths.CacheLocation))
        self._manager.setCache(cache)
        self._manager.finished.connect(self.handleNetworkData)

    def invalidate(self):
        if self.width <= 0 or self.height <= 0:
            return

        ct = tileForCoordinate(self.latitude, self.longitude, self.zoom)
        tx = ct.x()
        ty = ct.y()

        # top-left corner of the center tile
        xp = int(self.width / 2 - (tx - math.floor(tx)) * TDIM)
        yp = int(self.height / 2 - (ty - math.floor(ty)) * TDIM)

        # first tile vertical and horizontal
        xa = (xp + TDIM - 1) / TDIM
        ya = (yp + TDIM - 1) / TDIM
        xs = int(tx) - xa
        ys = int(ty) - ya

        # offset for top-left tile
        self._offset = QPoint(xp - xa * TDIM, yp - ya * TDIM)

        # last tile vertical and horizontal
        xe = int(tx) + (self.width - xp - 1) / TDIM
        ye = int(ty) + (self.height - yp - 1) / TDIM

        # build a rect
        self._tilesRect = QRect(xs, ys, xe - xs + 1, ye - ys + 1)

        if self._url.isEmpty():
            self.download()

        self.updated.emit(QRect(0, 0, self.width, self.height))

    def render(self, p, rect):
        for x in range(self._tilesRect.width()):
            for y in range(self._tilesRect.height()):
                tp = Point(x + self._tilesRect.left(),
                           y + self._tilesRect.top())
                box = self.tileRect(tp)
                if rect.intersects(box):
                    p.drawPixmap(box,
                                 self._tilePixmaps.get(tp, self._emptyTile))

    def pan(self, delta):
        dx = QPointF(delta) / float(TDIM)
        center = tileForCoordinate(self.latitude, self.longitude,
                                   self.zoom) - dx
        self.latitude = latitudeFromTile(center.y(), self.zoom)
        self.longitude = longitudeFromTile(center.x(), self.zoom)
        self.invalidate()

    # slots
    def handleNetworkData(self, reply):
        img = QImage()
        tp = Point(reply.request().attribute(QNetworkRequest.User))
        url = reply.url()
        if not reply.error():
            if img.load(reply, None):
                self._tilePixmaps[tp] = QPixmap.fromImage(img)
        reply.deleteLater()
        self.updated.emit(self.tileRect(tp))

        # purge unused tiles
        bound = self._tilesRect.adjusted(-2, -2, 2, 2)
        for tp in list(self._tilePixmaps.keys()):
            if not bound.contains(tp):
                del self._tilePixmaps[tp]
        self.download()

    def download(self):
        grab = None
        for x in range(self._tilesRect.width()):
            for y in range(self._tilesRect.height()):
                tp = Point(self._tilesRect.topLeft() + QPoint(x, y))
                if tp not in self._tilePixmaps:
                    grab = QPoint(tp)
                    break

        if grab is None:
            self._url = QUrl()
            return

        path = 'http://tile.openstreetmap.org/%d/%d/%d.png' % (
            self.zoom, grab.x(), grab.y())
        self._url = QUrl(path)
        request = QNetworkRequest()
        request.setUrl(self._url)
        request.setRawHeader(b'User-Agent', b'Nokia (PyQt) Graphics Dojo 1.0')
        request.setAttribute(QNetworkRequest.User, grab)
        self._manager.get(request)

    def tileRect(self, tp):
        t = tp - self._tilesRect.topLeft()
        x = t.x() * TDIM + self._offset.x()
        y = t.y() * TDIM + self._offset.y()

        return QRect(x, y, TDIM, TDIM)
示例#27
0
    def paintGraph(self, graph, painter):
        brush = QBrush(Qt.SolidPattern)
        pen = QPen()
        brush.setColor(Qt.white)

        for i, edge in enumerate(graph.edges):
            if ("color" in edge.kwargs.keys()):
                pen.setColor(QColor(edge.kwargs["color"]))
            else:
                pen.setColor(QColor("black"))

            if ("width" in edge.kwargs.keys()):
                pen.setWidth(int(edge.kwargs["width"]))
            else:
                pen.setWidth(1)

            painter.setPen(pen)
            painter.setBrush(brush)
            if (edge.source.parent_graph != graph and not self.show_subgraphs):
                gspos = edge.source.parent_graph.global_pos
            else:
                gspos = edge.source.global_pos

            if (edge.dest.parent_graph != graph and not self.show_subgraphs):
                gspos = edge.dest.parent_graph.global_pos
            else:
                gdpos = edge.dest.global_pos

            nb_next = 0
            for j in range(i, len(graph.edges)):
                if (graph.edges[j].source == edge.source
                        and graph.edges[j].dest == edge.dest):
                    nb_next += 1

            # offset=[0,0]
            # if(nb_next%2==1):
            #     offset[0]=20*(nb_next/2)
            # else:
            #     offset[0]=-20*(nb_next/2)

            dist = math.sqrt((gdpos[0] - gspos[0]) * (gdpos[0] - gspos[0]) +
                             (gdpos[1] - gspos[1]) * (gdpos[1] - gspos[1]))
            offsetAmount = 25
            offsetDestx = gdpos[0] - ((offsetAmount *
                                       (gdpos[0] - gspos[0])) / dist)
            offsetDesty = gdpos[1] - ((offsetAmount *
                                       (gdpos[1] - gspos[1])) / dist)

            offsetSourcex = gspos[0] - ((offsetAmount *
                                         (gspos[0] - gdpos[0])) / dist)
            offsetSourcey = gspos[1] - ((offsetAmount *
                                         (gspos[1] - gdpos[1])) / dist)

            path = QPainterPath()
            path.moveTo(offsetSourcex, offsetSourcey)
            #path.cubicTo(gspos[0],gspos[1],offset[0]+(gspos[0]+gdpos[0])/2,(gspos[1]+gdpos[1])/2,offsetDestx,offsetDesty)
            path.lineTo(offsetDestx, offsetDesty)
            painter.strokePath(path, pen)

            dx = offsetDestx - gspos[0]
            dy = offsetDesty - gspos[1]
            linLength = math.sqrt(dx * dx + dy * dy)
            unitDx = dx / linLength
            unitDy = dy / linLength

            #Arrow head size constant, increase for larger arrow head
            arrowHeadBoxSize = 15

            arrowPoint1x = int(offsetDestx - unitDx * arrowHeadBoxSize -
                               unitDy * arrowHeadBoxSize)
            arrowPoint1y = int(offsetDesty - unitDy * arrowHeadBoxSize +
                               unitDx * arrowHeadBoxSize)

            arrowPoint2x = int(offsetDestx - unitDx * arrowHeadBoxSize +
                               unitDy * arrowHeadBoxSize)
            arrowPoint2y = int(offsetDesty - unitDy * arrowHeadBoxSize -
                               unitDx * arrowHeadBoxSize)

            painter.drawLine(offsetDestx, offsetDesty, arrowPoint1x,
                             arrowPoint1y)
            painter.drawLine(offsetDestx, offsetDesty, arrowPoint2x,
                             arrowPoint2y)

        if (self.show_subgraphs):
            for node in graph.nodes:
                if type(node) == Graph:
                    subgraph = node
                    self.paintSubgraph(subgraph, painter, pen, brush)

        # TODO : add more painting parameters
        for node in graph.nodes:
            if type(node) != Graph:
                if ("color" in node.kwargs.keys()):
                    pen.setColor(QColor(node.kwargs["color"]))
                else:
                    pen.setColor(QColor("black"))

                if ("fillcolor" in node.kwargs.keys()):
                    if (":" in node.kwargs["fillcolor"]):
                        gradient = QLinearGradient(
                            node.pos[0] - node.size[0] / 2, node.pos[1],
                            node.pos[0] + node.size[0] / 2, node.pos[1])
                        c = node.kwargs["fillcolor"].split(":")
                        for i, col in enumerate(c):
                            stop = i / (len(c) - 1)
                            gradient.setColorAt(stop, QColor(col))

                        brush = QBrush(gradient)
                    else:
                        brush = QBrush(QColor(node.kwargs["fillcolor"]))
                else:
                    brush = QBrush(QColor("white"))

                if ("width" in node.kwargs.keys()):
                    pen.setWidth(int(node.kwargs["width"]))
                else:
                    pen.setWidth(1)

                gpos = node.global_pos

                painter.setPen(pen)
                painter.setBrush(brush)
                if ("shape" in node.kwargs.keys()):
                    if (node.kwargs["shape"] == "box"):
                        painter.drawRect(gpos[0] - node.size[0] / 2,
                                         gpos[1] - node.size[1] / 2,
                                         node.size[0], node.size[1])

                    elif (node.kwargs["shape"] == "circle"):
                        painter.drawEllipse(gpos[0] - node.size[0] / 2,
                                            gpos[1] - node.size[1] / 2,
                                            node.size[0], node.size[1])
                    elif (node.kwargs["shape"] == "triangle"):
                        rect = QRect(gpos[0] - node.size[0] / 2,
                                     gpos[1] - 2 * node.size[1] / 3,
                                     node.size[0], node.size[1])

                        path = QPainterPath()
                        path.moveTo(rect.left() + (rect.width() / 2),
                                    rect.top())
                        path.lineTo(rect.bottomLeft())
                        path.lineTo(rect.bottomRight())
                        path.lineTo(rect.left() + (rect.width() / 2),
                                    rect.top())

                        painter.fillPath(path, brush)
                        painter.drawPath(path)
                    elif (node.kwargs["shape"] == "polygon"):
                        rect = QRect(gpos[0] - node.size[0] / 2,
                                     gpos[1] - node.size[1] / 2, node.size[0],
                                     node.size[1])

                        path = QPainterPath()
                        path.moveTo(rect.left() + (rect.width() / 4),
                                    rect.top())
                        path.lineTo(rect.left() + 3 * rect.width() / 4,
                                    rect.top())
                        path.lineTo(rect.left() + rect.width(),
                                    rect.top() + rect.height() / 2)
                        path.lineTo(rect.left() + 3 * rect.width() / 4,
                                    rect.top() + rect.height())
                        path.lineTo(rect.left() + rect.width() / 4,
                                    rect.top() + rect.height())
                        path.lineTo(rect.left(),
                                    rect.top() + rect.height() / 2)
                        path.lineTo(rect.left() + (rect.width() / 4),
                                    rect.top())

                        painter.fillPath(path, brush)
                        painter.drawPath(path)
                    elif (node.kwargs["shape"] == "diamond"):
                        rect = QRect(gpos[0] - node.size[0] / 2,
                                     gpos[1] - node.size[1] / 2, node.size[0],
                                     node.size[1])

                        path = QPainterPath()
                        path.moveTo(rect.left() + (rect.width() / 2),
                                    rect.top())
                        path.lineTo(rect.left() + rect.width(),
                                    rect.top() + rect.height() / 2)
                        path.lineTo(rect.left() + rect.width() / 2,
                                    rect.top() + rect.height())
                        path.lineTo(rect.left(),
                                    rect.top() + rect.height() / 2)
                        path.lineTo(rect.left() + (rect.width() / 2),
                                    rect.top())

                        painter.fillPath(path, brush)
                        painter.drawPath(path)

                    # Image as a node, this implementation checks to see if a
                    # file path was provided in the shape parameter
                    if (os.path.isfile(node.kwargs["shape"])):
                        img_path = node.kwargs["shape"]
                        painter.drawImage(
                            QRect(
                                gpos[0] - 40 / 2,
                                gpos[1] - 40 / 2,
                                40,  # set size of icon images 40x40
                                40),
                            QImage(img_path))
                else:
                    painter.drawEllipse(gpos[0] - node.size[0] / 2,
                                        gpos[1] - node.size[1] / 2,
                                        node.size[0], node.size[1])

                #if("label" in node.kwargs.keys()):
                # painter.drawText(
                #     gpos[0]-node.size[0]/2,
                #     (gpos[1]-node.size[1]/2),
                #     node.size[0], node.size[1],
                #     Qt.AlignCenter|Qt.AlignTop,node.kwargs["label"])
                # painter.drawText(
                #     gpos[0] - node.size[0] / 2,
                #     (gpos[1] - node.size[1] / 2)+60,
                #     node.size[0], node.size[1],
                #     Qt.AlignCenter | Qt.AlignTop, "Node Name Text")

                # painter.drawText(
                #     QRect(
                #         (gpos[0] - node.size[0] / 2)-30,
                #         (gpos[1] - node.size[1] / 2)+30,
                #         150, 200), Qt.AlignCenter | Qt.AlignTop |Qt.TextWordWrap, "Test Text")

                if ("text" in node.kwargs.keys()):
                    #painter2 = QPainter(self)
                    #font2 = QFont("Arial", 10) # Node text size
                    #painter2.setFont(font2)

                    y = (gpos[1] - node.size[1] / 2) + 40
                    lines = node.kwargs["text"].split(
                        "\n"
                    )  # Allows for new lines, y axiz displaced for each line
                    for line in lines:
                        painter.drawText((gpos[0] - node.size[0] / 2) - 30, y,
                                         line)
                        y += 16

            else:
                if (self.show_subgraphs):
                    self.paintGraph(subgraph, painter)
                else:
                    subgraph = node
                    self.paintSubgraph(subgraph, painter, pen, brush)
示例#28
0
class EnvelopeWidget(QtWidgets.QWidget):

    pointsChanged = pyqtSignal()

    def __init__(self, parent, points=None):
        super().__init__()
        self.parent = parent
        self.qrect = QRect()

        self.points = points or []
        self.minTime = 0
        self.minValue = 0
        self.maxTime = 1
        self.maxValue = 1
        self.logValue = False

        self.draggingPoint = None
        self.draggingPointOriginalTime = None
        self.draggingPointOriginalValue = None

        # drawing constants
        self.RECT_HMARGIN = 0.0
        self.RECT_VMARGIN = 0.0
        self.AXIS_LMARGIN = 0.07
        self.AXIS_RMARGIN = 0.02
        self.AXIS_TMARGIN = 0.033
        self.AXIS_BMARGIN = 0.15
        self.POINTSIZE = 0.05
        self.POINTSIZE_PROX = 0.03
        self.LABEL_LMARGIN = 0.06
        self.LABEL_BMARGIN = 0.03
        # colors, pens, fonts
        self.BGCOLOR = QColor(*mayStyle.group_bgcolor)
        self.TEXTCOLOR = QColor(*mayStyle.default_textcolor)
        self.AXIS_PEN = QtGui.QPen(self.TEXTCOLOR, 3)
        self.POINT_PEN = QtGui.QPen(self.TEXTCOLOR, 1.5)
        self.TEXT_FONT = QtGui.QFont("Roboto", 12)
        self.LABEL_FONT = QtGui.QFont("Roboto Condensed", 9)

    def paintEvent(self, event):
        self.qrect = event.rect()
        x = self.qrect.left()
        y = self.qrect.top()
        w = self.qrect.width()
        h = self.qrect.height()

        qp = QtGui.QPainter()
        qp.begin(self)

        qp.fillRect(x + self.RECT_HMARGIN * w, y + self.RECT_VMARGIN * h,
                    (1 - 2 * self.RECT_HMARGIN) * w,
                    (1 - 2 * self.RECT_VMARGIN) * h, self.BGCOLOR)

        qp.setPen(self.AXIS_PEN)
        path = QtGui.QPainterPath()
        path.moveTo(x + self.AXIS_LMARGIN * w, y + self.AXIS_TMARGIN * h)
        path.lineTo(x + self.AXIS_LMARGIN * w, y + (1 - self.AXIS_BMARGIN) * h)
        path.lineTo(x + (1 - self.AXIS_RMARGIN) * w,
                    y + (1 - self.AXIS_BMARGIN) * h)
        qp.drawPath(path)

        if not self.points:
            qp.setFont(self.TEXT_FONT)
            qp.drawText(event.rect(), Qt.AlignCenter, "no points available..!")
        else:
            path.clear()
            qp.setPen(self.POINT_PEN)
            qp.setFont(self.LABEL_FONT)
            for ip, p in enumerate(self.points):
                coordX, coordY = self.getCoordinatesOfDimensions(
                    p.time, p.value)
                qp.drawEllipse(coordX - self.POINTSIZE * h / 2,
                               coordY - self.POINTSIZE * h / 2,
                               self.POINTSIZE * h, self.POINTSIZE * h)
                if not (p.fixedTime and p.fixedValue):
                    axisX = x + self.LABEL_LMARGIN * w
                    self.drawText(qp, axisX,
                                  coordY, Qt.AlignRight | Qt.AlignVCenter,
                                  str(p.value))
                    axisY = y + (1 - self.LABEL_BMARGIN) * h
                    self.drawText(qp, coordX, axisY, Qt.AlignHCenter,
                                  str(p.time))

                if ip == 0:
                    path.moveTo(coordX, coordY)
                else:
                    path.lineTo(coordX, coordY)

            path.lineTo(x + w, coordY)
            qp.drawPath(path)

        qp.end()

    def drawText(self, qp, x, y, flags, text):
        size = 32767
        y -= size
        if flags & Qt.AlignHCenter:
            x -= 0.5 * size
        elif flags & Qt.AlignRight:
            x -= size
        if flags & Qt.AlignVCenter:
            y += 0.5 * size
        elif flags & Qt.AlignTop:
            y += size
        else:
            flags |= Qt.AlignBottom
        rect = QtCore.QRectF(x, y, size, size)
        qp.drawText(rect, flags, text)

    def loadEnvelope(self, envelope):
        self.points = envelope.points if envelope else []
        self.update()

    def setDimensions(self,
                      maxTime=None,
                      maxValue=None,
                      minTime=None,
                      minValue=None,
                      logValue=None):
        if maxTime:
            self.maxTime = maxTime
        if minTime:
            self.minTime = minTime
        if maxValue:
            self.maxValue = maxValue
        if minValue:
            self.minValue = minValue
        if logValue:
            self.logValue = logValue
            if self.logValue and self.minValue == 0:
                print(
                    "EnvelopeWidget can't have logarithmic axis when minimum is zero..!"
                )
                self.logValue = False
        self.update()

    def getCoordinatesOfDimensions(self, time, value):
        anti_HMargin = 1 - self.AXIS_LMARGIN - self.AXIS_RMARGIN
        anti_VMargin = 1 - self.AXIS_TMARGIN - self.AXIS_BMARGIN

        coordX = self.qrect.left() + self.qrect.width() * (
            self.AXIS_LMARGIN + anti_HMargin * (time - self.minTime) /
            (self.maxTime - self.minTime))

        if self.logValue:
            coordY = self.qrect.bottom() - self.qrect.height() * (
                self.AXIS_BMARGIN + anti_VMargin *
                (log(value) - log(self.minValue)) /
                (log(self.maxValue) - log(self.minValue)))
        else:
            coordY = self.qrect.bottom() - self.qrect.height() * (
                self.AXIS_BMARGIN + anti_VMargin * (value - self.minValue) /
                (self.maxValue - self.minValue))

        return (coordX, coordY)

    def getDimensionsOfCoordinates(self, coordX, coordY):
        anti_HMargin = 1 - self.AXIS_LMARGIN - self.AXIS_RMARGIN
        anti_VMargin = 1 - self.AXIS_TMARGIN - self.AXIS_BMARGIN

        timeFactor = self.qrect.width() * anti_HMargin / (self.maxTime -
                                                          self.minTime)
        timeOffset = self.qrect.left() + self.qrect.width() * self.AXIS_LMARGIN
        time = self.minTime + (coordX - timeOffset) / timeFactor

        if self.logValue:
            valueFactor = -self.qrect.height() * anti_VMargin / (
                log(self.maxValue) - log(self.minValue))
            valueOffset = self.qrect.bottom(
            ) - self.qrect.height() * self.AXIS_BMARGIN
            value = self.minValue * exp((coordY - valueOffset) / valueFactor)
        else:
            valueFactor = -self.qrect.height() * anti_VMargin / (
                self.maxValue - self.minValue)
            valueOffset = self.qrect.bottom(
            ) - self.qrect.height() * self.AXIS_BMARGIN
            value = self.minValue + (coordY - valueOffset) / valueFactor
            # TODO: something weird happens in this case with rounding the value to minValue, close to zero... wtf??
        return round(max(time, self.minTime), 3), round(
            value if value > self.minValue else self.minValue, 3)

    def findNextNeighbor(self, coordX, coordY):
        nextNeighbor = None
        minDistance = None
        for p in self.points:
            pX, pY = self.getCoordinatesOfDimensions(p.time, p.value)
            distance = sqrt((pX - coordX)**2 + (pY - coordY)**2)
            if distance < self.POINTSIZE_PROX * self.qrect.width():
                if nextNeighbor is None or distance < minDistance:
                    nextNeighbor = p
                    minDistance = distance
        return nextNeighbor

    def mousePressEvent(self, event):
        nextNeighbor = self.findNextNeighbor(event.pos().x(), event.pos().y())
        if nextNeighbor is None:
            return
        self.draggingPoint = nextNeighbor
        self.draggingPointOriginalTime = self.draggingPoint.time
        self.draggingPointOriginalValue = self.draggingPoint.value

        if event.button() == Qt.RightButton:
            inputDialog = DoubleInputDialog(self,
                                            maxValue=self.maxValue,
                                            point=self.draggingPoint)
            if inputDialog.exec_():
                time = round(inputDialog.timeBox.value(),
                             inputDialog.precision)
                value = round(inputDialog.valueBox.value(),
                              inputDialog.precision)
                self.draggingPoint.dragTo(time, value)
                self.finishPointChange()

    def mouseMoveEvent(self, event):
        if self.draggingPoint:
            newTime, newValue = self.getDimensionsOfCoordinates(
                event.pos().x(),
                event.pos().y())
            self.draggingPoint.dragTo(newTime, newValue)
            self.repaint()

    def mouseReleaseEvent(self, event):
        self.finishPointChange()
        self.draggingPoint = None
        self.update()

    def finishPointChange(self):
        if self.draggingPoint:
            if any(point.time >= nextPoint.time for point, nextPoint in zip(self.points, self.points[1:])) \
                or self.isOutsideOfWidget(*self.draggingPoint.values()):
                self.draggingPoint.dragTo(self.draggingPointOriginalTime,
                                          self.draggingPointOriginalValue)
            else:
                self.pointsChanged.emit()

    def isOutsideOfWidget(self, time, value):
        coordX, coordY = self.getCoordinatesOfDimensions(time, value)
        return coordX < self.qrect.left() or coordX > self.qrect.right(
        ) or coordY < self.qrect.top() or coordY > self.qrect.bottom()
示例#29
0
class BrushingModel(QObject):
    brushSizeChanged = pyqtSignal(int)
    brushColorChanged = pyqtSignal(QColor)
    brushStrokeAvailable = pyqtSignal(QPointF, object)
    drawnNumberChanged = pyqtSignal(int)

    minBrushSize = 1
    maxBrushSize = 61
    defaultBrushSize = 3
    defaultDrawnNumber = 1
    defaultColor = Qt.white
    erasingColor = Qt.black
    erasingNumber = 100

    def __init__(self, parent=None):
        QObject.__init__(self, parent=parent)
        self.sliceRect = None
        self.bb = QRect()  # bounding box enclosing the drawing
        self.brushSize = self.defaultBrushSize
        self.drawColor = self.defaultColor
        self._temp_color = None
        self._temp_number = None
        self.drawnNumber = self.defaultDrawnNumber

        self.pos = None
        self.erasing = False
        self._hasMoved = False

        self.drawOnto = None

        # an empty scene, where we add all drawn line segments
        # a QGraphicsLineItem, and which we can use to then
        # render to an image
        self.scene = QGraphicsScene()

    def toggleErase(self):
        self.erasing = not (self.erasing)
        if self.erasing:
            self.setErasing()
        else:
            self.disableErasing()

    def setErasing(self):
        self.erasing = True
        self._temp_color = self.drawColor
        self._temp_number = self.drawnNumber
        self.setBrushColor(self.erasingColor)
        self.brushColorChanged.emit(self.erasingColor)
        self.setDrawnNumber(self.erasingNumber)

    def disableErasing(self):
        self.erasing = False
        self.setBrushColor(self._temp_color)
        self.brushColorChanged.emit(self.drawColor)
        self.setDrawnNumber(self._temp_number)

    def setBrushSize(self, size):
        self.brushSize = size
        self.brushSizeChanged.emit(self.brushSize)

    def setDrawnNumber(self, num):
        self.drawnNumber = num
        self.drawnNumberChanged.emit(num)

    def getBrushSize(self):
        return self.brushSize

    def brushSmaller(self):
        b = self.brushSize
        if b > self.minBrushSize:
            self.setBrushSize(b - 1)

    def brushBigger(self):
        b = self.brushSize
        if self.brushSize < self.maxBrushSize:
            self.setBrushSize(b + 1)

    def setBrushColor(self, color):
        self.drawColor = QColor(color)
        self.brushColorChanged.emit(self.drawColor)

    def beginDrawing(self, pos, sliceRect):
        """

        pos -- QPointF-like
        """
        self.sliceRect = sliceRect
        self.scene.clear()
        self.bb = QRect()
        self.pos = QPointF(pos.x(), pos.y())
        self._hasMoved = False

    def endDrawing(self, pos):
        has_moved = self._hasMoved  # _hasMoved will change after calling moveTo
        if has_moved:
            self.moveTo(pos)
        else:
            assert self.pos == pos
            self.moveTo(QPointF(pos.x() + 0.0001,
                                pos.y() + 0.0001))  # move a little

        # Qt seems to use strange rules for determining which pixels to set when rendering a brush stroke to a QImage.
        # We seem to get better results if we do the following:
        # 1) Slightly offset the source window because apparently there is a small shift in the data
        # 2) Render the scene to an image that is MUCH larger than the scene resolution (4x by 4x)
        # 3) Downsample each 4x4 patch from the large image back to a single pixel in the final image,
        #     applying some threshold to determine if the final pixel is on or off.

        tempi = QImage(QSize(4 * self.bb.width(), 4 * self.bb.height()),
                       QImage.Format_ARGB32_Premultiplied)  # TODO: format
        tempi.fill(0)
        painter = QPainter(tempi)
        # Offset the source window.  At first I thought the right offset was 0.5, because
        #  that would seem to make sure points are rounded to pixel CENTERS, but
        #  experimentation indicates that 0.25 is slightly better for some reason...
        source_rect = QRectF(QPointF(self.bb.x() + 0.25,
                                     self.bb.y() + 0.25),
                             QSizeF(self.bb.width(), self.bb.height()))
        target_rect = QRectF(QPointF(0, 0),
                             QSizeF(4 * self.bb.width(), 4 * self.bb.height()))
        self.scene.render(painter, target=target_rect, source=source_rect)
        painter.end()

        # Now downsample: convert each 4x4 patch into a single pixel by summing and dividing
        ndarr = qimage2ndarray.rgb_view(tempi)[:, :, 0].astype(int)
        ndarr = ndarr.reshape((ndarr.shape[0], ) + (ndarr.shape[1] // 4, ) +
                              (4, ))
        ndarr = ndarr.sum(axis=-1)
        ndarr = ndarr.transpose()
        ndarr = ndarr.reshape((ndarr.shape[0], ) + (ndarr.shape[1] // 4, ) +
                              (4, ))
        ndarr = ndarr.sum(axis=-1)
        ndarr = ndarr.transpose()
        ndarr //= 4 * 4

        downsample_threshold = (7.0 / 16) * 255
        labels = numpy.where(ndarr >= downsample_threshold,
                             numpy.uint8(self.drawnNumber), numpy.uint8(0))
        labels = labels.swapaxes(0, 1)
        assert labels.shape[0] == self.bb.width()
        assert labels.shape[1] == self.bb.height()

        ##
        ## ensure that at least one pixel is label when the brush size is 1
        ##
        ## this happens when the user just clicked without moving
        ## in that case the lineitem will be so tiny, that it won't be rendered
        ## into a single pixel by the code above
        if not has_moved and self.brushSize <= 1 and numpy.count_nonzero(
                labels) == 0:
            labels[labels.shape[0] // 2,
                   labels.shape[1] // 2] = self.drawnNumber

        self.brushStrokeAvailable.emit(QPointF(self.bb.x(), self.bb.y()),
                                       labels)

    def dumpDraw(self, pos):
        res = self.endDrawing(pos)
        self.beginDrawing(pos, self.sliceRect)
        return res

    def moveTo(self, pos):
        # data coordinates
        oldX, oldY = self.pos.x(), self.pos.y()
        x, y = pos.x(), pos.y()

        line = QGraphicsLineItem(oldX, oldY, x, y)
        line.setPen(
            QPen(QBrush(Qt.white), self.brushSize, Qt.SolidLine, Qt.RoundCap,
                 Qt.RoundJoin))
        self.scene.addItem(line)
        self._hasMoved = True

        # update bounding Box
        if not self.bb.isValid():
            self.bb = QRect(QPoint(oldX, oldY), QSize(1, 1))
        # grow bounding box
        self.bb.setLeft(
            min(self.bb.left(), max(0, x - self.brushSize // 2 - 1)))
        self.bb.setRight(
            max(self.bb.right(),
                min(self.sliceRect[0] - 1, x + self.brushSize // 2 + 1)))
        self.bb.setTop(min(self.bb.top(), max(0, y - self.brushSize // 2 - 1)))
        self.bb.setBottom(
            max(self.bb.bottom(),
                min(self.sliceRect[1] - 1, y + self.brushSize // 2 + 1)))

        # update/move position
        self.pos = pos
示例#30
0
    def paint(self, painter, option, index):

        item = index.internalPointer()
        colors = outlineItemColors(item)

        style = qApp.style()

        opt = QStyleOptionViewItem(option)
        self.initStyleOption(opt, index)

        iconRect = style.subElementRect(style.SE_ItemViewItemDecoration, opt)
        textRect = style.subElementRect(style.SE_ItemViewItemText, opt)

        # Background
        style.drawPrimitive(style.PE_PanelItemViewItem, opt, painter)

        if settings.viewSettings["Tree"]["Background"] != "Nothing" and not opt.state & QStyle.State_Selected:

            col = colors[settings.viewSettings["Tree"]["Background"]]

            if col != QColor(Qt.transparent):
                col2 = QColor(Qt.white)
                if opt.state & QStyle.State_Selected:
                    col2 = opt.palette.brush(QPalette.Normal, QPalette.Highlight).color()
                col = mixColors(col, col2, .2)

            painter.save()
            painter.setBrush(col)
            painter.setPen(Qt.NoPen)

            rect = opt.rect
            if self._view:
                r2 = self._view.visualRect(index)
                rect = self._view.viewport().rect()
                rect.setLeft(r2.left())
                rect.setTop(r2.top())
                rect.setBottom(r2.bottom())

            painter.drawRoundedRect(rect, 5, 5)
            painter.restore()

        # Icon
        mode = QIcon.Normal
        if not opt.state & QStyle.State_Enabled:
            mode = QIcon.Disabled
        elif opt.state & QStyle.State_Selected:
            mode = QIcon.Selected
        state = QIcon.On if opt.state & QStyle.State_Open else QIcon.Off
        icon = opt.icon.pixmap(iconRect.size(), mode=mode, state=state)
        if opt.icon and settings.viewSettings["Tree"]["Icon"] != "Nothing":
            color = colors[settings.viewSettings["Tree"]["Icon"]]
            colorifyPixmap(icon, color)
        opt.icon = QIcon(icon)
        opt.icon.paint(painter, iconRect, opt.decorationAlignment, mode, state)

        # Text
        if opt.text:
            painter.save()
            if settings.viewSettings["Tree"]["Text"] != "Nothing":
                col = colors[settings.viewSettings["Tree"]["Text"]]
                if col == Qt.transparent:
                    col = Qt.black
                painter.setPen(col)
            f = QFont(opt.font)
            painter.setFont(f)
            fm = QFontMetrics(f)
            elidedText = fm.elidedText(opt.text, Qt.ElideRight, textRect.width())
            painter.drawText(textRect, Qt.AlignLeft, elidedText)

            extraText = ""
            if item.isFolder() and settings.viewSettings["Tree"]["InfoFolder"] != "Nothing":
                if settings.viewSettings["Tree"]["InfoFolder"] == "Count":
                    extraText = item.childCount()
                    extraText = " [{}]".format(extraText)
                elif settings.viewSettings["Tree"]["InfoFolder"] == "WC":
                    extraText = item.data(Outline.wordCount.value)
                    extraText = " ({})".format(extraText)
                elif settings.viewSettings["Tree"]["InfoFolder"] == "Progress":
                    extraText = int(toFloat(item.data(Outline.goalPercentage.value)) * 100)
                    if extraText:
                        extraText = " ({}%)".format(extraText)
                elif settings.viewSettings["Tree"]["InfoFolder"] == "Summary":
                    extraText = item.data(Outline.summarySentance.value)
                    if extraText:
                        extraText = " - {}".format(extraText)

            if item.isText() and settings.viewSettings["Tree"]["InfoText"] != "Nothing":
                if settings.viewSettings["Tree"]["InfoText"] == "WC":
                    extraText = item.data(Outline.wordCount.value)
                    extraText = " ({})".format(extraText)
                elif settings.viewSettings["Tree"]["InfoText"] == "Progress":
                    extraText = int(toFloat(item.data(Outline.goalPercentage.value)) * 100)
                    if extraText:
                        extraText = " ({}%)".format(extraText)
                elif settings.viewSettings["Tree"]["InfoText"] == "Summary":
                    extraText = item.data(Outline.summarySentance.value)
                    if extraText:
                        extraText = " - {}".format(extraText)


            if extraText:
                r = QRect(textRect)
                r.setLeft(r.left() + fm.width(opt.text + " "))

                painter.save()
                f = painter.font()
                f.setWeight(QFont.Normal)
                painter.setFont(f)
                painter.setPen(Qt.darkGray)
                painter.drawText(r, Qt.AlignLeft | Qt.AlignBottom, extraText)
                painter.restore()

            painter.restore()