Ejemplo n.º 1
0
class OptionsWidget(QtWidgets.QFrame):

    accepted = QtCore.Signal(object)

    OptionWidgetMap = {'label': LabelOption}

    def __init__(self, *args, **kwargs):
        super(OptionsWidget, self).__init__(*args, **kwargs)

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

        self.setStyleSheet(STYLE)
        self.setLayout(layout)

        self._widgets = []
        self._validator = None

        self._optionsFrame = QtWidgets.QFrame(self)
        self._optionsFrame.setObjectName('optionsFrame')

        layout = QtWidgets.QVBoxLayout(self._optionsFrame)
        self._optionsFrame.setLayout(layout)

        self.layout().addWidget(self._optionsFrame)

    def reset(self):
        """Reset all option widgets back to their default value."""
        for widget in self._widgets:
            widget.reset()
        self.validate()

    def setOptions(self, options):
        """Set the options for the widget."""
        self._options = options

        for option in options:

            cls = self.OptionWidgetMap.get(option.get('type', 'label'))

            widget = cls()
            widget.setOption(option)

            self._widgets.append(widget)

            callback = functools.partial(self._optionChanged, widget)
            widget.valueChanged.connect(callback)

            self._optionsFrame.layout().addWidget(widget)

        self._optionsFrame.layout().addStretch(0)

    def _optionChanged(self, widget):
        """
        Triggered when the given option widget changes value.
        
        :type widget: OptionWidget 
        """
        self.validate()

    def accept(self):
        """Accept the current options"""
        self.emitAcceptedCallback()

    def closeEvent(self, event):
        super(OptionsWidget, self).closeEvent(event)

    def setValidator(self, validator):
        """
        Set the validator for the options.
        
        :type validator: func
        """
        self._validator = validator

    def validate(self):
        """Validate the current options using the validator."""
        if self._validator:
            options = self.options()
            state = self._validator(**options)
            self._setState(state)
        else:
            logger.warning("No validator set.")

    def value(self, name):
        """
        Get the value for the given widget name.
        
        :type name: str 
        :rtype: object 
        """
        widget = self.widget(name)
        return widget.value()

    def widget(self, name):
        """
        Get the widget for the given widget name.
        
        :type name: str 
        :rtype: OptionWidget 
        """
        for widget in self._widgets:
            if widget.options().get('name') == name:
                return widget

    def options(self):
        """
        Get all the option data.
        
        :rtype: dict 
        """
        options = {}
        for widget in self._widgets:
            options[widget.option().get('name')] = widget.value()
        return options

    def state(self):
        """
        Get the current state.
        
        :rtype: dict 
        """
        state = {}
        for widget in self._widgets:
            name = widget.option().get('name')
            state.setdefault(name, {})
            state[name]['value'] = widget.value()
        return state

    def setState(self, state):
        """
        Set the current state.
        
        :type state: dict 
        """
        self._setState(state)
        self.validate()

    def _setState(self, state):
        for widget in self._widgets:
            widget.blockSignals(True)

        for widget in self._widgets:
            name = widget.option().get('name')
            widget.setState(state.get(name, {}))

        for widget in self._widgets:
            widget.blockSignals(False)
Ejemplo n.º 2
0
class BaseItemSignals(QtCore.QObject):
    """"""
    loadValueChanged = QtCore.Signal(object, object)
Ejemplo n.º 3
0
class SearchWidget(QtWidgets.QLineEdit):

    DEFAULT_PLACEHOLDER_TEXT = "Search"

    searchChanged = QtCore.Signal()

    def __init__(self, *args):
        QtWidgets.QLineEdit.__init__(self, *args)

        self._iconPadding = 6
        self._iconButton = QtWidgets.QPushButton(self)
        self._iconButton.clicked.connect(self._iconClicked)
        self._searchFilter = studioqt.SearchFilter("")

        icon = studioqt.icon("search")
        self.setIcon(icon)

        self._clearButton = QtWidgets.QPushButton(self)
        self._clearButton.setCursor(QtCore.Qt.ArrowCursor)
        icon = studioqt.icon("cross")
        self._clearButton.setIcon(icon)
        self._clearButton.setToolTip("Clear all search text")
        self._clearButton.clicked.connect(self._clearClicked)

        self.setPlaceholderText(self.DEFAULT_PLACEHOLDER_TEXT)

        self.textChanged.connect(self._textChanged)
        self.searchChanged = self.searchFilter().searchChanged

        self.update()

    def update(self):
        self.updateIconColor()
        self.updateClearButton()

    def updateIconColor(self):
        """
        Update the color of the icons from the current palette.

        :rtype: None
        """
        color = self.palette().color(self.foregroundRole())
        color = studioqt.Color.fromColor(color)
        self.setIconColor(color)

    def _clearClicked(self):
        """
        Triggered when the user clicks the cross icon.

        :rtype: None
        """
        self.setText("")
        self.setFocus()

    def _iconClicked(self):
        """
        Triggered when the user clicks on the icon.

        :rtype: None
        """
        if not self.hasFocus():
            self.setFocus()

    def _textChanged(self, text):
        """
        Triggered when the text changes.

        :type text: str
        :rtype: None
        """
        self.searchFilter().setPattern(text)
        self.updateClearButton()

    def updateClearButton(self):
        """
        Update the clear button depending on the current text.

        :rtype: None
        """
        text = self.text()
        if text:
            self._clearButton.show()
        else:
            self._clearButton.hide()

    def contextMenuEvent(self, event):
        """
        Triggered when the user right clicks on the search widget.

        :type event: QtCore.QEvent
        :rtype: None
        """
        self.showContextMenu()

    def setSpaceOperator(self, operator):
        """
        Set the space operator for the search filter.

        :type operator: studioqt.SearchFilter.Operator
        :rtype: None
        """
        self._searchFilter.setSpaceOperator(operator)

    def createSpaceOperatorMenu(self, parent=None):
        """
        Return the menu for changing the space operator.

        :type parent: QGui.QMenu
        :rtype: QGui.QMenu
        """
        searchFilter = self.searchFilter()

        menu = QtWidgets.QMenu(parent)
        menu.setTitle("Space Operator")

        # Create the space operator for the OR operator
        action = QtWidgets.QAction(menu)
        action.setText("OR")
        action.setCheckable(True)

        callback = partial(self.setSpaceOperator, searchFilter.Operator.OR)
        action.triggered.connect(callback)

        if searchFilter.spaceOperator() == searchFilter.Operator.OR:
            action.setChecked(True)

        menu.addAction(action)

        # Create the space operator for the AND operator
        action = QtWidgets.QAction(menu)
        action.setText("AND")
        action.setCheckable(True)

        callback = partial(self.setSpaceOperator, searchFilter.Operator.AND)
        action.triggered.connect(callback)

        if searchFilter.spaceOperator() == searchFilter.Operator.AND:
            action.setChecked(True)

        menu.addAction(action)

        return menu

    def showContextMenu(self):
        """
        Create and show the context menu for the search widget.

        :rtype QtWidgets.QAction
        """
        menu = QtWidgets.QMenu(self)

        subMenu = self.createStandardContextMenu()
        subMenu.setTitle("Edit")
        menu.addMenu(subMenu)

        subMenu = self.createSpaceOperatorMenu(menu)
        menu.addMenu(subMenu)

        point = QtGui.QCursor.pos()
        action = menu.exec_(point)

        return action

    def searchFilter(self):
        """
        Return the search filter for the widget.

        :rtype: studioqt.SearchFilter
        """
        return self._searchFilter

    def setIcon(self, icon):
        """
        Set the icon for the search widget.

        :type icon: QtWidgets.QIcon
        :rtype: None
        """
        self._iconButton.setIcon(icon)

    def setIconColor(self, color):
        """
        Set the icon color for the search widget icon.

        :type color: QtGui.QColor
        :rtype: None
        """
        icon = self._iconButton.icon()
        icon = studioqt.Icon(icon)
        icon.setColor(color)
        self._iconButton.setIcon(icon)

        icon = self._clearButton.icon()
        icon = studioqt.Icon(icon)
        icon.setColor(color)
        self._clearButton.setIcon(icon)

    def settings(self):
        """
        Return a dictionary of the current widget state.

        :rtype: dict
        """
        settings = {"text": self.text()}
        settings["searchFilter"] = self.searchFilter().settings()
        return settings

    def setSettings(self, settings):
        """
        Restore the widget state from a settings dictionary.

        :type settings: dict
        :rtype: None
        """
        searchFilterSettings = settings.get("searchFilter", None)
        if searchFilterSettings is not None:
            self.searchFilter().setSettings(searchFilterSettings)

        text = settings.get("text", "")
        self.setText(text)

    def resizeEvent(self, event):
        """
        Reimplemented so the icon maintains the same height as the widget.

        :type event:  QtWidgets.QResizeEvent
        :rtype: None
        """
        QtWidgets.QLineEdit.resizeEvent(self, event)

        self.setTextMargins(self.height(), 0, 0, 0)
        size = QtCore.QSize(self.height(), self.height())

        self._iconButton.setIconSize(size)
        self._iconButton.setFixedSize(size)

        self._clearButton.setIconSize(size)

        x = self.width() - self.height()
        self._clearButton.setGeometry(x, 0, self.height(), self.height())
Ejemplo n.º 4
0
def showMessageBox(parent,
                   title,
                   text,
                   width=None,
                   height=None,
                   buttons=None,
                   headerIcon=None,
                   headerColor=None,
                   enableDontShowCheckBox=False,
                   force=False):
    """
    Open a question message box with the given options.

    :type parent: QWidget
    :type title: str
    :type text: str
    :type buttons: list[QMessageBox.StandardButton]
    :type headerIcon: str
    :type headerColor: str
    :type enableDontShowCheckBox: bool
    :type force: bool

    :rtype: MessageBox
    """
    settings = QtCore.QSettings(SETTINGS_PATH, QtCore.QSettings.IniFormat)

    key = 'MessageBox/{}/'.format(title.replace(" ", "_"))

    clickedButton = int(settings.value(key + "clickedButton") or -1)
    dontShowAgain = settings.value(key + "dontShowAgain")

    if isinstance(dontShowAgain, basestring):
        dontShowAgain = dontShowAgain == "true"

    # Force show the dialog if the user is holding the ctrl key down
    if studioqt.isControlModifier() or studioqt.isAltModifier():
        force = True

    if force or not dontShowAgain or not enableDontShowCheckBox:

        mb = createMessageBox(parent,
                              title,
                              text,
                              width=width,
                              height=height,
                              buttons=buttons,
                              headerIcon=headerIcon,
                              headerColor=headerColor,
                              enableDontShowCheckBox=enableDontShowCheckBox)

        mb.exec_()
        mb.close()

        # Save the button that was clicked by the user
        clickedButton = mb.clickedStandardButton()
        settings.setValue(key + "clickedButton", clickedButton)

        # Save the dont show again checked state
        dontShowAgain = mb.isDontShowCheckboxChecked()
        settings.setValue(key + "dontShowAgain", dontShowAgain)

        settings.sync()

    return clickedButton
Ejemplo n.º 5
0
class CombinedWidget(QtWidgets.QWidget):

    IconMode = "icon"
    TableMode = "table"

    DEFAULT_PADDING = 5

    DEFAULT_ZOOM_AMOUNT = 90
    DEFAULT_TEXT_HEIGHT = 20
    DEFAULT_WHEEL_SCROLL_STEP = 2

    DEFAULT_MIN_SPACING = 0
    DEFAULT_MAX_SPACING = 50

    DEFAULT_MIN_LIST_SIZE = 15
    DEFAULT_MIN_ICON_SIZE = 50

    itemClicked = QtCore.Signal(object)
    itemDoubleClicked = QtCore.Signal(object)

    zoomChanged = QtCore.Signal(object)
    spacingChanged = QtCore.Signal(object)

    groupClicked = QtCore.Signal(object)

    def __init__(self, *args):
        QtWidgets.QWidget.__init__(self, *args)

        self._dpi = 1
        self._padding = self.DEFAULT_PADDING

        w, h = self.DEFAULT_ZOOM_AMOUNT, self.DEFAULT_ZOOM_AMOUNT

        self._iconSize = QtCore.QSize(w, h)
        self._zoomAmount = self.DEFAULT_ZOOM_AMOUNT
        self._isItemTextVisible = True

        self._treeWidget = CombinedTreeWidget(self)

        self._listView = CombinedListView(self)
        self._listView.setTreeWidget(self._treeWidget)

        self._delegate = CombinedItemDelegate()
        self._delegate.setCombinedWidget(self)

        self._listView.setItemDelegate(self._delegate)
        self._treeWidget.setItemDelegate(self._delegate)

        self._toastWidget = studioqt.ToastWidget(self)
        self._toastWidget.hide()
        self._toastEnabled = True

        self._textColor = QtGui.QColor(255, 255, 255, 200)
        self._textSelectedColor = QtGui.QColor(255, 255, 255, 200)
        self._backgroundColor = QtGui.QColor(255, 255, 255, 30)
        self._backgroundHoverColor = QtGui.QColor(255, 255, 255, 35)
        self._backgroundSelectedColor = QtGui.QColor(30, 150, 255)

        layout = QtWidgets.QHBoxLayout(self)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self._treeWidget)
        layout.addWidget(self._listView)

        header = self.treeWidget().header()
        header.sortIndicatorChanged.connect(self._sortIndicatorChanged)

        self.setLayout(layout)

        self.listView().itemClicked.connect(self._itemClicked)
        self.listView().itemDoubleClicked.connect(self._itemDoubleClicked)

        self.treeWidget().itemClicked.connect(self._itemClicked)
        self.treeWidget().itemDoubleClicked.connect(self._itemDoubleClicked)

        self.itemMoved = self._listView.itemMoved
        self.itemDropped = self._listView.itemDropped
        self.itemSelectionChanged = self._treeWidget.itemSelectionChanged

    def _sortIndicatorChanged(self):
        """
        Triggered when the sort indicator changes.

        :rtype: None
        """
        pass

    def _itemClicked(self, item):
        """
        Triggered when the given item has been clicked.

        :type item: studioqt.CombinedWidgetItem
        :rtype: None
        """
        if isinstance(item, studioqt.CombinedWidgetItemGroup):
            self.groupClicked.emit(item)
        else:
            self.itemClicked.emit(item)

    def _itemDoubleClicked(self, item):
        """
        Triggered when the given item has been double clicked.

        :type item: studioqt.CombinedWidgetItem
        :rtype: None
        """
        self.itemDoubleClicked.emit(item)

    def setToastEnabled(self, enabled):
        """
        :type enabled: bool
        :rtype: None
        """
        self._toastEnabled = enabled

    def toastEnabled(self):
        """
        :rtype: bool
        """
        return self._toastEnabled

    def showToastMessage(self, text, duration=500):
        """
        Show a toast with the given text for the given duration.

        :type text: str
        :type duration: None or int
        :rtype: None
        """
        if self.toastEnabled():
            self._toastWidget.setDuration(duration)
            self._toastWidget.setText(text)
            self._toastWidget.show()

    def sortOrder(self):
        """
        Reimplemented for convenience.

        Calls self.treeWidget().sortOrder()
        """
        return self.treeWidget().sortOrder()

    def sortColumn(self):
        """
        Reimplemented for convenience.

        Calls self.treeWidget().sortColumn()
        """
        return self.treeWidget().sortColumn()

    def sortByColumn(self, *args, **kwargs):
        """
        Reimplemented for convenience.

        Calls self.treeWidget().sortByColumn(*args)
        """
        self.treeWidget().sortByColumn(*args, **kwargs)

    def groupOrder(self):
        """
        Reimplemented for convenience.

        Calls self.treeWidget().groupOrder()
        """
        return self.treeWidget().groupOrder()

    def groupColumn(self):
        """
        Reimplemented for convenience.

        Calls self.treeWidget().groupColumn()
        """
        return self.treeWidget().groupColumn()

    def groupByColumn(self, *args):
        """
        Reimplemented for convenience.

        Calls self.treeWidget().groupByColumn(*args)
        """
        self.treeWidget().groupByColumn(*args)

    def columnFromLabel(self, *args):
        """
        Reimplemented for convenience.
        
        :return: int 
        """
        return self.treeWidget().columnFromLabel(*args)

    def setColumnHidden(self, column, hidden):
        """
        Reimplemented for convenience.

        Calls self.treeWidget().setColumnHidden(column, hidden)
        """
        self.treeWidget().setColumnHidden(column, hidden)

    def setLocked(self, value):
        """
        Disables drag and drop.

        :Type value: bool
        :rtype: None
        """
        self.listView().setDragEnabled(not value)
        self.listView().setDropEnabled(not value)

    def scrollToItem(self, item):
        """
        Ensures that the item is visible.

        :type item: QtWidgets.QTreeWidgetItem
        :rtype: None
        """
        if self.isTableView():
            self.treeWidget().scrollToItem(
                item, QtWidgets.QAbstractItemView.PositionAtCenter)
        elif self.isIconView():
            self.listView().scrollToItem(
                item, QtWidgets.QAbstractItemView.PositionAtCenter)

    def scrollToSelectedItem(self):
        """
        Ensures that the item is visible.

        :rtype: None
        """
        item = self.selectedItem()
        if item:
            self.scrollToItem(item)

    def dpi(self):
        """
        return the zoom multiplier.

        Used for high resolution devices.

        :rtype: int
        """
        return self._dpi

    def setDpi(self, dpi):
        """
        Set the zoom multiplier.

        Used for high resolution devices.

        :type dpi: int
        """
        self._dpi = dpi
        self.refreshSize()

    def itemAt(self, pos):
        """
        Return the current item at the given pos.

        :type pos: QtWidgets.QPoint
        :rtype: studioqt.CombinedWidgetItem
        """
        if self.isIconView():
            return self.listView().itemAt(pos)
        else:
            return self.treeView().itemAt(pos)

    def insertItems(self, items, itemAt=None):
        """
        Insert the given items at the given itemAt position.

        :type items: list[studioqt.CombinedWidgetItem]
        :type itemAt: studioqt.CombinedWidgetItem
        :rtype: Nones
        """
        self.addItems(items)
        self.moveItems(items, itemAt=itemAt)
        self.treeWidget().setItemsSelected(items, True)

    def moveItems(self, items, itemAt=None):
        """
        Move the given items to the given itemAt position.

        :type items: list[studioqt.CombinedWidgetItem]
        :type itemAt: studioqt.CombinedWidgetItem
        :rtype: None
        """
        self.listView().moveItems(items, itemAt=itemAt)

    def listView(self):
        """
        Return the list view that contains the items.

        :rtype: ListView
        """
        return self._listView

    def treeWidget(self):
        """
        Return the tree widget that contains the items.

        :rtype: TreeWidget
        """
        return self._treeWidget

    def clear(self):
        """
        Reimplemented for convenience.

        Calls self.treeWidget().clear()
        """
        self.treeWidget().clear()

    def refresh(self):
        """
        Refresh the sorting and size of the items.

        :rtype: None
        """
        self.refreshSortBy()
        self.refreshSize()

    def refreshSize(self):
        """
        Refresh the size of the items.

        :rtype: None
        """
        self.setZoomAmount(self.zoomAmount() + 1)
        self.setZoomAmount(self.zoomAmount() - 1)
        self.repaint()

    def refreshSortBy(self):
        """
        Refresh the sorting of the items.

        :rtype: None
        """
        self.treeWidget().refreshSortBy()

    def itemFromIndex(self, index):
        """
        Return a pointer to the QTreeWidgetItem assocated with the given index.

        :type index: QtCore.QModelIndex
        :rtype: QtWidgets.QTreeWidgetItem
        """
        return self._treeWidget.itemFromIndex(index)

    def textFromItems(self, *args, **kwargs):
        """
        Return all data for the given items and given column.

        :rtype: list[str]
        """
        return self.treeWidget().textFromItems(*args, **kwargs)

    def textFromColumn(self, *args, **kwargs):
        """
        Return all data for the given column.

        :rtype: list[str]
        """
        return self.treeWidget().textFromColumn(*args, **kwargs)

    def toggleTextVisible(self):
        """
        Toggle the item text visibility.

        :rtype: None
        """
        if self.isItemTextVisible():
            self.setItemTextVisible(False)
        else:
            self.setItemTextVisible(True)

    def setItemTextVisible(self, value):
        """
        Set the visibility of the item text.

        :type value: bool
        :rtype: None
        """
        self._isItemTextVisible = value
        self.refreshSize()

    def isItemTextVisible(self):
        """
        Return the visibility of the item text.

        :rtype: bool
        """
        if self.isIconView():
            return self._isItemTextVisible
        else:
            return True

    def itemTextHeight(self):
        """
        Return the height of the item text.

        :rtype: int
        """
        return self.DEFAULT_TEXT_HEIGHT * self.dpi()

    def itemDelegate(self):
        """
        Return the item delegate for the views.

        :rtype: CombinedItemDelegate
        """
        return self._delegate

    def settings(self):
        """
        Return the current state of the widget.

        :rtype: dict
        """
        settings = {}

        settings["columnLabels"] = self.columnLabels()
        settings["padding"] = self.padding()
        settings["spacing"] = self.spacing()
        settings["zoomAmount"] = self.zoomAmount()
        settings["selectedPaths"] = self.selectedPaths()
        settings["textVisible"] = self.isItemTextVisible()
        settings.update(self.treeWidget().settings())

        return settings

    def setSettings(self, settings):
        """
        Set the current state of the widget.

        :type settings: dict
        :rtype: None
        """
        self.setToastEnabled(False)

        padding = settings.get("padding", 5)
        self.setPadding(padding)

        spacing = settings.get("spacing", 2)
        self.setSpacing(spacing)

        zoomAmount = settings.get("zoomAmount", 100)
        self.setZoomAmount(zoomAmount)

        selectedPaths = settings.get("selectedPaths", [])
        self.selectPaths(selectedPaths)

        itemTextVisible = settings.get("textVisible", True)
        self.setItemTextVisible(itemTextVisible)

        self.treeWidget().setSettings(settings)

        self.setToastEnabled(True)

        return settings

    def createSortByMenu(self):
        return self.treeWidget().createSortByMenu()

    def createGroupByMenu(self):
        return self.treeWidget().createGroupByMenu()

    def createCopyTextMenu(self):
        return self.treeWidget().createCopyTextMenu()

    def createItemSettingsMenu(self):

        menu = QtWidgets.QMenu("Item View", self)

        action = studioqt.SeparatorAction("View Settings", menu)
        menu.addAction(action)

        action = studioqt.SliderAction("Size", menu)
        action.slider().setMinimum(10)
        action.slider().setMaximum(200)
        action.slider().setValue(self.zoomAmount())
        action.slider().valueChanged.connect(self.setZoomAmount)
        menu.addAction(action)

        action = studioqt.SliderAction("Border", menu)
        action.slider().setMinimum(0)
        action.slider().setMaximum(20)
        action.slider().setValue(self.padding())
        action.slider().valueChanged.connect(self.setPadding)
        menu.addAction(action)
        #
        action = studioqt.SliderAction("Spacing", menu)
        action.slider().setMinimum(self.DEFAULT_MIN_SPACING)
        action.slider().setMaximum(self.DEFAULT_MAX_SPACING)
        action.slider().setValue(self.spacing())
        action.slider().valueChanged.connect(self.setSpacing)
        menu.addAction(action)

        action = studioqt.SeparatorAction("Item Options", menu)
        menu.addAction(action)

        action = QtWidgets.QAction("Show labels", menu)
        action.setCheckable(True)
        action.setChecked(self.isItemTextVisible())
        action.triggered[bool].connect(self.setItemTextVisible)
        menu.addAction(action)

        return menu

    def createSettingsMenu(self):
        """
        Create and return the settings menu for the widget.

        :rtype: QtWidgets.QMenu
        """
        menu = QtWidgets.QMenu("Item View", self)

        menu.addSeparator()

        action = QtWidgets.QAction("Show labels", menu)
        action.setCheckable(True)
        action.setChecked(self.isItemTextVisible())
        action.triggered[bool].connect(self.setItemTextVisible)
        menu.addAction(action)

        menu.addSeparator()

        sortByMenu = self.treeWidget().createSortByMenu()
        menu.addMenu(sortByMenu)

        groupByMenu = self.treeWidget().createGroupByMenu()
        menu.addMenu(groupByMenu)

        copyTextMenu = self.treeWidget().createCopyTextMenu()
        menu.addMenu(copyTextMenu)

        menu.addSeparator()

        action = studioqt.SliderAction("Size", menu)
        action.slider().setMinimum(10)
        action.slider().setMaximum(200)
        action.slider().setValue(self.zoomAmount())
        action.slider().valueChanged.connect(self.setZoomAmount)
        menu.addAction(action)

        action = studioqt.SliderAction("Border", menu)
        action.slider().setMinimum(0)
        action.slider().setMaximum(20)
        action.slider().setValue(self.padding())
        action.slider().valueChanged.connect(self.setPadding)
        menu.addAction(action)
        #
        action = studioqt.SliderAction("Spacing", menu)
        action.slider().setMinimum(self.DEFAULT_MIN_SPACING)
        action.slider().setMaximum(self.DEFAULT_MAX_SPACING)
        action.slider().setValue(self.spacing())
        action.slider().valueChanged.connect(self.setSpacing)
        menu.addAction(action)

        return menu

    def createItemsMenu(self, items=None):
        """
        Create the item menu for given item.

        :rtype: QtWidgets.QMenu
        """
        item = items or self.selectedItem()

        menu = QtWidgets.QMenu(self)

        if item:
            try:
                item.contextMenu(menu)
            except Exception, msg:
                logger.exception(msg)
        else:
Ejemplo n.º 6
0
class PoseItemSignals(QtCore.QObject):
    """Signals need to be attached to a QObject"""
    mirrorChanged = QtCore.Signal(bool)
Ejemplo n.º 7
0
class ListView(ItemViewMixin, QtWidgets.QListView):

    itemMoved = QtCore.Signal(object)
    itemDropped = QtCore.Signal(object)
    itemClicked = QtCore.Signal(object)
    itemDoubleClicked = QtCore.Signal(object)

    DEFAULT_DRAG_THRESHOLD = 10

    def __init__(self, *args):
        QtWidgets.QListView.__init__(self, *args)
        ItemViewMixin.__init__(self)

        self._treeWidget = None
        self._rubberBand = None
        self._rubberBandStartPos = None
        self._rubberBandColor = QtGui.QColor(QtCore.Qt.white)
        self._customSortOrder = []

        self._drag = None
        self._dragStartPos = None
        self._dragStartIndex = None
        self._dropEnabled = True

        self.setSpacing(5)

        self.setMouseTracking(True)
        self.setSelectionRectVisible(True)
        self.setViewMode(QtWidgets.QListView.IconMode)
        self.setResizeMode(QtWidgets.QListView.Adjust)
        self.setSelectionMode(QtWidgets.QListWidget.ExtendedSelection)
        self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)

        self.setAcceptDrops(True)
        self.setDragEnabled(True)
        self.setDragDropMode(QtWidgets.QAbstractItemView.DragDrop)

        self.clicked.connect(self._indexClicked)
        self.doubleClicked.connect(self._indexDoubleClicked)

    def _indexClicked(self, index):
        """
        Triggered when the user clicks on an index.

        :type index: QtCore.QModelIndex
        :rtype: None
        """
        item = self.itemFromIndex(index)
        item.clicked()
        self.setItemsSelected([item], True)
        self.itemClicked.emit(item)

    def _indexDoubleClicked(self, index):
        """
        Triggered when the user double clicks on an index.

        :type index: QtCore.QModelIndex
        :rtype: None
        """
        item = self.itemFromIndex(index)
        self.setItemsSelected([item], True)
        item.doubleClicked()
        self.itemDoubleClicked.emit(item)

    def treeWidget(self):
        """
        Return the tree widget that contains the item.

        :rtype: QtWidgets.QTreeWidget
        """
        return self._treeWidget

    def setTreeWidget(self, treeWidget):
        """
        Set the tree widget that contains the item.

        :type treeWidget: QtWidgets.QTreeWidget
        :rtype: None
        """
        self._treeWidget = treeWidget
        self.setModel(treeWidget.model())
        self.setSelectionModel(treeWidget.selectionModel())

    def scrollToItem(self, item, pos=None):
        """
        Ensures that the item is visible.

        :type item: QtWidgets.QTreeWidgetItem
        :type pos: QtCore.QPoint or None
        :rtype: None
        """
        index = self.indexFromItem(item)
        pos = pos or QtWidgets.QAbstractItemView.PositionAtCenter

        self.scrollTo(index, pos)

    def items(self):
        """
        Return all the items.

        :rtype: list[QtWidgets.QTreeWidgetItem]
        """
        return self.treeWidget().items()

    def itemAt(self, pos):
        """
        Return a pointer to the item at the coordinates p.

        The coordinates are relative to the tree widget's viewport().

        :type pos: QtCore.QPoint
        :rtype: QtWidgets.QTreeWidgetItem
        """
        index = self.indexAt(pos)
        return self.itemFromIndex(index)

    def indexFromItem(self, item):
        """
        Return the QModelIndex assocated with the given item.

        :type item: QtWidgets.QTreeWidgetItem.
        :rtype: QtCore.QModelIndex
        """
        return self.treeWidget().indexFromItem(item)

    def itemFromIndex(self, index):
        """
        Return a pointer to the QTreeWidgetItem assocated with the given index.

        :type index: QtCore.QModelIndex
        :rtype: QtWidgets.QTreeWidgetItem
        """
        return self.treeWidget().itemFromIndex(index)

    def insertItem(self, row, item):
        """
        Inserts the item at row in the top level in the view.

        :type row: int
        :type item: QtWidgets.QTreeWidgetItem
        :rtype: None
        """
        self.treeWidget().insertTopLevelItem(row, item)

    def takeItems(self, items):
        """
        Removes and returns the items from the view

        :type items: list[QtWidgets.QTreeWidgetItem]
        :rtype: list[QtWidgets.QTreeWidgetItem]
        """
        for item in items:
            row = self.treeWidget().indexOfTopLevelItem(item)
            self.treeWidget().takeTopLevelItem(row)

        return items

    def selectedItem(self):
        """
        Return the last selected non-hidden item.

        :rtype: QtWidgets.QTreeWidgetItem
        """
        return self.treeWidget().selectedItem()

    def selectedItems(self):
        """
        Return a list of all selected non-hidden items.

        :rtype: list[QtWidgets.QTreeWidgetItem]
        """
        return self.treeWidget().selectedItems()

    def setIndexesSelected(self, indexes, value):
        """
        Set the selected state for the given indexes.

        :type indexes: list[QtCore.QModelIndex]
        :type value: bool
        :rtype: None
        """
        items = self.itemsFromIndexes(indexes)
        self.setItemsSelected(items, value)

    def setItemsSelected(self, items, value):
        """
        Set the selected state for the given items.

        :type items: list[studioqt.WidgetItem]
        :type value: bool
        :rtype: None
        """
        self.treeWidget().blockSignals(True)
        for item in items:
            self.treeWidget().setItemSelected(item, value)
        self.treeWidget().blockSignals(False)

    def moveItems(self, items, itemAt):
        """
        Move the given items to the position of the destination row.

        :type items: list[studioqt.Item]
        :type itemAt: studioqt.Item
        :rtype: None
        """
        scrollValue = self.verticalScrollBar().value()

        self.treeWidget().moveItems(items, itemAt)
        self.itemMoved.emit(items[-1])

        self.verticalScrollBar().setValue(scrollValue)

    # ---------------------------------------------------------------------
    # Support for a custom colored rubber band.
    # ---------------------------------------------------------------------

    def createRubberBand(self):
        """
        Create a new instance of the selection rubber band.

        :rtype: QtWidgets.QRubberBand
        """
        rubberBand = QtWidgets.QRubberBand(QtWidgets.QRubberBand.Rectangle,
                                           self)
        palette = QtGui.QPalette()
        color = self.rubberBandColor()
        palette.setBrush(QtGui.QPalette.Highlight, QtGui.QBrush(color))
        rubberBand.setPalette(palette)
        return rubberBand

    def setRubberBandColor(self, color):
        """
        Set the color for the rubber band.

        :type color: QtGui.QColor
        :rtype: None
        """
        self._rubberBand = None
        self._rubberBandColor = color

    def rubberBandColor(self):
        """
        Return the rubber band color for this widget.

        :rtype: QtGui.QColor
        """
        return self._rubberBandColor

    def rubberBand(self):
        """
        Return the selection rubber band for this widget.

        :rtype: QtWidgets.QRubberBand
        """
        if not self._rubberBand:
            self.setSelectionRectVisible(False)
            self._rubberBand = self.createRubberBand()

        return self._rubberBand

    # ---------------------------------------------------------------------
    # Events
    # ---------------------------------------------------------------------

    def validateDragEvent(self, event):
        """
        Validate the drag event.

        :type event: QtWidgets.QMouseEvent
        :rtype: bool
        """
        return QtCore.Qt.LeftButton == event.mouseButtons()

    def mousePressEvent(self, event):
        """
        Triggered when the user presses the mouse button for the viewport.

        :type event: QtWidgets.QMouseEvent
        :rtype: None
        """
        item = self.itemAt(event.pos())
        if not item:
            self.clearSelection()

        ItemViewMixin.mousePressEvent(self, event)
        if event.isAccepted():
            QtWidgets.QListView.mousePressEvent(self, event)
            self.itemsWidget().treeWidget().setItemSelected(item, True)

        self.endDrag()
        self._dragStartPos = event.pos()

        isLeftButton = self.mousePressButton() == QtCore.Qt.LeftButton
        isItemDraggable = item and item.dragEnabled()
        isSelectionEmpty = not self.selectedItems()

        if isLeftButton and (isSelectionEmpty or not isItemDraggable):
            self.rubberBandStartEvent(event)

    def mouseMoveEvent(self, event):
        """
        Triggered when the user moves the mouse over the current viewport.

        :type event: QtWidgets.QMouseEvent
        :rtype: None
        """
        if not self.isDraggingItems():

            isLeftButton = self.mousePressButton() == QtCore.Qt.LeftButton

            if isLeftButton and self.rubberBand().isHidden(
            ) and self.selectedItems():
                self.startDrag(event)
            else:
                ItemViewMixin.mouseMoveEvent(self, event)
                QtWidgets.QListView.mouseMoveEvent(self, event)

            if isLeftButton:
                self.rubberBandMoveEvent(event)

    def mouseReleaseEvent(self, event):
        """
        Triggered when the user releases the mouse button for this viewport.

        :type event: QtWidgets.QMouseEvent
        :rtype: None
        """
        item = self.itemAt(event.pos())
        items = self.selectedItems()

        ItemViewMixin.mouseReleaseEvent(self, event)

        if item not in items:
            if event.button() != QtCore.Qt.MidButton:
                QtWidgets.QListView.mouseReleaseEvent(self, event)
        elif not items:
            QtWidgets.QListView.mouseReleaseEvent(self, event)

        self.endDrag()
        self.rubberBand().hide()

    def rubberBandStartEvent(self, event):
        """
        Triggered when the user presses an empty area.

        :type event: QtWidgets.QMouseEvent
        :rtype: None
        """
        self._rubberBandStartPos = event.pos()
        rect = QtCore.QRect(self._rubberBandStartPos, QtCore.QSize())

        rubberBand = self.rubberBand()
        rubberBand.setGeometry(rect)
        rubberBand.show()

    def rubberBandMoveEvent(self, event):
        """
        Triggered when the user moves the mouse over the current viewport.

        :type event: QtWidgets.QMouseEvent
        :rtype: None
        """
        if self.rubberBand() and self._rubberBandStartPos:
            rect = QtCore.QRect(self._rubberBandStartPos, event.pos())
            rect = rect.normalized()
            self.rubberBand().setGeometry(rect)

    # -----------------------------------------------------------------------
    # Support for drag and drop
    # -----------------------------------------------------------------------

    def rowAt(self, pos):
        """
        Return the row for the given pos.

        :type pos: QtCore.QPoint
        :rtype: int
        """
        return self.treeWidget().rowAt(pos)

    def itemsFromUrls(self, urls):
        """
        Return items from the given url objects.

        :type urls: list[QtCore.QUrl]
        :rtype: list[studioqt.Item]
        """
        items = []
        for url in urls:
            item = self.itemFromUrl(url)
            if item:
                items.append(item)
        return items

    def itemFromUrl(self, url):
        """
        Return the item from the given url object.

        :type url: QtCore.QUrl
        :rtype: studioqt.Item
        """
        return self.itemFromPath(url.path())

    def itemsFromPaths(self, paths):
        """
        Return the items from the given paths.

        :type paths: list[str]
        :rtype: list[studioqt.Item]
        """
        items = []
        for path in paths:
            item = self.itemFromPath(path)
            if item:
                items.append(item)
        return items

    def itemFromPath(self, path):
        """
        Return the item from the given path.

        :type path: str
        :rtype: studioqt.Item
        """
        for item in self.items():
            path_ = item.url().path()
            if path_ and path_ == path:
                return item

    def setDropEnabled(self, value):
        """
        :type value: bool
        :rtype: None
        """
        self._dropEnabled = value

    def dropEnabled(self):
        """
        :rtype: bool
        """
        return self._dropEnabled

    def dragThreshold(self):
        """
        :rtype: int
        """
        return self.DEFAULT_DRAG_THRESHOLD

    def mimeData(self, items):
        """
        :type items: list[studioqt.Item]
        :rtype: QtCore.QMimeData
        """
        mimeData = QtCore.QMimeData()

        urls = [item.url() for item in items]
        text = "\n".join([item.mimeText() for item in items])

        mimeData.setUrls(urls)
        mimeData.setText(text)

        return mimeData

    def dropEvent(self, event):
        """
        This event handler is called when the drag is dropped on this widget.

        :type event: QtWidgets.QDropEvent
        :rtype: None
        """
        item = self.itemAt(event.pos())
        selectedItems = self.selectedItems()

        if selectedItems and item:
            if self.treeWidget().isSortByCustomOrder():
                self.moveItems(selectedItems, item)
            else:
                msg = "You can only re-order items when sorting by custom order."
                logger.info(msg)

        if item:
            item.dropEvent(event)

        self.itemDropped.emit(event)

    def dragMoveEvent(self, event):
        """
        This event handler is called if a drag is in progress.

        :type event: QtGui.QDragMoveEvent
        :rtype: None
        """
        mimeData = event.mimeData()

        if (mimeData.hasText() or mimeData.hasUrls()) and self.dropEnabled():
            event.accept()
        else:
            event.ignore()

    def dragEnterEvent(self, event):
        """
        This event handler is called when the mouse enters this widget
        while a drag is in pregress.

        :type event: QtGui.QDragEnterEvent
        :rtype: None
        """
        mimeData = event.mimeData()

        if (mimeData.hasText() or mimeData.hasUrls()) and self.dropEnabled():
            event.accept()
        else:
            event.ignore()

    def isDraggingItems(self):
        """
        Return true if the user is currently dragging items.

        :rtype: bool
        """
        return bool(self._drag)

    def startDrag(self, event):
        """
        Starts a drag using the given event.

        :type event: QtCore.QEvent
        :rtype: None
        """
        if not self.dragEnabled():
            return

        if self._dragStartPos and hasattr(event, "pos"):

            item = self.itemAt(event.pos())

            if item and item.dragEnabled():

                self._dragStartIndex = self.indexAt(event.pos())

                point = self._dragStartPos - event.pos()
                dt = self.dragThreshold()

                if point.x() > dt or point.y() > dt or point.x(
                ) < -dt or point.y() < -dt:

                    items = self.selectedItems()
                    mimeData = self.mimeData(items)

                    pixmap = self.dragPixmap(item, items)
                    hotSpot = QtCore.QPoint(pixmap.width() / 2,
                                            pixmap.height() / 2)

                    self._drag = QtGui.QDrag(self)
                    self._drag.setPixmap(pixmap)
                    self._drag.setHotSpot(hotSpot)
                    self._drag.setMimeData(mimeData)
                    self._drag.start(QtCore.Qt.MoveAction)

    def endDrag(self):
        """
        Ends the current drag.

        :rtype: None
        """
        logger.debug("End Drag")
        self._dragStartPos = None
        self._dragStartIndex = None
        if self._drag:
            del self._drag
            self._drag = None

    def dragPixmap(self, item, items):
        """
        Show the drag pixmap for the given item.

        :type item: studioqt.Item
        :type items: list[studioqt.Item]
        
        :rtype: QtGui.QPixmap
        """
        rect = self.visualRect(self.indexFromItem(item))

        pixmap = QtGui.QPixmap()
        pixmap = pixmap.grabWidget(self, rect)

        if len(items) > 1:
            cWidth = 35
            cPadding = 5
            cText = str(len(items))
            cX = pixmap.rect().center().x() - float(cWidth / 2)
            cY = pixmap.rect().top() + cPadding
            cRect = QtCore.QRect(cX, cY, cWidth, cWidth)

            painter = QtGui.QPainter(pixmap)
            painter.setRenderHint(QtGui.QPainter.Antialiasing)

            painter.setPen(QtCore.Qt.NoPen)
            painter.setBrush(self.itemsWidget().backgroundSelectedColor())
            painter.drawEllipse(cRect.center(), float(cWidth / 2),
                                float(cWidth / 2))

            font = QtGui.QFont('Serif', 12, QtGui.QFont.Light)
            painter.setFont(font)
            painter.setPen(self.itemsWidget().textSelectedColor())
            painter.drawText(cRect, QtCore.Qt.AlignCenter, str(cText))

        return pixmap
