Exemplo n.º 1
0
    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 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())
    def createPixmap(self, path, color):
        """
        Create a new Pixmap from the given path.

        :type path: str
        :type color: str or QtCore.QColor
        :rtype: QtCore.QPixmap
        """
        dpi = self.treeWidget().dpi()
        key = path + color + "DPI-" + str(dpi)
        pixmap = self.PIXMAP_CACHE.get(key)

        if not pixmap:

            width = 20 * dpi
            height = 18 * dpi

            if "/" not in path and "\\" not in path:
                path = studiolibrary.resource.get("icons", path)

            if not os.path.exists(path):
                path = self.defaultIconPath()

            pixmap2 = studioqt.Pixmap(path)
            pixmap2.setColor(color)
            pixmap2 = pixmap2.scaled(16 * dpi, 16 * dpi,
                                     QtCore.Qt.KeepAspectRatio,
                                     QtCore.Qt.SmoothTransformation)

            x = (width - pixmap2.width()) / 2
            y = (height - pixmap2.height()) / 2

            pixmap = QtGui.QPixmap(QtCore.QSize(width, height))
            pixmap.fill(QtCore.Qt.transparent)

            painter = QtGui.QPainter(pixmap)
            painter.drawPixmap(x, y, pixmap2)
            painter.end()

            self.PIXMAP_CACHE[key] = pixmap

        return pixmap
