示例#1
0
    def sizeHint(self, option, index):
        """
        :parameters:
            option :QStyleOptionViewItem
                drawing parameters
            index : QModelIndex
                index of item
        """
        # TODO: speed this up, by caching more?
        widget = self._view.indexWidget(index)
        if widget:
            hint = widget.sizeHint()
            gridSize = 1 if self._view.showGrid() else 0
            return QtCore.QSize(hint.width(), hint.height() + gridSize)

        hint = Delegate.sizeHint(self, option, index)

        if self._view.showGrid():
            if self.MARGIN is None:
                self.MARGIN = QtGui.QApplication.style().pixelMetric(
                    QtGui.QStyle.PM_HeaderMargin, None)
            margin = self.MARGIN
            minimumHeight = max(QtGui.QApplication.globalStrut().height(),
                                option.fontMetrics.height() + margin)
            return QtCore.QSize(hint.width(), max(hint.height(),
                                                  minimumHeight))

        return hint
示例#2
0
 def setInputValue(cls, widget, value):
     value, ok = cls.resolveValue(value)
     if ok:
         widget.setDateTime(
             QtCore.QDateTime(
                 QtCore.QDate(value.year, value.month, value.day),
                 QtCore.QTime(value.hour, value.minute, value.second)))
     return ok
示例#3
0
    def selectAll(self):
        """
        """
        if not isinstance(self.selectionModel(), TreeSelectionModel):
            return QtGui.QTreeView.selectAll(self)
        # store parameters
        selectionModel = self.selectionModel()
        model = self.model()
        getIndex = model.index
        columnCount = model.columnCount(QtCore.QModelIndex())
        selection = QtGui.QItemSelection()
        propagate = selectionModel.propagateSelection()
        oldSelection = selectionModel.selection()

        def selectChildRange(parentIndex):
            rowCount = model.rowCount(parentIndex)
            firstIndex = getIndex(0, 0, parentIndex)
            lastIndex = getIndex(rowCount - 1, columnCount - 1, parentIndex)
            selection.select(firstIndex, lastIndex)

        def recursiveSelect(parentIndex):
            selectChildRange(parentIndex)
            if propagate is True:  # if we skip this check it will always select child rows.
                for row in range(model.rowCount(parentIndex)):
                    index = getIndex(row, 0, parentIndex)
                    if index.isValid():
                        recursiveSelect(index)

        # prepare
        block = selectionModel.blockSignals(True)
        self.setUpdatesEnabled(False)
        if propagate is True:
            selectionModel.setPropagateSelection(False)
        # do selection
        if self.selectionMode() == QtGui.QAbstractItemView.SingleSelection:
            selection.select(
                model.index(0, 0, QtCore.QModelIndex()),
                model.index(0, columnCount - 1, QtCore.QModelIndex()))
        else:
            recursiveSelect(QtCore.QModelIndex())
        selectionModel.select(selection, QtGui.QItemSelectionModel.Select)
        # restore previous settings
        self.setUpdatesEnabled(True)
        selectionModel.setPropagateSelection(propagate)
        selectionModel.blockSignals(block)
        # refresh view
        QtGui.QApplication.processEvents()
        selectionModel.selectionChanged.emit(selection, oldSelection)
示例#4
0
文件: common.py 项目: mhamid3d/jinx
def createAction(parentWidget,
                 text=None,
                 slot=None,
                 shortcut=None,
                 shortcutContext=QtCore.Qt.WidgetShortcut,
                 icon=None,
                 menu=None,
                 tip=None,
                 checkable=False,
                 signal="triggered()"):
    action = QtGui.QAction(parentWidget)
    if text is not None:
        action.setText(text)
    if icon is not None:
        action.setIcon(icon)
    if menu is not None:
        action.setMenu(menu)
    if shortcut:
        if isinstance(shortcut, list):
            action.setShortcuts(shortcut)
        else:
            action.setShortcut(shortcut)
        action.setShortcutContext(shortcutContext)
    if tip is not None:
        action.setToolTip(tip)
        action.setStatusTip(tip)
    if slot is not None:
        QtCore.QObject.connect(action, QtCore.SIGNAL(signal), slot)
    action.setCheckable(checkable)
    return action
示例#5
0
文件: delegate.py 项目: mhamid3d/jinx
    def __imageLoaded(self, filePath, targetSize, image, extraArgs):
        """
        Repaint the rect containing the image

        :parameters:
            filePath : str
                path to image file
            targetSize : QtCore.QSize
                intended size of the image
            image : QtGui.QImage
                the image that has been loaded
            extraArgs : list
                Index of model item containing the image
                [ QtCore.QPersistandModelIndex ]
        """
        index = QtCore.QModelIndex(extraArgs[0])
        model = index.model()
        if model is None:
            return
        # On OSX 10.9 PyQt 4.10 replaces null QImages with QPyNullVariants when retreiving them from a model
        if not isinstance(image, QtGui.QImage):
            image = QtGui.QImage()
        model.setItemData(index, {
            common.ROLE_IMAGE: image,
            self.ROLE_SCALED_IMAGE: image
        })
        if image.isNull():
            return
        if index.isValid():
            rect = self._view.visualRect(index)
            if rect.isValid():
                self._view.viewport().repaint(rect)
示例#6
0
    def paint(self, painter, option, index):
        """
        """
        origRect = QtCore.QRect(option.rect)
        if self._view.showGrid():
            # remove 1 pixel from right and bottom edge of rect, to
            # make room for drawing grid
            option.rect.setRight(option.rect.right() - 1)
            option.rect.setBottom(option.rect.bottom() - 1)

        # paint the item
        Delegate.paint(self, painter, option, index)

        # draw the vertical grid lines (horizontal lines are painted
        # in TreeView.drawRow())
        if self._view.showGrid():
            painter.save()
            gridHint = QtGui.QApplication.style().styleHint(
                QtGui.QStyle.SH_Table_GridLineColor, option, self._view, None)
            # must ensure that the value is positive before
            # constructing a QColor from it
            # http://www.riverbankcomputing.com/pipermail/pyqt/2010-February/025893.html
            gridColor = QtGui.QColor.fromRgb(gridHint & 0xffffffff)
            painter.setPen(QtGui.QPen(gridColor, 0, QtCore.Qt.SolidLine))
            # paint the vertical line
            painter.drawLine(origRect.right(), origRect.top(),
                             origRect.right(), origRect.bottom())
            painter.restore()
示例#7
0
文件: delegate.py 项目: mhamid3d/jinx
    def __init__(self, view):
        """
        Initialiser

        :parameters:
            view : QtGui.QAbstractItemView
                The view that this delegate acts upon
        """
        super(Delegate, self).__init__(view)
        self._view = view
        self.__viewStyle = None
        self.__shadedSortColumn = False
        self.__imageCellAspectRatio = 16.0 / 9
        self.__imageLoader = ImageLoader()
        self.__imageLoader.imageLoaded.connect(self.__imageLoaded)
        # image cycling
        self.__cycleIndex = QtCore.QPersistentModelIndex(QtCore.QModelIndex())
        self.__imageCyclingEnabled = True
示例#8
0
 def applyExpandOnLoad(self):
     """
     """
     if self.__expandOnLoad:
         model = self.model()
         root = QtCore.QModelIndex()
         getIndex = lambda row: model.index(row, 0, root)
         rowCount = model.rowCount(root)
         for row in xrange(0, rowCount + 1):
             self._expandOneBranch(getIndex(row), self.__expandOnLoad)
示例#9
0
 def __init__(self, parent=None):
     self.__dragLogicalIdx = None
     super(TopHeaderView, self).__init__(QtCore.Qt.Horizontal, parent)
     self.__dragTimer = QtCore.QTimer()
     self.__dragTimer.setSingleShot(True)
     # use a slightly smaller font than the default and decrease margins
     self.setStyleSheet(
         "QHeaderView::section { margin: 4px 1px 4px 1px; } QHeaderView { font: 9pt; } "
     )
     # self.sectionClicked.connect( self._columnClicked )
     self.setMovable(True)
示例#10
0
文件: delegate.py 项目: mhamid3d/jinx
    def stopImageCycling(self):
        """
        Stop frame cycling on a cell at <index>

        :parameters:
            index : QtCore.QModelIndex
                the model index to stop frame cycling on
        """
        cycleIndex = QtCore.QModelIndex(self.__cycleIndex)
        indexWidget = self._view.indexWidget(cycleIndex)
        if indexWidget is not None:
            # index widget could be set to something different in subclasses, so we can't be sure
            movie = indexWidget.movie() if hasattr(indexWidget,
                                                   'movie') else None
            if movie is not None:
                movie.stop()
            indexWidget.close()
            self._view.setIndexWidget(cycleIndex, None)
            self.__cycleIndex = QtCore.QPersistentModelIndex(
                QtCore.QModelIndex())