Ejemplo n.º 8
0
class Item(QtWidgets.QTreeWidgetItem):
    """The Item is used to hold rows of information for an item view."""

    SortRole = "SortRole"
    DataRole = "DataRole"

    ThreadPool = QtCore.QThreadPool()

    MAX_ICON_SIZE = 256

    DEFAULT_FONT_SIZE = 13
    DEFAULT_PLAYHEAD_COLOR = QtGui.QColor(255, 255, 255, 220)

    THUMBNAIL_COLUMN = 0
    ENABLE_THUMBNAIL_THREAD = True

    _globalSignals = GlobalSignals()
    blendChanged = _globalSignals.blendChanged

    def __init__(self, *args):
        QtWidgets.QTreeWidgetItem.__init__(self, *args)

        self._url = None
        self._path = None
        self._size = None
        self._rect = None
        self._textColumnOrder = []

        self._data = {}
        self._itemData = {}

        self._icon = {}
        self._fonts = {}
        self._thread = None
        self._pixmap = {}
        self._pixmapRect = None
        self._pixmapScaled = None

        self._iconPath = ""
        self._thumbnailIcon = None

        self._underMouse = False
        self._searchText = None
        self._infoWidget = None

        self._groupItem = None
        self._groupColumn = 0

        self._mimeText = None
        self._itemsWidget = None
        self._stretchToWidget = None

        self._dragEnabled = True

        self._imageSequence = None
        self._imageSequencePath = None

        self._blendValue = 0.0
        self._blendPreviousValue = 0.0
        self._blendPosition = None
        self._blendingEnabled = False

        self._worker = ImageWorker()
        self._worker.setAutoDelete(False)
        self._worker.signals.triggered.connect(self._thumbnailFromImage)
        self._workerStarted = False

    def __eq__(self, other):
        return id(other) == id(self)

    def __ne__(self, other):
        return id(other) != id(self)

    def __del__(self):
        """
        Make sure the sequence is stopped when deleted.

        :rtype: None
        """
        self.stop()

    def columnFromLabel(self, label):
        if self.treeWidget():
            return self.treeWidget().columnFromLabel(label)
        else:
            return None

    def labelFromColumn(self, column):
        if self.treeWidget():
            return self.treeWidget().labelFromColumn(column)
        else:
            return None

    def mimeText(self):
        """
        Return the mime text for drag and drop.

        :rtype: str
        """
        return self._mimeText or self.text(0)

    def setMimeText(self, text):
        """
        Set the mime text for drag and drop.

        :type text: str
        :rtype: None
        """
        self._mimeText = text

    def setHidden(self, value):
        """
        Set the item hidden.

        :type value: bool
        :rtype: None
        """
        QtWidgets.QTreeWidgetItem.setHidden(self, value)
        row = self.treeWidget().indexFromItem(self).row()
        self.itemsWidget().listView().setRowHidden(row, value)

    def setDragEnabled(self, value):
        """
        Set True if the item can be dragged.

        :type value: bool
        :rtype: None
        """
        self._dragEnabled = value

    def dragEnabled(self):
        """
        Return True if the item can be dragged.

        :rtype: bool
        """
        return self._dragEnabled

    def setIcon(self, column, icon, color=None):
        """
        Set the icon to be displayed in the given column.

        :type column: int or str
        :type icon: QtGui.QIcon
        :type color: QtGui.QColor or None
        :rtype: None
        """
        # Safe guard for when the class is being used without the gui.
        isAppRunning = bool(QtWidgets.QApplication.instance())
        if not isAppRunning:
            return

        if isinstance(icon, basestring):
            if not os.path.exists(icon):
                color = color or studioqt.Color(255, 255, 255, 20)
                icon = studiolibrary.resource().icon("image", color=color)
            else:
                icon = QtGui.QIcon(icon)

        if isinstance(column, basestring):
            self._icon[column] = icon
        else:
            self._pixmap[column] = None
            QtWidgets.QTreeWidgetItem.setIcon(self, column, icon)

        self.updateIcon()

    def setItemData(self, data):
        """
        Set the given dictionary as the data for the item.

        :type data: dict
        :rtype: None
        """
        self._itemData = data

    def itemData(self):
        """
        Return the item data for this item.

        :rtype: dict
        """
        return self._itemData

    def setName(self, text):
        """
        Set the name that is shown under the icon and in the Name column.

        :type text: str
        :rtype: None 
        """
        itemData = self.itemData()
        itemData['icon'] = text
        itemData['name'] = text

    def name(self):
        """
        Return text for the Name column.

        :rtype: str
        """
        return self.itemData().get("name")

    def displayText(self, label):
        """
        Return the sort data for the given column.

        :type label: str
        :rtype: str
        """
        return unicode(self.itemData().get(label, ''))

    def sortText(self, label):
        """
        Return the sort data for the given column.

        :type label: str
        :rtype: str
        """
        return unicode(self.itemData().get(label, ''))

    def update(self):
        """
        Refresh the visual state of the icon.

        :rtype: None 
        """
        self.updateIcon()
        self.updateFrame()

    def updateIcon(self):
        """
        Clear the pixmap cache for the item.

        :rtype: None 
        """
        self.clearCache()

    def clearCache(self):
        """Clear the thumbnail cache."""
        self._pixmap = {}
        self._pixmapRect = None
        self._pixmapScaled = None
        self._thumbnailIcon = None

    def dpi(self):
        """
        Used for high resolution devices.

        :rtype: int
        """
        if self.itemsWidget():
            return self.itemsWidget().dpi()
        else:
            return 1

    def clicked(self):
        """
        Triggered when an item is clicked.

        :rtype: None
        """
        pass

    def takeFromTree(self):
        """
        Takes this item from the tree.
        """
        tree = self.treeWidget()
        parent = self.parent()

        if parent:
            parent.takeChild(parent.indexOfChild(self))
        else:
            tree.takeTopLevelItem(tree.indexOfTopLevelItem(self))

    def selectionChanged(self):
        """
        Triggered when an item has been either selected or deselected.

        :rtype: None
        """
        self.resetBlending()

    def doubleClicked(self):
        """
        Triggered when an item is double clicked.

        :rtype: None
        """
        pass

    def setGroupItem(self, groupItem):
        self._groupItem = groupItem

    def groupItem(self):
        return self._groupItem

    def itemsWidget(self):
        """
        Returns the items widget that contains the items.

        :rtype: ItemsWidget
        """
        itemsWidget = None

        if self.treeWidget():
            itemsWidget = self.treeWidget().parent()

        return itemsWidget

    def url(self):
        """
        Return the url object for the given item.

        :rtype: QtCore.QUrl or None
        """
        if not self._url:
            self._url = QtCore.QUrl(self.text(0))
        return self._url

    def setUrl(self, url):
        """
        Set the url object for the item.

        :type: QtCore.QUrl or None
        :rtype: None
        """
        self._url = url

    def searchText(self):
        """
        Return the search string used for finding the item.

        :rtype: str
        """
        if not self._searchText:
            self._searchText = unicode(self._data)

        return self._searchText

    def setStretchToWidget(self, widget):
        """
        Set the width of the item to the width of the given widget.

        :type widget: QtWidgets.QWidget
        :rtype: None
        """
        self._stretchToWidget = widget

    def stretchToWidget(self):
        """
        Return the sretchToWidget.

        :rtype: QtWidgets.QWidget
        """
        return self._stretchToWidget

    def setSize(self, size):
        """
        Set the size for the item.

        :type size: QtCore.QSize
        :rtype: None
        """
        self._size = size

    def sizeHint(self, column=0):
        """
        Return the current size of the item.

        :type column: int
        :rtype: QtCore.QSize
        """
        if self.stretchToWidget():
            if self._size:
                size = self._size
            else:
                size = self.itemsWidget().iconSize()

            w = self.stretchToWidget().width()
            h = size.height()
            return QtCore.QSize(w - 20, h)

        if self._size:
            return self._size
        else:
            iconSize = self.itemsWidget().iconSize()

            if self.isTextVisible():
                w = iconSize.width()
                h = iconSize.width() + self.textHeight()
                iconSize = QtCore.QSize(w, h)

            return iconSize

    def setPixmap(self, column, pixmap):
        """
        Set the pixmap to be displayed in the given column.

        :type column: int
        :type pixmap: QtWidgets.QPixmap
        :rtype: None
        """
        self._pixmap[column] = pixmap

    def thumbnailPath(self):
        """
        Return the thumbnail path on disk.

        :rtype: None or str
        """
        return ""

    def _thumbnailFromImage(self, image):
        """
        Called after the given image object has finished loading.

        :type image: QtGui.QImage
        :rtype: None  
        """
        self.clearCache()

        pixmap = QtGui.QPixmap()
        pixmap.convertFromImage(image)
        icon = QtGui.QIcon(pixmap)

        self._thumbnailIcon = icon
        if self.itemsWidget():
            self.itemsWidget().update()

    def thumbnailIcon(self):
        """
        Return the thumbnail icon.

        :rtype: QtGui.QIcon
        """
        thumbnailPath = self.thumbnailPath()

        if not os.path.exists(thumbnailPath):
            color = self.textColor()
            thumbnailPath = studiolibrary.resource().icon("thumbnail",
                                                          color=color)

        if not self._thumbnailIcon:
            if self.ENABLE_THUMBNAIL_THREAD and not self._workerStarted:
                self._workerStarted = True
                self._worker.setPath(thumbnailPath)

                self.ThreadPool.start(self._worker)

                color = self.textColor()
                self._thumbnailIcon = studiolibrary.resource().icon(
                    "thumbnail", color=color)
            else:
                self._thumbnailIcon = QtGui.QIcon(thumbnailPath)

        return self._thumbnailIcon

    def icon(self, column):
        """
        Overriding the icon method to add support for the thumbnail icon.

        :type column: int
        :rtype: QtGui.QIcon
        """
        icon = QtWidgets.QTreeWidgetItem.icon(self, column)

        if not icon and column == self.THUMBNAIL_COLUMN:
            icon = self.thumbnailIcon()

        return icon

    def pixmap(self, column):
        """
        Return the pixmap for the given column.

        :type column: int
        :rtype: QtWidgets.QPixmap
        """

        if not self._pixmap.get(column):

            icon = self.icon(column)
            if icon:
                size = QtCore.QSize(self.MAX_ICON_SIZE, self.MAX_ICON_SIZE)
                iconSize = icon.actualSize(size)
                self._pixmap[column] = icon.pixmap(iconSize)

        return self._pixmap.get(column)

    def padding(self):
        """
        Return the padding/border size for the item.

        :rtype: int
        """
        return self.itemsWidget().padding()

    def textHeight(self):
        """
        Return the height of the text for the item.

        :rtype: int
        """
        return self.itemsWidget().itemTextHeight()

    def isTextVisible(self):
        """
        Return True if the text is visible.

        :rtype: bool
        """
        return self.itemsWidget().isItemTextVisible()

    def textAlignment(self, column):
        """
        Return the text alignment for the label in the given column.

        :type column: int
        :rtype: QtCore.Qt.AlignmentFlag
        """
        if self.itemsWidget().isIconView():
            return QtCore.Qt.AlignCenter
        else:
            return QtWidgets.QTreeWidgetItem.textAlignment(self, column)

    # -----------------------------------------------------------------------
    # Support for mouse and key events
    # -----------------------------------------------------------------------

    def underMouse(self):
        """Return True if the item is under the mouse cursor."""
        return self._underMouse

    def contextMenu(self, menu):
        """
        Return the context menu for the item.

        Reimplement in a subclass to return a custom context menu for the item.

        :rtype: QtWidgets.QMenu
        """
        pass

    def dropEvent(self, event):
        """
        Reimplement in a subclass to receive drop events for the item.

        :type event: QtWidgets.QDropEvent
        :rtype: None
        """

    def mouseLeaveEvent(self, event):
        """
        Reimplement in a subclass to receive mouse leave events for the item.

        :type event: QtWidgets.QMouseEvent
        :rtype: None
        """
        self._underMouse = False
        self.stop()

    def mouseEnterEvent(self, event):
        """
        Reimplement in a subclass to receive mouse enter events for the item.

        :type event: QtWidgets.QMouseEvent
        :rtype: None
        """
        self._underMouse = True
        self.play()

    def mouseMoveEvent(self, event):
        """
        Reimplement in a subclass to receive mouse move events for the item.

        :type event: QtWidgets.QMouseEvent
        :rtype: None
        """
        self.blendingEvent(event)
        self.imageSequenceEvent(event)

    def mousePressEvent(self, event):
        """
        Reimplement in a subclass to receive mouse press events for the item.

        :type event: QtWidgets.QMouseEvent
        :rtype: None
        """
        if event.button() == QtCore.Qt.MidButton:
            self._blendPosition = event.pos()

    def mouseReleaseEvent(self, event):
        """
        Reimplement in a subclass to receive mouse release events for the item.

        :type event: QtWidgets.QMouseEvent
        :rtype: None
        """
        if self.isBlending():
            self._blendPosition = None
            self._blendPreviousValue = self.blendValue()

    def keyPressEvent(self, event):
        """
        Reimplement in a subclass to receive key press events for the item.

        :type event: QtWidgets.QKeyEvent
        :rtype: None
        """
        pass

    def keyReleaseEvent(self, event):
        """
        Reimplement in a subclass to receive key release events for the item.

        :type event: QtWidgets.QKeyEvent
        :rtype: None
        """
        pass

    # -----------------------------------------------------------------------
    # Support for custom painting
    # -----------------------------------------------------------------------

    def textColor(self):
        """
        Return the text color for the item.

        :rtype: QtWidgets.QtColor
        """
        # This will be changed to use the palette soon.
        # Note: There were problems with older versions of Qt's palette (Maya 2014).
        # Eg:
        # return self.itemsWidget().palette().color(self.itemsWidget().foregroundRole())
        return self.itemsWidget().textColor()

    def textSelectedColor(self):
        """
        Return the selected text color for the item.

        :rtype: QtWidgets.QtColor
        """
        return self.itemsWidget().textSelectedColor()

    def backgroundColor(self):
        """
        Return the background color for the item.

        :rtype: QtWidgets.QtColor
        """
        return self.itemsWidget().backgroundColor()

    def backgroundHoverColor(self):
        """
        Return the background color when the mouse is over the item.

        :rtype: QtWidgets.QtColor
        """
        return self.itemsWidget().backgroundHoverColor()

    def backgroundSelectedColor(self):
        """
        Return the background color when the item is selected.

        :rtype: QtWidgets.QtColor
        """
        return self.itemsWidget().backgroundSelectedColor()

    def rect(self):
        """
        Return the rect for the current paint frame.

        :rtype: QtCore.QRect
        """
        return self._rect

    def setRect(self, rect):
        """
        Set the rect for the current paint frame.

        :type rect: QtCore.QRect
        :rtype: None
        """
        self._rect = rect

    def visualRect(self, option):
        """
        Return the visual rect for the item.

        :type option: QtWidgets.QStyleOptionViewItem
        :rtype: QtCore.QRect
        """
        rect = QtCore.QRect(option.rect)
        return rect

    def paintRow(self, painter, option, index):
        """
        Paint performs low-level painting for the item.

        :type painter: QtWidgets.QPainter
        :type option: QtWidgets.QStyleOptionViewItem
        :type index: QtCore.QModelIndex
        :rtype: None
        """
        QtWidgets.QTreeWidget.drawRow(self.treeWidget(), painter, option,
                                      index)

    def paint(self, painter, option, index):
        """
        Paint performs low-level painting for the item.

        :type painter: QtWidgets.QPainter
        :type option: QtWidgets.QStyleOptionViewItem
        :type index: QtCore.QModelIndex
        :rtype: None
        """
        self.setRect(QtCore.QRect(option.rect))

        painter.save()

        try:
            self.paintBackground(painter, option, index)

            if self.isTextVisible():
                self.paintText(painter, option, index)

            self.paintIcon(painter, option, index)

            if index.column() == 0:
                if self.imageSequence():
                    self.paintPlayhead(painter, option)
        finally:
            painter.restore()

    def paintBackground(self, painter, option, index):
        """
        Draw the background for the item.

        :type painter: QtWidgets.QPainter
        :type option: QtWidgets.QStyleOptionViewItem
        :type index: QtCore.QModelIndex
        :rtype: None
        """
        isSelected = option.state & QtWidgets.QStyle.State_Selected
        isMouseOver = option.state & QtWidgets.QStyle.State_MouseOver
        painter.setPen(QtGui.QPen(QtCore.Qt.NoPen))

        visualRect = self.visualRect(option)

        if isSelected:
            color = self.backgroundSelectedColor()
            painter.setBrush(QtGui.QBrush(color))
        elif isMouseOver:
            color = self.backgroundHoverColor()
            painter.setBrush(QtGui.QBrush(color))
        else:
            color = self.backgroundColor()
            painter.setBrush(QtGui.QBrush(color))

        painter.drawRect(visualRect)

    def iconRect(self, option):
        """
        Return the icon rect for the item.

        :type option: QtWidgets.QStyleOptionViewItem
        :rtype: QtCore.QRect
        """
        padding = self.padding()
        rect = self.visualRect(option)
        width = rect.width()
        height = rect.height()

        if self.isTextVisible() and self.itemsWidget().isIconView():
            height -= self.textHeight()

        width -= padding
        height -= padding

        rect.setWidth(width)
        rect.setHeight(height)

        x = 0
        x += float(padding) / 2
        x += float((width - rect.width())) / 2

        y = float((height - rect.height())) / 2
        y += float(padding) / 2

        rect.translate(x, y)
        return rect

    def scalePixmap(self, pixmap, rect):
        """
        Scale the given pixmap to the give rect size.
        
        This method will cache the scaled pixmap if called with the same size.

        :type pixmap: QtGui.QPixmap
        :type rect: QtCore.QRect
        :rtype: QtGui.QPixmap
        """
        rectChanged = True

        if self._pixmapRect:
            widthChanged = self._pixmapRect.width() != rect.width()
            heightChanged = self._pixmapRect.height() != rect.height()

            rectChanged = widthChanged or heightChanged

        if not self._pixmapScaled or rectChanged:

            self._pixmapScaled = pixmap.scaled(
                rect.width(),
                rect.height(),
                QtCore.Qt.KeepAspectRatio,
                QtCore.Qt.SmoothTransformation,
            )

            self._pixmapRect = rect

        return self._pixmapScaled

    def paintIcon(self, painter, option, index, align=None):
        """
        Draw the icon for the item.

        :type painter: QtWidgets.QPainter
        :type option: QtWidgets.QStyleOptionViewItem
        :rtype: None
        """
        column = index.column()
        pixmap = self.pixmap(column)

        if not pixmap:
            return

        rect = self.iconRect(option)
        pixmap = self.scalePixmap(pixmap, rect)

        pixmapRect = QtCore.QRect(rect)
        pixmapRect.setWidth(pixmap.width())
        pixmapRect.setHeight(pixmap.height())

        align = QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter

        x, y = 0, 0

        isAlignBottom = align == QtCore.Qt.AlignBottom | QtCore.Qt.AlignLeft \
                        or align == QtCore.Qt.AlignBottom | QtCore.Qt.AlignHCenter \
                        or align == QtCore.Qt.AlignBottom | QtCore.Qt.AlignRight

        isAlignHCenter = align == QtCore.Qt.AlignHCenter \
                         or align == QtCore.Qt.AlignCenter \
                         or align == QtCore.Qt.AlignHCenter | QtCore.Qt.AlignBottom \
                         or align == QtCore.Qt.AlignHCenter | QtCore.Qt.AlignTop

        isAlignVCenter = align == QtCore.Qt.AlignVCenter \
                         or align == QtCore.Qt.AlignCenter \
                         or align == QtCore.Qt.AlignVCenter | QtCore.Qt.AlignLeft \
                         or align == QtCore.Qt.AlignVCenter | QtCore.Qt.AlignRight

        if isAlignHCenter:
            x += float(rect.width() - pixmap.width()) / 2

        if isAlignVCenter:
            y += float(rect.height() - pixmap.height()) / 2

        elif isAlignBottom:
            y += float(rect.height() - pixmap.height())

        pixmapRect.translate(x, y)
        painter.drawPixmap(pixmapRect, pixmap)

    def drawIconBorder(self, painter, pixmapRect):
        """
        Draw a border around the icon.

        :type painter: QtWidgets.QPainter
        :type pixmapRect: QtWidgets.QRect
        :rtype: None
        """
        pixmapRect = QtCore.QRect(pixmapRect)
        pixmapRect.setX(pixmapRect.x() - 5)
        pixmapRect.setY(pixmapRect.y() - 5)
        pixmapRect.setWidth(pixmapRect.width() + 5)
        pixmapRect.setHeight(pixmapRect.height() + 5)

        color = QtGui.QColor(255, 255, 255, 10)
        painter.setPen(QtGui.QPen(QtCore.Qt.NoPen))
        painter.setBrush(QtGui.QBrush(color))

        painter.drawRect(pixmapRect)

    def fontSize(self):
        """
        Return the font size for the item.

        :rtype: int
        """
        return self.DEFAULT_FONT_SIZE

    def font(self, column):
        """
        Return the font for the given column.

        :type column: int
        :rtype: QtWidgets.QFont
        """
        default = QtWidgets.QTreeWidgetItem.font(self, column)

        font = self._fonts.get(column, default)

        font.setPixelSize(self.fontSize() * self.dpi())
        return font

    def setFont(self, column, font):
        """
        Set the font for the given column.

        :type column: int
        :type font: QtWidgets.QFont
        :rtype: Noen
        """
        self._fonts[column] = font

    def paintText(self, painter, option, index):
        """
        Draw the text for the item.

        :type painter: QtWidgets.QPainter
        :type option: QtWidgets.QStyleOptionViewItem
        :rtype: None
        """
        column = index.column()

        if column == 0 and self.itemsWidget().isTableView():
            return

        self._paintText(painter, option, column)

    def textWidth(self, column):
        text = self.text(column)

        font = self.font(column)
        metrics = QtGui.QFontMetricsF(font)
        textWidth = metrics.width(text)
        return textWidth

    def _paintText(self, painter, option, column):

        if self.itemsWidget().isIconView():
            text = self.name()
        else:
            label = self.labelFromColumn(column)
            text = self.displayText(label)

        isSelected = option.state & QtWidgets.QStyle.State_Selected

        if isSelected:
            color = self.textSelectedColor()
        else:
            color = self.textColor()

        visualRect = self.visualRect(option)

        width = visualRect.width()
        height = visualRect.height()

        padding = self.padding()
        x = padding / 2
        y = padding / 2

        visualRect.translate(x, y)
        visualRect.setWidth(width - padding)
        visualRect.setHeight(height - padding)

        font = self.font(column)
        align = self.textAlignment(column)
        metrics = QtGui.QFontMetricsF(font)

        if text:
            textWidth = metrics.width(text)
        else:
            textWidth = 1

        # # Check if the current text fits within the rect.
        if textWidth > visualRect.width() - padding:
            visualWidth = visualRect.width()
            text = metrics.elidedText(text, QtCore.Qt.ElideRight, visualWidth)
            align = QtCore.Qt.AlignLeft

        if self.itemsWidget().isIconView():
            align = align | QtCore.Qt.AlignBottom
        else:
            align = align | QtCore.Qt.AlignVCenter

        pen = QtGui.QPen(color)
        painter.setPen(pen)
        painter.setFont(font)
        painter.drawText(visualRect, align, text)

    # ------------------------------------------------------------------------
    # Support for middle mouse blending (slider)
    # ------------------------------------------------------------------------

    def setBlendingEnabled(self, enabled):
        """
        Set if middle mouse slider is enabled.

        :type enabled: bool
        :rtype: None
        """
        self._blendingEnabled = enabled

    def isBlendingEnabled(self):
        """
        Return true if middle mouse slider is enabled.

        :rtype: None
        """
        return self._blendingEnabled

    def blendingEvent(self, event):
        """
        Called when the mouse moves while the middle mouse button is held down.

        :param event: QtGui.QMouseEvent
        :rtype: None
        """
        if self.isBlending():
            value = (event.pos().x() - self.blendPosition().x()) / 1.5
            value = math.ceil(value) + self.blendPreviousValue()
            try:
                self.setBlendValue(value)
            except Exception:
                self.stopBlending()

    def startBlendingEvent(self, event):
        """
        Called when the middle mouse button is pressed.

        :param event: QtGui.QMouseEvent
        :rtype: None
        """
        if self.isBlendingEnabled():
            if event.button() == QtCore.Qt.MidButton:
                self._blendPosition = event.pos()

    def stopBlending(self):
        """
        Called when the middle mouse button is released.

        :param event: QtGui.QMouseEvent
        :rtype: None
        """
        self._blendPosition = None
        self._blendPreviousValue = self.blendValue()

    def resetBlending(self):
        """
        Reset the blending value to zero.

        :rtype: None
        """
        self._blendValue = 0.0
        self._blendPreviousValue = 0.0

    def isBlending(self):
        """
        Return True if blending.

        :rtype: bool
        """
        return self.blendPosition() is not None

    def setBlendValue(self, value):
        """
        Set the blend value.

        :type value: float
        :rtype: bool
        """
        if self.isBlendingEnabled():
            self._blendValue = value
            self.blendChanged.emit(value)
            logger.debug("BLENDING:" + str(value))

    def blendValue(self):
        """
        Return the blend value.

        :rtype: float
        """
        return self._blendValue

    def blendPreviousValue(self):
        """
        :rtype: float
        """
        return self._blendPreviousValue

    def blendPosition(self):
        """
        :rtype: QtGui.QPoint
        """
        return self._blendPosition

    # ------------------------------------------------------------------------
    # Support animated image sequence
    # ------------------------------------------------------------------------

    def imageSequenceEvent(self, event):
        """
        :type event: QtCore.QEvent
        :rtype: None
        """
        if self.imageSequence():
            if studioqt.isControlModifier():
                if self.rect():
                    x = event.pos().x() - self.rect().x()
                    width = self.rect().width()
                    percent = 1.0 - (float(width - x) / float(width))
                    frame = int(self.imageSequence().frameCount() * percent)
                    self.imageSequence().jumpToFrame(frame)
                    self.updateFrame()

    def resetImageSequence(self):
        self._imageSequence = None

    def imageSequence(self):
        """
        :rtype: studioqt.ImageSequence
        """
        return self._imageSequence

    def setImageSequence(self, value):
        """
        :type value: studioqt.ImageSequence
        """
        self._imageSequence = value

    def setImageSequencePath(self, path):
        """
        :type path: str
        :rtype: None
        """
        self._imageSequencePath = path

    def imageSequencePath(self):
        """
        :rtype: str
        """
        return self._imageSequencePath

    def stop(self):
        """
        :rtype: None
        """
        if self.imageSequence():
            self.imageSequence().stop()

    def play(self):
        """
        :rtype: None
        """
        self.resetImageSequence()
        path = self.imageSequencePath() or self.thumbnailPath()

        movie = None

        if os.path.isfile(path) and path.lower().endswith(".gif"):

            movie = QtGui.QMovie(path)
            movie.setCacheMode(QtGui.QMovie.CacheAll)
            movie.frameChanged.connect(self._frameChanged)

        elif os.path.isdir(path):

            if not self.imageSequence():
                movie = studioqt.ImageSequence(path)
                movie.frameChanged.connect(self._frameChanged)

        if movie:
            self.setImageSequence(movie)
            self.imageSequence().start()

    def _frameChanged(self, frame):
        """Triggered when the movie object updates to the given frame."""
        if not studioqt.isControlModifier():
            self.updateFrame()

    def updateFrame(self):
        """Triggered when the movie object updates the current frame."""
        if self.imageSequence():
            pixmap = self.imageSequence().currentPixmap()
            self.setIcon(0, pixmap)

    def playheadColor(self):
        """
        Return the playhead color.

        :rtype: QtGui.Color
        """
        return self.DEFAULT_PLAYHEAD_COLOR

    def paintPlayhead(self, painter, option):
        """
        Paint the playhead if the item has an image sequence.

        :type painter: QtWidgets.QPainter
        :type option: QtWidgets.QStyleOptionViewItem
        :rtype: None
        """
        imageSequence = self.imageSequence()

        if imageSequence and self.underMouse():

            count = imageSequence.frameCount()
            current = imageSequence.currentFrameNumber()

            if count > 0:
                percent = float((count + current) + 1) / count - 1
            else:
                percent = 0

            r = self.iconRect(option)
            c = self.playheadColor()

            painter.setPen(QtCore.Qt.NoPen)
            painter.setBrush(QtGui.QBrush(c))

            if percent <= 0:
                width = 0
            elif percent >= 1:
                width = r.width()
            else:
                width = (percent * r.width()) - 1

            height = 3 * self.dpi()
            y = r.y() + r.height() - (height - 1)

            painter.drawRect(r.x(), y, width, height)