Exemplo n.º 4
0
    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
        """
        self.setRect(QtCore.QRect(option.rect))

        painter.save()

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

            if self.isTextVisible():
                self._paintText(painter, option, 1)

        finally:
            painter.restore()
Exemplo n.º 5
0
    def createColorDialog(
        self,
        parent,
        standardColors=None,
        currentColor=None,
    ):
        """
        Create a new instance of the color dialog.

        :type parent: QtWidgets.QWidget
        :type standardColors: list[int]
        :rtype: QtWidgets.QColorDialog
        """
        dialog = QtWidgets.QColorDialog(parent)

        if standardColors:
            index = -1
            for colorR, colorG, colorB in standardColors:
                index += 1

                color = QtGui.QColor(colorR, colorG, colorB).rgba()

                try:
                    # Support for new qt5 signature
                    color = QtGui.QColor(color)
                    dialog.setStandardColor(index, color)
                except:
                    # Support for new qt4 signature
                    color = QtGui.QColor(color).rgba()
                    dialog.setStandardColor(index, color)

        # PySide2 doesn't support d.open(), so we need to pass a blank slot.
        dialog.open(self, QtCore.SLOT("blankSlot()"))

        if currentColor:
            dialog.setCurrentColor(currentColor)

        return dialog
Exemplo n.º 6
0
def fadeOut(widget, duration=200, onFinished=None):
    """
    Fade out the given widget using the opacity effect.
    
    :type widget: QtWidget.QWidgets
    :type duration: int
    :type onFinished: func
    :rtype: QtCore.QPropertyAnimation 
    """
    widget._fadeOutEffect_ = QtWidgets.QGraphicsOpacityEffect()
    widget.setGraphicsEffect(widget._fadeOutEffect_)
    animation = QtCore.QPropertyAnimation(widget._fadeOutEffect_, b"opacity")
    animation.setDuration(duration)
    animation.setStartValue(1.0)
    animation.setEndValue(0.0)
    animation.setEasingCurve(QtCore.QEasingCurve.InOutCubic)
    animation.start()

    if onFinished:
        animation.finished.connect(onFinished)

    widget._fadeOut_ = animation

    return animation
Exemplo n.º 7
0
    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)
Exemplo n.º 8
0
    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
Exemplo n.º 9
0
class ThumbnailCaptureDialog(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.playblast(
            path,
            modelPanel,
            startFrame,
            endFrame,
            width,
            height,
            step=step,
        )

        self.accept()

        self.captured.emit(self._capturedPath)
        return self._capturedPath
Exemplo n.º 10
0
class IconPickerWidget(QtWidgets.QFrame):

    BUTTON_CLASS = IconButton

    iconChanged = QtCore.Signal(object)

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

        self._buttons = []
        self._currentIcon = None
        self._menuButton = None

        layout = QtWidgets.QGridLayout()
        layout.setSpacing(0)
        layout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(layout)

    def enterEvent(self, event):
        """
        Overriding this method to fix a bug with custom actions.

        :type event: QtCore.QEvent
        """
        if self.parent():
            menu = self.parent().parent()
            if isinstance(menu, QtWidgets.QMenu):
                menu.setActiveAction(None)

    def _iconChanged(self, iconPath):
        """
        Triggered when the user clicks or browses for a color.

        :type iconPath: str
        :rtype: None
        """
        self.setCurrentIcon(iconPath)
        self.iconChanged.emit(iconPath)

    def menuButton(self):
        """
        Get the menu button used for browsing for custom colors.

        :rtype: QtGui.QWidget
        """
        return self._menuButton

    def deleteButtons(self):
        """
        Delete all the color buttons.

        :rtype: None
        """
        layout = self.layout()
        while layout.count():
            item = layout.takeAt(0)
            item.widget().deleteLater()

    def currentIcon(self):
        """
        Return the current color.

        :rtype: studioqt.Color
        """
        return self._currentIcon

    def setCurrentIcon(self, color):
        """
        Set the current color.

        :type color: studioqt.Color
        """
        self._currentIcon = color
        self.refresh()

    def refresh(self):
        """Update the current state of the selected color."""
        for button in self._buttons:
            button.setChecked(button.iconPath() == self.currentIcon())

    def setIcons(self, icons):
        """
        Set the colors for the color bar.

        :type icons: list[str] or list[studioqt.Icon]
        """
        self.deleteButtons()

        i = 0
        first = True
        last = False

        positions = [(i, j) for i in range(5) for j in range(5)]
        for position, iconPath in zip(positions, icons):
            i += 1

            if i == len(icons) - 1:
                last = True

            callback = partial(self._iconChanged, iconPath)

            button = self.BUTTON_CLASS(self)
            button.setIconPath(iconPath)
            button.setIconSize(QtCore.QSize(16, 16))

            button.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
                                 QtWidgets.QSizePolicy.Preferred)

            button.setProperty("first", first)
            button.setProperty("last", last)

            button.clicked.connect(callback)

            self.layout().addWidget(button, *position)

            self._buttons.append(button)

            first = False

        self._menuButton = QtWidgets.QPushButton("...", self)
        self._menuButton.setObjectName('menuButton')
        self._menuButton.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
                                       QtWidgets.QSizePolicy.Preferred)

        self._menuButton.clicked.connect(self.browseColor)
        self.layout().addWidget(self._menuButton)
        self.refresh()

    @QtCore.Slot()
    def blankSlot(self):
        """Blank slot to fix an issue with PySide2.QColorDialog.open()"""
        pass

    def browseColor(self):
        """
        Show the color dialog.

        :rtype: None
        """
        pass
Exemplo n.º 11
0
class GroupBoxWidget(QtWidgets.QFrame):

    toggled = QtCore.Signal(bool)

    def __init__(self, title, widget, persistent=False, *args, **kwargs):
        super(GroupBoxWidget, self).__init__(*args, **kwargs)

        self._widget = None
        self._persistent = 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._toggled)

        on_path = studiolibrary.resource.get("icons", "caret-down.svg")
        off_path = studiolibrary.resource.get("icons", "caret-right.svg")
        icon = studioqt.Icon.fa(on_path, color="rgb(255,255,255,200)", off=off_path)
        self._titleWidget.setIcon(icon)

        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)

        self.setPersistent(persistent)

    def setPersistent(self, persistent):
        """
        Save and load the state of the widget to disk.

        :type persistent: bool
        """
        self._persistent = persistent
        self.loadSettings()

    def title(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 _toggled(self, visible):
        """
        Triggered when the user clicks the title.

        :type visible: bool
        """
        self.saveSettings()
        self.setChecked(visible)
        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)

    def saveSettings(self):
        """Save the state to disc."""
        if self._persistent:

            if not self.objectName():
                raise NameError("No object name set for persistent widget.")

            data = {self.objectName(): {"checked": self.isChecked()}}
            settings.save(data)

    def loadSettings(self):
        """Load the state to disc."""
        if self._persistent:

            if not self.objectName():
                raise NameError("No object name set for persistent widget.")

            data = settings.read()
            data = data.get(self.objectName(), {})

            if isinstance(data, dict):
                checked = data.get("checked", True)
                self.setChecked(checked)
Exemplo n.º 12
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())
Exemplo 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._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())
Exemplo n.º 14
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
Exemplo n.º 15
0
class Theme(QtCore.QObject):

    updated = QtCore.Signal()

    DEFAULT_DARK_COLOR = QtGui.QColor(60, 60, 60)
    DEFAULT_LIGHT_COLOR = QtGui.QColor(220, 220, 220)

    DEFAULT_ACCENT_COLOR = QtGui.QColor(0, 175, 255)
    DEFAULT_BACKGROUND_COLOR = QtGui.QColor(60, 60, 80)

    def __init__(self):
        QtCore.QObject.__init__(self)

        self._dpi = 1

        self._name = "Default"
        self._accentColor = None
        self._backgroundColor = None

        self.setAccentColor(self.DEFAULT_ACCENT_COLOR)
        self.setBackgroundColor(self.DEFAULT_BACKGROUND_COLOR)

    def settings(self):
        """
        Return a dictionary of settings for the current Theme.

        :rtype: dict
        """
        settings = {}

        settings["name"] = self.name()

        accentColor = self.accentColor()
        settings["accentColor"] = accentColor.toString()

        backgroundColor = self.backgroundColor()
        settings["backgroundColor"] = backgroundColor.toString()

        return settings

    def setSettings(self, settings):
        """
        Set a dictionary of settings for the current Theme.

        :type settings: dict
        :rtype: None
        """
        name = settings.get("name")
        self.setName(name)

        color = settings.get("accentColor")
        if color:
            color = studioqt.Color.fromString(color)
            self.setAccentColor(color)

        color = settings.get("backgroundColor")
        if color:
            color = studioqt.Color.fromString(color)
            self.setBackgroundColor(color)

    def dpi(self):
        """
        Return the dpi for the Theme

        :rtype: float
        """
        return self._dpi

    def setDpi(self, dpi):
        """
        Set the dpi for the Theme

        :type dpi: float
        :rtype: None
        """
        self._dpi = dpi

    def name(self):
        """
        Return the name for the Theme

        :rtype: str
        """
        return self._name

    def setName(self, name):
        """
        Set the name for the Theme

        :type name: str
        :rtype: None
        """
        self._name = name

    def isDark(self):
        """
        Return True if the current theme is dark.

        rtype: bool
        """
        # The luminance for digital formats are (0.299, 0.587, 0.114)
        red = self.backgroundColor().redF() * 0.299
        green = self.backgroundColor().greenF() * 0.587
        blue = self.backgroundColor().blueF() * 0.114

        darkness = red + green + blue

        if darkness < 0.6:
            return True

        return False

    def setDark(self):
        """
        Set the current theme to the default dark color.
        
        :rtype: None 
        """
        self.setBackgroundColor(self.DEFAULT_DARK_COLOR)

    def setLight(self):
        """
        Set the current theme to the default light color.

        :rtype: None 
        """
        self.setBackgroundColor(self.DEFAULT_LIGHT_COLOR)

    def iconColor(self):
        """
        Return the icon color for the theme.

        :rtype: studioqt.Color 
        """
        return self.forgroundColor()

    def accentForgroundColor(self):
        """
        Return the foreground color for the accent color.

        :rtype: studioqt.Color 
        """
        return studioqt.Color(255, 255, 255, 255)

    def forgroundColor(self):
        """
        Return the foreground color for the theme.

        :rtype: studioqt.Color 
        """
        if self.isDark():
            return studioqt.Color(250, 250, 250, 225)
        else:
            return studioqt.Color(0, 40, 80, 180)

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

        :rtype: studioqt.Color 
        """
        if self.isDark():
            return studioqt.Color(255, 255, 255, 20)
        else:
            return studioqt.Color(255, 255, 255, 120)

    def itemBackgroundHoverColor(self):
        """
        Return the item background color when the mouse hovers over the item.

        :rtype: studioqt.Color 
        """
        return studioqt.Color(255, 255, 255, 60)

    def accentColor(self):
        """
        Return the accent color for the theme.

        :rtype: studioqt.Color or None
        """
        return self._accentColor

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

        :rtype: studioqt.Color or None
        """
        return self._backgroundColor

    def setAccentColor(self, color):
        """
        Set the accent color for the theme.

        :type color: studioqt.Color | QtGui.QColor
        """
        if isinstance(color, basestring):
            color = studioqt.Color.fromString(color)

        if isinstance(color, QtGui.QColor):
            color = studioqt.Color.fromColor(color)

        self._accentColor = color

        self.updated.emit()

    def setBackgroundColor(self, color):
        """
        Set the background color for the theme.

        :type color: studioqt.Color | QtGui.QColor
        """
        if isinstance(color, basestring):
            color = studioqt.Color.fromString(color)

        if isinstance(color, QtGui.QColor):
            color = studioqt.Color.fromColor(color)

        self._backgroundColor = color

        self.updated.emit()

    def createColorDialog(
        self,
        parent,
        standardColors=None,
        currentColor=None,
    ):
        """
        Create a new instance of the color dialog.

        :type parent: QtWidgets.QWidget
        :type standardColors: list[int]
        :rtype: QtWidgets.QColorDialog
        """
        dialog = QtWidgets.QColorDialog(parent)

        if standardColors:
            index = -1
            for colorR, colorG, colorB in standardColors:
                index += 1

                color = QtGui.QColor(colorR, colorG, colorB).rgba()

                try:
                    # Support for new qt5 signature
                    color = QtGui.QColor(color)
                    dialog.setStandardColor(index, color)
                except:
                    # Support for new qt4 signature
                    color = QtGui.QColor(color).rgba()
                    dialog.setStandardColor(index, color)

        # PySide2 doesn't support d.open(), so we need to pass a blank slot.
        dialog.open(self, QtCore.SLOT("blankSlot()"))

        if currentColor:
            dialog.setCurrentColor(currentColor)

        return dialog

    def browseAccentColor(self, parent=None):
        """
        Show the color dialog for changing the accent color.
        
        :type parent: QtWidgets.QWidget
        :rtype: None
        """
        standardColors = [(230, 60, 60), (210, 40, 40), (190, 20, 20),
                          (250, 80, 130), (230, 60, 110), (210, 40, 90),
                          (255, 90, 40), (235, 70, 20), (215, 50, 0),
                          (240, 100, 170), (220, 80, 150), (200, 60, 130),
                          (255, 125, 100), (235, 105, 80), (215, 85, 60),
                          (240, 200, 150), (220, 180, 130), (200, 160, 110),
                          (250, 200, 0), (230, 180, 0), (210, 160, 0),
                          (225, 200, 40), (205, 180, 20), (185, 160, 0),
                          (80, 200, 140), (60, 180, 120), (40, 160, 100),
                          (80, 225, 120), (60, 205, 100), (40, 185, 80),
                          (50, 180, 240), (30, 160, 220), (10, 140, 200),
                          (100, 200, 245), (80, 180, 225), (60, 160, 205),
                          (130, 110, 240), (110, 90, 220), (90, 70, 200),
                          (180, 160, 255), (160, 140, 235), (140, 120, 215),
                          (180, 110, 240), (160, 90, 220), (140, 70, 200),
                          (210, 110, 255), (190, 90, 235), (170, 70, 215)]

        currentColor = self.accentColor()

        dialog = self.createColorDialog(parent, standardColors, currentColor)
        dialog.currentColorChanged.connect(self.setAccentColor)

        if dialog.exec_():
            self.setAccentColor(dialog.selectedColor())
        else:
            self.setAccentColor(currentColor)

    def browseBackgroundColor(self, parent=None):
        """
        Show the color dialog for changing the background color.

        :type parent: QtWidgets.QWidget
        :rtype: None
        """
        standardColors = [(0, 0, 0), (20, 20, 20), (40, 40, 40), (60, 60, 60),
                          (80, 80, 80), (100, 100, 100), (20, 20, 30),
                          (40, 40, 50), (60, 60, 70), (80, 80, 90),
                          (100, 100, 110), (120, 120, 130), (0, 30, 60),
                          (20, 50, 80), (40, 70, 100), (60, 90, 120),
                          (80, 110, 140), (100, 130, 160), (0, 60, 60),
                          (20, 80, 80), (40, 100, 100), (60, 120, 120),
                          (80, 140, 140), (100, 160, 160), (0, 60, 30),
                          (20, 80, 50), (40, 100, 70), (60, 120, 90),
                          (80, 140, 110), (100, 160, 130), (60, 0, 10),
                          (80, 20, 30), (100, 40, 50), (120, 60, 70),
                          (140, 80, 90), (160, 100, 110), (60, 0, 40),
                          (80, 20, 60), (100, 40, 80), (120, 60, 100),
                          (140, 80, 120), (160, 100, 140), (40, 15, 5),
                          (60, 35, 25), (80, 55, 45), (100, 75, 65),
                          (120, 95, 85), (140, 115, 105)]

        currentColor = self.backgroundColor()

        dialog = self.createColorDialog(parent, standardColors, currentColor)
        dialog.currentColorChanged.connect(self.setBackgroundColor)

        if dialog.exec_():
            self.setBackgroundColor(dialog.selectedColor())
        else:
            self.setBackgroundColor(currentColor)

    def options(self):
        """
        Return the variables used to customise the style sheet.

        :rtype: dict
        """
        accentColor = self.accentColor()
        accentForegroundColor = self.accentForgroundColor()

        foregroundColor = self.forgroundColor()
        backgroundColor = self.backgroundColor()

        itemBackgroundColor = self.itemBackgroundColor()
        itemBackgroundHoverColor = self.itemBackgroundHoverColor()

        if self.isDark():
            darkness = "white"
        else:
            darkness = "black"

        resourceDirname = studiolibrary.resource.RESOURCE_DIRNAME
        resourceDirname = resourceDirname.replace("\\", "/")

        options = {
            "DARKNESS": darkness,
            "RESOURCE_DIRNAME": resourceDirname,
            "ACCENT_COLOR": accentColor.toString(),
            "ACCENT_COLOR_R": str(accentColor.red()),
            "ACCENT_COLOR_G": str(accentColor.green()),
            "ACCENT_COLOR_B": str(accentColor.blue()),
            "ACCENT_FOREGROUND_COLOR": accentForegroundColor.toString(),
            "FOREGROUND_COLOR": foregroundColor.toString(),
            "FOREGROUND_COLOR_R": str(foregroundColor.red()),
            "FOREGROUND_COLOR_G": str(foregroundColor.green()),
            "FOREGROUND_COLOR_B": str(foregroundColor.blue()),
            "BACKGROUND_COLOR": backgroundColor.toString(),
            "BACKGROUND_COLOR_R": str(backgroundColor.red()),
            "BACKGROUND_COLOR_G": str(backgroundColor.green()),
            "BACKGROUND_COLOR_B": str(backgroundColor.blue()),
            "ITEM_TEXT_COLOR": foregroundColor.toString(),
            "ITEM_TEXT_SELECTED_COLOR": accentForegroundColor.toString(),
            "ITEM_BACKGROUND_COLOR": itemBackgroundColor.toString(),
            "ITEM_BACKGROUND_HOVER_COLOR": itemBackgroundHoverColor.toString(),
            "ITEM_BACKGROUND_SELECTED_COLOR": accentColor.toString(),
        }

        return options

    def styleSheet(self):
        """
        Return the style sheet for this theme.

        :rtype: str
        """
        options = self.options()
        path = studiolibrary.resource.get("css", "default.css")
        styleSheet = studioqt.StyleSheet.fromPath(path,
                                                  options=options,
                                                  dpi=self.dpi())
        return styleSheet.data()
Exemplo n.º 16
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 savePersistentValues(self):
        """
        Triggered when the user changes the options.
        """
        data = {}

        for widget in self._widgets:
            name = widget.data().get("name")
            if name and widget.data().get("persistent"):

                key = self.objectName() or "FormWidget"
                key = widget.data().get("persistentKey", key)

                data.setdefault(key, {})
                data[key][name] = widget.value()

        for key in data:
            settings.set(key, data[key])

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

        :rtype: dict
        """
        values = {}
        defaultValues = self.defaultValues()

        for field in self.schema():
            name = field.get("name")
            persistent = field.get("persistent")

            if persistent:
                key = self.objectName() or "FormWidget"
                key = field.get("persistentKey", key)
                value = settings.get(key, {}).get(name)
            else:
                value = defaultValues.get(name)

            if value:
                values[name] = value

        self.setValues(values)

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

        :rtype: dict
        """
        return self._schema

    def _sortSchema(self, schema):
        """
        Sort the schema depending on the group order.

        :type schema: list[dict]
        :rtype: list[dict]
        """
        order = 0

        for i, field in enumerate(schema):
            if field.get("type") == "group":
                order = field.get("order", order)
            field["order"] = order

        def _key(field):
            return field["order"]

        return sorted(schema, key=_key)

    def setSchema(self, schema, layout=None, errorsVisible=False):
        """
        Set the schema for the widget.
        
        :type schema: list[dict]
        :type layout: None or str
        :type errorsVisible: bool
        """
        self._schema = self._sortSchema(schema)

        for field in self._schema:

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

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

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

            errorVisible = field.get("errorVisible")
            if errorVisible is not None:
                field["errorVisible"] = errorVisible
            else:
                field["errorVisible"] = errorsVisible

            widget = cls(data=field, parent=self._fieldsFrame, formWidget=self)

            data_ = widget.defaultData()
            data_.update(field)

            widget.setData(data_)

            value = field.get("value")
            default = field.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(widget=widget)

    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 errors(self):
        """
        Get all the errors.

        :rtype: list[str]
        """
        errors = []
        for widget in self._widgets:
            error = widget.data().get("error")
            if error:
                errors.append(error)
        return errors

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

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

    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, widget=None):
        """Validate the current options using the validator."""

        if self._validator:

            logger.debug("Running validator: form.validate(widget=%s)", widget)

            values = {}

            for name, value in self.values().items():
                data = self.widget(name).data()
                if data.get("validate", True):
                    values[name] = value

            if widget:
                values["fieldChanged"] = widget.name()

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

            self.validated.emit()

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

    def setData(self, name, data):
        """
        Set the data for the given field name.

        :type name: str
        :type data: dict
        """
        widget = self.widget(name)
        widget.setData(data)

    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 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()
Exemplo n.º 17
0
    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

        align = align | QtCore.Qt.AlignVCenter

        rect = QtCore.QRect(visualRect)

        if self.itemsWidget().isIconView():

            if self.isLabelOverItem() or self.isLabelUnderItem():
                padding = 8 if padding < 8 else padding

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

                rect.setY(y)
                rect.setHeight(height)

            if self.isLabelOverItem():
                color2 = self.itemsWidget().backgroundColor().toRgb()
                color2.setAlpha(200)

                painter.setPen(QtCore.Qt.NoPen)
                painter.setBrush(QtGui.QBrush(color2))
                painter.drawRect(rect)

        pen = QtGui.QPen(color)
        painter.setPen(pen)
        painter.setFont(font)
        painter.drawText(rect, align, text)
Exemplo n.º 18
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._formWidget.validated.connect(self._validated)
        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 _validated(self):
        """Triggered when the form has been validated"""
        self._acceptButton.setEnabled(not self._formWidget.hasErrors())

    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()
Exemplo n.º 19
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._globalQueries = {}
        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 []
        queries.extend(self._globalQueries.values())

        items = self.createItems()
        for item in items:
            value = item.itemData().get(field)
            if value:
                results.setdefault(value, {'count': 0, 'name': value})
                match = self.match(item.itemData(), queries)
                if match:
                    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 clear(self):
        """Clear all the item data."""
        self._items = []
        self._results = []
        self._groupedResults = {}
        self.dataChanged.emit()

    def sync(self, progressCallback=None):
        """Sync the file system with the database."""
        if not self.path():
            logger.info('No path set for syncing data')
            return

        if progressCallback:
            progressCallback("Syncing")

        new = {}
        old = self.read()
        depth = self.recursiveDepth()

        items = studiolibrary.findItems(
            self.path(),
            depth=depth,
        )

        items = list(items)
        count = len(items)

        for i, item in enumerate(items):
            percent = (float(i+1)/float(count))
            if progressCallback:
                percent *= 100
                label = "{0:.0f}%".format(percent)
                progressCallback(label, percent)

            path = item.path()

            itemData = old.get(path, {})
            itemData.update(item.createItemData())

            new[path] = itemData

        if progressCallback:
            progressCallback("Post Callbacks")

        self.postSync(new)

        if progressCallback:
            progressCallback("Saving Cache")

        self.save(new)

        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):
        """
        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=self._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 = []

        queries = copy.copy(queries)
        queries.extend(self._globalQueries.values())

        logger.debug("Search queries:")
        for query in queries:
            logger.debug('Query: %s', query)

        items = self.createItems()
        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 queries(self, exclude=None):
        """
        Get all the queries for the dataset excluding the given ones.
        
        :type exclude: list[str] or None
        
        :rtype: list[dict] 
        """
        queries = []
        exclude = exclude or []

        for query in self._queries.values():
            if query.get('name') not in exclude:
                queries.append(query)

        return queries

    def addGlobalQuery(self, query):
        """
        Add a global query to library.
        
        :type query: dict 
        """
        self._globalQueries[query["name"]] = query

    def addQuery(self, query):
        """
        Add a search query to the library.
        
        Examples:
            addQuery({
                'name': 'My Query',
                'operator': 'or',
                'filters': [
                    ('folder', 'is' '/library/proj/test'),
                    ('folder', 'startswith', '/library/proj/test'),
                ]
            })
        
        :type query: dict
        """
        self._queries[query["name"]] = query

    def removeQuery(self, name):
        """
        Remove the query with the given name.
        
        :type name: str 
        """
        if name in self._queries:
            del self._queries[name]

    def queryExists(self, name):
        """
        Check if the given query name exists.
        
        :type name: str
        :rtype: bool 
        """
        return name in self._queries

    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
