Ejemplo n.º 1
0
class SelectorWidget(QtWidgets.QWidget):

    container_picked = QtCore.Signal(str, dict)
    host_selected = QtCore.Signal(str)
    version_changed = QtCore.Signal(str, io.ObjectId)

    def __init__(self, side, parent=None):
        super(SelectorWidget, self).__init__(parent=parent)

        def icon(name):
            return qtawesome.icon("fa.{}".format(name), color=SIDE_COLOR[side])

        body = {
            "tab": QtWidgets.QTabWidget(),
        }

        selector = {
            "host": HostSelectorWidget(),
            "databse": DatabaseSelectorWidget(),
        }

        body["tab"].addTab(selector["databse"], icon("cloud"), "Published")
        body["tab"].addTab(selector["host"], icon("home"), "In Scene")

        layout = QtWidgets.QHBoxLayout(self)
        layout.addWidget(body["tab"])

        # Connect

        selector["host"].container_picked.connect(self.on_container_picked)
        selector["host"].host_selected.connect(self.on_host_selected)
        selector["databse"].version_changed.connect(self.on_version_changed)

        # Init

        self.selector = selector
        self.side = side

        if not has_host() or side == SIDE_B:
            body["tab"].setCurrentIndex(0)
        else:
            body["tab"].setCurrentIndex(1)

    def connect_comparer(self, comparer):
        self.container_picked.connect(comparer.on_container_picked)
        self.host_selected.connect(comparer.on_host_selected)
        self.version_changed.connect(comparer.on_version_changed)

    def on_container_picked(self, container):
        self.container_picked.emit(self.side, container)

    def on_host_selected(self):
        self.host_selected.emit(self.side)

    def on_version_changed(self, version_id):
        self.version_changed.emit(self.side, version_id)
Ejemplo n.º 2
0
class MatchOutliner(QtWidgets.QWidget):

    selection_changed = QtCore.Signal()

    def __init__(self, parent=None):
        QtWidgets.QWidget.__init__(self, parent)

        # look manager layout
        layout = QtWidgets.QVBoxLayout(self)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(10)

        # Looks from database
        title = QtWidgets.QLabel("Matches:")
        title.setAlignment(QtCore.Qt.AlignLeft)
        title.setStyleSheet("font-weight: bold; font-size: 12px")

        model = models.MatchModel()
        view = views.View()
        view.setModel(model)
        view.setSortingEnabled(False)
        view.setHeaderHidden(True)
        view.setMinimumHeight(180)
        view.setIndentation(10)

        layout.addWidget(title)
        layout.addWidget(view)

        selection_model = view.selectionModel()
        selection_model.selectionChanged.connect(self.selection_changed)

        self.view = view
        self.model = model
        self._selection_model = selection_model

    def clear(self):
        self.model.clear()

    def add_items(self, items):
        self.model.add_items(items)

    def clear_selection(self):
        flags = self._selection_model.Clear
        self._selection_model.select(QtCore.QModelIndex(), flags)

    def select_index(self, index, flags=None):
        flags = flags or self._selection_model.ClearAndSelect
        self._selection_model.select(index, flags)

    def get_selected_items(self):
        """Get current selected items from view

        Returns:
            list: list of dictionaries
        """

        datas = [i.data(NODEROLE) for i in self.view.get_indices()]
        items = [d for d in datas if d is not None]  # filter Nones

        return items
Ejemplo n.º 3
0
class ResolutionDelegate(QtWidgets.QStyledItemDelegate):
    # DEPRECATED

    value_changed = QtCore.Signal(list)

    def displayText(self, value, locale):
        return "{} x {}".format(*value)

    def createEditor(self, parent, option, index):
        editor = ResolutionEditor(parent)

        def commit_data(value):
            self.commitData.emit(editor)  # Update model data
            self.value_changed.emit(value)

        editor.value_changed.connect(commit_data)

        return editor

    def setEditorData(self, editor, index):
        value = index.data(QtCore.Qt.DisplayRole)
        editor.set_value(value)

    def setModelData(self, editor, model, index):
        value = editor.get_value()
        model.setData(index, value)

    def updateEditorGeometry(self, editor, option, index):
        editor.setGeometry(option.rect)
Ejemplo n.º 4
0
class NameEditDelegate(QtWidgets.QStyledItemDelegate):

    name_changed = QtCore.Signal()

    def displayText(self, value, locale):
        return value

    def createEditor(self, parent, option, index):
        editor = QtWidgets.QLineEdit(parent)

        def commit_data():
            self.commitData.emit(editor)  # Update model data
            self.name_changed.emit()  # Display model data

        editor.editingFinished.connect(commit_data)

        return editor

    def setEditorData(self, editor, index):
        value = index.data(QtCore.Qt.DisplayRole)
        editor.setText(value)

    def setModelData(self, editor, model, index):
        name = editor.text()
        model.setData(index, name)

    def updateEditorGeometry(self, editor, option, index):
        editor.setGeometry(option.rect)
Ejemplo n.º 5
0
class Navigation(QtWidgets.QWidget):
    """Navigation panel widget"""

    index_changed = QtCore.Signal(int)
    log = logging.getLogger("Navigation")

    def __init__(self, parent=None):
        QtWidgets.QWidget.__init__(self, parent=parent)

        # self.setWindowFlag()
        self.setStyleSheet(style.flat_button)

        layout = QtWidgets.QVBoxLayout()

        dynamic_layout = QtWidgets.QVBoxLayout()
        static_layout = QtWidgets.QVBoxLayout()
        static_layout.addStretch()

        layout.addLayout(dynamic_layout)
        layout.addStretch()
        layout.addLayout(static_layout)

        self.dynamic_layout = dynamic_layout
        self.static_layout = static_layout

        self.setLayout(layout)
        self.layout = layout

    def add_button(self, label, order, widget=None):
        """Add a button to the navigation panel with the given label and order

        Args:
            label(str): Name displayed on the button
            order(int): Number which dictates its order in the panel
            widget(QtWidgets.QWidget): Instance of a widget

        Returns:
            None
        """

        if order < 0:
            layout = self.static_layout
        else:
            layout = self.dynamic_layout

        # Check new position
        widget_at_item = layout.itemAt(abs(order))
        if widget_at_item:
            self.log.warning("Found multiple items for the same order: `%i`" %
                             order)
            return

        button = QtWidgets.QPushButton(label)
        if widget:
            button.clicked.connect(partial(widget.show))

        layout.insertWidget(order, button)

        return button
Ejemplo n.º 6
0
class View(QtWidgets.QTreeView):
    data_changed = QtCore.Signal()

    def __init__(self, parent=None):
        super(View, self).__init__(parent=parent)

        # view settings
        self.setAlternatingRowColors(False)
        self.setSortingEnabled(True)
        self.setSelectionMode(self.SingleSelection)
        self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)

    def get_indices(self):
        """Get the selected rows"""
        selection_model = self.selectionModel()
        return selection_model.selectedRows()
Ejemplo n.º 7
0
class View(QtWidgets.QTreeView):
    data_changed = QtCore.Signal()

    def __init__(self, parent=None):
        super(View, self).__init__(parent=parent)

        # view settings
        self.setAlternatingRowColors(False)
        self.setSortingEnabled(True)
        self.setSelectionMode(self.ExtendedSelection)
        self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)

    def get_indices(self):
        """Get the selected rows"""
        selection_model = self.selectionModel()
        return selection_model.selectedRows()

    def extend_to_children(self, indices):
        """Extend the indices to the children indices.

        Top-level indices are extended to its children indices. Sub-items
        are kept as is.

        :param indices: The indices to extend.
        :type indices: list

        :return: The children indices
        :rtype: list
        """

        subitems = set()
        for i in indices:
            valid_parent = i.parent().isValid()
            if valid_parent and i not in subitems:
                subitems.add(i)
            else:
                # is top level node
                model = i.model()
                rows = model.rowCount(parent=i)
                for row in range(rows):
                    child = model.index(row, 0, parent=i)
                    subitems.add(child)

        return list(subitems)
Ejemplo n.º 8
0
class Popup2(Popup):

    on_show = QtCore.Signal()

    def __init__(self, parent=None, *args, **kwargs):
        Popup.__init__(self, parent=parent, *args, **kwargs)

        layout = self.layout()

        # Add toggle
        toggle = QtWidgets.QCheckBox("Update Keys")
        layout.insertWidget(1, toggle)
        self.widgets["toggle"] = toggle

        layout.insertStretch(1, 1)

        # Update button text
        fix = self.widgets["show"]
        fix.setText("Fix")
Ejemplo n.º 9
0
class CollectionItem(QtWidgets.QListWidgetItem):

    InstanceRole = QtCore.Qt.UserRole + 2
    item_clicked = QtCore.Signal()

    def __init__(self, data=None, parent=None):
        QtWidgets.QListWidgetItem.__init__(self, parent)

        self.setFlags(self.flags() | QtCore.Qt.ItemIsUserCheckable)
        self.setCheckState(QtCore.Qt.Checked)

        self._data = None
        if data:
            self.set_data(data)

    def set_data(self, data):
        self._data = data
        self.setData(self.InstanceRole, data)

    def get_data(self):
        return self._data
Ejemplo n.º 10
0
class ResolutionEditor(QtWidgets.QWidget):
    # DEPRECATED

    value_changed = QtCore.Signal(list)

    def __init__(self, parent=None):
        super(ResolutionEditor, self).__init__(parent)

        weditor = QtWidgets.QLineEdit()
        heditor = QtWidgets.QLineEdit()

        weditor.setValidator(QtGui.QIntValidator())
        heditor.setValidator(QtGui.QIntValidator())

        layout = QtWidgets.QHBoxLayout(self)
        layout.addWidget(weditor)
        layout.addWidget(heditor)
        # This is important to make those QLineEdit widgets to have
        # proper hight in column.
        layout.setContentsMargins(2, 0, 2, 0)

        weditor.editingFinished.connect(self.on_editingFinished)
        heditor.editingFinished.connect(self.on_editingFinished)

        self.data = {
            "w": weditor,
            "h": heditor,
        }

    def on_editingFinished(self):
        self.value_changed.emit(self.get_value())

    def get_value(self):
        return (int(self.data["w"].text() or 0), int(self.data["h"].text()
                                                     or 0))

    def set_value(self, value):
        w, h = value
        self.data["w"].setText(str(w))
        self.data["h"].setText(str(h))