示例#11
0
文件: delegate.py 项目: mhamid3d/jinx
    def startImageCycling(self, index):
        """
        Start frame cycling on a cell at <index>

        :parameters:
            index : QtCore.QModelIndex
                the model index to start frame cycling on
        """
        if self.__imageCyclingEnabled and index.isValid(
        ) and index != self.__cycleIndex:
            # stop cycling on old index
            self.stopImageCycling()
            newRect = self._thumbnailRect(index)
            if newRect.isValid():
                filename = str(index.data())
                if not common.imageFormatIsAnimatable(
                        filename) or not os.path.isfile(filename):
                    return
                scaledImage = index.data(role=self.ROLE_SCALED_IMAGE)
                # On OSX 10.9 PyQt 4.10 replaces null QImages with QPyNullVariants when retreiving them from a model
                if scaledImage is None or not isinstance(
                        scaledImage, QtGui.QImage) or scaledImage.isNull():
                    return
                # make the index widget ( QLabel displaying a QMovie )
                movieLabel = QtGui.QLabel()
                movie = QtGui.QMovie(filename, parent=movieLabel)
                movie.setCacheMode(QtGui.QMovie.CacheAll)
                # QMovie bug?, jumpToNextFrame() will only return False for static images after it has been called more than once
                movie.jumpToNextFrame()
                # if there is no frame 1, then it is a static image, so abort.
                # this must be done after it has been set and started playing
                # or the QMovie has no frame attributes
                if movie.jumpToNextFrame() is False:
                    self.stopImageCycling()
                    return
                # movieLabel.setFrameStyle( QtGui.QFrame.Box )
                movieLabel.setSizePolicy(QtGui.QSizePolicy.Fixed,
                                         QtGui.QSizePolicy.Fixed)
                newSize = scaledImage.size()
                movie.setScaledSize(newSize)
                movieLabel.setFixedSize(newSize)
                movieLabel.setMovie(movie)
                # start playing
                movie.start()
                self.__cycleIndex = QtCore.QPersistentModelIndex(index)
                # set the new index widget
                self._view.setIndexWidget(index, movieLabel)
                # move to center of cell
                movieLabel.move(
                    movieLabel.pos().x() + ((newRect.width() / 2) -
                                            (newSize.width() / 2)),
                    movieLabel.pos().y())
示例#12
0
    def __init__(self,
                 text=None,
                 icon=None,
                 tip=None,
                 shortcut=None,
                 shortcutContext=QtCore.Qt.WidgetShortcut,
                 menu=None,
                 checkable=False,
                 separator=False,
                 selectionBased=False,
                 signal="triggered()",
                 enabled=True,
                 isValidFn=None,
                 runFn=None,
                 parent=None):

        super(Action, self).__init__(parent)

        if text:
            self.setText(text)

        if icon:
            self.setIcon(icon)

        if tip:
            self.setToolTip(tip)
            self.setStatusTip(tip)

        if shortcut:
            if isinstance(shortcut, list):
                shortcuts = [QtGui.QKeySequence(key) for key in shortcut]
                self.setShortcuts(shortcuts)
            else:
                self.setShortcut(QtGui.QKeySequence(shortcut))
            self.setShortcutContext(shortcutContext)

        if menu:
            self.setMenu(menu)

        self.setCheckable(checkable)
        self.setEnabled(enabled)
        self.setSeparator(separator)

        self.__selectionBased = selectionBased
        self.__isValidFn = isValidFn
        self.__runFn = runFn

        self.connect(self, QtCore.SIGNAL(signal), self.run)

        self.__subActions = []
示例#13
0
文件: delegate.py 项目: mhamid3d/jinx
    def sizeHint(self, option, index):
        """
        Get the sizehint for a cell

        :parameters:
            option : QtGui.QStyleOption
                style information
            index : QtCore.QModelIndex
                index if the cell to draw
        :return:
            The preferred size
        :rtype:
            QtCore.QSize
        """
        # TODO: speed this up further by caching more?
        i_dataType = None
        i_model = index.model()
        if i_model:
            i_dataType = i_model.dataType(index)

        size = index.data(QtCore.Qt.SizeHintRole)
        if not size:
            option4 = QtGui.QStyleOptionViewItemV4(option)
            self.initStyleOption(option4,
                                 index,
                                 model=i_model,
                                 dataType=i_dataType)
            style = self._view.style()
            size = style.sizeFromContents(QtGui.QStyle.CT_ItemViewItem,
                                          option4, QtCore.QSize(), self._view)

        # if it is an image column and has data...
        if i_model and i_dataType == common.TYPE_IMAGE and index.data(
                QtCore.Qt.DisplayRole):
            imageHeight = int(
                self._view.topHeader().sectionSize(index.column()) /
                self.__imageCellAspectRatio
            )  # give the cell a 16/9 aspect ratio
            if imageHeight > size.height():
                size.setHeight(imageHeight)

        return size if isinstance(size, QtCore.QSize) else size.toSize()
示例#14
0
    def expandToSelected(self):
        """
        """
        model = self.model()
        propagateSelection = self.selectionModel().propagateSelection()

        # remove duplicates
        def removeIndexDuplicates(indexList):
            return filter(
                QtCore.QModelIndex.isValid,
                map(model.indexFromItem,
                    set(map(model.itemFromIndex, indexList))))

        # index has no parent
        def isTopLevel(index):
            return not index.parent().isValid()

        # get selected indexes
        indexes = removeIndexDuplicates(
            filter(QtCore.QModelIndex.isValid,
                   self.selectionModel().selectedIndexes()))
        # if no selection, expand one level
        if not indexes:
            self._expandOneBranch(model.index(0, 0, QtCore.QModelIndex()), 1)
            return
        block = self.blockSignals(True)
        # if the top level is selected, it's quicker to just expand all
        if propagateSelection is True and filter(isTopLevel, indexes):
            self.__expandAllIsExpanded = False
            self.expandAll()
            self.blockSignals(block)
            return
        # expand back up the hierarchy to reveal the selected index
        for index in indexes:
            while index.isValid() and not self.isExpanded(index):
                self.setExpanded(index, True)
                index = index.parent()
        self.blockSignals(block)
示例#15
0
 def __init__(self, parent=None):
     """
     """
     QtGui.QTreeView.__init__(self, parent)
     AbstractView.__init__(self)
     self.addMenuHandler(TreeMenuHandler(self, parent=self))
     self.addMenuHandler(StandardMenuHandler(self, parent=self))
     # use custom delegate
     self.setItemDelegate(TreeDelegate(self))
     # properties
     self.__refreshOnNextCtrlRelease = False
     self.__showGrid = False
     # timer for auto-expanding branches on drag/drop
     self.__openTimer = QtCore.QTimer(self)
     self.__openTimer.timeout.connect(self.__doAutoExpand)
     # set default values for properties
     self.setAutoExpandDelay(self.AUTO_EXPAND_DELAY)
     # persistent expansion state using model's uniqueId
     self.__expandOnLoad = 0
     self.__expandAllIsExpanded = False
     # cache of the number of things in model, -1 means we haven't
     # set this yet, see setModel
     self.modelrows = -1
