def add_conf_group(self, conf_group_widget): row = conf_group_widget.childCount() conformer_item = QTreeWidgetItem(conf_group_widget) conf_group_widget.insertChild(row, conformer_item) nrg_combobox = FilereaderComboBox(self.session, otherItems=['energy']) nrg_combobox.currentIndexChanged.connect( lambda *args: self.changes.emit()) freq_combobox = FilereaderComboBox(self.session, otherItems=['frequency']) freq_combobox.currentIndexChanged.connect( lambda *args: self.changes.emit()) trash_button = QPushButton() trash_button.setFlat(True) trash_button.clicked.connect( lambda *args, combobox=nrg_combobox: combobox.deleteLater()) trash_button.clicked.connect( lambda *args, combobox=freq_combobox: combobox.deleteLater()) trash_button.clicked.connect(lambda *args, child=conformer_item: conf_group_widget.removeChild(child)) trash_button.clicked.connect(lambda *args: self.changes.emit()) trash_button.setIcon( QIcon(self.style().standardIcon(QStyle.SP_DialogCancelButton))) self.tree.setItemWidget(conformer_item, 0, nrg_combobox) self.tree.setItemWidget(conformer_item, 1, freq_combobox) self.tree.setItemWidget(conformer_item, 2, trash_button) self.changes.emit()
class InputWidgetSingle(InputWidgetRaw): """ This type of widget is used for a simple widgets like buttons, checkboxes etc. It consists of horizontal layout widget itself and reset button. """ def __init__(self, parent=None, dataSetCallback=None, defaultValue=None, userStructClass=None, **kwds): super(InputWidgetSingle, self).__init__(parent=parent, dataSetCallback=dataSetCallback, defaultValue=defaultValue, userStructClass=userStructClass, **kwds) # from widget self.bWidgetSet = False self.gridLayout = QGridLayout(self) self.gridLayout.setSpacing(1) self.gridLayout.setContentsMargins(0, 0, 0, 0) self.gridLayout.setObjectName("gridLayout") self.horizontalLayout = QHBoxLayout() self.horizontalLayout.setObjectName("horizontalLayout") spacerItem = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem) self.pbReset = QPushButton(self) self.pbReset.setMaximumSize(QtCore.QSize(25, 25)) self.pbReset.setText("") self.pbReset.setObjectName("pbReset") self.pbReset.setIcon(QtGui.QIcon(":/icons/resources/reset.png")) self.horizontalLayout.addWidget(self.pbReset) self.pbReset.clicked.connect(self.onResetValue) self.gridLayout.addLayout(self.horizontalLayout, 0, 0, 1, 1) self._index = 0 def setWidget(self, widget): self.horizontalLayout.insertWidget(self._index, widget)
def get_button(button_type): button = QPushButton() button.setFlat(True) if button_type == "success": button.setIcon(button.style().standardIcon( QStyle.SP_DialogApplyButton)) elif button_type == "fail": button.setIcon(button.style().standardIcon( QStyle.SP_MessageBoxCritical)) elif button_type == "error": button.setIcon(button.style().standardIcon( QStyle.SP_MessageBoxWarning)) elif button_type == "skip" or button_type == "expected fail": button.setIcon(button.style().standardIcon( QStyle.SP_MessageBoxInformation)) elif button_type == "unexpected success": button.setIcon(button.style().standardIcon( QStyle.SP_MessageBoxQuestion)) return button
def add_mol_group(self, *args): row = self.tree.topLevelItemCount() root_item = self.tree.invisibleRootItem() mol_group = QTreeWidgetItem(root_item) self.tree.insertTopLevelItem(row, mol_group) trash_button = QPushButton() trash_button.setFlat(True) trash_button.clicked.connect( lambda *args, parent=mol_group: self.remove_mol_group(parent)) trash_button.setIcon( QIcon(self.style().standardIcon(QStyle.SP_DialogDiscardButton))) add_conf_button = QPushButton("add conformer") add_conf_button.setFlat(True) add_conf_button.clicked.connect( lambda *args, conf_group_widget=mol_group: self.add_conf_group( conf_group_widget)) add_conf_button2 = QPushButton("") add_conf_button2.setFlat(True) add_conf_button2.clicked.connect( lambda *args, conf_group_widget=mol_group: self.add_conf_group( conf_group_widget)) add_conf_child = QTreeWidgetItem(mol_group) self.tree.setItemWidget(add_conf_child, 0, add_conf_button) self.tree.setItemWidget(add_conf_child, 1, add_conf_button2) self.tree.setItemWidget(mol_group, 2, trash_button) mol_group.setText(0, "group %i" % row) mol_group.addChild(add_conf_child) self.add_conf_group(mol_group) self.tree.expandItem(mol_group) self.changes.emit()
class Template(base.BaseWidget): templateChecked = Signal(object) def __init__(self, parent=None): super(Template, self).__init__(parent=parent) def ui(self): super(Template, self).ui() self.setMaximumWidth(160) self.setMaximumHeight(200) widget_layout = layouts.VerticalLayout(spacing=0, margins=(2, 2, 2, 2)) main_frame = QFrame() main_frame.setFrameStyle(QFrame.StyledPanel | QFrame.Raised) main_frame.setLineWidth(1) main_frame.setLayout(widget_layout) self.main_layout.addWidget(main_frame) self.template_btn = QPushButton('', self) self.template_btn.setCheckable(True) self.template_btn.setIcon(self.get_icon()) self.template_btn.setIconSize(QSize(120, 120)) template_lbl = label.BaseLabel(self.name, parent=self) template_lbl.setObjectName('templateLabel') template_lbl.setAlignment(Qt.AlignCenter) widget_layout.addWidget(self.template_btn) widget_layout.addWidget(template_lbl) def setup_signals(self): self.template_btn.toggled.connect(self._on_selected_template) def get_icon(self): return resources.icon(name='project', extension='png') def _on_selected_template(self, template): self.templateChecked.emit(self)
class UIVariable(QWidget, IPropertiesViewSupport): def __init__(self, rawVariable, variablesWidget, parent=None): super(UIVariable, self).__init__(parent) self._rawVariable = rawVariable self.variablesWidget = variablesWidget # ui self.horizontalLayout = QHBoxLayout(self) self.horizontalLayout.setSpacing(1) self.horizontalLayout.setContentsMargins(1, 1, 1, 1) self.horizontalLayout.setObjectName("horizontalLayout") self.widget = TypeWidget( findPinClassByType(self._rawVariable.dataType).color(), self) self.widget.setObjectName("widget") self.horizontalLayout.addWidget(self.widget) self.labelName = QLabel(self) self.labelName.setStyleSheet("background:transparent") self.labelName.setObjectName("labelName") self.horizontalLayout.addWidget(self.labelName) spacerItem = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem) # find refs self.pbFindRefs = QPushButton("") self.pbFindRefs.setIcon( QtGui.QIcon(":/searching-magnifying-glass.png")) self.pbFindRefs.setObjectName("pbFindRefs") self.horizontalLayout.addWidget(self.pbFindRefs) self.pbFindRefs.clicked.connect(self.onFindRefsClicked) # kill variable self.pbKill = QPushButton("") self.pbKill.setIcon(QtGui.QIcon(":/delete_icon.png")) self.pbKill.setObjectName("pbKill") self.horizontalLayout.addWidget(self.pbKill) self.pbKill.clicked.connect(self.onKillClicked) QtCore.QMetaObject.connectSlotsByName(self) self.setName(self._rawVariable.name) self._rawVariable.setWrapper(self) def onStructureChanged(self, name): self._rawVariable.structure = PinStructure[name] self.variablesWidget.pyFlowInstance.onRequestFillProperties( self.createPropertiesWidget) EditorHistory().saveState("Change variable struct", modify=True) def setDataType(self, dataType): self.dataType = dataType self._rawVariable.dataType = dataType EditorHistory().saveState("Change variable data type", modify=True) def createPropertiesWidget(self, propertiesWidget): baseCategory = CollapsibleFormWidget(headName="Base", modify=True) # name le_name = QLineEdit(self._rawVariable.name) le_name.returnPressed.connect(lambda: self.setName(le_name.text())) baseCategory.addWidget("Name", le_name) # data type cbTypes = EnumComboBox([ pin.__name__ for pin in getAllPinClasses() if pin.IsValuePin() if pin.__name__ != "AnyPin" ]) cbTypes.setCurrentIndex(cbTypes.findText(self.dataType)) cbTypes.setEditable(False) cbTypes.changeCallback.connect(self.setDataType) propertiesWidget.addWidget(baseCategory) # structure type cbStructure = EnumComboBox( [i.name for i in (PinStructure.Single, PinStructure.Array)]) cbStructure.setEditable(False) cbStructure.setCurrentIndex( cbStructure.findText(self._rawVariable.structure.name)) cbStructure.changeCallback.connect(self.onStructureChanged) propertiesWidget.addWidget(baseCategory) baseCategory.addWidget("Type", cbTypes) baseCategory.addWidget("Structure", cbStructure) valueCategory = CollapsibleFormWidget(headName="Value") # current value if self._rawVariable.structure == PinStructure.Single: if not type(self._rawVariable.value) in {list, set, dict, tuple}: def valSetter(x): self._rawVariable.value = x w = createInputWidget( self._rawVariable.dataType, valSetter, getPinDefaultValueByType(self._rawVariable.dataType)) if w: w.setWidgetValue(self._rawVariable.value) w.setObjectName(self._rawVariable.name) valueCategory.addWidget(self._rawVariable.name, w) # access level cb = QComboBox() cb.addItem('public', 0) cb.addItem('private', 1) cb.addItem('protected', 2) def accessLevelChanged(x): self._rawVariable.accessLevel = AccessLevel[x] EditorHistory().saveState("Change variable access level", modify=True) cb.currentTextChanged.connect(accessLevelChanged) cb.setCurrentIndex(self._rawVariable.accessLevel) valueCategory.addWidget('Access level', cb) propertiesWidget.addWidget(valueCategory) def onFindRefsClicked(self): from PyFlow.App import PyFlow refs = [n.getWrapper() for n in self._rawVariable.findRefs()] app = self.variablesWidget.pyFlowInstance if "Search results" not in [ t.name() for t in app.getRegisteredTools() ]: app.invokeDockToolByName("PyFlowBase", "Search results") self.variablesWidget.pyFlowInstance.getCanvas( ).requestShowSearchResults.emit(refs) def onKillClicked(self): # check refs and ask user what to do refs = self._rawVariable.findRefs() if len(refs) > 0: item, accepted = QInputDialog.getItem( None, 'Decide!', 'What to do with getters and setters in canvas?', ['kill', 'leave'], editable=False) if accepted: self.variablesWidget.killVar(self) if item == 'kill': for i in refs: i.kill() elif item == 'leave': for i in refs: i.var = None else: self.variablesWidget.killVar(self) @property def dataType(self): return self._rawVariable.dataType @dataType.setter def dataType(self, value): self._rawVariable.dataType = value self.widget.color = findPinClassByType( self._rawVariable.dataType).color() self.widget.update() self.variablesWidget.onUpdatePropertyView(self) @property def packageName(self): return self._rawVariable.packageName @property def accessLevel(self): return self._rawVariable.accessLevel @accessLevel.setter def accessLevel(self, value): self._rawVariable.accessLevel = value @property def uid(self): return self._rawVariable.uid @uid.setter def uid(self, value): self._rawVariable.uid = value if self._rawVariable.uid in self.graph.getVars(): self.graph.getVars().pop(self._rawVariable.uid) self.graph.getVars()[self._rawVariable.uid] = self._rawVariable @staticmethod def jsonTemplate(): template = { 'name': None, 'uuid': None, 'value': None, 'type': None, 'package': None, 'accessLevel': None } return template def serialize(self): pinClass = findPinClassByType(self._rawVariable.dataType) template = UIVariable.jsonTemplate() template['name'] = self._rawVariable.name template['uuid'] = str(self._rawVariable.uid) if self._rawVariable.dataType == "AnyPin": # don't save any variables # value will be calculated for this type of variables template['value'] = None else: template['value'] = json.dumps(self._rawVariable.value, cls=pinClass.jsonEncoderClass()) template['type'] = self._rawVariable.dataType template['package'] = self._rawVariable.packageName template['accessLevel'] = self._rawVariable.accessLevel.value return template @staticmethod def deserialize(data, graph): pinClass = findPinClassByType(data['dataType']) varUid = uuid.UUID(data['uuid']) var = graph.getApp().variablesWidget.createVariable( dataType=data['dataType'], accessLevel=AccessLevel(data['accessLevel']), uid=varUid) var.setName(data['name']) var.setDataType(data['dataType']) if data['dataType'] == 'AnyPin': var.value = getPinDefaultValueByType('AnyPin') else: var.value = json.loads(data['value'], cls=pinClass.jsonDecoderClass()) return var @property def value(self): return self._rawVariable.value @value.setter def value(self, data): self._rawVariable.value = data def mousePressEvent(self, event): super(UIVariable, self).mousePressEvent(event) self.variablesWidget.onUpdatePropertyView(self) def setName(self, name): self._rawVariable.name = name self.labelName.setText(self._rawVariable.name)
class WindowDragger(QFrame, object): """ Class to create custom window dragger for Solstice Tools """ DEFAULT_LOGO_ICON_SIZE = 22 doubleClicked = Signal() def __init__(self, window=None, on_close=None): super(WindowDragger, self).__init__(window) self._window = window self._dragging_enabled = True self._lock_window_operations = False self._mouse_press_pos = None self._mouse_move_pos = None self._dragging_threshold = 5 self._minimize_enabled = True self._maximize_enabled = True self._on_close = on_close self.setObjectName('titleFrame') self.ui() # ================================================================================================================= # PROPERTIES # ================================================================================================================= @property def contents_layout(self): return self._contents_layout @property def corner_contents_layout(self): return self._corner_contents_layout # ================================================================================================================= # OVERRIDES # ================================================================================================================= def mousePressEvent(self, event): if event.button() == Qt.LeftButton and self._dragging_enabled: self._mouse_press_pos = event.globalPos() self._mouse_move_pos = event.globalPos() - self._window.pos() super(WindowDragger, self).mousePressEvent(event) def mouseMoveEvent(self, event): if event.buttons() & Qt.LeftButton: global_pos = event.globalPos() if self._mouse_press_pos and self._dragging_enabled: moved = global_pos - self._mouse_press_pos if moved.manhattanLength() > self._dragging_threshold: diff = global_pos - self._mouse_move_pos self._window.move(diff) self._mouse_move_pos = global_pos - self._window.pos() super(WindowDragger, self).mouseMoveEvent(event) def mouseDoubleClickEvent(self, event): if self._lock_window_operations: return if self._button_maximized.isVisible(): self._on_maximize_window() else: self._on_restore_window() super(WindowDragger, self).mouseDoubleClickEvent(event) self.doubleClicked.emit() def mouseReleaseEvent(self, event): if self._mouse_press_pos is not None: if event.button() == Qt.LeftButton and self._dragging_enabled: moved = event.globalPos() - self._mouse_press_pos if moved.manhattanLength() > self._dragging_threshold: event.ignore() self._mouse_press_pos = None super(WindowDragger, self).mouseReleaseEvent(event) # ================================================================================================================= # BASE # ================================================================================================================= def ui(self): self.setFixedHeight(qtutils.dpi_scale(40)) main_layout = layouts.HorizontalLayout(spacing=5, margins=(15, 0, 15, 0)) self.setLayout(main_layout) self._logo_button = self._setup_logo_button() self._title_text = label.ClippedLabel(text=self._window.windowTitle()) self._title_text.setObjectName('WindowDraggerLabel') self._contents_layout = layouts.HorizontalLayout() self._corner_contents_layout = layouts.HorizontalLayout() main_layout.addWidget(self._logo_button) main_layout.addWidget(self._title_text) main_layout.addItem( QSpacerItem(25, 0, QSizePolicy.Fixed, QSizePolicy.Fixed)) main_layout.addLayout(self._contents_layout) main_layout.addLayout(self._corner_contents_layout) buttons_widget = QWidget() self.buttons_layout = layouts.HorizontalLayout(spacing=0, margins=(0, 0, 0, 0)) self.buttons_layout.setAlignment(Qt.AlignRight) buttons_widget.setLayout(self.buttons_layout) main_layout.addWidget(buttons_widget) self._button_minimized = QPushButton() self._button_minimized.setIconSize(QSize(25, 25)) # self._button_minimized.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)) self._button_minimized.setIcon( resources.icon('minimize', theme='window')) self._button_minimized.setStyleSheet( 'QWidget {background-color: rgba(255, 255, 255, 0); border:0px;}') self._button_maximized = QPushButton() self._button_maximized.setIcon( resources.icon('maximize', theme='window')) # self._button_maximized.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)) self._button_maximized.setStyleSheet( 'QWidget {background-color: rgba(255, 255, 255, 0); border:0px;}') self._button_maximized.setIconSize(QSize(25, 25)) self._button_restored = QPushButton() # self._button_restored.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)) self._button_restored.setVisible(False) self._button_restored.setIcon(resources.icon('restore', theme='window')) self._button_restored.setStyleSheet( 'QWidget {background-color: rgba(255, 255, 255, 0); border:0px;}') self._button_restored.setIconSize(QSize(25, 25)) self._button_closed = QPushButton() # button_closed.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)) self._button_closed.setIcon(resources.icon('close', theme='window')) self._button_closed.setStyleSheet( 'QWidget {background-color: rgba(255, 255, 255, 0); border:0px;}') self._button_closed.setIconSize(QSize(25, 25)) self.buttons_layout.addWidget(self._button_minimized) self.buttons_layout.addWidget(self._button_maximized) self.buttons_layout.addWidget(self._button_restored) self.buttons_layout.addWidget(self._button_closed) self._button_maximized.clicked.connect(self._on_maximize_window) self._button_minimized.clicked.connect(self._on_minimize_window) self._button_restored.clicked.connect(self._on_restore_window) self._button_closed.clicked.connect(self._on_close_window) def set_icon(self, icon=None, highlight=False): """ Sets the icon of the window dragger :param icon: QIcon :param highlight: bool """ icon = icon or self._window.windowIcon() if not icon or icon.isNull(): icon = resources.icon('tpDcc') size = self.DEFAULT_LOGO_ICON_SIZE if highlight: self._logo_button.set_icon( [icon], colors=[None], tint_composition=QPainter.CompositionMode_Plus, size=size, icon_scaling=[1], color_offset=0, grayscale=True) else: self._logo_button.set_icon([icon], colors=None, size=size, icon_scaling=[1], color_offset=0) self._logo_button.set_icon_idle(icon) # self._lbl_icon.setPixmap(icon.pixmap(icon.actualSize(QSize(24, 24)))) def set_height(self, value): """ Sets the size of the dragger and updates icon :param value: float """ self.setFixedHeight(qtutils.dpi_scale(value)) def set_title(self, title): """ Sets the title of the window dragger :param title: str """ self._title_text.setText(title) def set_dragging_enabled(self, flag): """ Sets whether or not drag functionality is enabled :param flag: bool """ self._dragging_enabled = flag def set_minimize_enabled(self, flag): """ Sets whether dragger shows minimize button or not :param flag: bool """ self._minimize_enabled = flag self._button_minimized.setVisible(flag) def set_maximized_enabled(self, flag): """ Sets whether dragger shows maximize button or not :param flag: bool """ self._maximize_enabled = flag self._button_maximized.setVisible(flag) def show_logo(self): """ Shows window logo """ self._logo_button.setVisible(True) def hide_logo(self): """ Hides window logo """ self._logo_button.setVisible(False) def set_window_buttons_state(self, state, show_close_button=False): """ Sets the state of the dragger buttons :param state: bool :param show_close_button: bool """ self._lock_window_operations = not state self._button_closed.setEnabled(state or show_close_button) self._button_closed.setVisible(state or show_close_button) if self._maximize_enabled: self._button_maximized.setEnabled(state) self._button_maximized.setVisible(state) else: self._button_maximized.setEnabled(False) self._button_maximized.setVisible(False) if self._minimize_enabled: self._button_minimized.setEnabled(state) self._button_minimized.setVisible(state) else: self._button_minimized.setEnabled(False) self._button_minimized.setVisible(False) if not state: self._button_restored.setEnabled(state) self._button_restored.setVisible(state) else: if self.isMaximized(): self._button_restored.setEnabled(state) self._button_restored.setVisible(state) def set_frameless_enabled(self, frameless=False): """ Enables/Disables frameless mode or OS system default :param frameless: bool """ from tpDcc.managers import tools tool_inst = tools.ToolsManager().get_tool_by_plugin_instance( self._window) if not tool_inst: return offset = QPoint() if self._window.docked(): rect = self._window.rect() pos = self._window.mapToGlobal(QPoint(-10, -10)) rect.setWidth(rect.width() + 21) self._window.close() else: rect = self.window().rect() pos = self.window().pos() offset = QPoint(3, 15) self.window().close() tool_inst._launch(launch_frameless=frameless) new_tool = tool_inst.latest_tool() QTimer.singleShot( 0, lambda: new_tool.window().setGeometry(pos.x() + offset.x(), pos.y() + offset.y(), rect.width(), rect.height())) new_tool.framelessChanged.emit(frameless) QApplication.processEvents() return new_tool def _setup_logo_button(self): """ Internal function that setup window dragger button logo :return: IconMenuButton """ from tpDcc.libs.qt.widgets import buttons logo_button = buttons.IconMenuButton(parent=self) logo_button.setIconSize(QSize(24, 24)) logo_button.setFixedSize(QSize(30, 30)) self._toggle_frameless = logo_button.addAction( 'Toggle Frameless Mode', connect=self._on_toggle_frameless_mode, checkable=True) self._toggle_frameless.setChecked(self._window.is_frameless()) logo_button.set_menu_align(Qt.AlignLeft) return logo_button def _on_toggle_frameless_mode(self, action): """ Internal callback function that is called when switch frameless mode button is pressed by user :param flag: bool """ self.set_frameless_enabled(action.isChecked()) def _on_maximize_window(self): """ Internal callback function that is called when the user clicks on maximize button """ self._button_restored.setVisible(True) self._button_maximized.setVisible(False) self._window.setWindowState(Qt.WindowMaximized) def _on_minimize_window(self): """ Internal callback function that is called when the user clicks on minimize button """ self._window.setWindowState(Qt.WindowMinimized) def _on_restore_window(self): """ Internal callback function that is called when the user clicks on restore button """ self._button_restored.setVisible(False) self._button_maximized.setVisible(True) self._window.setWindowState(Qt.WindowNoState) def _on_close_window(self): """ Internal callback function that is called when the user clicks on close button """ from tpDcc.managers import tools closed = False if hasattr(self._window, 'WindowId'): closed = tools.ToolsManager().close_tool(self._window.WindowId, force=False) if not closed: if hasattr(self._window, 'docked'): if self._window.docked(): self._window.fade_close() else: self.window().fade_close() else: self._window.fade_close()
def __init__(self, parent=None): QMainWindow.__init__(self, parent) self.data = ParticleData() toolbar = self.addToolBar("Test") openButton = QPushButton("") openButton.setFlat(True) openButton.setIconSize(QSize(32, 32)) openButton.setIcon(QIcon("/jobs2/soft/icons/dlight/open.png")) openButton.setToolTip("Open File") toolbar.addWidget(openButton) openButton.clicked.connect(self.openSlot) QShortcut(QKeySequence(Qt.CTRL + Qt.Key_O), self, self.openSlot) saveButton = QPushButton("") saveButton.setFlat(True) saveButton.setIconSize(QSize(32, 32)) saveButton.setIcon(QIcon("/jobs2/soft/icons/dlight/file_save.png")) saveButton.setToolTip("Save File") toolbar.addWidget(saveButton) saveButton.clicked.connect(self.saveSlot) QShortcut(QKeySequence(Qt.CTRL + Qt.Key_S), self, self.saveSlot) saveDeltaButton = QPushButton("") saveDeltaButton.setFlat(True) saveDeltaButton.setIconSize(QSize(32, 32)) saveDeltaButton.setIcon( QIcon("/jobs2/soft/icons/dlight/file_save_as.png")) saveDeltaButton.setToolTip("Save File As Delta") toolbar.addWidget(saveDeltaButton) saveDeltaButton.clicked.connect(self.saveDeltaSlot) QShortcut(QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_S), self, self.saveDeltaSlot) addParticleButton = QPushButton("Particle") addParticleButton.setFlat(True) addParticleButton.setIconSize(QSize(32, 32)) addParticleButton.setIcon(QIcon("/jobs2/soft/icons/shared/plus.png")) addParticleButton.setToolTip("Add Particle") toolbar.addWidget(addParticleButton) addParticleButton.clicked.connect(self.addParticleSlot) addAttributeButton = QPushButton("Attribute") addAttributeButton.setFlat(True) addAttributeButton.setIconSize(QSize(32, 32)) addAttributeButton.setIcon(QIcon("/jobs2/soft/icons/shared/plus.png")) addAttributeButton.setToolTip("Add Attribute") toolbar.addWidget(addAttributeButton) addAttributeButton.clicked.connect(self.addAttributeSlot) splitter = QSplitter(self) self.setCentralWidget(splitter) particleTable = ParticleTableWidget(self.data, self) splitter.addWidget(particleTable) right = QWidget(self) splitter.addWidget(right) vbox = QVBoxLayout(right) right.setLayout(vbox) fixedAttrWidget = FixedAttributesWidget(self.data, self) vbox.addWidget(fixedAttrWidget) indexedStrings = IndexedStringsWidget(self.data, self) vbox.addWidget(indexedStrings) vbox.addStretch() # TODD: SCROLLABLE AREAS FOR EVERYTHING self.data.dirtied.connect(self.dataDirtiedSlot) # Configure ctrl-w to close the window QShortcut(QKeySequence(Qt.CTRL + Qt.Key_W), self, self.close)
def create_flat_button( icon=None, icon_size=None, name='', text=200, background_color=[54, 51, 51], ui_color=68, border_color=180, push_col=120, checkable=True, w_max=None, w_min=None, h_max=None, h_min=None, policy=None, tip=None, flat=True, hover=True, destroy_flag=False, context=None, ): btn = QPushButton() btn.setText(name) btn.setCheckable(checkable) if icon: if isinstance(icon, QIcon): btn.setIcon(icon) else: btn.setIcon(QIcon(icon)) btn.setFlat(flat) if flat: change_button_color(button=btn, text_color=text, bg_color=ui_color, hi_color=background_color, mode='button', hover=hover, destroy=destroy_flag, ds_color=border_color) btn.toggled.connect( lambda: change_button_color(button=btn, text_color=text, bg_color=ui_color, hi_color=background_color, mode='button', toggle=True, hover=hover, destroy=destroy_flag, ds_color=border_color)) else: change_button_color(button=btn, text_color=text, bg_color=background_color, hi_color=push_col, mode='button', hover=hover, destroy=destroy_flag, ds_color=border_color) if w_max: btn.setMaximumWidth(w_max) if w_min: btn.setMinimumWidth(w_min) if h_max: btn.setMaximumHeight(h_max) if h_min: btn.setMinimumHeight(h_min) if icon_size: btn.setIconSize(QSize(*icon_size)) if policy: btn.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Expanding) if tip: btn.setToolTip(tip) if context: btn.setContextMenuPolicy(Qt.CustomContextMenu) btn.customContextMenuRequested.connect(context) return btn
class WindowStatusBar(statusbar.StatusWidget, object): def __init__(self, parent=None): super(WindowStatusBar, self).__init__(parent) self._info_url = None self._tool = None self.setFixedHeight(25) self._info_btn = QPushButton() self._info_btn.setIconSize(QSize(25, 25)) self._info_btn.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)) self._info_btn.setIcon(resources.icon('info1')) self._info_btn.setStyleSheet('QWidget {background-color: rgba(255, 255, 255, 0); border:0px;}') self._bug_btn = QPushButton() self._bug_btn.setIconSize(QSize(25, 25)) self._bug_btn.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self._bug_btn.setIcon(resources.icon('bug')) self._bug_btn.setStyleSheet('QWidget {background-color: rgba(255, 255, 255, 0); border:0px;}') self.main_layout.insertWidget(0, self._info_btn) self.main_layout.insertWidget(1, self._bug_btn) self._info_btn.clicked.connect(self._on_open_url) self._bug_btn.clicked.connect(self._on_send_bug) def set_info_url(self, url): """ Sets the URL used to open tool info documentation web :param url: str """ self._info_url = url def set_tool(self, tool): """ :param tool: :return: """ self._tool = tool def has_url(self): """ Returns whether the URL documentation web is set or not :return: bool """ if self._info_url: return True return False def has_tool(self): """ Returns whether window has a tool attached or not :return: bool """ if self._tool: return True return False def show_info(self): """ Shows the info button of the status bar """ self._info_btn.setVisible(True) def hide_info(self): """ Hides the info button of the status bar """ self._info_btn.setVisible(False) def show_bug(self): self._bug_btn.setVisible(True) def hide_bug(self): self._bug_btn.setVisible(False) def open_info_url(self): """ Opens tool documentation URL in user web browser """ if not self._project: return False if self._info_url: webbrowser.open_new_tab(self._info_url) def _on_send_bug(self): if not self._project: return False # tools.ToolsManager().run_tool(self._project, 'bugtracker', extra_args={'tool': self._tool}) def _on_open_url(self): """ Internal callback function that is called when the user presses the info icon button :return: """ self.open_info_url()
def __init__(self, parent=None): QMainWindow.__init__(self, parent) self.data = ParticleData() toolbar = self.addToolBar("Test") openButton = QPushButton("") openButton.setFlat(True) openButton.setIconSize( QSize(32, 32) ) openButton.setIcon(QIcon("/jobs2/soft/icons/dlight/open.png")) openButton.setToolTip( "Open File" ) toolbar.addWidget(openButton) openButton.clicked.connect(self.openSlot) QShortcut( QKeySequence(Qt.CTRL + Qt.Key_O), self, self.openSlot ) saveButton = QPushButton("") saveButton.setFlat(True) saveButton.setIconSize( QSize(32, 32) ) saveButton.setIcon(QIcon("/jobs2/soft/icons/dlight/file_save.png")) saveButton.setToolTip( "Save File" ) toolbar.addWidget(saveButton) saveButton.clicked.connect(self.saveSlot) QShortcut( QKeySequence(Qt.CTRL + Qt.Key_S), self, self.saveSlot ) saveDeltaButton = QPushButton("") saveDeltaButton.setFlat(True) saveDeltaButton.setIconSize( QSize(32, 32) ) saveDeltaButton.setIcon(QIcon("/jobs2/soft/icons/dlight/file_save_as.png")) saveDeltaButton.setToolTip( "Save File As Delta" ) toolbar.addWidget(saveDeltaButton) saveDeltaButton.clicked.connect(self.saveDeltaSlot) QShortcut( QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_S), self, self.saveDeltaSlot ) addParticleButton = QPushButton("Particle") addParticleButton.setFlat(True) addParticleButton.setIconSize( QSize(32, 32) ) addParticleButton.setIcon(QIcon("/jobs2/soft/icons/shared/plus.png")) addParticleButton.setToolTip( "Add Particle" ) toolbar.addWidget(addParticleButton) addParticleButton.clicked.connect(self.addParticleSlot) addAttributeButton = QPushButton("Attribute") addAttributeButton.setFlat(True) addAttributeButton.setIconSize( QSize(32, 32) ) addAttributeButton.setIcon(QIcon("/jobs2/soft/icons/shared/plus.png")) addAttributeButton.setToolTip( "Add Attribute" ) toolbar.addWidget(addAttributeButton) addAttributeButton.clicked.connect(self.addAttributeSlot) splitter = QSplitter(self) self.setCentralWidget(splitter) particleTable = ParticleTableWidget(self.data, self) splitter.addWidget(particleTable) right = QWidget(self) splitter.addWidget(right) vbox = QVBoxLayout(right) right.setLayout(vbox) fixedAttrWidget = FixedAttributesWidget(self.data, self) vbox.addWidget(fixedAttrWidget) indexedStrings = IndexedStringsWidget(self.data, self) vbox.addWidget(indexedStrings) vbox.addStretch() # TODD: SCROLLABLE AREAS FOR EVERYTHING self.data.dirtied.connect(self.dataDirtiedSlot) # Configure ctrl-w to close the window QShortcut( QKeySequence(Qt.CTRL + Qt.Key_W), self, self.close )
class MayaShelf(abstract_shelf.AbstractShelf, object): def __init__(self, name='MayaShelf', label_background=(0, 0, 0, 0), label_color=(0.9, 0.9, 0.9), category_icon=None, enable_labels=True): super(MayaShelf, self).__init__(name=name, label_background=label_background, label_color=label_color, category_icon=category_icon, enable_labels=enable_labels) @staticmethod def add_menu_item(parent, label, command='', icon=''): """ Adds a menu item with the given attributes :param parent: :param label: :param command: :param icon: :return: """ return maya.cmds.menuItem(parent=parent, label=label, command=command, image=icon or '') @staticmethod def add_sub_menu(parent, label, icon=None): """ Adds a sub menu item with the given label and icon to the given parent popup menu :param parent: :param label: :param icon: :return: """ return maya.cmds.menuItem(parent=parent, label=label, icon=icon or '', subMenu=True) def create(self, delete_if_exists=True): """ Creates a new shelf """ if delete_if_exists: if gui.shelf_exists(shelf_name=self._name): gui.delete_shelf(shelf_name=self._name) else: assert not gui.shelf_exists(self._name), 'Shelf with name {} already exists!'.format(self._name) self._name = gui.create_shelf(name=self._name) # ======================================================================================================== self._category_btn = QPushButton('') if self._category_icon: self._category_btn.setIcon(self._category_icon) self._category_btn.setIconSize(QSize(18, 18)) self._category_menu = QMenu(self._category_btn) self._category_btn.setStyleSheet( 'QPushButton::menu-indicator {image: url(myindicator.png);' 'subcontrol-position: right center;subcontrol-origin: padding;left: -2px;}') self._category_btn.setMenu(self._category_menu) self._category_lbl = QLabel('MAIN') self._category_lbl.setAlignment(Qt.AlignCenter) font = self._category_lbl.font() font.setPointSize(6) self._category_lbl.setFont(font) menu_ptr = maya.OpenMayaUI.MQtUtil.findControl(self._name) menu_widget = qtutils.wrapinstance(menu_ptr, QWidget) menu_widget.layout().addWidget(self._category_btn) menu_widget.layout().addWidget(self._category_lbl) self.add_separator() def set_as_active(self): """ Sets this shelf as active shelf in current DCC session """ main_shelf = maya.mel.eval("$_tempVar = $gShelfTopLevel") maya.cmds.tabLayout(main_shelf, edit=True, selectTab=self._name) def add_button(self, label, tooltip=None, icon='customIcon.png', command=None, double_command=None, command_type='python'): """ Adds a shelf button width the given parameters :param label: :param tooltip: :param icon: :param command: :param double_command: :param command_type: :return: """ maya.cmds.setParent(self._name) command = command or '' double_command = double_command or '' if not self._enable_labels: label = '' return maya.cmds.shelfButton(width=37, height=37, image=icon or '', label=label, command=command, doubleClickCommand=double_command, annotation=tooltip or '', imageOverlayLabel=label, overlayLabelBackColor=self._label_background, overlayLabelColor=self._label_color, sourceType=command_type) def add_separator(self): """ Adds a separator to shelf :param parent: :return: """ maya.cmds.separator( parent=self._name, manage=True, visible=True, horizontal=False, style='shelf', enableBackground=False, preventOverride=False) def build_category(self, shelf_file, category_name): self._category_lbl.setText(category_name.upper()) self.load_category(shelf_file, 'general', clear=True) if category_name != 'general': self.add_separator() self.load_category(shelf_file, category_name, clear=False) def build_categories(self, shelf_file, categories): """ Builds all categories given :param categories: list<str>, list of categories to build """ self._category_lbl.setText('ALL') self.load_category(shelf_file, 'general', clear=True) for cat in categories: if cat == 'general': continue self.add_separator() self.load_category(shelf_file, cat, clear=False) def load_category(self, shelf_file, category_name, clear=True): """ Loads into a shelf all the items of given category name, if exists :param category_name: str, name of the category """ if clear: self.clear_list() # self.add_separator() with open(shelf_file) as f: shelf_data = json.load(f, object_pairs_hook=OrderedDict) for item, item_data in shelf_data.items(): if item != category_name: continue for i in item_data: icon = i.get('icon') command = i.get('command') annotation = i.get('annotation') label = i.get('label') if annotation == 'separator': self.add_separator() else: self.add_button(label=label, command=command, icon=icon, tooltip=annotation) return def build(self, shelf_file): """ Builds shelf from JSON file :param shelf_file: str """ first_item = None all_categories = list() with open(shelf_file) as f: shelf_data = json.load(f, object_pairs_hook=OrderedDict) for i, item in enumerate(shelf_data.keys()): if i == 0: first_item = item category_action = self._category_menu.addAction(item.title()) category_action.triggered.connect(partial(self.build_category, shelf_file, item)) all_categories.append(item) category_action = self._category_menu.addAction('All') category_action.triggered.connect(partial(self.build_categories, shelf_file, all_categories)) if first_item: self.load_category(shelf_file, first_item, clear=False) def clear_list(self): """ Clears all the elements of the shelf """ if gui.shelf_exists(shelf_name=self._name): menu_items = maya.cmds.shelfLayout(self._name, query=True, childArray=True) for item in menu_items: try: maya.cmds.deleteUI(item) except Exception: pass
class MyToolBar(QWidget): """ ToolBar widget """ # signal trigger = Signal(int) def __init__(self, flags, parent=None): super(MyToolBar, self).__init__(parent) self.setWindowFlags(Qt.ToolTip) self.paddingX = 5 self.paddingY = 2 self.iconWidth = self.iconHeight = 28 self.setFixedHeight(self.iconHeight + 2 * self.paddingY) # self.setFixedWidth(300) self.rectButton = None self.ellipseButton = None self.arrowButton = None self.lineButton = None self.freePenButton = None self.textButton = None self.undoButton = None self.cancelButton = None self.okButton = None self.saveButton = None self.button_list = [] self.initWindow(flags) def initDrawButtons(self, flags): self.drawButtonGroup = QButtonGroup(self) # draw action buttons if flags & constant.RECT: self.rectButton = QPushButton(self) self.rectButton.setIcon(QIcon(":/resource/icon/rect.png")) self.rectButton.setFixedSize(self.iconWidth, self.iconHeight) self.rectButton.setCheckable(True) self.drawButtonGroup.addButton(self.rectButton) self.hlayout.addWidget(self.rectButton) self.button_list.append(self.rectButton) if flags & constant.ELLIPSE: self.ellipseButton = QPushButton(self) self.ellipseButton.setIcon(QIcon(":/resource/icon/ellipse.png")) self.ellipseButton.setFixedSize(self.iconWidth, self.iconHeight) self.ellipseButton.setCheckable(True) self.drawButtonGroup.addButton(self.ellipseButton) self.hlayout.addWidget(self.ellipseButton) self.button_list.append(self.ellipseButton) if flags & constant.ARROW: self.arrowButton = QPushButton(self) self.arrowButton.setIcon(QIcon(":/resource/icon/arrow.png")) self.arrowButton.setFixedSize(self.iconWidth, self.iconHeight) self.arrowButton.setCheckable(True) self.drawButtonGroup.addButton(self.arrowButton) self.hlayout.addWidget(self.arrowButton) self.button_list.append(self.arrowButton) if flags & constant.LINE: self.lineButton = QPushButton(self) self.lineButton.setIcon(QIcon(":/resource/icon/line.png")) self.lineButton.setFixedSize(self.iconWidth, self.iconHeight) self.lineButton.setCheckable(True) self.drawButtonGroup.addButton(self.lineButton) self.hlayout.addWidget(self.lineButton) self.button_list.append(self.lineButton) if flags & constant.FREEPEN: self.freePenButton = QPushButton(self) self.freePenButton.setIcon(QIcon(":/resource/icon/pen.png")) self.freePenButton.setFixedSize(self.iconWidth, self.iconHeight) self.freePenButton.setCheckable(True) self.drawButtonGroup.addButton(self.freePenButton) self.hlayout.addWidget(self.freePenButton) self.button_list.append(self.freePenButton) if flags & constant.TEXT: self.textButton = QPushButton(self) self.textButton.setIcon(QIcon(":/resource/icon/text.png")) self.textButton.setFixedSize(self.iconWidth, self.iconHeight) self.textButton.setCheckable(True) self.drawButtonGroup.addButton(self.textButton) self.hlayout.addWidget(self.textButton) self.button_list.append(self.textButton) self.drawButtonGroup.buttonClicked.connect(self.buttonToggled) def initOtherButtons(self, flags): # other action buttons if len(self.button_list) != 0: self.separator1 = QFrame(self) self.separator1.setFrameShape(QFrame.VLine) self.separator1.setFrameShadow(QFrame.Sunken) self.hlayout.addWidget(self.separator1) self.undoButton = QPushButton(self) self.undoButton.setIcon(QIcon(":/resource/icon/undo.png")) self.undoButton.setFixedSize(self.iconWidth, self.iconWidth) self.undoButton.clicked.connect(self.otherButtonsClicked) self.hlayout.addWidget(self.undoButton) if flags & constant.SAVE_TO_FILE: self.saveButton = QPushButton(self) self.saveButton.setIcon(QIcon(":/resource/icon/save.png")) self.saveButton.setFixedSize(self.iconWidth, self.iconHeight) self.saveButton.clicked.connect(self.otherButtonsClicked) self.hlayout.addWidget(self.saveButton) self.separator2 = QFrame(self) self.separator2.setFrameShape(QFrame.VLine) self.separator2.setFrameShadow(QFrame.Sunken) self.hlayout.addWidget(self.separator2) self.cancelButton = QPushButton(self) self.cancelButton.setIcon(QIcon(":/resource/icon/close.png")) self.cancelButton.setFixedSize(self.iconWidth, self.iconHeight) self.cancelButton.clicked.connect(self.otherButtonsClicked) if flags & constant.CLIPBOARD: self.okButton = QPushButton(self) self.okButton.setIcon(QIcon(":/resource/icon/check.png")) self.okButton.setFixedSize(self.iconWidth, self.iconHeight) self.okButton.clicked.connect(self.otherButtonsClicked) self.hlayout.addWidget(self.okButton) self.hlayout.addWidget(self.cancelButton) def initWindow(self, flags): self.hlayout = QHBoxLayout() self.hlayout.setSpacing(2) self.hlayout.setContentsMargins(10, 2, 10, 2) self.setLayout(self.hlayout) self.initDrawButtons(flags) self.initOtherButtons(flags) # slots def buttonToggled(self, button): """ :type button: QPushButton :param button: :return: """ if button == self.rectButton: self.trigger.emit(ACTION_RECT) elif button == self.ellipseButton: self.trigger.emit(ACTION_ELLIPSE) elif button == self.arrowButton: self.trigger.emit(ACTION_ARROW) elif button == self.lineButton: self.trigger.emit(ACTION_LINE) elif button == self.freePenButton: self.trigger.emit(ACTION_FREEPEN) elif button == self.textButton: self.trigger.emit(ACTION_TEXT) else: pass def otherButtonsClicked(self): if self.sender() == self.undoButton: self.trigger.emit(ACTION_UNDO) elif self.sender() == self.cancelButton: self.trigger.emit(ACTION_CANCEL) elif self.sender() == self.okButton: self.trigger.emit(ACTION_SURE) elif self.sender() == self.saveButton: self.trigger.emit(ACTION_SAVE)
class Project(base.BaseWidget): projectOpened = Signal(object) projectRemoved = Signal(str) projectImageChanged = Signal(str) def __init__(self, project_data, parent=None): self._project_data = project_data super(Project, self).__init__(parent) # ============================================================================================================ # PROPERTIES # ============================================================================================================ @property def name(self): return self._project_data.name @property def path(self): return self._project_data.path @property def full_path(self): return self._project_data.full_path @property def settings(self): return self._project_data.settings @property def project_data(self): return self._project_data # ============================================================================================================ # CLASS FUNCTIONS # ============================================================================================================ @classmethod def create_project_from_data(cls, project_data_path): """ Creates a new project using a project data JSON file :param project_data_path: str, path where project JSON data file is located :return: Project """ if project_data_path is None or not path.is_file(project_data_path): LOGGER.warning('Project Data Path {} is not valid!'.format(project_data_path)) return None project_data = settings.JSONSettings() project_options = settings.JSONSettings() project_dir = path.get_dirname(project_data_path) project_name = path.get_basename(project_data_path) project_data.set_directory(project_dir, project_name) project_options.set_directory(project_dir, 'options.json') project_name = project_data.get('name') project_path = path.get_dirname(path.get_dirname(project_data_path)) project_image = project_data.get('image') LOGGER.debug('New Project found [{}]: {}'.format(project_name, project_path)) project_data = core_project.ProjectData( name=project_name, project_path=project_path, settings=project_data, options=project_options) new_project = cls(project_data=project_data) if project_image: new_project.set_image(project_image) return new_project # ============================================================================================================ # OVERRIDES # ============================================================================================================ def ui(self): super(Project, self).ui() self.setMaximumWidth(qtutils.dpi_scale(160)) self.setMaximumHeight(qtutils.dpi_scale(200)) widget_layout = layouts.VerticalLayout(spacing=0, margins=(0, 0, 0, 0)) main_frame = QFrame() main_frame.setFrameStyle(QFrame.StyledPanel | QFrame.Raised) main_frame.setLineWidth(1) main_frame.setLayout(widget_layout) self.main_layout.addWidget(main_frame) self.project_btn = QPushButton('', self) self.project_btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.project_btn.setIconSize(QSize(120, 120)) project_lbl = label.BaseLabel(self.name, parent=self) project_lbl.setObjectName('projectLabel') project_lbl.setAlignment(Qt.AlignCenter) widget_layout.addWidget(self.project_btn) widget_layout.addWidget(project_lbl) def setup_signals(self): self.project_btn.clicked.connect(self._on_open_project) def contextMenuEvent(self, event): menu = QMenu(self) remove_icon = resources.icon(name='delete') remove_action = QAction(remove_icon, 'Remove', menu) remove_tooltip = 'Delete selected project' remove_action.setStatusTip(remove_tooltip) remove_action.setToolTip(remove_tooltip) remove_action.triggered.connect(self._on_remove_project) folder_icon = resources.icon(name='open_folder', extension='png') folder_action = QAction(folder_icon, 'Open in Browser', menu) open_project_in_explorer_tooltip = 'Open project folder in explorer' folder_action.setStatusTip(open_project_in_explorer_tooltip) folder_action.setToolTip(open_project_in_explorer_tooltip) folder_action.triggered.connect(self._on_open_in_browser) image_icon = resources.icon(name='picture', extension='png') set_image_action = QAction(image_icon, 'Set Project Image', menu) set_project_image_tooltip = 'Set the image used by the project' set_image_action.setToolTip(set_project_image_tooltip) set_image_action.setStatusTip(set_project_image_tooltip) set_image_action.triggered.connect(self._on_set_project_image) for action in [remove_action, None, folder_action, None, set_image_action]: if action is None: menu.addSeparator() else: menu.addAction(action) menu.exec_(self.mapToGlobal(event.pos())) # ============================================================================================================ # BASE # ============================================================================================================ def open(self): """ Opens project """ self._on_open_project() def has_option(self, name, group=None): """ Returns whether the current object has given option or not :param name: str, name of the option :param group: variant, str || None, group of the option (optional) :return: bool """ if not self._project_data: return False return self._project_data.has_option(name=name, group=group) def add_option(self, name, value, group=None, option_type=None): """ Adds a new option to the options file :param name: str, name of the option :param value: variant, value of the option :param group: variant, str || None, group of the option (optional) :param option_type: variant, str || None, option type (optional) """ if not self._project_data: return self._project_data.add_option(name, value, group=group, option_type=option_type) def get_option(self, name, group=None, default=None): """ Returns option by name and group :param name: str, name of the option we want to retrieve :param group: variant, str || None, group of the option (optional) :return: variant """ if not self._project_data: return return self._project_data.get_option(name, group=group, default=default) def reload_options(self): """ Reload settings """ if not self._project_data: return self._project_data.reload_options() def clear_options(self): """ Clears all the options """ if not self._project_data: return self._project_data.clear_options() def set_image(self, encoded_image): from tpDcc.libs.qt.core import image if not encoded_image: return encoded_image = encoded_image.encode('utf-8') project_icon = QIcon(QPixmap.fromImage(image.base64_to_image(encoded_image))) if project_icon.isNull(): project_icon = resources.icon('tpDcc') self.project_btn.setIcon(project_icon) def remove(self, force=False): if not path.is_dir(self.full_path): LOGGER.warning('Impossible to remove Project Path: {}'.format(self.full_path)) return False project_name = self.project_data.name project_path = self.project_data.path if not force: result = qtutils.get_permission( message='Are you sure you want to delete project: "{}"'.format(self.name), title='Deleting Project', cancel=False, parent=self) if not result: return valid_delete = folder.delete_folder(folder_name=project_name, directory=project_path) if valid_delete is None: return False return True def load_project_data(self): """ Return dictionary data contained in the project :return: dict """ if not self.settings: return return self.settings.data() def get_project_nodes(self): """ Returns path where nodes should be stored :return: str """ return [os.path.join(self.full_path, 'nodes'), os.path.join(self.full_path, 'components')] def get_options(self): """ Returns all options contained in the project :return: str """ return self._project_data.get_options() def get_project_image(self): """ Returns the image used by the project :return: QPixmap """ return self._project_data.get_project_image() # ============================================================================================================ # CALLBACKS # ============================================================================================================ def _on_open_project(self): """ Internal callback function that is called when a project is opened """ LOGGER.info('Loading project "{}" ...'.format(self.full_path)) self.projectOpened.emit(self) def _on_remove_project(self): """ Internal callback function that is called when a project is removed """ valid_remove = self.remove() if valid_remove: self.projectRemoved.emit(self.name) def _on_open_in_browser(self): """ Internal callback function that is called when a project is browsed """ fileio.open_browser(self.full_path) def _on_set_project_image(self): """ Internal callback function that is called when project image is set """ image_file = dcc.select_file_dialog( title='Select Project Image File', pattern="PNG Files (*.png)") if image_file is None or not path.is_file(image_file): LOGGER.warning('Selected Image "{}" is not valid!'.format(image_file)) return valid_change = self._project_data.set_project_image(image_file) if valid_change: project_image = self._project_data.settings.get('image') if project_image: self.set_image(project_image) self.projectImageChanged.emit(image_file)
def fill_tree(self, trigger_name=None, trigger_job=None): item_stack = [self.tree.invisibleRootItem()] self.tree.clear() jobs = self.session.seqcrow_job_manager.jobs for job in jobs: name = job.name parent = item_stack[0] item = QTreeWidgetItem(parent) item_stack.append(item) item.setData(self.NAME_COL, Qt.DisplayRole, job) item.setText(self.NAME_COL, name) if isinstance(job, LocalJob): if job.killed: item.setText(self.STATUS_COL, "killed") del_job_widget = QWidget() del_job_layout = QGridLayout(del_job_widget) del_job = QPushButton() del_job.clicked.connect( lambda *args, job=job: self.remove_job(job)) del_job.setIcon( QIcon(del_job_widget.style().standardIcon( QStyle.SP_DialogDiscardButton))) del_job.setFlat(True) del_job_layout.addWidget(del_job, 0, 0, 1, 1, Qt.AlignHCenter) del_job_layout.setColumnStretch(0, 1) del_job_layout.setContentsMargins(0, 0, 0, 0) self.tree.setItemWidget(item, self.DEL_COL, del_job_widget) elif job.isRunning(): if job in self.session.seqcrow_job_manager.unknown_status_jobs: unk_widget = QWidget() unk_layout = QGridLayout(unk_widget) unk = QPushButton() unk.setIcon( QIcon(unk_widget.style().standardIcon( QStyle.SP_MessageBoxQuestion))) unk.setFlat(True) unk.clicked.connect(lambda *args, job=job: self. show_ask_if_running(job)) unk_layout.addWidget(unk, 0, 0, 1, 1, Qt.AlignHCenter) unk_layout.setColumnStretch(0, 1) unk_layout.setContentsMargins(0, 0, 0, 0) self.tree.setItemWidget(item, self.STATUS_COL, unk_widget) else: item.setText(self.STATUS_COL, "running") kill_widget = QWidget() kill_layout = QGridLayout(kill_widget) kill = QPushButton() kill.setIcon( QIcon(kill_widget.style().standardIcon( QStyle.SP_DialogCancelButton))) kill.setFlat(True) kill.clicked.connect(lambda *args, job=job: job.kill()) kill.clicked.connect( lambda *args, session=self.session: session. seqcrow_job_manager.triggers.activate_trigger( JOB_QUEUED, "resume")) kill_layout.addWidget(kill, 0, 0, 1, 1, Qt.AlignLeft) kill_layout.setColumnStretch(0, 0) kill_layout.setContentsMargins(0, 0, 0, 0) self.tree.setItemWidget(item, self.KILL_COL, kill_widget) elif job.isFinished(): if not job.error: item.setText(self.STATUS_COL, "finished") else: error_widget = QWidget() error_layout = QGridLayout(error_widget) error = QPushButton() error.setIcon( QIcon(error_widget.style().standardIcon( QStyle.SP_MessageBoxWarning))) error.setFlat(True) error.setToolTip( "job did not finish without errors or output file cannot be found" ) error_layout.addWidget(error, 0, 0, 1, 1, Qt.AlignHCenter) error_layout.setColumnStretch(0, 1) error_layout.setContentsMargins(0, 0, 0, 0) self.tree.setItemWidget(item, self.STATUS_COL, error_widget) del_job_widget = QWidget() del_job_layout = QGridLayout(del_job_widget) del_job = QPushButton() del_job.clicked.connect( lambda *args, job=job: self.remove_job(job)) del_job.setIcon( QIcon(del_job_widget.style().standardIcon( QStyle.SP_DialogDiscardButton))) del_job.setFlat(True) del_job_layout.addWidget(del_job, 0, 0, 1, 1, Qt.AlignHCenter) del_job_layout.setColumnStretch(0, 1) del_job_layout.setContentsMargins(0, 0, 0, 0) self.tree.setItemWidget(item, self.DEL_COL, del_job_widget) else: item.setText(self.STATUS_COL, "queued") priority_widget = QWidget() priority_layout = QGridLayout(priority_widget) inc_priority = QPushButton() inc_priority.setIcon( QIcon(priority_widget.style().standardIcon( QStyle.SP_ArrowUp))) inc_priority.setFlat(True) inc_priority.clicked.connect( lambda *args, job=job: self.session.seqcrow_job_manager .increase_priotity(job)) priority_layout.addWidget(inc_priority, 0, 0, 1, 1, Qt.AlignRight) dec_priority = QPushButton() dec_priority.setIcon( QIcon(priority_widget.style().standardIcon( QStyle.SP_ArrowDown))) dec_priority.setFlat(True) dec_priority.clicked.connect( lambda *args, job=job: self.session.seqcrow_job_manager .decrease_priotity(job)) priority_layout.addWidget(dec_priority, 0, 1, 1, 1, Qt.AlignLeft) priority_layout.setColumnStretch(0, 1) priority_layout.setColumnStretch(1, 1) priority_layout.setContentsMargins(0, 0, 0, 0) self.tree.setItemWidget(item, self.CHANGE_PRIORITY, priority_widget) kill_widget = QWidget() kill_layout = QGridLayout(kill_widget) kill = QPushButton() kill.setIcon( QIcon(kill_widget.style().standardIcon( QStyle.SP_DialogCancelButton))) kill.setFlat(True) kill.clicked.connect(lambda *args, job=job: job.kill()) kill.clicked.connect( lambda *args, session=self.session: session. seqcrow_job_manager.triggers.activate_trigger( JOB_QUEUED, "resume")) kill_layout.addWidget(kill, 0, 0, 1, 1, Qt.AlignLeft) kill_layout.setColumnStretch(0, 0) kill_layout.setContentsMargins(0, 0, 0, 0) self.tree.setItemWidget(item, self.KILL_COL, kill_widget) item.setText(self.SERVER_COL, "local") if job.scratch_dir and os.path.exists(job.scratch_dir): browse_widget = QWidget() browse_layout = QGridLayout(browse_widget) browse = QPushButton() browse.clicked.connect( lambda *args, job=job: self.browse_local(job)) browse.setIcon( QIcon(browse_widget.style().standardIcon( QStyle.SP_DirOpenIcon))) browse.setFlat(True) browse_layout.addWidget(browse, 0, 0, 1, 1, Qt.AlignLeft) browse_layout.setColumnStretch(0, 1) browse_layout.setContentsMargins(0, 0, 0, 0) self.tree.setItemWidget(item, self.BROWSE_COL, browse_widget) self.tree.expandItem(item) self.tree.resizeColumnToContents(self.STATUS_COL) self.tree.resizeColumnToContents(self.SERVER_COL) self.tree.resizeColumnToContents(self.CHANGE_PRIORITY) self.tree.resizeColumnToContents(self.KILL_COL) self.tree.resizeColumnToContents(self.DEL_COL) self.tree.resizeColumnToContents(self.BROWSE_COL)
class UIVariable(QWidget, IPropertiesViewSupport): def __init__(self, rawVariable, variablesWidget, parent=None): super(UIVariable, self).__init__(parent) self._rawVariable = rawVariable self.variablesWidget = variablesWidget # ui self.horizontalLayout = QHBoxLayout(self) self.horizontalLayout.setSpacing(1) self.horizontalLayout.setContentsMargins(1, 1, 1, 1) self.horizontalLayout.setObjectName("horizontalLayout") self.widget = TypeWidget( findPinClassByType(self._rawVariable.dataType).color(), self) self.widget.setObjectName("widget") self.horizontalLayout.addWidget(self.widget) self.labelName = QLabel(self) self.labelName.setObjectName("labelName") self.horizontalLayout.addWidget(self.labelName) spacerItem = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem) # find refs self.pbFindRefs = QPushButton("") self.pbFindRefs.setIcon( QtGui.QIcon(RESOURCES_DIR + "/searching-magnifying-glass.png")) self.pbFindRefs.setObjectName("pbFindRefs") self.horizontalLayout.addWidget(self.pbFindRefs) self.pbFindRefs.clicked.connect(self.onFindRefsClicked) # kill variable self.pbKill = QPushButton("") self.pbKill.setIcon(QtGui.QIcon(RESOURCES_DIR + "/delete_icon.png")) self.pbKill.setObjectName("pbKill") self.horizontalLayout.addWidget(self.pbKill) self.pbKill.clicked.connect(self.onKillClicked) QtCore.QMetaObject.connectSlotsByName(self) self.setName(self._rawVariable.name) def createPropertiesWidget(self, propertiesWidget): baseCategory = CollapsibleFormWidget(headName="Base") # name le_name = QLineEdit(self._rawVariable.name) le_name.returnPressed.connect(lambda: self.setName(le_name.text())) baseCategory.addWidget("Name", le_name) # data type cbTypes = VarTypeComboBox(self) baseCategory.addWidget("Type", cbTypes) propertiesWidget.addWidget(baseCategory) valueCategory = CollapsibleFormWidget(headName="Value") # current value def valSetter(x): self._rawVariable.value = x w = createInputWidget( self._rawVariable.dataType, valSetter, getPinDefaultValueByType(self._rawVariable.dataType)) if w: w.setWidgetValue(self._rawVariable.value) w.setObjectName(self._rawVariable.name) valueCategory.addWidget(self._rawVariable.name, w) # access level cb = QComboBox() cb.addItem('public', 0) cb.addItem('private', 1) cb.addItem('protected', 2) def accessLevelChanged(x): self._rawVariable.accessLevel = AccessLevel[x] cb.currentTextChanged.connect(accessLevelChanged) cb.setCurrentIndex(self._rawVariable.accessLevel) valueCategory.addWidget('Access level', cb) propertiesWidget.addWidget(valueCategory) def onFindRefsClicked(self): refs = self._rawVariable.findRefs() print(refs) def onKillClicked(self): # check refs and ask user what to do refs = self._rawVariable.findRefs() if len(refs) > 0: item, accepted = QInputDialog.getItem( None, 'Decide!', 'What to do with getters and setters in canvas?', ['kill', 'leave'], editable=False) if accepted: if item == 'kill': self.variablesWidget.killVar(self) elif item == 'leave': # mark node as invalid # TODO: For future. Like in ue4, if variable is removed, it can be recreated from node (e.g. promote to variable) print('leave') else: self.variablesWidget.killVar(self) @property def dataType(self): return self._rawVariable.dataType @dataType.setter def dataType(self, value): self._rawVariable.dataType = value @property def packageName(self): return self._rawVariable.packageName @property def accessLevel(self): return self._rawVariable.accessLevel @accessLevel.setter def accessLevel(self, value): self._rawVariable.accessLevel = value @property def uid(self): return self._rawVariable.uid @uid.setter def uid(self, value): self._rawVariable.uid = value if self._rawVariable.uid in self.graph.vars: self.graph.vars.pop(self._rawVariable.uid) self.graph.vars[self._rawVariable.uid] = self._rawVariable @staticmethod def jsonTemplate(): template = { 'name': None, 'uuid': None, 'value': None, 'type': None, 'package': None, 'accessLevel': None } return template def serialize(self): pinClass = findPinClassByType(self._rawVariable.dataType) template = UIVariable.jsonTemplate() template['name'] = self._rawVariable.name template['uuid'] = str(self._rawVariable.uid) if self._rawVariable.dataType == "AnyPin": # don't save any variables # value will be calculated for this type of variables template['value'] = None else: template['value'] = json.dumps(self._rawVariable.value, cls=pinClass.jsonEncoderClass()) template['type'] = self._rawVariable.dataType template['package'] = self._rawVariable.packageName template['accessLevel'] = self._rawVariable.accessLevel.value return template @staticmethod def deserialize(data, graph): pinClass = findPinClassByType(data['dataType']) varUid = uuid.UUID(data['uuid']) # TODO: this is probably bad. Too long call chain var = graph.parent.variablesWidget.createVariable( dataType=data['dataType'], accessLevel=AccessLevel(data['accessLevel']), uid=varUid) var.setName(data['name']) var.setDataType(data['dataType']) if data['dataType'] == 'AnyPin': var.value = getPinDefaultValueByType('AnyPin') else: var.value = json.loads(data['value'], cls=pinClass.jsonDecoderClass()) return var @property def value(self): return self._rawVariable.value @value.setter def value(self, data): self._rawVariable.value = data # Changes variable data type and updates [TypeWidget](@ref PyFlow.Core.Variable.TypeWidget) color # @bug in the end of this method we clear undo stack, but we should not. We do this because undo redo goes crazy def setDataType(self, dataType, _bJustSpawned=False): self._rawVariable.dataType = dataType self.widget.color = findPinClassByType(self.dataType).color() self.widget.update() if _bJustSpawned: return self.variablesWidget.onUpdatePropertyView(self) def mousePressEvent(self, event): super(UIVariable, self).mousePressEvent(event) self.variablesWidget.onUpdatePropertyView(self) def setName(self, name): self._rawVariable.name = name self.labelName.setText(self._rawVariable.name)
class PenSetWidget(QWidget): penSizeTrigger = Signal(int) penColorTrigger = Signal(str) fontChangeTrigger = Signal(QFont) def __init__(self, parent=None): super(PenSetWidget, self).__init__(parent) self.paddingX = 5 self.paddingY = 2 self.iconWidth = self.iconHeight = 24 self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed) self.setWindowFlags(Qt.ToolTip) self.initWindows() self.prevSizeButton = self.penSize1 self.penSize1.setChecked(True) self.presentColor.setStyleSheet( 'QPushButton { background-color: %s; }' % PENCOLOR) def generateButtons(self, parent=None): """ Generate buttons due to colorDic """ self.colorButtons = [] for color in self.colorList: button = QPushButton(parent) button.setObjectName(color[0]) button.setStyleSheet('QPushButton { background-color: %s; }' % color[1]) button.setFixedSize(self.iconWidth / 2, self.iconHeight / 2) button.setCheckable(True) self.colorButtons.append(button) def initWindows(self): self.mainLayout = QHBoxLayout() self.setLayout(self.mainLayout) self.mainLayout.setSpacing(0) self.mainLayout.setContentsMargins(5, 2, 5, 2) self.initPenSizeButtons() self.initFontWidget() self.initPenColorButtons() self.separator = QFrame(self) self.separator.setFrameShape(QFrame.VLine) self.separator.setFrameShadow(QFrame.Sunken) self.mainLayout.addWidget(self.penSize) self.mainLayout.addWidget(self.changeFontButton) self.mainLayout.addWidget(self.separator) self.mainLayout.addWidget(self.colorSet) def initPenSizeButtons(self): self.penSize = QWidget(self) self.penSizeLayout = QHBoxLayout() self.penSize.setLayout(self.penSizeLayout) # adjust pen size self.penSize1 = QPushButton(self.penSize) self.penSize1.setIcon(QIcon(":/resource/icon/pensize1.png")) self.penSize1.setObjectName('1') self.penSize1.setFixedSize(self.iconWidth, self.iconHeight) self.penSize1.setCheckable(True) self.penSize2 = QPushButton(self.penSize) self.penSize2.setIcon(QIcon(":/resource/icon/pensize2.png")) self.penSize2.setObjectName('2') self.penSize2.setFixedSize(self.iconWidth, self.iconHeight) self.penSize2.setCheckable(True) self.penSize3 = QPushButton(self.penSize) self.penSize3.setIcon(QIcon(":/resource/icon/pensize3.png")) self.penSize3.setObjectName('3') self.penSize3.setFixedSize(self.iconWidth, self.iconHeight) self.penSize3.setCheckable(True) self.sizeButtonGroup = QButtonGroup(self.penSize) self.sizeButtonGroup.addButton(self.penSize1) self.sizeButtonGroup.addButton(self.penSize2) self.sizeButtonGroup.addButton(self.penSize3) self.sizeButtonGroup.buttonClicked.connect(self.sizeButtonToggled) self.penSizeLayout.addWidget(self.penSize1) self.penSizeLayout.addWidget(self.penSize2) self.penSizeLayout.addWidget(self.penSize3) self.penSizeLayout.setSpacing(5) self.penSizeLayout.setContentsMargins(0, 0, 0, 0) def initPenColorButtons(self): self.colorSet = QWidget(self) self.colorLayout = QHBoxLayout() self.colorLayout.setSpacing(5) self.colorLayout.setContentsMargins(5, 0, 5, 0) self.colorSet.setLayout(self.colorLayout) self.presentColor = QPushButton(self.colorSet) self.presentColor.setFixedSize(self.iconWidth, self.iconHeight) self.presentColor.setEnabled(False) # adjust pen color self.colorPick = QWidget(self.colorSet) self.colorGrid = QGridLayout() self.colorGrid.setSpacing(0) self.colorGrid.setContentsMargins(5, 0, 5, 0) self.colorPick.setLayout(self.colorGrid) self.colorList = [('white', '#ffffff'), ('red', '#ff0000'), ('green', '#00ff00'), ('blue', '#0000ff'), ('cyan', '#00ffff'), ('magenta', '#ff00ff'), ('yellow', '#ffff00'), ('gray', '#a0a0a4'), ('black', '#000000'), ('darkRed', '#800000'), ('darkGreen', '#008000'), ('darkBlue', '#000080'), ('darkCyan', '#008080'), ('darkMagenta', '#800080'), ('darkYellow', '#808000'), ('darkGray', '#808080')] self.generateButtons() self.colorButtonGroup = QButtonGroup(self) for button in self.colorButtons: self.colorButtonGroup.addButton(button) self.colorButtonGroup.buttonClicked.connect(self.colorButtonToggled) # set the layout tmp = 0 for x in range(0, 2): for y in range(0, int(len(self.colorList) / 2)): self.colorGrid.addWidget(self.colorButtons[tmp], x, y) tmp += 1 self.colorGrid.setSpacing(0) self.colorGrid.setContentsMargins(0, 0, 0, 0) self.colorLayout.addWidget(self.presentColor) self.colorLayout.addWidget(self.colorPick) def initFontWidget(self): self.fontDialog = QFontDialog() self.changeFontButton = QPushButton(self) self.fontDialog.setCurrentFont(QFont('Sans serif')) self.changeFontButton.setText('{0} {1}'.format( self.fontDialog.currentFont().family(), self.fontDialog.currentFont().pointSize())) self.changeFontButton.clicked.connect(self.fontButtonClicked) def showFontWidget(self): self.changeFontButton.show() self.penSize1.hide() self.penSize2.hide() self.penSize3.hide() def showPenWidget(self): self.changeFontButton.hide() self.penSize1.show() self.penSize2.show() self.penSize3.show() # slots def colorButtonToggled(self, button): self.presentColor.setStyleSheet( 'QPushButton { background-color: %s; }' % button.objectName()) self.penColorTrigger.emit(button.objectName()) def sizeButtonToggled(self, button): self.penSizeTrigger.emit(int(button.objectName()) * 2) def fontButtonClicked(self): ok = True font = QFontDialog.getFont(self) if font[1]: self.changeFontButton.setText('{0} {1}'.format( font[0].family(), font[0].pointSize())) self.fontChangeTrigger.emit(font[0])
class ExpandablePanel(base.BaseWidget, object): def __init__(self, header_text, min_height=30, max_height=1000, show_header_text=True, is_opened=False, parent=None): self._header_text = header_text self._show_header_text = show_header_text self._min_height = min_height self._max_height = max_height if is_opened: self._panel_state = PanelState.OPEN else: self._panel_state = PanelState.CLOSED self._collapse_icon = QIcon() self._icon = QPushButton() self._icon.setMaximumSize(20, 20) self._icon.setIcon(self._collapse_icon) super(ExpandablePanel, self).__init__(parent=parent) self.setObjectName('ExpandablePanel') self.update_size() self.update_icon() def ui(self): super(ExpandablePanel, self).ui() widget_palette = QPalette() widget_palette.setColor(QPalette.Background, QColor.fromRgb(60, 60, 60)) self.setAutoFillBackground(True) self.setPalette(widget_palette) frame = QFrame() frame.setFrameShape(QFrame.StyledPanel) frame.setFrameShadow(QFrame.Sunken) self.main_layout.addWidget(frame) main_layout = layouts.VerticalLayout(spacing=0, margins=(2, 2, 2, 2), parent=frame) main_layout.setAlignment(Qt.AlignTop) self._header_area = QWidget() self._header_area.setMinimumHeight(20) self._widget_area = QWidget() self._widget_area.setAutoFillBackground(True) self._widget_area.setPalette(widget_palette) self._header_text_label = dividers.Divider(self._header_text) self._widget_layout = layouts.VerticalLayout(spacing=5) self._widget_layout.setMargin(5) self._widget_area.setLayout(self._widget_layout) header_layout = layouts.HorizontalLayout(margins=(0, 0, 0, 0)) header_layout.addWidget(self._icon) header_layout.addWidget(self._header_text_label) self._header_area.setLayout(header_layout) main_layout.addWidget(self._header_area) main_layout.addWidget(self._widget_area) self._icon.clicked.connect(self.change_state) def update_icon(self): if self._panel_state == PanelState.OPEN: self._icon.setStyleSheet( 'QLabel {image: url(:/icons/open_hover_collapsible_panel) no-repeat;} ' 'QLabel:hover {image:url(:/icons/open_hover_collapsible_panel) no-repeat;}') self._icon.setToolTip('Close') self._widget_area.show() else: self._icon.setStyleSheet( 'QLabel {image: url(:/icons/closed_collapsible_panel) no-repeat;} ' 'QLabel:hover {image:url(:/icons/closed_hover_collapsible_panel) no-repeat;}') self._icon.setToolTip('Open') self._widget_area.hide() def update_size(self): if self._panel_state == PanelState.OPEN: self.setMaximumHeight(self._max_height) self.setMinimumHeight(self._min_height) else: self.setMaximumHeight(self._min_height) self.setMinimumHeight(self._min_height) def change_state(self): if not self._show_header_text: self._header_text_label.setVisible(False) if self._panel_state == PanelState.OPEN: self._panel_state = PanelState.CLOSED # self._header_text_label.setText('Closed') self._widget_area.hide() else: self._panel_state = PanelState.OPEN # self._header_text_label.setText('Open') self._widget_area.show() self.update_icon() self.update_size() def add_widget(self, widget): self._widget_layout.addWidget(widget) def add_layout(self, layout): self._widget_layout.addLayout(layout)
class SaveWidget(BaseSaveWidget, object): def __init__(self, item, settings, temp_path=None, parent=None): self._script_job = None self._sequence_path = None self._icon_path = '' super(SaveWidget, self).__init__(item=item, settings=settings, temp_path=temp_path, parent=parent) self.create_sequence_widget() self.update_thumbnail_size() try: self._on_selection_changed() # self.set_script_job_enabled(True) except NameError as e: LOGGER.error('{} | {}'.format(e, traceback.format_exc())) def ui(self): super(SaveWidget, self).ui() model_panel_layout = layouts.HorizontalLayout() model_panel_layout.setContentsMargins(0, 0, 0, 0) model_panel_layout.setSpacing(0) thumbnail_layout = layouts.VerticalLayout() thumbnail_layout.setContentsMargins(0, 0, 0, 0) thumbnail_layout.setSpacing(0) self._thumbnail_frame = QFrame() self._thumbnail_frame.setMinimumSize(QSize(50, 50)) self._thumbnail_frame.setMaximumSize(QSize(150, 150)) self._thumbnail_frame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self._thumbnail_frame.setFrameShape(QFrame.NoFrame) self._thumbnail_frame.setFrameShadow(QFrame.Plain) self._thumbnail_frame.setLineWidth(0) self._thumbnail_frame.setLayout(thumbnail_layout) model_panel_layout.addWidget(self._thumbnail_frame) self._thumbnail_btn = QPushButton() self._thumbnail_btn.setMinimumSize(QSize(0, 0)) self._thumbnail_btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self._thumbnail_btn.setMaximumSize(QSize(150, 150)) self._thumbnail_btn.setToolTip('Take snapshot') self._thumbnail_btn.setStyleSheet( 'color: rgb(40, 40, 40);border: 0px solid rgb(0, 0, 0, 150);background-color: rgb(254, 255, 230, 200);' ) self._thumbnail_btn.setIcon(resources.icon('thumbnail')) self._thumbnail_btn.setToolTip(""" Click to capture a thumbnail from the current viewport.\n CTRL + Click to show the capture window for better framing """) thumbnail_layout.addWidget(self._thumbnail_btn) self._extra_layout.addLayout(model_panel_layout) def setup_signals(self): super(SaveWidget, self).setup_signals() self._thumbnail_btn.clicked.connect(self._on_thumbnail_capture) def resizeEvent(self, event): """ Overrides base QWidget resizeEvent function :param event: QResizeEvent """ self.update_thumbnail_size() def icon_path(self): """ Returns the icon path to be used for the thumbnail :return: str """ return self._icon_path def set_icon(self, icon): """ Sets the icon for the create widget thumbnail :param icon: QIcon """ self._thumbnail_btn.setIcon(icon) self._thumbnail_btn.setIconSize(QSize(200, 200)) self._thumbnail_btn.setText('') def sequence_path(self): """ Returns the playblast path :return: str """ return self._sequence_path def set_sequence_path(self, path): """ Sets the disk location for the image sequence to be saved :param path: str """ self._sequence_path = path self._thumbnail_btn.set_dirname(os.path.dirname(path)) def create_sequence_widget(self): """ Creates a sequence widget to replace the static thumbnail widget """ sequence_widget = widgets.LibraryImageSequenceWidget(self) sequence_widget.setObjectName('thumbnailButton') sequence_widget.setStyleSheet(self._thumbnail_btn.styleSheet()) sequence_widget.setToolTip(self._thumbnail_btn.toolTip()) camera_icon = resources.get('icons', 'camera.svg') expand_icon = resources.get('icons', 'expand.svg') folder_icon = resources.get('icons', 'folder.svg') sequence_widget.addAction(camera_icon, 'Capture new image', 'Capture new image', self._on_thumbnail_capture) sequence_widget.addAction(expand_icon, 'Show Capture window', 'Show Capture window', self._on_show_capture_window) sequence_widget.addAction(folder_icon, 'Load image from disk', 'Load image from disk', self._on_show_browse_image_dialog) sequence_widget.setIcon(resources.icon('thumbnail2')) self._thumbnail_frame.layout().insertWidget(0, sequence_widget) self._thumbnail_btn.hide() self._thumbnail_btn = sequence_widget self._thumbnail_btn.clicked.connect(self._on_thumbnail_capture) def set_sequence(self, source): """ Sets the sequenced path for the thumbnail widget :param source: str """ self.set_thumbnail(source, sequence=True) def set_thumbnail(self, source, sequence=False): """ Sets the thumbnail :param source: str :param sequence: bool """ source = os.path.normpath(source) # TODO: Find a way to remove temp folder afteer saving the file # filename, extension = os.path.splitext(source) # with path_utils.temp_dir() as dir_path: # dir_path = path_utils.temp_dir() # target = os.path.join(dir_path, 'thumbnail{}'.format(extension)) # shutil.copyfile(source, target) # tpQtLib.logger.debug('Source Thumbnail: {}'.format(source)) # tpQtLib.logger.debug('Target Thumbnail: {}'.format(target)) # self._icon_path = target # self._thumbnail_btn.set_path(target) self._icon_path = source self._thumbnail_btn.set_path(source) if sequence: self.set_sequence_path(source) def update_thumbnail_size(self): """ Updates the thumbnail button to teh size of the widget """ width = self.width() - 10 if width > 250: width = 250 size = QSize(width, width) self._thumbnail_btn.setIconSize(size) self._thumbnail_btn.setMaximumSize(size) self._thumbnail_frame.setMaximumSize(size) def show_by_frame_dialog(self): """ Show the by frame dialog """ help_text = """ To help speed up the playblast you can set the "by frame" to another greather than 1. For example if the "by frame" is set to 2 it will playblast every second frame """ options = self._options_widget.values() by_frame = options.get('byFrame', 1) start_frame, end_frame = options.get('frameRange', [None, None]) duration = 1 if start_frame is not None and end_frame is not None: duration = end_frame - start_frame if duration > 100 and by_frame == 1: buttons = QDialogButtonBox.Ok | QDialogButtonBox.Cancel result = messagebox.MessageBox.question( self.library_window(), title='Tip', text=help_text, buttons=buttons, enable_dont_show_checkbox=True) if result != QDialogButtonBox.Ok: raise Exception('Cancelled by user') def show_thumbnail_capture_dialog(self): """ Asks the user if they would like to capture a thumbnail :return: int """ buttons = QDialogButtonBox.Yes | QDialogButtonBox.Ignore | QDialogButtonBox.Cancel parent = self.item().library_window() btn = messagebox.MessageBox.question( None, 'Create a thumbnail', 'Would you like to capture a thumbnail?', buttons=buttons) if btn == QDialogButtonBox.Yes: self.thumbnail_capture() return btn def thumbnail_capture(self, show=False): """ Captures a playblast and saves it to the temporal thumbnail path :param show: bool """ options = self._options_widget.values() start_frame, end_frame = options.get('frameRange', [None, None]) step = options.get('byFrame', 1) if not qtutils.is_control_modifier(): self.show_by_frame_dialog() if not self._temp_path or not os.path.isdir(self._temp_path): self._temp_path = tempfile.mkdtemp() self._temp_path = os.path.join(self._temp_path, 'thumbnail.jpg') try: snapshot.SnapshotWindow(path=self._temp_path, on_save=self._on_thumbnail_captured) # thumbnail.ThumbnailCaptureDialog.thumbnail_capture( # path=self._temp_path, # show=show, # start_frame=start_frame, # end_frame=end_frame, # step=step, # clear_cache=False, # captured=self._on_thumbnail_captured # ) except Exception as e: messagebox.MessageBox.critical(self.library_window(), 'Error while capturing thumbnail', str(e)) LOGGER.error(traceback.format_exc()) def save(self, path, icon_path, objects=None): """ Saves the item with the given objects to the given disk location path :param path: list(str) :param icon_path: str :param objects: str """ item = self.item() options = self._options_widget.values() sequence_path = self.sequence_path() if sequence_path: sequence_path = os.path.dirname(sequence_path) item.save(path=path, objects=objects, icon_path=icon_path, sequence_path=sequence_path, **options) self.close() def _on_selection_changed(self): """ Internal callback functino that is called when DCC selection changes """ if self._options_widget: self._options_widget.validate() def _on_thumbnail_capture(self): self.thumbnail_capture(show=False) def _on_thumbnail_captured(self, captured_path): """ Internal callback function that is called when thumbnail is captured :param captured_path: str """ self.set_sequence(captured_path) def _on_show_capture_window(self): """ Internal callback function that shows the capture window for framing """ self.thumbnail_capture(show=True) def _on_show_browse_image_dialog(self): """ Internal callback function that shows a file dialog for choosing an image from disk """ file_dialog = QFileDialog(self, caption='Open Image', filter='Image Files (*.png *.jpg)') file_dialog.fileSelected.connect(self.set_thumbnail) file_dialog.exec_()