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()
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()
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
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)
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))
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)
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
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()
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
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
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)
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()
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
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()
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)
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)
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()
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()
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)
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)
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
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)
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)
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)
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()
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
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()