Ejemplo n.º 11
0
class Popup2(Popup):

    on_show = QtCore.Signal()

    def __init__(self, parent=None, *args, **kwargs):
        Popup.__init__(self, parent=parent, *args, **kwargs)

        layout = self.layout()

        # Add toggle
        toggle = QtWidgets.QCheckBox("Update Keys")
        layout.insertWidget(1, toggle)
        self.widgets["toggle"] = toggle

        layout.insertStretch(1, 1)

        # Update button text
        fix = self.widgets["show"]
        fix.setText("Fix")

    def calculate_window_geometry(self):
        """Respond to status changes

        On creation, align window with screen bottom right.

        """
        parent_widget = self.parent()

        app = QtWidgets.QApplication.instance()
        if parent_widget:
            screen = app.desktop().screenNumber(parent_widget)
        else:
            screen = app.desktop().screenNumber(app.desktop().cursor().pos())
        center_point = app.desktop().screenGeometry(screen).center()

        frame_geo = self.frameGeometry()
        frame_geo.moveCenter(center_point)

        return frame_geo
Ejemplo n.º 12
0
class PopupUpdateKeys(Popup):
    """Popup with Update Keys checkbox (intended for Maya)"""

    on_clicked_state = QtCore.Signal(bool)

    def __init__(self, parent=None, *args, **kwargs):
        Popup.__init__(self, parent=parent, *args, **kwargs)

        layout = self.layout()

        # Insert toggle for Update keys
        toggle = QtWidgets.QCheckBox("Update Keys")
        layout.insertWidget(1, toggle)
        self.widgets["toggle"] = toggle

        self.on_clicked.connect(self.emit_click_with_state)

        layout.insertStretch(1, 1)

    def emit_click_with_state(self):
        """Emit the on_clicked_state signal with the toggled state"""
        checked = self.widgets["toggle"].isChecked()
        self.on_clicked_state.emit(checked)
Ejemplo n.º 13
0
class Window(QtWidgets.QWidget):

    project_changed = QtCore.Signal(str)
    log = logging.getLogger("Project Manager")

    def __init__(self, parent=None):
        QtWidgets.QWidget.__init__(self, parent)

        self.setWindowTitle("Project Manager")
        self.resize(1200, 800)

        self.projects = {}

        layout = QtWidgets.QVBoxLayout()

        # Main control
        ctrl_button_w = 30
        ctrl_button_h = 30

        # Calculate icon size
        icon_size = QtCore.QSize(ctrl_button_w - 4, ctrl_button_h - 4)

        main_control_layout = QtWidgets.QHBoxLayout()

        database_label = QtWidgets.QLabel()

        # Main buttons - create
        create_button = QtWidgets.QPushButton()
        create_icon = qta.icon("fa.plus-square", color=style.colors.light)

        create_button.setIconSize(icon_size)

        create_button.setFixedWidth(ctrl_button_w)
        create_button.setFixedHeight(ctrl_button_h)

        create_button.setIcon(create_icon)
        create_button.setStyleSheet(cbstyle.flat_button)

        # Main buttons - refresh
        refresh_button = QtWidgets.QPushButton()
        refresh_icon = qta.icon("fa.refresh", color=style.colors.light)

        refresh_button.setIconSize(icon_size)

        refresh_button.setFixedWidth(ctrl_button_w)
        refresh_button.setFixedHeight(ctrl_button_h)

        refresh_button.setIcon(refresh_icon)
        refresh_button.setStyleSheet(cbstyle.flat_button)

        # Project switch control
        projects_label = QtWidgets.QLabel("Project:")
        projects = QtWidgets.QComboBox()
        projects.insertItem(0, "<None>")

        # Add buttons to the main control layout
        main_control_layout.addWidget(create_button)
        main_control_layout.addStretch()
        main_control_layout.addWidget(database_label)
        main_control_layout.addWidget(projects_label)
        main_control_layout.addWidget(projects)
        main_control_layout.addWidget(refresh_button)

        # Splitter for tabwidget and preview / details widget
        split_widget = QtWidgets.QSplitter()

        # Widgets will be stored in a StackedWidget
        stacked_widget = QtWidgets.QStackedWidget()

        # Control widgets which make the tool
        manager_widget = ManageProjectWidget(parent=self)

        # Sub manager widgets
        overview = OverviewWidget()
        manager_widget.add_widget(overview)

        stacked_widget.insertWidget(manager_widget.order, manager_widget)

        # Navigation panel widget
        navigation_panel = Navigation()

        # Add buttons to navigation panel
        navigation_panel.add_button(manager_widget.label, manager_widget.order)

        # Add widgets to the SplitWidget
        split_widget.addWidget(navigation_panel)
        split_widget.addWidget(stacked_widget)
        split_widget.setHandleWidth(4)
        split_widget.setSizes([100, 700])

        layout.addLayout(main_control_layout)
        layout.addWidget(split_widget)

        self.setLayout(layout)

        # To connect widget store in self attribute
        self._navigation_panel = navigation_panel
        self._stacked_widget = stacked_widget

        self._database_label = database_label
        self._create_button = create_button
        self._projects = projects
        self._refresh_button = refresh_button

        self._overview = overview

        self.connect_signals()

        self.refresh()

        manager_widget.setFocus(True)

    def connect_signals(self):
        """Create connections between widgets"""

        self._navigation_panel.index_changed.connect(
            self._stacked_widget.setCurrentIndex)

        self.project_changed.connect(self.on_project_changed)

        self._refresh_button.clicked.connect(self.refresh)
        self._create_button.clicked.connect(self.on_create)
        self._projects.currentIndexChanged.connect(
            self.on_project_index_changed)

    def refresh(self):
        """Refresh connection to database and """
        def sorter(project):
            """Sort based on order attribute of the plugin"""
            return project["name"]

        lib.install()
        self.set_database_label(lib.get_database_name())

        query = list(lib.get_projects())
        projects = sorted(query, key=sorter)

        self.projects = {p["name"]: p["_id"] for p in projects}

        self.populate_projects(projects)

    def set_database_label(self, name=None):
        label = "Database: {}".format(name or "<None>")
        self._database_label.setText(label)

    def populate_projects(self, projects):
        """Add projects to project dropdown menu"""

        completer = QtWidgets.QCompleter([p["name"] for p in projects])
        self._projects.setCompleter(completer)

        for idx, project in enumerate(projects):
            self._projects.insertItem(idx + 1,
                                      project["name"],
                                      userData=project["_id"])

    def get_project(self, as_id=False):

        current_index = self._projects.currentIndex()
        if current_index == 0:
            return

        item = self._projects.itemAt(current_index)
        if as_id:
            _id = item.userData()
            assert _id, "This is a bug!"
            return _id

        project_name = item.text()
        assert project_name, "This is a bug!"

        return project_name

    def on_project_changed(self, name):

        current_project = self._projects.currentText()
        if name == current_project:
            return

        self.refresh()

        idx = self._projects.findText(name)
        if idx == -1:
            raise RuntimeError("Something went wrong, can't find name `%s`" %
                               name)

        self._projects.setCurrentIndex(idx)

        # Refresh the overview with
        self._overview.refresh(name)

    def on_project_index_changed(self):
        project_name = self._projects.currentText()
        self._overview.refresh(project_name)

    def on_create(self):
        create_widget = CreateProjectWidget(parent=self)
        create_widget.data_changed.connect(self.on_project_changed)

        create_widget.show()
Ejemplo n.º 14
0
class Window(QtWidgets.QWidget):
    UserRole = QtCore.Qt.UserRole

    connected = QtCore.Signal(list)
    disconnected = QtCore.Signal(list)

    def __init__(self, parent=None):
        QtWidgets.QWidget.__init__(self, parent=parent)

        title = "Yeti Rig Manager 1.1.0 - [%s]" % lib.get_workfile()
        geometry = (800, 400)

        self.log = logging.getLogger("Yeti Rig Manager")

        self.setObjectName("yetiRigManager")
        self.setWindowTitle(title)
        self.setWindowFlags(QtCore.Qt.Window)
        self.setParent(parent)

        layout = QtWidgets.QVBoxLayout()

        # Control layout
        control_layout = QtWidgets.QHBoxLayout()

        force_checkbox = QtWidgets.QCheckBox("Force")
        force_checkbox.setChecked(True)

        refresh_button = QtWidgets.QPushButton()
        refresh_icon = qta.icon("fa.refresh", color="white")

        refresh_button.setIcon(refresh_icon)
        refresh_button.setFixedWidth(28)
        refresh_button.setFixedHeight(28)

        control_layout.addWidget(force_checkbox)
        control_layout.addStretch(True)
        control_layout.addWidget(refresh_button)

        view_layout = QtWidgets.QHBoxLayout()

        rig_view = AssetOutliner()
        match_view = MatchOutliner()

        view_layout.addWidget(rig_view)
        view_layout.addWidget(match_view)

        # Default action buttons
        action_button_layout = QtWidgets.QHBoxLayout()
        connect_button = QtWidgets.QPushButton("Connect")
        disconnect_button = QtWidgets.QPushButton("Disconnect")

        action_button_layout.addWidget(connect_button)
        action_button_layout.addWidget(disconnect_button)

        layout.addLayout(control_layout)
        layout.addLayout(view_layout)
        layout.addLayout(action_button_layout)

        self.setLayout(layout)

        self.force_checkbox = force_checkbox
        self.refresh_button = refresh_button
        self.connect_button = connect_button
        self.disconnect_button = disconnect_button

        self.rig_view = rig_view
        self.match_view = match_view

        self.resize(*geometry)

        self.connections()

    def connections(self):

        self.rig_view.selection_changed.connect(self.on_rig_selection_changed)

        self.refresh_button.clicked.connect(self.refresh)
        self.connect_button.clicked.connect(self.connect_container_nodes)
        self.disconnect_button.clicked.connect(self.disconnect_container_nodes)

    def on_rig_selection_changed(self):
        selection_model = self.rig_view.get_selection_model()
        indices = selection_model.selectedIndexes()
        if len(indices) != 1:
            return
        index = indices[0]

        self.match_view.model.set_linked_index(index)

        # The font will only update the widget gets focus
        self.match_view.setFocus()
        self.rig_view.setFocus()

    def refresh(self):

        self.rig_view.clear()
        self.match_view.clear()

        rig_items = []
        other_items = []

        # Separate based on loader
        for container in lib.get_containers():
            node = lib.create_node(container)
            if node["loader"] == "YetiRigLoader":
                rig_items.append(node)
            else:
                other_items.append(node)

        match_items = lib.get_matches(rig_items, other_items)
        print(match_items)

        self.rig_view.add_items(rig_items)
        self.match_view.add_items(match_items)

        self.log.info("Refreshed ..")

        self._link_connected()

    def connect_container_nodes(self):

        force = self.force_checkbox.isChecked()

        rig_node = self._get_rig_node()
        match_node = self._get_match_node()

        # Get needs information
        connections = lib.get_connections(rig_node["representation"])
        rig_members_by_id = rig_node["nodes"]
        input_members_by_id = match_node["nodes"]

        lib.connect(rig_members_by_id, input_members_by_id, connections, force)

        self.refresh()

    def disconnect_container_nodes(self):

        rig_node = self._get_rig_node()
        if not rig_node:
            self.log.error("Please select one rig item")

        match_node = self._get_match_node()
        if not match_node:
            self.log.error("Please select one match item")

        connections = lib.get_connections(rig_node["representation"])

        rig_members_by_id = rig_node["nodes"]
        input_members_by_id = match_node["nodes"]

        lib.disconnect(rig_members_by_id, input_members_by_id, connections)

        self.refresh()

    def _get_rig_node(self):
        items = self.rig_view.get_selected_items()
        if len(items) != 1:
            return

        item = items[0]
        return item

    def _get_match_node(self):
        items = self.match_view.get_selected_items()
        if len(items) != 1:
            return

        item = items[0]
        return item

    def _link_connected(self):

        rig_model = self.rig_view.model
        rig_indexes = rig_model.get_indexes()

        match_model = self.match_view.model
        match_indexes = match_model.get_indexes()

        node_role = rig_model.NodeRole

        for rig_index in rig_indexes:
            if not rig_index.isValid():
                continue

            rig_node = rig_model.data(rig_index, node_role)
            rig_members_by_id = rig_node["nodes"]
            connections = lib.get_connections(rig_node["representation"])

            for match_index in match_indexes:
                if not match_index.isValid():
                    continue
                match_node = match_model.data(match_index, node_role)
                match_members_by_id = match_node["nodes"]

                if not lib.are_items_connected(
                        rig_members_by_id, match_members_by_id, connections):
                    continue
                self.log.info("Found connected items..")

                match_node.update({"linkedIndex": [rig_index]})
                break

    def _find_rig_node_index(self, label):

        model = self.rig_view.model

        indexes = model.get_indexes()
        for idx in indexes:
            if not idx.isValid():
                continue

            node = model.data(idx, model.NodeRole)
            if node["label"] == label:
                return idx

    def _find_match_node_index(self, label):

        model = self.match_view.model

        indexes = model.get_indexes()
        for idx in indexes:
            if not idx.isValid():
                continue

            node = model.data(idx, model.NodeRole)
            if node["label"] == label:
                return idx