示例#16
0
class ModelItemSorter(QtCore.QObject):
    # constants
    DEFAULT_SORT_DIRECTION = QtCore.Qt.AscendingOrder
    _OPPOSITE_SORT_DIRECTION = int(not DEFAULT_SORT_DIRECTION)
    _SORT_DIRECTION_MAP = {
        wizqt.TYPE_STRING_SINGLELINE: DEFAULT_SORT_DIRECTION,
        wizqt.TYPE_STRING_MULTILINE: DEFAULT_SORT_DIRECTION,
        wizqt.TYPE_INTEGER: _OPPOSITE_SORT_DIRECTION,
        wizqt.TYPE_UNSIGNED_INTEGER: _OPPOSITE_SORT_DIRECTION,
        wizqt.TYPE_FLOAT: _OPPOSITE_SORT_DIRECTION,
        wizqt.TYPE_BOOLEAN: DEFAULT_SORT_DIRECTION,
        wizqt.TYPE_DATE: _OPPOSITE_SORT_DIRECTION,
        wizqt.TYPE_DATE_TIME: _OPPOSITE_SORT_DIRECTION,
        wizqt.TYPE_FILE_PATH: DEFAULT_SORT_DIRECTION,
        wizqt.TYPE_DN_PATH: DEFAULT_SORT_DIRECTION,
        wizqt.TYPE_DN_RANGE: DEFAULT_SORT_DIRECTION,
        wizqt.TYPE_RESOLUTION: _OPPOSITE_SORT_DIRECTION,
        wizqt.TYPE_IMAGE: DEFAULT_SORT_DIRECTION,
        wizqt.TYPE_ENUM: DEFAULT_SORT_DIRECTION
    }
    MAX_SORT_COLUMNS = 3

    # signals
    sortingChanged = QtCore.Signal()

    # properties
    sortColumns = property(lambda self: self._sortColumns[:])
    sortDirections = property(lambda self: self._sortDirections[:])

    def __init__(self, model):
        super(ModelItemSorter, self).__init__(model)
        self._model = model
        self._sortColumns = []
        self._sortDirections = []

    def setSortColumns(self, columns, directions):
        """
        Update the list of columns/directions for multi-sorting
        """
        # copy the current column/directions for comparison
        oldColumns = self._sortColumns[:]
        oldDirections = self._sortDirections[:]

        self._sortColumns = []
        self._sortDirections = []
        for (column, direction) in zip(columns, directions):
            self.__addSortColumn(column, direction)

        if self._sortColumns != oldColumns or self._sortDirections != oldDirections:
            self.sortingChanged.emit()

    def addSortColumn(self, column, direction=None):
        """
        Add one more column/direction to the multi-sort list
        """
        # copy the current column/directions for comparison
        oldColumns = self._sortColumns[:]
        oldDirections = self._sortDirections[:]

        self.__addSortColumn(column, direction)

        if self._sortColumns != oldColumns or self._sortDirections != oldDirections:
            self.sortingChanged.emit()

    def __addSortColumn(self, column, direction):
        # skip invalid and unsortable columns
        if column < 0 or not self._model.headerData(
                column, QtCore.Qt.Horizontal, wizqt.ROLE_IS_SORTABLE):
            return

        # check if this column is already in the list
        try:
            idx = self._sortColumns.index(column)
        except ValueError:
            pass
        else:
            # remove the column/direction from the list
            del self._sortColumns[idx]
            del self._sortDirections[idx]

        # add the column/direction to the list
        self._sortColumns.append(column)
        self._sortDirections.append(direction)

        # limit the number of columns that can be sorted on
        if len(self._sortColumns) > self.MAX_SORT_COLUMNS:
            del self._sortColumns[:-self.MAX_SORT_COLUMNS]
            del self._sortDirections[:-self.MAX_SORT_COLUMNS]

    def defaultSortDirection(self, column):
        dataType = self._model.headerData(column, QtCore.Qt.Horizontal,
                                          wizqt.ROLE_TYPE)
        if dataType not in wizqt.TYPES:
            dataType = wizqt.TYPE_DEFAULT
        return self._SORT_DIRECTION_MAP.get(dataType,
                                            self.DEFAULT_SORT_DIRECTION)

    def sortItems(self, itemList):
        """
        Sort a list of ModelItems in-place. All child items are recursively sorted as well.
        The itemList argument is re-ordered based on the current multisort column/direction settings in the sorter.
        When sorting, ModelItems are compared based on their dataType.
        """
        if not (self._sortColumns and self._sortDirections):
            return

        name = (self._model.dataSource or self._model).__class__.__name__
        _LOGGER.log(
            dnlogging.VERBOSE, "Sorting %s items on columns %s..." %
            (name, zip(self._sortColumns, self._sortDirections)))
        start = time.time()

        # sort the list
        itemList.sort(cmp=self.__compareItems)

        # recursively sort each child
        for item in itemList:
            item.sortTree(cmp=self.__compareItems)

        end = time.time()
        _LOGGER.debug("%f seconds" % (end - start))

    def __compareItems(self, item1, item2):
        """
        Compare the value of 2 ModelItems based on their dataType.
        """
        comparison = 0
        for (column, direction) in zip(self._sortColumns,
                                       self._sortDirections):

            # first compare values by sort role
            sortVal1 = item1.data(column, wizqt.ROLE_SORT)
            sortVal2 = item2.data(column, wizqt.ROLE_SORT)
            if sortVal1 or sortVal2:
                # do case insensitive comparison of strings
                if isinstance(sortVal1, basestring):
                    sortVal1 = sortVal1.lower()
                if isinstance(sortVal2, basestring):
                    sortVal2 = sortVal2.lower()
                comparison = cmp(sortVal1, sortVal2)
                if comparison != 0:
                    return comparison if direction == QtCore.Qt.AscendingOrder else -comparison

            # next check the display role
            displayVal1 = item1.data(column)
            displayVal2 = item2.data(column)

            # compare items using the typehandler for the data type set on this column
            dataType = self._model.headerData(column, QtCore.Qt.Horizontal,
                                              wizqt.ROLE_TYPE)
            if dataType not in wizqt.TYPES:
                dataType = wizqt.TYPE_DEFAULT

            comparison = getTypeHandler(dataType).compare(
                displayVal1, displayVal2)
            if comparison != 0:
                return comparison if direction == QtCore.Qt.AscendingOrder else -comparison

        return comparison
示例#17
0
文件: delegate.py 项目: mhamid3d/jinx
    def _paintImage(self, painter, option, index):
        """
        Draw an image in the cell

        :parameters:
            painter : QtGui.QPainter
                painter to draw the image with
            option : QtGui.QStyleOption
                style information
            index : QtCore.QModelIndex
                index if the cell to draw
        """
        # using old method for now
        imageFile = index.data()
        if not imageFile:
            return
        model = index.model()
        # only load an image where one hasn't already been loaded
        origImage = image = index.data(role=common.ROLE_IMAGE)
        origScaledImage = scaledImage = index.data(role=self.ROLE_SCALED_IMAGE)
        origFailedImage = failedImage = index.data(role=self.ROLE_FAILED_IMAGE)
        # On OSX 10.9 PyQt 4.10 replaces null QImages with QPyNullVariants when retreiving them from a model
        if image is not None and not isinstance(image, QtGui.QImage):
            image = QtGui.QImage()
        if scaledImage is not None and not isinstance(scaledImage,
                                                      QtGui.QImage):
            scaledImage = QtGui.QImage()
        if not failedImage:
            if image is None:
                # if it's a null image or a loading image, load it.
                scaledImage = image = self.__imageLoader.loadImage(
                    imageFile, None, QtCore.QPersistentModelIndex(index))
            # if it's not a null image or a loading image...
            if not image.isNull():
                if scaledImage is None:
                    scaledImage = image
                scaledImageSize = scaledImage.size()
                targetSize = option.rect.size().boundedTo(image.size())
                targetSize.setHeight(
                    int(targetSize.width() / self.__imageCellAspectRatio))
                if scaledImageSize.width() != targetSize.width(
                ) and scaledImageSize.height() != targetSize.height():
                    scaledImage = image.scaled(targetSize,
                                               QtCore.Qt.KeepAspectRatio,
                                               QtCore.Qt.SmoothTransformation)
            else:
                failedImage = True
            # if <image> is null, set scaled image to null as well
            # this would make null images show up as failed, we could maybe have a more neutral icon?
            # scaledImage = self.__imageLoader._imageMissingLrg
            # update model data
            dataUpdateMap = {}
            if origImage is not image:
                dataUpdateMap[common.ROLE_IMAGE] = image
            if origScaledImage is not scaledImage:
                dataUpdateMap[self.ROLE_SCALED_IMAGE] = scaledImage
            if origFailedImage != failedImage:
                dataUpdateMap[self.ROLE_FAILED_IMAGE] = failedImage
            if dataUpdateMap:
                model.setItemData(index, dataUpdateMap)
        # center the image in the cell
        targetRect = scaledImage.rect()
        targetRect.moveCenter(option.rect.center())
        # draw the image
        painter.save()
        painter.drawImage(targetRect, scaledImage)
        # Image cycling icon ( test )
        # if common.imageFormatIsAnimatable( imageFile ) and os.path.isfile( imageFile ):
        # 	animIcon = QtGui.QImage( common.ICON_CLAPBOARD_SML )
        # 	animIconSize = animIcon.size()
        # 	brPoint = targetRect.bottomRight() - QtCore.QPoint( animIconSize.width(), animIconSize.height() )
        # 	painter.drawImage( brPoint, animIcon ) # TODO proper icon
        painter.restore()
