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())
예제 #3
0
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)
예제 #8
0
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)
예제 #9
0
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
예제 #11
0
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')
예제 #13
0
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())
예제 #14
0
class WorkerSignal(QtCore.QObject):
    finished = QtCore.Signal(bool, str)
예제 #15
0
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
예제 #16
0
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)
예제 #17
0
class ItemSignal(QtCore.QObject):
    asset_regenerated = QtCore.Signal()

    def __init__(self, parent):
        super(ItemSignal, self).__init__()
        self.parent = parent
예제 #18
0
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()
예제 #19
0
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)