Exemplo n.º 20
0
class ColorPickerWidget(QtWidgets.QFrame):

    COLOR_BUTTON_CLASS = ColorButton

    colorChanged = QtCore.Signal(object)

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

        self._buttons = []
        self._currentColor = None
        self._browserColors = None
        self._menuButton = None

        layout = QtWidgets.QHBoxLayout()
        layout.setSpacing(0)
        layout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(layout)

    def enterEvent(self, event):
        """
        Overriding this method to fix a bug with custom actions.

        :type event: QtCore.QEvent
        """
        if self.parent():
            menu = self.parent().parent()
            if isinstance(menu, QtWidgets.QMenu):
                menu.setActiveAction(None)

    def _colorChanged(self, color):
        """
        Triggered when the user clicks or browses for a color.

        :type color: studioqt.Color
        :rtype: None
        """
        self.setCurrentColor(color)
        self.colorChanged.emit(color)

    def menuButton(self):
        """
        Get the menu button used for browsing for custom colors.

        :rtype: QtGui.QWidget
        """
        return self._menuButton

    def deleteButtons(self):
        """
        Delete all the color buttons.

        :rtype: None
        """
        layout = self.layout()
        while layout.count():
            item = layout.takeAt(0)
            item.widget().deleteLater()

    def currentColor(self):
        """
        Return the current color.

        :rtype: studioqt.Color
        """
        return self._currentColor

    def setCurrentColor(self, color):
        """
        Set the current color.

        :type color: studioqt.Color
        """
        self._currentColor = color
        self.refresh()

    def refresh(self):
        """Update the current state of the selected color."""
        for button in self._buttons:
            button.setChecked(button.color() == self.currentColor())

    def setColors(self, colors):
        """
        Set the colors for the color bar.

        :type colors: list[str] or list[studioqt.Color]
        """
        self.deleteButtons()

        first = True
        last = False

        for i, color in enumerate(colors):

            if i == len(colors)-1:
                last = True

            if not isinstance(color, str):
                color = studioqt.Color(color)
                color = color.toString()

            callback = partial(self._colorChanged, color)

            button = self.COLOR_BUTTON_CLASS(self)

            button.setObjectName('colorButton')
            button.setColor(color)

            button.setSizePolicy(
                QtWidgets.QSizePolicy.Expanding,
                QtWidgets.QSizePolicy.Preferred
            )

            button.setProperty("first", first)
            button.setProperty("last", last)

            button.clicked.connect(callback)

            self.layout().addWidget(button)

            self._buttons.append(button)

            first = False

        self._menuButton = QtWidgets.QPushButton("...", self)
        self._menuButton.setObjectName('menuButton')
        self._menuButton.setSizePolicy(
            QtWidgets.QSizePolicy.Expanding,
            QtWidgets.QSizePolicy.Preferred
        )

        self._menuButton.clicked.connect(self.browseColor)
        self.layout().addWidget(self._menuButton)
        self.refresh()

    def setBrowserColors(self, colors):
        """
        :type colors: list((int,int,int))
        """
        self._browserColors = colors

    def browserColors(self):
        """
        Get the colors to be displayed in the browser
    
        :rtype: list[studioqt.Color]
        """
        return self._browserColors

    @QtCore.Slot()
    def blankSlot(self):
        """Blank slot to fix an issue with PySide2.QColorDialog.open()"""
        pass

    def browseColor(self):
        """
        Show the color dialog.

        :rtype: None
        """
        color = self.currentColor()
        if color:
            color = studioqt.Color.fromString(color)

        d = QtWidgets.QColorDialog(self)
        d.setCurrentColor(color)

        standardColors = self.browserColors()

        if standardColors:
            index = -1
            for standardColor in standardColors:
                index += 1

                try:
                    # Support for new qt5 signature
                    standardColor = QtGui.QColor(standardColor)
                    d.setStandardColor(index, standardColor)
                except:
                    # Support for new qt4 signature
                    standardColor = QtGui.QColor(standardColor).rgba()
                    d.setStandardColor(index, standardColor)

        d.currentColorChanged.connect(self._colorChanged)

        if d.exec_():
            self._colorChanged(d.selectedColor())
        else:
            self._colorChanged(color)