示例#18
0
class TreeView(AbstractView, QtGui.QTreeView):
    """The TreeView view shows a list of model item rows, which can be nested under each other."""

    AUTO_EXPAND_DELAY = 250  # milliseconds
    itemDoubleClicked = QtCore.Signal(list)
    allExpanded = QtCore.Signal()
    allCollapsed = QtCore.Signal()
    # MENU_HANDLER_CLASS = TreeMenuHandler
    expandOnLoad = property(lambda self: self.__expandOnLoad)

    def __init__(self, parent=None):
        """
        """
        QtGui.QTreeView.__init__(self, parent)
        AbstractView.__init__(self)
        self.addMenuHandler(TreeMenuHandler(self, parent=self))
        self.addMenuHandler(StandardMenuHandler(self, parent=self))
        # use custom delegate
        self.setItemDelegate(TreeDelegate(self))
        # properties
        self.__refreshOnNextCtrlRelease = False
        self.__showGrid = False
        # timer for auto-expanding branches on drag/drop
        self.__openTimer = QtCore.QTimer(self)
        self.__openTimer.timeout.connect(self.__doAutoExpand)
        # set default values for properties
        self.setAutoExpandDelay(self.AUTO_EXPAND_DELAY)
        # persistent expansion state using model's uniqueId
        self.__expandOnLoad = 0
        self.__expandAllIsExpanded = False
        # cache of the number of things in model, -1 means we haven't
        # set this yet, see setModel
        self.modelrows = -1

    def setModel(self, model):
        """
        """
        # disconnect any existing model
        oldModel = self.model()
        if oldModel:
            oldModel.modelReset.disconnect(self.applyExpandOnLoad)
        QtGui.QTreeView.setModel(self, model)
        AbstractView.setModel(self, model)
        # tree-specific model setup
        newModel = self.model()
        # update the number of rows we think the model has because the
        # model changed, the can be updated in expandAll() if the model
        # has changed when that is called
        self.modelrows = newModel.num_items()
        newModel.modelReset.connect(self.applyExpandOnLoad)
        # set custom selection model
        self.setSelectionModel(TreeSelectionModel(newModel, self))
        # load the model data
        self.doModelRefresh()

    def selectAll(self):
        """
        """
        if not isinstance(self.selectionModel(), TreeSelectionModel):
            return QtGui.QTreeView.selectAll(self)
        # store parameters
        selectionModel = self.selectionModel()
        model = self.model()
        getIndex = model.index
        columnCount = model.columnCount(QtCore.QModelIndex())
        selection = QtGui.QItemSelection()
        propagate = selectionModel.propagateSelection()
        oldSelection = selectionModel.selection()

        def selectChildRange(parentIndex):
            rowCount = model.rowCount(parentIndex)
            firstIndex = getIndex(0, 0, parentIndex)
            lastIndex = getIndex(rowCount - 1, columnCount - 1, parentIndex)
            selection.select(firstIndex, lastIndex)

        def recursiveSelect(parentIndex):
            selectChildRange(parentIndex)
            if propagate is True:  # if we skip this check it will always select child rows.
                for row in range(model.rowCount(parentIndex)):
                    index = getIndex(row, 0, parentIndex)
                    if index.isValid():
                        recursiveSelect(index)

        # prepare
        block = selectionModel.blockSignals(True)
        self.setUpdatesEnabled(False)
        if propagate is True:
            selectionModel.setPropagateSelection(False)
        # do selection
        if self.selectionMode() == QtGui.QAbstractItemView.SingleSelection:
            selection.select(
                model.index(0, 0, QtCore.QModelIndex()),
                model.index(0, columnCount - 1, QtCore.QModelIndex()))
        else:
            recursiveSelect(QtCore.QModelIndex())
        selectionModel.select(selection, QtGui.QItemSelectionModel.Select)
        # restore previous settings
        self.setUpdatesEnabled(True)
        selectionModel.setPropagateSelection(propagate)
        selectionModel.blockSignals(block)
        # refresh view
        QtGui.QApplication.processEvents()
        selectionModel.selectionChanged.emit(selection, oldSelection)

    ###########################################################################
    # PERSISTENT SETTINGS
    ###########################################################################

    def getState(self):
        """
        """
        state = AbstractView.getState(self)
        if state is None:
            return None
        state['expandOnLoad'] = self.__expandOnLoad
        if not self.__expandOnLoad:
            # expanded indexes
            expandedIds = []
            # the expanded index list must be in order so that when we restore the expanded state,
            # parents are expanded before their children
            indexQueue = [self.rootIndex()]
            while indexQueue:
                parentIndex = indexQueue.pop(0)
                # iterate over all children of this index
                numChildren = self.model().rowCount(parentIndex)
                for i in xrange(numChildren):
                    childIndex = self.model().index(i, 0, parentIndex)
                    if childIndex.isValid() and self.isExpanded(childIndex):
                        uniqueId = self.model().uniqueIdFromIndex(childIndex)
                        if uniqueId is not None:
                            expandedIds.append(uniqueId)
                            indexQueue.append(childIndex)
            state["expanded"] = expandedIds
        return state

    def restoreState(self):
        blockSignals = self.selectionModel().blockSignals
        block = blockSignals(True)
        AbstractView.restoreState(self)
        blockSignals(block)
        self.applyExpandOnLoad()

    def applyState(self, state):
        """
        """
        # expand indexes before calling superclass to ensure selected items are properly scrolled to
        expandOnLoad = state.get('expandOnLoad', 0)
        if type(expandOnLoad) == bool:
            expandOnLoad = -1 if expandOnLoad else 0
        self.setExpandOnLoad(expandOnLoad)
        self.applyExpandOnLoad()
        state = state or {}
        if "expanded" in state and isinstance(state["expanded"], list):
            indexFromUniqueId = self.model().indexFromUniqueId
            blocked = self.blockSignals(True)
            for uniqueId in state["expanded"]:
                index = indexFromUniqueId(uniqueId)
                if index.isValid() and not self.isExpanded(index):
                    # IMPORTANT: must layout the view before calling setExpanded() for lazy-loaded
                    # trees, otherwise expand() will not call fetchMore() and child indexes will not be loaded
                    self.executeDelayedItemsLayout()
                    self.setExpanded(index, True)
            self.blockSignals(blocked)
        AbstractView.applyState(self, state)
        if self.selectionModel() and hasattr(self.selectionModel(),
                                             "requestRefresh"):
            self.selectionModel().requestRefresh()

    ###########################################################################
    # GRID METHODS
    ###########################################################################
    def showGrid(self):
        """
        """
        return self.__showGrid

    def setShowGrid(self, show):
        """
        """
        if show != self.__showGrid:
            self.__showGrid = show
            self.viewport().update()

    def visualRect(self, index):
        """
        """
        rect = QtGui.QTreeView.visualRect(self, index)
        if self.__showGrid and rect.isValid():
            # remove 1 pixel from right and bottom edge of rect, to account for grid lines
            rect.setRight(rect.right() - 1)
            rect.setBottom(rect.bottom() - 1)
        return rect

    def drawRow(self, painter, option, index):
        """
        """
        # draw the partially selected rows a lighter colour
        selectionState = index.model().itemFromIndex(index).data(
            role=common.ROLE_SELECTION_STATE)
        if selectionState == 1:
            palette = self.palette()
            selectionColor = palette.color(palette.Highlight)
            selectionColor.setAlpha(127)
            painter.save()
            painter.fillRect(option.rect, selectionColor)
            painter.restore()

        QtGui.QTreeView.drawRow(self, painter, option, index)

        # draw the grid line
        if self.__showGrid:
            painter.save()
            gridHint = self.style().styleHint(
                QtGui.QStyle.SH_Table_GridLineColor, self.viewOptions(), self,
                None)
            # must ensure that the value is positive before constructing a QColor from it
            # http://www.riverbankcomputing.com/pipermail/pyqt/2010-February/025893.html
            gridColor = QtGui.QColor.fromRgb(gridHint & 0xffffffff)
            painter.setPen(QtGui.QPen(gridColor, 0, QtCore.Qt.SolidLine))
            # paint the horizontal line
            painter.drawLine(option.rect.left(), option.rect.bottom(),
                             option.rect.right(), option.rect.bottom())
            painter.restore()

    def drawBranches(self, painter, rect, index):
        """
        """

        QtGui.QTreeView.drawBranches(self, painter, rect, index)
        # draw the grid line
        if self.__showGrid:
            painter.save()
            gridHint = QtGui.QApplication.style().styleHint(
                QtGui.QStyle.SH_Table_GridLineColor, self.viewOptions(), self,
                None)
            # must ensure that the value is positive before
            # constructing a QColor from it
            # http://www.riverbankcomputing.com/pipermail/pyqt/2010-February/025893.html
            gridColor = QtGui.QColor.fromRgb(gridHint & 0xffffffff)
            painter.setPen(QtGui.QPen(gridColor, 0, QtCore.Qt.SolidLine))
            # paint the horizontal line
            painter.drawLine(rect.left(), rect.bottom(), rect.right(),
                             rect.bottom())
            painter.restore()

    def sizeHintForColumn(self, column):
        """
        """
        return QtGui.QTreeView.sizeHintForColumn(
            self, column) + (1 if self.__showGrid else 0)

    def scrollContentsBy(self, dx, dy):
        """
        """
        QtGui.QTreeView.scrollContentsBy(self, dx, dy)
        self._updateImageCycler()

    ###########################################################################
    # EVENTS
    ###########################################################################
    def paintEvent(self, event):
        """
        """
        QtGui.QTreeView.paintEvent(self, event)
        AbstractView.paintEvent(self, event)

    def viewportEvent(self, event):
        """
        """
        AbstractView.viewportEvent(self, event)
        return QtGui.QTreeView.viewportEvent(self, event)

    def keyReleaseEvent(self, event):
        # if we've postponed the refresh during a multiselect, and ctrl has now been released,
        # emit the selection changed signal.
        if event.key(
        ) == QtCore.Qt.Key_Control and self.__refreshOnNextCtrlRelease:
            self.selectionModel().requestRefresh()
            self.__refreshOnNextCtrlRelease = False
        QtGui.QTreeView.keyReleaseEvent(self, event)

    def keyPressEvent(self, event):
        """
        """
        def getHashableShortcut(action):
            # QKeySequence not hashable in pyside
            shortcut = action.shortcut()
            if isinstance(shortcut, QtGui.QKeySequence):
                return shortcut.toString()
            return shortcut

        # handle shortcut keys of actions
        if event.key():
            QtGui.QTreeView.keyPressEvent(self, event)
            if event.key() in (QtCore.Qt.Key_Up, QtCore.Qt.Key_Down):
                if self.selectionModel() and hasattr(self.selectionModel(),
                                                     "requestRefresh"):
                    self.selectionModel().requestRefresh()
                self.setFocus(QtCore.Qt.OtherFocusReason)
        else:
            QtGui.QTreeView.keyPressEvent(self, event)

    def mouseReleaseEvent(self, event):
        """
        Reimplemented for parent-only selection on alt+click when selection is propagated to children.
        Ctrl+alt+click adds the single item to the selection when selection is propagated to children.

        :parameters:
            event : QMouseEvent
        """
        modifiers = event.modifiers()
        button = event.button()
        if button == QtCore.Qt.LeftButton:
            selectionModel = self.selectionModel()
            if modifiers & (QtCore.Qt.AltModifier | QtCore.Qt.ControlModifier):
                # only ctrl is pressed - default behaviour
                if modifiers == QtCore.Qt.ControlModifier:
                    if self.selectionModel() and hasattr(
                            self.selectionModel(), "requestRefresh"):
                        self.__refreshOnNextCtrlRelease = True
                        QtGui.QTreeView.mouseReleaseEvent(self, event)
                        return
                else:
                    # store current propagation state
                    propagationState = selectionModel.propagateSelection()
                    if propagationState:
                        # set propagation to false so that only the item clicked is selected
                        selectionModel.setPropagateSelection(False)
                        # both alt and ctrl are pressed - add single item to selection
                        if modifiers == (QtCore.Qt.AltModifier
                                         | QtCore.Qt.ControlModifier):
                            selectionModel.setCurrentIndex(
                                self.indexAt(event.pos()),
                                QtGui.QItemSelectionModel.Select)
                        # only alt is pressed - clear selection and select single item
                        elif modifiers == QtCore.Qt.AltModifier:
                            selectionModel.setCurrentIndex(
                                self.indexAt(event.pos()),
                                QtGui.QItemSelectionModel.ClearAndSelect)
                        # restore propagation state
                        selectionModel.setPropagateSelection(propagationState)
        QtGui.QTreeView.mouseReleaseEvent(self, event)
        if self.selectionModel() and hasattr(self.selectionModel(),
                                             "requestRefresh"):
            self.selectionModel().requestRefresh()

    def mousePressEvent(self, event):
        """
        Reimplement QAbstractItemView.mousePressEvent
        """
        # Note: Uncomment this and the section at the end of this
        #       function if you want profile the selection in
        #       twig/stalk etc widgets. Do Not leave this uncomented
        #       in production.
        #
        # import time
        # import cProfile
        # prof = cProfile.Profile()
        # prof.enable()
        # statsfile = 'selection.pstats'
        # start = time.time()

        QtGui.QTreeView.mousePressEvent(self, event)

    # Note: Uncomment this and the section at the start of this
    #       function if you want profile the selection in
    #       twig/stalk etc widgets. Do Not leave this uncomented
    #       in production.
    #
    # print( 'wzq::m::tv::mousePressEvent: {0:11.8f} ({1})'.format( time.time()-start, statsfile ) )
    # prof.disable()
    # prof.dump_stats(statsfile)

    def mouseDoubleClickEvent(self, event):
        """
        Mouse double click event
        """
        if self.model():
            indexes = self.currentSelection(minimal=False)
            items = [self.model().itemFromIndex(i) for i in indexes]
            self.itemDoubleClicked.emit(items)
        super(TreeView, self).mouseDoubleClickEvent(event)

    # 	def _canExpand( self ):
    # 		"""
    # 		"""
    # 		# determine if the selection can be expanded
    # 		indexes = self.selectionModel().selectedIndexes()
    # 		if not indexes:
    # 			return False
    # 		for idx in indexes:
    # 			if self.model().hasChildren( idx ) and not self.isExpanded( idx ):
    # 				return True
    # 		return False
    #
    # 	def _canCollapse( self ):
    # 		"""
    # 		"""
    # 		# determine if the selection can be collapsed
    # 		indexes = self.selectionModel().selectedIndexes()
    # 		if not indexes:
    # 			return False
    # 		for idx in indexes:
    # 			if self.model().hasChildren( idx ) and self.isExpanded( idx ):
    # 				return True
    # 		return False

    ###########################################################################
    # EXPAND/COLLAPSE METHODS
    ###########################################################################

    def _toggleExpanded(self):
        selection = self.currentSelection()
        if not selection:
            return
        # make them all do the same thing, not some expand and others collapse
        expanded = not self.isExpanded(selection[0])
        for index in selection:
            self.setExpanded(index, expanded)
            if (not expanded):
                self.__expandAllIsExpanded = False

    def _setSelectedExpanded(self, expanded):
        """
        """
        for index in self.currentSelection():
            self.setExpanded(index, expanded)
            if (not expanded):
                self.__expandAllIsExpanded = False

    def _expandOneBranch(self, index, depth):
        """
        """
        model = self.model()
        # use depth < 0 for infinite depth
        if depth != 0 and self.model().hasChildren(index):
            if not self.isExpanded(index):
                model.modelReset.disconnect(self.applyExpandOnLoad)
                self.expand(index)
                model.modelReset.connect(self.applyExpandOnLoad)
            for row in xrange(model.rowCount(index)):
                childIndex = model.index(row,
                                         index.column(),
                                         parentIndex=index)
                if childIndex != index:
                    self._expandOneBranch(childIndex, depth - 1)

    def expandAll(self, always_expand=False):
        # Only bother expanding all, if either:
        #  * we have not already expanded all the things, or
        #  * the number of things has changed since we expanded all
        # The modelrows check was added to cope with the model contents
        # changing underneath the tree_view. There's probably a better
        # way to fix it, but this works for now.
        if (not self.__expandAllIsExpanded
                or (self.modelrows != self.model().num_items())
                or always_expand):
            self.modelrows = self.model().num_items()
            self.__expandAllIsExpanded = True
            super(TreeView, self).expandAll()
            self.allExpanded.emit()

    def collapseAll(self):
        self.__expandAllIsExpanded = False
        super(TreeView, self).collapseAll()
        self.allCollapsed.emit()

    def applyExpandOnLoad(self):
        """
        """
        if self.__expandOnLoad:
            model = self.model()
            root = QtCore.QModelIndex()
            getIndex = lambda row: model.index(row, 0, root)
            rowCount = model.rowCount(root)
            for row in xrange(0, rowCount + 1):
                self._expandOneBranch(getIndex(row), self.__expandOnLoad)

    def setExpandOnLoad(self, value):
        """
        """
        if type(value) is not int:
            value = -1 if value else 0
        enabled = value != 0
        # 		self._expandOnLoadAction.setChecked( enabled )
        self.__expandOnLoad = value
        if enabled:
            self.applyExpandOnLoad()

    def expand(self, index):
        model = self.model()
        if model.canFetchMore(index):
            model.fetchMore(index)
        super(TreeView, self).expand(index)
        self.__expandAllIsExpanded = False

    def expandToSelected(self):
        """
        """
        model = self.model()
        propagateSelection = self.selectionModel().propagateSelection()

        # remove duplicates
        def removeIndexDuplicates(indexList):
            return filter(
                QtCore.QModelIndex.isValid,
                map(model.indexFromItem,
                    set(map(model.itemFromIndex, indexList))))

        # index has no parent
        def isTopLevel(index):
            return not index.parent().isValid()

        # get selected indexes
        indexes = removeIndexDuplicates(
            filter(QtCore.QModelIndex.isValid,
                   self.selectionModel().selectedIndexes()))
        # if no selection, expand one level
        if not indexes:
            self._expandOneBranch(model.index(0, 0, QtCore.QModelIndex()), 1)
            return
        block = self.blockSignals(True)
        # if the top level is selected, it's quicker to just expand all
        if propagateSelection is True and filter(isTopLevel, indexes):
            self.__expandAllIsExpanded = False
            self.expandAll()
            self.blockSignals(block)
            return
        # expand back up the hierarchy to reveal the selected index
        for index in indexes:
            while index.isValid() and not self.isExpanded(index):
                self.setExpanded(index, True)
                index = index.parent()
        self.blockSignals(block)

    ###########################################################################
    # DRAG/DROP METHODS
    ###########################################################################
    def dragMoveEvent(self, event):
        """
        """
        # re-implemented so we can customise the autoExpandDelay behaviour
        if self.autoExpandDelay() >= 0:
            self.__openTimer.start(self.autoExpandDelay())
        AbstractView.dragMoveEvent(self, event)

    def __doAutoExpand(self):
        """
        """
        pos = self.viewport().mapFromGlobal(QtGui.QCursor.pos())
        if self.state(
        ) == QtGui.QAbstractItemView.DraggingState and self.viewport().rect(
        ).contains(pos):
            index = self.indexAt(pos)
            # only expand branches, never collapse them
            if not self.isExpanded(index):
                self.setExpanded(index, True)
        self.__openTimer.stop()

    def loadSettings(self, settings):
        AbstractView.loadSettings(self, settings)
        # allow 'expand on load' to be passed from SITE cfg file.
        # 'state' is usually stored and read from the local cfg, which will override a SITE default being set
        self.setExpandOnLoad(settings['expandOnLoad'] or 0)