Ejemplo n.º 15
0
class AssetOutliner(QtWidgets.QWidget):

    refreshed = QtCore.Signal()
    selection_changed = QtCore.Signal()

    def __init__(self, parent=None):
        QtWidgets.QWidget.__init__(self, parent)

        layout = QtWidgets.QVBoxLayout()

        title = QtWidgets.QLabel("Assets")
        title.setAlignment(QtCore.Qt.AlignCenter)
        title.setStyleSheet("font-weight: bold; font-size: 12px")

        model = models.AssetModel()
        view = views.View()
        view.setModel(model)
        view.customContextMenuRequested.connect(self.right_mouse_menu)
        view.setSortingEnabled(False)
        view.setHeaderHidden(False)
        view.setIndentation(10)

        from_all_asset_btn = QtWidgets.QPushButton("Get All Assets")
        from_selection_btn = QtWidgets.QPushButton("Get Assets From Selection")

        layout.addWidget(title)
        layout.addWidget(from_all_asset_btn)
        layout.addWidget(from_selection_btn)
        layout.addWidget(view)

        # Build connections
        from_selection_btn.clicked.connect(self.get_selected_assets)
        from_all_asset_btn.clicked.connect(self.get_all_assets)

        selection_model = view.selectionModel()
        selection_model.selectionChanged.connect(self.selection_changed)

        self.view = view
        self.model = model

        self.setLayout(layout)

        self.log = logging.getLogger(__name__)

    def clear(self):
        self.model.clear()

        # fix looks remaining visible when no items present after "refresh"
        # todo: figure out why this workaround is needed.
        self.selection_changed.emit()

    def add_items(self, items):
        """Add new items to the outliner"""

        self.model.add_items(items)
        self.refreshed.emit()

    def get_selected_items(self):
        """Get current selected items from view

        Returns:
            list: list of dictionaries
        """

        selection_model = self.view.selectionModel()
        items = [row.data(NODEROLE) for row in selection_model.selectedRows(0)]

        return items

    def get_all_assets(self):
        """Add all items from the current scene"""

        with preserve_expanded_rows(self.view):
            with preserve_selection(self.view):
                self.clear()
                nodes = commands.get_all_asset_nodes()
                items = commands.create_items_from_nodes(nodes)
                self.add_items(items)

        return len(items) > 0

    def get_selected_assets(self):
        """Add all selected items from the current scene"""

        with preserve_expanded_rows(self.view):
            with preserve_selection(self.view):
                self.clear()
                nodes = commands.get_selected_nodes()
                items = commands.create_items_from_nodes(nodes)
                self.add_items(items)

    def get_nodes(self):
        """Find the nodes in the current scene per asset."""

        items = self.get_selected_items()

        # Collect the asset item entries per asset
        assets = dict()
        for item in items:
            asset_name = item["asset"]["name"]

            namespaces = item.get("namespace", item["namespaces"])
            nodes = commands.get_groups_from_namespaces(namespaces)

            assets[item.get("namespace") or asset_name] = item
            assets[item.get("namespace") or asset_name]["nodes"] = nodes

        return assets

    def select_asset_from_items(self):
        """Select nodes from listed asset"""

        items = self.get_nodes()
        nodes = []
        for item in items.values():
            nodes.extend(item["nodes"])

        commands.select(nodes)

    def remove_look_from_items(self):
        namespaces = set()
        asset_ids = set()

        for item in self.get_selected_items():
            namespace = item.get("namespace")
            if namespace:
                namespaces.add(namespace)
            else:
                namespaces.update(item["namespaces"])

            asset_ids.add(str(item["asset"]["_id"]))

        commands.remove_look(namespaces, asset_ids)

    def right_mouse_menu(self, pos):
        """Build RMB menu for asset outliner"""

        active = self.view.currentIndex()  # index under mouse
        active = active.sibling(active.row(), 0)  # get first column
        globalpos = self.view.viewport().mapToGlobal(pos)

        menu = QtWidgets.QMenu(self.view)

        apply_action = QtWidgets.QAction(menu, text="Select nodes")
        apply_action.triggered.connect(self.select_asset_from_items)

        remove_action = QtWidgets.QAction(menu, text="Remove look")
        remove_action.triggered.connect(self.remove_look_from_items)

        if not active.isValid():
            apply_action.setEnabled(False)
            remove_action.setEnabled(False)

        menu.addAction(apply_action)
        menu.addAction(remove_action)

        menu.exec_(globalpos)
Ejemplo n.º 16
0
class View(QtWidgets.QTreeView):
    data_changed = QtCore.Signal()

    def __init__(self, parent=None):
        super(View, self).__init__(parent=parent)

        # view settings
        self.setIndentation(6)
        self.setAlternatingRowColors(True)
        self.setSortingEnabled(True)
        self.setSelectionMode(self.ExtendedSelection)
        self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)

    def get_indices(self):
        """Get the selected rows"""
        selection_model = self.selectionModel()
        return selection_model.selectedRows()

    def extend_to_children(self, indices):
        """Extend the indices to the children indices.

        Top-level indices are extended to its children indices. Sub-items
        are kept as is.

        :param indices: The indices to extend.
        :type indices: list

        :return: The children indices
        :rtype: list
        """

        subitems = set()
        for i in indices:
            valid_parent = i.parent().isValid()
            if valid_parent and i not in subitems:
                subitems.add(i)
            else:
                # is top level node
                model = i.model()
                rows = model.rowCount(parent=i)
                for row in range(rows):
                    child = model.index(row, 0, parent=i)
                    subitems.add(child)

        return list(subitems)

    def show_version_dialog(self, items):
        """Create a dialog with the available versions for the selected file

        :param items: list of items to run the "set_version" for
        :type items: list

        :returns: None
        """

        active = items[-1]

        # Get available versions for active representation
        representation_id = io.ObjectId(active["representation"])
        representation = io.find_one({"_id": representation_id})
        version = io.find_one({"_id": representation["parent"]})

        versions = io.find({"parent": version["parent"]}, sort=[("name", 1)])
        versions = list(versions)

        current_version = active["version"]

        # Get index among the listed versions
        index = len(versions) - 1
        for i, version in enumerate(versions):
            if version["name"] == current_version:
                index = i
                break

        versions_by_label = dict()
        labels = []
        for version in versions:
            label = "v{0:03d}".format(version["name"])
            labels.append(label)
            versions_by_label[label] = version

        label, state = QtWidgets.QInputDialog.getItem(self,
                                                      "Set version..",
                                                      "Set version number "
                                                      "to",
                                                      labels,
                                                      current=index,
                                                      editable=False)
        if not state:
            return

        if label:
            version = versions_by_label[label]["name"]
            for item in items:
                api.update(item, version)
            # refresh model when done
            self.data_changed.emit()
