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)
def __init__(self, parent=None): super(SnapshotsWidget).__init__(parent=parent) self._view_size = QtCore.QSize(600, 400) self._widget_size = QtCore.QSize(self._view_size.width() + 4, self._view_size.height()) self.setupUI() self._source = self._viewer_btn self.initiate_snapshots()
def sizeHint(self, option, index): is_btn = index.data(QtCore.Qt.UserRole) if is_btn: height = self._padding_item["top"] + self._padding_item["bottom"] height += self._space_before_btn + self._fm_desc.height() return QtCore.QSize(self._width, height) # Otherwise we draw the asset version_nb = index.data(TreeItem.version_nb_role) comment = index.data(TreeItem.comment_role) available = index.data(TreeItem.is_available_role) padding_left = option.rect.left( ) + self._padding_item["left"] + self._padding_content["left"] padding_left += self._type_indicator_width if self._show_thumbnail: thumnbail_size = self._thumnbail_size if not self._is_top_asset_version(index): thumnbail_size = self._thumnbail_child_size padding_left += thumnbail_size.width() + 10 base_height = self._padding_item["top"] + self._padding_content["top"] height = base_height + (self._fm_desc.height() * 2) height += (self._inter_text * 3) if not available: height += self._fm_desc.height() + self._inter_text if comment != None: r_comment, r_comment_text = self._bounding_boxes( comment, self._width, padding_left, 0) height += r_comment.height() else: height += self._fm_desc.height() if self._is_top_asset_version(index): height += self._fm_name.height() + self._inter_text if self._need_button(index, version_nb): height += self._space_before_btn + self._fm_desc.height() if self._show_thumbnail: if height < base_height + thumnbail_size.height(): height = base_height + thumnbail_size.height() height += self._padding_content["bottom"] + \ self._padding_item["bottom"] return QtCore.QSize(self._width, height)
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())
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))
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)
def __init__(self, parent=None, image=None): super(ThumbnailWidget, self).__init__(parent) css_thumbnail = """ background: #000; border-radius: 2px; border: 1px solid #AAA; """ self.size = QtCore.QSize(354, 236) self.setMinimumSize(self.size) self.setMaximumSize(self.size) self.setStyleSheet(css_thumbnail) self.setAlignment(QtCore.Qt.AlignCenter | QtCore.Qt.AlignTop) self.update_image(image)
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
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 _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 makeUI(self): self.mainWidget = QtWidgets.QWidget() applyTheme(self.mainWidget, 'integration') self.mainWidget.setContentsMargins(0, 0, 0, 0) self.hlayout = QtWidgets.QHBoxLayout() self.hlayout.setContentsMargins(0, 0, 0, 0) self.mainWidget.setLayout(self.hlayout) self._lineEdit = QtWidgets.QLineEdit() self._lineEdit.setText( HelpFunctions.getPath(self.current_task, slash=True)) self.hlayout.addWidget(self._lineEdit) self._browse = QtWidgets.QPushButton("Browse") self.hlayout.addWidget(self._browse) QtCore.QObject.connect(self._browse, QtCore.SIGNAL('clicked()'), self.openBrowser) return self.mainWidget
def __init__(self, name, parent): super(SnapshotsView, self).__init__(parent) self._pixmap = None self._name = name self._scene = QtGui.QGraphicsScene(parent) self.setGeometry(QtCore.QRect(0, 0, parent.width(), parent.height())) self._scale_factor = 1.15 css_frame = """ /*background: #000; border-top: 2px solid #000; border-left: 2px solid #000; border-right: 2px solid #000; border-top-left-radius: 4px; border-top-right-radius: 4px;*/ """ self.setStyleSheet(css_frame) self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) self._item = QtGui.QGraphicsPixmapItem() self._scene.addItem(self._item) self._drawings = [] image_pen_px = QtGui.QPixmap(':ftrack/image/integration/pencil') image_pen_px.setMask(image_pen_px.mask()) self._image_pen_cursor = QtGui.QCursor(image_pen_px) self._pen_color = QtCore.Qt.red self._drawing_mode = False self._pencil_pressed = False self._position_mouse = None self.setBackgroundBrush( QtGui.QBrush(QtCore.Qt.black, QtCore.Qt.SolidPattern)) self.setScene(self._scene)
def _build(self): '''Build and layout widget.''' layout = QtWidgets.QHBoxLayout() self.setLayout(layout) icon = QtWidgets.QPixmap(':icon-ftrack-box') smallIcon = icon.scaled(QtCore.QSize(24, 24), QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation) iconLabel = QtWidgets.QLabel() iconLabel.setPixmap(smallIcon) layout.addWidget(iconLabel) self._label = QtWidgets.QLabel(self._noSelectionMessage) self._label.setEnabled(False) layout.addWidget(self._label) layout.addStretch() self._browseButton = QtWidgets.QPushButton('Browse...') layout.addWidget(self._browseButton)
def _bounding_boxes(self, comment, width, padding_left, padding_top): padding_left_box = padding_left - self._padding_comment width_comment = width - padding_left width_comment -= (self._padding_item["right"] + self._padding_content["right"]) if width_comment < 0: width_comment = 0 width_adjust = - \ self._padding_comment if width_comment >= self._padding_comment else 0 r_comment = QtCore.QRect(padding_left, padding_top, width_comment, 50) r_comment_text = r_comment.adjusted(self._padding_comment, self._padding_comment, width_adjust, -self._padding_comment) bounding_text = self._fm_desc.boundingRect(r_comment_text, self._comment_flags, comment) r_comment.setHeight(bounding_text.height() + self._padding_comment * 2) r_comment_text.setHeight(bounding_text.height()) return (r_comment, r_comment_text)
def mouseMoveEvent(self, event): result = super(SnapshotsView, self).mouseMoveEvent(event) if self._pencil_pressed: position = self.mapToScene(event.pos()) if self._position_mouse == None: self._position_mouse = position else: pen = QtGui.QPen() pen.setCosmetic(True) pen.setCapStyle(QtCore.Qt.RoundCap) pen.setJoinStyle(QtCore.Qt.RoundJoin) pen.setColor(self._pen_color) pen.setWidth(3) graphics_path = QtGui.QGraphicsLineItem() graphics_path.setPen(pen) graphics_path.setVisible(True) graphics_path.setLine( QtCore.QLineF(self._position_mouse, position)) self._scene.addItem(graphics_path) self._drawings.append(graphics_path) self._position_mouse = position return result
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)
def sizeHint(self, option, index): tree_widget = self.parent() font = self._font_has_children font.setPixelSize(tree_widget.font_size) fm = QtGui.QFontMetrics(font) return QtCore.QSize(200, fm.height() + 5)
def paint(self, painter, option, index): ''' Override the tree widget draw widget function. ''' rect = option.rect tree_widget = self.parent() model = tree_widget.model() item = index.model().itemFromIndex(index) line = item.script_line.clean_line line_number = item.line_number is_title = item.script_line.has_children() is_comment = item.script_line.is_comment() color_value = self._color_default_value if item.script_line.is_comment(): font = self._font_comment color_default = self._color_comment highlight_value = False elif item.script_line.is_layer_addition(): font = self._font_default color_default = self._color_layer_addition highlight_value = False elif (item.script_line.stack_pushed() != None or item.script_line.stack_set() != None): font = self._font_default color_default = self._color_stacked_pushed color_value = self._color_stacked_pushed_value highlight_value = True elif item.script_line.has_children(): font = self._font_has_children color_default = self._color_has_children highlight_value = True else: font = self._font_default color_default = self._color_default highlight_value = True font.setPixelSize(tree_widget.font_size) # Get the size of the text according to the chosen font fm = QtGui.QFontMetrics(font) # Separate the line in a list of tuple (text, color) to draw the text if item.script_line.has_children( ) and not tree_widget.isExpanded(index): to_write = [(line + "...}", color_default)] else: to_write = [(line, color_default)] if highlight_value: # Try to highlight the name and the value(s) if possible tuple_split = item.script_line.clean_line.split(" ", 1) if len(tuple_split) > 1: if tuple_split[-1].strip() not in ["{", "{...}"]: to_write = [(tuple_split[0], color_default), (tuple_split[-1], color_value)] # Set line number indentation font_line_number = self._font_default font_line_number.setPixelSize(tree_widget.font_size) fm_line_number = QtGui.QFontMetrics(font_line_number) self.line_numbers_indent = fm_line_number.width( str(model.total_line_number)) # Draw the line number if the option has been set painter.setPen( QtGui.QPen(self._color_line_number, 1, QtCore.Qt.SolidLine)) painter.setFont(font_line_number) painter.drawText(5, rect.top() + 15, str(item.line_number)) interval_left = rect.left() + 15 + self.line_numbers_indent # Draw the filter if we need one if tree_widget.filter != None: self._color_selection.setAlpha(70) elements = re.findall(tree_widget.filter, line, re.IGNORECASE) tmp_line = line interval_rect = interval_left for element in elements: prefix, tmp_line = tmp_line.split(element, 1) interval_rect += fm.width(prefix) width = fm.width(element) rect_selection = QtCore.QRect(interval_rect, rect.y(), width, rect.height()) painter.setBrush(self._color_selection) painter.setPen( QtGui.QPen(self._color_selection, 2, QtCore.Qt.SolidLine)) painter.drawRect(rect_selection) interval_rect += width # Draw the text for tuple_to_write in to_write: text, color = tuple_to_write pen = QtGui.QPen(color, 1, QtCore.Qt.SolidLine) painter.setPen(QtGui.QPen(color, 1, QtCore.Qt.SolidLine)) painter.setFont(font) painter.drawText(interval_left, rect.top() + 15, text) interval_left += fm.width(text) + 5
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 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 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
def sizeHint(self): return QtCore.QSize(850, 650)
def sizeHint(self): return QtCore.QSize(600, 400)
def sizeHint(self): return QtCore.QSize(400, 450)
def setUrl(self, url): '''Load *url* and display result in web view.''' self._webView.load(QtCore.QUrl(url))
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')
def sizeHint(self): return QtCore.QSize(800, 550)