示例#19
0
文件: common.py 项目: mhamid3d/jinx
DEFAULT_LEVEL_CONSOLE = crosslogging.DEBUG
DEFAULT_LEVEL_FILE = crosslogging.DEBUG
DEFAULT_LEVEL_QT = crosslogging.INFO

# Integer limits
# do not use sys.maxint
# the values defined here are compatible with QSpinBox widgets
MINIMUM_INTEGER = -2147483648
MAXIMUM_INTEGER = 2147483647

# Float limits
MINIMUM_FLOAT = -1.7976931348623157e+308
MAXIMUM_FLOAT = 1.7976931348623157e+308

# Default window geometry
DEFAULT_WINDOW_POS = QtCore.QPoint(50, 50)
DEFAULT_WINDOW_SIZE = QtCore.QSize(800, 600)

# log level icons
LEVEL_ICON_MAP = {
    crosslogging.CRITICAL: icons.ICON_ERROR_SML,
    crosslogging.ERROR: icons.ICON_ERROR_SML,
    crosslogging.WARNING: icons.ICON_WARNING_SML,
    crosslogging.INFO: icons.ICON_INFO_SML,
    crosslogging.VERBOSE: icons.ICON_VERBOSE_SML,
    crosslogging.DEBUG: icons.ICON_DEBUG_SML,
}