Ejemplo n.º 17
0
class AssetWidget(QtWidgets.QWidget):
    """A Widget to display a tree of assets with filter

    To list the assets of the active project:
        >>> # widget = AssetWidget()
        >>> # widget.refresh()
        >>> # widget.show()

    """

    assets_refreshed = QtCore.Signal()  # on model refresh
    selection_changed = QtCore.Signal()  # on view selection change
    current_changed = QtCore.Signal()  # on view current index change

    def __init__(self, parent=None):
        super(AssetWidget, self).__init__(parent=parent)
        self.setContentsMargins(0, 0, 0, 0)

        layout = QtWidgets.QVBoxLayout(self)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(4)

        # Tree View
        model = AssetModel(self)
        proxy = RecursiveSortFilterProxyModel()
        proxy.setSourceModel(model)
        proxy.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)

        view = DeselectableTreeView()
        view.setIndentation(15)
        view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        view.setHeaderHidden(True)
        view.setModel(proxy)

        # Header
        header = QtWidgets.QHBoxLayout()

        icon = qtawesome.icon("fa.refresh", color=style.colors.light)
        refresh = QtWidgets.QPushButton(icon, "")
        refresh.setToolTip("Refresh items")

        filter = QtWidgets.QLineEdit()
        filter.textChanged.connect(proxy.setFilterFixedString)
        filter.setPlaceholderText("Filter assets..")

        header.addWidget(filter)
        header.addWidget(refresh)

        # Layout
        layout.addLayout(header)
        layout.addWidget(view)

        # Signals/Slots
        selection = view.selectionModel()
        selection.selectionChanged.connect(self.selection_changed)
        selection.currentChanged.connect(self.current_changed)
        refresh.clicked.connect(self.refresh)

        self.refreshButton = refresh
        self.model = model
        self.proxy = proxy
        self.view = view

    def _refresh_model(self):
        with preserve_expanded_rows(self.view,
                                    column=0,
                                    role=self.model.ObjectIdRole):
            with preserve_selection(self.view,
                                    column=0,
                                    role=self.model.ObjectIdRole):
                self.model.refresh()

        self.assets_refreshed.emit()

    def refresh(self):
        self._refresh_model()

    def get_active_asset(self):
        """Return the asset id the current asset."""
        current = self.view.currentIndex()
        return current.data(self.model.ItemRole)

    def get_active_index(self):
        return self.view.currentIndex()

    def get_selected_assets(self):
        """Return the assets' ids that are selected."""
        selection = self.view.selectionModel()
        rows = selection.selectedRows()
        return [row.data(self.model.ObjectIdRole) for row in rows]

    def select_assets(self, assets, expand=True, key="name"):
        """Select assets by name.

        Args:
            assets (list): List of asset names
            expand (bool): Whether to also expand to the asset in the view

        Returns:
            None

        """
        # TODO: Instead of individual selection optimize for many assets

        if not isinstance(assets, (tuple, list)):
            assets = [assets]
        assert isinstance(assets,
                          (tuple, list)), "Assets must be list or tuple"

        # convert to list - tuple cant be modified
        assets = list(assets)

        # Clear selection
        selection_model = self.view.selectionModel()
        selection_model.clearSelection()

        # Select
        mode = selection_model.Select | selection_model.Rows
        for index in iter_model_rows(self.proxy, column=0, include_root=False):
            # stop iteration if there are no assets to process
            if not assets:
                break

            value = index.data(self.model.ItemRole).get(key)
            if value not in assets:
                continue

            # Remove processed asset
            assets.pop(assets.index(value))

            selection_model.select(index, mode)

            if expand:
                # Expand parent index
                self.view.expand(self.proxy.parent(index))

            # Set the currently active index
            self.view.setCurrentIndex(index)
Ejemplo n.º 18
0
class FocusComparing(QtWidgets.QWidget):

    focus_enabled = QtCore.Signal(int)

    def __init__(self, parent=None):
        super(FocusComparing, self).__init__(parent=parent)

        widget = pindict.to_pindict({
            "overallDiff": {
                "main": QtWidgets.QGroupBox("Compare Features"),
                "name": {
                    "main": QtWidgets.QWidget(),
                    "icon": QtWidgets.QLabel(),
                    "label": QtWidgets.QLabel("Hierarchy"),
                    "status": QtWidgets.QLabel("--"),
                },
                "id": {
                    "main": QtWidgets.QWidget(),
                    "icon": QtWidgets.QLabel(),
                    "label": QtWidgets.QLabel("Avalon Id"),
                    "status": QtWidgets.QLabel("--"),
                },
                "mesh": {
                    "main": QtWidgets.QWidget(),
                    "icon": QtWidgets.QLabel(),
                    "label": QtWidgets.QLabel("Mesh"),
                    "status": QtWidgets.QLabel("--"),
                },
                "uv": {
                    "main": QtWidgets.QWidget(),
                    "icon": QtWidgets.QLabel(),
                    "label": QtWidgets.QLabel("UV"),
                    "status": QtWidgets.QLabel("--"),
                },
            },
            "featureMenu": {
                "main": QtWidgets.QWidget(),
                "label": QtWidgets.QLabel("Focus On"),
                "list": QtWidgets.QComboBox(),
            },
            "focus": {
                "view": QtWidgets.QTreeView(),
                "model": models.FocusModel(),
                "pathDelegate": delegates.PathTextDelegate(),
            }
        })

        with widget.pin("overallDiff") as diff:
            layout = QtWidgets.QVBoxLayout(diff["main"])

            for key in ["name", "id", "mesh", "uv"]:
                with widget.pin("overallDiff." + key) as feature:
                    lay = QtWidgets.QHBoxLayout(feature["main"])
                    lay.addWidget(feature["label"])
                    lay.addSpacing(8)
                    lay.addWidget(feature["icon"])
                    lay.addSpacing(12)
                    lay.addWidget(feature["status"], stretch=True)

                    feature["label"].setFixedWidth(60)
                    feature["label"].setAlignment(QtCore.Qt.AlignRight)

                    icon = delegates.FEATURE_ICONS[key]
                    pixmap = lib.icon(icon, models.COLOR_DARK).pixmap(16, 16)
                    feature["icon"].setPixmap(pixmap)

                layout.addWidget(feature["main"])
                layout.addSpacing(-16)
            layout.addSpacing(16)
            layout.setContentsMargins(0, 0, 0, 0)

        with widget.pin("featureMenu") as menu:
            layout = QtWidgets.QHBoxLayout(menu["main"])
            layout.addWidget(menu["label"])
            layout.addWidget(menu["list"])
            layout.addStretch()

        layout = QtWidgets.QVBoxLayout(self)
        layout.addSpacing(-16)
        layout.addWidget(widget["overallDiff"]["main"])
        layout.addSpacing(-8)
        layout.addWidget(widget["featureMenu"]["main"])
        layout.addSpacing(-8)
        layout.addWidget(widget["focus"]["view"])

        # Init
        with widget.pin("featureMenu") as menu:
            menu["list"].addItem(" Hierarchy", "longName")
            menu["list"].addItem(" Avalon Id", "avalonId")
            menu["list"].addItem(" Full Path", "fullPath")
            menu["list"].addItem(" Mesh", "points")
            menu["list"].addItem(" UV", "uvmap")

        with widget.pin("focus") as focus:
            focus["view"].setModel(focus["model"])
            focus["view"].setItemDelegateForColumn(1, focus["pathDelegate"])
            focus["view"].setHeaderHidden(True)
            focus["view"].setUniformRowHeights(True)
            focus["view"].setAlternatingRowColors(False)
            focus["view"].setIndentation(6)
            focus["view"].setStyleSheet("""
                QTreeView::item{
                    padding: 2px 1px;
                    border: 0px;
                }
            """)
            focus["view"].setSelectionMode(focus["view"].NoSelection)
            height = focus["view"].sizeHintForRow(0) * 2 + 4  # MagicNum
            focus["view"].setFixedHeight(height)
            focus["view"].setColumnWidth(0, 28)

        self.widget = widget
        self._focusing = False

        # Connect
        with widget.pin("featureMenu") as menu:
            menu["list"].currentIndexChanged.connect(self.on_feature_changed)
        self.focus_enabled.connect(self.on_focus_enabled)

    def on_focus_enabled(self, enable):
        if not enable:
            self.widget["focus"]["model"].reset_sides()
            self.update()  # Reset
        self._focusing = enable

    def on_feature_changed(self, index=None):
        with self.widget.pin("featureMenu.list") as menu:
            feature = menu.currentData()
        with self.widget.pin("focus") as focus:
            focus["model"].set_focus(feature)

    def on_picked(self, side, data=None):
        if not self._focusing:
            return

        with self.widget.pin("focus") as focus:
            data = data or dict()
            focus["model"].set_side(side, data)
        # Compare
        self.update()
        self.on_feature_changed()

    def update(self):
        with self.widget.pin("focus") as focus:
            node_A = focus["model"].nodes[SIDE_A]
            node_B = focus["model"].nodes[SIDE_B]

        def related(this, that):
            return this == that or this.endswith(that) or that.endswith(this)

        def equal(this, that):
            return this == that

        features = [
            ("name", "longName", related),
            ("id", "avalonId", equal),
            ("mesh", "points", equal),
            ("uv", "uvmap", equal),
        ]

        for feature, key, compare in features:
            with self.widget.pin("overallDiff." + feature) as widget:
                fet_A = node_A.get(key)
                fet_B = node_B.get(key)

                if fet_A and fet_B:
                    if compare(fet_A, fet_B):
                        status = "Match"
                        color = models.COLOR_BRIGHT
                    else:
                        status = "Not Match"
                        color = models.COLOR_DANGER
                else:
                    status = "--"
                    color = models.COLOR_DARK

                icon = delegates.FEATURE_ICONS[feature]
                pixmap = lib.icon(icon, color).pixmap(16, 16)
                widget["icon"].setPixmap(pixmap)
                widget["status"].setText(status)