Ejemplo n.º 9
0
class MayaDockWidgetMixin(object):

    DEFAULT_DOCK_AREA = "none"
    DEFAULT_DOCK_ALLOWED_AREAS = ["top", "bottom", "left", "right"]

    dockingChanged = QtCore.Signal()

    @staticmethod
    def generateUniqueObjectName(name, attempts=100):
        """
        Generate a unique name for the dock widget.

        :type name: str
        :type attempts: int
        :rtype: str
        """
        for i in range(1, attempts):
            uniqueName = name + str(i)
            controlExists = maya.cmds.control(uniqueName, exists=True)
            if not controlExists:
                return uniqueName

        msg = 'Cannot find unique window name for "{0}"'
        msg = msg.format(name)
        raise ValueError(msg)

    @staticmethod
    def dockAreaStrMap():
        """
        :rtype: dict
        """
        return {
            'none': QtCore.Qt.NoDockWidgetArea,
            'top': QtCore.Qt.TopDockWidgetArea,
            'left': QtCore.Qt.LeftDockWidgetArea,
            'right': QtCore.Qt.RightDockWidgetArea,
            'bottom': QtCore.Qt.BottomDockWidgetArea,
            'all': QtCore.Qt.AllDockWidgetAreas
        }

    @staticmethod
    def dockAreaMap():
        """
        :rtype: dict
        """
        return {
            QtCore.Qt.NoDockWidgetArea: 'none',
            QtCore.Qt.TopDockWidgetArea: 'top',
            QtCore.Qt.LeftDockWidgetArea: 'left',
            QtCore.Qt.RightDockWidgetArea: 'right',
            QtCore.Qt.BottomDockWidgetArea: 'bottom',
            QtCore.Qt.AllDockWidgetAreas: 'all'
        }

    def __init__(self, *args):
        self._dockWidgetName = None
        self._dockLayoutName = None

    def setObjectName(self, name):
        """
        :type name: str
        """
        try:
            name = self.generateUniqueObjectName(name)
        except NameError, msg:
            logger.exception(msg)

        QtWidgets.QWidget.setObjectName(self, name)
Ejemplo n.º 10
0
class SearchWidget(QtWidgets.QLineEdit):
    DEFAULT_PLACEHOLDER_TEXT = 'Search'
    searchChanged = QtCore.Signal()

    def __init__(self, *args):
        QtWidgets.QLineEdit.__init__(self, *args)
        self._iconPadding = 4
        self._iconButton = QtWidgets.QPushButton(self)
        self._iconButton.clicked.connect(self._iconClicked)
        self._searchFilter = studioqt.SearchFilter('')
        icon = studioqt.icon('search')
        self.setIcon(icon)
        self.setPlaceholderText(self.DEFAULT_PLACEHOLDER_TEXT)
        self.textChanged.connect(self._textChanged)
        self.searchChanged = self.searchFilter().searchChanged
        self.update()

    def update(self):
        self.updateIconColor()

    def updateIconColor(self):
        color = self.palette().color(self.foregroundRole())
        color = studioqt.Color.fromColor(color)
        self.setIconColor(color)

    def _textChanged(self, text):
        """
        Triggered when the text changes.
        
        :type text: str
        :rtype: None
        """
        self.searchFilter().setPattern(text)

    def contextMenuEvent(self, event):
        """
        Triggered when the user right clicks on the search widget.
        
        :type event: QtCore.QEvent
        :rtype: None
        """
        self.showContextMenu()

    def setSpaceOperator(self, operator):
        """
        Set the space operator for the search filter.
        
        :type operator: studioqt.SearchFilter.Operator
        :rtype: None
        """
        self._searchFilter.setSpaceOperator(operator)

    def createSpaceOperatorMenu(self, parent=None):
        """
        Return the menu for changing the space operator.
        
        :type parent: QGui.QMenu
        :rtype: QGui.QMenu
        """
        searchFilter = self.searchFilter()
        menu = QtWidgets.QMenu(parent)
        menu.setTitle('Space Operator')
        action = QtWidgets.QAction(menu)
        action.setText('OR')
        action.setCheckable(True)
        callback = partial(self.setSpaceOperator, searchFilter.Operator.OR)
        action.triggered.connect(callback)
        if searchFilter.spaceOperator() == searchFilter.Operator.OR:
            action.setChecked(True)
        menu.addAction(action)
        action = QtWidgets.QAction(menu)
        action.setText('AND')
        action.setCheckable(True)
        callback = partial(self.setSpaceOperator, searchFilter.Operator.AND)
        action.triggered.connect(callback)
        if searchFilter.spaceOperator() == searchFilter.Operator.AND:
            action.setChecked(True)
        menu.addAction(action)
        return menu

    def showContextMenu(self):
        """
        Create and show the context menu for the search widget.
        
        :rtype QtWidgets.QAction
        """
        menu = QtWidgets.QMenu(self)
        subMenu = self.createStandardContextMenu()
        subMenu.setTitle('Edit')
        menu.addMenu(subMenu)
        subMenu = self.createSpaceOperatorMenu(menu)
        menu.addMenu(subMenu)
        point = QtGui.QCursor.pos()
        action = menu.exec_(point)
        return action

    def searchFilter(self):
        """
        Return the search filter for the widget.
        
        :rtype: studioqt.SearchFilter
        """
        return self._searchFilter

    def _iconClicked(self):
        """
        Triggered when the user clicks on the icon.
        
        :rtype: None
        """
        if not self.hasFocus():
            self.setFocus()

    def setIcon(self, icon):
        """
        Set the icon for the search widget.
        
        :type icon: QtWidgets.QIcon
        :rtype: None
        """
        self._iconButton.setIcon(icon)

    def setIconColor(self, color):
        """
        Set the icon color for the search widget icon.
        
        :type color: QtGui.QColor
        :rtype: None
        """
        icon = self._iconButton.icon()
        icon = studioqt.Icon(icon)
        icon.setColor(color)
        self._iconButton.setIcon(icon)

    def settings(self):
        """
        Return a dictionary of the current widget state.
        
        :rtype: dict
        """
        settings = {'text': self.text()}
        settings['searchFilter'] = self.searchFilter().settings()
        return settings

    def setSettings(self, settings):
        """
        Restore the widget state from a settings dictionary.
        
        :type settings: dict
        :rtype: None
        """
        searchFilterSettings = settings.get('searchFilter', None)
        if searchFilterSettings is not None:
            self.searchFilter().setSettings(searchFilterSettings)
        text = settings.get('text', '')
        self.setText(text)

    def resizeEvent(self, event):
        """
        Reimplemented so the icon maintains the same height as the widget.
        
        :type event:  QtWidgets.QResizeEvent
        :rtype: None
        """
        QtWidgets.QLineEdit.resizeEvent(self, event)
        self.setTextMargins(self.height(), 0, 0, 0)
        size = QtCore.QSize(self.height(), self.height())
        self._iconButton.setIconSize(size)
        self._iconButton.setFixedSize(size)
Ejemplo n.º 11
0
class WorkerSignals(QtCore.QObject):
    triggered = QtCore.Signal(object)
Ejemplo n.º 12
0
class FormWidget(QtWidgets.QFrame):

    accepted = QtCore.Signal(object)
    stateChanged = QtCore.Signal()
    validated = QtCore.Signal()

    def __init__(self, *args, **kwargs):
        super(FormWidget, self).__init__(*args, **kwargs)

        self._schema = []
        self._widgets = []
        self._validator = None

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

        self.setLayout(layout)

        self._fieldsFrame = QtWidgets.QFrame(self)
        self._fieldsFrame.setObjectName("optionsFrame")

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

        self._fieldsFrame.setLayout(layout)

        self._titleWidget = QtWidgets.QPushButton(self)
        self._titleWidget.setCheckable(True)
        self._titleWidget.setObjectName("titleWidget")
        self._titleWidget.toggled.connect(self._titleClicked)
        self._titleWidget.hide()

        self.layout().addWidget(self._titleWidget)
        self.layout().addWidget(self._fieldsFrame)

    def _titleClicked(self, toggle):
        """Triggered when the user clicks the title widget."""
        self.setExpanded(toggle)
        self.stateChanged.emit()

    def titleWidget(self):
        """
        Get the title widget.
        
        :rtype: QWidget
        """
        return self._titleWidget

    def setTitle(self, title):
        """
        Set the text for the title widget.
        
        :type title: str
        """
        self.titleWidget().setText(title)

    def setExpanded(self, expand):
        """
        Expands the options if expand is true, otherwise collapses the options.
        
        :type expand: bool
        """
        self._titleWidget.blockSignals(True)

        try:
            self._titleWidget.setChecked(expand)
            self._fieldsFrame.setVisible(expand)
        finally:
            self._titleWidget.blockSignals(False)

    def isExpanded(self):
        """
        Returns true if the item is expanded, otherwise returns false.
        
        :rtype: bool
        """
        return self._titleWidget.isChecked()

    def setTitleVisible(self, visible):
        """
        A convenience method for setting the title visible.
        
        :type visible: bool
        """
        self.titleWidget().setVisible(visible)

    def reset(self):
        """Reset all option widgets back to their default value."""
        for widget in self._widgets:
            widget.reset()
        self.validate()

    def readSettings(self):
        """
        Return the local settings from the location of the SETTING_PATH.

        :rtype: dict
        """
        path = studiolibrary.localPath("FormWidget.json")
        return studiolibrary.readJson(path)

    def saveSettings(self, data):
        """
        Save the given dict to the local location of the SETTING_PATH.

        :type data: dict
        :rtype: None
        """
        path = studiolibrary.localPath("FormWidget.json")
        studiolibrary.updateJson(path, data)

    def savePersistentValues(self):
        """
        Triggered when the user changes the options.
        """
        settings = self.readSettings()

        settings[self.__class__.__name__] = self.persistentValues()

        self.saveSettings(settings)

    def loadPersistentValues(self):
        """
        Get the options from the user settings.

        :rtype: dict
        """
        settings = self.readSettings()

        values = settings.get(self.__class__.__name__, {})
        defaultValues = self.defaultValues()

        # Remove options from the user settings that are not persistent
        if values:
            for value in self.schema():
                name = value.get("name")
                persistent = value.get("persistent")
                if not persistent and name in values:
                    values[name] = defaultValues[name]

        self.setValues(values)

    def schema(self):
        """
        Get the schema for the form.

        :rtype: dict
        """
        return self._schema

    def setSchema(self, schema, layout=None):
        """
        Set the schema for the widget.
        
        :type schema: list[dict]
        """
        self._schema = schema

        for data in schema:

            cls = FIELD_WIDGET_REGISTRY.get(data.get("type", "label"))

            if not cls:
                logger.warning("Cannot find widget for %s", data)
                continue

            if layout and not data.get("layout"):
                data["layout"] = layout

            widget = cls(data=data, formWidget=self)
            widget.setData(data)

            value = data.get("value")
            default = data.get("default")
            if value is None and default is not None:
                widget.setValue(default)

            self._widgets.append(widget)

            callback = functools.partial(self._fieldChanged, widget)
            widget.valueChanged.connect(callback)

            self._fieldsFrame.layout().addWidget(widget)

        self.loadPersistentValues()

    def _fieldChanged(self, widget):
        """
        Triggered when the given option widget changes value.
        
        :type widget: FieldWidget 
        """
        self.validate()

    def accept(self):
        """Accept the current options"""
        self.emitAcceptedCallback()
        self.savePersistentValues()

    def closeEvent(self, event):
        """Called when the widget is closed."""
        self.savePersistentValues()
        super(FormWidget, self).closeEvent(event)

    def hasErrors(self):
        """
        Return True if the form contains any errors.

        :rtype: bool
        """
        for widget in self._widgets:
            if widget.data().get("error"):
                return True
        return False

    def setValidator(self, validator):
        """
        Set the validator for the options.
        
        :type validator: func
        """
        self._validator = validator

    def validator(self):
        """
        Return the validator for the form.

        :rtype: func
        """
        return self._validator

    def validate(self):
        """Validate the current options using the validator."""
        if self._validator:

            fields = self._validator(**self.values())
            if fields is not None:
                self._setState(fields)

            self.validated.emit()

        else:
            logger.debug("No validator set.")

    def setValue(self, name, value):
        """
        Set the value for the given field name and value

        :type name: str
        :type value: object
        """
        widget = self.widget(name)
        widget.setValue(value)

    def value(self, name):
        """
        Get the value for the given widget name.
        
        :type name: str 
        :rtype: object 
        """
        widget = self.widget(name)
        return widget.value()

    def widget(self, name):
        """
        Get the widget for the given widget name.
        
        :type name: str 
        :rtype: FieldWidget 
        """
        for widget in self._widgets:
            if widget.data().get("name") == name:
                return widget

    def fields(self):
        """
        Get all the field data for the form.

        :rtype: dict
        """
        fields = []
        for widget in self._widgets:
            fields.append(widget.data())
        return fields

    def fieldWidgets(self):
        """
        Get all the field widgets.

        :rtype: list[FieldWidget]
        """
        return self._widgets

    def setValues(self, values):
        """
        Set the field values for the current form.

        :type values: dict
        """
        state = []
        for name in values:
            state.append({"name": name, "value": values[name]})
        self._setState(state)

    def values(self):
        """
        Get the all the field values indexed by the field name.

        :rtype: dict
        """
        values = {}
        for widget in self._widgets:
            name = widget.data().get("name")
            if name:
                values[name] = widget.value()
        return values

    def persistentValues(self):
        """
        Get all the persistent field values for the current form.

        :rtype: dict
        """
        values = {}
        for widget in self._widgets:
            name = widget.data().get("name")
            if name and widget.data().get("persistent"):
                values[name] = widget.value()
        return values

    def defaultValues(self):
        """
        Get the all the default field values indexed by the field name.

        :rtype: dict
        """
        values = {}
        for widget in self._widgets:
            name = widget.data().get("name")
            if name:
                values[name] = widget.default()
        return values

    def state(self):
        """
        Get the current state.
        
        :rtype: dict 
        """
        fields = []

        for widget in self._widgets:
            fields.append(widget.state())

        state = {"fields": fields, "expanded": self.isExpanded()}

        return state

    def setState(self, state):
        """
        Set the current state.
        
        :type state: dict 
        """
        expanded = state.get("expanded")
        if expanded is not None:
            self.setExpanded(expanded)

        fields = state.get("fields")
        if fields is not None:
            self._setState(fields)

        self.validate()

    def _setState(self, fields):
        """
        Set the state while blocking all signals.
        
        :type fields: list[dict]
        """
        for widget in self._widgets:
            widget.blockSignals(True)

        for widget in self._widgets:
            widget.setError("")
            for field in fields:
                if field.get("name") == widget.data().get("name"):
                    widget.setData(field)

        for widget in self._widgets:
            widget.blockSignals(False)

        self.stateChanged.emit()
Ejemplo n.º 13
0
class SearchWidget(QtWidgets.QLineEdit):

    SPACE_OPERATOR = "and"
    PLACEHOLDER_TEXT = "Search"

    searchChanged = QtCore.Signal()

    def __init__(self, *args):
        QtWidgets.QLineEdit.__init__(self, *args)

        self._dataset = None
        self._spaceOperator = "and"
        self._iconPadding = 6
        self._iconButton = QtWidgets.QPushButton(self)
        self._iconButton.clicked.connect(self._iconClicked)

        icon = studiolibrary.resource().icon("search")
        self.setIcon(icon)

        self._clearButton = QtWidgets.QPushButton(self)
        self._clearButton.setCursor(QtCore.Qt.ArrowCursor)
        icon = studiolibrary.resource().icon("cross")
        self._clearButton.setIcon(icon)
        self._clearButton.setToolTip("Clear all search text")
        self._clearButton.clicked.connect(self._clearClicked)
        self._clearButton.setStyleSheet("background-color: transparent;")

        self.setPlaceholderText(self.PLACEHOLDER_TEXT)
        self.textChanged.connect(self._textChanged)

        self.update()

    def update(self):
        self.updateIconColor()
        self.updateClearButton()

    def setDataset(self, dataset):
        """
        Set the data set for the search widget:
        
        :type dataset: studiolibrary.Dataset
        """
        self._dataset = dataset

    def dataset(self):
        """
        Get the data set for the search widget.
        
        :rtype: studiolibrary.Dataset 
        """
        return self._dataset

    def _clearClicked(self):
        """
        Triggered when the user clicks the cross icon.

        :rtype: None
        """
        self.setText("")
        self.setFocus()

    def _iconClicked(self):
        """
        Triggered when the user clicks on the icon.

        :rtype: None
        """
        if not self.hasFocus():
            self.setFocus()

    def _textChanged(self, text):
        """
        Triggered when the text changes.

        :type text: str
        :rtype: None
        """
        self.search()

    def search(self):
        """Run the search query on the data set."""
        if self.dataset():
            self.dataset().addQuery(self.query())
            self.dataset().search()
        else:
            logger.info("No dataset found the the search widget.")

        self.updateClearButton()
        self.searchChanged.emit()

    def query(self):
        """
        Get the query used for the data set.
        
        :rtype: dict 
        """
        text = str(self.text())

        filters = []
        for filter_ in text.split(' '):
            if filter_.split():
                filters.append(('*', 'contains', filter_))

        uniqueName = 'searchwidget' + str(id(self))

        return {
            'name': uniqueName,
            'operator': self.spaceOperator(),
            'filters': filters
        }

    def updateClearButton(self):
        """
        Update the clear button depending on the current text.

        :rtype: None
        """
        text = self.text()
        if text:
            self._clearButton.show()
        else:
            self._clearButton.hide()

    def contextMenuEvent(self, event):
        """
        Triggered when the user right clicks on the search widget.

        :type event: QtCore.QEvent
        :rtype: None
        """
        self.showContextMenu()

    def spaceOperator(self):
        """
        Get the space operator for the search widget.

        :rtype: str
        """
        return self._spaceOperator

    def setSpaceOperator(self, operator):
        """
        Set the space operator for the search widget.

        :type operator: str
        """
        self._spaceOperator = operator
        self.search()

    def createSpaceOperatorMenu(self, parent=None):
        """
        Return the menu for changing the space operator.

        :type parent: QGui.QMenu
        :rtype: QGui.QMenu
        """
        menu = QtWidgets.QMenu(parent)
        menu.setTitle("Space Operator")

        # Create the space operator for the OR operator
        action = QtWidgets.QAction(menu)
        action.setText("OR")
        action.setCheckable(True)

        callback = partial(self.setSpaceOperator, "or")
        action.triggered.connect(callback)

        if self.spaceOperator() == "or":
            action.setChecked(True)

        menu.addAction(action)

        # Create the space operator for the AND operator
        action = QtWidgets.QAction(menu)
        action.setText("AND")
        action.setCheckable(True)

        callback = partial(self.setSpaceOperator, "and")
        action.triggered.connect(callback)

        if self.spaceOperator() == "and":
            action.setChecked(True)

        menu.addAction(action)

        return menu

    def showContextMenu(self):
        """
        Create and show the context menu for the search widget.

        :rtype QtWidgets.QAction
        """
        menu = QtWidgets.QMenu(self)

        subMenu = self.createStandardContextMenu()
        subMenu.setTitle("Edit")
        menu.addMenu(subMenu)

        subMenu = self.createSpaceOperatorMenu(menu)
        menu.addMenu(subMenu)

        point = QtGui.QCursor.pos()
        action = menu.exec_(point)

        return action

    def setIcon(self, icon):
        """
        Set the icon for the search widget.

        :type icon: QtWidgets.QIcon
        :rtype: None
        """
        self._iconButton.setIcon(icon)

    def setIconColor(self, color):
        """
        Set the icon color for the search widget icon.

        :type color: QtGui.QColor
        :rtype: None
        """
        icon = self._iconButton.icon()
        icon = studioqt.Icon(icon)
        icon.setColor(color)
        self._iconButton.setIcon(icon)

        icon = self._clearButton.icon()
        icon = studioqt.Icon(icon)
        icon.setColor(color)
        self._clearButton.setIcon(icon)

    def updateIconColor(self):
        """
        Update the icon colors to the current foregroundRole.

        :rtype: None
        """
        color = self.palette().color(self.foregroundRole())
        color = studioqt.Color.fromColor(color)
        self.setIconColor(color)

    def settings(self):
        """
        Return a dictionary of the current widget state.

        :rtype: dict
        """
        settings = {
            "text": self.text(),
            "spaceOperator": self.spaceOperator(),
        }
        return settings

    def setSettings(self, settings):
        """
        Restore the widget state from a settings dictionary.

        :type settings: dict
        :rtype: None
        """
        text = settings.get("text", "")
        self.setText(text)

        spaceOperator = settings.get("spaceOperator")
        if spaceOperator:
            self.setSpaceOperator(spaceOperator)

    def resizeEvent(self, event):
        """
        Reimplemented so the icon maintains the same height as the widget.

        :type event:  QtWidgets.QResizeEvent
        :rtype: None
        """
        QtWidgets.QLineEdit.resizeEvent(self, event)

        self.setTextMargins(self.height(), 0, 0, 0)
        size = QtCore.QSize(self.height(), self.height())

        self._iconButton.setIconSize(size)
        self._iconButton.setFixedSize(size)

        self._clearButton.setIconSize(size)

        x = self.width() - self.height()
        self._clearButton.setGeometry(x, 0, self.height(), self.height())