# cell edit types
EDITBITMASK_INSERT = 0x1 << 0
EDITBITMASK_DELETE = 0x1 << 1
示例#20
0
class TreeSelectionModel(QtGui.QItemSelectionModel):
    """A custom selection model for :class:`TreeView`, which inherits :class:`QtGui.QItemSelectionModel`,
    and adds selection propagation, i.e. when propagation is turned on, by calling ``setPropagateSelection( True )``
    it will select all items below the item that is selected by the mouse.
    """
    STATE_UNSELECTED = 0
    STATE_PARTIALLY_SELECTED = 1
    STATE_SELECTED = 2

    refreshRequested = QtCore.Signal()

    def __init__(self, model, parent=None):
        """
        """
        super(TreeSelectionModel, self).__init__(model, parent)
        self.__propagateSelection = False
        self.__showPartiallySelected = True
        self.__lastselectionflags = 0

    def __propagateSelectionDown(self, selection):
        """
        """
        childSelection = QtGui.QItemSelection()
        indexQueue = selection.indexes()
        while indexQueue:
            index = indexQueue.pop(0)
            if index.isValid():
                numChildren = self.model().rowCount(index)
                childIndexes = [
                    self.model().index(row, 0, index)
                    for row in xrange(numChildren)
                ]
                if childIndexes:
                    # add child indexes to the selection
                    childSelection.append(
                        QtGui.QItemSelectionRange(childIndexes[0],
                                                  childIndexes[-1]))
                    indexQueue.extend(childIndexes)
        return childSelection

    def _propagateSelectionUp(self, selection, command):
        """
        """
        parentSelection = QtGui.QItemSelection()
        # filter out duplicates by unique id because pyside QModelIndexes are not hashable, and cannot be added to a set
        parentIndexes = map(QtCore.QModelIndex.parent, selection.indexes())
        parentIndexes = dict(
            zip(map(self.model().uniqueIdFromIndex, parentIndexes),
                parentIndexes)).values()
        for index in parentIndexes:
            while index.isValid():
                if not (selection.contains(index)
                        or parentSelection.contains(index)):
                    if command & QtGui.QItemSelectionModel.Deselect:
                        # children are being deselected, deselect parents too
                        parentSelection.select(index, index)
                    elif command & QtGui.QItemSelectionModel.Select:
                        # children are being selected, select parent if all children are now selected
                        numChildren = self.model().rowCount(index)
                        if numChildren:
                            numSelected = 0
                            for row in xrange(numChildren):
                                childIndex = self.model().index(row, 0, index)
                                if selection.contains(childIndex) or \
                                        parentSelection.contains(childIndex) or \
                                        (not (command & QtGui.QItemSelectionModel.Clear) and self.isSelected(
                                            childIndex)):
                                    numSelected += 1
                                else:
                                    break
                            if numSelected == numChildren:
                                # all children are selected, select parent too
                                parentSelection.select(index, index)
                index = index.parent()
        return parentSelection

    def setPropagateSelection(self, enabled):
        """
        """
        self.__propagateSelection = bool(enabled)

    def propagateSelection(self):
        """
        """
        return self.__propagateSelection

    def setShowPartiallySelected(self, state):
        self.__showPartiallySelected = bool(state)

    def showPartiallySelected(self):
        return self.__showPartiallySelected

    def setSelectionStates(self, indexes, previous=None):
        """
        go from child up through the parents, setting the ancestors to partially selected
        """
        model = self.model()
        role = common.ROLE_SELECTION_STATE

        def selectUpstream(item, state=self.STATE_PARTIALLY_SELECTED):
            parent = item.parent()
            while parent is not None:
                parent.setData(state, role=role)
                parent = parent.parent()

        itemFromIndex = model.itemFromIndex
        # if there is a previous selection, unselect it
        if previous is not None:
            for index in previous:
                item = itemFromIndex(index)
                # always deselect upstream, in case self.__showPartiallySelected changes between selections
                selectUpstream(item, state=self.STATE_UNSELECTED)
                item.setData(self.STATE_UNSELECTED, role=role)
        # select new
        for item in set(map(itemFromIndex, indexes)):
            # if self.__showPartiallySelected is True, partial-select parents until we hit the root node
            if self.__showPartiallySelected is True:
                selectUpstream(item, state=self.STATE_PARTIALLY_SELECTED)
            item.setData(self.STATE_SELECTED, role=role)
        # emit model data changed signal to trigger view repaint
        model.dataChanged.emit(
            model.index(0, 0),
            model.index(model.rowCount(), model.columnCount()))

    def getSelectionFlags(self):
        return self.__lastselectionflags

    def select(self, selection, command):
        """
        Select items

        :parameters:
            selection : QtGui.QItemSelection
                selected model items
            command : QtGui.QItemSelectionModel.SelectionFlags
                NoUpdate: No selection will be made.
                Clear: The complete selection will be cleared.
                Select: All specified indexes will be selected.
                Deselect: All specified indexes will be deselected.
                Toggle: All specified indexes will be selected or deselected depending on their current state.
                Current: The current selection will be updated.
                Rows: All indexes will be expanded to span rows.
                Columns: All indexes will be expanded to span columns.
                SelectCurrent:A combination of Select and Current, provided for convenience.
                ToggleCurrent: A combination of Toggle and Current, provided for convenience.
                ClearAndSelect: A combination of Clear and Select, provided for convenience.
        """
        if self.__propagateSelection:
            # propagate selection to children/parents of selected indexes
            if isinstance(selection, QtCore.QModelIndex):
                selection = QtGui.QItemSelection(selection, selection)
            # propagate selection down to children
            childSelection = self.__propagateSelectionDown(selection)
            # propagate selection up to parents
            parentSelection = self._propagateSelectionUp(selection, command)
            selection.merge(childSelection,
                            QtGui.QItemSelectionModel.SelectCurrent)
            selection.merge(parentSelection,
                            QtGui.QItemSelectionModel.SelectCurrent)

        # NOTE: the 'command' parameter is really the 'selectionFlags'
        self.__lastselectionflags = command
        if (command & QtGui.QItemSelectionModel.Columns):
            # NOTE: I'm not sure anyone ever has this set but just in
            # case for compatibility. In future we should apptrack
            # this and if no one uses column seleciton then this
            # option should be removed.
            previousSelection = self.selectedIndexes()
        else:
            # This saves on many many duplicates in the selection
            previousSelection = self.selectedRows()

        QtGui.QItemSelectionModel.select(self, selection, command)

        # NOTE: the 'command' parameter is really 'selectionFlags'
        if (command & QtGui.QItemSelectionModel.Columns):
            # NOTE: I'm not sure anyone ever has this set but just in
            # case for compatibility. In future we should apptrack
            # this and if no one uses column seleciton then this
            # option should be removed.
            selected_now = self.selectedIndexes()
        else:
            # This saves on many many duplicates in the selection
            selected_now = self.selectedRows()

        self.setSelectionStates(selected_now, previous=previousSelection)

        self.requestRefresh()

    def requestRefresh(self):
        """
        """
        self.refreshRequested.emit()
示例#21
0
 def paintSection(self, painter, rect, logicalIndex):
     """
     Re-implementation of QHeaderView.paintSection() to deal with multi-sorting.
     """
     if not rect.isValid():
         return
     painter.save()
     # get the state of the section
     opt = QtGui.QStyleOptionHeader()
     self.initStyleOption(opt)
     state = QtGui.QStyle.State_None
     if self.isEnabled():
         state |= QtGui.QStyle.State_Enabled
     if self.window().isActiveWindow():
         state |= QtGui.QStyle.State_Active
     # store some frequently used objects
     setOptBrush = opt.palette.setBrush
     getHeaderData = self.model().headerData
     palette = self.palette()
     orientation = self.orientation()
     # set up sorted column headers
     sortColumns = self.model().sorter.sortColumns
     sortDirections = self.model().sorter.sortDirections
     sortIndex = -1
     if self.isSortIndicatorShown():
         try:
             sortIndex = sortColumns.index(logicalIndex)
         except ValueError:
             pass  # this column is not a part of the multi-sort
         if sortIndex >= 0:
             opt.sortIndicator = QtGui.QStyleOptionHeader.SortDown if sortDirections[
                 sortIndex] == QtCore.Qt.AscendingOrder else QtGui.QStyleOptionHeader.SortUp
             # paint sorted column slightly darker than normal
             setOptBrush(QtGui.QPalette.Button,
                         QtGui.QBrush(palette.button().color().darker(110)))
             setOptBrush(QtGui.QPalette.Window,
                         QtGui.QBrush(palette.window().color().darker(110)))
             setOptBrush(QtGui.QPalette.Dark,
                         QtGui.QBrush(palette.dark().color().darker(110)))
     # setup the style options structure
     opt.rect = rect
     opt.section = logicalIndex
     opt.state |= state
     opt.text = getHeaderData(logicalIndex, orientation,
                              QtCore.Qt.DisplayRole)
     textAlignment = getHeaderData(logicalIndex, orientation,
                                   QtCore.Qt.TextAlignmentRole)
     opt.textAlignment = QtCore.Qt.Alignment(
         textAlignment if textAlignment is not None else self.
         defaultAlignment())
     opt.iconAlignment = QtCore.Qt.AlignVCenter
     if self.textElideMode() != QtCore.Qt.ElideNone:
         opt.text = opt.fontMetrics.elidedText(opt.text,
                                               self.textElideMode(),
                                               rect.width() - 4)
     icon = getHeaderData(logicalIndex, orientation,
                          QtCore.Qt.DecorationRole)
     if icon:
         opt.icon = icon
     foregroundBrush = getHeaderData(logicalIndex, orientation,
                                     QtCore.Qt.ForegroundRole)
     if foregroundBrush:
         setOptBrush(QtGui.QPalette.ButtonText, foregroundBrush)
     backgroundBrush = getHeaderData(logicalIndex, orientation,
                                     QtCore.Qt.BackgroundRole)
     if backgroundBrush:
         setOptBrush(QtGui.QPalette.Button, backgroundBrush)
         setOptBrush(QtGui.QPalette.Window, backgroundBrush)
         painter.setBrushOrigin(opt.rect.topLeft())
     # determine column position
     visual = self.visualIndex(logicalIndex)
     assert visual != -1
     if self.count() == 1:
         opt.position = QtGui.QStyleOptionHeader.OnlyOneSection
     elif visual == 0:
         opt.position = QtGui.QStyleOptionHeader.Beginning
     elif visual == self.count() - 1:
         opt.position = QtGui.QStyleOptionHeader.End
     else:
         opt.position = QtGui.QStyleOptionHeader.Middle
     opt.orientation = orientation
     # draw the section
     self.style().drawControl(QtGui.QStyle.CE_Header, opt, painter, self)
     painter.restore()
     # darken if it is a locked column in the view
     if not self.__columnIsUnlocked(logicalIndex):
         painter.fillRect(rect, QtGui.QColor(0, 0, 0, 40))
     # paint a number overlay when multi-sorting
     if sortIndex >= 0 and len(sortColumns) > 1:
         # paint a number indicator when multi-sorting
         text = str(sortIndex + 1)
         headerFont = QtGui.QFont(painter.font())
         headerFont.setPointSize(headerFont.pointSize() - 2)
         textWidth = QtGui.QFontMetrics(headerFont).boundingRect(
             text).width()
         # calculate the indicator location
         point = QtCore.QPointF(rect.bottomRight())
         point.setX(point.x() - textWidth - 1)
         point.setY(point.y() - 1)
         # paint the number overlay
         painter.save()
         painter.setCompositionMode(QtGui.QPainter.CompositionMode_Source)
         painter.setFont(headerFont)
         painter.drawText(point, text)
         painter.restore()