Ejemplo n.º 19
0
class AssetOutliner(QtWidgets.QWidget):

    refreshed = QtCore.Signal()
    selection_changed = QtCore.Signal()

    def __init__(self, parent=None):
        QtWidgets.QWidget.__init__(self, parent)

        layout = QtWidgets.QVBoxLayout()

        title = QtWidgets.QLabel("Assets")
        title.setAlignment(QtCore.Qt.AlignCenter)
        title.setStyleSheet("font-weight: bold; font-size: 12px")

        model = models.AssetModel()
        view = views.View()
        view.setModel(model)
        view.customContextMenuRequested.connect(self.right_mouse_menu)
        view.setSortingEnabled(False)
        view.setHeaderHidden(True)
        view.setIndentation(10)

        from_all_asset_btn = QtWidgets.QPushButton("Get All Assets")
        from_selection_btn = QtWidgets.QPushButton("Get Assets From Selection")

        layout.addWidget(title)
        layout.addWidget(from_all_asset_btn)
        layout.addWidget(from_selection_btn)
        layout.addWidget(view)

        # Build connections
        from_selection_btn.clicked.connect(self.get_selected_assets)
        from_all_asset_btn.clicked.connect(self.get_all_assets)

        selection_model = view.selectionModel()
        selection_model.selectionChanged.connect(self.selection_changed)

        self.view = view
        self.model = model

        self.setLayout(layout)

        self.log = logging.getLogger(__name__)

    def clear(self):
        self.model.clear()

        # fix looks remaining visible when no items present after "refresh"
        # todo: figure out why this workaround is needed.
        self.selection_changed.emit()

    def add_items(self, items):
        """Add new items to the outliner"""

        self.model.add_items(items)
        self.refreshed.emit()

    def get_selected_items(self):
        """Get current selected items from view

        Returns:
            list: list of dictionaries
        """

        selection_model = self.view.selectionModel()
        items = [
            row.data(TreeModel.ItemRole)
            for row in selection_model.selectedRows(0)
        ]

        return items

    def get_all_assets(self):
        """Add all items from the current scene"""

        with lib.preserve_expanded_rows(self.view):
            with lib.preserve_selection(self.view):
                self.clear()
                nodes = commands.get_all_asset_nodes()
                items = commands.create_items_from_nodes(nodes)
                self.add_items(items)

        return len(items) > 0

    def get_selected_assets(self):
        """Add all selected items from the current scene"""

        with lib.preserve_expanded_rows(self.view):
            with lib.preserve_selection(self.view):
                self.clear()
                nodes = commands.get_selected_nodes()
                items = commands.create_items_from_nodes(nodes)
                self.add_items(items)

    def get_nodes(self, selection=False):
        """Find the nodes in the current scene per asset."""

        items = self.get_selected_items()

        # Collect all nodes by hash (optimization)
        if not selection:
            nodes = cmds.ls(dag=True, long=True)
        else:
            nodes = commands.get_selected_nodes()
        id_nodes = commands.create_asset_id_hash(nodes)

        # Collect the asset item entries per asset
        # and collect the namespaces we'd like to apply
        assets = dict()
        asset_namespaces = defaultdict(set)
        for item in items:
            asset_id = str(item["asset"]["_id"])
            asset_name = item["asset"]["name"]
            asset_namespaces[asset_name].add(item.get("namespace"))

            if asset_name in assets:
                continue

            assets[asset_name] = item
            assets[asset_name]["nodes"] = id_nodes.get(asset_id, [])

        # Filter nodes to namespace (if only namespaces were selected)
        for asset_name in assets:
            namespaces = asset_namespaces[asset_name]

            # When None is present there should be no filtering
            if None in namespaces:
                continue

            # Else only namespaces are selected and *not* the top entry so
            # we should filter to only those namespaces.
            nodes = assets[asset_name]["nodes"]
            nodes = [
                node for node in nodes
                if commands.get_namespace_from_node(node) in namespaces
            ]
            assets[asset_name]["nodes"] = nodes

        return assets

    def select_asset_from_items(self):
        """Select nodes from listed asset"""

        items = self.get_nodes(selection=False)
        nodes = []
        for item in items.values():
            nodes.extend(item["nodes"])

        commands.select(nodes)

    def right_mouse_menu(self, pos):
        """Build RMB menu for asset outliner"""

        active = self.view.currentIndex()  # index under mouse
        active = active.sibling(active.row(), 0)  # get first column
        globalpos = self.view.viewport().mapToGlobal(pos)

        menu = QtWidgets.QMenu(self.view)

        # Direct assignment
        apply_action = QtWidgets.QAction(menu, text="Select nodes")
        apply_action.triggered.connect(self.select_asset_from_items)

        if not active.isValid():
            apply_action.setEnabled(False)

        menu.addAction(apply_action)

        menu.exec_(globalpos)
Ejemplo n.º 20
0
class AssetOutliner(QtWidgets.QWidget):

    refreshed = QtCore.Signal()
    selection_changed = QtCore.Signal()

    def __init__(self, parent=None):
        QtWidgets.QWidget.__init__(self, parent)

        layout = QtWidgets.QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(10)

        title = QtWidgets.QLabel("Rigs:")
        title.setAlignment(QtCore.Qt.AlignLeft)
        title.setStyleSheet("font-weight: bold; font-size: 12px")

        model = models.AssetModel()
        view = views.View()
        view.setModel(model)
        view.setSortingEnabled(False)
        view.setHeaderHidden(True)
        view.setMinimumHeight(180)
        view.setIndentation(10)

        layout.addWidget(title)
        layout.addWidget(view)

        selection_model = view.selectionModel()
        selection_model.selectionChanged.connect(self.selection_changed)

        self.view = view
        self.model = model
        self._selection_model = selection_model

        self.setLayout(layout)

    def clear(self):
        self.model.clear()

        # fix looks remaining visible when no items present after "refresh"
        # todo: figure out why this workaround is needed.
        self.selection_changed.emit()

    def clear_selection(self):
        flags = self._selection_model.Clear
        self._selection_model.select(QtCore.QModelIndex(), flags)

    def select_index(self, index, flags=None):
        flags = flags or self._selection_model.ClearAndSelect
        self._selection_model.select(index, flags)

    def add_items(self, items):
        """Add new items to the outliner"""

        self.model.add_items(items)
        self.refreshed.emit()

    def get_selection_model(self):
        return self.view.selectionModel()

    def get_selected_items(self):
        """Get current selected items from view

        Returns:
            list: list of dictionaries
        """

        selection_model = self.view.selectionModel()
        items = [row.data(NODEROLE) for row in selection_model.selectedRows(0)]

        return items

    def get_all_assets(self):
        """Add all items from the current scene"""

        with preserve_expanded_rows(self.view):
            with preserve_selection(self.view):
                self.clear()
                containers = lib.get_containers()
                items = lib.create_node(containers)
                self.add_items(items)

        return len(items) > 0

    def get_selected_assets(self):
        """Add all selected items from the current scene"""
        raise NotImplementedError

    def get_nodes(self, selection=False):
        """Find the nodes in the current scene per asset."""
        raise NotImplementedError

        items = self.get_selected_items()

        # Collect all nodes by hash (optimization)
        # if not selection:
        #     nodes = cmds.ls(dag=True,  long=True)
        # else:
        #     nodes = commands.get_selected_nodes()
        # id_nodes = commands.create_asset_id_hash(nodes)
        #
        # # Collect the asset item entries per asset
        # # and collect the namespaces we'd like to apply
        # assets = dict()
        # asset_namespaces = defaultdict(set)
        # for item in items:
        #     asset_id = str(item["asset"]["_id"])
        #     asset_name = item["asset"]["name"]
        #     asset_namespaces[asset_name].add(item.get("namespace"))
        #
        #     if asset_name in assets:
        #         continue
        #
        #     assets[asset_name] = item
        #     assets[asset_name]["nodes"] = id_nodes.get(asset_id, [])
        #
        # # Filter nodes to namespace (if only namespaces were selected)
        # for asset_name in assets:
        #     namespaces = asset_namespaces[asset_name]
        #
        #     # When None is present there should be no filtering
        #     if None in namespaces:
        #         continue
        #
        #     # Else only namespaces are selected and *not* the top entry so
        #     # we should filter to only those namespaces.
        #     nodes = assets[asset_name]["nodes"]
        #     nodes = [node for node in nodes if
        #              commands.get_namespace_from_node(node) in namespaces]
        #     assets[asset_name]["nodes"] = nodes
        #
        # return assets

    def select_asset_from_items(self):
        """Select nodes from listed asset"""

        # items = self.get_nodes(selection=False)
        # nodes = []
        # for item in items.values():
        #     nodes.extend(item["nodes"])
        #
        # commands.select(nodes)

        raise NotImplementedError
Ejemplo n.º 21
0
class HostSelectorWidget(QtWidgets.QWidget):

    container_picked = QtCore.Signal(dict)
    host_selected = QtCore.Signal()

    def __init__(self, parent=None):
        super(HostSelectorWidget, self).__init__(parent=parent)

        panel = {
            "selection": QtWidgets.QWidget(),
            "container": QtWidgets.QWidget(),
        }

        model = {
            "containerModel": models.HostContainerListModel(),
        }

        widget = {
            "selectionChk": QtWidgets.QCheckBox("Use Selection"),
            "selectionBtn": QtWidgets.QPushButton("Compare Selection"),
            "containerChk": QtWidgets.QCheckBox("Use Container"),
            "containerBox": QtWidgets.QComboBox(),
        }

        widget["containerBox"].setModel(model["containerModel"])

        layout = QtWidgets.QVBoxLayout(panel["selection"])
        layout.addWidget(widget["selectionChk"])
        layout.addWidget(widget["selectionBtn"])

        layout = QtWidgets.QVBoxLayout(panel["container"])
        layout.addWidget(widget["containerChk"])
        layout.addWidget(widget["containerBox"])

        layout = QtWidgets.QVBoxLayout(self)
        layout.addWidget(panel["selection"])
        layout.addWidget(panel["container"])

        # Connect

        widget["selectionChk"].stateChanged.connect(self.on_use_selection)
        widget["containerChk"].stateChanged.connect(self.on_use_container)
        widget["selectionBtn"].pressed.connect(self.on_selection_pressed)
        widget["containerBox"].currentIndexChanged.connect(
            self.on_container_picked)

        # Init

        self.widget = widget
        self.model = model

        widget["containerChk"].setCheckState(QtCore.Qt.Checked)

        # Confirm host registered
        if not has_host():
            # Disable all widgets
            for widget in self.widget.values():
                widget.setEnabled(False)

    def build_container_list(self):
        self.model["containerModel"].reset()
        self.widget["containerBox"].setCurrentIndex(0)

    def on_use_selection(self, state):
        inverse = QtCore.Qt.Checked if not state else QtCore.Qt.Unchecked
        self.widget["containerChk"].blockSignals(True)
        self.widget["containerChk"].setCheckState(inverse)
        self.widget["containerChk"].blockSignals(False)

        self.widget["selectionBtn"].setEnabled(state)
        self.widget["containerBox"].setEnabled(not state)

        if not state:
            self.build_container_list()

    def on_use_container(self, state):
        inverse = QtCore.Qt.Checked if not state else QtCore.Qt.Unchecked
        self.widget["selectionChk"].blockSignals(True)
        self.widget["selectionChk"].setCheckState(inverse)
        self.widget["selectionChk"].blockSignals(False)

        self.widget["selectionBtn"].setEnabled(not state)
        self.widget["containerBox"].setEnabled(state)

        if state:
            self.build_container_list()

    def on_container_picked(self):
        container = self.widget["containerBox"].currentData()
        if container is not None:
            self.container_picked.emit(container)

    def on_selection_pressed(self):
        self.host_selected.emit()