Ejemplo n.º 14
0
class SearchFilter(QtCore.QObject):

    searchChanged = QtCore.Signal()

    class Operator:
        OR = " or "
        AND = " and "

    def __init__(self, pattern, spaceOperator=Operator.AND):
        """
        :type pattern: str
        :type spaceOperator: SearchFilter.Operator
        """
        QtCore.QObject.__init__(self)

        self._matches = 0
        self._pattern = None
        self._resolvedPattern = None
        self._spaceOperator = spaceOperator

        self.setPattern(pattern)

    def pattern(self):
        """
        Return the pattern for the search filter.

        :rtype: str
        """
        return self._pattern

    def setPattern(self, pattern):
        """
        Set the pattern for the search filter.

        :type pattern: str
        """
        self._pattern = pattern
        self._searchChanged()

    def _searchChanged(self):
        """
        Triggered when the search filter changes.

        :rtype: None
        """
        self.resolvePattern()
        self.searchChanged.emit()

    def resolvedPattern(self):
        """
        Return the resolved pattern.

        :rtype: str
        """
        return self._resolvedPattern

    def setResolvedPattern(self, resolvedPattern):
        """
        Set the resolved pattern.

        :type resolvedPattern: str
        :rtype: None
        """
        self._resolvedPattern = resolvedPattern

    def spaceOperator(self):
        """
        Return the operator for all white spaces in the pattern.

        :rtype: SearchFilter.Operator
        """
        return self._spaceOperator

    def setSpaceOperator(self, operator):
        """
        Set the operator for all white spaces in the pattern.

        :type: SearchFilter.Operator
        """
        self._spaceOperator = operator
        self._searchChanged()

    def settings(self):
        """
        Return the state of the search filter as a dict object.

        :rtype: dict
        """
        settings = {}

        settings["pattern"] = self.pattern()
        settings["spaceOperator"] = self.spaceOperator()

        return settings

    def setSettings(self, settings):
        """
        Set the state of the search filter from a dict object.

        :type settings: dict
        :rtype: None
        """
        pattern = settings.get("pattern", "")
        self.setPattern(pattern)

        spaceOperator = settings.get("spaceOperator", self.Operator.AND)
        self.setSpaceOperator(spaceOperator)

    def resolvePattern(self):
        """
        Resolve the pattern to speed up the match method.

        :rtype: None
        """
        pattern = self.pattern()
        spaceOperator = self.spaceOperator()

        pattern = pattern.strip()

        # Case-sensitive is not supported
        pattern = pattern.lower()

        # Remove all double spaces.
        pattern = re.sub(' +', ' ', pattern)

        # Replace all white spaces with the space operator
        pattern = pattern.replace(self.Operator.OR, "_OR_")
        pattern = pattern.replace(self.Operator.AND, "_AND_")
        pattern = pattern.replace(" ", spaceOperator)
        pattern = pattern.replace("_OR_", self.Operator.OR)
        pattern = pattern.replace("_AND_", self.Operator.AND)

        self.setResolvedPattern(pattern)

    def matches(self):
        """
        Return the number of matches from the last match.

        :rtype: int
        """
        return self._matches

    def match(self, text):
        """
        Match the given text to the resolved pattern.

        :type text: str
        :rtype: bool
        """
        match = False
        matches = 0

        pattern = self.resolvedPattern()
        groups = pattern.split(self.Operator.OR)

        for group in groups:

            match = True
            labels = [
                label.lower() for label in group.split(self.Operator.AND)
            ]

            for label in labels:
                if label not in text.lower():
                    matches += 1
                    match = False
                    break
                matches += 1

            if match:
                break

            matches += 1

        if not match:
            matches = 0

        self._matches = matches

        return match
Ejemplo n.º 15
0
class ItemsWidget(QtWidgets.QWidget):

    IconMode = "icon"
    TableMode = "table"

    DEFAULT_PADDING = 5

    DEFAULT_ZOOM_AMOUNT = 90
    DEFAULT_TEXT_HEIGHT = 20
    DEFAULT_WHEEL_SCROLL_STEP = 2

    DEFAULT_MIN_SPACING = 0
    DEFAULT_MAX_SPACING = 50

    DEFAULT_MIN_LIST_SIZE = 15
    DEFAULT_MIN_ICON_SIZE = 50

    itemClicked = QtCore.Signal(object)
    itemDoubleClicked = QtCore.Signal(object)

    zoomChanged = QtCore.Signal(object)
    spacingChanged = QtCore.Signal(object)

    groupClicked = QtCore.Signal(object)

    def __init__(self, *args):
        QtWidgets.QWidget.__init__(self, *args)

        self._dpi = 1
        self._padding = self.DEFAULT_PADDING

        w, h = self.DEFAULT_ZOOM_AMOUNT, self.DEFAULT_ZOOM_AMOUNT

        self._iconSize = QtCore.QSize(w, h)
        self._zoomAmount = self.DEFAULT_ZOOM_AMOUNT
        self._isItemTextVisible = True

        self._dataset = None
        self._treeWidget = TreeWidget(self)

        self._listView = ListView(self)
        self._listView.setTreeWidget(self._treeWidget)

        self._delegate = ItemDelegate()
        self._delegate.setItemsWidget(self)

        self._listView.setItemDelegate(self._delegate)
        self._treeWidget.setItemDelegate(self._delegate)

        self._toastWidget = ToastWidget(self)
        self._toastWidget.hide()
        self._toastEnabled = True

        self._textColor = QtGui.QColor(255, 255, 255, 200)
        self._textSelectedColor = QtGui.QColor(255, 255, 255, 200)
        self._backgroundColor = QtGui.QColor(255, 255, 255, 30)
        self._backgroundHoverColor = QtGui.QColor(255, 255, 255, 35)
        self._backgroundSelectedColor = QtGui.QColor(30, 150, 255)

        layout = QtWidgets.QHBoxLayout(self)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self._treeWidget)
        layout.addWidget(self._listView)

        header = self.treeWidget().header()
        header.sortIndicatorChanged.connect(self._sortIndicatorChanged)

        self.setLayout(layout)

        self.listView().itemClicked.connect(self._itemClicked)
        self.listView().itemDoubleClicked.connect(self._itemDoubleClicked)

        self.treeWidget().itemClicked.connect(self._itemClicked)
        self.treeWidget().itemDoubleClicked.connect(self._itemDoubleClicked)

        self.itemMoved = self._listView.itemMoved
        self.itemDropped = self._listView.itemDropped
        self.itemSelectionChanged = self._treeWidget.itemSelectionChanged

    def _sortIndicatorChanged(self):
        """
        Triggered when the sort indicator changes.

        :rtype: None
        """
        pass

    def _itemClicked(self, item):
        """
        Triggered when the given item has been clicked.

        :type item: studioqt.ItemsWidget
        :rtype: None
        """
        if isinstance(item, GroupItem):
            self.groupClicked.emit(item)
        else:
            self.itemClicked.emit(item)

    def _itemDoubleClicked(self, item):
        """
        Triggered when the given item has been double clicked.

        :type item: studioqt.Item
        :rtype: None
        """
        self.itemDoubleClicked.emit(item)

    def setDataset(self, dataset):
        self._dataset = dataset
        self.setColumnLabels(dataset.Fields)
        dataset.searchFinished.connect(self.updateItems)

    def dataset(self):
        return self._dataset

    def updateItems(self):
        """Sets the items to the widget."""
        selectedItems = self.selectedItems()
        self.clearSelection()

        results = self.dataset().groupedResults()

        items = []

        for group in results:
            if group != "None":
                groupItem = self.createGroupItem(group)
                items.append(groupItem)
            items.extend(results[group])

        self.treeWidget().setItems(items)

        if selectedItems:
            self.selectItems(selectedItems)
            self.scrollToSelectedItem()

    def createGroupItem(self, text, children=None):
        """
        Create a new group item for the given text and children.

        :type text: str
        :type children: list[studioqt.Item]
        
        :rtype: GroupItem
        """
        groupItem = GroupItem()
        groupItem.setName(text)
        groupItem.setStretchToWidget(self)
        groupItem.setChildren(children)

        return groupItem

    def setToastEnabled(self, enabled):
        """
        :type enabled: bool
        :rtype: None
        """
        self._toastEnabled = enabled

    def toastEnabled(self):
        """
        :rtype: bool
        """
        return self._toastEnabled

    def showToastMessage(self, text, duration=500):
        """
        Show a toast with the given text for the given duration.

        :type text: str
        :type duration: None or int
        :rtype: None
        """
        if self.toastEnabled():
            self._toastWidget.setDuration(duration)
            self._toastWidget.setText(text)
            self._toastWidget.show()

    def columnFromLabel(self, *args):
        """
        Reimplemented for convenience.
        
        :return: int 
        """
        return self.treeWidget().columnFromLabel(*args)

    def setColumnHidden(self, column, hidden):
        """
        Reimplemented for convenience.

        Calls self.treeWidget().setColumnHidden(column, hidden)
        """
        self.treeWidget().setColumnHidden(column, hidden)

    def setLocked(self, value):
        """
        Disables drag and drop.

        :Type value: bool
        :rtype: None
        """
        self.listView().setDragEnabled(not value)
        self.listView().setDropEnabled(not value)

    def verticalScrollBar(self):
        """
        Return the active vertical scroll bar.
        
        :rtype: QtWidget.QScrollBar 
        """
        if self.isTableView():
            return self.treeWidget().verticalScrollBar()
        else:
            return self.listView().verticalScrollBar()

    def visualItemRect(self, item):
        """
        Return the visual rect for the item.

        :type item: QtWidgets.QTreeWidgetItem
        :rtype: QtCore.QRect
        """
        if self.isTableView():
            visualRect = self.treeWidget().visualItemRect(item)
        else:
            index = self.treeWidget().indexFromItem(item)
            visualRect = self.listView().visualRect(index)

        return visualRect

    def isItemVisible(self, item):
        """
        Return the visual rect for the item.

        :type item: QtWidgets.QTreeWidgetItem
        :rtype: bool
        """
        height = self.height()
        itemRect = self.visualItemRect(item)
        scrollBarY = self.verticalScrollBar().value()

        y = (scrollBarY - itemRect.y()) + height
        return y > scrollBarY and y < scrollBarY + height

    def scrollToItem(self, item):
        """
        Ensures that the item is visible.

        :type item: QtWidgets.QTreeWidgetItem
        :rtype: None
        """
        position = QtWidgets.QAbstractItemView.PositionAtCenter

        if self.isTableView():
            self.treeWidget().scrollToItem(item, position)
        elif self.isIconView():
            self.listView().scrollToItem(item, position)

    def scrollToSelectedItem(self):
        """
        Ensures that the item is visible.

        :rtype: None
        """
        item = self.selectedItem()
        if item:
            self.scrollToItem(item)

    def dpi(self):
        """
        return the zoom multiplier.

        Used for high resolution devices.

        :rtype: int
        """
        return self._dpi

    def setDpi(self, dpi):
        """
        Set the zoom multiplier.

        Used for high resolution devices.

        :type dpi: int
        """
        self._dpi = dpi
        self.refreshSize()

    def itemAt(self, pos):
        """
        Return the current item at the given pos.

        :type pos: QtWidgets.QPoint
        :rtype: studioqt.Item
        """
        if self.isIconView():
            return self.listView().itemAt(pos)
        else:
            return self.treeView().itemAt(pos)

    def insertItems(self, items, itemAt=None):
        """
        Insert the given items at the given itemAt position.

        :type items: list[studioqt.Item]
        :type itemAt: studioqt.Item
        :rtype: Nones
        """
        self.addItems(items)
        self.moveItems(items, itemAt=itemAt)
        self.treeWidget().setItemsSelected(items, True)

    def moveItems(self, items, itemAt=None):
        """
        Move the given items to the given itemAt position.

        :type items: list[studioqt.Item]
        :type itemAt: studioqt.Item
        :rtype: None
        """
        self.listView().moveItems(items, itemAt=itemAt)

    def listView(self):
        """
        Return the list view that contains the items.

        :rtype: ListView
        """
        return self._listView

    def treeWidget(self):
        """
        Return the tree widget that contains the items.

        :rtype: TreeWidget
        """
        return self._treeWidget

    def clear(self):
        """
        Reimplemented for convenience.

        Calls self.treeWidget().clear()
        """
        self.treeWidget().clear()

    def refresh(self):
        """Refresh the item size."""
        self.refreshSize()

    def refreshSize(self):
        """
        Refresh the size of the items.

        :rtype: None
        """
        self.setZoomAmount(self.zoomAmount() + 1)
        self.setZoomAmount(self.zoomAmount() - 1)
        self.repaint()

    def itemFromIndex(self, index):
        """
        Return a pointer to the QTreeWidgetItem assocated with the given index.

        :type index: QtCore.QModelIndex
        :rtype: QtWidgets.QTreeWidgetItem
        """
        return self._treeWidget.itemFromIndex(index)

    def textFromItems(self, *args, **kwargs):
        """
        Return all data for the given items and given column.

        :rtype: list[str]
        """
        return self.treeWidget().textFromItems(*args, **kwargs)

    def textFromColumn(self, *args, **kwargs):
        """
        Return all data for the given column.

        :rtype: list[str]
        """
        return self.treeWidget().textFromColumn(*args, **kwargs)

    def toggleTextVisible(self):
        """
        Toggle the item text visibility.

        :rtype: None
        """
        if self.isItemTextVisible():
            self.setItemTextVisible(False)
        else:
            self.setItemTextVisible(True)

    def setItemTextVisible(self, value):
        """
        Set the visibility of the item text.

        :type value: bool
        :rtype: None
        """
        self._isItemTextVisible = value
        self.refreshSize()

    def isItemTextVisible(self):
        """
        Return the visibility of the item text.

        :rtype: bool
        """
        if self.isIconView():
            return self._isItemTextVisible
        else:
            return True

    def itemTextHeight(self):
        """
        Return the height of the item text.

        :rtype: int
        """
        return self.DEFAULT_TEXT_HEIGHT * self.dpi()

    def itemDelegate(self):
        """
        Return the item delegate for the views.

        :rtype: ItemDelegate
        """
        return self._delegate

    def settings(self):
        """
        Return the current state of the widget.

        :rtype: dict
        """
        settings = {}

        settings["columnLabels"] = self.columnLabels()
        settings["padding"] = self.padding()
        settings["spacing"] = self.spacing()
        settings["zoomAmount"] = self.zoomAmount()
        settings["selectedPaths"] = self.selectedPaths()
        settings["textVisible"] = self.isItemTextVisible()
        settings.update(self.treeWidget().settings())

        return settings

    def setSettings(self, settings):
        """
        Set the current state of the widget.

        :type settings: dict
        :rtype: None
        """
        self.setToastEnabled(False)

        padding = settings.get("padding", 5)
        self.setPadding(padding)

        spacing = settings.get("spacing", 2)
        self.setSpacing(spacing)

        zoomAmount = settings.get("zoomAmount", 100)
        self.setZoomAmount(zoomAmount)

        selectedPaths = settings.get("selectedPaths", [])
        self.selectPaths(selectedPaths)

        itemTextVisible = settings.get("textVisible", True)
        self.setItemTextVisible(itemTextVisible)

        self.treeWidget().setSettings(settings)

        self.setToastEnabled(True)

        return settings

    def createCopyTextMenu(self):
        return self.treeWidget().createCopyTextMenu()

    def createItemSettingsMenu(self):

        menu = QtWidgets.QMenu("Item View", self)

        action = SeparatorAction("View Settings", menu)
        menu.addAction(action)

        action = SliderAction("Size", menu)
        action.slider().setMinimum(10)
        action.slider().setMaximum(200)
        action.slider().setValue(self.zoomAmount())
        action.slider().valueChanged.connect(self.setZoomAmount)
        menu.addAction(action)

        action = SliderAction("Border", menu)
        action.slider().setMinimum(0)
        action.slider().setMaximum(20)
        action.slider().setValue(self.padding())
        action.slider().valueChanged.connect(self.setPadding)
        menu.addAction(action)
        #
        action = SliderAction("Spacing", menu)
        action.slider().setMinimum(self.DEFAULT_MIN_SPACING)
        action.slider().setMaximum(self.DEFAULT_MAX_SPACING)
        action.slider().setValue(self.spacing())
        action.slider().valueChanged.connect(self.setSpacing)
        menu.addAction(action)

        action = SeparatorAction("Item Options", menu)
        menu.addAction(action)

        action = QtWidgets.QAction("Show labels", menu)
        action.setCheckable(True)
        action.setChecked(self.isItemTextVisible())
        action.triggered[bool].connect(self.setItemTextVisible)
        menu.addAction(action)

        return menu

    def createSettingsMenu(self):
        """
        Create and return the settings menu for the widget.

        :rtype: QtWidgets.QMenu
        """
        menu = QtWidgets.QMenu("Item View", self)

        menu.addSeparator()

        action = QtWidgets.QAction("Show labels", menu)
        action.setCheckable(True)
        action.setChecked(self.isItemTextVisible())
        action.triggered[bool].connect(self.setItemTextVisible)
        menu.addAction(action)

        menu.addSeparator()

        copyTextMenu = self.treeWidget().createCopyTextMenu()
        menu.addMenu(copyTextMenu)

        menu.addSeparator()

        action = SliderAction("Size", menu)
        action.slider().setMinimum(10)
        action.slider().setMaximum(200)
        action.slider().setValue(self.zoomAmount())
        action.slider().valueChanged.connect(self.setZoomAmount)
        menu.addAction(action)

        action = SliderAction("Border", menu)
        action.slider().setMinimum(0)
        action.slider().setMaximum(20)
        action.slider().setValue(self.padding())
        action.slider().valueChanged.connect(self.setPadding)
        menu.addAction(action)
        #
        action = SliderAction("Spacing", menu)
        action.slider().setMinimum(self.DEFAULT_MIN_SPACING)
        action.slider().setMaximum(self.DEFAULT_MAX_SPACING)
        action.slider().setValue(self.spacing())
        action.slider().valueChanged.connect(self.setSpacing)
        menu.addAction(action)

        return menu

    def createItemsMenu(self, items=None):
        """
        Create the item menu for given item.

        :rtype: QtWidgets.QMenu
        """
        item = items or self.selectedItem()

        menu = QtWidgets.QMenu(self)

        if item:
            try:
                item.contextMenu(menu)
            except Exception as error:
                logger.exception(error)
        else:
            action = QtWidgets.QAction(menu)
            action.setText("No Item selected")
            action.setDisabled(True)

            menu.addAction(action)

        return menu

    def createContextMenu(self):
        """
        Create and return the context menu for the widget.

        :rtype: QtWidgets.QMenu
        """
        menu = self.createItemsMenu()

        settingsMenu = self.createSettingsMenu()
        menu.addMenu(settingsMenu)

        return menu

    def contextMenuEvent(self, event):
        """
        Show the context menu.

        :type event: QtCore.QEvent
        :rtype: None
        """
        menu = self.createContextMenu()
        point = QtGui.QCursor.pos()
        return menu.exec_(point)

    # ------------------------------------------------------------------------
    # Support for saving the current item order.
    # ------------------------------------------------------------------------

    def itemData(self, columnLabels):
        """
        Return all column data for the given column labels.

        :type columnLabels: list[str]
        :rtype: dict
                
        """
        data = {}

        for item in self.items():
            key = item.id()

            for columnLabel in columnLabels:
                column = self.treeWidget().columnFromLabel(columnLabel)
                value = item.data(column, QtCore.Qt.EditRole)

                data.setdefault(key, {})
                data[key].setdefault(columnLabel, value)

        return data

    def setItemData(self, data):
        """
        Set the item data for all the current items.

        :type data: dict
        :rtype: None
        """
        for item in self.items():
            key = item.id()
            if key in data:
                item.setItemData(data[key])

    def updateColumns(self):
        """
        Update the column labels with the current item data.

        :rtype: None
        """
        self.treeWidget().updateHeaderLabels()

    def columnLabels(self):
        """
        Set all the column labels.

        :rtype: list[str]
        """
        return self.treeWidget().columnLabels()

    def _removeDuplicates(self, labels):
        """
        Removes duplicates from a list in Python, whilst preserving order.

        :type labels: list[str]
        :rtype: list[str]
        """
        s = set()
        sadd = s.add
        return [x for x in labels if x.strip() and not (x in s or sadd(x))]

    def setColumnLabels(self, labels):
        """
        Set the columns for the widget.

        :type labels: list[str]
        :rtype: None
        """
        labels = self._removeDuplicates(labels)

        if "Custom Order" not in labels:
            labels.append("Custom Order")

        # if "Search Order" not in labels:
        #     labels.append("Search Order")

        self.treeWidget().setHeaderLabels(labels)

        self.setColumnHidden("Custom Order", True)
        # self.setColumnHidden("Search Order", True)

    def items(self):
        """
        Return all the items in the widget.

        :rtype: list[studioqt.Item]
        """
        return self._treeWidget.items()

    def addItems(self, items):
        """
        Add the given items to the items widget.

        :type items: list[studioqt.Item]
        :rtype: None
        """
        self._treeWidget.addTopLevelItems(items)

    def addItem(self, item):
        """
        Add the item to the tree widget.

        :type item: Item
        :rtype: None
        """
        self.addItems([item])

    def columnLabelsFromItems(self):
        """
        Return the column labels from all the items.

        :rtype: list[str]
        """
        seq = []
        for item in self.items():
            seq.extend(item._textColumnOrder)

        seen = set()
        return [x for x in seq if x not in seen and not seen.add(x)]

    def refreshColumns(self):
        self.setColumnLabels(self.columnLabelsFromItems())

    def padding(self):
        """
        Return the item padding.

        :rtype: int
        """
        return self._padding

    def setPadding(self, value):
        """
        Set the item padding.

        :type: int
        :rtype: None
        """
        if value % 2 == 0:
            self._padding = value
        else:
            self._padding = value + 1
        self.repaint()

        self.showToastMessage("Border: " + str(value))

    def spacing(self):
        """
        Return the spacing between the items.

        :rtype: int
        """
        return self._listView.spacing()

    def setSpacing(self, spacing):
        """
        Set the spacing between the items.

        :type spacing: int
        :rtype: None
        """
        self._listView.setSpacing(spacing)
        self.scrollToSelectedItem()

        self.showToastMessage("Spacing: " + str(spacing))

    def iconSize(self):
        """
        Return the icon size for the views.

        :rtype: QtCore.QSize
        """
        return self._iconSize

    def setIconSize(self, size):
        """
        Set the icon size for the views.

        :type size: QtCore.QSize
        :rtype: None
        """
        self._iconSize = size
        self._listView.setIconSize(size)
        self._treeWidget.setIconSize(size)

    def clearSelection(self):
        """
        Clear the user selection.

        :rtype: None
        """
        self._treeWidget.clearSelection()

    def wheelScrollStep(self):
        """
        Return the wheel scroll step amount.

        :rtype: int
        """
        return self.DEFAULT_WHEEL_SCROLL_STEP

    def model(self):
        """
        Return the model that this view is presenting.

        :rtype: QAbstractItemModel
        """
        return self._treeWidget.model()

    def indexFromItem(self, item):
        """
        Return the QModelIndex assocated with the given item.

        :type item: QtWidgets.QTreeWidgetItem.
        :rtype: QtCore.QModelIndex
        """
        return self._treeWidget.indexFromItem(item)

    def selectionModel(self):
        """
        Return the current selection model.

        :rtype: QtWidgets.QItemSelectionModel
        """
        return self._treeWidget.selectionModel()

    def selectedItem(self):
        """
        Return the last selected non-hidden item.

        :rtype: QtWidgets.QTreeWidgetItem
        """
        return self._treeWidget.selectedItem()

    def selectedItems(self):
        """
        Return a list of all selected non-hidden items.

        :rtype: list[QtWidgets.QTreeWidgetItem]
        """
        return self._treeWidget.selectedItems()

    def setItemHidden(self, item, value):
        """
        Set the visibility of given item.

        :type item: QtWidgets.QTreeWidgetItem
        :type value: bool
        :rtype: None
        """
        item.setHidden(value)

    def setItemsHidden(self, items, value):
        """
        Set the visibility of given items.

        :type items: list[QtWidgets.QTreeWidgetItem]
        :type value: bool
        :rtype: None
        """
        for item in items:
            self.setItemHidden(item, value)

    def selectedPaths(self):
        """
        Return the selected item paths.

        :rtype: list[str]
        """
        paths = []
        for item in self.selectedItems():
            path = item.url().toLocalFile()
            paths.append(path)
        return paths

    def selectPaths(self, paths):
        """
        Selected the items that have the given paths.

        :type paths: list[str]
        :rtype: None
        """
        for item in self.items():
            path = item.id()
            if path in paths:
                item.setSelected(True)

    def selectItems(self, items):
        """
        Select the given items.

        :type items: list[studiolibrary.LibraryItem]
        :rtype: None
        """
        paths = [item.id() for item in items]
        self.selectPaths(paths)

    def isIconView(self):
        """
        Return True if widget is in Icon mode.

        :rtype: bool
        """
        return not self._listView.isHidden()

    def isTableView(self):
        """
        Return True if widget is in List mode.

        :rtype: bool
        """
        return not self._treeWidget.isHidden()

    def setViewMode(self, mode):
        """
        Set the view mode for this widget.

        :type mode: str
        :rtype: None
        """
        if mode == self.IconMode:
            self.setZoomAmount(self.DEFAULT_MIN_ICON_SIZE)
        elif mode == self.TableMode:
            self.setZoomAmount(self.DEFAULT_MIN_ICON_SIZE)

    def _setViewMode(self, mode):
        """
        Set the view mode for this widget.

        :type mode: str
        :rtype: None
        """
        if mode == self.IconMode:
            self.setIconMode()
        elif mode == self.TableMode:
            self.setListMode()

    def setListMode(self):
        """
        Set the tree widget visible.

        :rtype: None
        """
        self._listView.hide()
        self._treeWidget.show()
        self._treeWidget.setFocus()

    def setIconMode(self):
        """
        Set the list view visible.

        :rtype: None
        """
        self._treeWidget.hide()
        self._listView.show()
        self._listView.setFocus()

    def zoomAmount(self):
        """
        Return the zoom amount for the widget.

        :rtype: int
        """
        return self._zoomAmount

    def setZoomAmount(self, value):
        """
        Set the zoom amount for the widget.

        :type value: int
        :rtype: None
        """
        if value < self.DEFAULT_MIN_LIST_SIZE:
            value = self.DEFAULT_MIN_LIST_SIZE

        self._zoomAmount = value
        size = QtCore.QSize(value * self.dpi(), value * self.dpi())
        self.setIconSize(size)

        if value >= self.DEFAULT_MIN_ICON_SIZE:
            self._setViewMode(self.IconMode)
        else:
            self._setViewMode(self.TableMode)

        columnWidth = value * self.dpi() + self.itemTextHeight()

        self._treeWidget.setIndentation(0)
        self._treeWidget.setColumnWidth(0, columnWidth)
        self.scrollToSelectedItem()

        msg = "Size: {0}%".format(value)
        self.showToastMessage(msg)

    def wheelEvent(self, event):
        """
        Triggered on any wheel events for the current viewport.

        :type event: QtWidgets.QWheelEvent
        :rtype: None
        """
        modifier = QtWidgets.QApplication.keyboardModifiers()

        validModifiers = (
            QtCore.Qt.AltModifier,
            QtCore.Qt.ControlModifier,
        )

        if modifier in validModifiers:
            numDegrees = event.delta() / 8
            numSteps = numDegrees / 15

            delta = (numSteps * self.wheelScrollStep())
            value = self.zoomAmount() + delta
            self.setZoomAmount(value)

    def setTextColor(self, color):
        """
        Set the item text color.

        :type color: QtWidgets.QtColor
        """
        self._textColor = color

    def setTextSelectedColor(self, color):
        """
        Set the text color when an item is selected.

        :type color: QtWidgets.QtColor
        """
        self._textSelectedColor = color

    def setBackgroundColor(self, color):
        """
        Set the item background color.

        :type color: QtWidgets.QtColor
        """
        self._backgroundColor = color

    def setBackgroundHoverColor(self, color):
        """
        Set the background color when the mouse hovers over the item.

        :type color: QtWidgets.QtColor
        """
        self._backgroundHoverColor = color

    def setBackgroundSelectedColor(self, color):
        """
        Set the background color when an item is selected.

        :type color: QtWidgets.QtColor
        """
        self._backgroundSelectedColor = color
        self._listView.setRubberBandColor(QtGui.QColor(200, 200, 200, 255))

    def textColor(self):
        """
        Return the item text color.

        :rtype: QtGui.QColor
        """
        return self._textColor

    def textSelectedColor(self):
        """
        Return the item text color when selected.

        :rtype: QtGui.QColor
        """
        return self._textSelectedColor

    def backgroundColor(self):
        """
        Return the item background color.

        :rtype: QtWidgets.QtColor
        """
        return self._backgroundColor

    def backgroundHoverColor(self):
        """
        Return the background color for when the mouse is over an item.

        :rtype: QtWidgets.QtColor
        """
        return self._backgroundHoverColor

    def backgroundSelectedColor(self):
        """
        Return the background color when an item is selected.

        :rtype: QtWidgets.QtColor
        """
        return self._backgroundSelectedColor