示例#22
0
class AbstractDataSource(QtCore.QObject):
    """
    This is an abstract base class representing the datasource for a model.
    The datasource is what supplies a model with its items.
    The model requests items via the :meth:`fetchItems` and :meth:`fetchMore` methods.
    The datasource could receive its data from an SQL database, a file, etc.
    Subclass AbstractDataSource to provide your own datasource and connect it to a model
    using :meth:`~wizqt.modelview.model.Model.setDataSource`
    """
    # load methods
    LOAD_METHOD_ALL = "all"
    LOAD_METHOD_PAGINATED = "paginated"
    LOAD_METHOD_INCREMENTAL = "incremental"
    DEFAULT_LOAD_METHOD = LOAD_METHOD_ALL
    DEFAULT_BATCH_SIZE = 500
    # signals
    dataNeedsRefresh = QtCore.Signal()
    totalCountChanged = QtCore.Signal(int)
    pageChanged = QtCore.Signal(int)

    # properties
    @property
    def model(self):
        return self._model

    @property
    def headerItem(self):
        return self._headerItem

    @property
    def loadMethod(self):
        return self._loadMethod

    @property
    def lazyLoadChildren(self):
        return self._lazyLoadChildren

    @property
    def batchSize(self):
        return self._batchSize

    @property
    def needToRefresh(self):
        return self._needToRefresh

    @property
    def isValid(self):
        return self._isValid

    def __init__(self, columnNames=[], parent=None):
        """
        Initialiser

        :keywords:
            columnNames : list
                list of column header names
            parent : QtCore.QObject
                parent qt object
        """
        super(AbstractDataSource, self).__init__(parent)
        self._model = None  # link to the model this datasource is populating
        self._loadMethod = self.DEFAULT_LOAD_METHOD
        self._needToRefresh = True  # indicate that data needs to be refreshed to trigger initial population
        self._isValid = True
        self._itemsCheckable = False
        # lazy-load children for tree models
        # children are not populated until parent is expanded in tree
        self._lazyLoadChildren = False
        # lazy-load in batches
        self._pageNum = 0
        self._batchSize = self.DEFAULT_BATCH_SIZE
        self._totalCount = -1
        # initialize the header item
        self._headerItem = self._createHeaderItem(columnNames)

    def _setTotalCount(self, totalCount):
        """
        Store the number of results

        :parameters:
            totalCount : int
                number of results
        """
        if self._totalCount != totalCount:
            self._totalCount = totalCount
            self.totalCountChanged.emit(self._totalCount)

    def _createHeaderItem(self, columnNames):
        """
        Create a modelitem to represent header data

        :parameters:
            columnNames : list
                Create a modelitem with columns <columnNames>
        :return:
            the created model item
        :rtype:
            wizqt.modelview.model_item.ModelItem
        """
        headerItem = ModelItem(len(columnNames))
        for i, columnName in enumerate(columnNames):
            self._populateHeaderColumn(headerItem, i, columnName)
        return headerItem

    def _populateHeaderColumn(self, headerItem, col, columnName):
        """
        Add the text <columnName> to the modelitem <headerItem> at column index <col>

        :parameters:
            headerItem : wizqt.modelview.model_item.ModelItem
                the header's model item
            col : int
                index of a column
            columnName : str
                column name
        """
        headerItem.setData(columnName, col, QtCore.Qt.DisplayRole)
        headerItem.setData(columnName, col, common.ROLE_NAME)

    def setModel(self, model):
        """
        Set the model that this datasource is populating

        :parameters:
            model : wizqt.modelview.model_item.Model
                the model
        """
        self._model = model

    def setLoadMethod(self, loadMethod):
        """
        Set the way that data is loaded

        :parameters:
            loadMethod : str
                the load method

                self.LOAD_METHOD_ALL
                    load all data in one go
                self.LOAD_METHOD_PAGINATED
                    load data in groups i.e. pages. The number of items per page is self.DEFAULT_BATCH_SIZE
                    :see:
                        self.setBatchSize
                self.LOAD_METHOD_INCREMENTAL
                    load data incrementally, as long as self.canFetchMore() returns True
        """
        self._loadMethod = loadMethod

    def setLazyLoadChildren(self, enabled):
        """
        lazy load children (children are not populated until parent is expanded in tree)

        :parameters:
            enabled : bool
                enabled state
        """
        self._lazyLoadChildren = bool(enabled)

    def setNeedToRefresh(self, needToRefresh):
        """
        Either emit a signal that indicates that the data is no longer up to date,
        or stop the signal from being emitted, i.e. the data is now up to date

        :parameters:
            needToRefresh : bool
                the data needs to be updated
        """
        if self._needToRefresh != needToRefresh:
            self._needToRefresh = needToRefresh
            if self._needToRefresh:
                self.dataNeedsRefresh.emit()

    def setBatchSize(self, batchSize):
        """
        Set the maximum number of items that can be loaded in one go.

        :parameters:
            batchSize : int
                the result count limit
        """
        if self._batchSize != batchSize:
            self._batchSize = batchSize
            self.setNeedToRefresh(True)

    def setPage(self, pageNum):
        """
        If we're loading data in pages, set the current page that we're on

        :parameters:
            pageNum : int
                page number
        """
        if self._loadMethod == self.LOAD_METHOD_PAGINATED and self._pageNum != pageNum:
            self._pageNum = pageNum
            self.pageChanged.emit(pageNum)
            self.setNeedToRefresh(True)

    def canFetchMore(self, parentIndex):
        """
        Check whether the parentIndex needs to be populated. This applies only to
        incremental loading or lazy population of children

        :parameters:
            parentIndex : QtCore.QModelIndex
                model index to be queried
        :return:
            True if item needs to be populated
        :rtype:
            bool
        """
        if self._isValid and (self._loadMethod == self.LOAD_METHOD_INCREMENTAL or self._lazyLoadChildren):
            """
            if not parentIndex.isValid():
                # top-level item
                if self._totalCount < 0: # total count is uninitialized, so item is unpopulated
                    return True
                elif self._loadMethod == self.LOAD_METHOD_INCREMENTAL and self._model.rowCount( parentIndex ) < self._totalCount:
                    return True
            else:
            """
            if True:
                # must be a tree model
                item = self._model.itemFromIndex(parentIndex)
                if self._lazyLoadChildren and item.totalChildCount() < 0:  # totalChildCount is uninitialized, so item is unpopulated
                    return True
                elif self._loadMethod == self.LOAD_METHOD_INCREMENTAL and item.childCount() < item.totalChildCount():
                    return True
        return False

    def fetchMore(self, parentIndex):
        """
        This method only gets called when canFetchMore() returns True
        should only be for incremental loading or lazy population of children

        :parameters:
            parentIndex : QtCore.QModelIndex
                model index to be queried
        """
        assert self._isValid and (self._loadMethod == self.LOAD_METHOD_INCREMENTAL or self._lazyLoadChildren)
        offset = self._model.rowCount(parentIndex)
        limit = self._batchSize if self._loadMethod == self.LOAD_METHOD_INCREMENTAL else 0
        itemList = self._fetchBatch(parentIndex, offset, limit, False)  # FIXME: reload?
        # sort the items using the model's sorter
        self._model.sorter.sortItems(itemList)
        # mark parent as populated
        if self._lazyLoadChildren:  # and parentIndex.isValid():
            parentItem = self._model.itemFromIndex(parentIndex)
            parentItem.setTotalChildCount(len(itemList))
        # insert the new items in the model
        self._model.appendItems(itemList, parentIndex)

    def fetchItems(self, parentIndex, reload):
        """
        Get modelitems for this index. This method should only be called by requestRefresh()
        in Model, and only for top-level items.

        :parameters:
            parentIndex : QtCore.QModelIndex
                model index to be populated
            reload : bool
                the index's data should be replaced
        :return:
            The result of self._fetchBatch
        :rtype:
            ?, self._fetchBatch is abstract in this class
        """
        assert not parentIndex.isValid()
        if not self._isValid:
            self._setTotalCount(-1)
            return []
        limit = 0
        offset = 0
        if self._loadMethod == self.LOAD_METHOD_PAGINATED and not parentIndex.isValid():
            limit = self._batchSize
            offset = self._pageNum * self._batchSize
        elif self._loadMethod == self.LOAD_METHOD_INCREMENTAL:
            limit = self._batchSize
            item = self._model.itemFromIndex(parentIndex)
            if item.totalChildCount() < 0:
                offset = 0
            else:
                offset = self._model.rowCount(parentIndex)
        itemList = self._fetchBatch(parentIndex, offset, limit, reload)
        # sort the items using the model's sorter
        self._model.sorter.sortItems(itemList)
        return itemList

    def setItemsCheckable(self, checkable):
        """
        Set model items to be checkable.
        This setting should be taken into account when model items are constructed in _fetchBatch.

        :parameters:
            checkable : bool
        """
        if self._itemsCheckable != checkable:
            self._itemsCheckable = checkable
            self.setNeedToRefresh(True)

    ##############################################################################################
    # ABSTRACT METHODS
    # these are the only methods you should need to implement in datasource sub-classes
    ##############################################################################################
    def _fetchBatch(self, parentIndex, offset, limit, reload):
        """
        ABSTRACT: Return a list of ModelItem entities to the model

        :parameters:
            parentIndex : QtCore.QModelIndex
                model index to be populated
            offset : int
                page offset
            limit : int
                limit of results per page
            reload : bool
                reload existing results
        """
        raise NotImplementedError

    def sortByColumns(self, columns, directions, refresh=True):
        """
        ABSTRACT: multi sort columns

        :parameters:
            columns : list
                list of int
            directions : list
                list of QtCore.Qt.SortOrder
        """
        raise NotImplementedError

    @property
    def stateId(self):
        """
        ABSTRACT:
        A string uniquely identifying the current state of the data source contents
        For example, a string representation of the active filter
        """
        return None
