from decimal import Decimal, getcontext from ..pyside import QtWidgets, QtGui, QtCore HANDLER_WIDTH = 16 HANDLER_HEIGHT = 16 class StackEditor(QtWidgets.QWidget): panelsChanged = QtCore.Signal(object) panelSelected = QtCore.Signal(int) panelDoubleClicked = QtCore.Signal(int) def __init__(self, parent=None): super(StackEditor, self).__init__(parent) self.data = [[1., [1.]]] self.orientation = 'vertical' self.stack_rects = get_stack_rects( self.data, self.rect(), self.orientation) self.setMouseTracking(True) self.clicked_action = None self.selected_index = None self.panels_are_changed = False self.panel_is_selected = None def set_orientation(self, orientation): self.orientation = orientation self.stack_rects = get_stack_rects( self.data, self.rect(), self.orientation) self.update() def set_data(self, data): self.data = data self.update() def sizeHint(self): return QtCore.QSize(300, 210) def resizeEvent(self, event): self.stack_rects = get_stack_rects( self.data, self.rect(), self.orientation) def mousePressEvent(self, event): if event.button() != QtCore.Qt.LeftButton: return self.clicked_action = self.get_action(event.pos()) def mouseDoubleClickEvent(self, event): if event.button() != QtCore.Qt.LeftButton: return clicked_action = self.get_action(event.pos()) if clicked_action[0] == 'select': panel = self.panel_number(clicked_action[1]) self.panelSelected.emit(panel) self.panelDoubleClicked.emit(panel) self.selected_index = clicked_action[1] self.update() def panel_number(self, index): k = 1 for i, (_, rows) in enumerate(self.data): for j in range(len(rows)): if [i, j] == index: return k k += 1 def mouseReleaseEvent(self, event): if event.button() != QtCore.Qt.LeftButton or not self.clicked_action: return index = self.clicked_action[1] if self.clicked_action[0] == 'delete': delete_panel(self.data, index) self.stack_rects = get_stack_rects( self.data, self.rect(), self.orientation) self.panelsChanged.emit(self.data) elif self.clicked_action[0] == 'select': index = self.clicked_action[1] if index == self.selected_index: self.selected_index = None self.panelSelected.emit(-1) else: self.selected_index = index self.panelSelected.emit(self.panel_number(self.selected_index)) else: self.check_buffer_states() self.update() self.clicked_action = None def check_buffer_states(self): if self.panel_is_selected is not None: self.panelSelected.emit(self.panel_is_selected) if self.panels_are_changed: self.panelsChanged.emit(self.data) self.panel_is_selected = None self.panels_are_changed = False def get_action(self, cursor): for i, column in enumerate(self.stack_rects): for j, rect in enumerate(column): if not rect.contains(cursor): continue if get_close_handler_rect(rect).contains(cursor): if i + j: return 'delete', [i, j] hrect = get_horizontal_handler_rect(rect) vrect = get_vertical_handler_rect(rect) if self.orientation == 'horizontal': hrect, vrect = vrect, hrect if hrect.contains(cursor): if j == len(column) - 1: return 'create vertical', [i, j] return 'move vertical', [i, j] if vrect.contains(cursor): if i == len(self.data) - 1: return 'create horizontal', [i, j] return 'move horizontal', [i, j] return 'select', [i, j] def mouseMoveEvent(self, event): if not self.clicked_action: return vertical = self.orientation == 'vertical' if self.clicked_action[0] == 'create vertical': index = self.clicked_action[1] col = self.data[index[0]][1] col[-1] -= .1 col.append(.1) self.clicked_action = 'move vertical', index self.selected_index = [index[0], index[1] + 1] self.panel_is_selected = self.panel_number(self.selected_index) self.panels_are_changed = True elif self.clicked_action[0] == 'create horizontal': index = self.clicked_action[1] self.data[-1][0] -= .1 self.data.append([.1, [1.]]) self.clicked_action = 'move horizontal', index self.selected_index = [index[0] + 1, 0] self.panel_is_selected = self.panel_number(self.selected_index) self.panels_are_changed = True elif self.clicked_action[0] == 'move vertical' and vertical: index = self.clicked_action[1] y = event.pos().y() / self.height() move_vertical(self.data, index, y) self.panels_are_changed = True elif self.clicked_action[0] == 'move vertical': index = self.clicked_action[1] x = event.pos().x() / self.width() move_vertical(self.data, index, x) self.panels_are_changed = True elif self.clicked_action[0] == 'move horizontal' and vertical: index = self.clicked_action[1] x = event.pos().x() / self.width() move_horizontal(self.data, index, x) self.panels_are_changed = True elif self.clicked_action[0] == 'move horizontal': index = self.clicked_action[1] y = event.pos().y() / self.height() move_horizontal(self.data, index, y) self.panels_are_changed = True self.stack_rects = get_stack_rects( self.data, self.rect(), self.orientation) self.update() def paintEvent(self, _): painter = QtGui.QPainter(self) k = 1 original_pen = painter.pen() original_brush = painter.brush() for i, column in enumerate(self.stack_rects): for j, rect in enumerate(column): if [i, j] == self.selected_index: pen = QtGui.QPen(QtGui.QColor('yellow')) pen.setWidth(5) painter.setPen(pen) brush = QtGui.QBrush(original_brush) color = brush.color() color.setAlpha(50) brush.setColor(color) brush.setStyle(QtCore.Qt.FDiagPattern) painter.setBrush(brush) else: pen = original_pen painter.setPen(pen) painter.setBrush(original_brush) painter.drawRect(rect) painter.setPen(QtCore.Qt.NoPen) painter.setBrush(pen.color()) handler_rect = get_horizontal_handler_rect(rect) painter.drawPath(up_arrow(handler_rect)) handler_rect = get_vertical_handler_rect(rect) painter.drawPath(left_arrow(handler_rect)) painter.setPen(original_pen) painter.setBrush(original_brush) font = QtGui.QFont() font.setPointSize(15) font.setBold(True) painter.setFont(font) painter.drawText(rect, QtCore.Qt.AlignCenter, str(k)) k += 1 if i + j == 0: continue font = QtGui.QFont() font.setBold(True) painter.setFont(font) painter.drawText( get_close_handler_rect(rect), QtCore.Qt.AlignCenter, 'X') painter.end() def get_stack_rects(data, rect, orientation): if orientation == 'vertical': return get_vertical_stack_rects(data, rect) return get_horizontal_stack_rects(data, rect) def get_horizontal_stack_rects(data, rect): result = [] y = 0 for height, rows in data: column = [] x = 0 for width in rows: panel_rect = QtCore.QRectF( x * rect.width(), y * rect.height(), (width * rect.width()) - 1, (height * rect.height()) - 1) column.append(panel_rect) x += width y += height result.append(column) return result def get_vertical_stack_rects(data, rect): result = [] x = 0 for width, rows in data: column = [] y = 0 for height in rows: panel_rect = QtCore.QRectF( x * rect.width(), y * rect.height(), (width * rect.width()) - 1, (height * rect.height()) - 1) column.append(panel_rect) y += height x += width result.append(column) return result def get_vertical_handler_rect(rect): return QtCore.QRectF( rect.right() - HANDLER_WIDTH, rect.center().y() - (HANDLER_HEIGHT / 2), HANDLER_WIDTH, HANDLER_HEIGHT) def get_horizontal_handler_rect(rect): return QtCore.QRectF( rect.center().x() - (HANDLER_WIDTH / 2), rect.bottom() - HANDLER_HEIGHT, HANDLER_WIDTH, HANDLER_HEIGHT) def get_close_handler_rect(rect): return QtCore.QRectF( rect.right() - HANDLER_WIDTH, rect.top(), HANDLER_HEIGHT, HANDLER_WIDTH) def delete_panel(data, index): column = data[index[0]][1] if len(column) > 1: data[index[0]][1] = delete_value(column, index[1]) return values = delete_value([c[0] for c in data], index[0]) del data[index[0]] for i, value in enumerate(values): data[i][0] = value def delete_value(values, index): getcontext().prec = 50 decimal_values = [Decimal(v) for v in values] values = [ Decimal(v) for i, v in enumerate(decimal_values) if i != index] return [ float((v / sum(values)).quantize(Decimal('1.00000'))) for v in values] def move_vertical(data, index, y): column = data[index[0]][1] ratios = to_ratios(column) if index[1] == 0: y = max((.1, y)) else: y = max((ratios[index[1] - 1] + .1, y)) y = min((y, ratios[index[1] + 1] - .1)) ratios[index[1]] = y data[index[0]][1] = to_weights(ratios) def move_horizontal(data, index, x): ratios = to_ratios(c[0] for c in data) if index[0] == 0: x = max((.1, x)) else: x = max((ratios[index[0] - 1] + .1, x)) x = min((x, ratios[index[0] + 1] - .1)) ratios[index[0]] = x for i, col in enumerate(to_weights(ratios)): data[i][0] = col def up_arrow(rect): path = QtGui.QPainterPath(rect.bottomLeft()) path.lineTo(rect.bottomRight()) point = QtCore.QPointF(rect.center().x(), rect.top()) path.lineTo(point) path.lineTo(rect.bottomLeft()) return path def left_arrow(rect): path = QtGui.QPainterPath(rect.topRight()) path.lineTo(rect.bottomRight()) point = QtCore.QPointF(rect.left(), rect.center().y()) path.lineTo(point) path.lineTo(rect.topRight()) return path def to_ratios(weights): """ Convert weight list to ratios. input: [0.2, 0.3, 0.4, 0.1] output: [0.2, 0.5, 0.9, 1.0] """ total = 0.0 result = [] for weight in weights: total += weight result.append(total) return result def to_weights(ratios): """ Convert ratio list to weights. input: [0.2, 0.5, 0.9, 1.0] output: [0.2, 0.3, 0.4, 0.1] """ result = [] result.extend(ratio - sum(result) for ratio in ratios) return result