Exemplo n.º 21
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._index = {}
        self._locked = False
        self._dataset = None
        self._recursive = True
        self._options = {
            'field': 'path',
            'separator': '/',
            'recursive': True,
            'autoRootPath': True,
            'rootText': 'FOLDERS',
            'sortBy': None,
            'queries': [{
                'filters': [('type', 'is', 'Folder')]
            }]
        }

        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 clear(self):
        """Clear all the items from the tree widget."""
        self._items = []
        self._index = {}
        super(SidebarWidget, self).clear()

    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 sortBy(self):
        """
        Get the sortby field.
        
        :rtype: str 
        """
        return self._options.get('sortBy', [self.field()])

    def field(self):
        """
        Get the field.
        
        :rtype: str 
        """
        return self._options.get('field', '')

    def rootText(self):
        """
        Get the root text.
        
        :rtype: str 
        """
        return self._options.get('rootText')

    def separator(self):
        """
        Get the separator used in the fields to separate level values.

        :rtype: str 
        """
        return self._options.get('separator', DEFAULT_SEPARATOR)

    def _dataChanged(self):
        """Triggered when the data set has changed."""
        pass
        # data = collections.OrderedDict()
        # queries = self._options.get("queries")
        #
        # items = self.dataset().findItems(queries)
        #
        # for item in items:
        #     itemData = item.itemData()
        #     value = itemData.get(self.field())
        #     data[value] = {'iconPath': itemData.get('iconPath')}
        #
        # if data:
        #     root = findRoot(data.keys(), separator=self.separator())
        #     self.setPaths(data, 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 = []

        for path in self.selectedPaths():
            if self.isRecursive():

                suffix = "" if path.endswith("/") else "/"

                filter_ = ('folder', 'startswith', path + suffix)
                filters.append(filter_)

            filter_ = ('folder', 'is', path)
            filters.append(filter_)

        uniqueName = 'sidebar_widget_' + 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(14 * dpi)
        self.setMinimumWidth(22 * 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
        """
        return self._index.get(path)

    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.setDpi(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 studiolibrary.normPaths(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 = studiolibrary.normPaths(paths)
        items = self.items()
        for item in items:
            if studiolibrary.normPath(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 setPaths(self, *args, **kwargs):
        """
        This method has been deprecated.
        """
        logger.warning("This method has been deprecated!")
        self.setData(*args, **kwargs)

    def setData(self, data, root="", split=None):
        """
        Set the items to the given items.

        :type data: list[str]
        :type root: str
        :type split: str
        :rtype: None
        """
        settings = self.settings()

        self.blockSignals(True)

        self.clear()

        if not root:
            root = findRoot(data.keys(), self.separator())

        self.addPaths(data, root=root, split=split)

        self.setSettings(settings)

        self.blockSignals(False)

        self.search()

    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
        """
        data = pathsToDict(paths, root=root, separator=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 DEFAULT_SEPARATOR

        self._index = {}

        for key in data:

            path = split.join([key])

            item = SidebarWidgetItem(self)
            item.setText(0, unicode(key))
            item.setPath(path)
            self._index[path] = item

            if self.rootText():
                item.setText(0, self.rootText())
                item.setBold(True)
                item.setIconPath('none')
                item.setExpanded(True)

            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)
                    self._index[path] = child

                    _recursive(child, val, split=split)

            _recursive(item, data[key], split=split)

        self.update()
Exemplo n.º 22
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.setPath(path)

    def firstFrame(self):
        """
        Get the path to the first frame.

        :rtype: str
        """
        if self._frames:
            return self._frames[0]
        return ""

    def setPath(self, path):
        """
        Set a single frame or a directory to an image sequence.
        
        :type path: str
        """
        if os.path.isfile(path):
            self._frame = 0
            self._frames = [path]
        elif os.path.isdir(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._timer.timeout.connect(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)
Exemplo n.º 23
0
 def url(self):
     """Used by the mime data when dragging/dropping the item."""
     return QtCore.QUrl("file:///" + self.path())
Exemplo n.º 24
0
class Item(QtWidgets.QTreeWidgetItem):
    """The Item is used to hold rows of information for an item view."""

    ICON_PATH = None
    TYPE_ICON_PATH = None

    ThreadPool = QtCore.QThreadPool()
    THUMBNAIL_PATH = ""

    MAX_ICON_SIZE = 256

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

    THUMBNAIL_COLUMN = 0
    ENABLE_THUMBNAIL_THREAD = True
    PAINT_SLIDER = False

    _globalSignals = GlobalSignals()
    sliderChanged = _globalSignals.sliderChanged

    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 = None
        self._typePixmap = None

        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 = ""

        self._sliderDown = False
        self._sliderValue = 0.0
        self._sliderPreviousValue = 0.0
        self._sliderPosition = None
        self._sliderEnabled = 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.resetSlider()

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

        :rtype: None
        """
        pass

    def setGroupItem(self, groupItem):
        """
        Set the group item that this item is a child to.

        :type groupItem: groupitem.GroupItem
        """
        self._groupItem = groupItem

    def groupItem(self):
        """
        Get the group item that this item is a child to.

        :rtype: groupitem.GroupItem
        """
        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.isLabelUnderItem():
                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 defaultThumbnailPath(self):
        """
        Get the default thumbnail path.
        
        :rtype: str 
        """
        return self.THUMBNAIL_PATH

    def defaultThumbnailIcon(self):
        """
        Get the default thumbnail icon.
        
        :rtype: QtGui.QIcon 
        """
        return QtGui.QIcon(self.defaultThumbnailPath())

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

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

        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)

                self._thumbnailIcon = self.defaultThumbnailIcon()
            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):
        """
        Check if the label should be displayed.

        :rtype: bool
        """
        return self.labelDisplayOption() != LabelDisplayOption.Hide

    def isLabelOverItem(self):
        """
        Check if the label should be displayed over the item.

        :rtype: bool
        """
        return self.labelDisplayOption() == LabelDisplayOption.Over

    def isLabelUnderItem(self):
        """
        Check if the label should be displayed under the item.

        :rtype: bool
        """
        return self.labelDisplayOption() == LabelDisplayOption.Under

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

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

    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.sliderEvent(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:
            if self.isSliderEnabled():
                self.setSliderDown(True)
                self._sliderPosition = 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.isSliderDown():
            self._sliderPosition = None
            self._sliderPreviousValue = self.sliderValue()

    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().itemBackgroundColor()

    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
        """
        return QtCore.QRect(option.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)

            self.paintIcon(painter, option, index)

            if index.column() == 0 and self.sliderValue() != 0:
                self.paintSlider(painter, option, index)

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

            if index.column() == 0:
                self.paintTypeIcon(painter, option)

            if index.column() == 0 and 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
        """
        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))

        if not self.itemsWidget().isIconView():
            spacing = 1 * self.dpi()
            height = visualRect.height() - spacing
            visualRect.setHeight(height)

        painter.drawRect(visualRect)

    def paintSlider(self, painter, option, index):
        """
        Draw the virtual slider for the item.

        :type painter: QtWidgets.QPainter
        :type option: QtWidgets.QStyleOptionViewItem
        :type index: QtCore.QModelIndex
        """
        if not self.PAINT_SLIDER:
            return

        if not self.itemsWidget().isIconView():
            return

        # Draw slider background
        painter.setPen(QtGui.QPen(QtCore.Qt.NoPen))

        rect = self.visualRect(option)

        color = self.itemsWidget().backgroundColor().toRgb()
        color.setAlpha(75)
        painter.setBrush(QtGui.QBrush(color))

        height = rect.height()

        ratio = self.sliderValue()

        if ratio < 0:
            width = 0
        elif ratio > 100:
            width = rect.width()
        else:
            width = rect.width() * (float(ratio) / 100)

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

        painter.drawRect(rect)

        # Draw slider value
        rect = self.visualRect(option)
        rect.setY(rect.y() + (4 * self.dpi()))

        color = self.itemsWidget().textColor().toRgb()
        color.setAlpha(220)
        pen = QtGui.QPen(color)
        align = QtCore.Qt.AlignTop | QtCore.Qt.AlignHCenter

        painter.setPen(pen)
        painter.drawText(rect, align, str(self.sliderValue()) + "%")

    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.isLabelUnderItem():
            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

        align = align | QtCore.Qt.AlignVCenter

        rect = QtCore.QRect(visualRect)

        if self.itemsWidget().isIconView():

            if self.isLabelOverItem() or self.isLabelUnderItem():
                padding = 8 if padding < 8 else padding

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

                rect.setY(y)
                rect.setHeight(height)

            if self.isLabelOverItem():
                color2 = self.itemsWidget().backgroundColor().toRgb()
                color2.setAlpha(200)

                painter.setPen(QtCore.Qt.NoPen)
                painter.setBrush(QtGui.QBrush(color2))
                painter.drawRect(rect)

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

    # ------------------------------------------------------------------------
    # Support for middle mouse slider
    # ------------------------------------------------------------------------

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

        :type enabled: bool
        """
        self._sliderEnabled = enabled

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

        :rtype: bool
        """
        return self._sliderEnabled

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

        :param event: QtGui.QMouseEvent
        """
        if self.isSliderDown():
            value = (event.pos().x() - self.sliderPosition().x()) / 1.5
            value = math.ceil(value) + self.sliderPreviousValue()
            try:
                self.setSliderValue(value)
            except Exception:
                self.setSliderDown(False)

    def resetSlider(self):
        """Reset the slider value to zero."""
        self._sliderValue = 0.0
        self._sliderPreviousValue = 0.0

    def setSliderDown(self, down):
        """Called when the middle mouse button is released."""
        self._sliderDown = down
        if not down:
            self._sliderPosition = None
            self._sliderPreviousValue = self.sliderValue()

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

        :rtype: bool
        """
        return self._sliderDown

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

        :type value: float
        :rtype: bool
        """
        if self.isSliderEnabled():

            self._sliderValue = value

            if self.PAINT_SLIDER:
                self.update()

            self.sliderChanged.emit(value)

            if self.PAINT_SLIDER:
                self.update()

            logger.debug("Blending:" + str(value))

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

        :rtype: float
        """
        return self._sliderValue

    def sliderPreviousValue(self):
        """
        :rtype: float
        """
        return self._sliderPreviousValue

    def sliderPosition(self):
        """
        :rtype: QtGui.QPoint
        """
        return self._sliderPosition

    # ------------------------------------------------------------------------
    # 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
        """
        self._imageSequencePath = path

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

    def stop(self):
        """Stop playing the image sequence movie."""
        if self.imageSequence():
            self.imageSequence().stop()

    def play(self):
        """Start playing the image sequence movie."""
        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)

    def typeIconPath(self):
        """
        Return the type icon path on disc.

        :rtype: path or None
        """
        if self.TYPE_ICON_PATH is None:
            return self.ICON_PATH

        return self.TYPE_ICON_PATH

    def typePixmap(self):
        """
        Return the type pixmap for the plugin.

        :rtype: QtWidgets.QPixmap
        """
        if not self._typePixmap:
            iconPath = self.typeIconPath()
            if iconPath and os.path.exists(iconPath):
                self._typePixmap = QtGui.QPixmap(iconPath)
        return self._typePixmap

    def typeIconRect(self, option):
        """
        Return the type icon rect.

        :rtype: QtGui.QRect
        """
        padding = 2 * self.dpi()
        r = self.iconRect(option)

        x = r.x() + padding
        y = r.y() + padding
        rect = QtCore.QRect(x, y, 13 * self.dpi(), 13 * self.dpi())

        return rect

    def paintTypeIcon(self, painter, option):
        """
        Draw the item type icon at the top left.

        :type painter: QtWidgets.QPainter
        :type option: QtWidgets.QStyleOptionViewItem
        :rtype: None
        """
        rect = self.typeIconRect(option)
        typePixmap = self.typePixmap()
        if typePixmap:
            painter.setOpacity(0.5)
            painter.drawPixmap(rect, typePixmap)
            painter.setOpacity(1)
Exemplo n.º 25
0
class Library(QtCore.QObject):

    Fields = [
        {
            "name": "icon",
            "sortable": False,
            "groupable": False,
        },
        {
            "name": "name",
            "sortable": True,
            "groupable": False,
        },
        {
            "name": "path",
            "sortable": True,
            "groupable": False,
        },
        {
            "name": "type",
            "sortable": True,
            "groupable": True,
        },
        {
            "name": "folder",
            "sortable": True,
            "groupable": False,
        },
        {
            "name": "category",
            "sortable": True,
            "groupable": True,
        },
        {
            "name": "modified",
            "sortable": True,
            "groupable": False,
        },
        {
            "name": "Custom Order",
            "sortable": True,
            "groupable": False,
        },
    ]

    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._globalQueries = {}
        self._groupedResults = {}
        self._searchTime = 0
        self._searchEnabled = True
        self._registeredItems = None
        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):
        """
        Get all the fields for the library.

        :rtype: list[dict]
        """
        return self.Fields

    def fieldNames(self):
        """
        Get all the field names for the library.

        :rtype: list[str]
        """
        return [field["name"] for field in 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 []
        queries.extend(self._globalQueries.values())

        items = self.createItems()
        for item in items:
            value = item.itemData().get(field)
            if value:
                results.setdefault(value, {'count': 0, 'name': value})
                match = self.match(item.itemData(), queries)
                if match:
                    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 clear(self):
        """Clear all the item data."""
        self._items = []
        self._results = []
        self._groupedResults = {}
        self._registeredItems = None
        self.dataChanged.emit()

    def registeredItems(self):
        """
        Get registered items for the library.

        :rtype: list[LibraryItem.__class__]
        """
        return studiolibrary.registeredItems()

    def isValidPath(self, path):
        """
        Check if the given item path should be ignored.

        :type path: str
        :rtype: bool
        """
        for ignore in studiolibrary.config.get('ignorePaths', []):
            if ignore in path:
                return False
        return True

    def walker(self, path):
        """
        Walk the given root path for valid items and return the item data.

        :type path: str

        :rtype: collections.Iterable[dict]
        """
        path = studiolibrary.normPath(path)
        maxDepth = self.recursiveDepth()
        startDepth = path.count(os.path.sep)

        for root, dirs, files in os.walk(path, followlinks=True):

            files.extend(dirs)

            for filename in files:

                # Normalise the path for consistent matching
                path = studiolibrary.normPath(os.path.join(root, filename))

                # Ignore any paths that have been specified in the config
                if not self.isValidPath(path):
                    continue

                # Match the path with a registered item
                item = self.itemFromPath(path)

                remove = False
                if item:

                    # Yield the item data that matches the current path
                    yield item.createItemData()

                    # Stop walking if the item doesn't support nested items
                    if not item.ENABLE_NESTED_ITEMS:
                        remove = True

                if remove and filename in dirs:
                    dirs.remove(filename)

            if maxDepth == 1:
                break

            # Stop walking the directory if the maximum depth has been reached
            currentDepth = root.count(os.path.sep)
            if (currentDepth - startDepth) >= maxDepth:
                del dirs[:]

    def sync(self, progressCallback=None):
        """
        Sync the file system with the database.

        :type progressCallback: None or func
        """
        if not self.path():
            logger.info('No path set for syncing data')
            return

        if progressCallback:
            progressCallback("Syncing")

        new = {}
        old = self.read()
        items = list(self.walker(self.path()))
        count = len(items)

        for i, item in enumerate(items):
            percent = (float(i+1)/float(count))
            if progressCallback:
                percent *= 100
                label = "{0:.0f}%".format(percent)
                progressCallback(label, percent)

            path = item.get("path")
            new[path] = old.get(path, {})
            new[path].update(item)

        if progressCallback:
            progressCallback("Post Callbacks")

        self.postSync(new)

        if progressCallback:
            progressCallback("Saving Cache")

        self.save(new)

        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):
        """
        Create all the items for the model.

        :rtype: list[studiolibrary.LibraryItem] 
        """
        # Check if the cache has changed since the last read call
        if self.isDirty():

            logger.debug("Creating items")

            self._items = []

            data = self.read()

            modules = []
            for itemData in data.values():
                if '__class__' in itemData:
                    modules.append(itemData.get("__class__"))
            modules = set(modules)

            classes = {}
            for module in modules:
                classes[module] = studiolibrary.resolveModule(module)

            for path in data.keys():
                module = data[path].get("__class__")
                cls = classes.get(module)
                if cls:
                    item = cls(path, library=self, libraryWindow=self._libraryWindow)
                    item.setItemData(data[path])
                    self._items.append(item)
                else:
                    # This is to support the older database data before v2.6.
                    # Will remove in a later version.
                    item = self.itemFromPath(path, library=self, libraryWindow=self._libraryWindow)
                    if item:
                        item.setItemData(data[path])
                        self._items.append(item)

        return self._items

    def itemFromPath(self, path, **kwargs):
        """
        Return a new item instance for the given path.

        :type path: str
        :rtype: studiolibrary.LibraryItem or None
        """
        path = studiolibrary.normPath(path)

        for cls in self.registeredItems():
            if cls.match(path):
                return cls(path, **kwargs)

    def itemsFromPaths(self, paths, **kwargs):
        """
        Return new item instances for the given paths.

        :type paths: list[str]:
        :rtype: collections.Iterable[studiolibrary.LibraryItem]
        """
        for path in paths:
            item = self.itemFromPath(path, **kwargs)
            if item:
                yield item

    def itemsFromUrls(self, urls, **kwargs):
        """
        Return new item instances for the given QUrl objects.

        :type urls: list[QtGui.QUrl]
        :rtype: list[studiolibrary.LibraryItem]
        """
        items = []
        for path in studiolibrary.pathsFromUrls(urls):

            item = self.itemFromPath(path, **kwargs)

            if item:
                data = item.createItemData()
                item.setItemData(data)

                items.append(item)
            else:
                msg = 'Cannot find the item for path "{0}"'
                msg = msg.format(path)
                logger.warning(msg)

        return 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 = []

        queries = copy.copy(queries)
        queries.extend(self._globalQueries.values())

        logger.debug("Search queries:")
        for query in queries:
            logger.debug('Query: %s', query)

        items = self.createItems()
        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 queries(self, exclude=None):
        """
        Get all the queries for the dataset excluding the given ones.
        
        :type exclude: list[str] or None
        
        :rtype: list[dict] 
        """
        queries = []
        exclude = exclude or []

        for query in self._queries.values():
            if query.get('name') not in exclude:
                queries.append(query)

        return queries

    def addGlobalQuery(self, query):
        """
        Add a global query to library.
        
        :type query: dict 
        """
        self._globalQueries[query["name"]] = query

    def addQuery(self, query):
        """
        Add a search query to the library.
        
        Examples:
            addQuery({
                'name': 'My Query',
                'operator': 'or',
                'filters': [
                    ('folder', 'is' '/library/proj/test'),
                    ('folder', 'startswith', '/library/proj/test'),
                ]
            })
        
        :type query: dict
        """
        self._queries[query["name"]] = query

    def removeQuery(self, name):
        """
        Remove the query with the given name.
        
        :type name: str 
        """
        if name in self._queries:
            del self._queries[name]

    def queryExists(self, name):
        """
        Check if the given query name exists.
        
        :type name: str
        :rtype: bool 
        """
        return name in self._queries

    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 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 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 = six.text_type(data)
                else:
                    itemValue = data.get(key)

                if isinstance(value, six.string_types):
                    value = value.lower()

                if isinstance(itemValue, six.string_types):
                    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