示例#23
0
    def mouseMoveEvent(self, event):
        """
        Don't allow user to move locked columns
        """
        if (self.cursor().shape() == QtCore.Qt.ArrowCursor and  # not a resize
                event.buttons() == QtCore.Qt.LeftButton
                and  # not a context menu
                self.__dragLogicalIdx is not None  # is a drag-move
            ):
            scrollOffset = self.offset()
            pos = event.pos().x()  # current mouse x position
            logicalIdx, cursorOffset = self.__dragLogicalIdx  # the logical column index of the column currently being dragged
            draggedSize = self.sectionSize(
                logicalIdx)  # the size of the column that's being dragged
            rightLogicalIndex = self.logicalIndexAt(
                (pos + (draggedSize / 2) + cursorOffset) - scrollOffset
            )  # the column under the right hand edge of the dragged column
            leftLogicalIndex = self.logicalIndexAt(
                (pos - (draggedSize / 2) + cursorOffset) - scrollOffset
            )  # the column under the left hand edge of the dragged column
            centralLogicalIndex = self.logicalIndexAt(
                pos)  # the column under the cursor
            currentVisualIndex = self.visualIndexAt(pos)
            startVisualIndex = self.visualIndex(logicalIdx)
            columnOnLeftIsLocked = not self.__columnIsUnlocked(
                leftLogicalIndex
            )  # the column under the left hand edge is locked
            columnOnRightIsLocked = not self.__columnIsUnlocked(
                rightLogicalIndex
            )  # the column under the right hand edge is locked
            columnUnderMouseIsLocked = not self.__columnIsUnlocked(
                centralLogicalIndex)

            def snapToLeft():
                # get furthest left column
                furthestLeft = sorted(filter(self.__columnIsUnlocked,
                                             range(self.count())),
                                      key=self.sectionPosition)[0]
                offset = (self.sectionPosition(furthestLeft) -
                          scrollOffset) + (draggedSize / 2)
                return offset

            def snapToRight():
                # get furthest right column
                furthestRight = sorted(filter(self.__columnIsUnlocked,
                                              range(self.count())),
                                       key=self.sectionPosition)[-1]
                offset = self.sectionPosition(furthestRight) - (
                    draggedSize / 2) - scrollOffset
                return offset

            offset = None
            # if its gone to the far left and the first column on the far left is locked...
            if currentVisualIndex == 0 and (columnOnLeftIsLocked
                                            or columnUnderMouseIsLocked):
                offset = snapToLeft()
            # if its dragged as far right as it can go and the last column is locked...
            elif currentVisualIndex == self.count() - 1 and (
                    columnOnRightIsLocked or columnUnderMouseIsLocked):
                offset = snapToRight()
            # if we've dragged it into left-hand edge of a locked column
            elif startVisualIndex > self.visualIndex(leftLogicalIndex) and (
                    columnOnLeftIsLocked or columnUnderMouseIsLocked):
                offset = snapToLeft()
            # if we've dragged it into right-hand edge of a locked column
            elif startVisualIndex < self.visualIndex(rightLogicalIndex) and (
                    columnOnRightIsLocked or columnUnderMouseIsLocked):
                offset = snapToRight()
            # if we need to override the position because we've hit a locked column...
            if offset is not None:
                QtGui.QHeaderView.mouseMoveEvent(
                    self,
                    QtGui.QMouseEvent(
                        event.type(),
                        QtCore.QPoint(offset - cursorOffset, 0),
                        event.button(),
                        event.buttons(),
                        event.modifiers(),
                    ))
                return
        QtGui.QHeaderView.mouseMoveEvent(self, event)
示例#24
0
class Action(QtGui.QAction):
    """
    """
    completed = QtCore.Signal(bool, list)

    def __init__(self,
                 text=None,
                 icon=None,
                 tip=None,
                 shortcut=None,
                 shortcutContext=QtCore.Qt.WidgetShortcut,
                 menu=None,
                 checkable=False,
                 separator=False,
                 selectionBased=False,
                 signal="triggered()",
                 enabled=True,
                 isValidFn=None,
                 runFn=None,
                 parent=None):

        super(Action, self).__init__(parent)

        if text:
            self.setText(text)

        if icon:
            self.setIcon(icon)

        if tip:
            self.setToolTip(tip)
            self.setStatusTip(tip)

        if shortcut:
            if isinstance(shortcut, list):
                shortcuts = [QtGui.QKeySequence(key) for key in shortcut]
                self.setShortcuts(shortcuts)
            else:
                self.setShortcut(QtGui.QKeySequence(shortcut))
            self.setShortcutContext(shortcutContext)

        if menu:
            self.setMenu(menu)

        self.setCheckable(checkable)
        self.setEnabled(enabled)
        self.setSeparator(separator)

        self.__selectionBased = selectionBased
        self.__isValidFn = isValidFn
        self.__runFn = runFn

        self.connect(self, QtCore.SIGNAL(signal), self.run)

        self.__subActions = []

    ###########################################################################
    # 	isSelectionBased
    ###########################################################################
    def isSelectionBased(self):
        return self.__selectionBased

    ###########################################################################
    # 	isValid
    ###########################################################################
    def isValid(self):
        if self.__isValidFn:
            return self.__isValidFn()
        return True

    ###########################################################################
    # 	run
    ###########################################################################
    def run(self, *args):
        status = False
        info = []

        if self.isValid() and self.__runFn:

            if metrics.METRICS_ARE_ON:
                # Record metrics for the action we're going to run
                try:
                    # This'll work for a normal function or lambda
                    action_name = self.__runFn.__name__
                except AttributeError:
                    # This'll work for a functools.partial, which will
                    # have thrown an AttributeError.
                    action_name = self.__runFn.func.__name__

                if action_name == '<lambda>':
                    action_name = self.text().replace(' ', '_')
                metrics.incr('menuactions.{0}'.format(action_name))

            # Run the action function
            info = self.__runFn(*args)
            if info:
                if not isinstance(info, list):
                    info = [info]
            else:
                info = []

            status = True

        self.completed.emit(status, info)

    ###########################################################################
    # 	custom event system for using with custom wizqt toolbar
    ###########################################################################
    def event(self, event):
        ret = QtGui.QAction.event(self, event)
        return ret

    def eventFilter(self, obj, event):
        return QtCore.QObject.eventFilter(self, obj, event)

    def addSubActions(self, actions):
        self.__subActions.extend(actions)

    def addSubAction(self, action):
        self.addSubActions([action])

    def getSubActions(self):
        return self.__subActions
示例#25
0
 def setInputValue(cls, widget, value):
     value, ok = cls.resolveValue(value)
     if ok:
         widget.setDate(QtCore.QDate(value.year, value.month, value.day))
     return ok