def rename_node(self): """Presents a rename dialog pop-up over the node item. The new name is sent to the model.""" # get dialog geometry scale_factor = self.view.transform().m11() scale = scale_factor if scale_factor > 0.75 else 0.75 title_rect = self.title_bounding_rect.toRect() width = title_rect.width() * scale height = title_rect.height() * scale pos = self.screen_pos geometry = QtCore.QRect(pos.x(), pos.y(), width, height) # rename dialog name = nxt_path.node_name_from_node_path(self.node_path) dialog = NameEditDialog(name=name, geometry=geometry, parent=None) color_name = self.colors[-1].name() dialog.setStyleSheet('background-color: %s; color: white' % color_name) font = QtGui.QFont(self.title_font.family(), self.title_font.pointSizeF() * scale) dialog.line_edit.setFont(font) dialog.exec_() # rename node if dialog.result(): new_name = dialog.value self.model.set_node_name(self.node_path, new_name, self.model.target_layer)
def __init__(self, node_path, parent=None): super(CheckBox, self).__init__(parent=parent) self.setStyleSheet('QWidget {margin-top: -4; margin-bottom: -4;}') # internal setup self._parent = parent self.stage_model = parent.stage_model self.node_path = node_path attr_data = get_resolved_attr_values(node_path=node_path, stage_model=self.stage_model) # layout self.main_layout = QtWidgets.QHBoxLayout(self) self.main_layout.setContentsMargins(0, 0, 0, 0) self.main_layout.setSpacing(0) alignment = attr_data.get(self.ALIGNMENT_ATTR) layout_alignment = self.ALIGNMENT.get(alignment) if layout_alignment: self.main_layout.setAlignment(layout_alignment) self.setLayout(self.main_layout) # checkbox text_color = attr_data.get(self.TEXT_COLOR_ATTR) or 'white' is_checked = attr_data.get(self.IS_CHECKED_ATTR) checked = True if is_checked == 'True' else False text = (attr_data.get(self.TEXT_ATTR) or nxt_path.node_name_from_node_path(self.node_path)) style = 'QCheckBox {color: %s;}' % text_color self.checkbox = QtWidgets.QCheckBox(text=text, checked=checked) self.checkbox.setStyleSheet(style) self.checkbox.toggled.connect(self.execute_node_path) self.main_layout.addWidget(self.checkbox) text_align = attr_data.get(self.TEXT_ALIGN_ATTR) label_alignment = self.LABEL_ALIGNMENT.get(text_align) if label_alignment: self.checkbox.setLayoutDirection(label_alignment) # add context menu ContextMenu(stage_model=self.stage_model, node_path=node_path, widget=self.checkbox, items=None, parent=self)
def get_widget_type(node_path, stage_model): node = stage_model.comp_layer.lookup(node_path) if not node: return instance_trace_list = stage_model.stage.get_instance_sources( node=node, trace_list=[], comp_layer=stage_model.comp_layer) if not instance_trace_list: return instance_node = instance_trace_list[-1] instance_path = stage_model.comp_layer.get_node_path(instance_node) if not instance_path: return instance_name = nxt_path.node_name_from_node_path(instance_path) widget_type = instance_name if instance_name in WIDGET_TYPES else None return widget_type
def test_partition_helpers(self): test_in_exp = { 'stuff.thing': 'stuff', 'all/other/stuff.another': 'all/other/stuff', '/stuff/and/things': '/stuff/and/things' } for inp, exp in test_in_exp.items(): result = nxt_path.node_path_from_attr_path(inp) self.assertEqual(exp, result) test_in_exp = { 'stuff.thing': 'thing', 'all/other/stuff.another': 'another', '/stuff/and/things': None } for inp, exp in test_in_exp.items(): result = nxt_path.attr_name_from_attr_path(inp) self.assertEqual(exp, result) test_in_exp = { 'all/other/stuff': 'stuff', '/stuff/and/things': 'things' } for inp, exp in test_in_exp.items(): result = nxt_path.node_name_from_node_path(inp) self.assertEqual(exp, result) test_in_exp = { 'all/other/stuff': 'all/other', '/stuff/and/things': '/stuff/and' } for inp, exp in test_in_exp.items(): result = nxt_path.get_parent_path(inp) self.assertEqual(exp, result) test_in_exp = { '/all/other/stuff': '/all', '/stuff/and/things': '/stuff' } for inp, exp in test_in_exp.items(): result = nxt_path.get_root_path(inp) self.assertEqual(exp, result)
def draw_title(self, painter, lod=1.): """Draw title of the node. Called exclusively in paint. :param painter: painter from paint. :type painter: QtGui.QPainter """ # draw bg painter.setPen(QtCore.Qt.NoPen) bg = painter.background() bgm = painter.backgroundMode() if self.error_item: self.scene().removeItem(self.error_item) self.error_item.deleteLater() self.error_item = None if self.is_real: painter.setBackgroundMode(QtCore.Qt.OpaqueMode) else: painter.setBackgroundMode(QtCore.Qt.TransparentMode) [c.setAlphaF(self.color_alpha) for c in self.colors] color_count = len(self.colors) color_band_width = 10 for i in range(color_count): color = self.colors[i] if self.is_proxy: painter.setBackground(color.darker(self.dim_factor)) brush = QtGui.QBrush(color.darker(self.dim_factor * 2), QtCore.Qt.FDiagPattern) painter.setBrush(brush) else: painter.setBrush(color.darker(self.dim_factor)) # Top Opinion if i + 1 == color_count: remaining_width = self.max_width - (i * color_band_width) painter.drawRect(0, 0, remaining_width, self.title_rect_height) # Lower Opinions else: x_pos = self.max_width - (i + 1) * color_band_width painter.drawRect(x_pos, 0, color_band_width, self.title_rect_height) painter.setBackground(bg) painter.setBackgroundMode(bgm) # draw exec plugs exec_attr = nxt_node.INTERNAL_ATTRS.EXECUTE_IN exec_in_pos = self.get_attr_in_pos(exec_attr, scene=False) exec_radius = self.EXEC_PLUG_RADIUS if nxt_path.get_parent_path(self.node_path) == nxt_path.WORLD: if self.is_start: color = self.start_color elif self.is_break: color = QtCore.Qt.red else: color = QtCore.Qt.white if not self.exec_in_plug: is_break = self.is_break if self.is_start: is_break = False self.exec_in_plug = NodeGraphicsPlug(pos=exec_in_pos, radius=exec_radius, color=color, is_exec=True, is_input=True, is_break=is_break, is_start=self.is_start) self.exec_in_plug.setParentItem(self) else: self.exec_in_plug.color = QtGui.QColor(color) self.exec_in_plug.is_break = self.is_break self.exec_in_plug.is_start = self.is_start self.exec_in_plug.setPos(exec_in_pos) self.exec_in_plug.update() out_pos = self.get_attr_out_pos(exec_attr, scene=False) if not self.exec_out_plug: self.exec_out_plug = NodeGraphicsPlug(pos=out_pos, radius=exec_radius, color=QtCore.Qt.white, is_exec=True, is_input=False) self.exec_out_plug.setParentItem(self) else: self.exec_out_plug.setPos(out_pos) else: if not self.exec_in_plug and self.is_break: self.exec_in_plug = NodeGraphicsPlug(pos=exec_in_pos, radius=exec_radius, color=QtCore.Qt.red, is_exec=True, is_input=True, is_break=self.is_break) self.exec_in_plug.setParentItem(self) elif self.exec_in_plug and not self.is_break: self.scene().removeItem(self.exec_in_plug) self.exec_in_plug = None if self.exec_out_plug: self.scene().removeItem(self.exec_out_plug) self.exec_out_plug = None if lod > MIN_LOD: # draw attr dots offset = -6 for fill in self.attr_dots: painter.setBrush(QtCore.Qt.white) if fill: painter.setBrush(QtCore.Qt.white) else: painter.setBrush(QtCore.Qt.NoBrush) dots_color = QtGui.QColor(QtCore.Qt.white).darker( self.dim_factor) painter.setPen(QtGui.QPen(dots_color, 0.5)) dot_x = self.max_width - 15 dot_y = (self.title_rect_height / 2) + offset painter.drawEllipse(QtCore.QPointF(dot_x, dot_y), 2, 2) offset += 6 # draw title painter.setFont(self.title_font) title_str = nxt_path.node_name_from_node_path(self.node_path) font_metrics = QtGui.QFontMetrics(self.title_font) width = self.max_width - 40 if self.error_list: width -= 20 if lod > MIN_LOD: painter.setPen( QtGui.QColor(QtCore.Qt.white).darker(self.dim_factor)) if not self.node_enabled: painter.setPen(QtGui.QColor(QtCore.Qt.white).darker(150)) title = font_metrics.elidedText(title_str, QtCore.Qt.ElideRight, width) painter.drawText(15, 0, self.max_width - 15, self.title_rect_height, QtCore.Qt.AlignVCenter, title) else: painter.setBrush( QtGui.QColor(QtCore.Qt.white).darker(self.dim_factor)) if not self.node_enabled: painter.setBrush(QtGui.QColor(QtCore.Qt.white).darker(150)) proxy_rect = font_metrics.boundingRect(title_str) r_width = proxy_rect.width() * .8 height = proxy_rect.height() painter.drawRect(15, height * .8, min(r_width, width), height * .2) if lod > MIN_LOD: # draw error if self.error_list: pos = QtCore.QPointF(self.max_width - 45, self.title_rect_height / 4) error_item = ErrorItem(font=QtGui.QFont('Roboto', 16, 75), pos=pos, text='!') error_item.setParentItem(self) error_item.setZValue(50) self.error_item = error_item # draw collapse state arrow for arrow in self.collapse_arrows: self.scene().removeItem(arrow) if lod > MIN_LOD: self.collapse_arrows = [] # TODO calculation needed arrows should be done outside drawing if self.collapse_state: des_colors = self.model.get_descendant_colors(self.node_path) filled = self.model.has_children(self.node_path) if not filled: des_colors = [QtCore.Qt.white] elif not des_colors: disp = self.model.comp_layer des_colors = [ self.model.get_node_color(self.node_path, disp) ] i = 0 num = len(des_colors) for c in des_colors: arrow = CollapseArrow(self, filled=filled, color=c) arrow_width = arrow.width * 1.1 center_offset = (arrow_width * (num * .5) - arrow_width * .5) cur_offset = (i * arrow_width) pos = ((self.max_width * .5) + center_offset - cur_offset) arrow.setPos(pos, self.boundingRect().height()) self.collapse_arrows += [arrow] i += 1
def __init__(self, node_path, parent=None): super(Button, self).__init__(parent=parent) # internal setup self._parent = parent self.stage_model = parent.stage_model self.node_path = node_path attr_data = get_resolved_attr_values(node_path=node_path, stage_model=self.stage_model) # button setup text = (attr_data.get(self.TEXT_ATTR) or nxt_path.node_name_from_node_path(node_path)) self.setText(text) self.pressed.connect(lambda p=self.node_path, a=self.EXEC_PATH_ATTR: self.execute_node_path(p, a)) background_color = attr_data.get( self.BACKGROUND_COLOR_ATTR) or '#606060' border_color = attr_data.get(self.BORDER_COLOR_ATTR) or 'none' text_color = attr_data.get(self.TEXT_COLOR_ATTR) or 'white' style = ''' QPushButton { color: %s; background-color: %s; border-radius: 9px; border: none; } QPushButton:hover { padding-bottom: -1; padding-top: -1; border: 2px solid %s; } ''' % (text_color, background_color, border_color) self.setStyleSheet(style) # add context menu items = [] for menu_item_path in self.get_menu_item_paths(): menu_data = get_resolved_attr_values(node_path=menu_item_path, stage_model=self.stage_model) if menu_data.get(self.SEPARATOR_ATTR) == 'True': items.append('separator') else: text = menu_data.get(self.TEXT_ATTR) action = QtWidgets.QAction(text, self) if menu_data.get(self.SELECTOR_ITEM_ATTR) == 'True': title = menu_data.get(self.SELECTOR_TITLE_ATTR) action.triggered.connect(lambda p=menu_item_path, t=title: self.selection_widget(p, t)) else: action.triggered.connect( lambda p=menu_item_path, a=self.ITEM_PATH_ATTR: self. execute_node_path(p, a)) items.append(action) ContextMenu(stage_model=self.stage_model, node_path=self.node_path, widget=self, items=items, parent=self)
def __init__(self, node_path, parent=None): super(DropDownMenu, self).__init__(parent=parent) size_policy = QtWidgets.QSizePolicy() size_policy.setHorizontalPolicy(QtWidgets.QSizePolicy.Preferred) size_policy.setHorizontalStretch(1) self.setSizePolicy(size_policy) # internal setup self._parent = parent self.stage_model = parent.stage_model self.node_path = node_path attr_data = get_resolved_attr_values(node_path=node_path, stage_model=self.stage_model) # layout self.main_layout = QtWidgets.QHBoxLayout(self) self.main_layout.setContentsMargins(0, 0, 0, 0) self.main_layout.setSpacing(0) self.setLayout(self.main_layout) # label text = (attr_data.get(self.TEXT_ATTR) or nxt_path.node_name_from_node_path(self.node_path)) text_color = attr_data.get(self.TEXT_COLOR_ATTR) or 'white' self.label = QtWidgets.QLabel(text) self.label.setStyleSheet('QLabel {color: %s}' % text_color) self.main_layout.addWidget(self.label) # style background_color = attr_data.get( self.BACKGROUND_COLOR_ATTR) or '#232323' border_color = attr_data.get(self.BORDER_COLOR_ATTR) or 'transparent' border_line = '2px solid %s' % border_color if border_color != 'transparent' else 'none' padding = '4' if border_color != 'transparent' else '6' style = ''' QComboBox { color: %s; background: %s; border-radius: 9px; border: none; padding-left: 6; overflow: hidden; } QComboBox:hover { padding-left: %s; border: %s; } ''' % (text_color, background_color, padding, border_line) view_style = ''' QComboBox QAbstractItemView { selection-color: white; selection-background-color: #148CD2; background: %s; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-left-radius: 9px; border-bottom-right-radius: 9px; border-width: 2px; border-style: solid; border-color: transparent %s %s %s; margin-right: 9px; margin-left: 9px; outline: 0; } ''' % (background_color, border_color, border_color, border_color) # combo box self.combo_box = QtWidgets.QComboBox(self) self.combo_box.setStyleSheet(style) self.combo_box.view().window().setWindowFlags( QtCore.Qt.Popup | QtCore.Qt.FramelessWindowHint | QtCore.Qt.NoDropShadowWindowHint) self.combo_box.view().setStyleSheet(view_style) self.combo_box.view().window().setAttribute( QtCore.Qt.WA_TranslucentBackground) self.main_layout.addWidget(self.combo_box, 1) # menu items menu_items = attr_data.get(self.MENU_ITEMS_ATTR) if not menu_items: return menu_items_list = menu_items.split(' ') self.combo_box.addItems(menu_items_list) menu_value = self.stage_model.get_node_attr_value( node_path=self.node_path, attr_name=self.MENU_VALUE_ATTR, layer=self.stage_model.comp_layer, data_state=DATA_STATE.RESOLVED) if menu_value in menu_items_list: self.combo_box.setCurrentText(menu_value) self.combo_box.currentIndexChanged.connect(self.execute_node_path) # add context menus ContextMenu(stage_model=self.stage_model, node_path=node_path, widget=self, items=None, parent=self) ContextMenu(stage_model=self.stage_model, node_path=node_path, widget=self.combo_box, items=None, parent=self)