def rebuildTiles(self): # create the foreground pixmap gantt = self.ganttWidget() header = gantt.treeWidget().header() width = self.sceneRect().width() height = header.height() # create the main color palette = gantt.palette() color = palette.color(palette.Button) textColor = palette.color(palette.ButtonText) borderColor = color.darker(140) text_align = Qt.AlignBottom | Qt.AlignHCenter # create the gradient gradient = QLinearGradient() gradient.setStart(0, 0) gradient.setFinalStop(0, height) gradient.setColorAt(0, color) gradient.setColorAt(1, color.darker(120)) # generate the tiles tiles = [] painters = [] for rect, label in self._topLabels: tile_rect = QRectF(rect.x(), 0, rect.width(), height) pixmap = QPixmap(rect.width(), height) with XPainter(pixmap) as painter: painter.setBrush(QBrush(gradient)) painter.drawRect(tile_rect) rx = 0 ry = 0 rw = rect.width() rh = rect.height() painter.setPen(borderColor) painter.drawRect(rx, ry, rw, rh) painter.setPen(textColor) painter.drawText(rx, ry, rw, rh - 2, text_align, label) tiles.append((tile_rect, pixmap)) painters.append((tile_rect, pixmap, painter)) # add bottom labels for rect, label in self._labels: for tile_rect, tile, painter in painters: if tile_rect.x() <= rect.x() and \ rect.right() <= tile_rect.right(): rx = rect.x() - tile_rect.x() ry = rect.y() rw = rect.width() rh = rect.height() with painter: painter.setPen(borderColor) painter.drawRect(rx, ry, rw, rh) painter.setPen(textColor) painter.drawText(rx, ry, rw, rh - 2, text_align, label) self._tiles = tiles
def layout(self, scene, nodes, center=None, padX=None, padY=None, direction=None, animationGroup=None): """ Lays out the nodes for this scene based on a block layering algorithm. :param scene | <XNodeScene> nodes | [<XNode>, ..] center | <QPointF> || None padX | <int> || None padY | <int> || None direction | <Qt.Direction> animationGroup | <QAnimationGroup> || None :return {<XNode>: <QRectF>, ..} | new rects per affected node """ nodes = filter(lambda x: x is not None and x.isVisible(), nodes) # make sure we have at least 1 node, otherwise, it is already laid out if not nodes or len(nodes) == 1: return {} # calculate the default padding based on the scene if padX == None: if direction == Qt.Vertical: padX = 2 * scene.cellWidth() else: padX = 4 * scene.cellWidth() if padY == None: if direction == Qt.Vertical: padY = 4 * scene.cellHeight() else: padY = 2 * scene.cellWidth() # step 1: create a mapping of the connections connection_map = self.connectionMap(scene, nodes) # step 2: organize the nodes into layers based on their connection chain layers = self.generateLayers(scene, nodes, connection_map) # step 3: calculate the total dimensions for the layout bounds = QRectF() # step 3.1: compare the nodes together that have common connections layer_widths = [] layer_heights = [] node_heights = {} node_widths = {} for layer_index, layer in enumerate(layers): layer_w = 0 layer_h = 0 layer_node_w = [] layer_node_h = [] for node in layer: rect = node.rect() layer_node_w.append(rect.width()) layer_node_h.append(rect.height()) if direction == Qt.Vertical: layer_w += rect.width() layer_h = max(rect.height(), layer_h) else: layer_w = max(rect.width(), layer_w) layer_h += rect.height() # update the bounding area if direction == Qt.Vertical: layer_w += padX * 1 - len(layer) bounds.setWidth(max(layer_w, bounds.width())) bounds.setHeight(bounds.height() + layer_h) else: layer_h += padY * 1 - len(layer) bounds.setWidth(bounds.width() + layer_w) bounds.setHeight(max(layer_h, bounds.height())) node_widths[layer_index] = layer_node_w node_heights[layer_index] = layer_node_h layer_widths.append(layer_w) layer_heights.append(layer_h) if not center: center = scene.sceneRect().center() w = bounds.width() h = bounds.height() bounds.setX(center.x() - bounds.width() / 2.0) bounds.setY(center.y() - bounds.height() / 2.0) bounds.setWidth(w) bounds.setHeight(h) # step 4: assign positions for each node by layer processed_nodes = {} layer_grps = [(i, layer) for i, layer in enumerate(layers)] layer_grps.sort(key=lambda x: len(x[1])) used_rects = [n.sceneRect() for n in scene.nodes() if not n in nodes] for layer_index, layer in reversed(layer_grps): layer_width = layer_widths[layer_index] layer_height = layer_heights[layer_index] # determine the starting point for this layer if direction == Qt.Vertical: offset = layer_index * padY + sum(layer_heights[:layer_index]) point = QPointF(bounds.x(), offset + bounds.y()) else: offset = layer_index * padX + sum(layer_widths[:layer_index]) point = QPointF(offset + bounds.x(), bounds.y()) # assign node positions based on existing connections for node_index, node in enumerate(layer): max_, min_ = (None, None) inputs, outputs = connection_map[node] for connected_node in inputs + outputs: if not connected_node in processed_nodes: continue npos = processed_nodes[connected_node] nrect = connected_node.rect() rect = QRectF(npos.x(), npos.y(), nrect.width(), nrect.height()) if direction == Qt.Vertical: if min_ is None: min_ = rect.left() min_ = min(rect.left(), min_) max_ = max(rect.right(), max_) else: if min_ is None: min_ = rect.top() min_ = min(rect.top(), min_) max_ = max(rect.bottom(), max_) if direction == Qt.Vertical: off_x = 0 off_y = (layer_height - node.rect().height()) / 2.0 start_x = (bounds.width() - layer_width) start_y = 0 else: off_x = (layer_width - node.rect().width()) / 2.0 off_y = 0 start_x = 0 start_y = (bounds.height() - layer_height) # align against existing nodes point_x = -1 point_y = -1 offset = 0 before = True found_point = True new_rect = QRectF() while found_point: if not None in (min_, max_): if direction == Qt.Vertical: off_x = (max_ - min_) / 2.0 - node.rect().width() / 2.0 if before: off_x -= offset offset += node.rect().width() + padX else: off_x += offset point_x = min_ + off_x point_y = point.y() + off_y else: off_y = (max_ - min_) / 2.0 - node.rect().height() / 2.0 if before: off_y -= offset offset += node.rect().height() + padY else: off_y += offset point_x = point.x() + off_x point_y = min_ + off_y # otherwise, align based on its position in the layer else: if direction == Qt.Vertical: off_x = sum(node_widths[layer_index][:node_index]) off_x += node_index * padX off_x += start_x if before: off_x -= offset offset += node.rect().width() + padX else: off_x += offset point_x = point.x() + off_x point_y = point.y() + off_y else: off_y = sum(node_heights[layer_index][:node_index]) off_y += node_index * padY off_y += start_y if before: off_y -= offset offset += node.rect().height() + padY else: off_y += offset point_x = point.x() + off_x point_y = point.y() + off_y # determine if we've already used this point before before = not before found_point = False orect = node.rect() new_rect = QRectF(point_x, point_y, orect.width(), orect.height()) for used_rect in used_rects: if used_rect.intersects(new_rect): found_point = True break used_rects.append(new_rect) if not animationGroup: node.setPos(point_x, point_y) else: anim = XNodeAnimation(node, 'setPos') anim.setStartValue(node.pos()) anim.setEndValue(QPointF(point_x, point_y)) animationGroup.addAnimation(anim) processed_nodes[node] = QPointF(point_x, point_y) if self._testing: QApplication.processEvents() time.sleep(0.5) return processed_nodes
def layout(self, scene, nodes, center=None, padX=None, padY=None, direction=None, animationGroup=None): """ Lays out the nodes for this scene based on a block layering algorithm. :param scene | <XNodeScene> nodes | [<XNode>, ..] center | <QPointF> || None padX | <int> || None padY | <int> || None direction | <Qt.Direction> animationGroup | <QAnimationGroup> || None :return {<XNode>: <QRectF>, ..} | new rects per affected node """ nodes = filter(lambda x: x is not None and x.isVisible(), nodes) # make sure we have at least 1 node, otherwise, it is already laid out if not nodes or len(nodes) == 1: return {} # calculate the default padding based on the scene if padX == None: if direction == Qt.Vertical: padX = 2 * scene.cellWidth() else: padX = 4 * scene.cellWidth() if padY == None: if direction == Qt.Vertical: padY = 4 * scene.cellHeight() else: padY = 2 * scene.cellWidth() # step 1: create a mapping of the connections connection_map = self.connectionMap(scene, nodes) # step 2: organize the nodes into layers based on their connection chain layers = self.generateLayers(scene, nodes, connection_map) # step 3: calculate the total dimensions for the layout bounds = QRectF() # step 3.1: compare the nodes together that have common connections layer_widths = [] layer_heights = [] node_heights = {} node_widths = {} for layer_index, layer in enumerate(layers): layer_w = 0 layer_h = 0 layer_node_w = [] layer_node_h = [] for node in layer: rect = node.rect() layer_node_w.append(rect.width()) layer_node_h.append(rect.height()) if direction == Qt.Vertical: layer_w += rect.width() layer_h = max(rect.height(), layer_h) else: layer_w = max(rect.width(), layer_w) layer_h += rect.height() # update the bounding area if direction == Qt.Vertical: layer_w += padX * 1 - len(layer) bounds.setWidth(max(layer_w, bounds.width())) bounds.setHeight(bounds.height() + layer_h) else: layer_h += padY * 1 - len(layer) bounds.setWidth(bounds.width() + layer_w) bounds.setHeight(max(layer_h, bounds.height())) node_widths[layer_index] = layer_node_w node_heights[layer_index] = layer_node_h layer_widths.append(layer_w) layer_heights.append(layer_h) if not center: center = scene.sceneRect().center() w = bounds.width() h = bounds.height() bounds.setX(center.x() - bounds.width() / 2.0) bounds.setY(center.y() - bounds.height() / 2.0) bounds.setWidth(w) bounds.setHeight(h) # step 4: assign positions for each node by layer processed_nodes = {} layer_grps = [(i, layer) for i, layer in enumerate(layers)] layer_grps.sort(key=lambda x: len(x[1])) used_rects = [n.sceneRect() for n in scene.nodes() if not n in nodes] for layer_index, layer in reversed(layer_grps): layer_width = layer_widths[layer_index] layer_height = layer_heights[layer_index] # determine the starting point for this layer if direction == Qt.Vertical: offset = layer_index * padY + sum(layer_heights[:layer_index]) point = QPointF(bounds.x(), offset + bounds.y()) else: offset = layer_index * padX + sum(layer_widths[:layer_index]) point = QPointF(offset + bounds.x(), bounds.y()) # assign node positions based on existing connections for node_index, node in enumerate(layer): max_, min_ = (None, None) inputs, outputs = connection_map[node] for connected_node in inputs + outputs: if not connected_node in processed_nodes: continue npos = processed_nodes[connected_node] nrect = connected_node.rect() rect = QRectF(npos.x(), npos.y(), nrect.width(), nrect.height()) if direction == Qt.Vertical: if min_ is None: min_ = rect.left() min_ = min(rect.left(), min_) max_ = max(rect.right(), max_) else: if min_ is None: min_ = rect.top() min_ = min(rect.top(), min_) max_ = max(rect.bottom(), max_) if direction == Qt.Vertical: off_x = 0 off_y = (layer_height - node.rect().height()) / 2.0 start_x = (bounds.width() - layer_width) start_y = 0 else: off_x = (layer_width - node.rect().width()) / 2.0 off_y = 0 start_x = 0 start_y = (bounds.height() - layer_height) # align against existing nodes point_x = -1 point_y = -1 offset = 0 before = True found_point = True new_rect = QRectF() while found_point: if not None in (min_, max_): if direction == Qt.Vertical: off_x = (max_ - min_)/2.0 - node.rect().width()/2.0 if before: off_x -= offset offset += node.rect().width() + padX else: off_x += offset point_x = min_ + off_x point_y = point.y() + off_y else: off_y = (max_ - min_)/2.0 - node.rect().height()/2.0 if before: off_y -= offset offset += node.rect().height() + padY else: off_y += offset point_x = point.x() + off_x point_y = min_ + off_y # otherwise, align based on its position in the layer else: if direction == Qt.Vertical: off_x = sum(node_widths[layer_index][:node_index]) off_x += node_index * padX off_x += start_x if before: off_x -= offset offset += node.rect().width() + padX else: off_x += offset point_x = point.x() + off_x point_y = point.y() + off_y else: off_y = sum(node_heights[layer_index][:node_index]) off_y += node_index * padY off_y += start_y if before: off_y -= offset offset += node.rect().height() + padY else: off_y += offset point_x = point.x() + off_x point_y = point.y() + off_y # determine if we've already used this point before before = not before found_point = False orect = node.rect() new_rect = QRectF(point_x, point_y, orect.width(), orect.height()) for used_rect in used_rects: if used_rect.intersects(new_rect): found_point = True break used_rects.append(new_rect) if not animationGroup: node.setPos(point_x, point_y) else: anim = XNodeAnimation(node, 'setPos') anim.setStartValue(node.pos()) anim.setEndValue(QPointF(point_x, point_y)) animationGroup.addAnimation(anim) processed_nodes[node] = QPointF(point_x, point_y) if self._testing: QApplication.processEvents() time.sleep(0.5) return processed_nodes