class StatusComboBox(QtGui.QComboBox): currentStatusChanged = QtCore.Signal(str) def __init__(self, status_list, parent=None): super(StatusComboBox, self).__init__(parent) for status in status_list: self.addItem(status.getName()) css_combobox = """ QComboBox { padding: 2px 18px 2px 3px; border-radius: 4px; background: #AAA; color: #333; } QComboBox::on { background: #DDD; color: #333; } QComboBox::drop-down { subcontrol-origin: padding; subcontrol-position: top right; width: 15px; border: 0px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; } QComboBox::down-arrow { image: url(':ftrack/image/integration/branch-open') } QAbstractItemView { background: #888; border: 0px; } """ # self.setStyleSheet(css_combobox) self.activated.connect(self._changed_text) def set_status(self, status_name): index = self.findText(status_name) if index != -1: self.setCurrentIndex(index) def _changed_text(self): self.currentStatusChanged.emit(self.currentText())
class StatusWidgetDisplay(QtGui.QLabel): mouseDblClick = QtCore.Signal(str) def __init__(self, parent=None): super(StatusWidgetDisplay, self).__init__(parent) self.initiate() self.status = None def set_status(self, status): self.setText(status.getName()) self.set_status_css(status.get("color")) self.status = status def set_status_css(self, color=None): background_color = "#000" if color is None else color text_color = "#E5E5E5" css_status = """ padding: 5px; color: %s; background: %s; """ self.setStyleSheet(css_status % (text_color, background_color)) def initiate(self): self.setText("None") self.set_status_css() self.status = None def mouseDoubleClickEvent(self, event): if self.status != None: self.mouseDblClick.emit(self.text())
class Controller(QtCore.QObject): completed = QtCore.Signal() error = QtCore.Signal(str) def __init__(self, func, args=None, kwargs=None): ''' initiate a new instance ''' super(Controller, self).__init__() args = args or () kwargs = kwargs or {} self.worker = Worker(func, args, kwargs) self.worker.signal.finished.connect(self._emit_signal) def _emit_signal(self, success, error_message): if success: self.completed.emit() else: self.error.emit(error_message) def start(self): QtCore.QThreadPool.globalInstance().start(self.worker)
class SceneVersionWidget(QtGui.QWidget): notify = QtCore.Signal((str, str)) def __init__(self, parent=None): super(SceneVersionWidget, self).__init__(parent) self.setupUI() self._scene_versions_dict = dict() def setupUI(self): main_layout = QtGui.QVBoxLayout(self) main_layout.setContentsMargins(0, 0, 0, 0) main_layout.setSpacing(0) self._empty_asset_version = NoSceneVersionWidget(self) self._stackLayout = QtGui.QStackedLayout() self._stackLayout.addWidget(self._empty_asset_version) main_layout.addLayout(self._stackLayout) # self.setStyleSheet() def set_empty(self): self._stackLayout.setCurrentWidget(self._empty_asset_version) def set_scene_version(self, scene_version): if scene_version.getId() not in self._scene_versions_dict.keys(): widget = SingleSceneVersionWidget(scene_version, self) widget.notify.connect(self.notify.emit) self._scene_versions_dict[scene_version.getId()] = widget self._stackLayout.addWidget(widget) self._stackLayout.setCurrentWidget( self._scene_versions_dict[scene_version.getId()]) @property def current_scene_version(self): widget = self._stackLayout.currentWidget() return widget.scene_version def is_being_loaded(self): return self._stackLayout.currentWidget() == self._loading_asset_version def is_error(self): if self._stackLayout.currentWidget() == self._empty_asset_version: return True widget = self._stackLayout.currentWidget() return widget.error def is_locked(self): widget = self._stackLayout.currentWidget() return widget.locked
class NoSceneVersionWidget(QtGui.QWidget): notify = QtCore.Signal((str, str)) def __init__(self, parent=None): super(NoSceneVersionWidget, self).__init__(parent) self.setMinimumWidth(700) css_loading = """ background:#222; border-radius: 4px; padding:10px; border: 0px; """ css_image = """ background: url(':ftrack/image/integration/no-asset') no-repeat center center; """ main_layout = QtGui.QHBoxLayout(self) frame = QtGui.QFrame(self) frame.setMaximumSize(QtCore.QSize(350, 400)) frame.setStyleSheet(css_loading) frame.setFrameShape(QtGui.QFrame.StyledPanel) frame.setFrameShadow(QtGui.QFrame.Raised) frame_layout = QtGui.QVBoxLayout(frame) movie_screen = QtGui.QFrame(frame) movie_screen.setMinimumSize(QtCore.QSize(300, 300)) movie_screen.setStyleSheet(css_image) warning = QtGui.QLabel(frame) warning.setText("No scene asset found for the selected task") warning.setWordWrap(True) warning.setAlignment(QtCore.Qt.AlignCenter) frame_layout.addWidget(movie_screen) frame_layout.addWidget(warning) main_layout.addWidget(frame)
class SceneAssetsWidget(QtWidgets.QWidget): worker_started = QtCore.Signal() worker_stopped = QtCore.Signal() def __init__(self, parent=None): super(SceneAssetsWidget, self).__init__(parent) # self._scenes_connectors = SceneIO.connectors() self._connectors_per_type = dict() # for scene_connector in self._scenes_connectors: self._connectors_per_type['comp'] = NukeSceneAsset() self._task = None self.setupUI() def setupUI(self): css_settings_global = """ QFrame { border: none; color: #FFF; } QCheckBox { color: #DDD; padding: 0px; background: none; } /*QComboBox { color: #DDD; padding: 2px; background: #333; }*/ QComboBox::drop-down { border-radius: 0px; } QToolButton { color: #DDD; padding: 0px; background: #333; } """ self.setStyleSheet(css_settings_global) main_layout = QtWidgets.QVBoxLayout(self) main_layout.setContentsMargins(0, 0, 0, 0) main_layout.setSpacing(5) settings_frame = QtWidgets.QFrame(self) layout_settings = QtWidgets.QHBoxLayout(settings_frame) layout_settings.setContentsMargins(0, 0, 0, 0) layout_settings.setSpacing(6) asset_types = ["All Asset Types"] + ['comp'] self._asset_connectors_cbbox = QtWidgets.QComboBox(self) self._asset_connectors_cbbox.addItems(asset_types) self._asset_connectors_cbbox.currentIndexChanged.connect( self._update_tree) self._asset_connectors_cbbox.setMaximumHeight(23) self._asset_connectors_cbbox.setMinimumWidth(100) self._asset_connectors_cbbox.setSizeAdjustPolicy( QtWidgets.QComboBox.AdjustToContents) self._refresh_btn = QtWidgets.QPushButton(self) self._refresh_btn.setText("refresh") self._refresh_btn.clicked.connect(self.initiate_assets_tree) spacer = QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) layout_settings.addWidget(self._asset_connectors_cbbox) layout_settings.addItem(spacer) layout_settings.addWidget(self._refresh_btn) main_layout.addWidget(settings_frame) self.assets_tree = AssetsTree(self) main_layout.addWidget(self.assets_tree) def initiate_task(self, task, current_scene=None): self._task = task self.initiate_assets_tree() def initiate_assets_tree(self): asset_types = [self._asset_connectors_cbbox.currentText()] if self._asset_connectors_cbbox.currentIndex() == 0: asset_types = None self.assets_tree.create_asset.connect( self.assets_tree._model.appendRow) args = ( self._task.getId(), asset_types, ) self.worker = Worker(self.assets_tree.import_assets, args=args) self.worker.started.connect(self.worker_started.emit) self.worker.finished.connect(self.worker_stopped.emit) self.worker.start() while self.worker.isRunning(): app = QtGui.QApplication.instance() app.processEvents() def _update_tree(self): asset_type = self._asset_connectors_cbbox.currentText() if self._asset_connectors_cbbox.currentIndex() == 0: asset_type = None self.assets_tree.update_display(asset_type) def set_selection_mode(self, bool_value): self.assets_tree.set_selection_mode(bool_value)
class TaskWidget(QtWidgets.QFrame): asset_version_selected = QtCore.Signal(object) no_asset_version = QtCore.Signal() def __init__(self, parent=None): super(TaskWidget, self).__init__(parent) self.setupUI() self._current_task = None self._read_only = True self._selection_mode = False self.setObjectName('ftrack-task-widget') self._tasks_dict = dict() self.setStyleSheet(''' #ftrack-task-widget { padding: 3px; border-radius: 1px; background: #222; color: #FFF; font-size: 13px; } ''') def setupUI(self): main_layout = QtWidgets.QVBoxLayout(self) main_layout.setContentsMargins(0, 0, 0, 0) main_layout.setSpacing(0) empty_task = SingleTaskWidget(parent=self) self._stackLayout = QtWidgets.QStackedLayout() self._stackLayout.addWidget(empty_task) main_layout.addLayout(self._stackLayout) self._stackLayout.setCurrentWidget(empty_task) @property def current_shot_status(self): single_task_widget = self._stackLayout.currentWidget() return single_task_widget._shot_status.status @property def current_task_status(self): single_task_widget = self._stackLayout.currentWidget() return single_task_widget._task_status.status def _get_task_parents(self, task): parents = [t.getName() for t in task.getParents()] parents.reverse() parents.append(task.getName()) parents = ' / '.join(parents) return parents @property def current_asset_version(self): if self._current_task != None and self._selection_mode: parents = self._get_task_parents(self._current_task) widget = self._tasks_dict[parents] tree = widget.assets_widget.assets_tree return tree.current_version def set_read_only(self, bool_value): self._read_only = bool_value for widget in self._tasks_dict.values(): widget.set_read_only(bool_value) def set_selection_mode(self, bool_value): self._selection_mode = bool_value for widget in self._tasks_dict.values(): widget.set_selection_mode(bool_value) def set_task(self, task, current_scene=None): parents = self._get_task_parents(task) self._current_task = task if parents not in self._tasks_dict.keys(): single_task_widget = SingleTaskWidget(task=task, parent=self) single_task_widget.assets_widget.assets_tree.asset_version_selected.connect( self._emit_asset_version_selected) single_task_widget.set_read_only(self._read_only) single_task_widget.set_selection_mode(self._selection_mode) self._tasks_dict[parents] = single_task_widget self._stackLayout.addWidget(single_task_widget) self._stackLayout.setCurrentWidget(self._tasks_dict[parents]) def current_shot_status_changed(self): single_task_widget = self._stackLayout.currentWidget() return single_task_widget._shot_status.status_changed() def current_task_status_changed(self): single_task_widget = self._stackLayout.currentWidget() return single_task_widget._task_status.status_changed() def _emit_asset_version_selected(self, asset_version): self.asset_version_selected.emit(asset_version)
class ScriptEditorTreeView(QtWidgets.QTreeView): file_dropped = QtCore.Signal(str) def __init__(self, parent=None): super(ScriptEditorTreeView, self).__init__(parent) css_tree = """ QTreeView { background: #444; border: 1px solid #555; } QTreeView::branch:has-siblings:!adjoins-item { background: transparent; } QTreeView::branch:has-siblings:adjoins-item { background: transparent; } QTreeView::branch:!has-children:!has-siblings:adjoins-item { background: transparent; } QTreeView::branch:has-children:!has-siblings:closed, QTreeView::branch:closed:has-children:has-siblings { border-image: none; image: url(":ftrack/image/integration/branch-closed"); } QTreeView::branch:open:has-children:!has-siblings, QTreeView::branch:open:has-children:has-siblings { border-image: none; image: url(":ftrack/image/integration/branch-open"); } QScrollArea { padding: 3px; border: 0px; background: #252525; } QScrollBar { border: 0; background-color: #333; margin: 1px; } QScrollBar::handle { background: #222; border: 1px solid #111; } QScrollBar::sub-line, QScrollBar::add-line { height: 0px; width: 0px; } """ self.setStyleSheet(css_tree) self.setAcceptDrops(True) self.setDragDropMode(QtWidgets.QAbstractItemView.DropOnly) self.setDropIndicatorShown(True) self._drag_over = False # filter to highlight (from the search widget) self.filter = None self.font_size = 12 self._delegate = ScriptEditorItemDelegate(self) self.setItemDelegate(self._delegate) def dropEvent(self, event): self._drag_over = False if event.mimeData() is None: return file = event.mimeData().data("text/uri-list") if file is None: return file = file.data().strip() if file.startswith("file://"): file = file[len("file://"):] if os.access(file, os.R_OK) and file.endswith(".gizmo"): self.file_dropped.emit(file) def dragMoveEvent(self, event): event.accept() def dragEnterEvent(self, event): if event.mimeData() is None: event.ignore() return file = event.mimeData().data("text/uri-list") if file is None: event.ignore() return file = file.data().strip() if file.startswith("file://"): file = file[len("file://"):] if os.access(file, os.R_OK) and file.endswith(".gizmo"): self._drag_over = True event.accept() self.repaint() else: event.ignore() def dragLeaveEvent(self, event): self._drag_over = False return super(ScriptEditorTreeView, self).dragLeaveEvent(event) def set_filter(self, filter): ''' Set a element to highlight in the tree. ''' if len(str(filter)) == 0: self.filter = None else: self.filter = str(filter) def drawBranches(self, painter, rect, index): ''' Move the branches on the right to let the space for the line number display. ''' rect.setRight(rect.right() + self._delegate.line_numbers_indent + 10) super(ScriptEditorTreeView, self).drawBranches(painter, rect, index) def paintEvent(self, event): super(ScriptEditorTreeView, self).paintEvent(event) if self._drag_over: painter = QtGui.QPainter(self.viewport()) painter.setRenderHint(QtGui.QPainter.Antialiasing) rect = self.rect() painter.save() painter.setBrush(QtGui.QColor(255, 230, 183, 50)) painter.drawRect(rect) painter.restore() painter.setPen( QtGui.QPen(QtGui.QColor(255, 230, 183), 5, QtCore.Qt.DashLine)) painter.drawRoundedRect(rect.adjusted(20, 20, -20, -20), 20, 20) def viewportEvent(self, event): ''' Catch the click event to override the item "expand/collapse" function which is still called in the place it was before moving the branches in the drawBranches method. Catch the double-click event to override the item "expand/collapse" function which doesn't work after applying the delegate ''' # Click if event.type() == 2 and self.model() != None: index = self.indexAt(event.pos()) item = self.model().itemFromIndex(index) if item is None: return super(ScriptEditorTreeView, self).viewportEvent(event) width_button = 10 indent = self._delegate.line_numbers_indent + \ item.level * self.indentation() + 15 if event.pos().x() > indent and event.pos().x( ) < indent + width_button: if self.isExpanded(index): self.collapse(index) else: self.expand(index) return True # Double Click elif event.type() == 4: index = self.indexAt(event.pos()) if self.isExpanded(index): self.collapse(index) else: self.expand(index) return True # Other events... return super(ScriptEditorTreeView, self).viewportEvent(event)
class ScriptEditorWidget(QtWidgets.QWidget): file_dropped = QtCore.Signal(str) def __init__(self, parent=None): super(ScriptEditorWidget, self).__init__(parent) self.file = None self.setupUI() def setupUI(self): main_layout = QtWidgets.QVBoxLayout(self) main_layout.setContentsMargins(0, 0, 0, 0) main_layout.setSpacing(0) self._script_editor_tree = ScriptEditorTreeView(self) self._script_editor_tree.setSelectionMode( QtWidgets.QAbstractItemView.NoSelection) self._script_editor_tree.setIndentation(20) self._script_editor_tree.setAnimated(True) self._script_editor_tree.setHeaderHidden(True) self._script_editor_tree.setExpandsOnDoubleClick(True) self._script_editor_tree.file_dropped.connect(self._emit_dropped_file) main_layout.addWidget(self._script_editor_tree) self._option_frame = QtWidgets.QFrame(self) option_layout = QtWidgets.QHBoxLayout(self._option_frame) option_layout.setContentsMargins(0, 8, 0, 8) option_layout.setSpacing(8) # filter_lbl = QtGui.QLabel("Filter", self._option_frame) css_filter = """ QLineEdit { border: 1px solid #666; background: #555; color: #000; } """ self._filter_edit = QtWidgets.QLineEdit(self._option_frame) self._filter_edit.setMaximumHeight(20) # self._filter_edit.setStyleSheet(css_filter) self._filter_edit.textChanged.connect(self._set_filter) self._previous_occurence = QtWidgets.QPushButton( 'previous', self._option_frame) # self._previous_occurence.setArrowType(QtCore.Qt.LeftArrow) # self._previous_occurence.setMaximumWidth(20) # self._previous_occurence.setMaximumHeight(20) self._next_occurence = QtWidgets.QPushButton('next', self._option_frame) # self._next_occurence.setArrowType(QtCore.Qt.RightArrow) # self._next_occurence.setMaximumWidth(20) # self._next_occurence.setMaximumHeight(20) spacer = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self._collapse_all_btn = QtWidgets.QPushButton("Collapse All", self._option_frame) self._collapse_all_btn.setMaximumHeight(20) # self._collapse_all_btn.setStyleSheet(css_btn) self._collapse_all_btn.clicked.connect( self._script_editor_tree.collapseAll) self._expand_all_btn = QtWidgets.QPushButton("Expand All", self._option_frame) self._expand_all_btn.setMaximumHeight(20) # self._expand_all_btn.setStyleSheet(css_btn) self._expand_all_btn.clicked.connect( self._script_editor_tree.expandAll) option_layout.addWidget(self._filter_edit) option_layout.addWidget(self._previous_occurence) option_layout.addWidget(self._next_occurence) option_layout.addItem(spacer) option_layout.addWidget(self._collapse_all_btn) option_layout.addWidget(self._expand_all_btn) main_layout.addWidget(self._option_frame) def set_file(self, file): f = open(file, "r") script_str = "".join(f.readlines()) f.close() script = Script(script_str) model = ScriptEditorItemModel(script) self._script_editor_tree.setModel(model) self._script_editor_tree.expandAll() self.file = file def initiate(self): if self._script_editor_tree.model() is not None: self._script_editor_tree.model().clear() self._set_filter("") self.file = None def _emit_dropped_file(self, file): self.file_dropped.emit(file) def set_enabled(self, bool_value): self._option_frame.setEnabled(bool_value) def _toggle_line_number(self): self._script_editor_tree.repaint() def _set_filter(self, filter): if len(filter) == 0: self._script_editor_tree.filter = None self._previous_occurence.setEnabled(False) self._next_occurence.setEnabled(False) else: self._script_editor_tree.filter = filter self._previous_occurence.setEnabled(True) self._next_occurence.setEnabled(True) self._script_editor_tree.repaint() def _toggle_zoom(self): if self.sender() == self._zoom_text_in: self._script_editor_tree.font_size += 1 elif self.sender() == self._zoom_text_out: self._script_editor_tree.font_size -= 1 self._script_editor_tree.repaint() @property def script_str(self): full_lines = [] if self._script_editor_tree.model() != None: for line in self._script_editor_tree.model().script_lines(): full_lines.append(line.full_line) return '\n'.join(full_lines) @property def script_top_lines(self): if self._script_editor_tree.model() != None: return self._script_editor_tree.model().script_top_lines() @property def script_lines(self): lines = [] if self._script_editor_tree.model() != None: for line in self._script_editor_tree.model().script_lines(): lines.append(line) return lines
class PublishShotClipsWidget(QtGui.QWidget): """ A dialog to present the user with options pertaining to creating shots in an asset manager, based on a number of selected track items. Clips from these TrackItems can also be published to the shots, or, if shared with multiple TrackItems, they can be published to an alternate location. @specUsage FnAssetAPI.specifications.ImageSpecification @specUsage FnAssetAPI.specifications.ShotSpecification """ optionsChanged = QtCore.Signal() ## @name Constants for Option Keys ## @{ kTargetEntityRef = 'targetEntityRef' kPublishClips = 'publishClips' kClipsUseCustomName = 'clipsUseCustomName' kCustomClipName = 'customClipName' kPublishSharedClips = 'publishSharedClips' kUsePublishedClips = 'usePublishedClips' kIgnorePublishedClips = 'ignorePublishedClips' kSharedClipEntityRef = 'sharedClipTargetEntityRef' kManagerOptionsClip = 'managerOptionsClip' ## @} ## @todo We currently require a context at initialisation time, as we need to # create Manager UI elements. Ideally, we'd let this be set later, and # re-create any context-dependent UI as necessary. def __init__(self, context, parent=None, options=None): super(PublishShotClipsWidget, self).__init__(parent=parent) self.__trackItems = [] self.__shotItems = [] self.__clipItems = [] self.__sharedClipItems = [] self.__updatingOptions = False self.__options = { self.kTargetEntityRef: '', self.kPublishClips: True, self.kPublishSharedClips: False, self.kUsePublishedClips: True, self.kSharedClipEntityRef: '', self.kClipsUseCustomName: False, self.kCustomClipName: 'plate', self.kIgnorePublishedClips: True, } self._session = FnAssetAPI.ui.UISessionManager.currentSession() self._context = context # Note, this is a reference self._context.access = context.kWriteMultiple # Make some caches for these, to avoid thrashing the API self.__clipPolicy = cmdUtils.policy.clipPolicy(forWrite=True) self.__perEntityClipPolicy = {} # We'll need to keep track of some lookups to avoid excess traffic self._parentEntity = None self._newShots = [] self._existi_existingShotsLabelngShots = [] layout = QtGui.QVBoxLayout() self.setLayout(layout) self._buildUI(layout) self._connectUI() if options: self.setOptions(options) else: self._readOptions() def _buildUI(self, layout): ## @todo Some of these should probably be widgets in their own right, but ## it needs a little though due to the interaction between them. # Add the 'Create Under' section, to choose the parent entity that should # receive the new shots. specification = ShotSpecification() pickerCls = self._session.getManagerWidget( FnAssetAPI.ui.constants.kInlinePickerWidgetId, instantiate=False) # Parent Picker parentPickerLayout = QtGui.QHBoxLayout() parentPickerLayout.addWidget( QtGui.QLabel("Look for matching Shots under:")) self._shotParentPicker = pickerCls(specification, self._context) parentPickerLayout.addWidget(self._shotParentPicker) layout.addLayout(parentPickerLayout) mediaWidget = self._buildClipsTab() layout.addWidget(mediaWidget) def _buildClipsTab(self): l = FnAssetAPI.l imageSpecification = ImageSpecification() # > Media Ta mediaWidget = QtGui.QWidget() mediaWidget.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) mediaWidgetLayout = QtGui.QVBoxLayout() mediaWidgetLayout.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop) mediaWidget.setLayout(mediaWidgetLayout) # - Shared Media self._sharedClipsGroup = QtGui.QGroupBox( l("Some Source Clips are Shared " + "and used in more than one Shot in the Edit")) mediaWidgetLayout.addWidget(self._sharedClipsGroup) sharedClipsGroupLayout = QtGui.QVBoxLayout() self._sharedClipsGroup.setLayout(sharedClipsGroupLayout) self._sharedIgnoredRadio = QtGui.QRadioButton(l("Don't {publish}")) self._sharedToSequenceRadio = QtGui.QRadioButton( l("{publish} at the level above the Shots")) self._sharedToCustomRadio = QtGui.QRadioButton( l("{publish} to another location")) self._sharedIgnoredRadio.setChecked(True) sharedClipsGroupLayout.addWidget(self._sharedIgnoredRadio) sharedClipsGroupLayout.addWidget(self._sharedToSequenceRadio) sharedClipsGroupLayout.addWidget(self._sharedToCustomRadio) ## @todo Use the project entityReferences Parent if we have one? pickerCls = self._session.getManagerWidget( FnAssetAPI.ui.constants.kInlinePickerWidgetId, instantiate=False) self._sharedClipParentPicker = pickerCls(imageSpecification, self._context) self._sharedClipParentPicker.setVisible(False) sharedClipsGroupLayout.addWidget(self._sharedClipParentPicker) self._previewWidget = PublishShotClipsSummaryWidget() self._previewWidget.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) mediaWidgetLayout.addWidget(self._previewWidget) # - Options self._clipOptionsGroup = QtGui.QGroupBox(l("Options")) optionsGroupLayout = QtGui.QVBoxLayout() self._clipOptionsGroup.setLayout(optionsGroupLayout) mediaWidgetLayout.addWidget(self._clipOptionsGroup) # See if we have any options from the manager self._managerOptionsClip = self._session.getManagerWidget( FnAssetAPI.ui.constants.kRegistrationManagerOptionsWidgetId, throw=False, args=(imageSpecification, self._context)) if self._managerOptionsClip: optionsGroupLayout.addWidget(self._managerOptionsClip) optionsGroupLayout.addSpacing(10) hieroOptionsGrid = QtGui.QGridLayout() ## @todo we should have some base widget for this hieroOptionsGrid.addWidget(QtGui.QLabel(l("{asset} name:")), 0, 0) self._clipNameCombo = QtGui.QComboBox() self._clipNameCombo.addItems(("Clip Name", "Custom")) hieroOptionsGrid.addWidget(self._clipNameCombo, 0, 1) self._clipNameCustomField = QtGui.QLineEdit() hieroOptionsGrid.addWidget(self._clipNameCustomField, 0, 2) self._replaceClipSource = QtGui.QCheckBox( l("Link Source Clips to " + "{published} {assets}")) hieroOptionsGrid.addWidget(self._replaceClipSource, 1, 1, 1, 2) self._ignorePublishedClips = QtGui.QCheckBox( l("Ignore Source Clips that are " + "already {published}")) hieroOptionsGrid.addWidget(self._ignorePublishedClips, 2, 1, 1, 2) # Make sure we don't stretch the grid layout too much and make the last #column really wide hieroOptionsHBox = QtGui.QHBoxLayout() optionsGroupLayout.addLayout(hieroOptionsHBox) hieroOptionsHBox.addLayout(hieroOptionsGrid) hieroOptionsHBox.addStretch() return mediaWidget def _connectUI(self): self._shotParentPicker.selectionChanged.connect( lambda v: self.__updateOption(self.kTargetEntityRef, v[0] if v else '')) # Make sure the shared clip destination is updated too self._shotParentPicker.selectionChanged.connect( self.__sharedClipDestToggle) self._replaceClipSource.toggled.connect( lambda s: self.__updateOption(self.kUsePublishedClips, s)) self._ignorePublishedClips.toggled.connect( lambda s: self.__updateOption( self.kIgnorePublishedClips, s, clearItems=True)) self._sharedToSequenceRadio.toggled.connect( self.__sharedClipDestToggle) self._sharedToCustomRadio.toggled.connect(self.__sharedClipDestToggle) self._sharedIgnoredRadio.toggled.connect(self.__sharedClipDestToggle) self._sharedClipParentPicker.selectionChanged.connect( self.__sharedClipDestToggle) self._clipNameCustomField.editingFinished.connect( self.__clipNameOptionsChanged) self._clipNameCombo.currentIndexChanged.connect( self.__clipNameOptionsChanged) ## @todo Do we need to connect up the manager options widget too? def __clipNameOptionsChanged(self): if self.__updatingOptions: return source = self._clipNameCombo.currentText() self.__updateOption(self.kClipsUseCustomName, source == "Custom", refresh=False) name = self._clipNameCustomField.text() self.__updateOption(self.kCustomClipName, name, refresh=True, clearItems=True) def __sharedClipDestToggle(self): ignore = self._sharedIgnoredRadio.isChecked() useCustom = self._sharedToCustomRadio.isChecked() self._sharedClipParentPicker.setVisible(useCustom) if self.__updatingOptions: return if useCustom: sharedTarget = self._sharedClipParentPicker.getSelectionSingle() else: sharedTarget = self._shotParentPicker.getSelectionSingle() self.__updateOption(self.kPublishSharedClips, not ignore) self.__updateOption(self.kSharedClipEntityRef, sharedTarget) def __updateOption(self, option, value, refresh=True, clearParent=False, clearItems=False): if self.__updatingOptions: return self.__options[option] = value if refresh: if clearParent: self._parentEntity = None if clearItems: self.__shotItems = [] self.refresh() self._validateOptions() def _readOptions(self): self.__updatingOptions = True # Drive some defaults if the options aren't set publishSharedClips = self.__options.get(self.kPublishSharedClips, False) # Update UI, this will set the options in the defaulted case due to the # signal connections on the toggled event targetEntityRef = self.__options.get(self.kTargetEntityRef, '') sharedTargetEntityRef = self.__options.get(self.kSharedClipEntityRef, '') # Update the radios first due to signal connections if publishSharedClips: if sharedTargetEntityRef or sharedTargetEntityRef == targetEntityRef: self._sharedToSequenceRadio.setChecked(True) else: self._sharedIgnoredRadio.setChecked(True) else: try: self._sharedClipParentPicker.setSelectionSingle( sharedTargetEntityRef) except Exception as e: FnAssetAPI.logging.debug(e) self._sharedToCustomRadio.setChecked(True) # Update main picked value try: self._shotParentPicker.setSelectionSingle(targetEntityRef) except Exception as e: FnAssetAPI.logging.debug(e) replaceClips = self.__options.get(self.kUsePublishedClips, True) self._replaceClipSource.setChecked(replaceClips) # Manager Options managerOptionsClip = self.__options.get(self.kManagerOptionsClip, None) if managerOptionsClip and self._managerOptionsClip: self._managerOptionsClip.setOptions(managerOptionsClip) clipCustomName = self.__options.get(self.kCustomClipName, '') self._clipNameCustomField.setText(clipCustomName) useClipCustomName = self.__options.get(self.kClipsUseCustomName, False) self._clipNameCombo.setCurrentIndex(1 if useClipCustomName else 0) ignorePublished = self.__options.get(self.kIgnorePublishedClips, True) self._ignorePublishedClips.setChecked(ignorePublished) self.__updatingOptions = False # Make sure that the shared clip options are correctly configured - there # isn't a 1:1 mapping between options and controls, so the case of 'publish # to shot parent' lets just double check that the options dict contain the # right parent self.__sharedClipDestToggle() def _validateOptions(self): # Make sure that the asset manager can take us publishing a clip clipsAllowed = self.__clipPolicy != FnAssetAPI.constants.kIgnored ## @todo disable dialog if clips not allowed # If people are choosing to publish shared clips to the main sequence, # make sure that the parent is capable of taking them (some cases, its not) # Disable the radio button if its not applicable sharedPublishEnabled = True if self._sharedToSequenceRadio.isChecked(): dest = self.__options.get(self.kSharedClipEntityRef, None) if dest: if dest not in self.__perEntityClipPolicy: self.__perEntityClipPolicy[ dest] = cmdUtils.policy.clipPolicy(forWrite=True, entityRef=dest) sharedClipPolicy = self.__perEntityClipPolicy.get( dest, FnAssetAPI.constants.kIgnored) if sharedClipPolicy == FnAssetAPI.constants.kIgnored: sharedPublishEnabled = False if not sharedPublishEnabled: self._sharedToCustomRadio.setChecked(True) ## @todo For some reason, this doesn't seem to take effect, so it looks a bit # confusing to the user :( self._sharedToSequenceRadio.setEnabled(sharedPublishEnabled) self._sharedToSequenceRadio.setCheckable(sharedPublishEnabled) self._clipNameCustomField.setEnabled( self.__options.get(self.kClipsUseCustomName, False)) def sizeHint(self): return QtCore.QSize(600, 400) def setTrackItems(self, trackItems): self.__trackItems = [] self.__trackItems = trackItems self.__shotItems = [] # Clear cache self.refresh() def getTrackItems(self): return self.__trackItems def getOptions(self): options = dict(self.__options) managerOptionsClip = {} if self._managerOptionsClip: managerOptionsClip = self._managerOptionsClip.getOptions() options[self.kManagerOptionsClip] = managerOptionsClip return options def setOptions(self, options): self.__options.update(options) self._readOptions() if self._managerOptionsClip: managerOptions = options.get(self.kManagerOptionsClip, {}) self._managerOptionsClip.setOptions(managerOptions) self.refresh() # This refreshes the UI based on its current state, it doesn't re-read the # options dict directly. If required, call _readOptions() first def refresh(self): ## @todo Call managementPolicy on an image sequence to the chosen sequence ## in case, say someone selected a project as the destination and the ams ## can't handle image sequences at the project level... session = FnAssetAPI.SessionManager.currentSession() if not session: raise RuntimeError("No Asset Management session available") if not self.__shotItems: self.__shotItems = cmdUtils.object.trackItemsToShotItems( self.__trackItems, self.getOptions(), coalesseByName=True) self._parentEntity = None self._previewWidget.setShotItems(self.__shotItems) # Update Shot Creation parentRef = self.__options.get(self.kTargetEntityRef, None) # Ensure we don't waste time repeatedly looking under the same parent if not self._parentEntity or self._parentEntity.reference != parentRef: self._parentEntity = session.getEntity(parentRef) if self._parentEntity: # Ensure we have the entity for any existing shots cmdUtils.shot.analyzeHieroShotItems(self.__shotItems, self._parentEntity, checkForConflicts=False, adopt=True) self.__clipItems, self.__sharedClipItems = \ cmdUtils.shot.analyzeHeiroShotItemClips(self.__shotItems, asItems=True) haveShared = bool(self.__sharedClipItems) if self.__options.get(self.kIgnorePublishedClips, True): itemFilter = lambda i: not i.getEntity() self.__clipItems = filter(itemFilter, self.__clipItems) self.__sharedClipItems = filter(itemFilter, self.__sharedClipItems) # Update Clip publishing self._previewWidget.setShotItems(self.__shotItems) self._previewWidget.setOptions(self.getOptions()) self._previewWidget.refresh() self._sharedClipsGroup.setDisabled(not bool(self.__sharedClipItems)) self._sharedClipsGroup.setVisible(haveShared) self._validateOptions() self.optionsChanged.emit() def getButtonState(self): publishClips = bool( self.__clipItems) and self.__options[self.kTargetEntityRef] publishShared = (self.__options[self.kPublishSharedClips] and bool(self.__sharedClipItems)) publishSharedValid = self.__options[self.kSharedClipEntityRef] publish = bool(publishClips or publishShared) enabled = publish if publishShared and not publishSharedValid: enabled = False title = FnAssetAPI.l("{publish}") return title, enabled
class UpdateShotsWidget(QtGui.QWidget): """ A dialog to present the user with options pertaining to creating shots in an asset manager, based on a number of selected track items. Clips from these TrackItems can also be published to the shots, or, if shared with multiple TrackItems, they can be published to an alternate location. @specUsage FnAssetAPI.specifications.ShotSpecification """ optionsChanged = QtCore.Signal() ## @name Constants for Option Keys ## @{ kTargetEntityRef = 'targetEntityRef' kUpdateConflictingShots = 'updateConflictingShots' kSetShotTimings = 'setShotTimings' ## @} ## @todo We currently require a context at initialisation time, as we need to # create Manager UI elements. Ideally, we'd let this be set later, and # re-create any context-dependent UI as necessary. def __init__(self, context, parent=None, options=None): super(UpdateShotsWidget, self).__init__(parent=parent) self._tickIcon = QtGui.QIcon("icons:TagGood.png") self._crossIcon = QtGui.QIcon("icons:SwapInputs.png") self._blockIcon = QtGui.QIcon("icons:status/TagOnHold.png") self.__updatingOptions = False self.__trackItems = [] self.__shotItems = [] self.__options = { self.kTargetEntityRef : '', self.kUpdateConflictingShots : True, self.kSetShotTimings : True } self._session = FnAssetAPI.ui.UISessionManager.currentSession() self._context = context # Note, this is a reference self._context.access = context.kWriteMultiple # We'll need to keep track of some lookups to avoid excess traffic self._parentEntity = None self._newShots = [] self._existingShots = [] self._conflictingShots = [] layout = QtGui.QVBoxLayout() self.setLayout(layout) self._buildUI(layout) self._connectUI() if options: self.setOptions(options) else: self._readOptions() def _buildUI(self, layout): # Add the 'Create Under' section, to choose the parent entity that should # receive the new shots. specification = ShotSpecification() pickerCls = self._session.getManagerWidget( FnAssetAPI.ui.constants.kInlinePickerWidgetId, instantiate=False) # Parent Picker l = FnAssetAPI.l parentPickerLayout = QtGui.QHBoxLayout() parentPickerLayout.addWidget(QtGui.QLabel(l("Match {shots} under:"))) self._shotParentPicker = pickerCls(specification, self._context) parentPickerLayout.addWidget(self._shotParentPicker) layout.addLayout(parentPickerLayout) shotsWidget = self._buildShotsTab() layout.addWidget(shotsWidget) def _buildShotsTab(self): l = FnAssetAPI.l # > Shots Tab shotsWidget = QtGui.QWidget() shotsWidget.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) shotsWidgetLayout = QtGui.QVBoxLayout() shotsWidgetLayout.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop) shotsWidget.setLayout(shotsWidgetLayout) # - Conflicting Shots self._shotsList = AdvancedHieroItemSpreadsheet() self._shotsList.setAlternatingRowColors(True) self._shotsList.setHiddenProperties(("nameHint",)) self._shotsList.setForcedProperties( ("startFrame", "endFrame", "inFrame", "outFrame", "inTimecode", "sourceTimecode")) self._shotsList.setStatusText(l("Update Timings"), l("Timings Match")) self._shotsList.setDisabledCallback(self.__shotItemIsDisabled) self._shotsList.setStatusCallback(self.__statusForShotItem) self._shotsList.setIconCallback(self.__iconForShotItem) shotsWidgetLayout.addWidget(self._shotsList) # Length Options self._shotLengthGBox = QtGui.QGroupBox("Set Shot Timings") self._shotLengthGBox.setCheckable(True) self._shotLengthGBox.setChecked(False) slGbLayout = QtGui.QHBoxLayout() self._shotLengthGBox.setLayout(slGbLayout) self._shotLengthOptionsWidget = TrackItemTimingOptionsWidget() slGbLayout.addWidget(self._shotLengthOptionsWidget) slGbLayout.addStretch() shotsWidgetLayout.addWidget(self._shotLengthGBox) return shotsWidget def _connectUI(self): self._shotParentPicker.selectionChanged.connect( lambda v: self.__updateOption(self.kTargetEntityRef, v[0] if v else '')) self._shotLengthOptionsWidget.optionsChanged.connect(self.__timingOptionsChanged) self._shotLengthGBox.toggled.connect(self.__timingOptionsChanged) ## @todo Do we need to connect up the manager options widget too? def __timingOptionsChanged(self): if self.__updatingOptions: return self.__options.update(self._shotLengthOptionsWidget.getOptions()) self.__options[self.kSetShotTimings] = bool(self._shotLengthGBox.isChecked()) # Force a full refresh self.__shotItems = [] self._parentEntity = None self.refresh() def __updateOption(self, option, value, refresh=True, clearParent=False, clearItems=False): if self.__updatingOptions: return self.__options[option] = value if refresh: if clearParent: self._parentEntity = None if clearItems: self.__shotItems = [] self.refresh() self._validateOptions() def __iconForShotItem(self, item): if item in self._existingShots: return self._tickIcon elif item in self._newShots: return self._blockIcon else: return self._crossIcon def __statusForShotItem(self, item): if item in self._existingShots: return "Timings Match" elif item in self._newShots: return FnAssetAPI.l("No Matching {shot}") else: return "Timings Different" def __shotItemIsDisabled(self, item): return item not in self._conflictingShots def _readOptions(self): self.__updatingOptions = True # Update UI, this will set the options in the defaulted case due to the # signal connections on the toggled event targetEntityRef = self.__options.get(self.kTargetEntityRef, '') # Update main picked value try: self._shotParentPicker.setSelectionSingle(targetEntityRef) except Exception as e: FnAssetAPI.logging.debug(e) # Shot length options are read directly in the widget setTimings = self.__options.get(self.kSetShotTimings, True) self._shotLengthGBox.setChecked(setTimings) self.__updatingOptions = False def _validateOptions(self): pass def sizeHint(self): return QtCore.QSize(600, 400) def setTrackItems(self, trackItems): self.__trackItems = [] self.__trackItems = trackItems self.__shotItems = [] # Clear cache self.refresh() def getTrackItems(self): return self.__trackItems def getOptions(self): options = dict(self.__options) options.update(self._shotLengthOptionsWidget.getOptions()) return options def setOptions(self, options): self.__options.update(options) self._readOptions() self._shotLengthOptionsWidget.setOptions(options) self.refresh() # This refreshes the UI based on its current state, it doesn't re-read the # options dict directly. If required, call _readOptions() first def refresh(self): ## @todo Call managementPolicy on an image sequence to the chosen sequence ## in case, say someone selected a project as the destination and the ams ## can't handle image sequences at the project level... session = FnAssetAPI.SessionManager.currentSession() if not session: raise RuntimeError("No Asset Management session available") if not self.__shotItems: self.__shotItems = cmdUtils.object.trackItemsToShotItems(self.__trackItems, self.getOptions(), coalesseByName=True) # Update Shot Creation parentRef = self.__options.get(self.kTargetEntityRef, None) # Ensure we don't waste time repeatedly looking under the same parent if not self._parentEntity or self._parentEntity.reference != parentRef: self._parentEntity = session.getEntity(parentRef) newShots, existingShots, conflictingShots = cmdUtils.shot.analyzeHieroShotItems( self.__shotItems, self._parentEntity, self._context) self._newShots = newShots self._existingShots = existingShots self._conflictingShots = conflictingShots self._shotsList.setItems(self.__shotItems) self._validateOptions() self.optionsChanged.emit() def getButtonState(self): update = bool(self._conflictingShots) return "Update", update
class SingleSceneVersionWidget(QtGui.QWidget): notify = QtCore.Signal((str, str)) def __init__(self, scene_version=None, parent=None): super(SingleSceneVersionWidget, self).__init__(parent) self.scene_version = scene_version self.error = False self.locked = False self.setupUI() self.initiate_scene_version() def setupUI(self): css_asset_global = """ QFrame { padding: 3px; background: #222; color: #FFF; font-size: 13px; } QLabel { padding: 0px; background: none; } """ self._css_lbl = "color: #AAA;" css_asset_name = "color: #c3cfa4; font-weight: bold;" css_asset_version = "color: #de8888; font-weight: bold;" css_comment = """ color: #f0f0f0; background: #444; padding: 3px ; border-radius: 2px; """ self._css_value = "color: #FFF; text-decoration: none;" self.setMinimumWidth(700) asset_frame_layout = QtGui.QVBoxLayout(self) asset_frame_layout.setContentsMargins(0, 0, 0, 0) asset_frame_layout.setSpacing(10) asset_main_frame = QtGui.QFrame(self) asset_main_frame.setStyleSheet(css_asset_global) asset_main_frame_layout = QtGui.QHBoxLayout(asset_main_frame) asset_main_frame_layout.setSpacing(10) asset_name_lbl = QtGui.QLabel("Asset", asset_main_frame) self._asset_name = QtGui.QLabel("...", asset_main_frame) self._asset_name.setStyleSheet(css_asset_name) spacer_asset = QtGui.QSpacerItem(0, 0, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) asset_version_lbl = QtGui.QLabel("Version", asset_main_frame) self._asset_version = QtGui.QLabel("...", asset_main_frame) self._asset_version.setStyleSheet(css_asset_version) asset_main_frame_layout.addWidget(asset_name_lbl) asset_main_frame_layout.addWidget(self._asset_name) asset_main_frame_layout.addItem(spacer_asset) asset_main_frame_layout.addWidget(asset_version_lbl) asset_main_frame_layout.addWidget(self._asset_version) asset_frame_layout.addWidget(asset_main_frame) overview_layout = QtGui.QHBoxLayout() overview_layout.setContentsMargins(0, 0, 0, 0) overview_layout.setSpacing(10) self._thumbnail_widget = ThumbnailWidget(self) overview_layout.addWidget(self._thumbnail_widget) self._infos_layout = QtGui.QFormLayout() self._infos_layout.setContentsMargins(0, 0, 0, 0) self._infos_layout.setSpacing(10) asset_type_lbl = QtGui.QLabel("Asset type", self) asset_type_lbl.setStyleSheet(self._css_lbl) self._asset_type = QtGui.QLabel(self) self.set_asset_type("...") status_lbl = QtGui.QLabel("Status", self) status_lbl.setStyleSheet(self._css_lbl) self._status = StatusWidget(ftrack.getTaskStatuses(), self) self._status.set_read_only(True) publish_lbl = QtGui.QLabel("Published by", self) publish_lbl.setStyleSheet(self._css_lbl) self._owner = QtGui.QLabel("...", self) self._owner.setTextFormat(QtCore.Qt.RichText) self._owner.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) self._owner.setOpenExternalLinks(True) date_lbl = QtGui.QLabel("on", self) date_lbl.setStyleSheet(self._css_lbl) self._date = QtGui.QLabel(self) self._date.setStyleSheet(self._css_value) self._editor = None self._date_edit = None availability_lbl = QtGui.QLabel("Availability", self) availability_lbl.setStyleSheet(self._css_lbl) self._availability = QtGui.QLabel(self) self._availability.setStyleSheet(self._css_value) comment_lbl = QtGui.QLabel("Comment", self) comment_lbl.setStyleSheet(self._css_lbl) self._comment = QtGui.QLabel("...", self) self._comment.setWordWrap(True) self._comment.setStyleSheet(css_comment) self._infos_layout.setWidget(0, QtGui.QFormLayout.LabelRole, asset_type_lbl) self._infos_layout.setWidget(0, QtGui.QFormLayout.FieldRole, self._asset_type) self._infos_layout.setWidget(1, QtGui.QFormLayout.LabelRole, status_lbl) self._infos_layout.setWidget(1, QtGui.QFormLayout.FieldRole, self._status) self._infos_layout.setWidget(2, QtGui.QFormLayout.LabelRole, publish_lbl) self._infos_layout.setWidget(2, QtGui.QFormLayout.FieldRole, self._owner) self._infos_layout.setWidget(3, QtGui.QFormLayout.LabelRole, date_lbl) self._infos_layout.setWidget(3, QtGui.QFormLayout.FieldRole, self._date) self._infos_layout.setWidget(4, QtGui.QFormLayout.LabelRole, availability_lbl) self._infos_layout.setWidget(4, QtGui.QFormLayout.FieldRole, self._availability) self._infos_layout.setWidget(5, QtGui.QFormLayout.LabelRole, comment_lbl) self._infos_layout.setWidget(5, QtGui.QFormLayout.FieldRole, self._comment) overview_layout.addItem(self._infos_layout) spacer_overview = QtGui.QSpacerItem(0, 0, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) overview_layout.addItem(spacer_overview) asset_frame_layout.addItem(overview_layout) self._tab_widget = QtGui.QTabWidget(self) css_tab = """ /* QTabWidget::pane { border-top: 2px solid #151515; top: -2px; border-top-left-radius: 0px; border-top-right-radius: 0px; background: #282828; } QTabBar::tab { padding: 6px 10px; background: #151515; border-top: 2px solid #151515; border-right: 2px solid #151515; border-left: 2px solid #151515; border-radius: 0px; } QTabBar::tab:selected { background: #333; border-top-left-radius: 4px; border-top-right-radius: 4px; } QTabBar::tab:hover { background: #222; } QTabBar::tab:!selected { margin-top: 2px; } */ """ self._tab_widget.setStyleSheet(css_tab) # Display asset history tab_asset_history = QtGui.QWidget() tab_asset_history_layout = QtGui.QVBoxLayout(tab_asset_history) tab_asset_history_layout.setContentsMargins(0, 8, 0, 0) self._graph_widget = StatisticWidget(self.scene_version, self) tab_asset_history_layout.addWidget(self._graph_widget) self._tab_widget.addTab(tab_asset_history, "Asset history") asset_frame_layout.addWidget(self._tab_widget) spacer_global = QtGui.QSpacerItem(0, 0, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) asset_frame_layout.addItem(spacer_global) def _load_image(self, image): default_thumbnail = os.environ["FTRACK_SERVER"] + "/img/thumbnail2.png" thumbnail = self.scene_version.getThumbnail() or default_thumbnail image.loadFromData(urllib.urlopen(thumbnail).read()) return image def initiate_scene_version(self): if self.scene_version == None: return self._asset_name.setText(self.scene_version.getParent().getName()) self._status.set_status(self.scene_version.getStatus()) self._asset_version.setText("%03d" % self.scene_version.get('version')) image = QtGui.QPixmap() self._controller = Controller(self._load_image, [image]) self._controller.completed.connect( lambda: self._thumbnail_widget.update_image(image)) self._controller.start() self.set_owner(self.scene_version.getOwner()) self._date.setText(str(self.scene_version.getDate())) # self._availability.setText(', '.join(self.scene_version.locations)) # self.set_asset_type(self.scene_version.asset.connector.asset_type) self._comment.setText(self.scene_version.getComment()) self._validate() def set_asset_type(self, current_asset_type): asset_type_name = current_asset_type color = "#282828" # for scene_connector in self._scenes_connectors: # if scene_connector.asset_type == current_asset_type: # color = scene_connector.color # asset_type_name = scene_connector.name css_asset_type = """ border-radius: 2px; border: 0px; color: #f0f0f0; padding: 3px; background: """ + color + """; """ self._asset_type.setText(asset_type_name) self._asset_type.setStyleSheet(css_asset_type) def set_owner(self, owner): name = owner.getName() email = owner.getEmail() self._owner.setText("<a style='" + self._css_value + "' href='mailto:" + email + "'>" + name + "</a>") def set_editor(self, editor): name = editor.getName() email = editor.getEmail() self._editor.setText("<a style='" + self._css_value + "' href='mailto:" + email + "'>" + name + "</a>") def _validate(self): errors = [] warnings = [] # TODO --- try: scene_path = self.scene_version.getComponent( name='comp').getFilesystemPath() logging.debug(scene_path) except Exception as err: errors.append(str(err)) else: if scene_path == None: error = "This scene doesn't seem to be available in your location. Please" "synchronize the script in your location before loading it." errors.append(error) elif not os.path.isfile(scene_path): error = "The scene component exists and is in your location.. However it seems" "that its path is incorrect. Did anyone move it or renamed it?" errors.append(error) elif not scene_path.endswith(".nk"): file = os.path.basename(scene_path) error = "The scene component exists and is in your location... But this is" "not a Nuke script (ending with .nk)<br/>[file: %s]" % file errors.append(error) if len(errors) > 0: self.notify.emit("<br/><br/>".join(errors), 'error') elif len(warnings) > 0: self.notify.emit("<br/><br/>".join(warning), 'error')
class Browser(FnAssetAPI.ui.widgets.BrowserWidget): '''Browse entities.''' clickedIdSignal = QtCore.Signal(str, name='clickedIdSignal') def __init__(self, bridge, specification, context, parent=None): '''Initialise widget. *bridge* should be an instance of :py:class:`ftrack_connect_foundry.bridge.Bridge`. ''' self._bridge = bridge self._specification = specification self._context = context super(Browser, self).__init__(specification, context, parent=parent) self._selectionValid = False self._componentNamesFilter = None self._metaFilters = None self._showAssets = True self._showTasks = True self._showAssetVersions = True self._showShots = True self._shotsEnabled = True self._currentBrowsingId = None self._selection = [] self._build() self._postBuild() def _build(self): '''Build and layout widget.''' layout = QtWidgets.QVBoxLayout() self.setLayout(layout) # Header header = ftrack_connect.ui.widget.header.Header( getpass.getuser(), self) header.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed) layout.addWidget(header) secondaryHeader = QtWidgets.QFrame() headerLayout = QtWidgets.QHBoxLayout() headerLayout.setContentsMargins(0, 0, 0, 0) secondaryHeader.setLayout(headerLayout) layout.addWidget(secondaryHeader) self._createButton = QtWidgets.QToolButton() self._createButton.setIcon( QtGui.QIcon.fromTheme('plus', QtGui.QIcon(':icon-plus'))) headerLayout.addWidget(self._createButton) self._navigateUpButton = QtWidgets.QToolButton() self._navigateUpButton.setIcon( QtGui.QIcon.fromTheme('go-up', QtGui.QIcon(':icon-arrow-up'))) headerLayout.addWidget(self._navigateUpButton) headerLayout.addStretch(1) # Bookmarks contentSplitter = QtWidgets.QSplitter() layout.addWidget(contentSplitter) self._bookmarksView = QtWidgets.QTableWidget() self._bookmarksView.setEditTriggers( QtWidgets.QAbstractItemView.NoEditTriggers) self._bookmarksView.setGridStyle(QtCore.Qt.NoPen) self._bookmarksView.setColumnCount(1) self._bookmarksView.setColumnCount(1) self._bookmarksView.setRowCount(0) self._bookmarksView.horizontalHeader().setVisible(False) self._bookmarksView.horizontalHeader().setStretchLastSection(True) self._bookmarksView.verticalHeader().setVisible(False) self._bookmarksView.verticalHeader().setDefaultSectionSize(25) contentSplitter.addWidget(self._bookmarksView) # Navigation self._navigator = QtWidgets.QTableWidget() self._navigator.setEditTriggers( QtWidgets.QAbstractItemView.NoEditTriggers) self._navigator.setGridStyle(QtCore.Qt.NoPen) self._navigator.setColumnCount(1) self._navigator.horizontalHeader().setStretchLastSection(True) self._navigator.verticalHeader().hide() self._navigator.setHorizontalHeaderLabels(['Name']) contentSplitter.addWidget(self._navigator) self._versionsNavigator = QtWidgets.QTableWidget() self._versionsNavigator.setEditTriggers( QtWidgets.QAbstractItemView.NoEditTriggers) self._versionsNavigator.setGridStyle(QtCore.Qt.NoPen) self._versionsNavigator.setColumnCount(1) self._versionsNavigator.verticalHeader().hide() self._versionsNavigator.setSortingEnabled(False) self._versionsNavigator.setHorizontalHeaderLabels(['Version']) contentSplitter.addWidget(self._versionsNavigator) self._componentsNavigator = QtWidgets.QTableWidget() self._componentsNavigator.setEditTriggers( QtWidgets.QAbstractItemView.NoEditTriggers) self._componentsNavigator.setColumnCount(1) self._componentsNavigator.horizontalHeader().setStretchLastSection( True) self._componentsNavigator.verticalHeader().hide() self._componentsNavigator.verticalHeader().setStretchLastSection(False) self._componentsNavigator.setHorizontalHeaderLabels(['Component']) contentSplitter.addWidget(self._componentsNavigator) # Details self._detailView = ftrack_connect_foundry.ui.detail_view.DetailView( self._bridge) contentSplitter.addWidget(self._detailView) # Location self._locationField = QtWidgets.QLineEdit() layout.addWidget(self._locationField) self._locationOptions = QtWidgets.QFrame() layout.addWidget(self._locationOptions) locationOptionsLayout = QtWidgets.QHBoxLayout() locationOptionsLayout.setContentsMargins(0, 0, 0, 0) self._locationOptions.setLayout(locationOptionsLayout) self._assetNameField = QtWidgets.QLineEdit() self._assetNameField.setEnabled(False) locationOptionsLayout.addWidget(self._assetNameField) self._overrideNameHintOption = QtWidgets.QCheckBox( 'Specify Asset Name') locationOptionsLayout.addWidget(self._overrideNameHintOption) def _postBuild(self): '''Perform post build operations.''' # Event handling self._createButton.clicked.connect(self._showCreateDialog) self._navigateUpButton.clicked.connect(self.navigateUp) self._bookmarksView.cellClicked.connect(self._onBookmarkCellClicked) self._navigator.cellClicked.connect(self._onNavigatorCellClicked) self._versionsNavigator.cellClicked.connect(self._onVersionCellClicked) self._componentsNavigator.cellClicked.connect( self._onComponentCellClicked) self._overrideNameHintOption.toggled.connect( self._assetNameField.setEnabled) self.clickedIdSignal.connect(self._onSelection) # Customise views using context and specification. # isGrouping = self._specification.isOfType( FnAssetAPI.specifications.GroupingSpecification) isShot = self._specification.isOfType( FnAssetAPI.specifications.ShotSpecification) isFile = self._specification.isOfType( FnAssetAPI.specifications.FileSpecification) isImage = self._specification.isOfType( FnAssetAPI.specifications.ImageSpecification) # Location options. displayLocationOptions = True if self._context.isForMultiple(): displayLocationOptions = False if not isGrouping: if self._context.isForRead(): self._showTasks = False displayLocationOptions = False else: if (self._context and self._context.locale and self._context.locale.isOfType('ftrack.publish')): displayLocationOptions = False if isShot: displayLocationOptions = False if self._context.isForWrite() and not isFile: displayLocationOptions = False if not displayLocationOptions: self._locationOptions.hide() # Filters. if self._specification.getType() == 'file.nukescript': self._componentNamesFilter = ['nukescript'] elif self._specification.getType() == 'file.hrox': self._componentNamesFilter = ['hieroproject'] if isImage: self._metaFilters = ['img_main'] # Navigators. if self._context.isForWrite(): self._showAssetVersions = False if isShot: if self._context.isForWrite(): self._shotsEnabled = False self._showTasks = False self._showAssets = False elif self._context.isForRead(): self._shotsEnabled = False self._showTasks = False self._showAssets = False elif isFile or isImage or self._specification.isOfType('file.hrox'): if self._context.access in ['write']: self._showTasks = True self._showAssets = True elif self._context.access in ['writeMultiple']: self._showTasks = True self._showAssets = False if (self._context and self._context.locale and self._context.locale.isOfType('ftrack.publish')): self._showAssets = False # Load bookmarks. self._populateBookmarks() # Set start location. referenceHint = self._specification.getField('referenceHint') if referenceHint: self.setStartLocation(referenceHint) elif 'FTRACK_TASKID' in os.environ: task = ftrack.Task(os.environ['FTRACK_TASKID']) self.setStartLocation(task.getEntityRef()) def setStartLocation(self, startTargetHref): '''Set initial location to *startTargetHref*.''' try: entity = self._bridge.getEntityById(startTargetHref) except FnAssetAPI.exceptions.InvalidEntityReference: FnAssetAPI.logging.debug(traceback.format_exc()) return # Determine appropriate reference. targetHref = None if isinstance(entity, ftrack.Component): ancestor = entity.getVersion().getAsset().getParent() targetHref = ancestor.getEntityRef() elif isinstance(entity, ftrack.Task): if entity.getObjectType() == 'Task': targetHref = entity.getParent().getEntityRef() else: targetHref = entity.getEntityRef() if targetHref: self._updateNavigator(targetHref) entity = self._bridge.getEntityById(targetHref) entityType = self._bridge.getEntityType(targetHref) self._selectionValid = self._isValid(entityType, entity) self.clickedIdSignal.emit(self._currentBrowsingId) def navigateUp(self): '''Navigate up one level from current location.''' entity = self._bridge.getEntityById(self._currentBrowsingId) if hasattr(entity, 'getParent'): parent = entity.getParent() self._updateNavigator(parent.getEntityRef()) def _onSelection(self, selection): '''Process *selection* and store store result as current selection. Emit selection changed signal. ''' if self._context.isForRead(): entity = self._bridge.getEntityById(selection) # If current selection refers to an asset or version then store # appropriate component reference as selection. For an asset, first # try to retrieve the latest version. if (isinstance(entity, ftrack.Asset) or isinstance(entity, ftrack.AssetVersion)): if isinstance(entity, ftrack.Asset): version = entity.getVersions()[-1] elif isinstance(entity, ftrack.AssetVersion): version = entity componentName = None if self._specification.isOfType('file.nukescript'): componentName = 'nukescript' elif self._specification.isOfType('file.hrox'): componentName = 'hieroproject' component = None if componentName is not None: component = version.getComponent(name=componentName) else: components = version.getComponents() if components and len(components) == 1: component = components[0] else: self._selectionValid = False if component: selection = component.getEntityRef() self._selection = [selection] self.selectionChanged.emit(self._selection) def getSelection(self): '''Return list of currently selected entity references.''' assetName = None if self._overrideNameHintOption.checkState(): assetNameText = self._assetNameField.text() if assetNameText != '': assetName = assetNameText if assetName: for index in range(len(self._selection)): self._selection[index] = (self._selection[index] + '&assetName=' + assetName) return self._selection def setSelection(self, selection): '''Set selection from list of entity references in *selection*. .. note:: Only supports setting Component selection in write mode, but this should be the only case. ''' if len(selection) > 0 and selection[0] != '': if len(selection) > 1: FnAssetAPI.logging.debug('Multi selection not yet supported.') selection = selection[0] if self._context.access in ['write', 'writeMultiple']: try: entity = self._bridge.getEntityById(selection) if isinstance(entity, ftrack.Component): asset = entity.getVersion().getAsset() assetId = asset.getEntityRef() rowCount = self._navigator.rowCount() for index in range(rowCount): item = self._navigator.item(index, 0) targetReference = item.data(QtCore.Qt.UserRole) if targetReference == assetId: self._updateNavigator(targetReference) self._navigator.setCurrentCell(index, 0) except FnAssetAPI.exceptions.InvalidEntityReference: FnAssetAPI.logging.debug(traceback.format_exc()) else: FnAssetAPI.logging.debug('Could not set selection.') def selectionValid(self): '''Return whether selection is valid for context and specification.''' return self._selectionValid def _populateBookmarks(self): '''Populate bookmarks view.''' # TODO: Extract bookmarks to separate widget. # For now just display non-editable list of projects from ftrack. projects = ftrack.getProjects() self._bookmarksView.setRowCount(len(projects)) # Sort projects by display name. projects = sorted(projects, key=lambda project: project.getName()) for index, project in enumerate(projects): item = QtWidgets.QTableWidgetItem(project.getName()) item.setData(QtCore.Qt.UserRole, project.getEntityRef()) icon = QtGui.QIcon() icon.addPixmap(QtGui.QPixmap(':icon-home'), QtGui.QIcon.Normal, QtGui.QIcon.Off) item.setIcon(icon) self._bookmarksView.setItem(index, 0, item) def _onBookmarkCellClicked(self, x, y): '''Handle click on bookmarks view at *x* and *y* coordinates.''' item = self._bookmarksView.item(x, y) self._updateNavigator(targetReference=item.data(QtCore.Qt.UserRole)) def _onNavigatorCellClicked(self, x, y): '''Handle click on navigator view at *x* and *y* coordinates.''' item = self._navigator.item(x, y) flags = item.flags() if not (flags & QtCore.Qt.ItemIsEnabled): return self._updateNavigator(targetReference=item.data(QtCore.Qt.UserRole)) def _onVersionCellClicked(self, x, y): '''Handle click on versions view at *x* and *y* coordinates.''' item = self._versionsNavigator.item(x, y) self._updateNavigator(targetReference=item.data(QtCore.Qt.UserRole)) def _onComponentCellClicked(self, x, y): '''Handle click on components view at *x* and *y* coordinates.''' item = self._componentsNavigator.item(x, y) self._updateNavigator(targetReference=item.data(QtCore.Qt.UserRole)) def _updateNavigator(self, targetReference): '''Update navigator to display entries under *targetReference*.''' entity = self._bridge.getEntityById(targetReference) # Display path to entity. self._locationField.setText( self._bridge.getEntityPath(targetReference, slash=True, includeAssettype=True)) # Update selection. self._currentBrowsingId = targetReference entityType = self._bridge.getEntityType(targetReference) self._selectionValid = self._isValid(entityType, entity) self.clickedIdSignal.emit(self._currentBrowsingId) # Update details view. self._detailView.updateDetails(self._currentBrowsingId) # Update other navigators. if hasattr(entity, 'getVersions'): if self._showAssetVersions == True: self._updateVersionsNavigator(entity) self._versionsNavigator.show() return elif hasattr(entity, 'getComponents'): components = entity.getComponents() importableComponents = [] self._componentsNavigator.hide() for component in components: if self._componentNamesFilter: if not component in self._componentNamesFilter: continue if self._metaFilters: metaData = component.getMeta() # img_main to be replaced by settable option for metaFilter in self._metaFilters: if metaFilter in metaData: importableComponents.append(component) else: importableComponents.append(component) if len(importableComponents) > 1: self._updateComponentsNavigator(importableComponents) self._componentsNavigator.show() elif len(importableComponents) == 1: self._updateNavigator(importableComponents[0].getEntityRef()) return elif entityType == 'Task': return elif isinstance(entity, ftrack.Component): return else: self._versionsNavigator.hide() self._componentsNavigator.hide() # Update main navigator view. self._navigator.setRowCount(0) self._versionsNavigator.setRowCount(0) self._navigator.setHorizontalHeaderLabels( [self._bridge.getEntityName(targetReference)]) children = [] tasks = [] assets = [] if isinstance(entity, ftrack.Project) or isinstance( entity, ftrack.Task): children = entity.getChildren() if hasattr(entity, 'getTasks') and self._showTasks == True: tasks = entity.getTasks() if hasattr(entity, 'getAssets'): if (not isinstance(entity, ftrack.Project) and entity.getObjectType() in ['Shot', 'Sequence'] and self._showAssets == True): if self._componentNamesFilter: assets = entity.getAssets( componentNames=self._componentNamesFilter) else: assets = entity.getAssets() entities = children + tasks + assets entities = sorted(entities, key=lambda entity: self._bridge.getEntityName( entity.getEntityRef()).lower()) self._navigator.setRowCount(len(entities)) for index, entity in enumerate(entities): makeBold = None makeItalic = None makeDisabled = None if (isinstance(entity, ftrack.Task) and entity.getObjectType() in ['Shot', 'Sequence']): text = self._bridge.getEntityName(entity.getEntityRef()) + '/' makeBold = True elif (isinstance(entity, ftrack.Task) and entity.getObjectType() in ['Task']): text = self._bridge.getEntityName(entity.getEntityRef()) makeItalic = True if isinstance(entity.getParent(), ftrack.Project): makeDisabled = True elif isinstance(entity, ftrack.Asset): text = (self._bridge.getEntityName(entity.getEntityRef()) + '.' + entity.getType().getShort()) else: text = self._bridge.getEntityName(entity.getEntityRef()) if entityType == 'Sequence' and self._shotsEnabled == False: makeDisabled = True item = QtWidgets.QTableWidgetItem(text) item.setData(QtCore.Qt.UserRole, entity.getEntityRef()) icon = self._getIcon(entity) if icon: item.setIcon(icon) if makeDisabled: item.setFlags(QtCore.Qt.NoItemFlags) self._navigator.setItem(index, 0, item) if makeBold: font = QtGui.QFont() font.setBold(True) self._navigator.item(index, 0).setFont(font) elif makeItalic: font = QtGui.QFont() font.setItalic(True) self._navigator.item(index, 0).setFont(font) def _updateVersionsNavigator(self, asset): '''Update versions navigator to display versions for *asset*.''' self._versionsNavigator.setRowCount(0) if self._componentNamesFilter: versions = asset.getVersions( componentNames=self._componentNamesFilter) else: versions = asset.getVersions() self._versionsNavigator.setRowCount(len(versions)) self._detailView.updateDetails(asset.getEntityRef()) for index, version in enumerate(reversed(versions)): text = self._bridge.getEntityName(version.getEntityRef()) item = QtWidgets.QTableWidgetItem(text) item.setData(QtCore.Qt.UserRole, version.getEntityRef()) self._versionsNavigator.setItem(index, 0, item) self._versionsNavigator.setCurrentCell(0, 0) self._updateNavigator(versions[-1].getEntityRef()) def _updateComponentsNavigator(self, importableComponents=[]): '''Update versions navigator to display *importableComponents*.''' self._componentsNavigator.setRowCount(0) self._componentsNavigator.setRowCount(len(importableComponents)) for index, component in enumerate(importableComponents): text = self._bridge.getEntityName(component.getEntityRef()) item = QtWidgets.QTableWidgetItem(text) item.setData(QtCore.Qt.UserRole, component.getEntityRef()) self._componentsNavigator.setItem(index, 0, item) self._componentsNavigator.setCurrentCell(0, 0) self._updateNavigator(importableComponents[0].getEntityRef()) def _getIcon(self, entity): '''Retrieve appropriate icon for *entity*.''' iconPath = None if isinstance(entity, ftrack.Project): iconPath = ':icon-home' elif isinstance(entity, ftrack.Task): objectType = entity.getObjectType() if objectType == 'Sequence': iconPath = ':icon-folder_open' elif objectType == 'Shot': iconPath = ':icon-movie' elif objectType == 'Task': iconPath = ':icon-signup' elif objectType == 'Asset Build': iconPath = ':icon-box' elif objectType is None: # Check for asset build id until getObjectType fixed if (entity.get('object_typeid') == 'ab77c654-df17-11e2-b2f3-20c9d0831e59'): iconPath = ':icon-box' elif isinstance(entity, ftrack.Asset): iconPath = ':icon-layers' if iconPath: icon = QtGui.QIcon() icon.addPixmap(QtGui.QPixmap(iconPath), QtGui.QIcon.Normal, QtGui.QIcon.Off) return icon return None def _isValid(self, entityType, entity): '''Return whether selection is valid for specification and context.''' validSelection = False isImage = isinstance(self._specification, FnAssetAPI.specifications.ImageSpecification) isFile = isinstance(self._specification, FnAssetAPI.specifications.FileSpecification) isShot = isinstance(self._specification, FnAssetAPI.specifications.ShotSpecification) if isShot: if self._context.access in ['write', 'writeMultiple']: if entityType in ['Sequence']: validSelection = True elif self._context.access in ['read', 'readMultiple']: if entityType in ['Sequence']: validSelection = True elif isImage or isFile or self._specification.getType() == 'file.hrox': if entityType in ['Task']: if self._context.access in ['write', 'writeMultiple']: parentEntity = entity.getParent().get('entityType') if parentEntity != 'show': validSelection = True elif entityType in ['Sequence', 'Shot']: if self._context.access in ['write', 'writeMultiple']: validSelection = True elif isinstance(entity, ftrack.Asset): if self._context.access in ['write']: validSelection = True if self._context.access in ['read', 'readMultiple']: if (isinstance(entity, ftrack.Asset) or isinstance(entity, ftrack.AssetVersion) or isinstance(entity, ftrack.Component)): validSelection = True if (self._context and self._context.locale and self._context.locale.getType().startswith( 'ftrack.publish')): if entityType != 'Task': validSelection = False else: validSelection = True return validSelection def _showCreateDialog(self): '''Display create dialog and update navigator with result.''' dialog = CreateDialog(self._bridge, self, currentHref=self._currentBrowsingId) result = dialog.exec_() if result == 1: entity = self._bridge.getEntityById(dialog.currentHref) entityType = dialog.getEntityType() entityName = dialog.getEntityName() if entityType == 'seq': entity.createSequence(name=entityName) elif entityType == 'shot': entity.createShot(name=entityName) elif entityType == 'task': taskType = ftrack.TaskType(dialog.getTaskType()) entity.createTask(name=entityName, taskType=taskType) self._updateNavigator(entity.getEntityRef())
class WorkerSignal(QtCore.QObject): finished = QtCore.Signal(bool, str)
class CreateShotsWidget(QtGui.QWidget): """ A dialog to present the user with options pertaining to creating shots in an asset manager, based on a number of selected track items. Clips from these TrackItems can also be published to the shots, or, if shared with multiple TrackItems, they can be published to an alternate location. @specUsage FnAssetAPI.specifications.ImageSpecification @specUsage FnAssetAPI.specifications.ShotSpecification """ optionsChanged = QtCore.Signal() ## @name Constants for Option Keys ## @{ kTargetEntityRef = 'targetEntityRef' kManagerOptionsShot = 'managerOptionsShot' kSetShotTimings = 'setShotTimings' ## @} ## @todo We currently require a context at initialisation time, as we need to # create Manager UI elements. Ideally, we'd let this be set later, and # re-create any context-dependent UI as necessary. def __init__(self, context, parent=None, options=None): super(CreateShotsWidget, self).__init__(parent=parent) self.__trackItems = [] self.__shotItems = [] self.__updatingOptions = False self.__options = { self.kTargetEntityRef : '', self.kSetShotTimings : True } self._session = FnAssetAPI.ui.UISessionManager.currentSession() self._context = context # Note, this is a reference self._context.access = context.kWriteMultiple # We'll need to keep track of some lookups to avoid excess traffic self._parentEntity = None self._newShots = [] self._existingShots = [] self._conflictingShots = [] layout = QtGui.QVBoxLayout() self.setLayout(layout) self._buildUI(layout) self._connectUI() if options: self.setOptions(options) else: self._readOptions() def _buildUI(self, layout): self._managerOptionsShot = None # Add the 'Create Under' section, to choose the parent entity that should # receive the new shots. specification = ShotSpecification() pickerCls = self._session.getManagerWidget( FnAssetAPI.ui.constants.kInlinePickerWidgetId, instantiate=False) # Parent Picker parentPickerLayout = QtGui.QHBoxLayout() layout.addLayout(parentPickerLayout) parentPickerLayout.addWidget(QtGui.QLabel("Create Under:")) self._shotParentPicker = pickerCls(specification, self._context) parentPickerLayout.addWidget(self._shotParentPicker) shotsWidget = self._buildShotsTab() layout.addWidget(shotsWidget) def _buildShotsTab(self): l = FnAssetAPI.l # > Shots Tab shotsWidget = QtGui.QWidget() shotsWidget.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) shotsWidgetLayout = QtGui.QVBoxLayout() shotsWidgetLayout.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop) shotsWidget.setLayout(shotsWidgetLayout) self._tickIcon = QtGui.QIcon("icons:TagGood.png") self._actionIcon = QtGui.QIcon("icons:Add.png") self._shotsList = AdvancedHieroItemSpreadsheet() self._shotsList.setAlternatingRowColors(True) self._shotsList.setIcons(self._actionIcon, self._tickIcon) self._shotsList.setHiddenProperties(("nameHint",)) self._shotsList.setForcedProperties( ("startFrame", "endFrame", "inFrame", "outFrame")) self._shotsList.setStatusText(l("New {shot}"), l("Existing {shot}")) self._shotsList.setDisabledCallback(self.__shotItemIsDisabled) shotsWidgetLayout.addWidget(self._shotsList) # See if we have any options from the manager shotSpec = ShotSpecification() self._managerOptionsShot = self._session.getManagerWidget( FnAssetAPI.ui.constants.kRegistrationManagerOptionsWidgetId, throw=False, args=(shotSpec, self._context)) if self._managerOptionsShot: shotsWidgetLayout.addWidget(self._managerOptionsShot) shotsWidgetLayout.addSpacing(10) # Length Options self._shotLengthGBox = QtGui.QGroupBox("Set Shot Timings from Hiero") self._shotLengthGBox.setCheckable(True) self._shotLengthGBox.setChecked(False) slGbLayout = QtGui.QHBoxLayout() self._shotLengthGBox.setLayout(slGbLayout) self._shotLengthOptionsWidget = TrackItemTimingOptionsWidget() slGbLayout.addWidget(self._shotLengthOptionsWidget) slGbLayout.addStretch() shotsWidgetLayout.addWidget(self._shotLengthGBox) return shotsWidget def _connectUI(self): self._shotParentPicker.selectionChanged.connect( lambda v: self.__updateOption(self.kTargetEntityRef, v[0] if v else '')) self._shotLengthOptionsWidget.optionsChanged.connect(self.__timingOptionsChanged) self._shotLengthGBox.toggled.connect(self.__timingOptionsChanged) ## @todo Do we need to connect up the manager options widget too? def __timingOptionsChanged(self): if self.__updatingOptions: return self.__options.update(self._shotLengthOptionsWidget.getOptions()) self.__options[self.kSetShotTimings] = bool(self._shotLengthGBox.isChecked()) # Force a full refresh self.__shotItems = [] self._parentEntity = None self.refresh() def __updateOption(self, option, value, refresh=True, clearParent=False, clearItems=False): if self.__updatingOptions: return self.__options[option] = value if refresh: if clearParent: self._parentEntity = None if clearItems: self.__shotItems = [] self.refresh() def _readOptions(self): self.__updatingOptions = True # Update main picked value targetEntityRef = self.__options.get(self.kTargetEntityRef, '') try: self._shotParentPicker.setSelectionSingle(targetEntityRef) except Exception as e: FnAssetAPI.logging.debug(e) # Manager Options managerOptionsShot = self.__options.get(self.kManagerOptionsShot, None) if managerOptionsShot and self._managerOptionsShot: self._managerOptionsShot.setOptions(managerOptionsShot) # Shot length options are read directly in the widget setTimings = self.__options.get(self.kSetShotTimings, True) self._shotLengthGBox.setChecked(setTimings) self.__updatingOptions = False def sizeHint(self): return QtCore.QSize(600, 400) def setTrackItems(self, trackItems): self.__trackItems = [] self.__trackItems = trackItems self.__shotItems = [] # Clear cache self.refresh() def getTrackItems(self): return self.__trackItems def getOptions(self): options = dict(self.__options) managerOptionsShot = {} if self._managerOptionsShot: managerOptionsShot = self._managerOptionsShot.getOptions() options[self.kManagerOptionsShot] = managerOptionsShot options.update(self._shotLengthOptionsWidget.getOptions()) return options def setOptions(self, options): self.__options.update(options) self._readOptions() self._shotLengthOptionsWidget.setOptions(options) if self._managerOptionsShot: managerOptions = options.get(self.kManagerOptionsShot, {}) self._managerOptionsShot.setOptions(managerOptions) self.refresh() def __shotItemIsDisabled(self, item): return item in self._existingShots # This refreshes the UI based on its current state, it doesn't re-read the # options dict directly. If required, call _readOptions() first def refresh(self): ## @todo Call managementPolicy on an image sequence to the chosen sequence ## in case, say someone selected a project as the destination and the ams ## can't handle image sequences at the project level... session = FnAssetAPI.SessionManager.currentSession() if not session: raise RuntimeError("No Asset Management session available") if not self.__shotItems: self.__shotItems = cmdUtils.object.trackItemsToShotItems(self.__trackItems, self.getOptions(), coalesseByName=True) # Update Shot Creation parentRef = self.__options.get(self.kTargetEntityRef, None) # Ensure we don't waste time repeatedly looking under the same parent if not self._parentEntity or self._parentEntity.reference != parentRef: self._parentEntity = session.getEntity(parentRef, mustExist=True, throw=False) if self._parentEntity: newShots, existingShots, unused = cmdUtils.shot.analyzeHieroShotItems( self.__shotItems, self._parentEntity, self._context, adopt=True, checkForConflicts=False) self._newShots = newShots self._existingShots = existingShots self._shotsList.setItems(self.__shotItems) self._shotsList.setEnabled(bool(self._parentEntity)) self.optionsChanged.emit() def __refreshClips(self): self.__clipItems, self.__sharedClipItems = \ cmdUtils.shot.analyzeHeiroShotItemClips(self.__shotItems, asItems=True) if self.__options.get(self.kIgnorePublishedClips, True): itemFilter = lambda i : not i.getEntity() self.__clipItems = filter(itemFilter, self.__clipItems) self.__sharedClipItems = filter(itemFilter, self.__sharedClipItems) ## @todo We probably shouldn't be duplicating this work, and it should ## come out of the utils functions or something... Its here for now as it ## minimises redundant work done to create the UI. That probably means ## the utility functions arent so well structured now we've added so much ## more into this dialog ;) customClipName = None if self.__options.get(self.kClipsUseCustomName, False): customClipName = self.__options.get(self.kCustomClipName, None) if customClipName: for c in self.__clipItems: c.nameHint = customClipName # Update Clip publishing self._clipsGroup.setVisible(bool(self.__clipItems)) self._clipsList.setItems(self.__clipItems) self._sharedClipsGroup.setVisible(bool(self.__sharedClipItems)) self._sharedClipsList.setItems(self.__sharedClipItems) haveClips = bool(self.__sharedClipItems) or bool(self.__clipItems) self._noMedia.setVisible(not haveClips) def getButtonState(self): create = bool(self._newShots) and bool(self.__options[self.kTargetEntityRef]) return "Create", create
class AssetsTree(QtGui.QTreeView): asset_version_selected = QtCore.Signal(object) resized = QtCore.Signal() create_asset = QtCore.Signal(object) def __init__(self, parent=None, show_thumbnail=True): super(AssetsTree, self).__init__(parent) css_list = """ /*QTreeView { background: #666; margin: 0px; padding-top: 3px; border-top-right-radius: 0px; border-top-left-radius: 0px; border-bottom-right-radius: 4px; border-bottom-left-radius: 4px; } QTreeView::item { background: none; } QTreeView::item:selected { background: #dde4cb; } QTreeView::branch:has-siblings:!adjoins-item {background: #555;} QTreeView::branch:has-siblings:adjoins-item {background: #555;} QTreeView::branch:!has-children:!has-siblings:adjoins-item {background: #555;} QTreeView::branch:has-children:!has-siblings:closed {background: #555;} QScrollBar { border: 0; border-radius: 6px; background-color: #333; margin: 0px;} QScrollBar::handle {background: #222; border: 0px solid #111;} QScrollBar::sub-line, QScrollBar::add-line {height: 0px; width: 0px;}*/ """ self.setStyleSheet(css_list) minimum_width = 540 self.setMinimumWidth(minimum_width) self._delegate = TreeDelegateStyle(self, minimum_width, show_thumbnail) self.setItemDelegate(self._delegate) self._model = QtGui.QStandardItemModel(self) self.proxy_model = AssetSortFilter(self, self._model) self.proxy_model.setDynamicSortFilter(True) self.setModel(self.proxy_model) self.header().setVisible(False) self.setRootIsDecorated(False) self.setExpandsOnDoubleClick(False) self.setIndentation(0) self.setMouseTracking(True) self.setAnimated(True) self._add_versions_nb = 3 self._colors_assets = dict(default="#282828") self._assets = dict() @property def current_version(self): index = self.currentIndex() index_source = self.proxy_model.mapToSource(index) parent_item = self._model.itemFromIndex(index_source) if parent_item is None: return elif parent_item.is_button(): return return parent_item.version def startDrag(self, supportedActions): drag = QtGui.QDrag(self) version = self.current_version if version is not None: version_id = version.id id_ftrack = "FTRACK_DROP_ACTION" asset_name = version.asset_io().__name__ mime_data = QtCore.QMimeData() mime_data.setData("text/plain", ":".join([id_ftrack, asset_name, version_id])) pixmap = QtGui.QPixmap(':ftrack/image/integration/drag') drag.setPixmap(pixmap) drag.setMimeData(mime_data) drag.exec_() def drop_asset_to_dag(self, mime_type, text): return True def resizeEvent(self, event): self.resized.emit() super(AssetsTree, self).resizeEvent(event) def asset_color(self, asset_type): if asset_type in self._colors_assets.keys(): return self._colors_assets[asset_type] return self._colors_assets["default"] def add_assets_colors(self, color_dict): self._colors_assets.update(color_dict) def set_selection_mode(self, bool_value): mode = QtGui.QAbstractItemView.SingleSelection if bool_value else QtGui.QAbstractItemView.NoSelection self.setSelectionMode(mode) def set_draggable(self, bool_value): self.setDragEnabled(True) def import_assets(self, assets, asset_types=None, filter=None): self.initiate() # Thread that... self._get_versions(assets) self.set_assets() self.update_display(asset_types, filter) def _get_versions(self, taskId): self._assets.clear() task = ftrack.Task(taskId) assets = task.getAssets(assetTypes=['comp']) if not assets: return asset_type = 'comp' for asset in assets: for asset_version in asset.getVersions(): if asset_type not in self._assets.keys(): self._assets['comp'] = [] self._assets['comp'].append(asset_version) def set_assets(self): for asset_type, scene_versions in self._assets.iteritems(): for scene_version in scene_versions: self.create_item(scene_version, asset_type) if self.selectionMode() != QtGui.QAbstractItemView.NoSelection: self.select_first_item() self.setCursor(QtCore.Qt.ArrowCursor) def update_display(self, asset_types=None, filter=None): self.proxy_model.set_asset_types(asset_types) reg_exp = QtCore.QRegExp(filter, QtCore.Qt.CaseInsensitive, QtCore.QRegExp.RegExp) self.proxy_model.setFilterRegExp(reg_exp) def initiate(self): self._model.clear() def create_item(self, asset_version, asset_role): try: item = AssetItem(self, asset_version, asset_role) except: traceback.print_exc(file=sys.stdout) return item.signal.asset_regenerated.connect(self._item_regenerated) self.create_asset.emit(item) def select_first_item(self): first_index = self.proxy_model.index(0, 0) if first_index.isValid(): index_source = self.proxy_model.mapToSource(first_index) parent_item = self._model.itemFromIndex(index_source) self.setCurrentIndex(first_index) self.asset_version_selected.emit(parent_item.version) else: self.asset_version_selected.emit(None) def selectionChanged(self, item_selected, item_unselected): if len(item_selected.indexes()) > 0: index = item_selected.indexes()[0] index_source = self.proxy_model.mapToSource(index) parent_item = self._model.itemFromIndex(index_source) if parent_item is None: return elif parent_item.is_button(): return self.asset_version_selected.emit(parent_item.version) def toggle_show_versions(self, index): if self.isExpanded(index): self.collapse(index) else: if not index.child(0, 0).isValid(): self._insert_versions(index) self.expand(index) def toggle_show_more_versions(self, btn_index): btn_index_source = self.proxy_model.mapToSource(btn_index) btn_item = self._model.itemFromIndex(btn_index_source) last_asset_version = btn_item.version parent_index = btn_item.parent_index index_source = self.proxy_model.mapToSource(parent_index) parent_item = self._model.itemFromIndex(index_source) parent_item.removeRow(btn_index.row()) self._insert_versions(parent_index, last_asset_version, btn_index.row()) def _insert_versions(self, parent_index, asset_version=None, start_number=0): index_source = self.proxy_model.mapToSource(parent_index) parent_item = self._model.itemFromIndex(index_source) if asset_version == None: asset_version = parent_item.version i = start_number while (i < start_number + self._add_versions_nb): if asset_version.get('version') == 1: break asset_version = asset_version.previous_version version_item = AssetItem(self, asset_version, parent_item.asset_type()) version_item.signal.asset_regenerated.connect( self._item_regenerated) parent_item.setChild(i, 0, version_item) i += 1 if asset_version.version_number != 1: btn_item = ButtonItem(self, asset_version, parent_item.asset_type(), parent_index) parent_item.setChild(i, 0, btn_item) def _item_regenerated(self): item = self.sender() proxy_item = self.proxy_model.mapFromSource(item.parent.index()) if self.currentIndex() == proxy_item: self.asset_version_selected.emit(item.parent.version)
class ItemSignal(QtCore.QObject): asset_regenerated = QtCore.Signal() def __init__(self, parent): super(ItemSignal, self).__init__() self.parent = parent
class CommentWidget(QtWidgets.QFrame): changed = QtCore.Signal() def __init__(self, parent=None): super(CommentWidget, self).__init__(parent) css_comment_frame = """ QFrame { 4px; background: #222; color: #FFF; } QLabel { padding: 0px; background: none; } QTextEdit { border: 3px solid #252525; background: #444; } QScrollBar { border: 0; background-color: #333; margin: 1px;} QScrollBar::handle {background: #222; border: 1px solid #111;} QScrollBar::sub-line, QScrollBar::add-line {height: 0px; width: 0px;} """ self.setMinimumWidth(600) # self.setMaximumHeight(100) self.setStyleSheet(css_comment_frame) layout = QtWidgets.QFormLayout(self) layout.setSpacing(10) layout.setContentsMargins(0, 0, 0, 0) label = QtWidgets.QLabel("Comment", self) self._edit_field = QtWidgets.QTextEdit(self) self._edit_field.setObjectName('ftrack-edit-field') self._edit_field.textChanged.connect(self._validate_comment) layout.setWidget(0, QtWidgets.QFormLayout.LabelRole, label) layout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self._edit_field) @property def text(self): return self._edit_field.toPlainText() def set_text(self, msg): self._edit_field.blockSignals(True) self._edit_field.setPlainText(msg) self._edit_field.blockSignals(False) def setFocus(self): self._edit_field.setFocus() def _validate_comment(self): self._edit_field.blockSignals(True) text = self._edit_field.toPlainText() good_text = "" tmp_cursor = self._edit_field.textCursor() position_cursor = tmp_cursor.position() pattern_NL = re.compile('\n') pattern_Space = re.compile('\s') pattern_BadChar = re.compile('[^a-zA-Z0-9\!\?\'\"@£$\,\(\)\&.-]') for letter in text: if re.search(pattern_NL, letter): good_text += letter elif re.search(pattern_Space, letter): good_text += ' ' elif re.search(pattern_BadChar, letter) is None: good_text += letter self._edit_field.setPlainText(good_text) length_diff = len(text) - len(good_text) tmp_cursor.setPosition(position_cursor - length_diff) self._edit_field.setTextCursor(tmp_cursor) self._edit_field.blockSignals(False) self.changed.emit()
class SnapshotsEditButtons(QtGui.QWidget): refresh_toggled = QtCore.Signal() zoom_level_toggled = QtCore.Signal(int) handscroll_mode_toggled = QtCore.Signal(bool) drawing_mode_toggled = QtCore.Signal(bool) color_modified = QtCore.Signal(str) eraser_toggled = QtCore.Signal() def __init__(self, drawing_mode, handscroll_mode, parent=None): super(SnapshotsEditButtons, self).__init__(parent) self._drawing_mode = drawing_mode self._handscroll_mode = handscroll_mode self._icones = {} self._icones = { 'fitscreen': ':ftrack/image/integration/fit-screen', 'move': ':ftrack/image/integration/hand-scroll', 'draw': ':ftrack/image/integration/pencil', 'eraser': ':ftrack/image/integration/eraser', } self.setupUI(parent) def setupUI(self, parent): self._refresh = QtWidgets.QToolButton(parent) self._refresh.setMaximumSize(QtCore.QSize(45, 15)) button_css_refresh = """ QToolButton{background:transparent; border:none; color: rgba(255,255,255,80);} QToolButton:hover{color: rgba(255,255,255,200);} QToolButton:pressed{color: rgba(255,255,255,255);} """ self._refresh.setStyleSheet(button_css_refresh) self._refresh.move(parent.width() - self._refresh.width() - 10, 5) self._refresh.setText("refresh") self._refresh.clicked.connect(self.refresh) button_css = """ QToolButton{ background:rgba(255,255,255,50); border:none; color: rgba(255,255,255,80); border-radius: 5px; } QToolButton:hover{ background:rgba(255,255,255,120); color: rgba(255,255,255,200); } QToolButton:pressed{ background:rgba(255,255,255,80); color: rgba(255,255,255,255); } """ # Colors buttons left_gap = 10 top_padding = 80 self._color_white = QtWidgets.QToolButton(parent) self._color_white.setMaximumSize(QtCore.QSize(20, 20)) self._color_white.move(left_gap, parent.height() - top_padding) self._color_white.clicked.connect(self._toggle_color) left_gap += self._color_white.width() + 10 self._color_black = QtWidgets.QToolButton(parent) self._color_black.setMaximumSize(QtCore.QSize(20, 20)) self._color_black.move(left_gap, parent.height() - top_padding) self._color_black.clicked.connect(self._toggle_color) left_gap += self._color_black.width() + 10 self._color_red = QtWidgets.QToolButton(parent) self._color_red.setMaximumSize(QtCore.QSize(20, 20)) self._color_red.move(left_gap, parent.height() - top_padding) self._color_red.clicked.connect(self._toggle_color) left_gap += self._color_red.width() + 10 self._color_green = QtWidgets.QToolButton(parent) self._color_green.setMaximumSize(QtCore.QSize(20, 20)) self._color_green.move(left_gap, parent.height() - top_padding) self._color_green.clicked.connect(self._toggle_color) left_gap += self._color_green.width() + 10 self._color_blue = QtWidgets.QToolButton(parent) self._color_blue.setMaximumSize(QtCore.QSize(20, 20)) self._color_blue.move(left_gap, parent.height() - top_padding) self._color_blue.clicked.connect(self._toggle_color) left_gap += self._color_blue.width() + 10 self._color_yellow = QtWidgets.QToolButton(parent) self._color_yellow.setMaximumSize(QtCore.QSize(20, 20)) self._color_yellow.move(left_gap, parent.height() - top_padding) self._color_yellow.clicked.connect(self._toggle_color) self._activate_color_button("white", False) self._activate_color_button("black", False) self._activate_color_button("red", True) # default self._activate_color_button("green", False) self._activate_color_button("blue", False) self._activate_color_button("yellow", False) # Edit buttons left_gap = 10 self._fit_screen = QtWidgets.QToolButton(parent) self._fit_screen.setMaximumSize(QtCore.QSize(20, 20)) self._fit_screen.setStyleSheet(button_css) self._fit_screen.move(left_gap, 5) self._fit_screen.setToolButtonStyle(QtCore.Qt.ToolButtonIconOnly) self._fit_screen.clicked.connect(self.fit_screen) self.activate_button(self._fit_screen, "fitscreen") left_gap += self._fit_screen.width() + 10 self._zoom_out = QtWidgets.QToolButton(parent) self._zoom_out.setMaximumSize(QtCore.QSize(20, 20)) self._zoom_out.setStyleSheet(button_css) self._zoom_out.move(left_gap, 5) self._zoom_out.setText("-") self._zoom_out.clicked.connect(self.zoom_out) left_gap += self._zoom_out.width() + 10 self._zoom_in = QtWidgets.QToolButton(parent) self._zoom_in.setMaximumSize(QtCore.QSize(20, 20)) self._zoom_in.setStyleSheet(button_css) self._zoom_in.move(left_gap, 5) self._zoom_in.setText("+") self._zoom_in.clicked.connect(self.zoom_in) left_gap += self._zoom_in.width() + 10 self._drag = QtWidgets.QToolButton(parent) self._drag.setMaximumSize(QtCore.QSize(20, 20)) self._drag.move(left_gap, 5) self._drag.setToolButtonStyle(QtCore.Qt.ToolButtonIconOnly) self._drag.clicked.connect(self._toggle_handscroll_mode) self.set_handscroll_mode(self._handscroll_mode) left_gap += self._drag.width() + 10 self._pencil = QtWidgets.QToolButton(parent) self._pencil.setMaximumSize(QtCore.QSize(20, 20)) self._pencil.move(left_gap, 5) self._pencil.setToolButtonStyle(QtCore.Qt.ToolButtonIconOnly) self._pencil.clicked.connect(self._toggle_drawing_mode) self.set_drawing_mode(self._drawing_mode) left_gap += self._pencil.width() + 10 self._eraser = QtWidgets.QToolButton(parent) self._eraser.setMaximumSize(QtCore.QSize(20, 20)) self._eraser.move(left_gap, 5) self._eraser.setToolButtonStyle(QtCore.Qt.ToolButtonIconOnly) self._eraser.clicked.connect(self._toggle_eraser) self.activate_button(self._eraser, "eraser", hover_emphasize=True) def _activate_color_button(self, color, bool_value): colors_button = dict(white=self._color_white, black=self._color_black, red=self._color_red, green=self._color_green, blue=self._color_blue, yellow=self._color_yellow) btn_css = "background: %s; " % color if bool_value: btn_css += "border-radius: 5px; border: 3px solid rgb(200,200,200);" global_css = "QToolButton{%s}" % (btn_css) colors_button[color].setStyleSheet(global_css) else: btn_css += "border-radius: 5px; border: 3px solid rgb(100,100,100);" global_css = "QToolButton{%s}" % (btn_css) colors_button[color].setStyleSheet(global_css) colors_button[color].setIconSize(QtCore.QSize(18, 18)) def activate_button(self, button, icon_name=None, bool_value=False, hover_emphasize=False): if bool_value: if icon_name != None: btn_css = "background: url(%s) rgba(230,120,120,150); " % self._icones[ icon_name] else: btn_css = "background: rgba(230,120,120,150); " btn_css += "border-radius: 5px; border: none;" global_css = "QToolButton{%s}" % (btn_css) button.setStyleSheet(global_css) else: if hover_emphasize: hover_background = "rgba(230,120,120,150)" else: hover_background = "rgba(255,255,255,80)" if icon_name != None: btn_css = "background: url(%s) rgba(255,255,255,50); " % self._icones[ icon_name] btn_css += "border-radius: 5px; border: none;" btn_hover_css = "background: url(%s) %s;" % ( self._icones[icon_name], hover_background) else: btn_css = "background: rgba(255,255,255,50); " btn_css += "border-radius: 5px; border: none;" btn_hover_css = "background: %s;" % hover_background global_css = "QToolButton{%s} QToolButton:hover{%s}" % ( btn_css, btn_hover_css) button.setStyleSheet(global_css) button.setIconSize(QtCore.QSize(18, 18)) def set_drawing_mode(self, bool_value): if self._handscroll_mode and bool_value: self.set_handscroll_mode(False) self.activate_button(self._pencil, "draw", bool_value) self._drawing_mode = bool_value self.drawing_mode_toggled.emit(bool_value) self.raise_color_buttons(bool_value) def set_handscroll_mode(self, bool_value): if self._drawing_mode and bool_value: self.set_drawing_mode(False) self.activate_button(self._drag, "move", bool_value) self._handscroll_mode = bool_value self.handscroll_mode_toggled.emit(bool_value) def _toggle_drawing_mode(self): self.set_drawing_mode(not self._drawing_mode) def _toggle_handscroll_mode(self): self.set_handscroll_mode(not self._handscroll_mode) def _toggle_eraser(self): self.eraser_toggled.emit() def _toggle_color(self): colors = {self._color_white: "white", self._color_black: "black", self._color_red: "red", self._color_green: "green", self._color_blue: "blue", self._color_yellow: "yellow"} self.set_color(colors[self.sender()]) def set_color(self, color_chosen): colors = ["white", "black", "red", "green", "blue", "yellow"] for color in colors: self._activate_color_button(color, color == color_chosen) self.color_modified.emit(color_chosen) def raise_color_buttons(self, bool_value): colors = [self._color_white, self._color_black, self._color_red, self._color_green, self._color_blue, self._color_yellow] for color in colors: color.setVisible(bool_value) color.raise_() def fit_screen(self): self.zoom_level_toggled.emit(0) def zoom_in(self): self.zoom_level_toggled.emit(1) def zoom_out(self): self.zoom_level_toggled.emit(-1) def refresh(self): self.refresh_toggled.emit() def update(self, display_edit_buttons): self._refresh.raise_() edit_buttons = [self._zoom_in, self._zoom_out, self._drag, self._pencil, self._eraser, self._fit_screen] if display_edit_buttons: for button in edit_buttons: button.raise_() button.setVisible(True) self.set_drawing_mode(False) self.set_handscroll_mode(False) else: for button in edit_buttons: button.setVisible(False)