Source code for grid_renderer

# -*- coding: utf-8 -*-

# Copyright Martin Manns
# Distributed under the terms of the GNU General Public License

# --------------------------------------------------------------------
# pyspread is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# pyspread is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with pyspread.  If not, see <http://www.gnu.org/licenses/>.
# --------------------------------------------------------------------

"""

**Provides**

 * :func: `painter_save`: Context manager saving and restoring painter state
 * :func: `painter_zoom`: Context manager scaling and restoring the painter
 * :func: `painter_rotate`: Context manager rotating and restoring the painter
 * :class:`GridCellNavigator`: Find neighbors of a cell
 * :class:`EdgeBorders`: Dataclass for edge properties
 * :class:`CellEdgeRenderer`: Paints cell edges
 * :class:`QColorCache`: QColor cache
 * :class:`CellRenderer`: Paints cells

"""

from contextlib import contextmanager
try:
    from dataclasses import dataclass
except ImportError:
    from pyspread.lib.dataclasses import dataclass  # Python 3.6 compatibility
from typing import List, Tuple

from PyQt6.QtCore import Qt, QModelIndex, QRectF, QPointF
from PyQt6.QtGui import (QBrush, QPainter, QPalette, QPen,
                         QPainterPath, QPolygonF, QPainterPathStroker)

from PyQt6.QtGui import QColor as __QColor
from PyQt6.QtWidgets import QTableView, QStyleOptionViewItem
from functools import lru_cache