Ejemplo n.º 22
0
class DatabaseSelectorWidget(QtWidgets.QWidget):

    version_changed = QtCore.Signal(io.ObjectId)

    def __init__(self, parent=None):
        super(DatabaseSelectorWidget, self).__init__(parent=parent)

        panel = {
            "silo": QtWidgets.QWidget(),
            "asset": QtWidgets.QWidget(),
            "subset": QtWidgets.QWidget(),
            "version": QtWidgets.QWidget(),
        }

        label = {
            "silo": QtWidgets.QLabel("Silo"),
            "asset": QtWidgets.QLabel("Asset"),
            "subset": QtWidgets.QLabel("Subset"),
            "version": QtWidgets.QLabel("Version"),
        }

        widget = {
            "silo": QtWidgets.QComboBox(),
            "asset": QtWidgets.QComboBox(),
            "subset": QtWidgets.QComboBox(),
            "version": QtWidgets.QComboBox(),
        }

        model = {
            "silo": models.DatabaseDocumentModel(level="silo"),
            "asset": models.DatabaseDocumentModel(level="asset"),
            "subset": models.DatabaseDocumentModel(level="subset"),
            "version": models.DatabaseDocumentModel(level="version"),
        }

        view = {
            "silo": QtWidgets.QListView(),
            "asset": QtWidgets.QListView(),
            "subset": QtWidgets.QListView(),
            "version": QtWidgets.QListView(),
        }

        widget["silo"].setModel(model["silo"])
        widget["asset"].setModel(model["asset"])
        widget["subset"].setModel(model["subset"])
        widget["version"].setModel(model["version"])

        widget["silo"].setView(view["silo"])
        widget["asset"].setView(view["asset"])
        widget["subset"].setView(view["subset"])
        widget["version"].setView(view["version"])

        def build_panel(level):
            label[level].setFixedWidth(60)
            label[level].setAlignment(QtCore.Qt.AlignVCenter |
                                      QtCore.Qt.AlignRight)
            layout = QtWidgets.QHBoxLayout(panel[level])
            layout.addWidget(label[level])
            layout.addWidget(widget[level])
        build_panel("silo")
        build_panel("asset")
        build_panel("subset")
        build_panel("version")

        layout = QtWidgets.QVBoxLayout(self)
        layout.addWidget(panel["silo"])
        layout.addSpacing(-16)
        layout.addWidget(panel["asset"])
        layout.addSpacing(-16)
        layout.addWidget(panel["subset"])
        layout.addSpacing(-16)
        layout.addWidget(panel["version"])

        # Connect

        def connect_index_changed(level, callback):
            widget[level].currentIndexChanged.connect(callback)
        connect_index_changed("silo", self.on_silo_changed)
        connect_index_changed("asset", self.on_asset_changed)
        connect_index_changed("subset", self.on_subset_changed)
        connect_index_changed("version", self.on_version_changed)

        # Init

        self.widget = widget
        self.model = model
        self.view = view

        silo = api.Session.get("AVALON_SILO")
        if silo:
            init_index = self.widget["silo"].findText(silo)
            self.widget["silo"].setCurrentIndex(init_index)

        asset = api.Session.get("AVALON_ASSET")
        if silo and asset:
            init_index = self.widget["asset"].findText(asset)
            self.widget["asset"].setCurrentIndex(init_index)

    def _on_level_changed(self, level, child_level):
        combobox = self.widget[level]
        child_model = self.model[child_level]
        child_box = self.widget[child_level]

        data = combobox.currentData()
        child_model.reset(data)
        child_box.setCurrentIndex(0)

    def on_silo_changed(self):
        self._on_level_changed("silo", "asset")

    def on_asset_changed(self):
        self._on_level_changed("asset", "subset")

    def on_subset_changed(self):
        self._on_level_changed("subset", "version")

    def on_version_changed(self):
        combobox = self.widget["version"]
        data = combobox.currentData()
        if data:
            self.version_changed.emit(data)

    def on_container_picked(self, container):
        if container is None:
            return

        asset = io.find_one({"_id": io.ObjectId(container["assetId"])})
        if asset is None:
            main_logger.error("Asset not found.")
            return

        self.widget["silo"].setCurrentText(asset["silo"])
        self.widget["asset"].setCurrentText(asset["name"])

        subset = io.find_one({"_id": io.ObjectId(container["subsetId"])})
        if subset is None:
            main_logger.error("Subset not found.")
            return

        self.widget["subset"].setCurrentText(subset["name"])
Ejemplo n.º 23
0
class StatusLineWidget(QtWidgets.QWidget):
    """Logging message receiver and displayer

    Args:
        logger: `logging.Logger` instance

    """

    echo = QtCore.Signal(int, str, int)

    def __init__(self, logger, parent=None):
        super(StatusLineWidget, self).__init__(parent)

        icon = _LogLevelIconButton()

        line = QtWidgets.QLineEdit()
        line.setReadOnly(True)
        line.setStyleSheet("""
            QLineEdit {
                border: 0px;
                border-radius: 6px;
                padding: 0 6px;
                color: #AAAAAA;
                background: #363636;
            }
        """)

        body = QtWidgets.QHBoxLayout(self)
        body.addWidget(icon)
        body.addWidget(line)

        handler = _WidgetLogHandler(self)
        logger.addHandler(handler)

        self.icon = icon
        self.line = line

        self.echo.connect(self.on_echo)
        self.icon.clicked.connect(lambda: self._echo(0, "", True))

    def _echo(self, level, log, reset=False):
        if reset:
            self.icon.previous = logging.NOTSET
        self.icon.level = level
        self.icon.update()
        self.line.setText(log)

    def on_echo(self, level, log, dotting=False):

        ALARM = logging.WARNING

        if level >= ALARM and level > self.icon.previous:
            self.icon.previous = level

        if dotting:
            # Display dotting animation
            if log.endswith("....."):
                log = log[:-4]
            else:
                log += "."

            self._echo(level, log)

            # Loop
            animator = (lambda: self.on_echo(level, log, dotting))
            tools.lib.schedule(animator, 300, channel="statusline")

        else:
            self._echo(level, log)

            if level < ALARM:
                # Back to default state
                job = (lambda: self._echo(0, ""))
            else:
                # Keep alarm
                job = (lambda: self._echo(level, log))

            tools.lib.schedule(job, 5000, channel="statusline")
Ejemplo n.º 24
0
class Popup(QtWidgets.QDialog):

    on_show = QtCore.Signal()

    def __init__(self, parent=None, *args, **kwargs):
        super(Popup, self).__init__(parent=parent, *args, **kwargs)
        self.setContentsMargins(0, 0, 0, 0)

        # Layout
        layout = QtWidgets.QHBoxLayout(self)
        layout.setContentsMargins(10, 5, 10, 10)
        message = QtWidgets.QLabel("")
        message.setStyleSheet("""
        QLabel {
            font-size: 12px;
        }
        """)
        show = QtWidgets.QPushButton("Show")
        show.setSizePolicy(QtWidgets.QSizePolicy.Maximum,
                           QtWidgets.QSizePolicy.Maximum)
        show.setStyleSheet("""QPushButton { background-color: #BB0000 }""")

        layout.addWidget(message)
        layout.addWidget(show)

        # Size
        self.resize(400, 40)
        geometry = self.calculate_window_geometry()
        self.setGeometry(geometry)

        self.widgets = {
            "message": message,
            "show": show,
        }

        # Signals
        show.clicked.connect(self._on_show_clicked)

        # Set default title
        self.setWindowTitle("Popup")

    def setMessage(self, message):
        self.widgets['message'].setText(message)

    def _on_show_clicked(self):
        """Callback for when the 'show' button is clicked.

        Raises the parent (if any)

        """

        parent = self.parent()
        self.close()

        # Trigger the signal
        self.on_show.emit()

        if parent:
            parent.raise_()

    def calculate_window_geometry(self):
        """Respond to status changes

        On creation, align window with screen bottom right.

        """

        window = self

        width = window.width()
        width = max(width, window.minimumWidth())

        height = window.height()
        height = max(height, window.sizeHint().height())

        desktop_geometry = QtWidgets.QDesktopWidget().availableGeometry()
        screen_geometry = window.geometry()

        screen_width = screen_geometry.width()
        screen_height = screen_geometry.height()

        # Calculate width and height of system tray
        systray_width = screen_geometry.width() - desktop_geometry.width()
        systray_height = screen_geometry.height() - desktop_geometry.height()

        padding = 10

        x = screen_width - width
        y = screen_height - height

        x -= systray_width + padding
        y -= systray_height + padding

        return QtCore.QRect(x, y, width, height)
Ejemplo n.º 25
0
class AssetOutliner(QtWidgets.QWidget):

    refreshed = QtCore.Signal()
    selection_changed = QtCore.Signal()

    def __init__(self, parent=None):
        QtWidgets.QWidget.__init__(self, parent)

        layout = QtWidgets.QVBoxLayout()

        title = QtWidgets.QLabel("Assets")
        title.setAlignment(QtCore.Qt.AlignCenter)
        title.setStyleSheet("font-weight: bold; font-size: 12px")

        model = models.AssetModel()
        view = views.View()
        view.setModel(model)
        view.customContextMenuRequested.connect(self.right_mouse_menu)
        view.setSortingEnabled(False)
        view.setHeaderHidden(False)
        view.setIndentation(10)

        asset_all = QtWidgets.QPushButton("All Loaded")
        asset_sel = QtWidgets.QPushButton("From Selection")
        asset_all.setCheckable(True)
        asset_sel.setCheckable(True)

        asset_group = QtWidgets.QButtonGroup(self)
        asset_group.addButton(asset_all)
        asset_group.addButton(asset_sel)

        lister_layout = QtWidgets.QHBoxLayout()
        lister_layout.addWidget(asset_all)
        lister_layout.addWidget(asset_sel)

        layout.addWidget(title)
        layout.addLayout(lister_layout)
        layout.addWidget(view)

        # Build connections
        asset_all.clicked.connect(self.on_all_loaded)
        asset_sel.clicked.connect(self.on_selection)

        selection_model = view.selectionModel()
        selection_model.selectionChanged.connect(self.selection_changed)

        self.view = view
        self.model = model

        self.setLayout(layout)

        self.log = logging.getLogger(__name__)

    def clear(self):
        self.model.clear()

        # fix looks remaining visible when no items present after "refresh"
        # todo: figure out why this workaround is needed.
        self.selection_changed.emit()

    def add_items(self, items, by_selection=False):
        """Add new items to the outliner"""

        self.model.add_items(items, by_selection)
        self.refreshed.emit()

    def get_selected_items(self):
        """Get current selected items from view

        Returns:
            list: list of dictionaries
        """

        selection_model = self.view.selectionModel()
        items = [
            self.model.data(index, self.model.ItemRole)
            for index in selection_model.selectedRows(0)
        ]
        return items

    def on_all_loaded(self):
        """Add all items from the current scene"""
        items = list()

        with lib.preserve_expanded_rows(self.view):
            with lib.preserve_selection(self.view):
                self.clear()
                nodes = commands.get_all_asset_nodes()
                items += commands.create_items(nodes)
                self.add_items(items)

        return len(items) > 0

    def on_selection(self):
        """Add all selected items from the current scene"""

        with lib.preserve_expanded_rows(self.view):
            with lib.preserve_selection(self.view):
                self.clear()
                nodes = commands.get_selected_asset_nodes()
                items = commands.create_items(nodes, by_selection=True)
                self.add_items(items, by_selection=True)

    def get_nodes(self):
        """Find the nodes in the current scene per asset."""

        asset_nodes = dict()

        for item in self.get_selected_items():
            asset_name = item["asset"]["name"]

            if "subset" in item:
                key = (asset_name, item["namespace"])
                asset_nodes[key] = item

            else:
                for child in item.children():
                    key = (asset_name, child["namespace"])
                    asset_nodes[key] = child

        return asset_nodes

    def select_asset_from_items(self):
        asset_nodes = self.get_nodes()
        nodes = set()
        for item in asset_nodes.values():
            nodes.update(item["selectBack"])

        commands.select(list(nodes))

    def remove_look_from_items(self):

        asset_nodes = self.get_nodes()
        nodes = []
        asset_ids = set()
        for item in asset_nodes.values():
            nodes.extend(item["nodes"])
            asset_ids.add(str(item["asset"]["_id"]))

        commands.remove_look(nodes, asset_ids)

    def right_mouse_menu(self, pos):
        """Build RMB menu for asset outliner"""

        active = self.view.currentIndex()  # index under mouse
        active = active.sibling(active.row(), 0)  # get first column
        globalpos = self.view.viewport().mapToGlobal(pos)

        menu = QtWidgets.QMenu(self.view)

        apply_action = QtWidgets.QAction(menu, text="Select nodes")
        apply_action.triggered.connect(self.select_asset_from_items)

        remove_action = QtWidgets.QAction(menu, text="Remove look")
        remove_action.triggered.connect(self.remove_look_from_items)

        if not active.isValid():
            apply_action.setEnabled(False)
            remove_action.setEnabled(False)

        menu.addAction(apply_action)
        menu.addAction(remove_action)

        menu.exec_(globalpos)