class SidebarWidget(QtWidgets.QWidget):

    itemDropped = QtCore.Signal(object)
    itemRenamed = QtCore.Signal(str, str)
    itemSelectionChanged = QtCore.Signal()
    settingsMenuRequested = QtCore.Signal(object)

    def __init__(self, *args):
        super(SidebarWidget, self).__init__(*args)

        self._dataset = None
        self._lineEdit = None
        self._previousFilterText = ""

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

        self.setLayout(layout)

        self._treeWidget = TreeWidget(self)
        self._treeWidget.itemDropped = self.itemDropped
        self._treeWidget.itemRenamed = self.itemRenamed
        self._treeWidget.itemSelectionChanged.connect(self._itemSelectionChanged)

        self._titleWidget = self.createTitleWidget()
        self._titleWidget.ui.menuButton.clicked.connect(self.showSettingsMenu)
        self._titleWidget.ui.titleButton.clicked.connect(self.clearSelection)

        self.layout().addWidget(self._titleWidget)
        self.layout().addWidget(self._treeWidget)

        self._treeWidget.installEventFilter(self)

    def _itemSelectionChanged(self, *args):
        self.itemSelectionChanged.emit()

    def eventFilter(self, obj, event):
        """Using an event filter to show the search widget on key press."""
        if event.type() == QtCore.QEvent.KeyPress:
            self._keyPressEvent(event)

        return super(SidebarWidget, self).eventFilter(obj, event)

    def _keyPressEvent(self, event):
        """
        Triggered from the tree widget key press event.

        :type event: QKeyEvent
        """
        text = event.text().strip()

        if not text.isalpha() and not text.isdigit():
            return

        if text and not self._titleWidget.ui.filterEdit.hasFocus():
                self._titleWidget.ui.filterEdit.setText(text)

        self.setFilterVisible(True)

        self._previousFilterText = text

    def _filterVisibleTrigger(self, visible):
        """
        Triggered by the filter visible action.

        :type visible: bool
        """
        self.setFilterVisible(visible)
        self._titleWidget.ui.filterEdit.selectAll()

    def createTitleWidget(self):
        """
        Create a new instance of the title bar widget.

        :rtype: QtWidgets.QFrame
        """

        class UI(object):
            """Proxy class for attaching ui widgets as properties."""
            pass

        titleWidget = QtWidgets.QFrame(self)
        titleWidget.setObjectName("titleWidget")
        titleWidget.ui = UI()

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

        hlayout = QtWidgets.QHBoxLayout(self)
        hlayout.setSpacing(0)
        hlayout.setContentsMargins(0,0,0,0)

        vlayout.addLayout(hlayout)

        titleButton = QtWidgets.QPushButton(self)
        titleButton.setText("Folders")
        titleButton.setObjectName("titleButton")
        titleWidget.ui.titleButton = titleButton

        hlayout.addWidget(titleButton)

        menuButton = QtWidgets.QPushButton(self)
        menuButton.setText("...")
        menuButton.setObjectName("menuButton")
        titleWidget.ui.menuButton = menuButton

        hlayout.addWidget(menuButton)

        self._lineEdit = studiolibrary.widgets.LineEdit(self)
        self._lineEdit.hide()
        self._lineEdit.setObjectName("filterEdit")
        self._lineEdit.setText(self.treeWidget().filterText())
        self._lineEdit.textChanged.connect(self.searchChanged)
        titleWidget.ui.filterEdit = self._lineEdit

        vlayout.addWidget(self._lineEdit)

        titleWidget.setLayout(vlayout)

        return titleWidget

    def _dataChanged(self):
        pass

    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 = []

        for path in self.selectedPaths():
            if self.isRecursive():
                suffix = "" if path.endswith("/") else "/"

                filter_ = ('folder', 'startswith', path + suffix)
                filters.append(filter_)

            filter_ = ('folder', 'is', path)
            filters.append(filter_)

        uniqueName = 'sidebar_widget_' + str(id(self))
        return {'name': uniqueName, 'operator': 'or', 'filters': filters}

    def searchChanged(self, text):
        """
        Triggered when the search filter has changed.

        :type text: str
        """
        self.refreshFilter()
        if text:
            self.setFilterVisible(True)
        else:
            self.treeWidget().setFocus()
            self.setFilterVisible(False)

    def showSettingsMenu(self):
        """Create and show a new settings menu instance."""

        menu = studioqt.Menu(self)

        self.settingsMenuRequested.emit(menu)

        self.createSettingsMenu(menu)

        point = QtGui.QCursor.pos()
        point.setX(point.x() + 3)
        point.setY(point.y() + 3)
        action = menu.exec_(point)
        menu.close()

    def createSettingsMenu(self, menu):
        """
        Create a new settings menu instance.

        :rtype: QMenu
        """
        action = menu.addAction("Show Filter")
        action.setCheckable(True)
        action.setChecked(self.isFilterVisible())

        callback = functools.partial(self._filterVisibleTrigger, not self.isFilterVisible())
        action.triggered.connect(callback)

        action = menu.addAction("Show Icons")
        action.setCheckable(True)
        action.setChecked(self.iconsVisible())

        callback = functools.partial(self.setIconsVisible, not self.iconsVisible())
        action.triggered.connect(callback)

        action = menu.addAction("Show Root Folder")
        action.setCheckable(True)
        action.setChecked(self.isRootVisible())

        callback = functools.partial(self.setRootVisible, not self.isRootVisible())
        action.triggered.connect(callback)

        return menu

    def setFilterVisible(self, visible):
        """
        Set the filter widget visible

        :type visible: bool
        """
        self._titleWidget.ui.filterEdit.setVisible(visible)
        self._titleWidget.ui.filterEdit.setFocus()

        if not visible and bool(self.treeWidget().filterText()):
            self.treeWidget().setFilterText("")
        else:
            self.refreshFilter()

    def setSettings(self, settings):
        """
        Set the settings for the widget.

        :type settings: dict
        """
        self.treeWidget().setSettings(settings)

        value = settings.get("filterVisible")
        if value is not None:
            self.setFilterVisible(value)

        value = settings.get("filterText")
        if value is not None:
            self.setFilterText(value)

    def settings(self):
        """
        Get the settings for the widget.

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

        settings["filterText"]  = self.filterText()
        settings["filterVisible"]  = self.isFilterVisible()

        return settings

    # --------------------------------
    # convenience methods
    # --------------------------------

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

    def setFilterText(self, text):
        self._titleWidget.ui.filterEdit.setText(text)

    def refreshFilter(self):
        self.treeWidget().setFilterText(self._titleWidget.ui.filterEdit.text())

    def isFilterVisible(self):
        return bool(self.treeWidget().filterText()) or self._titleWidget.ui.filterEdit.isVisible()

    def setIconsVisible(self, visible):
        self.treeWidget().setIconsVisible(visible)

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

    def setRootVisible(self, visible):
        self.treeWidget().setRootVisible(visible)

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

    def treeWidget(self):
        return self._treeWidget

    def setDpi(self, dpi):
        self.treeWidget().setDpi(dpi)

    def setRecursive(self, enabled):
        self.treeWidget().setRecursive(enabled)

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

    def setData(self, *args, **kwargs):
        self.treeWidget().setData(*args, **kwargs)

    def setItemData(self, id, data):
        self.treeWidget().setPathSettings(id, data)

    def setLocked(self, locked):
        self.treeWidget().setLocked(locked)

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

    def selectPaths(self, paths):
        self.treeWidget().selectPaths(paths)

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

    def clearSelection(self):
        self.treeWidget().clearSelection()
Exemplo n.º 27
0
class GlobalSignals(QtCore.QObject):
    """"""
    sliderChanged = QtCore.Signal(float)
Exemplo n.º 28
0
class FieldWidget(QtWidgets.QFrame):
    """The base widget for all field widgets.
    
    Examples:
        
        data = {
            'name': 'startFrame',
            'type': 'int'
            'value': 1,
        }
        
        fieldWidget = FieldWidget(data)
        
    """
    valueChanged = QtCore.Signal()

    DefaultLayout = "horizontal"

    def __init__(self, parent=None, data=None, formWidget=None):
        super(FieldWidget, self).__init__(parent)

        self._data = data or {}
        self._widget = None
        self._default = None
        self._required = None
        self._collapsed = False
        self._errorLabel = None
        self._menuButton = None
        self._actionResult = None
        self._formWidget = None

        if formWidget:
            self.setFormWidget(formWidget)

        self.setObjectName("fieldWidget")

        direction = self._data.get("layout", self.DefaultLayout)

        if direction == "vertical":
            layout = QtWidgets.QVBoxLayout(self)
        else:
            layout = QtWidgets.QHBoxLayout(self)

        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)

        self.setContentsMargins(0, 0, 0, 0)

        self._label = QtWidgets.QLabel(self)
        self._label.setObjectName('label')
        self._label.setSizePolicy(
            QtWidgets.QSizePolicy.Preferred,
            QtWidgets.QSizePolicy.Preferred,
        )

        layout.addWidget(self._label)

        self._layout2 = QtWidgets.QHBoxLayout(self)
        layout.addLayout(self._layout2)

        if direction == "vertical":
            self._label.setAlignment(QtCore.Qt.AlignLeft
                                     | QtCore.Qt.AlignVCenter)
        else:
            self._label.setAlignment(QtCore.Qt.AlignRight
                                     | QtCore.Qt.AlignVCenter)

        widget = self.createWidget()
        if widget:
            self.setWidget(widget)

    def name(self):
        """
        Get the name of field widget.

        :rtype: str
        """
        return self.data()["name"]

    def defaultData(self):
        """
        This is the default data used by the schema.

        :rtype: dict
        """
        return {}

    def setFormWidget(self, formWidget):
        """
        Set the form widget which contains the field widget.

        :type formWidget: studiolibrary.widgets.formwidget.FormWidget
        """
        self._formWidget = formWidget

    def formWidget(self):
        """
        Get the form widget the contains the field widget.

        :return: studiolibrary.widgets.formwidget.FormWidget
        """
        return self._formWidget

    def setCollapsed(self, collapsed):
        """
        Set the field widget collapsed used by the GroupFieldWidget.

        :return: studiolibrary.widgets.formwidget.FormWidget
        """
        self._collapsed = collapsed

    def isCollapsed(self):
        """
        Get the collapsed state of the field widget.

        :return: studiolibrary.widgets.formwidget.FormWidget
        """
        return self._collapsed

    def createWidget(self):
        """
        Create the widget to be used by the field.

        :rtype: QtWidgets.Widget or None
        """
        return None

    def label(self):
        """
        Get the label widget.
        
        :rtype: QtWidgets.QLabel 
        """
        return self._label

    def state(self):
        """
        Get the current state of the data.
        
        :rtype: dict
        """
        return {"name": self._data["name"], "value": self.value()}

    def data(self):
        """
        Get the data for the widget.
        
        :rtype: dict 
        """
        return self._data

    def title(self):
        """
        Get the title to be displayed for the field.
        
        :rtype: str 
        """
        data = self.data()

        title = data.get("title")
        if title is None:
            title = data.get("name", "")
            title = toTitle(title)

        if self.isRequired():
            title += '*'

        return title

    def setData(self, data):
        """
        Set the current state of the field widget using a dictionary.
        
        :type data: dict
        """
        self.blockSignals(True)

        items = data.get('items')
        if items is not None:
            self.setItems(items)

        value = data.get('value')
        default = data.get('default')

        # Must set the default before value
        if default is not None:
            self.setDefault(default)
        elif value is not None:
            self.setDefault(value)

        if value is not None or (value and value != self.value()):
            try:
                self.setValue(value)
            except TypeError as error:
                logger.exception(error)

        enabled = data.get('enabled')
        if enabled is not None:
            self.setEnabled(enabled)
            self._label.setEnabled(enabled)

        hidden = data.get('hidden')
        if hidden is not None:
            self.setHidden(hidden)

        visible = data.get('visible')
        if visible is not None and not self.isCollapsed():
            self.setVisible(visible)

        required = data.get('required')
        if required is not None:
            self.setRequired(required)

        error = data.get('error')
        if error is not None:
            self.setError(error)

        value = data.get('errorVisible')
        if value is not None:
            self.setErrorVisible(value)

        toolTip = data.get('toolTip')
        if toolTip is not None:
            self.setToolTip(toolTip)
            self.setStatusTip(toolTip)

        placeholder = data.get("placeholder")
        if placeholder is not None:
            self.setPlaceholder(placeholder)

        style = data.get("style")
        if style is not None:
            self.setStyleSheet(style)

        title = self.title() or ""
        self.setText(title)

        label = data.get('label')
        if label is not None:

            text = label.get("name")
            if text is not None:
                self.setText(text)

            visible = label.get('visible')
            if visible is not None:
                self.label().setVisible(visible)

        # Menu Items
        actions = data.get('actions')
        if actions is not None:
            self._menuButton.setVisible(True)

        # Menu Button
        menu = data.get('menu')
        if menu is not None:
            text = menu.get("name")
            if text is not None:
                self._menuButton.setText(text)

            visible = menu.get("visible", True)
            self._menuButton.setVisible(visible)

        self._data.update(data)

        self.refresh()

        self.blockSignals(False)

    def setPlaceholder(self, placeholder):
        """
        Set the placeholder text to be displayed for the widget.

        :type placeholder: str
        :raises: NotImplementedError
        """
        NotImplementedError(
            'The method "setPlaceholder" needs to be implemented')

    def hasError(self):
        """
        Check if the field contains any errors.

        :rtype: bool
        """
        return bool(self.data().get("error"))

    def setErrorVisible(self, visible):
        """
        Set the error message visibility.

        :type visible: bool
        """
        self._data["errorVisible"] = visible
        self.refreshError()

    def setError(self, message):
        """
        Set the error message to be displayed for the field widget.
        
        :type message: str
        """
        self._data["error"] = message
        self.refreshError()

    def refreshError(self):
        """Refresh the error message with the current data."""
        error = self.data().get("error")

        if self.hasError() and self.data().get("errorVisible", False):
            self._errorLabel.setText(error)
            self._errorLabel.setHidden(False)
            self.setToolTip(error)
        else:
            self._errorLabel.setText("")
            self._errorLabel.setHidden(True)
            self.setToolTip(self.data().get('toolTip'))

        self.refresh()

    def setText(self, text):
        """
        Set the label text for the field widget.
        
        :type text: str 
        """
        self._label.setText(text)

    def setValue(self, value):
        """
        Set the value of the field 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 field widget.
        
        :rtype: object
        """
        raise NotImplementedError('The method "value" needs to be implemented')

    def setItems(self, items):
        """
        Set the items for the field widget.
        
        :type items: list[str]
        """
        raise NotImplementedError(
            'The method "setItems" needs to be implemented')

    def reset(self):
        """Reset the field widget back to the defaults."""
        self.setState(self._data)

    def setRequired(self, required):
        """
        Set True if a value is required for this field.
        
        :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 field widget.
        
        :rtype: bool
        """
        return bool(self._required)

    def setDefault(self, default):
        """
        Set the default value for the field widget.
        
        :type default: object
        """
        self._default = default

    def default(self):
        """
        Get the default value for the field 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 field value.
        
        :type widget: QtWidgets.QWidget
        """
        widgetLayout = QtWidgets.QHBoxLayout(self)
        widgetLayout.setContentsMargins(0, 0, 0, 0)
        widgetLayout.setSpacing(0)

        self._widget = widget
        self._widget.setParent(self)
        self._widget.setObjectName('widget')
        self._widget.setSizePolicy(
            QtWidgets.QSizePolicy.Expanding,
            QtWidgets.QSizePolicy.Preferred,
        )

        self._menuButton = QtWidgets.QPushButton("...")
        self._menuButton.setHidden(True)
        self._menuButton.setObjectName("menuButton")
        self._menuButton.clicked.connect(self._menuCallback)
        self._menuButton.setSizePolicy(
            QtWidgets.QSizePolicy.Preferred,
            QtWidgets.QSizePolicy.Expanding,
        )

        widgetLayout.addWidget(self._widget)
        widgetLayout.addWidget(self._menuButton)

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

        self._errorLabel = QtWidgets.QLabel(self)
        self._errorLabel.setHidden(True)
        self._errorLabel.setObjectName("errorLabel")
        self._errorLabel.setSizePolicy(
            QtWidgets.QSizePolicy.Expanding,
            QtWidgets.QSizePolicy.Preferred,
        )

        layout.addLayout(widgetLayout)
        layout.addWidget(self._errorLabel)

        self._layout2.addLayout(layout)

    def _menuCallback(self):
        callback = self.data().get("menu", {}).get("callback", self.showMenu)
        callback()

    def _actionCallback(self, callback):
        """
        Wrap schema callback to get the return value.
        
        :type callback: func 
        """
        self._actionResult = callback()

    def showMenu(self):
        """Show the menu using the actions from the data."""
        menu = QtWidgets.QMenu(self)
        actions = self.data().get("actions", [])

        for action in actions:

            name = action.get("name", "No name found")
            enabled = action.get("enabled", True)
            callback = action.get("callback")

            func = functools.partial(self._actionCallback, callback)

            action = menu.addAction(name)
            action.setEnabled(enabled)
            action.triggered.connect(func)

        point = QtGui.QCursor.pos()
        point.setX(point.x() + 3)
        point.setY(point.y() + 3)

        # Reset the action results
        self._actionResult = None

        menu.exec_(point)

        if self._actionResult is not None:
            self.setValue(self._actionResult)

    def widget(self, ):
        """
        Get the widget used to set and get the field value.
        
        :rtype: QtWidgets.QWidget
        """
        return self._widget

    def refresh(self):
        """Refresh the style properties."""
        direction = self._data.get("layout", self.DefaultLayout)

        self.setProperty("layout", direction)
        self.setProperty('default', self.isDefault())

        if self.data().get("errorVisible", False):
            self.setProperty('error', self.hasError())

        self.setStyleSheet(self.styleSheet())
Exemplo n.º 29
0
class BaseItemSignals(QtCore.QObject):
    """"""
    loadValueChanged = QtCore.Signal(object, object)
Exemplo n.º 30
0
class WorkerSignals(QtCore.QObject):
    triggered = QtCore.Signal(object)