Ejemplo n.º 16
0
class ImageSequence(QtCore.QObject):

    DEFAULT_FPS = 24

    frameChanged = QtCore.Signal(int)

    def __init__(self, path, *args):
        QtCore.QObject.__init__(self, *args)

        self._fps = self.DEFAULT_FPS
        self._timer = None
        self._frame = 0
        self._frames = []
        self._dirname = None
        self._paused = False

        if path:
            self.setDirname(path)

    def setDirname(self, dirname):
        """
        Set the location to the image sequence.

        :type dirname: str
        :rtype: None
        """
        def naturalSortItems(items):
            """
            Sort the given list in the way that humans expect.
            """
            convert = lambda text: int(text) if text.isdigit() else text
            alphanum_key = lambda key: [
                convert(c) for c in re.split('([0-9]+)', key)
            ]
            items.sort(key=alphanum_key)

        self._dirname = dirname
        if os.path.isdir(dirname):
            self._frames = [
                dirname + "/" + filename for filename in os.listdir(dirname)
            ]
            naturalSortItems(self._frames)

    def dirname(self):
        """
        Return the location to the image sequence.

        :rtype: str
        """
        return self._dirname

    def reset(self):
        """
        Stop and reset the current frame to 0.

        :rtype: None
        """
        if not self._timer:
            self._timer = QtCore.QTimer(self.parent())
            self._timer.setSingleShot(False)
            self.connect(self._timer, QtCore.SIGNAL('timeout()'),
                         self._frameChanged)

        if not self._paused:
            self._frame = 0
        self._timer.stop()

    def pause(self):
        """
        ImageSequence will enter Paused state.

        :rtype: None
        """
        self._paused = True
        self._timer.stop()

    def resume(self):
        """
        ImageSequence will enter Playing state.

        :rtype: None
        """
        if self._paused:
            self._paused = False
            self._timer.start()

    def stop(self):
        """
        Stops the movie. ImageSequence enters NotRunning state.

        :rtype: None
        """
        self._timer.stop()

    def start(self):
        """
        Starts the movie. ImageSequence will enter Running state

        :rtype: None
        """
        self.reset()
        if self._timer:
            self._timer.start(1000.0 / self._fps)

    def frames(self):
        """
        Return all the filenames in the image sequence.

        :rtype: list[str]
        """
        return self._frames

    def _frameChanged(self):
        """
        Triggered when the current frame changes.

        :rtype: None
        """
        if not self._frames:
            return

        frame = self._frame
        frame += 1
        self.jumpToFrame(frame)

    def percent(self):
        """
        Return the current frame position as a percentage.

        :rtype: None
        """
        if len(self._frames) == self._frame + 1:
            _percent = 1
        else:
            _percent = float(
                (len(self._frames) + self._frame)) / len(self._frames) - 1
        return _percent

    def frameCount(self):
        """
        Return the number of frames.

        :rtype: int
        """
        return len(self._frames)

    def currentIcon(self):
        """
        Returns the current frame as a QIcon.

        :rtype: QtGui.QIcon
        """
        return QtGui.QIcon(self.currentFilename())

    def currentPixmap(self):
        """
        Return the current frame as a QPixmap.

        :rtype: QtGui.QPixmap
        """
        return QtGui.QPixmap(self.currentFilename())

    def currentFilename(self):
        """
        Return the current file name.

        :rtype: str or None
        """
        try:
            return self._frames[self.currentFrameNumber()]
        except IndexError:
            pass

    def currentFrameNumber(self):
        """
        Return the current frame.

        :rtype: int or None
        """
        return self._frame

    def jumpToFrame(self, frame):
        """
        Set the current frame.

        :rtype: int or None
        """
        if frame >= self.frameCount():
            frame = 0
        self._frame = frame
        self.frameChanged.emit(frame)
Ejemplo n.º 17
0
class LibraryItemSignals(QtCore.QObject):
    """"""
    saved = QtCore.Signal(object)
    saving = QtCore.Signal(object)
    loaded = QtCore.Signal(object)
Ejemplo n.º 18
0
class Library(QtCore.QObject):

    Fields = [
        "icon",
        "name",
        "path",
        "type",
        "folder",
        "category",
        # "modified"
    ]

    SortFields = [
        "name",
        "path",
        "type",
        "folder",
        "category",
        "Custom Order",  # legacy case
        # "modified"
    ]

    GroupFields = [
        "type",
        "category",
        # "modified",
    ]

    dataChanged = QtCore.Signal()
    searchStarted = QtCore.Signal()
    searchFinished = QtCore.Signal()
    searchTimeFinished = QtCore.Signal()

    def __init__(self, path=None, libraryWindow=None, *args):
        QtCore.QObject.__init__(self, *args)

        self._path = path
        self._mtime = None
        self._data = {}
        self._items = []
        self._fields = []
        self._sortBy = []
        self._groupBy = []
        self._results = []
        self._queries = []
        self._groupedResults = {}
        self._searchTime = 0
        self._searchEnabled = True
        self._libraryWindow = libraryWindow

        self.setPath(path)
        self.setDirty(True)

    def sortBy(self):
        """
        Get the list of fields to sort by.
        
        :rtype: list[str] 
        """
        return self._sortBy

    def setSortBy(self, fields):
        """
        Set the list of fields to group by.
        
        Example:
            library.setSortBy(["name:asc", "type:asc"])
        
        :type fields: list[str] 
        """
        self._sortBy = fields

    def groupBy(self):
        """
        Get the list of fields to group by.
        
        :rtype: list[str] 
        """
        return self._groupBy

    def setGroupBy(self, fields):
        """
        Set the list of fields to group by.
        
        Example:
            library.setGroupBy(["name:asc", "type:asc"])
        
        :type fields: list[str] 
        """
        self._groupBy = fields

    def settings(self):
        """
        Get the settings for the dataset.
        
        :rtype: dict 
        """
        return {"sortBy": self.sortBy(), "groupBy": self.groupBy()}

    def setSettings(self, settings):
        """
        Set the settings for the dataset object.
        
        :type settings: dict
        """
        value = settings.get('sortBy')
        if value is not None:
            self.setSortBy(value)

        value = settings.get('groupBy')
        if value is not None:
            self.setGroupBy(value)

    def setSearchEnabled(self, enabled):
        """Enable or disable the search the for the library."""
        self._searchEnabled = enabled

    def isSearchEnabled(self):
        """Check if search is enabled for the library."""
        return self._searchEnabled

    def recursiveDepth(self):
        """
        Return the recursive search depth.
        
        :rtype: int
        """
        return studiolibrary.config().get('recursiveSearchDepth')

    def fields(self):
        """Return all the fields for the library."""
        return self._fields

    def path(self):
        """
        Return the disc location of the db.

        :rtype: str
        """
        return self._path

    def setPath(self, path):
        """
        Set the disc location of the db.

        :type path: str
        """
        self._path = path

    def databasePath(self):
        """
        Return the path to the database.
        
        :rtype: str 
        """
        formatString = studiolibrary.config().get('databasePath')
        return studiolibrary.formatPath(formatString, path=self.path())

    def distinct(self, field, queries=None, sortBy="name"):
        """
        Get all the values for the given field.
        
        :type field: str
        :type queries None or list[dict]
        :type sortBy: str
        :rtype: list 
        """
        results = {}
        queries = queries or []

        for item in self.findItems(queries):
            value = item.itemData().get(field)
            results.setdefault(value, {'count': 0, 'name': value})
            results[value]['count'] += 1

        def sortKey(facet):
            return facet.get(sortBy)

        return sorted(results.values(), key=sortKey)

    def mtime(self):
        """
        Return when the database was last modified.

        :rtype: float or None
        """
        path = self.databasePath()
        mtime = None

        if os.path.exists(path):
            mtime = os.path.getmtime(path)

        return mtime

    def setDirty(self, value):
        """
        Update the model object with the current database timestamp.

        :type: bool
        """
        if value:
            self._mtime = None
        else:
            self._mtime = self.mtime()

    def isDirty(self):
        """
        Return True if the database has changed on disc.

        :rtype: bool
        """
        return not self._items or self._mtime != self.mtime()

    def read(self):
        """
        Read the database from disc and return a dict object.

        :rtype: dict
        """
        if self.path():
            if self.isDirty():
                self._data = studiolibrary.readJson(self.databasePath())
                self.setDirty(False)
        else:
            logger.info('No path set for reading the data from disc.')

        return self._data

    def save(self, data):
        """
        Write the given dict object to the database on disc.

        :type data: dict
        :rtype: None
        """
        if self.path():
            studiolibrary.saveJson(self.databasePath(), data)
            self.setDirty(True)
        else:
            logger.info('No path set for saving the data to disc.')

    def sync(self):
        """Sync the file system with the database."""
        if not self.path():
            logger.info('No path set for syncing data')
            return

        data = self.read()

        for path in data.keys():
            if not os.path.exists(path):
                del data[path]

        depth = self.recursiveDepth()
        items = studiolibrary.findItems(
            self.path(),
            depth=depth,
        )

        for item in items:
            path = item.path()

            itemData = data.get(path, {})
            itemData.update(item.itemData())

            data[path] = itemData

        self.postSync(data)

        self.save(data)

        self.dataChanged.emit()

    def postSync(self, data):
        """
        Use this function to execute code on the data after sync, but before save and dataChanged.emit

        :type data: dict
        :rtype: None
        """
        pass

    def createItems(self, libraryWindow=None):
        """
        Create all the items for the model.

        :rtype: list[studiolibrary.LibraryItem] 
        """
        # Check if the database has changed since the last read call
        if self.isDirty():

            paths = self.read().keys()
            items = studiolibrary.itemsFromPaths(paths,
                                                 library=self,
                                                 libraryWindow=libraryWindow)

            self._items = list(items)
            self.loadItemData(self._items)

        return self._items

    def findItems(self, queries):
        """
        Get the items that match the given queries.
        
        Examples:
            
            queries = [
                {
                    'operator': 'or',
                    'filters': [
                        ('folder', 'is' '/library/proj/test'),
                        ('folder', 'startswith', '/library/proj/test'),
                    ]
                },
                {
                    'operator': 'and',
                    'filters': [
                        ('path', 'contains' 'test'),
                        ('path', 'contains', 'run'),
                    ]
                }
            ]
            
            print(library.find(queries))
            
        :type queries: list[dict]            
        :rtype: list[studiolibrary.LibraryItem]
        """
        fields = []
        results = []

        logger.debug("Search queries:")
        for query in queries:
            logger.debug('Query: %s', query)

        items = self.createItems(libraryWindow=self._libraryWindow)
        for item in items:
            match = self.match(item.itemData(), queries)
            if match:
                results.append(item)
            fields.extend(item.itemData().keys())

        self._fields = list(set(fields))

        if self.sortBy():
            results = self.sorted(results, self.sortBy())

        return results

    def addQuery(self, query):
        """
        Add the given query to the dataset.
        
        Examples:
            addQuery({
                'operator': 'or',
                'filters': [
                    ('folder', 'is' '/library/proj/test'),
                    ('folder', 'startswith', '/library/proj/test'),
                ]
            })
        
        :type query: dict
        """
        if query.get('name'):
            for i, query_ in enumerate(self._queries):
                if query_.get('name') == query.get('name'):
                    self._queries[i] = query

        if query not in self._queries:
            self._queries.append(query)

    def removeQuery(self, name):
        """
        Remove the query with the given name.
        
        :type name: str 
        """
        for query in self._queries:
            if query.get('name') == name:
                self._queries.remove(query)
                break

    def queryExists(self, name):
        """
        Check if the given query name exists.
        
        :type name: str
        :rtype: bool 
        """
        for query in self._queries:
            if query.get('name') == name:
                return True

        return False

    def search(self):
        """Run a search using the queries added to this dataset."""
        if not self.isSearchEnabled():
            logger.debug('Search is disabled')
            return

        t = time.time()

        logger.debug("Searching items")

        self.searchStarted.emit()

        self._results = self.findItems(self._queries)

        self._groupedResults = self.groupItems(self._results, self.groupBy())

        self.searchFinished.emit()

        self._searchTime = time.time() - t

        self.searchTimeFinished.emit()

        logger.debug('Search time: %s', self._searchTime)

    def results(self):
        """
        Return the items found after a search is ran.
        
        :rtype: list[Item] 
        """
        return self._results

    def groupedResults(self):
        """
        Get the results grouped after a search is ran.
        
        :rtype: dict
        """
        return self._groupedResults

    def searchTime(self):
        """
        Return the time taken to run a search.
        
        :rtype: float 
        """
        return self._searchTime

    def addItem(self, item):
        """
        Add the given item to the database.    
    
        :type item: studiolibrary.LibraryItem
        :rtype: None 
        """
        self.saveItemData([item])

    def addItems(self, items):
        """
        Add the given items to the database.
        
        :type items: list[studiolibrary.LibraryItem]
        """
        self.saveItemData(items)

    def updateItem(self, item):
        """
        Update the given item in the database.    
    
        :type item: studiolibrary.LibraryItem
        :rtype: None 
        """
        self.saveItemData([item])

    def saveItemData(self, items, emitDataChanged=True):
        """
        Add the given items to the database.

        :type items: list[studiolibrary.LibraryItem]
        :type emitDataChanged: bool
        """
        logger.debug("Save item data %s", items)

        data_ = self.read()

        for item in items:
            path = item.path()
            data = item.itemData()
            data_.setdefault(path, {})
            data_[path].update(data)

        self.save(data_)

        if emitDataChanged:
            self.search()
            self.dataChanged.emit()

    def loadItemData(self, items):
        """
        Load the item data from the database to the given items.

        :type items: list[studiolibrary.LibraryItem]
        """
        logger.debug("Loading item data %s", items)

        data = self.read()

        for item in items:
            key = item.id()
            if key in data:
                item.setItemData(data[key])

    def addPaths(self, paths, data=None):
        """
        Add the given path and the given data to the database.    
    
        :type paths: list[str]
        :type data: dict or None
        :rtype: None 
        """
        data = data or {}
        self.updatePaths(paths, data)

    def updatePaths(self, paths, data):
        """
        Update the given paths with the given data in the database.

        :type paths: list[str]
        :type data: dict
        :rtype: None
        """
        data_ = self.read()
        paths = studiolibrary.normPaths(paths)

        for path in paths:
            if path in data_:
                data_[path].update(data)
            else:
                data_[path] = data

        self.save(data_)

    def copyPath(self, src, dst):
        """
        Copy the given source path to the given destination path.

        :type src: str
        :type dst: str
        :rtype: str
        """
        self.addPaths([dst])
        return dst

    def renamePath(self, src, dst):
        """
        Rename the source path to the given name.

        :type src: str
        :type dst: str
        :rtype: str
        """
        studiolibrary.renamePathInFile(self.databasePath(), src, dst)
        self.setDirty(True)
        return dst

    def removePath(self, path):
        """
        Remove the given path from the database.

        :type path: str
        :rtype: None
        """
        self.removePaths([path])

    def removePaths(self, paths):
        """
        Remove the given paths from the database.

        :type paths: list[str]
        :rtype: None
        """
        data = self.read()

        paths = studiolibrary.normPaths(paths)

        for path in paths:
            if path in data:
                del data[path]

        self.save(data)

    @staticmethod
    def match(data, queries):
        """
        Match the given data with the given queries.
        
        Examples:
            
            queries = [
                {
                    'operator': 'or',
                    'filters': [
                        ('folder', 'is' '/library/proj/test'),
                        ('folder', 'startswith', '/library/proj/test'),
                    ]
                },
                {
                    'operator': 'and',
                    'filters': [
                        ('path', 'contains' 'test'),
                        ('path', 'contains', 'run'),
                    ]
                }
            ]
            
            print(library.find(queries))
        """
        matches = []

        for query in queries:

            filters = query.get('filters')
            operator = query.get('operator', 'and')

            if not filters:
                continue

            match = False

            for key, cond, value in filters:

                if key == '*':
                    itemValue = unicode(data)
                else:
                    itemValue = data.get(key)

                if isinstance(value, basestring):
                    value = value.lower()

                if isinstance(itemValue, basestring):
                    itemValue = itemValue.lower()

                if not itemValue:
                    match = False

                elif cond == 'contains':
                    match = value in itemValue

                elif cond == 'not_contains':
                    match = value not in itemValue

                elif cond == 'is':
                    match = value == itemValue

                elif cond == 'not':
                    match = value != itemValue

                elif cond == 'startswith':
                    match = itemValue.startswith(value)

                if operator == 'or' and match:
                    break

                if operator == 'and' and not match:
                    break

            matches.append(match)

        return all(matches)

    @staticmethod
    def sorted(items, sortBy):
        """
        Return the given data sorted using the sortBy argument.
        
        Example:
            data = [
                {'name':'red', 'index':1},
                {'name':'green', 'index':2},
                {'name':'blue', 'index':3},
            ]
            
            sortBy = ['index:asc', 'name']
            # sortBy = ['index:dsc', 'name']
            
            print(sortedData(data, sortBy))
            
        :type items: list[Item]
        :type sortBy: list[str]
        :rtype: list[Item]
        """
        logger.debug('Sort by: %s', sortBy)

        t = time.time()

        for field in reversed(sortBy):

            tokens = field.split(':')

            reverse = False
            if len(tokens) > 1:
                field = tokens[0]
                reverse = tokens[1] != 'asc'

            def sortKey(item):

                default = False if reverse else ''

                return item.itemData().get(field, default)

            items = sorted(items, key=sortKey, reverse=reverse)

        logger.debug("Sort items took %s", time.time() - t)

        return items

    @staticmethod
    def groupItems(items, fields):
        """
        Group the given items by the given field.

        :type items: list[Item]
        :type fields: list[str]
        :rtype: dict
        """
        logger.debug('Group by: %s', fields)

        # Only support for top level grouping at the moment.
        if fields:
            field = fields[0]
        else:
            return {'None': items}

        t = time.time()

        results_ = {}
        tokens = field.split(':')

        reverse = False
        if len(tokens) > 1:
            field = tokens[0]
            reverse = tokens[1] != 'asc'

        for item in items:
            value = item.itemData().get(field)
            if value:
                results_.setdefault(value, [])
                results_[value].append(item)

        groups = sorted(results_.keys(), reverse=reverse)

        results = collections.OrderedDict()
        for group in groups:
            results[group] = results_[group]

        logger.debug("Group Items Took %s", time.time() - t)

        return results