Ejemplo n.º 26
0
class AddTaskIconDialog(QtWidgets.QWidget):

    addTask = QtCore.Signal(dict)

    def __init__(self, parent=None):
        QtWidgets.QWidget.__init__(self, parent=parent)

        self.setWindowFlag(QtCore.Qt.Dialog)

        if parent:
            self.setStyleSheet(parent.styleSheet())

        layout = QtWidgets.QVBoxLayout()

        task_icon_layout = QtWidgets.QHBoxLayout()
        task_name = QtWidgets.QLineEdit()
        icon_name = QtWidgets.QComboBox()

        icon_preview_button = QtWidgets.QPushButton()
        icon_preview_button.setEnabled(False)
        icon_preview_button.setStyleSheet(style.preview_button)

        task_icon_layout.addWidget(task_name)
        task_icon_layout.addWidget(icon_name)
        task_icon_layout.addWidget(icon_preview_button)

        buttons_layout = QtWidgets.QHBoxLayout()
        accept_button = QtWidgets.QPushButton("Accept")
        cancel_button = QtWidgets.QPushButton("Cancel")

        buttons_layout.addWidget(accept_button)
        buttons_layout.addWidget(cancel_button)

        layout.addLayout(task_icon_layout)
        layout.addLayout(buttons_layout)

        self.setLayout(layout)

        self.task_name = task_name
        self.icon_name = icon_name
        self.icon_preview = icon_preview_button

        self.fontlib = {}

        self.connect_signals()

        self.refresh()

    def refresh(self):

        self._install_fontlib()
        self._populate_icons()

    def _install_fontlib(self):

        # TODO: fix qta thingy
        package = os.path.dirname(qta.__file__)
        fonts = os.path.join(package, "fonts",
                             "fontawesome-webfont-charmap.json")

        with open(fonts, "r") as f:
            font_lib = json.load(f)

        self.fontlib = font_lib

    def _populate_icons(self):
        self.icon_name.blockSignals(True)
        icons = [""] + list(self.fontlib.keys())
        self.icon_name.addItems(icons)
        self.icon_name.blockSignals(False)

    def connect_signals(self):
        return

    def on_accept(self):

        # TODO: make preflight check
        self.addTask.emit({
            "name": self.task_name.currentText(),
            "icon": self.icon_name.currentText()
        })

        self.close()

    def _create_preview(self):

        icon_name = "fa.{}".format(self.icon_name.currentText())
        new_icon = qta.icon(icon_name, color=style.colors.dark)
        self.icon_preview.setIcon(new_icon)
Ejemplo n.º 27
0
class LookOutliner(QtWidgets.QWidget):

    menu_apply_action = QtCore.Signal()

    def __init__(self, parent=None):
        QtWidgets.QWidget.__init__(self, parent)

        # look manager layout
        layout = QtWidgets.QVBoxLayout(self)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(10)

        # Looks from database
        title = QtWidgets.QLabel("Looks")
        title.setAlignment(QtCore.Qt.AlignCenter)
        title.setStyleSheet("font-weight: bold; font-size: 12px")
        title.setAlignment(QtCore.Qt.AlignCenter)

        model = models.LookModel()

        # Proxy for dynamic sorting
        proxy = QtCore.QSortFilterProxyModel()
        proxy.setSourceModel(model)

        view = views.View()
        view.setModel(proxy)
        view.setMinimumHeight(180)
        view.setToolTip("Use right mouse button menu for direct actions")
        view.customContextMenuRequested.connect(self.right_mouse_menu)
        view.sortByColumn(0, QtCore.Qt.AscendingOrder)

        layout.addWidget(title)
        layout.addWidget(view)

        self.view = view
        self.model = model

    def clear(self):
        self.model.clear()

    def add_items(self, items):
        self.model.add_items(items)

    def get_selected_items(self):
        """Get current selected items from view

        Returns:
            list: list of dictionaries
        """

        datas = [i.data(TreeModel.ItemRole) for i in self.view.get_indices()]
        items = [d for d in datas if d is not None]  # filter Nones

        return items

    def right_mouse_menu(self, pos):
        """Build RMB menu for look view"""

        active = self.view.currentIndex()  # index under mouse
        active = active.sibling(active.row(), 0)  # get first column
        globalpos = self.view.viewport().mapToGlobal(pos)

        if not active.isValid():
            return

        menu = QtWidgets.QMenu(self.view)

        # Direct assignment
        apply_action = QtWidgets.QAction(menu, text="Assign looks..")
        apply_action.triggered.connect(self.menu_apply_action)

        menu.addAction(apply_action)

        menu.exec_(globalpos)
Ejemplo n.º 28
0
class ComparingTable(QtWidgets.QWidget):

    picked = QtCore.Signal(str, dict)
    focus_enabled = QtCore.Signal(int)

    def __init__(self, parent=None):
        super(ComparingTable, self).__init__(parent=parent)

        data = {
            "model": models.ComparerModel(),
            "proxy": QtCore.QSortFilterProxyModel(),
            "view": QtWidgets.QTreeView(),
            "diff": delegates.DiffDelegate(),
            "path": delegates.PathTextDelegate(),
        }

        data["view"].setIndentation(2)
        data["view"].setStyleSheet("""
            QTreeView::item{
                padding: 4px 1px;
                border: 0px;
            }
        """)
        data["view"].setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        data["view"].setAllColumnsShowFocus(False)
        data["view"].setAlternatingRowColors(False)
        data["view"].setSortingEnabled(True)
        data["view"].setSelectionBehavior(
            QtWidgets.QAbstractItemView.SelectItems)
        data["view"].setSelectionMode(
            QtWidgets.QAbstractItemView.ExtendedSelection)

        # Delegate
        diff_delegate = data["diff"]
        column = data["model"].Columns.index("diff")
        data["view"].setItemDelegateForColumn(column, diff_delegate)

        path_delegate = data["path"]
        column = data["model"].Columns.index(SIDE_A)
        data["view"].setItemDelegateForColumn(column, path_delegate)
        column = data["model"].Columns.index(SIDE_B)
        data["view"].setItemDelegateForColumn(column, path_delegate)

        # Set Model
        data["proxy"].setSourceModel(data["model"])
        data["view"].setModel(data["proxy"])

        layout = QtWidgets.QHBoxLayout(self)
        layout.addWidget(data["view"])

        # Init
        header = data["view"].header()
        header.setMinimumSectionSize(data["diff"].ICON_SPACE)
        header.setSectionResizeMode(0, QtWidgets.QHeaderView.Stretch)
        header.setSectionResizeMode(1, QtWidgets.QHeaderView.Fixed)
        header.setSectionResizeMode(2, QtWidgets.QHeaderView.Stretch)
        header.setSectionsMovable(False)

        data["view"].setColumnWidth(1, data["diff"].ICON_SPACE)

        self.data = data
        self._focusing = False

        # Connect
        data["view"].customContextMenuRequested.connect(self.on_context_menu)
        data["view"].clicked.connect(self.on_focused)
        self.focus_enabled.connect(self.on_focus_enabled)

    def on_context_menu(self, point):
        point_index = self.data["view"].indexAt(point)
        if not point_index.isValid():
            return

        menu = QtWidgets.QMenu(self)

        if lib.select_from_host is not NotImplemented:
            select_action = QtWidgets.QAction("Select", menu)
            select_action.triggered.connect(self.act_select_nodes)

            menu.addAction(select_action)

        # Show the context action menu
        global_point = self.data["view"].mapToGlobal(point)
        action = menu.exec_(global_point)
        if not action:
            return

    def on_version_changed(self, side, version_id):
        if version_id is not None:
            profile = lib.profile_from_database(version_id)
        else:
            profile = None
        self.data["model"].refresh_side(side, profile)
        self.update()

    def on_container_picked(self, side, container):
        if container is not None:
            profile = lib.profile_from_host(container)
        else:
            profile = None
        self.data["model"].refresh_side(side, profile, host=True)
        self.update()

    def on_host_selected(self, side):
        profile = lib.profile_from_host()
        self.data["model"].refresh_side(side, profile, host=True)
        self.update()

    def act_select_nodes(self):
        selection_model = self.data["view"].selectionModel()
        selection = selection_model.selection()
        source_selection = self.data["proxy"].mapSelectionToSource(selection)

        model = self.data["model"]
        names = list()
        for index in source_selection.indexes():
            name = index.data(model.HostSelectRole)
            if name is not None:
                names.append(name)

        lib.select_from_host(names)

    def on_focus_enabled(self, enable):
        if not enable:
            self.data["model"].set_fouced(SIDE_A, None)
            self.data["model"].set_fouced(SIDE_B, None)
        self._focusing = enable

    def on_focused(self, index):
        if not self._focusing:
            return

        column = index.column()
        if column == 1:
            # Diff section
            return

        side = SIDE_A if column == 0 else SIDE_B
        side_data = models.SIDE_A_DATA if column == 0 else models.SIDE_B_DATA

        selection_model = self.data["view"].selectionModel()
        selection = selection_model.selection()
        selected = index in selection.indexes()

        if selected:
            node = index.data(models.ComparerModel.ItemRole)
            data = node.get(side_data)
            index = self.data["proxy"].mapToSource(index) if data else None
            self.data["model"].set_fouced(side, index)
        else:
            data = None
            self.data["model"].set_fouced(side, None)

        self.picked.emit(side, data)
        self.update()

    def table_to_dict(self):
        return self.data["model"].to_dict()