[docs] @contextmanager def painter_save(painter: QPainter): """Context manager saving and restoring painter state :param painter: Painter, for which the state is preserved """ painter.save() yield painter.restore()
[docs] @contextmanager def painter_zoom(painter: QPainter, zoom: float, rect: QRectF) -> QRectF: """Context manager scaling and restoring the painter (rect.x(), rect.y()) is invariant :param painter: Painter, for which the state is preserved :param zoom: Zoom factor :param rect: Rect for setting zoom invariant point (rect.x(), rect.y()) """ with painter_save(painter): painter.translate(rect.x(), rect.y()) painter.scale(zoom, zoom) painter.translate(-rect.x() * zoom, -rect.y() * zoom) yield QRectF(rect.x() * zoom, rect.y() * zoom, rect.width() / zoom, rect.height() / zoom)
[docs] @contextmanager def painter_rotate(painter: QPainter, rect: QRectF, angle: int = 0) -> QRectF: """Context manager rotating and restoring the painter :param painter: Painter, which is rotated :param rect: Rect to be painted in :param angle: Rotataion angle must be in (0, 90, 180, 270) """ supported_angles = 0, 90, 180, 270 angle = int(angle) if angle not in supported_angles: msg = "Rotation angle {} not in {}".format(angle, supported_angles) raise Warning(msg) return center_x, center_y = rect.center().x(), rect.center().y() with painter_save(painter): painter.translate(center_x, center_y) painter.rotate(angle) if angle in (0, 180): painter.translate(-center_x, -center_y) elif angle in (90, 270): painter.translate(-center_y, -center_x) rect = QRectF(rect.y(), rect.x(), rect.height(), rect.width()) yield rect
[docs] class QColor(__QColor): """Hashable QColor""" def __hash__(self): return self.rgba()
[docs] class GridCellNavigator: """Find neighbors of a cell""" def __init__(self, grid: QTableView, key: Tuple[int, int, int]): """ :param grid: The main grid widget :param key: Key of cell fow which neighbors are identified """ self.grid = grid self.code_array = grid.model.code_array self.row, self.column, self.table = self.key = key self.borderwidth_bottom_cache = grid.borderwidth_bottom_cache self.borderwidth_right_cache = grid.borderwidth_right_cache @property def borderwidth_bottom(self) -> float: """Width of bottom border line""" return self.borderwidth_bottom_cache[self.key] @property def borderwidth_right(self) -> float: """Width of right border line""" return self.borderwidth_right_cache[self.key] @property def border_color_bottom(self) -> QColor: """Color of bottom border line""" return self.code_array.cell_attributes[self.key].bordercolor_bottom @property def border_color_right(self) -> QColor: """Color of right border line""" return self.code_array.cell_attributes[self.key].bordercolor_right @property def merge_area(self) -> Tuple[int, int, int, int]: """Merge area of the key cell""" return self.code_array.cell_attributes[self.key].merge_area
[docs] def merging_key(self, key: Tuple[int, int, int]) -> Tuple[int, int, int]: """Merging cell if cell is merged else cell key :param key: Key of cell that is checked for being merged """ merging_key = self.code_array.cell_attributes.get_merging_cell(key) return key if merging_key is None else merging_key
[docs] def above_keys(self) -> List[Tuple[int, int, int]]: """Key list of neighboring cells above the key cell""" merge_area = self.merge_area if merge_area is None: return [self.merging_key((self.row - 1, self.column, self.table))] _, left, _, right = merge_area return [self.merging_key((self.row - 1, col, self.table)) for col in range(left, right + 1)]
[docs] def below_keys(self) -> List[Tuple[int, int, int]]: """Key list of neighboring cells below the key cell""" merge_area = self.merge_area if merge_area is None: return [self.merging_key((self.row + 1, self.column, self.table))] _, left, _, right = merge_area return [self.merging_key((self.row + 1, col, self.table)) for col in range(left, right + 1)]
[docs] def left_keys(self) -> List[Tuple[int, int, int]]: """Key list of neighboring cells left of the key cell""" merge_area = self.merge_area if merge_area is None: return [self.merging_key((self.row, self.column - 1, self.table))] top, _, bottom, _ = merge_area return [self.merging_key((row, self.column - 1, self.table)) for row in range(top, bottom + 1)]
[docs] def right_keys(self) -> List[Tuple[int, int, int]]: """Key list of neighboring cells right of the key cell""" merge_area = self.merge_area if merge_area is None: return [self.merging_key((self.row, self.column + 1, self.table))] top, _, bottom, _ = merge_area return [self.merging_key((row, self.column + 1, self.table)) for row in range(top, bottom + 1)]
[docs] def above_left_key(self) -> Tuple[int, int, int]: """Key of neighboring cell above left of the key cell""" return self.merging_key((self.row - 1, self.column - 1, self.table))
[docs] def above_right_key(self) -> Tuple[int, int, int]: """Key of neighboring cell above right of the key cell""" return self.merging_key((self.row - 1, self.column + 1, self.table))
[docs] def below_left_key(self) -> Tuple[int, int, int]: """Key of neighboring cell below left of the key cell""" return self.merging_key((self.row + 1, self.column - 1, self.table))
[docs] def below_right_key(self) -> Tuple[int, int, int]: """Key of neighboring cell below right of the key cell""" return self.merging_key((self.row + 1, self.column + 1, self.table))
[docs] @dataclass class EdgeBorders: """Holds border data for an edge and provides effective edge properties""" left_width: float right_width: float top_width: float bottom_width: float left_color: QColor right_color: QColor top_color: QColor bottom_color: QColor left_x: float right_x: float top_y: float bottom_y: float _color_cache = None @property def _border_widths(self) -> Tuple[float, float, float, float]: """Tuple of border widths in order left, right, top, bottom""" return (self.left_width, self.right_width, self.top_width, self.bottom_width) @property def _border_colors(self) -> Tuple[QColor, QColor, QColor, QColor]: """Tuple of border colors in order left, right, top, bottom""" return (self.left_color, self.right_color, self.top_color, self.bottom_color) @property def width(self) -> float: """Edge width, i.e. thickest vertical border""" return max(self.top_width, self.bottom_width) @property def height(self) -> float: """Edge height, i.e. thickest horizontal border""" return max(self.left_width, self.right_width) @property def color(self) -> QColor: """Edge color, i.e. darkest color of thickest edge border""" if self._color_cache is not None: return self._color_cache max_border_width = max(self.width, self.height) colors = [] for width, color in zip(self._border_widths, self._border_colors): if width == max_border_width: colors.append(color) colors.sort(key=lambda color: color.lightnessF()) color = colors[0] self._color_cache = color return color
[docs] class CellEdgeRenderer: """Paints cell edges""" intersection_cache = {} def __init__(self, painter: QPainter, center: QPointF, borders: EdgeBorders, rect: QRectF, clip_path: QPainterPath, zoom: float): """ Borders are provided by EdgeBorders in order: left, right, top, bottom :param painter: Painter with which edge is drawn :param center: Edge center :param borders: Border widths and colors :param rect: Rect of the clip_path :param clip_path: Clip rectangle that is required for QtSVG clipping :param zoom: Current zoom level """ self.painter = painter self.center = center self.borders = borders self.rect = rect self.clip_path = clip_path self.zoom = zoom
[docs] def paint(self): """Paints the edge""" if not self.borders.width or not self.borders.height: return # Invisible edge x, y = self.center.x(), self.center.y() width = self.borders.width * self.zoom height = self.borders.height * self.zoom cache_key = (self.rect.x(), self.rect.y(), self.rect.width(), self.rect.height(), x, y, width, height) color = self.borders.color self.painter.setPen(QPen(Qt.PenStyle.NoPen)) self.painter.setBrush(QBrush(color)) try: intersection = self.intersection_cache[cache_key] except KeyError: rect = QRectF(x-width/2, y-height/2, width, height) rect_path = QPainterPath() # Required for clipping in SVG export rect_path.addRect(rect) intersection = self.clip_path.intersected(rect_path) self.intersection_cache[cache_key] = intersection self.painter.drawPath(intersection) self.painter.setPen(QPen(Qt.PenStyle.SolidLine))
[docs] class QColorCache(dict): """QColor cache that returns default color for None""" def __init__(self, grid, *args, **kwargs): self.grid = grid super().__init__(*args, **kwargs) def __missing__(self, key): if key is None: qcolor = QColor(self.grid.palette().color(QPalette.ColorRole.Mid)) else: qcolor = QColor(*key) self[key] = qcolor return qcolor
[docs] class BorderWidthBottomCache(dict): """BorderWidthBottom cache""" def __init__(self, grid, *args, **kwargs): self.grid = grid self.cell_attributes = grid.model.code_array.cell_attributes super().__init__(*args, **kwargs) def __missing__(self, key): borderwidth_bottom = self.cell_attributes[key].borderwidth_bottom self[key] = borderwidth_bottom return borderwidth_bottom
[docs] class BorderWidthRightCache(BorderWidthBottomCache): """BorderWidthRight cache""" def __missing__(self, key): borderwidth_right = self.cell_attributes[key].borderwidth_right self[key] = borderwidth_right return borderwidth_right
[docs] class EdgeBordersCache(dict): """Cache of all EdgeBorders objects""" def __missing__(self, key): self[key] = edge_border = EdgeBorders(*key) return edge_border
[docs] class BorderColorBottomCache(BorderWidthBottomCache): """BorderColorBottomCache cache""" def __missing__(self, key): border_color_bottom = self.cell_attributes[key].border_color_bottom self[key] = border_color_bottom return border_color_bottom
[docs] class BorderColorRightCache(BorderWidthBottomCache): """BorderColorBottomCache cache""" def __missing__(self, key): border_color_right = self.cell_attributes[key].border_color_right self[key] = border_color_right return border_color_right
[docs] class CellRenderer: """Paints cells Cell rendering governs the area of a cell inside its borders. It is done in a vector oriented way. Therefore, the following conventions shall apply: * Cell borders of width 1 shall be painted so that they appear only in the bottom and right edge of the cell. * Cell borders of all widths have the same center line. * Cell borders that are thicker than 1 are painted on all borders. * At the edges, borders are painted in the following order: 1. Thin borders are painted first 2. If border width is equal, lighter colors are painted first """ def __init__(self, grid: QTableView, painter: QPainter, option: QStyleOptionViewItem, index: QModelIndex): """ :param grid: The main grid widget :param painter: Painter with which borders are drawn :param option: Style option for rendering :param index: Index of cell to be rendered """ self.grid = grid self.painter = painter self.option = option self.index = index self.painter.setRenderHint(QPainter.RenderHint.LosslessImageRendering, True) self.painter.setRenderHint(QPainter.RenderHint.SmoothPixmapTransform, True) self.cell_attributes = grid.model.code_array.cell_attributes self.key = index.row(), index.column(), self.grid.table self.cell_nav = GridCellNavigator(grid, self.key) self.qcolor_cache = grid.qcolor_cache self.borderwidth_bottom_cache = grid.borderwidth_bottom_cache self.borderwidth_right_cache = grid.borderwidth_right_cache
[docs] def inner_rect(self, rect: QRectF) -> QRectF: """Returns inner rect that is shrunk by border widths For merged cells, minimum top/left border widths are taken into account :param rect: Cell rect to be shrunk """ above_keys = self.cell_nav.above_keys() left_keys = self.cell_nav.left_keys() width_top = min(self.borderwidth_bottom_cache[above_key] for above_key in above_keys) width_left = min(self.borderwidth_right_cache[left_key] for left_key in left_keys) width_bottom = self.cell_nav.borderwidth_bottom width_right = self.cell_nav.borderwidth_right width_top *= self.grid.zoom width_left *= self.grid.zoom width_bottom *= self.grid.zoom width_right *= self.grid.zoom rect_x = rect.x() + width_left / 2 rect_y = rect.y() + width_top / 2 rect_width = rect.width() - width_left / 2 - width_right / 2 rect_height = rect.height() - width_top / 2 - width_bottom / 2 return QRectF(rect_x, rect_y, rect_width, rect_height)
[docs] def paint_content(self, rect: QRectF): """ :param rect: Cell rect of the cell to be painted """ with painter_zoom(self.painter, self.grid.zoom, rect) as zrect: self.grid.delegate.paint_(self.painter, zrect, self.option, self.index)
[docs] @staticmethod @lru_cache(maxsize=65536) def _get_border_pen(width, zoom): """Gets zoomed border pen, white, fully transparent :param width: Unzoomed line width :param zoom: Current zoom level of grid """ zoomed_width = max(1, width * zoom) return QPen(QColor(255, 255, 255, 0), zoomed_width, Qt.PenStyle.SolidLine, Qt.PenCapStyle.FlatCap, Qt.PenJoinStyle.MiterJoin)
[docs] def _draw_line(self, point1: QPointF, point2: QPointF, width: float, color: QColor, clip_path: QPainterPath): """Draws line Uses drawLine for screen painting and QPainterPathStroker for svg painting. :param point1: Start point of line :param point2: End point of line :param width: Line width :param color: Line color :param clip_path: Clip rectangle """ zoom = self.grid.zoom alpha = max(0, round(255 - 255 * width * zoom)) if width * zoom > 1.01: self.painter.setRenderHint(QPainter.RenderHint.Antialiasing, True) screen_painting = self.grid.main_window.print_area is None if screen_painting: # Rendering for the screen pen = QPen(color) pen.setWidthF(width * zoom) self.painter.setPen(pen) self.painter.drawLine(point1, point2) else: # Rendering for the printer pen = self._get_border_pen(width, zoom) line_polygon = QPolygonF((point1, point2)) line_path = QPainterPath() line_path.addPolygon(line_polygon) stroker = QPainterPathStroker(pen) stroked_path = stroker.createStroke(line_path) self.painter.setPen(QPen(QColor(255, 255, 255, alpha))) self.painter.setBrush(QBrush(color)) self.painter.drawPath(clip_path.intersected(stroked_path)) self.painter.setRenderHint(QPainter.RenderHint.Antialiasing, False)
[docs] def paint_bottom_border(self, rect: QRectF, clip_path: QPainterPath): """Paint bottom border of cell :param rect: Cell rect of the cell to be painted :param clip_path: Clip rectangle that is required for QtSVG clipping """ if not self.cell_nav.borderwidth_bottom: return point1 = QPointF(rect.x(), rect.y() + rect.height()) point2 = QPointF(rect.x() + rect.width(), rect.y() + rect.height()) width = self.cell_nav.borderwidth_bottom color = self.qcolor_cache[self.cell_nav.border_color_bottom] self._draw_line(point1, point2, width, color, clip_path)
[docs] def paint_right_border(self, rect: QRectF, clip_path: QPainterPath): """Paint right border of cell :param rect: Cell rect of the cell to be painted :param clip_path: Clip rectangle that is requuired for QtSVG clipping """ if not self.cell_nav.borderwidth_right: return point1 = QPointF(rect.x() + rect.width(), rect.y()) point2 = QPointF(rect.x() + rect.width(), rect.y() + rect.height()) width = self.cell_nav.borderwidth_right color = self.qcolor_cache[self.cell_nav.border_color_right] self._draw_line(point1, point2, width, color, clip_path)
[docs] def paint_above_borders(self, rect: QRectF, clip_path: QPainterPath): """Paint lower borders of all above cells :param rect: Cell rect of below cell, in which the borders are painted :param clip_path: Clip rectangle that is requuired for QtSVG clipping """ for above_key in self.cell_nav.above_keys(): above_cell_nav = GridCellNavigator(self.grid, above_key) if not above_cell_nav.borderwidth_bottom: continue merge_area = above_cell_nav.merge_area if merge_area is None: columns = [above_key[1]] else: _, left, _, right = merge_area columns = list(range(left, right + 1)) above_rect_x = self.grid.columnViewportPosition(columns[0]) above_rect_width = sum(self.grid.columnWidth(column) for column in columns) point1 = QPointF(above_rect_x, rect.y()) point2 = QPointF(above_rect_x + above_rect_width, rect.y()) width = above_cell_nav.borderwidth_bottom color = self.qcolor_cache[above_cell_nav.border_color_bottom] self._draw_line(point1, point2, width, color, clip_path)
[docs] def paint_left_borders(self, rect: QRectF, clip_path: QPainterPath): """Paint right borders of all left cells :param rect: Cell rect of right cell, in which the borders are painted :param clip_path: Clip rectangle that is requuired for QtSVG clipping """ for left_key in self.cell_nav.left_keys(): left_cell_nav = GridCellNavigator(self.grid, left_key) if not left_cell_nav.borderwidth_right: continue merge_area = left_cell_nav.merge_area if merge_area is None: rows = [left_key[0]] else: top, _, bottom, _ = merge_area rows = list(range(top, bottom + 1)) left_rect_y = self.grid.rowViewportPosition(rows[0]) left_rect_height = sum(self.grid.rowHeight(row) for row in rows) point1 = QPointF(rect.x(), left_rect_y) point2 = QPointF(rect.x(), left_rect_y + left_rect_height) width = left_cell_nav.borderwidth_right color = self.qcolor_cache[left_cell_nav.border_color_right] self._draw_line(point1, point2, width, color, clip_path)
[docs] def paint_top_left_edge(self, rect: QRectF, clip_path: QPainterPath): """Paints top left edge of the cell :param rect: Cell rect of cell, for which the edge is painted :param clip_path: Clip rectangle that is requuired for QtSVG clipping top TL | T left ------------ right L | C bottom """ center = QPointF(rect.x(), rect.y()) top_left_key = self.cell_nav.above_left_key() left_key = self.cell_nav.left_keys()[0] top_key = self.cell_nav.above_keys()[0] top_left_cell_nav = GridCellNavigator(self.grid, top_left_key) left_cell_nav = GridCellNavigator(self.grid, left_key) top_cell_nav = GridCellNavigator(self.grid, top_key) left_width = top_left_cell_nav.borderwidth_bottom right_width = top_cell_nav.borderwidth_bottom top_width = top_left_cell_nav.borderwidth_right bottom_width = left_cell_nav.borderwidth_right left_color = self.qcolor_cache[top_left_cell_nav.border_color_bottom] right_color = self.qcolor_cache[top_cell_nav.border_color_bottom] top_color = self.qcolor_cache[top_left_cell_nav.border_color_right] bottom_color = self.qcolor_cache[left_cell_nav.border_color_right] left_x = rect.x() - rect.width() right_x = rect.x() top_y = rect.y() - rect.height() bottom_y = rect.y() key = (left_width, right_width, top_width, bottom_width, left_color, right_color, top_color, bottom_color, left_x, right_x, top_y, bottom_y) borders = self.grid.edge_borders_cache[key] renderer = CellEdgeRenderer(self.painter, center, borders, rect, clip_path, self.grid.zoom) renderer.paint()
[docs] def paint_top_right_edge(self, rect: QRectF, clip_path: QPainterPath): """Paints top right edge of the cell :param rect: Cell rect of cell, for which the edge is painted :param clip_path: Clip rectangle that is requuired for QtSVG clipping top T | TR left ------------ right C | R bottom """ center = QPointF(rect.x() + rect.width(), rect.y()) top_key = self.cell_nav.above_keys()[-1] top_right_key = self.cell_nav.above_right_key() top_cell_nav = GridCellNavigator(self.grid, top_key) top_right_cell_nav = GridCellNavigator(self.grid, top_right_key) left_width = top_cell_nav.borderwidth_bottom right_width = top_right_cell_nav.borderwidth_bottom top_width = top_cell_nav.borderwidth_right bottom_width = self.cell_nav.borderwidth_right left_color = self.qcolor_cache[top_cell_nav.border_color_bottom] right_color = self.qcolor_cache[top_right_cell_nav.border_color_bottom] top_color = self.qcolor_cache[top_cell_nav.border_color_right] bottom_color = self.qcolor_cache[self.cell_nav.border_color_right] left_x = rect.x() + rect.width() right_x = rect.x() + 2 * rect.width() top_y = rect.y() - rect.height() bottom_y = rect.y() key = (left_width, right_width, top_width, bottom_width, left_color, right_color, top_color, bottom_color, left_x, right_x, top_y, bottom_y) borders = self.grid.edge_borders_cache[key] renderer = CellEdgeRenderer(self.painter, center, borders, rect, clip_path, self.grid.zoom) renderer.paint()
[docs] def paint_bottom_left_edge(self, rect: QRectF, clip_path: QPainterPath): """Paints bottom left edge of the cell :param rect: Cell rect of cell, for which the edge is painted :param clip_path: Clip rectangle that is requuired for QtSVG clipping top L | C left ------------ right BL | B bottom """ center = QPointF(rect.x(), rect.y() + rect.height()) left_key = self.cell_nav.left_keys()[-1] bottom_left_key = self.cell_nav.below_left_key() left_cell_nav = GridCellNavigator(self.grid, left_key) bottom_left_cell_nav = GridCellNavigator(self.grid, bottom_left_key) left_width = left_cell_nav.borderwidth_bottom right_width = self.cell_nav.borderwidth_bottom top_width = left_cell_nav.borderwidth_right bottom_width = bottom_left_cell_nav.borderwidth_right left_color = self.qcolor_cache[left_cell_nav.border_color_bottom] right_color = self.qcolor_cache[self.cell_nav.border_color_bottom] top_color = self.qcolor_cache[left_cell_nav.border_color_right] bottom_color = \ self.qcolor_cache[bottom_left_cell_nav.border_color_right] left_x = rect.x() - rect.width() right_x = rect.x() top_y = rect.y() + rect.height() bottom_y = rect.y() + 2 * rect.height() key = (left_width, right_width, top_width, bottom_width, left_color, right_color, top_color, bottom_color, left_x, right_x, top_y, bottom_y) borders = self.grid.edge_borders_cache[key] renderer = CellEdgeRenderer(self.painter, center, borders, rect, clip_path, self.grid.zoom) renderer.paint()
[docs] def paint_bottom_right_edge(self, rect: QRectF, clip_path: QPainterPath): """Paints bottom right edge of the cell :param rect: Cell rect of cell, for which the edge is painted :param clip_path: Clip rectangle that is requuired for QtSVG clipping top C | R left ----------- right B | BR bottom """ center = QPointF(rect.x() + rect.width(), rect.y() + rect.height()) right_key = self.cell_nav.right_keys()[-1] bottom_key = self.cell_nav.below_keys()[-1] right_cell_nav = GridCellNavigator(self.grid, right_key) bottom_cell_nav = GridCellNavigator(self.grid, bottom_key) left_width = self.cell_nav.borderwidth_bottom right_width = right_cell_nav.borderwidth_bottom top_width = self.cell_nav.borderwidth_right bottom_width = bottom_cell_nav.borderwidth_right left_color = self.qcolor_cache[self.cell_nav.border_color_bottom] right_color = self.qcolor_cache[right_cell_nav.border_color_bottom] top_color = self.qcolor_cache[self.cell_nav.border_color_right] bottom_color = self.qcolor_cache[bottom_cell_nav.border_color_right] left_x = rect.x() + rect.width() right_x = rect.x() + 2 * rect.width() top_y = rect.y() + rect.height() bottom_y = rect.y() + 2 * rect.height() key = (left_width, right_width, top_width, bottom_width, left_color, right_color, top_color, bottom_color, left_x, right_x, top_y, bottom_y) borders = self.grid.edge_borders_cache[key] renderer = CellEdgeRenderer(self.painter, center, borders, rect, clip_path, self.grid.zoom) renderer.paint()
[docs] def paint_borders(self, rect): """Paint cell borders""" clip_path = QPainterPath() # Required for clipping in SVG export clip_path.addRect(rect) self.painter.save() self.paint_bottom_border(rect, clip_path) self.paint_right_border(rect, clip_path) self.paint_above_borders(rect, clip_path) self.paint_left_borders(rect, clip_path) self.paint_top_left_edge(rect, clip_path) self.paint_top_right_edge(rect, clip_path) self.paint_bottom_left_edge(rect, clip_path) self.paint_bottom_right_edge(rect, clip_path) self.painter.restore()
[docs] def paint(self): """Paints the cell""" rect = QRectF(self.option.rect) with painter_save(self.painter): self.painter.setClipRect(self.option.rect) angle = self.cell_attributes[self.key].angle inner_rect = self.inner_rect(rect) with painter_rotate(self.painter, inner_rect, angle) as rrect: self.paint_content(rrect) self.paint_borders(rect)