Ejemplo n.º 19
0
class GroupBoxWidget(QtWidgets.QFrame):

    toggled = QtCore.Signal(bool)

    def __init__(self, title, widget, *args, **kwargs):
        super(GroupBoxWidget, self).__init__(*args, **kwargs)

        self._widget = None

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

        self.setLayout(layout)

        self._titleWidget = QtWidgets.QPushButton(self)
        self._titleWidget.setCheckable(True)
        self._titleWidget.setText(title)
        self._titleWidget.setObjectName("title")
        self._titleWidget.toggled.connect(self._toggle)

        self.layout().addWidget(self._titleWidget)

        self._widgetFrame = QtWidgets.QFrame(self)
        self._widgetFrame.setObjectName("frame")

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

        self._widgetFrame.setLayout(layout)

        self.layout().addWidget(self._widgetFrame)

        if widget:
            self.setWidget(widget)

    def setText(self, text):
        """
        Set the text to be displayed for group box.

        :type text: str
        """
        self._titleWidget.setText(text)

    def text(self):
        """
        Get the title for the group box.

        :rtype: str
        """
        return self._titleWidget.text()

    def setWidget(self, widget):
        """
        Set the widget to hide when the user clicks the title.

        :type widget: QWidgets.QWidget
        """
        self._widget = widget
        self._widget.setParent(self._widgetFrame)
        self._widgetFrame.layout().addWidget(self._widget)

    def _toggle(self, visible):
        """
        Triggered when the user clicks the title.

        :type visible: bool
        """
        self.toggled.emit(visible)

    def isChecked(self):
        """
        Check the checked state for the group box.

        :rtype: bool
        """
        return self._titleWidget.isChecked()

    def setChecked(self, checked):
        """
        Overriding this method to hide the widget when the state changes.

        :type checked: bool
        """
        self._titleWidget.setChecked(checked)
        if self._widget:
            self._widget.setVisible(checked)
Ejemplo n.º 20
0
class ThumbnailCaptureMenu(QtWidgets.QMenu):

    captured = QtCore.Signal(str)

    def __init__(self, path, force=False, parent=None):
        """
        Thumbnail capture menu.

        :type path: str
        :type force: bool
        :type parent: None or QtWidgets.QWidget
        """
        QtWidgets.QMenu.__init__(self, parent)

        self._path = path
        self._force = force

        changeImageAction = QtWidgets.QAction('Capture new image', self)
        changeImageAction.triggered.connect(self.capture)
        self.addAction(changeImageAction)

        changeImageAction = QtWidgets.QAction('Show Capture window', self)
        changeImageAction.triggered.connect(self.showCaptureWindow)
        self.addAction(changeImageAction)

        loadImageAction = QtWidgets.QAction('Load image from disk', self)
        loadImageAction.triggered.connect(self.showLoadImageDialog)
        self.addAction(loadImageAction)

    def path(self):
        """
        Return the thumbnail path on disc.
        
        :rtype: str
        """
        return self._path

    def showWarningDialog(self):
        """Show a warning dialog for overriding the previous thumbnail."""
        title = "Override Thumbnail"

        text = u"This action will delete the previous thumbnail. The " \
               u"previous image cannot be backed up. Do you want to " \
               u"confirm the action to take a new image and delete " \
               u"the previous one?"

        clickedButton = studiolibrary.widgets.MessageBox.warning(
            self.parent(),
            title=title,
            text=text,
            enableDontShowCheckBox=True,
        )

        if clickedButton != QtWidgets.QDialogButtonBox.StandardButton.Yes:
            raise Exception("Dialog was canceled!")

    def showCaptureWindow(self):
        """Show the capture window for framing."""
        self.capture(show=True)

    def capture(self, show=False):
        """
        Capture an image from the Maya viewport.
        
        :type show: bool
        """
        if not self._force and os.path.exists(self.path()):
            self.showWarningDialog()

        mutils.gui.thumbnailCapture(show=show,
                                    path=self.path(),
                                    captured=self.captured.emit)

    def showLoadImageDialog(self):
        """Show a file dialog for choosing an image from disc."""
        if not self._force and os.path.exists(self.path()):
            self.showWarningDialog()

        fileDialog = QtWidgets.QFileDialog(
            self,
            caption="Open Image",
            filter="Image Files (*.png *.jpg *.bmp)")

        fileDialog.fileSelected.connect(self._fileSelected)
        fileDialog.exec_()

    def _fileSelected(self, path):
        """
        Triggered when the file dialog is accepted.
        
        :type path: str
        """
        shutil.copy(path, self.path())
        self.captured.emit(self.path())
Ejemplo n.º 21
0
class SettingsDialog(QtWidgets.QDialog):

    DEFAULT_ACCENT_COLOR = QtGui.QColor(20, 175, 250)
    DEFAULT_BACKGROUND_COLOR = QtGui.QColor(70, 70, 80)

    DEFAULT_ACCENT_COLORS = [
        QtGui.QColor(230, 75, 75),
        QtGui.QColor(235, 100, 70),
        QtGui.QColor(240, 125, 100),
        QtGui.QColor(240, 190, 40),
        QtGui.QColor(80, 200, 140),
        QtGui.QColor(20, 175, 250),
        QtGui.QColor(110, 110, 240),
    ]

    DEFAULT_BACKGROUND_COLORS = [
        QtGui.QColor(70, 70, 80),
        QtGui.QColor(65, 65, 75),
        QtGui.QColor(55, 55, 65),
        QtGui.QColor(50, 50, 57),
        QtGui.QColor(40, 40, 47),
    ]

    accentColorChanged = QtCore.Signal(object)
    backgroundColorChanged = QtCore.Signal(object)

    def __init__(self, parent=None):
        """
        :type parent: QtWidgets.QWidget
        :type library: studiolibrary.Library
        """
        QtWidgets.QDialog.__init__(self, parent)
        studioqt.loadUi(self)

        self._validator = None
        self._accentColor = self.DEFAULT_ACCENT_COLOR
        self._backgroundColor = self.DEFAULT_BACKGROUND_COLOR

        resource = studiolibrary.resource()
        self.setWindowIcon(resource.icon("icon_black"))

        windowTitle = "Studio Library - {version}"
        windowTitle = windowTitle.format(version=studiolibrary.version())
        self.setWindowTitle(windowTitle)

        self.createAccentColorBar()
        self.createBackgroundColorBar()

        self.ui.acceptButton.clicked.connect(self.accept)
        self.ui.rejectButton.clicked.connect(self.close)
        self.ui.browsePathButton.clicked.connect(self.browsePath)

        self.updateStyleSheet()
        self.center()

    def resizeEvent(self, event):
        """
        Reimplemented to support the logo scaling on DPI screens.

        :type event: QtGui.QEvent
        :rtype: None
        """
        scaleFactor = 1.4
        height = self.ui.headerFrame.height()
        self.ui.logo.setFixedWidth(height / scaleFactor)
        self.ui.logo.setFixedHeight(height / scaleFactor)

    def _accentColorChanged(self, color):
        """
        Triggered when the user clicks/changes the accent color.

        :type color: studioqt.Color
        :rtype: None
        """
        self.setAccentColor(color)

    def _backgroundColorClicked(self, color):
        """
        Triggered when the user clicks/changes the background color.

        :type color: studioqt.Color
        :rtype: None
        """
        self.setBackgroundColor(color)

    def createAccentColorBar(self):
        """
        Create and setup the accent color bar.

        :rtype: None
        """
        browserColors_ = [
            # Top row, Bottom row
            (230, 60, 60),
            (250, 80, 130),
            (255, 90, 40),
            (240, 100, 170),
            (255, 125, 100),
            (240, 200, 150),
            (250, 200, 0),
            (225, 200, 40),
            (80, 200, 140),
            (80, 225, 120),
            (50, 180, 240),
            (100, 200, 245),
            (130, 110, 240),
            (180, 160, 255),
            (180, 110, 240),
            (210, 110, 255),
        ]

        browserColors = []
        for colorR, colorG, colorB in browserColors_:
            for i in range(0, 3):

                colorR = colorR if colorR > 0 else 0
                colorG = colorG if colorG > 0 else 0
                colorB = colorB if colorB > 0 else 0

                color = QtGui.QColor(colorR, colorG, colorB).rgba()
                browserColors.append(color)

                colorR -= 20
                colorB -= 20
                colorG -= 20

        hColorBar = studioqt.HColorBar()
        hColorBar.setColors(self.DEFAULT_ACCENT_COLORS)
        hColorBar.setCurrentColor(self.DEFAULT_ACCENT_COLOR)
        hColorBar.setBrowserColors(browserColors)
        hColorBar.colorChanged.connect(self._accentColorChanged)

        self.ui.accentColorBarFrame.layout().addWidget(hColorBar)

    def createBackgroundColorBar(self):
        """
        Create and setup the background color bar.

        :rtype: None
        """
        browserColors_ = [
            (0, 0, 0),
            (20, 20, 30),
            (0, 30, 60),
            (0, 60, 60),
            (0, 60, 30),
            (60, 0, 10),
            (60, 0, 40),
            (40, 15, 5),
        ]

        browserColors = []
        for colorR, colorG, colorB in browserColors_:
            for i in range(0, 6):

                color = QtGui.QColor(colorR, colorG, colorB).rgba()
                browserColors.append(color)

                colorR += 20
                colorB += 20
                colorG += 20

        hColorBar = studioqt.HColorBar()
        hColorBar.setColors(self.DEFAULT_BACKGROUND_COLORS)
        hColorBar.setCurrentColor(self.DEFAULT_BACKGROUND_COLOR)
        hColorBar.setBrowserColors(browserColors)
        hColorBar.colorChanged.connect(self._backgroundColorClicked)

        self.ui.backgroundColorBarFrame.layout().addWidget(hColorBar)

    def accept(self):
        """
        Hides the modal dialog and sets the result code to Accepted.

        :rtype: None
        """
        self.validate()
        QtWidgets.QDialog.accept(self)

    def setValidator(self, validator):
        """
        Set the validator for the dialog.

        :type validator: func
        :rtype: None
        """
        self._validator = validator

    def validator(self):
        """
        Return the validator for the dialog.

        :rtype: func
        """
        return self._validator

    def validate(self):
        """
        Run the validate.

        :rtype: None
        """
        try:
            validator = self.validator()
            if validator:
                validator()
        except Exception, e:
            QtWidgets.QMessageBox.critical(self, "Validate Error", str(e))
            raise
Ejemplo n.º 22
0
class PoseItemSignals(QtCore.QObject):
    """"""
    mirrorChanged = QtCore.Signal(bool)
Ejemplo n.º 23
0
class GlobalSignals(QtCore.QObject):
    """"""
    blendChanged = QtCore.Signal(float)
Ejemplo n.º 24
0
 def run(self):
     if studiolibrary.package().isUpdateAvailable():
         self.emit(QtCore.SIGNAL('updateAvailable()'))
Ejemplo n.º 25
0
class CaptureDialog(QtWidgets.QDialog):
    DEFAULT_WIDTH = 250
    DEFAULT_HEIGHT = 250
    captured = QtCore.Signal(str)
    capturing = QtCore.Signal(str)

    def __init__(self,
                 path='',
                 parent=None,
                 startFrame=None,
                 endFrame=None,
                 step=1):
        """
        :type path: str
        :type parent: QtWidgets.QWidget
        :type startFrame: int
        :type endFrame:  int
        :type step: int
        """
        parent = parent or mutils.gui.mayaWindow()
        QtWidgets.QDialog.__init__(self, parent)
        self._path = path
        self._step = step
        self._endFrame = None
        self._startFrame = None
        self._capturedPath = None
        if endFrame is None:
            endFrame = int(maya.cmds.currentTime(query=True))
        if startFrame is None:
            startFrame = int(maya.cmds.currentTime(query=True))
        self.setEndFrame(endFrame)
        self.setStartFrame(startFrame)
        self.setObjectName('CaptureWindow')
        self.setWindowTitle('Capture Window')
        self._captureButton = QtWidgets.QPushButton('Capture')
        self._captureButton.clicked.connect(self.capture)
        self._modelPanelWidget = mutils.gui.modelpanelwidget.ModelPanelWidget(
            self)
        vbox = QtWidgets.QVBoxLayout(self)
        vbox.setObjectName(self.objectName() + 'Layout')
        vbox.addWidget(self._modelPanelWidget)
        vbox.addWidget(self._captureButton)
        self.setLayout(vbox)
        width = self.DEFAULT_WIDTH * 1.5
        height = self.DEFAULT_HEIGHT * 1.5 + 50
        self.setWidthHeight(width, height)
        self.centerWindow()

    def path(self):
        """
        Return the destination path.
        
        :rtype: str
        """
        return self._path

    def setPath(self, path):
        """
        Set the destination path.
        
        :type path: str
        """
        self._path = path

    def endFrame(self):
        """
        Return the end frame of the playblast.
        
        :rtype: int
        """
        return self._endFrame

    def setEndFrame(self, endFrame):
        """
        Specify the end frame of the playblast.
        
        :type endFrame: int
        """
        self._endFrame = int(endFrame)

    def startFrame(self):
        """
        Return the start frame of the playblast.
        
        :rtype: int
        """
        return self._startFrame

    def setStartFrame(self, startFrame):
        """
        Specify the start frame of the playblast.
        
        :type startFrame: int
        """
        self._startFrame = int(startFrame)

    def step(self):
        """
        Return the step amount of the playblast.
        
        Example:
            if step is set to 2 it will playblast every second frame.
        
        :rtype: int
        """
        return self._step

    def setStep(self, step):
        """
        Set the step amount of the playblast.
        
        :type step: int
        """
        self._step = step

    def setWidthHeight(self, width, height):
        """
        Set the width and height of the window.
        
        :type width: int
        :type height: int
        :rtype: None
        """
        x = self.geometry().x()
        y = self.geometry().y()
        self.setGeometry(x, y, width, height)

    def centerWindow(self):
        """
        Center the widget to the center of the desktop.
        
        :rtype: None
        """
        geometry = self.frameGeometry()
        pos = QtWidgets.QApplication.desktop().cursor().pos()
        screen = QtWidgets.QApplication.desktop().screenNumber(pos)
        centerPoint = QtWidgets.QApplication.desktop().screenGeometry(
            screen).center()
        geometry.moveCenter(centerPoint)
        self.move(geometry.topLeft())

    def capturedPath(self):
        """
        Return the location of the captured playblast.
        
        :rtype:
        """
        return self._capturedPath

    def capture(self):
        """
        Capture a playblast and save it to the given path.
        
        :rtype: None
        """
        path = self.path()
        self.capturing.emit(path)
        modelPanel = self._modelPanelWidget.name()
        startFrame = self.startFrame()
        endFrame = self.endFrame()
        step = self.step()
        width = self.DEFAULT_WIDTH
        height = self.DEFAULT_HEIGHT
        self._capturedPath = mutils.playblast(path,
                                              modelPanel,
                                              startFrame,
                                              endFrame,
                                              width,
                                              height,
                                              step=step)
        self.accept()
        self.captured.emit(self._capturedPath)
        return self._capturedPath