Ejemplo n.º 29
0
class Popup(QtWidgets.QDialog):
    """A Popup that moves itself to bottom right of screen on show event.

    The UI contains a message label and a red highlighted button to "show"
    or perform another custom action from this pop-up.

    """

    on_clicked = QtCore.Signal()

    def __init__(self, parent=None, *args, **kwargs):
        super(Popup, self).__init__(parent=parent, *args, **kwargs)
        self.setContentsMargins(0, 0, 0, 0)

        # Layout
        layout = QtWidgets.QHBoxLayout(self)
        layout.setContentsMargins(10, 5, 10, 10)

        # Increase spacing slightly for readability
        layout.setSpacing(10)

        message = QtWidgets.QLabel("")
        message.setStyleSheet("""
        QLabel {
            font-size: 12px;
        }
        """)
        button = QtWidgets.QPushButton("Show")
        button.setSizePolicy(QtWidgets.QSizePolicy.Maximum,
                             QtWidgets.QSizePolicy.Maximum)
        button.setStyleSheet("""QPushButton { background-color: #BB0000 }""")

        layout.addWidget(message)
        layout.addWidget(button)

        # Default size
        self.resize(400, 40)

        self.widgets = {
            "message": message,
            "button": button,
        }

        # Signals
        button.clicked.connect(self._on_clicked)

        # Set default title
        self.setWindowTitle("Popup")

    def setMessage(self, message):
        self.widgets['message'].setText(message)

    def setButtonText(self, text):
        self.widgets["button"].setText(text)

    def _on_clicked(self):
        """Callback for when the 'show' button is clicked.

        Raises the parent (if any)

        """

        parent = self.parent()
        self.close()

        # Trigger the signal
        self.on_clicked.emit()

        if parent:
            parent.raise_()

    def showEvent(self, event):

        # Position popup based on contents on show event
        geo = self.calculate_window_geometry()
        self.setGeometry(geo)

        return super(Popup, self).showEvent(event)

    def calculate_window_geometry(self):
        """Respond to status changes

        On creation, align window with screen bottom right.

        """

        window = self

        width = window.width()
        width = max(width, window.minimumWidth())

        height = window.height()
        height = max(height, window.sizeHint().height())

        desktop_geometry = QtWidgets.QDesktopWidget().availableGeometry()
        screen_geometry = window.geometry()

        screen_width = screen_geometry.width()
        screen_height = screen_geometry.height()

        # Calculate width and height of system tray
        systray_width = screen_geometry.width() - desktop_geometry.width()
        systray_height = screen_geometry.height() - desktop_geometry.height()

        padding = 10

        x = screen_width - width
        y = screen_height - height

        x -= systray_width + padding
        y -= systray_height + padding

        return QtCore.QRect(x, y, width, height)
Ejemplo n.º 30
0
class JobSourceModel(TreeModel):  # QueueModel ?

    MAX_CONNECTIONS = 10

    staging = QtCore.Signal()
    staged = QtCore.Signal()
    canceling = QtCore.Signal()
    canceled = QtCore.Signal()

    STAGING_COLUMNS = [
        "project",
        "type",
        "description",
        "site",
        "count",
        "size",
    ]

    StagingDisplayRole = QtCore.Qt.UserRole + 10
    StagingSortRole = QtCore.Qt.UserRole + 11

    UPLOAD_COLUMNS = [
        "project",
        "type",
        "description",
        "status",  # This has been hidden
        "progress",
    ]

    UploadDisplayRole = QtCore.Qt.UserRole + 20
    UploadSortRole = QtCore.Qt.UserRole + 21
    UploadDecorationRole = QtCore.Qt.UserRole + 22
    UploadErrorRole = QtCore.Qt.UserRole + 23

    STATUS = [
        "staging",
        "pending",
        "uploading",
        "errored",
        "completed",
        "endWithError",
    ]

    STATUS_ICON = [
        ("meh-o", "#999999"),
        ("clock-o", "#95A1A5"),
        ("paper-plane", "#52D77B"),
        ("paper-plane", "#ECA519"),
        ("check-circle", "#5AB6E4"),
        ("warning", "#EC534E"),
    ]

    def __init__(self, parent=None):
        super(JobSourceModel, self).__init__(parent=parent)

        self.jobsref = WeakValueDictionary()
        self.pipe_in = Queue()
        self.pipe_out = Queue()

        self.producer = _PackageProducer()
        self.consumers = [_Uploader(self.pipe_in, self.pipe_out, id)
                          for id in range(self.MAX_CONNECTIONS)]
        self.consume()

        self.status_icon = [
            qtawesome.icon("fa.{}".format(icon), color=color)
            for icon, color in self.STATUS_ICON
        ]

    def is_staging(self):
        return self.producer.producing

    def is_uploading(self):
        return any(c.consuming for c in self.consumers)

    def stage(self, job_file):
        """
        Args:
            job_file (str): JSON file

        """
        self.staging.emit()

        # Start staging
        self.producer.start(resource=job_file,
                            on_produce=self._append,
                            on_complete=self.staged.emit)

    def _append(self, data):

        package = PackageItem(data)

        # Check duplicated
        all_nodes = self._root_item.children()
        if package in all_nodes:
            # If duplicated package has completed, allow to stage
            # again.
            find = list(reversed(all_nodes))
            if find[find.index(package)]["status"] <= 2:
                return

        # Start
        root = QtCore.QModelIndex()
        last = self.rowCount(root)

        self.beginInsertRows(root, last, last)
        self.add_child(package)
        self.endInsertRows()

    def stop(self):
        """Stop all activities"""
        if self.producer.producing:
            self.producer.stop()

        for consumer in self.consumers:
            consumer.stop()

        if self.is_staging() or self.is_uploading():
            # Wait till they both stopped.
            self.canceling.emit()
            while self.is_staging() or self.is_uploading():
                pass
            self.canceled.emit()

    def pending(self, index, skip_exists):
        package = self.data(index, self.ItemRole)
        package["status"] = 1
        self.dataChanged.emit(index, index, list())

        for job in package.jobs:
            job.skip_exists = skip_exists
            self.pipe_in.put(job)
            self.jobsref[job._id] = job

    def requeue_failed(self, package):
        for job in package.jobs:
            if job.result in (0, 1):
                # (TODO) Maybe add another list attribute to hold failed
                #        job in package ?
                continue
            # Reset
            job.transferred = 0
            job.result = 0
            # Requeue
            self.pipe_in.put(job)
            self.jobsref[job._id] = job

    def requeue_all(self, package):
        for job in package.jobs:
            # Reset
            job.transferred = 0
            job.result = 0
            # Requeue
            self.pipe_in.put(job)
            self.jobsref[job._id] = job

    def consume(self):
        for c in self.consumers:
            c.start()

        def update():
            while True:
                id, progress, result, process_id = self.pipe_out.get()
                job = self.jobsref[id]
                job.transferred = progress
                job.result = result

                if result == 0:
                    # Still uploading
                    self.consumers[process_id].consuming = True
                else:
                    # Upload completed or error occurred
                    self.consumers[process_id].consuming = False

        updator = threading.Thread(target=update, daemon=True)
        updator.start()

    def clear_stage(self):
        all_nodes = self._root_item.children()

        if all(n.get("status", 0) == 0 for n in all_nodes):
            # All staged, clear all
            self.clear()
            return

        # Remove staged only
        root = QtCore.QModelIndex()
        for node in list(all_nodes):
            if node.get("status", 0) == 0:
                row = node.row()
                self.beginRemoveRows(root, row, row)
                all_nodes.remove(node)
                self.endRemoveRows()

    def columnCount(self, parent):
        return max(len(self.STAGING_COLUMNS), len(self.UPLOAD_COLUMNS))

    def data(self, index, role):
        if not index.isValid():
            return

        if role == self.StagingDisplayRole or role == self.StagingSortRole:
            node = index.internalPointer()
            column = index.column()
            key = self.STAGING_COLUMNS[column]

            return node.get(key, None)

        if role == self.UploadDisplayRole or role == self.UploadSortRole:
            node = index.internalPointer()
            column = index.column()
            key = self.UPLOAD_COLUMNS[column]

            value = node.get(key, None)
            if key == "progress":
                return value()
            return value

        if role == self.UploadDecorationRole:
            # Put icon to 'progress' column
            if index.column() == 4:
                node = index.internalPointer()
                status = node.get("status", 0)
                return self.status_icon[status]

        if role == self.UploadErrorRole:
            node = index.internalPointer()
            if any(job.result not in (0, 1) for job in node.jobs):
                return node  # Only return package that has failed job
            else:
                return None

        return super(JobSourceModel, self).data(index, role)

    def setData(self, index, value, role):
        """Change the data on the nodes.

        Returns:
            bool: Whether the edit was successful

        """
        if index.isValid():
            if role == self.StagingDisplayRole:
                node = index.internalPointer()
                column = index.column()

                key = self.STAGING_COLUMNS[column]
                node[key] = value
                # passing `list()` for PyQt5 (see PYSIDE-462)
                self.dataChanged.emit(index, index, list())

                return True  # must return true if successful

            if role == self.UploadDisplayRole:
                node = index.internalPointer()
                column = index.column()

                key = self.UPLOAD_COLUMNS[column]
                node[key] = value
                # passing `list()` for PyQt5 (see PYSIDE-462)
                self.dataChanged.emit(index, index, list())

                return True  # must return true if successful

        return False