Ejemplo n.º 26
0
class FormDialog(QtWidgets.QFrame):

    accepted = QtCore.Signal(object)
    rejected = QtCore.Signal(object)

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

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

        self.setLayout(layout)

        self._widgets = []
        self._validator = None

        self._title = QtWidgets.QLabel(self)
        self._title.setObjectName('title')
        self._title.setText('FORM')
        self.layout().addWidget(self._title)

        self._description = QtWidgets.QLabel(self)
        self._description.setObjectName('description')
        self.layout().addWidget(self._description)

        self._formWidget = FormWidget(self)
        self._formWidget.setObjectName("formWidget")
        self.layout().addWidget(self._formWidget)

        self.layout().addStretch(1)

        buttonLayout = QtWidgets.QHBoxLayout(self)
        buttonLayout.setContentsMargins(0, 0, 0, 0)
        buttonLayout.setSpacing(0)

        self.layout().addLayout(buttonLayout)

        buttonLayout.addStretch(1)

        self._acceptButton = QtWidgets.QPushButton(self)
        self._acceptButton.setObjectName('acceptButton')
        self._acceptButton.setText('Submit')
        self._acceptButton.clicked.connect(self.accept)

        self._rejectButton = QtWidgets.QPushButton(self)
        self._rejectButton.setObjectName('rejectButton')
        self._rejectButton.setText('Cancel')
        self._rejectButton.clicked.connect(self.reject)

        buttonLayout.addWidget(self._acceptButton)
        buttonLayout.addWidget(self._rejectButton)

        if form:
            self.setSettings(form)
        # buttonLayout.addStretch(1)

    def acceptButton(self):
        """
        Return the accept button.

        :rtype: QWidgets.QPushButton
        """
        return self._acceptButton

    def rejectButton(self):
        """
        Return the reject button.

        :rtype: QWidgets.QPushButton
        """
        return self._rejectButton

    def validateAccepted(self, **kwargs):
        """
        Triggered when the accept button has been clicked.

        :type kwargs: The values of the fields
        """
        self._formWidget.validator()(**kwargs)

    def validateRejected(self, **kwargs):
        """
        Triggered when the reject button has been clicked.

        :type kwargs: The default values of the fields
        """
        self._formWidget.validator()(**kwargs)

    def setSettings(self, settings):

        self._settings = settings

        title = settings.get("title")
        if title is not None:
            self._title.setText(title)

        callback = settings.get("accepted")
        if not callback:
            self._settings["accepted"] = self.validateAccepted

        callback = settings.get("rejected")
        if not callback:
            self._settings["rejected"] = self.validateRejected

        description = settings.get("description")
        if description is not None:
            self._description.setText(description)

        validator = settings.get("validator")
        if validator is not None:
            self._formWidget.setValidator(validator)

        layout = settings.get("layout")

        schema = settings.get("schema")
        if schema is not None:
            self._formWidget.setSchema(schema, layout=layout)

    def accept(self):
        """Call this method to accept the dialog."""
        callback = self._settings.get("accepted")
        if callback:
            callback(**self._formWidget.values())
        self.close()

    def reject(self):
        """Call this method to rejected the dialog."""
        callback = self._settings.get("rejected")
        if callback:
            callback(**self._formWidget.defaultValues())
        self.close()
Ejemplo n.º 27
0
 def url(self):
     """Used by the mime data when dragging/dropping the item."""
     return QtCore.QUrl("file:///" + self.path())
Ejemplo n.º 28
0
class FormWidget(QtWidgets.QFrame):

    accepted = QtCore.Signal(object)
    stateChanged = QtCore.Signal()

    def __init__(self, *args, **kwargs):
        super(FormWidget, self).__init__(*args, **kwargs)

        self._schema = []
        self._widgets = []
        self._validator = None

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

        self.setLayout(layout)

        self._optionsFrame = QtWidgets.QFrame(self)
        self._optionsFrame.setObjectName("optionsFrame")

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

        self._optionsFrame.setLayout(layout)

        self._titleWidget = QtWidgets.QPushButton(self)
        self._titleWidget.setCheckable(True)
        self._titleWidget.setObjectName("titleWidget")
        self._titleWidget.toggled.connect(self._titleClicked)
        self._titleWidget.hide()

        self.layout().addWidget(self._titleWidget)
        self.layout().addWidget(self._optionsFrame)

    def _titleClicked(self, toggle):
        """Triggered when the user clicks the title widget."""
        self.setExpanded(not toggle)
        self.stateChanged.emit()

    def titleWidget(self):
        """
        Get the title widget.
        
        :rtype: QWidget
        """
        return self._titleWidget

    def setTitle(self, title):
        """
        Set the text for the title widget.
        
        :type title: str
        """
        self.titleWidget().setText(title)

    def setExpanded(self, expand):
        """
        Expands the options if expand is true, otherwise collapses the options.
        
        :type expand: bool
        """
        self._titleWidget.blockSignals(True)

        try:
            self._titleWidget.setChecked(not expand)
            self._optionsFrame.setVisible(expand)
        finally:
            self._titleWidget.blockSignals(False)

    def isExpanded(self):
        """
        Returns true if the item is expanded, otherwise returns false.
        
        :rtype: bool
        """
        return self._optionsFrame.isVisible()

    def setTitleVisible(self, visible):
        """
        A convenience method for setting the title visible.
        
        :type visible: bool
        """
        self.titleWidget().setVisible(visible)

    def reset(self):
        """Reset all option widgets back to their default value."""
        for widget in self._widgets:
            widget.reset()
        self.validate()

    def setSchema(self, schema, layout=None):
        """
        Set the schema for the widget.
        
        :type schema: list[dict]
        """
        self._schema = schema

        for data in schema:

            cls = FIELD_WIDGET_REGISTRY.get(data.get("type", "label"))

            if not cls:
                logger.warning("Cannot find widget for %s", data)
                continue

            if layout and not data.get("layout"):
                data["layout"] = layout

            widget = cls(data=data)
            widget.setData(data)

            value = data.get("value")
            default = data.get("default")
            if value is None and default is not None:
                widget.setValue(default)

            self._widgets.append(widget)

            callback = functools.partial(self._optionChanged, widget)
            widget.valueChanged.connect(callback)

            self._optionsFrame.layout().addWidget(widget)

    def _optionChanged(self, widget):
        """
        Triggered when the given option widget changes value.
        
        :type widget: FieldWidget 
        """
        self.stateChanged.emit()
        self.validate()

    def accept(self):
        """Accept the current options"""
        self.emitAcceptedCallback()

    def closeEvent(self, event):
        super(FormWidget, self).closeEvent(event)

    def setValidator(self, validator):
        """
        Set the validator for the options.
        
        :type validator: func
        """
        self._validator = validator

    def validator(self):
        """
        Return the validator for the form.

        :rtype: func
        """
        return self._validator

    def validate(self):
        """Validate the current options using the validator."""
        if self._validator:
            state = self._validator(**self.values())

            if state is not None:
                self._setState(state)
        else:
            logger.debug("No validator set.")

    def setValue(self, name, value):
        """
        Set the value for the given field name and value

        :type name: str
        :type value: object
        """
        widget = self.widget(name)
        widget.setValue(value)

    def value(self, name):
        """
        Get the value for the given widget name.
        
        :type name: str 
        :rtype: object 
        """
        widget = self.widget(name)
        return widget.value()

    def widget(self, name):
        """
        Get the widget for the given widget name.
        
        :type name: str 
        :rtype: FieldWidget 
        """
        for widget in self._widgets:
            if widget.data().get("name") == name:
                return widget

    def options(self):
        options = []
        for widget in self._widgets:
            options.append(widget.data())
        return options

    def optionsToDict(self):
        """
        This method is deprecated.

        :rtype: dict
        """
        return self.values()

    def values(self):
        """
        Get the all the field values indexed by the field name.

        :rtype: dict
        """
        values = {}
        for widget in self._widgets:
            values[widget.data().get("name")] = widget.value()
        return values

    def defaultValues(self):
        """
        Get the all the default field values indexed by the field name.

        :rtype: dict
        """
        values = {}
        for widget in self._widgets:
            values[widget.data().get("name")] = widget.default()
        return values

    def state(self):
        """
        Get the current state.
        
        :rtype: dict 
        """
        options = []

        for widget in self._widgets:
            options.append(widget.state())

        state = {"options": options, "expanded": self.isExpanded()}

        return state

    def setState(self, state):
        """
        Set the current state.
        
        :type state: dict 
        """
        expanded = state.get("expanded")
        if expanded is not None:
            self.setExpanded(expanded)

        options = state.get("options")
        if options is not None:
            self._setState(options)

        self.validate()

    def optionsState(self):
        state = {}
        values = self.values()
        options = self.options()

        for option in options:
            name = option.get("name")
            persistent = option.get("persistent")
            if name and persistent:
                state[name] = values[name]

        return state

    def setStateFromOptions(self, options):
        state = []
        for option in options:
            state.append({"name": option, "value": options[option]})

        self._setState(state)

    def _setState(self, state):
        """
        Set the state.
        
        :type state: list[dict]
        """
        for widget in self._widgets:
            widget.blockSignals(True)

        for widget in self._widgets:
            widget.setError("")
            for data in state:
                if data.get("name") == widget.data().get("name"):
                    widget.setData(data)

        for widget in self._widgets:
            widget.blockSignals(False)
Ejemplo n.º 29
0
class SidebarWidget(QtWidgets.QTreeWidget):

    itemDropped = QtCore.Signal(object)
    itemRenamed = QtCore.Signal(str, str)
    itemSelectionChanged = QtCore.Signal()

    def __init__(self, *args):
        super(SidebarWidget, self).__init__(*args)

        self._dpi = 1
        self._items = []
        self._locked = False
        self._dataset = None
        self._recursive = True

        self.itemExpanded.connect(self.update)
        self.itemCollapsed.connect(self.update)

        self.setDpi(1)

        self.setAcceptDrops(True)
        self.setHeaderHidden(True)
        self.setFrameShape(QtWidgets.QFrame.NoFrame)
        self.setSelectionMode(QtWidgets.QTreeWidget.ExtendedSelection)
        self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)

    def selectionChanged(self, *args):
        """Triggered the current selection has changed."""
        self.search()

    def setRecursive(self, enable):
        """
        Set the search query on the dataset to be recursive.
        
        :type enable: bool
        """
        self._recursive = enable
        self.search()

    def isRecursive(self):
        """
        Get the recursive query enable state.
        
        :rtype: bool 
        """
        return self._recursive

    def _dataChanged(self):
        """Triggered when the data set has changed."""
        pass

    #     paths = collections.OrderedDict()
    #
    #     for value in self.dataset().values(self.field(), self.sortBy()):
    #         paths[value] = collections.OrderedDict()
    #
    #     if paths:
    #         root = findRoot(paths.keys(), separator=self.separator())
    #         self.setPaths(paths, root=root)

    def setDataset(self, dataset):
        """
        Set the dataset for the search widget:
        
        :type dataset: studioqt.Dataset
        """
        self._dataset = dataset
        self._dataset.dataChanged.connect(self._dataChanged)
        self._dataChanged()

    def dataset(self):
        """
        Get the dataset for the search widget.
        
        :rtype: studioqt.Dataset 
        """
        return self._dataset

    def search(self):
        """Run the dataset search."""
        if self.dataset():
            self.dataset().addQuery(self.query())
            self.dataset().search()
        else:
            logger.info('No dataset found for the sidebar widget.')

    def query(self):
        """
        Get the query for the sidebar widget.
        
        :rtype: dict
        """
        filters = []

        if self.isRecursive():
            condition = 'startswith'
        else:
            condition = 'is'

        for path in self.selectedPaths():
            filter_ = ('folder', condition, path)
            filters.append(filter_)

        uniqueName = 'sidebarwidget_' + str(id(self))
        return {'name': uniqueName, 'operator': 'or', 'filters': filters}

    def setLocked(self, locked):
        """
        Set the widget items to read only mode.
        
        :type locked: bool
        :rtype: None 
        """
        self._locked = locked

    def isLocked(self):
        """
        Return True if the items are in read only mode
        
        :rtype: bool 
        """
        return self._locked

    def itemAt(self, pos):
        """
        :type pos: QtGui.QPoint
        :rtype: None or Folder
        """
        index = self.indexAt(pos)
        if not index.isValid():
            return

        item = self.itemFromIndex(index)
        return item

    def dropEvent(self, event):
        """
        :type event: QtCore.QEvent
        :rtype: None
        """
        if self.isLocked():
            logger.debug("Folder is locked! Cannot accept drop!")
            return

        self.itemDropped.emit(event)

    def dragMoveEvent(self, event):
        """
        :type event: QtCore.QEvent
        :rtype: None
        """
        mimeData = event.mimeData()

        if mimeData.hasUrls():
            event.accept()
        else:
            event.ignore()

        item = self.itemAt(event.pos())
        if item:
            self.selectPaths([item.path()])

    def dragEnterEvent(self, event):
        """
        :type event: QtCore.QEvent
        :rtype: None
        """
        event.accept()

    def selectItem(self, item):
        """
        :type item: NavigationWidgetItem
        :rtype: None
        """
        self.selectPaths([item.path()])

    def dpi(self):
        """
        Return the dots per inch multiplier.

        :rtype: float
        """
        return self._dpi

    def setDpi(self, dpi):
        """
        Set the dots per inch multiplier.

        :type dpi: float
        :rtype: None
        """
        size = 24 * dpi
        self.setIndentation(15 * dpi)
        self.setMinimumWidth(35 * dpi)
        self.setIconSize(QtCore.QSize(size, size))
        self.setStyleSheet("height: {size}".format(size=size))

    def update(self, *args):
        """
        :rtype: None
        """
        for item in self.items():
            item.update()

    def items(self):
        """
        Return a list of all the items in the tree widget.

        :rtype: list[NavigationWidgetItem]
        """
        items = self.findItems(
            "*", QtCore.Qt.MatchWildcard | QtCore.Qt.MatchRecursive)

        return items

    def itemFromUrl(self, url):
        """
        Return the item for the given url.

        :type url: QtCore.QUrl
        :rtype: NavigationWidgetItem
        """
        for item in self.items():
            if url == item.url():
                return item

    def itemFromPath(self, path):
        """
        Return the item for the given path.

        :type path: str
        :rtype: NavigationWidgetItem
        """
        for item in self.items():
            if path == item.path():
                return item

    def settings(self):
        """
        Return a dictionary of the settings for this widget.

        :rtype: dict
        """
        settings = {}

        scrollBar = self.verticalScrollBar()
        settings["verticalScrollBar"] = {"value": scrollBar.value()}

        scrollBar = self.horizontalScrollBar()
        settings["horizontalScrollBar"] = {"value": scrollBar.value()}

        for item in self.items():
            itemSettings = item.settings()
            if itemSettings:
                settings[item.path()] = item.settings()

        return settings

    def setSettings(self, settings):
        """
        Set the settings for this widget

        :type settings: dict
        """
        for path in sorted(settings.keys()):
            s = settings.get(path, None)
            self.setPathSettings(path, s)

        scrollBarSettings = settings.get("verticalScrollBar", {})
        value = scrollBarSettings.get("value", None)
        if value:
            self.verticalScrollBar().setValue(value)

        scrollBarSettings = settings.get("horizontalScrollBar", {})
        value = scrollBarSettings.get("value", None)
        if value:
            self.horizontalScrollBar().setValue(value)

        self.setIndentation(18 * self.dpi())

    def setPathSettings(self, path, settings):
        """
        Show the context menu at the given position.

        :type path: str
        :type settings: dict
        :rtype: None
        """
        item = self.itemFromPath(path)
        if item and settings:
            item.setSettings(settings)

    def showContextMenu(self, position):
        """
        Show the context menu at the given position.
        
        :type position: QtCore.QPoint
        :rtype: None
        """
        menu = self.createContextMenu()
        menu.exec_(self.viewport().mapToGlobal(position))

    def expandedItems(self):
        """
        Return all the expanded items.

        :rtype:  list[NavigationWidgetItem]
        """
        for item in self.items():
            if self.isItemExpanded(item):
                yield item

    def expandedPaths(self):
        """
        Return all the expanded paths.

        :rtype:  list[NavigationWidgetItem]
        """
        for item in self.expandedItems():
            yield item.url()

    def setExpandedPaths(self, paths):
        """
        Set the given paths to expanded.

        :type paths: list[str]
        """
        for item in self.items():
            if item.url() in paths:
                item.setExpanded(True)

    def selectedItem(self):
        """
        Return the last selected item
        
        :rtype: SidebarWidgetItem 
        """
        path = self.selectedPath()
        return self.itemFromPath(path)

    def selectedPath(self):
        """
        Return the last selected path

        :rtype: str or None
        """
        paths = self.selectedPaths()
        if paths:
            return paths[-1]

    def selectedPaths(self):
        """
        Return the paths that are selected.

        :rtype: list[str]
        """
        paths = []
        items = self.selectedItems()
        for item in items:
            path = item.path()
            paths.append(path)
        return paths

    def selectPath(self, path):
        """
        Select the given path

        :type: str 
        :rtype: None
        """
        self.selectPaths([path])

    def selectPaths(self, paths):
        """
        Select the items with the given paths.

        :type paths: list[str]
        :rtype: None
        """
        paths = self.normPaths(paths)
        items = self.items()
        for item in items:
            if item.path() in paths:
                item.setSelected(True)
            else:
                item.setSelected(False)

    def selectUrl(self, url):
        """
        Select the item with the given url.

        :type url: str
        :rtype: None
        """
        items = self.items()

        for item in items:
            if item.url() == url:
                item.setSelected(True)
            else:
                item.setSelected(False)

    def selectedUrls(self):
        """
        Return the urls for the selected items.

        :rtype: list[str]
        """
        urls = []
        items = self.selectedItems()
        for item in items:
            urls.append(item.url())
        return urls

    def normPaths(self, paths):
        return [path.replace("\\", "/") for path in paths]

    def setPaths(self, paths, root="", split=None):
        """
        Set the items to the given items.

        :type paths: list[str]
        :type root: str
        :rtype: None
        """
        settings = self.settings()

        self.blockSignals(True)

        self.clear()
        self.addPaths(paths, root=root, split=split)

        self.setSettings(settings)

        self.blockSignals(False)

    def addPaths(self, paths, root="", split=None):
        """
        Set the given items as a flat list.

        :type paths: list[str]
        :type root: str or None
        :type split: str or None

        :rtype: None
        """
        data = pathsToDict(paths, root=root, split=split)
        self.createItems(data, split=split)

        if isinstance(paths, dict):
            self.setSettings(paths)

    def createItems(self, data, split=None):
        """ 
        Create the items from the given data dict

        :type data: dict
        :type split: str or None

        :rtype: None
        """
        split = split or SPLIT_TOKEN

        for key in data:

            path = split.join([key])

            item = SidebarWidgetItem(self)
            item.setText(0, unicode(key))
            item.setPath(path)

            def _recursive(parent, children, split=None):
                for text, val in sorted(children.iteritems()):

                    path = parent.path()
                    path = split.join([path, text])

                    child = SidebarWidgetItem()
                    child.setText(0, unicode(text))
                    child.setPath(path)

                    parent.addChild(child)

                    _recursive(child, val, split=split)

            _recursive(item, data[key], split=split)

        self.update()
Ejemplo n.º 30
0
class OptionWidget(QtWidgets.QFrame):
    """The base widget for all option widgets.
    
    Examples:
        
        option = {
            'name': 'startFrame',
            'type': 'int'
            'value': 1,
        }
        
        optionWidget = OptionWidget(option)
        
    """
    valueChanged = QtCore.Signal()

    def __init__(self, *args, **kwargs):
        super(OptionWidget, self).__init__(*args, **kwargs)

        self._option = {}
        self._widget = None
        self._default = None
        self._required = None

        layout = QtWidgets.QHBoxLayout(self)
        layout.setContentsMargins(0, 0, 0, 0)

        self.setLayout(layout)
        self.setContentsMargins(0, 0, 0, 0)

        self._label = QtWidgets.QLabel(self)
        self._label.setObjectName('label')
        self._label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)

        layout.addWidget(self._label)

    def label(self):
        """
        Get the label widget.
        
        :rtype: QtWidgets.QLabel 
        """
        return self._label

    def option(self):
        """
        Get the option data for the widget.
        
        :rtype: dict 
        """
        return self._option

    def setOption(self, state):
        """
        Set the current state of the option widget using a dictionary.
        
        :type state: dict
        """
        self._option.update(state)
        state = self._option

        self.blockSignals(True)

        items = state.get('items')
        if items is not None:
            self.setItems(items)

        value = state.get('value')
        default = state.get('default') or value

        # Must set the default before value
        if default is not None:
            self.setDefault(default)

        if value is not None:
            self.setValue(value)

        enabled = state.get('enabled')
        if enabled is not None:
            self.setEnabled(enabled)

        hidden = state.get('hidden')
        if hidden is not None:
            self.setHidden(hidden)

        required = state.get('required')
        if required is not None:
            self.setRequired(required)

        message = state.get('error', '')
        self.setError(message)

        annotation = state.get('annotation', '')
        self.setToolTip(annotation)

        label = state.get('label')
        if label is None:
            label = state.get('name')

        if label is not None:
            self.setText(label)

        self.refresh()

        self.blockSignals(False)

    def setError(self, message):
        """
        Set the error message to be displayed for the options widget.
        
        :type message: str
        """
        error = True if message else False

        if error:
            self.setToolTip(message)
        else:
            self.setToolTip(self.option().get('annotation'))

        self.setProperty('error', error)
        self.setStyleSheet(self.styleSheet())

    def setText(self, text):
        """
        Set the label text for the option.
        
        :type text: str 
        """
        if text:
            text = toTitle(text)

            if self.isRequired():
                text += '*'

            self._label.setText(text)

    def setValue(self, value):
        """
        Set the value of the option widget.
        
        Will emit valueChanged() if the new value is different from the old one.
        
        :type value: object
        """
        self.emitValueChanged()

    def value(self):
        """
        Get the value of the option widget.
        
        :rtype: object
        """
        raise NotImplementedError('The method "value" needs to be implemented')

    def setItems(self, items):
        """
        Set the items for the options widget.
        
        :type items: list[str]
        """
        raise NotImplementedError(
            'The method "setItems" needs to be implemented')

    def reset(self):
        """Reset the option widget back to the defaults."""
        self.setState(self._option)

    def setRequired(self, required):
        """
        Set True if a value is required for this option.
        
        :type required: bool
        """
        self._required = required
        self.setProperty('required', required)
        self.setStyleSheet(self.styleSheet())

    def isRequired(self):
        """
        Check if a value is required for the option widget.
        
        :rtype: bool
        """
        return bool(self._required)

    def setDefault(self, default):
        """
        Set the default value for the option widget.
        
        :type default: object
        """
        self._default = default

    def default(self):
        """
        Get the default value for the option widget.
        
        :rtype: object
        """
        return self._default

    def isDefault(self):
        """
        Check if the current value is the same as the default value.
        
        :rtype: bool
        """
        return self.value() == self.default()

    def emitValueChanged(self, *args):
        """
        Emit the value changed signal.
        
        :type args: list
        """
        self.valueChanged.emit()
        self.refresh()

    def setWidget(self, widget):
        """
        Set the widget used to set and get the option value.
        
        :type widget: QtWidgets.QWidget
        """
        self._widget = widget
        self._widget.setObjectName('widget')

        self.layout().addWidget(self._widget)

    def widget(self, ):
        """
        Get the widget used to set and get the option value.
        
        :rtype: QtWidgets.QWidget
        """
        return self._widget

    def refresh(self):
        """Refresh the style properties."""
        self.setProperty('default', self.isDefault())
        self.setStyleSheet(self.styleSheet())