Ejemplo n.º 1
0
class PythonObject(QtCore.QObject):
    def __init__(self):
        QtCore.QObject.__init__(self, None)
        self._called = ""
        self._arg1 = None
        self._arg2 = None

    def setCalled(self, v):
        self._called = v

    def setArg1(self, v):
        self._arg1 = v

    def setArg2(self, v):
        self._arg2 = v

    def getCalled(self):
        return self._called

    def getArg1(self):
        return self._arg1

    def getArg2(self):
        return self._arg2

    called = QtCore.Property(str, getCalled, setCalled)
    arg1 = QtCore.Property(int, getArg1, setArg1)
    arg2 = QtCore.Property('QVariant', getArg2, setArg2)
Ejemplo n.º 2
0
class DataFrameModel(QtCore.QAbstractTableModel):
    DtypeRole = QtCore.Qt.UserRole + 1000
    ValueRole = QtCore.Qt.UserRole + 1001

    def __init__(self, df=pd.DataFrame(), parent=None):
        super(DataFrameModel, self).__init__(parent)
        self._dataframe = df

    def setDataFrame(self, dataframe):
        self.beginResetModel()
        self._dataframe = dataframe.copy()
        self.endResetModel()

    def dataFrame(self):
        return self._dataframe

    dataFrame = QtCore.Property(pd.DataFrame, fget=dataFrame, fset=setDataFrame)

    @QtCore.Slot(int, QtCore.Qt.Orientation, result=str)
    def headerData(self, section: int, orientation: QtCore.Qt.Orientation, role: int = QtCore.Qt.DisplayRole):
        if role == QtCore.Qt.DisplayRole:
            if orientation == QtCore.Qt.Horizontal:
                return self._dataframe.columns[section]
            else:
                return str(self._dataframe.index[section])
        return None

    def rowCount(self, parent=QtCore.QModelIndex()):
        if parent.isValid():
            return 0
        return len(self._dataframe.index)

    def columnCount(self, parent=QtCore.QModelIndex()):
        if parent.isValid():
            return 0
        return 3 #self._dataframe.columns.size

    def data(self, index, role=QtCore.Qt.DisplayRole):
        if not index.isValid() or not (0 <= index.row() < self.rowCount() and 0 <= index.column() < self.columnCount()):
            return None
        row = self._dataframe.index[index.row()]
        col = self._dataframe.columns[index.column()]
        dt = self._dataframe[col].dtype

        val = self._dataframe.loc[row, col]
        if role == QtCore.Qt.DisplayRole:
            return str(val)
        elif role == DataFrameModel.ValueRole:
            return val
        if role == DataFrameModel.DtypeRole:
            return dt
        return None

    def roleNames(self):
        roles = {
            QtCore.Qt.DisplayRole: b'display',
            DataFrameModel.DtypeRole: b'dtype',
            DataFrameModel.ValueRole: b'value'
        }
        return roles
Ejemplo n.º 3
0
class BlueprintButton(QtWidgets.QPushButton):
    def __init__(self, parent=None, index=0):
        super(BlueprintButton, self).__init__(parent)
        self.setFixedHeight(17)
        self.index = index

        color1 = QtGui.QColor(255, 255, 255)
        color2 = QtGui.QColor(255, 0, 0)

        self.backColorAnim = QtCore.QPropertyAnimation(self, "backColor")
        self.backColorAnim.setDuration(250)
        self.backColorAnim.setLoopCount(4)
        self.backColorAnim.setStartValue(color1)
        self.backColorAnim.setKeyValueAt(0.5, color2)
        self.backColorAnim.setEndValue(color1)

    def getBackColor(self):
        return self.palette().text()

    def setBackColor(self, color):
        pal = self.palette()
        pal.setColor(self.foregroundRole(), color)
        self.setPalette(pal)

    backColor = QtCore.Property(QtGui.QColor, getBackColor, setBackColor)
Ejemplo n.º 4
0
class MaterialIcon(QtWidgets.QWidget):
    opacityChanged = QtCore.Signal()

    def __init__(self, parent, address):
        super(MaterialIcon, self).__init__(parent)
        self.icon = QtGui.QPixmap(address)
        self._opacity = 0.0

    def opacity(self):
        return self._opacity

    def setOpacity(self, o):
        if o != self._opacity:
            self._opacity = o
            self.opacityChanged.emit()
            self.update()

    opacity = QtCore.Property(float,
                              fget=opacity,
                              fset=setOpacity,
                              notify=opacityChanged)

    def paintEvent(self, event):
        painter = QtGui.QPainter(self)
        painter.setOpacity(self.opacity)
        mask = QtGui.QPainter(self.icon)
        mask.setCompositionMode(QtGui.QPainter.CompositionMode_SourceIn)
        mask.fillRect(self.icon.rect(), QtGui.QColor(0, 158, 227))
        mask.end()
        painter.drawPixmap(0, 0, self.icon)
Ejemplo n.º 5
0
class Squircle( QtQuick.QQuickFramebufferObject ):
    tChanged = QtCore.Signal(float)

    def __init__( self, parent=None ):
        logging.debug(threading.get_ident())
        logging.debug('Squircle.__init__')
        super().__init__( parent )

        self.renderer = None

    def gett( self ):
        if self.renderer == None:
            return 0.0
        else:
            return self.renderer.squircle.t

    def sett( self, value ):
        if self.renderer == None or self.renderer.squircle.t == value:
            return
        self.renderer.squircle.setT( value )
        self.tChanged.emit(value)
        if self.window():
            self.window().update()

    t = QtCore.Property(float, fget=gett, fset=sett, notify=tChanged)

    def createRenderer( self ):
        logging.debug(threading.get_ident())
        logging.debug('Squircle.createRenderer')
        self.renderer = SquircleInFboRenderer()
        logging.debug(threading.get_ident())
        logging.debug('set window inside Squircle.createRenderer')
        self.renderer.squircle.setWindow( self.window() )
        return self.renderer
Ejemplo n.º 6
0
class CustomLineEdit(QLineEdit):

    mousePressed = QtCore.Property(QMouseEvent)
    tagSelected = QtCore.Signal(str)

    def __init__(self, parent):
        super(self.__class__, self).__init__()
        self.model = QStringListModel()
        self.setCompleter(QCompleter())
        self.completer().setModel(self.model)
        self.completer().setCompletionMode(QCompleter.PopupCompletion)
        self.completer().activated.connect(self.selected)
        self.textEdited.connect(self.slot_text_edited)
        self.parent = parent
        self.setPlaceholderText("Type tags here")

    def slot_text_edited(self, text):
        self.completer().setCompletionMode(
            QCompleter.UnfilteredPopupCompletion if text ==
            '' else QCompleter.PopupCompletion)

    def selected(self, txt):
        self.tagSelected.emit(txt)

    def set_list(self, qsl):
        self.model.setStringList(qsl)

    def mousePressEvent(self, e):
        self.completer().complete()

    def focusInEvent(self, e):
        self.completer().complete()

    def focusOutEvent(self, e):
        pass
class DotLabel(QtWidgets.QFrame):
    def __init__(self, name, height=20, parent=None):
        super(DotLabel, self).__init__(parent)

        self._dot_color = QtGui.QColor(0, 0, 0)
        self._name = name
        self._height = height
        self.setFixedHeight(height)

    def get_dot_color(self):
        return self._dot_color

    def set_dot_color(self, color):
        self._dot_color = color

    def paintEvent(self, e):

        qp = QtGui.QPainter(self)
        qp.setRenderHint(QtGui.QPainter.Antialiasing)
        rect = QtCore.QRect(self._height, 0, self.width(), self._height)
        qp.drawText(rect, QtCore.Qt.AlignVCenter, self._name)
        qp.setBrush(self.get_dot_color())
        qp.setPen(QtCore.Qt.NoPen)
        qp.drawEllipse(QtCore.QPoint(self._height * .5, self._height * .5),
                       self._height * .20, self._height * .20)
        qp.end()

    dotColor = QtCore.Property(QtGui.QColor, get_dot_color, set_dot_color)
Ejemplo n.º 8
0
class BlinkBackgroundWidget(QWidget):

    # Fake bg_color property to use in the animation
    def get_bg_color(self):
        return self.palette().background().color()

    def set_bg_color(self, color):
        palette = self.palette()
        palette.setColor(self.backgroundRole(), color)
        self.setPalette(palette)

    bg_color = QtCore.Property(QtGui.QColor, get_bg_color, set_bg_color)

    def change_bg_color_with_blink(self, color: QColor):
        self.blink_bg_color(self.background_color, color)
        self.background_color = color

    def blink_bg_color(self, start_color, end_color):
        # make sure we apply any possible pending color changes
        self.blink_animation.stop()
        self.set_bg_color(self.background_color)

        self.blink_animation.setDuration(800)
        self.blink_animation.setLoopCount(1)
        self.blink_animation.setStartValue(start_color)
        self.blink_animation.setEndValue(end_color)
        self.blink_animation.setKeyValueAt(0.33, end_color)
        self.blink_animation.setKeyValueAt(0.66, start_color)
        self.blink_animation.start()

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setAutoFillBackground(True)
        self.background_color = self.get_bg_color()
        self.blink_animation = QtCore.QPropertyAnimation(self, b"bg_color")
Ejemplo n.º 9
0
class Ball(QtCore.QObject):
    def __init__(self, parent=None):
        super(Ball, self).__init__(parent)
        self.pixmap_item = QtWidgets.QGraphicsPixmapItem(
            QtGui.QPixmap("ball.png"))

    def _set_pos(self, pos):
        self.pixmap_item.setPos(pos)

    pos = QtCore.Property(QtCore.QPointF, fset=_set_pos)
Ejemplo n.º 10
0
class TimerLabel(qw.QLabel):
    def __init__(self,
                 parent: t.Optional[qw.QWidget] = None,
                 f: qq.WindowFlags = qq.WindowFlags()):
        super().__init__(parent, f)

        timer = qc.QTimer()

        timer.timeout.connect(self._on_timeout)

        self._timer = timer
        self._elapsed = qc.QTime(0, 0)
        self._work = False

        self.setText(self._elapsed.toString(_FORMAT))

    def get_interval(self):
        return self._timer.interval()

    def set_interval(self, msec: int):
        self._timer.setInterval(msec)

    def is_work(self) -> bool:
        return self._work

    def set_work(self, enabled: bool):
        self._work = enabled
        if enabled:
            self._timer.start()
        else:
            self._timer.stop()

    interval = qc.Property(int, get_interval, set_interval)
    work = qc.Property(bool, is_work, set_work)

    def _on_timeout(self):
        self._elapsed = self._elapsed.addMSecs(self._timer.interval())
        self.setText(self._elapsed.toString(_FORMAT))
Ejemplo n.º 11
0
class Pixmap(QtCore.QObject):
    def __init__(self, pix):
        super(Pixmap, self).__init__()

        self.pixmap_item = QtGui.QGraphicsPixmapItem(pix)
        self.pixmap_item.setCacheMode(QtGui.QGraphicsItem.DeviceCoordinateCache)

    def set_pos(self, pos):
        self.pixmap_item.setPos(pos)

    def get_pos(self):
        return self.pixmap_item.pos()

    pos = QtCore.Property(QtCore.QPointF, get_pos, set_pos)
Ejemplo n.º 12
0
class SettingsViewModel(QtCore.QObject):
    todos_reference_changed = QtCore.Signal()
    todo_count_changed = QtCore.Signal()

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

        self.__todos_reference = None

    def get_todo_count(self):
        if self.__todos_reference:
            return len(self.__todos_reference.todos)

        return 0

    def get_todos_reference(self):
        return self.__todos_reference

    def set_todos_reference(self, value):
        # Only change the view model if there is a new one (don't reload when closing the app)
        if value:
            self.__todos_reference = value
            self.todos_reference_changed.emit()

            # Connect signal in TodosViewModel
            self.__todos_reference.todo_count_changed.connect(
                lambda: self.todo_count_changed.emit())

    @QtCore.Slot()
    def clearTodos(self):
        self.__todos_reference.clear_todos()

    todosReference = QtCore.Property(TodosViewModel,
                                     get_todos_reference,
                                     set_todos_reference,
                                     notify=todos_reference_changed)
    todoCount = QtCore.Property(int, get_todo_count, notify=todo_count_changed)
Ejemplo n.º 13
0
class RotateValue(QtCore.QObject):
    def __init__(self):
        super(RotateValue, self).__init__()

    @QtCore.Slot(result=int)
    def val(self):
        return 100

    def setRotation(self, v):
        self._rotation = v

    def getRotation(self):
        return self._rotation

    rotation = QtCore.Property(int, getRotation, setRotation)
Ejemplo n.º 14
0
class LabelEditWidget(QtWidgets.QWidget):
    def __init__(self, parent=None, label=''):
        super(LabelEditWidget, self).__init__(parent)
        self.labelName = label
        self.label = None

        self.setupUI()

    def setupUI(self):
        mainLayout = VertBox()

        self.layout = QtWidgets.QHBoxLayout()
        self.layout.setContentsMargins(1, 1, 1, 1)

        if self.labelName != '':
            self.label = QtWidgets.QLabel(self.labelName)
            self.layout.addWidget(self.label)

        self.edit = QtWidgets.QLineEdit()
        self.edit.setValidator(CharNameValidator())

        color1 = self.edit.palette().color(self.edit.backgroundRole())
        color2 = QtGui.QColor(255, 0, 0)

        self.backColorAnim = QtCore.QPropertyAnimation(self, "backColor")
        self.backColorAnim.setDuration(250)
        self.backColorAnim.setLoopCount(4)
        self.backColorAnim.setStartValue(color1)
        self.backColorAnim.setKeyValueAt(0.5, color2)
        self.backColorAnim.setEndValue(color1)

        self.layout.addWidget(self.edit)

        mainLayout.addLayout(self.layout)
        self.setLayout(mainLayout)

    def getBackColor(self):
        return self.edit.palette().color(QtGui.QPalette.Background)

    def setBackColor(self, color):
        self.edit.setStyleSheet("QLineEdit { background: rgb(" +
                                str(color.red()) + "," + str(color.green()) +
                                "," + str(color.blue()) + "); }")

    backColor = QtCore.Property(QtGui.QColor, getBackColor, setBackColor)
Ejemplo n.º 15
0
class ColorButton(qtw.QPushButton):
    """Button with color and backgroundColor properties for animation"""
    def _color(self):
        return self.palette().color(qtg.QPalette.ButtonText)

    def _setColor(self, qcolor):
        palette = self.palette()
        palette.setColor(qtg.QPalette.ButtonText, qcolor)
        self.setPalette(palette)

    color = qtc.Property(qtg.QColor, _color, _setColor)

    @qtc.Property(qtg.QColor)
    def backgroundColor(self):
        return self.palette().color(qtg.QPalette.Button)

    @backgroundColor.setter
    def backgroundColor(self, qcolor):
        palette = self.palette()
        palette.setColor(qtg.QPalette.Button, qcolor)
        self.setPalette(palette)
class BackgroundWithColor(QtWidgets.QPushButton):
    '''
    Simple Background Color Class with color property that can be animated.

    :param int height: Height
    :param QColor color: Background color
    :param bool roundedCorner: Rounded/Square corner
    '''
    def __init__(self, parent=None, height=32, color=GRAY, roundedCorner=True):
        super(BackgroundWithColor, self).__init__(parent)
        self.border_radius = int(height * 0.5) if roundedCorner else 0
        self._color = color
        self.setColor(self._color)

    def getColor(self):
        return self._color

    def setColor(self, value):
        self._color = value
        css = 'background-color: rgb({0},{1},{2});'.format(value.red(), value.green(), value.blue())
        css += 'border-radius: {0}px;'.format(self.border_radius)
        self.setStyleSheet(css)

    color = QtCore.Property(QtGui.QColor, getColor, setColor)
Ejemplo n.º 17
0
class DataFrameModel(QtCore.QAbstractTableModel):
    DtypeRole = QtCore.Qt.UserRole + 1000
    ValueRole = QtCore.Qt.UserRole + 1001

    def __init__(self, df=pd.DataFrame(), parent=None):
        super(DataFrameModel, self).__init__(parent)
        self._dataframe = df

    def setDataFrame(self, dataframe):
        self.beginResetModel()
        self._dataframe = dataframe.copy()
        self.endResetModel()

    def dataFrame(self):
        return self._dataframe

    dataFrame = QtCore.Property(pd.DataFrame,
                                fget=dataFrame,
                                fset=setDataFrame)

    @QtCore.Slot(int, QtCore.Qt.Orientation, result=str)
    def headerData(self,
                   section: int,
                   orientation: QtCore.Qt.Orientation,
                   role: int = QtCore.Qt.DisplayRole):
        if role == QtCore.Qt.DisplayRole:
            if orientation == QtCore.Qt.Horizontal:
                return self._dataframe.columns[section]
            else:
                return str(self._dataframe.index[section])
        return None

    def rowCount(self, parent=QtCore.QModelIndex()):
        if parent.isValid():
            return 0
        return len(self._dataframe.index)

    def columnCount(self, parent=QtCore.QModelIndex()):
        if parent.isValid():
            return 0
        return self._dataframe.columns.size

    def data(self, index, role=QtCore.Qt.DisplayRole):
        if not index.isValid() or not (0 <= index.row() < self.rowCount() and 0
                                       <= index.column() < self.columnCount()):
            return None
        row = self._dataframe.index[index.row()]
        col = self._dataframe.columns[index.column()]
        dt = self._dataframe[col].dtype

        val = self._dataframe.loc[row][col]
        if role == QtCore.Qt.DisplayRole:
            return str(val)
        elif role == DataFrameModel.ValueRole:
            return val
        if role == DataFrameModel.DtypeRole:
            return dt
        return None

    def roleNames(self):
        roles = {
            QtCore.Qt.DisplayRole: b'display',
            DataFrameModel.DtypeRole: b'dtype',
            DataFrameModel.ValueRole: b'value'
        }
        return roles

    def sort(self, column, order):
        self.layoutAboutToBeChanged.emit()
        if order == 0:
            self._dataframe = self._dataframe.reindex(index=order_by_index(
                self._dataframe.index,
                index_natsorted(
                    eval('self._dataframe["%s"]' %
                         (list(self._dataframe.columns)[column])))))
        else:
            self._dataframe = self._dataframe.reindex(index=order_by_index(
                self._dataframe.index,
                reversed(
                    index_natsorted(
                        eval('self._dataframe["%s"]' %
                             (list(self._dataframe.columns)[column]))))))

        self._dataframe.reset_index(inplace=True, drop=True)
        self.setDataFrame(self._dataframe)
        self.layoutChanged.emit()
class RamDiagram(QtWidgets.QWidget):
    percentChanged = QtCore.Signal(float)

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

        # color constants
        self.dark = "#3B3A44"
        self.light = "#4A4953"
        self.color = "#75ECB5"

        # text constants
        self.module_name = "RAM"
        self.postfix = "average"

        # timer with an interval of 1 sec
        self.timer = QtCore.QTimer()
        self.timer.setInterval(2000)
        self.timer.timeout.connect(self.onTimeout)
        self.timer.start()

        # animation initialization
        self._percent = 0
        self._animation = QtCore.QPropertyAnimation(self, b"percent", duration=400)
        self._animation.setEasingCurve(QtCore.QEasingCurve.OutExpo)

        self.percentChanged.connect(self.update)

        self.setAttribute(QtCore.Qt.WA_TranslucentBackground, True)
        self.setAttribute(QtCore.Qt.WA_NoSystemBackground, True)


        self.setWindowFlags(QtCore.Qt.FramelessWindowHint |
                            QtCore.Qt.X11BypassWindowManagerHint
                            )

    @QtCore.Slot()
    def onTimeout(self):
        start_value = self.percent
        end_value = virtual_memory().percent

        self._animation.setStartValue(start_value)
        self._animation.setEndValue(end_value)
        self._animation.setDuration(1000)
        self._animation.start()

    def get_percent(self):
        return self._percent

    def set_percent(self, p):
        if self._percent != p:
            self._percent = p
            self.percentChanged.emit(p)

    percent = QtCore.Property(
        float, fget=get_percent, fset=set_percent, notify=percentChanged
    )

    def paintEvent(self, event: QtGui.QPaintEvent):
        painter = QtGui.QPainter(self)
        painter.setRenderHint(QtGui.QPainter.Antialiasing)

        # draw base
        basic_rect = self.rect().adjusted(5, 5, -5, -5)
        painter.setBrush(QtGui.QBrush(QtGui.QColor(self.dark)))
        painter.drawEllipse(basic_rect)

        # draw arc
        pen = QtGui.QPen(QtGui.QColor(self.light))
        pen.setWidth(8)
        pen.setCapStyle(QtCore.Qt.RoundCap)
        painter.setPen(pen)
        arc_rect = basic_rect.adjusted(13, 13, -13, -13)
        painter.drawEllipse(arc_rect)

        # draw active arc
        pen.setColor(QtGui.QColor(self.color))
        start_angle = 90
        span_angle = self.percent_to_angle(self.percent)
        painter.setPen(pen)
        painter.drawArc(arc_rect, start_angle * 16, span_angle * 16)

        # draw text

        # draw module name
        painter.setPen(QtGui.QPen(QtGui.QColor(QtCore.Qt.white)))
        font = QtGui.QFont()
        font.setPixelSize(30)
        painter.setFont(font)
        arc_rect.moveTop(5)
        painter.drawText(arc_rect, QtCore.Qt.AlignCenter, self.module_name)

        # draw postfix
        font = QtGui.QFont()
        font.setPixelSize(15)
        painter.setFont(font)
        arc_rect.moveTop(-19)
        painter.drawText(
            arc_rect, QtCore.Qt.AlignCenter | QtCore.Qt.AlignBottom, self.postfix
        )

        # draw percents
        font = QtGui.QFont()
        font.setPixelSize(14)
        painter.setFont(font)
        painter.setPen(QtGui.QPen(self.color))
        arc_rect.moveTop(5)
        painter.drawText(
            arc_rect,
            QtCore.Qt.AlignCenter | QtCore.Qt.AlignBottom,
            f"{self.percent:.2f} %",
        )

    def percent_to_angle(self, percent):
        return -percent / 100 * 360
Ejemplo n.º 19
0
class Test(QtWidgets.QWidget):
	def __init__(self, parent=None):
		super(Test, self).__init__(parent)
		self.angle = 150
		self.setBackgroundRole(QtGui.QPalette.Dark)
		self.setAutoFillBackground(True)
		self.value = 0
		self.maxValue = 7000

	def sizeHint(self):
		return QtCore.QSize(400, 400)

	@QtCore.Slot(int)
	def setDialValue(self, _val):
		self.value = _val

		self.angle = 150 + (210 * self.value) // self.maxValue

		self.update()

	@QtCore.Slot(int)
	def setRotationAngle(self, _angle):
		self.angle = _angle
		self.update()

	def paintEvent(self, event):
		painter = QtGui.QPainter(self)
		painter.setWindow(-200, -200, 400, 400)
		painter.setRenderHint(QtGui.QPainter.Antialiasing, True)
		painter.drawRect(-195, -195, 390, 390)

		# Test
		painter.drawLine(-200, 0, 200, 0)
		painter.drawLine(0, -200, 0, 200)
		radius = 90

		painter.save()
		# painter.rotate(360 - self.angle)
		painter.rotate(self.angle)

		palette = QtGui.QPalette()

		needleBaseWidth = 5
		needlePath = QtGui.QPainterPath()
		needlePath.moveTo(0, needleBaseWidth)
		needlePath.lineTo(radius, 0)
		needlePath.lineTo(0, -needleBaseWidth)
		needlePath.lineTo(0, needleBaseWidth)
		# needlePath.closeSubpath()

		painter.fillPath(needlePath, QtGui.QBrush(QtGui.QColor("lime")))
		painter.setPen(QtGui.QPen(QtCore.Qt.NoPen))
		painter.setBrush(palette.brush(QtGui.QPalette.Active, QtGui.QPalette.Light))
		painter.drawPath(needlePath)
		# painter.setPen(QtGui.QPen(QtCore.Qt.red, 3))
		# painter.drawPoint(0, 0)
		# painter.drawPoint(radius, 0)
		painter.restore()
		origin = QtCore.QPoint(-90, -90)
		width = 180
		height = 180

		penWidth = 20
		painter.setPen(QtGui.QPen(QtGui.QColor("lime"), penWidth))
		painter.drawArc(origin.x(), origin.y(), width, height, 16 * 210, -16 * (210 - 60))

		painter.setPen(QtGui.QPen(QtGui.QColor("yellow"), penWidth))
		painter.drawArc(origin.x(), origin.y(), width, height, 16 * 61, -16 * (60 - 20))
		
		painter.setPen(QtGui.QPen(QtGui.QColor("red"), penWidth))
		painter.drawArc(origin.x(), origin.y(), width, height, 0, 16 * 19)

		# Write value text
		font = painter.font()
		font.setPixelSize(24)
		painter.setFont(font)

		
		rect = QtCore.QRect(1, 10, 95, 25)
		painter.setBrush(QtCore.Qt.black)
		painter.setPen(QtGui.QPen(QtCore.Qt.NoPen))
		painter.drawRect(1, 10, 95, 25)

		painter.setPen(QtGui.QPen(QtGui.QColor("lime"), 2))
		painter.drawText(rect, QtCore.Qt.AlignRight, f"{self.value}")

		# painter.drawText()


	rotationAngle = QtCore.Property(int, fset=setRotationAngle)
Ejemplo n.º 20
0
class MaterialCheckBox(QtWidgets.QWidget):
    clicked = QtCore.Signal()
    toggled = QtCore.Signal(bool)

    def __init__(self, parent=None):
        super(MaterialCheckBox, self).__init__(parent)
        self._is_checked = False
        style = QtWidgets.QApplication.style()
        icon = style.standardIcon(QtWidgets.QStyle.SP_DialogApplyButton)
        checkedIcon = MaterialIcon(self, icon.pixmap(24, 24))
        icon = style.standardIcon(QtWidgets.QStyle.SP_DialogCancelButton)
        uncheckedIcon = MaterialIcon(self, icon.pixmap(24, 24))

        stateMachine = QtCore.QStateMachine(self)

        checkedState = QtCore.QState()
        checkedState.assignProperty(self, b"checked", True)
        checkedState.assignProperty(checkedIcon, b"opacity", 1.0)
        checkedState.assignProperty(uncheckedIcon, b"opacity", 0.0)

        uncheckedState = QtCore.QState()
        uncheckedState.assignProperty(self, b"checked", False)
        uncheckedState.assignProperty(checkedIcon, b"opacity", 0.0)
        uncheckedState.assignProperty(uncheckedIcon, b"opacity", 1.0)

        stateMachine.addState(checkedState)
        stateMachine.addState(uncheckedState)
        stateMachine.setInitialState(uncheckedState)

        duration = 2000

        transition1 = checkedState.addTransition(self.clicked, uncheckedState)
        animation1 = QtCore.QPropertyAnimation(checkedIcon,
                                               b"opacity",
                                               self,
                                               duration=duration)
        transition1.addAnimation(animation1)
        animation2 = QtCore.QPropertyAnimation(uncheckedIcon,
                                               b"opacity",
                                               self,
                                               duration=duration)
        transition1.addAnimation(animation2)

        transition2 = uncheckedState.addTransition(self.clicked, checkedState)
        animation3 = QtCore.QPropertyAnimation(checkedIcon,
                                               b"opacity",
                                               self,
                                               duration=duration)
        transition2.addAnimation(animation3)
        animation4 = QtCore.QPropertyAnimation(uncheckedIcon,
                                               b"opacity",
                                               self,
                                               duration=duration)
        transition2.addAnimation(animation4)

        stateMachine.start()

    def sizeHint(self):
        return QtCore.QSize(24, 24)

    def isChecked(self):
        return self._is_checked

    def setChecked(self, value):
        if self._is_checked != value:
            self._is_checked = value
            self.toggled.emit(self._is_checked)

    checked = QtCore.Property(bool,
                              fget=isChecked,
                              fset=setChecked,
                              notify=toggled)

    def mousePressEvent(self, event):
        self.clicked.emit()
        self.update()
        super(MaterialCheckBox, self).mousePressEvent(event)
Ejemplo n.º 21
0
class HistoryListModel(QtCore.QAbstractListModel):
    # Store role constants that are used as object keys in JS
    __TRACK_TITLE_ROLE = QtCore.Qt.UserRole  # UserRole means custom role
    __ARTIST_NAME_ROLE = QtCore.Qt.UserRole + 1
    __TIMESTAMP_ROLE = QtCore.Qt.UserRole + 2
    __LASTFM_IS_LOVED_ROLE = QtCore.Qt.UserRole + 3
    __ALBUM_IMAGE_URL_ROLE = QtCore.Qt.UserRole + 4
    __HAS_LASTFM_DATA = QtCore.Qt.UserRole + 5

    def __init__(
            self,
            parent=None):  # parent=None because it isn't within another list
        QtCore.QAbstractListModel.__init__(self, parent)

        # Store reference to application view model
        self.__history_reference = None

    def __scrobble_album_image_changed(self, row):
        '''Tell Qt that the scrobble album image has changed'''

        # Create a QModelIndex from the row index
        index = self.createIndex(row, 0)

        # Use list model dataChanged signal to indicate that UI needs to be updated at index
        self.dataChanged.emit(
            index, index,
            [self.__ALBUM_IMAGE_URL_ROLE, self.__HAS_LASTFM_DATA
             ])  # index twice because start and end range

    def __scrobble_lastfm_is_loved_changed(self, row):
        '''Tell the Qt that the track loved status has changed'''

        index = self.createIndex(row, 0)
        self.dataChanged.emit(index, index, [self.__LASTFM_IS_LOVED_ROLE])

    # --- Qt Property Getters and Setters ---

    def get_history_reference(self):
        return self.__history_reference

    def set_history_reference(self, new_reference):
        # Only change the view model reference if there is a new one (don't reload when closing the app)
        if new_reference:
            # Tell the list model that the entirety of the list will be replaced (not just change one item)
            self.beginResetModel()
            self.__history_reference: HistoryViewModel = new_reference
            self.endResetModel()

            # Tell Qt that a new row at the top of the list will be added
            self.__history_reference.pre_append_scrobble.connect(
                lambda: self.beginInsertRows(QtCore.QModelIndex(), 0, 0)
            )  # 0 and 0 are start and end indices

            # Tell Qt that a row has been added
            self.__history_reference.post_append_scrobble.connect(
                lambda: self.endInsertRows())

            # Tell Qt that we are beginning and ending a full refresh of the model
            self.__history_reference.begin_refresh_history.connect(
                lambda: self.beginResetModel())
            self.__history_reference.end_refresh_history.connect(
                lambda: self.endResetModel())

            # Connect row data changed signals
            self.__history_reference.scrobble_album_image_changed.connect(
                self.__scrobble_album_image_changed)
            self.__history_reference.scrobble_lastfm_is_loved_changed.connect(
                self.__scrobble_lastfm_is_loved_changed)

    # --- QAbstractListModel Implementation ---

    def roleNames(self):
        '''Create a mapping of our enum ints to their JS object key names'''

        # Only the track title, artist name, and timestamp attributes of a scrobble will be displayed in scrobble history item views
        # Use binary strings because C++ requires it
        return {
            self.__TRACK_TITLE_ROLE: b'trackTitle',
            self.__ARTIST_NAME_ROLE: b'artistName',
            self.__ALBUM_IMAGE_URL_ROLE: b'albumImageUrl',
            self.__LASTFM_IS_LOVED_ROLE: b'lastfmIsLoved',
            self.__TIMESTAMP_ROLE: b'timestamp',
            self.__HAS_LASTFM_DATA: b'hasLastfmData'
        }

    def rowCount(self, parent=QtCore.QModelIndex()):
        '''Return the number of rows in the scrobble history list'''

        # Prevent value from being returned if the list has a parent (is inside another list)
        if self.__history_reference and not parent.isValid():
            return len(self.__history_reference.scrobble_history)

        return 0

    def data(
        self,
        index,
        role=QtCore.Qt.DisplayRole
    ):  # DisplayRole is a default role that returns the fallback value for the data function
        '''Provide data about items to each delegate view in the list'''

        if (not self.__history_reference

                # Prevent checking for value if it's outside the current range of the list
                or not index.isValid()):
            return

        scrobble = self.__history_reference.scrobble_history[index.row()]

        if role == self.__TRACK_TITLE_ROLE:
            return scrobble.track_title
        elif role == self.__ARTIST_NAME_ROLE:
            return scrobble.artist_name
        elif role == self.__ALBUM_IMAGE_URL_ROLE:
            return scrobble.image_set.small_url if scrobble.image_set else ''
        elif role == self.__LASTFM_IS_LOVED_ROLE:
            return scrobble.lastfm_track.is_loved if scrobble.lastfm_track else False
        elif role == self.__TIMESTAMP_ROLE:
            return scrobble.timestamp.strftime('%-m/%-d/%y %-I:%M:%S %p')
        elif role == self.__HAS_LASTFM_DATA:
            return scrobble.lastfm_track is not None

        # Return no data if we don't have a reference to the scrobble history view model
        return None

    # --- Qt Properties ---

    # Allow the __history_reference to be set in the view
    historyReference = QtCore.Property(HistoryViewModel, get_history_reference,
                                       set_history_reference)
Ejemplo n.º 22
0
class OnboardingViewModel(QtCore.QObject):
    # Qt Property signals
    has_error_changed = QtCore.Signal()
    auth_url_changed = QtCore.Signal()
    current_page_index_changed = QtCore.Signal()
    selected_media_player_changed = QtCore.Signal()

    # Signals handled by QML
    openUrl = QtCore.Signal(str)

    def __init__(self) -> None:
        QtCore.QObject.__init__(self)

        self.__application_reference: ApplicationViewModel = None
        self.reset_state()

    def reset_state(self) -> None:
        # Keep track of errors, True if Last.fm can't get a session key (auth token wasn't authorized by user)
        self.__has_error = None

        # Keep track of which onboarding page is showing
        self.__current_page_index = 0

        # Store media player preference, will be written to preferences table in db
        self.__selected_media_player = ''

        # Store Last.fm authorization url and auth token to share between methods
        self.__auth_url = None
        self.__auth_token = None

        # Store Last.fm session until we're ready to submit it to the ApplicationViewModel
        self.__session = None

    # --- Qt Property Getters and Setters ---

    def set_application_reference(self,
                                  new_reference: ApplicationViewModel) -> None:
        if not new_reference:
            return

        self.__application_reference = new_reference
        self.__application_reference.openOnboarding.connect(self.__handle_open)

    def set_has_error(self, has_error):
        self.__has_error = has_error
        self.has_error_changed.emit()

    def set_current_page(self, page):
        self.__current_page_index = page

        if page == 0:  # Welcome page
            # Reset error state if you go back to the welcome page
            self.set_has_error(False)

        if page == 1:  # Connecting page
            # Immediately fetch and open the authorization url
            self.openNewAuthorizationUrl()

        self.current_page_index_changed.emit()

    def set_selected_media_player(self, media_player_name):
        self.__selected_media_player = media_player_name
        self.selected_media_player_changed.emit()

    # --- Slots ---

    @QtCore.Slot()
    def openNewAuthorizationUrl(self) -> None:
        '''Get an auth token, then generate and open the Last.fm user authorization url'''

        self.set_has_error(False)

        # Save the auth token for later use getting a Last.fm session
        self.__auth_token = self.__application_reference.lastfm.get_auth_token(
        )

        # Save the authorization url so that it can be displayed in the UI
        self.__auth_url = self.__application_reference.lastfm.generate_authorization_url(
            self.__auth_token)
        self.auth_url_changed.emit()

        # Open the authorization url by calling a QML signal that launches the user's web browser
        self.openUrl.emit(self.__auth_url)

    @QtCore.Slot()
    def handleTryAuthenticating(self) -> None:
        '''Try getting a Last.fm session after the user authorizes the auth token in their browser'''

        try:
            # Get and store a new session from Last.fm
            self.__session = self.__application_reference.lastfm.get_session(
                self.__auth_token)
        except:
            # The auth token wasn't authorized by the user
            self.__auth_url = None
            self.auth_url_changed.emit()
            self.set_has_error(True)
            return

        # Continue to choose media player page if there wasn't an error
        self.set_current_page(2)

    @QtCore.Slot()
    def handleFinish(self) -> None:
        '''Tell ApplicationViewModel to log in'''

        self.__application_reference.log_in_after_onboarding(
            self.__session, self.__selected_media_player)

    # --- Private Methods ---

    def __handle_open(self) -> None:
        '''Reset state when onboarding opens and call signals to update UI'''

        self.reset_state()
        self.has_error_changed.emit()
        self.auth_url_changed.emit()
        self.current_page_index_changed.emit()

    # --- Qt Properties ---

    applicationReference = QtCore.Property(
        type=ApplicationViewModel,
        fget=lambda self: self.__application_reference,
        fset=set_application_reference)

    hasError = QtCore.Property(type=bool,
                               fget=lambda self: self.__has_error,
                               fset=set_has_error,
                               notify=has_error_changed)

    authUrl = QtCore.Property(type=str,
                              fget=lambda self: self.__auth_url,
                              notify=auth_url_changed)

    currentPageIndex = QtCore.Property(
        type=int,
        fget=lambda self: self.__current_page_index,
        fset=set_current_page,
        notify=current_page_index_changed)

    selectedMediaPlayer = QtCore.Property(
        type=str,
        fget=lambda self: self.__selected_media_player,
        fset=set_selected_media_player,
        notify=selected_media_player_changed)
Ejemplo n.º 23
0
class TodosViewModel(QtCore.QObject):
    is_valid_changed = QtCore.Signal()
    new_todo_name_changed = QtCore.Signal()
    todo_count_changed = QtCore.Signal()
    pre_insert_todo = QtCore.Signal(int)
    post_insert_todo = QtCore.Signal()
    pre_clear_todos = QtCore.Signal()
    post_clear_todos = QtCore.Signal()
    todo_is_checked_changed = QtCore.Signal(int)

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

        self.__new_todo_name = ''
        self.todos = []

    def get_new_todo_name(self):
        return self.__new_todo_name

    def set_new_todo_name(self, value):
        self.__new_todo_name = value
        self.is_valid_changed.emit()
        self.new_todo_name_changed.emit()

    def get_is_valid(self):
        return len(self.__new_todo_name) > 0

    def clear_todos(self):
        # Tell the ListView that we are going to clear all todos
        self.pre_clear_todos.emit()

        self.todos = []
        self.todo_count_changed.emit()

        # Tell the ListView that we are finished clearing all todos
        self.post_clear_todos.emit()

    # A slot is a function we can use in JS in QML
    @QtCore.Slot()
    def toggleFirstTodo(
            self):  # Camel case because it will be used as a js function
        # Make sure there is a first todo
        if len(self.todos) > 0:
            todo = self.todos[0]
            todo.is_checked = not todo.is_checked  # Toggle the is_checked property
            self.todo_is_checked_changed.emit(
                0)  # 0 is the index of the changed todo

    @QtCore.Slot()
    def addTodo(self):
        # Only add a todo if the field validation passes
        if self.get_is_valid():
            # Tell the ListView that we are going to add a todo
            self.pre_insert_todo.emit(
                len(self.todos
                    ))  # Index of new todo is the current length of the list

            # Create a Todo instance with the current value of the input field
            todo = Todo(self.__new_todo_name)
            self.todos.append(todo)
            self.todo_count_changed.emit()

            # Clear the input field
            self.set_new_todo_name('')

            # Tell the ListView that we finished adding a todo
            self.post_insert_todo.emit()

    # Provide an interface through the Qt Property to communicate with the QML components
    newTodoName = QtCore.Property(str,
                                  get_new_todo_name,
                                  set_new_todo_name,
                                  notify=new_todo_name_changed)
    isValid = QtCore.Property(bool, get_is_valid, notify=is_valid_changed)
class ApplicationViewModel(QtCore.QObject):
  # Qt Property changed signals
  is_logged_in_changed = QtCore.Signal()
  is_offline_changed = QtCore.Signal()

  # JS event signals
  openOnboarding = QtCore.Signal()
  closeOnboarding = QtCore.Signal()
  
  # Signals handled from QML
  isInMiniModeChanged = QtCore.Signal()
  showNotification = QtCore.Signal(str, str)

  def __init__(self) -> None:
    QtCore.QObject.__init__(self)
    
    # Initialize helper classes
    self.lastfm = LastfmApiWrapper()
    self.spotify_api = SpotifyApiWrapper()
    self.art_provider = ArtProvider(self.lastfm, self.spotify_api)
    self.is_logged_in = False
    self.is_offline = False

    # Store whether the app is in mini mode
    self.__is_in_mini_mode: bool = None
    
    # Create network request manager and expose it to all NetworkImage instances
    self.network_manager = QtNetwork.QNetworkAccessManager()
    NetworkImage.NETWORK_MANAGER = self.network_manager

    # Connect to SQLite
    db_helper.connect()

  def log_in_after_onboarding(self, session: LastfmSession, media_player_preference: str) -> None:
    '''Save new login details to db, log in, and close onboarding'''

    self.lastfm.log_in_with_session(session)

    # Save Last.fm details and app preferences to the database
    db_helper.save_lastfm_session_to_database(session)
    db_helper.save_default_preferences_to_database(media_player_preference)

    # Close onboarding and start app
    self.__set_is_logged_in(True)
    self.closeOnboarding.emit()

  def update_is_offline(self) -> None:
    try:
      requests.get('https://1.1.1.1')
      self.__set_is_offline(False)
    except requests.exceptions.ConnectionError:
      self.__set_is_offline(True)

  # --- Slots ---

  @QtCore.Slot()
  def attemptLogin(self) -> None:
    '''Try to log in from database and open onboarding if they don't exist'''

    # Try to get session key and username from database
    session = db_helper.get_lastfm_session()

    if session:
      # Set Last.fm wrapper session key and username from database
      self.lastfm.log_in_with_session(session)
      self.__set_is_logged_in(True)
      logging.info(f'Logged in as {session.username}')
    else:
      self.openOnboarding.emit()
  
  @QtCore.Slot()
  def toggleMiniMode(self) -> None:
    self.__is_in_mini_mode = not self.__is_in_mini_mode
    self.isInMiniModeChanged.emit()
    db_helper.set_preference('is_in_mini_mode', self.__is_in_mini_mode)

  # --- Private Methods ---

  def __set_is_logged_in(self, is_logged_in: bool) -> None:
    if is_logged_in:
      # Load mini moce preference from database
      self.__is_in_mini_mode = db_helper.get_preference('is_in_mini_mode')
      self.isInMiniModeChanged.emit()

    self.is_logged_in = is_logged_in
    self.is_logged_in_changed.emit()

  def __set_is_offline(self, is_offline: bool) -> None:
    self.is_offline = is_offline
    self.is_offline_changed.emit()
  
  # --- Qt Properties ---
  
  isInMiniMode = QtCore.Property(
    type=bool,
    fget=lambda self: self.__is_in_mini_mode,
    notify=isInMiniModeChanged
  )

  isLoggedIn = QtCore.Property(
    type=bool,
    fget=lambda self: self.is_logged_in,
    notify=is_logged_in_changed
  )
Ejemplo n.º 25
0
class DetailsViewModel(QtCore.QObject):
    # Qt Property changed signals
    scrobble_changed = QtCore.Signal()
    is_player_paused_changed = QtCore.Signal()
    media_player_name_changed = QtCore.Signal()
    is_offline_changed = QtCore.Signal()

    def __init__(self) -> None:
        QtCore.QObject.__init__(self)

        # Store a reference to the scrobble history view model instance that provides data
        self.__history_reference: HistoryViewModel = None

        # Store a reference to the application view model
        self.__application_reference: ApplicationViewModel = None

    # --- Qt Property Getters and Setters ---

    def set_history_reference(self, new_reference: HistoryViewModel) -> None:
        if not new_reference:
            return

        self.__history_reference = new_reference

        # Pass through signals from history view model
        self.__history_reference.selected_scrobble_changed.connect(
            lambda: self.scrobble_changed.emit())
        self.__history_reference.is_player_paused_changed.connect(
            lambda: self.is_player_paused_changed.emit())
        self.__history_reference.media_player_name_changed.connect(
            lambda: self.media_player_name_changed.emit())

        # Update details view immediately after connecting
        self.scrobble_changed.emit()
        self.media_player_name_changed.emit()

    def set_application_reference(self,
                                  new_reference: ApplicationViewModel) -> None:
        if not new_reference:
            return

        self.__application_reference = new_reference

        # Pass through signal from application view model
        self.__application_reference.is_offline_changed.connect(
            lambda: self.is_offline_changed.emit())

    # --- Qt Properties ---

    applicationReference = QtCore.Property(
        type=ApplicationViewModel,
        fget=lambda self: self.__application_reference,
        fset=set_application_reference)

    historyReference = QtCore.Property(
        type=HistoryViewModel,
        fget=lambda self: self.__history_reference,
        fset=set_history_reference)

    scrobble = QtCore.Property(
        type='QVariant',
        fget=lambda self:
        (asdict(self.__history_reference.selected_scrobble)
         if self.__history_reference.selected_scrobble else None)
        if self.__history_reference else None,
        notify=scrobble_changed)

    isCurrentScrobble = QtCore.Property(
        type=bool,
        fget=lambda self:
        (self.__history_reference.get_selected_scrobble_index() == -1
         if self.__history_reference else None),
        notify=scrobble_changed)

    isOffline = QtCore.Property(
        type=bool,
        fget=(lambda self: self.__application_reference.is_offline
              if self.__application_reference else None),
        notify=is_offline_changed)

    isPlayerPaused = QtCore.Property(type=bool,
                                     fget=lambda self:
                                     (self.__history_reference.is_player_paused
                                      if self.__history_reference else None),
                                     notify=is_player_paused_changed)

    mediaPlayerName = QtCore.Property(type=str,
                                      fget=lambda self:
                                      (self.__history_reference.mediaPlayerName
                                       if self.__history_reference else None),
                                      notify=media_player_name_changed)
Ejemplo n.º 26
0
class TodosListModel(QtCore.QAbstractListModel):
    NAME_ROLE = QtCore.Qt.UserRole  # UserRole means custom role which means custom object key in JS
    IS_CHECKED_ROLE = QtCore.Qt.UserRole + 1  # Add one because it needs a unique enum value

    def __init__(self, parent=None):
        QtCore.QAbstractListModel.__init__(self, parent)

        self.__todos_reference = None

    def get_todo_count(self):
        if self.__todos_reference:
            return len(self.__todos_reference.todos)

        return 0

    def get_todos_reference(self):
        return self.__todos_reference

    def todo_is_checked_changed_callback(self, row):
        # Create a QModelIndex from the row number
        # createIndex is a ListModel class method
        index = self.createIndex(row, 0)  # Column is zero

        # Use the ListModel dataChanged signal to indicate that the UI needs to be updated at this index
        self.dataChanged.emit(index, index, [
            self.IS_CHECKED_ROLE
        ])  # Tell the ListModel which row range and roles are being updated

    def set_todos_reference(self, value):
        # Only change the view model if there is a new one (don't reload when closing the app)
        if value:
            # Tell the list model that we are going to replace the entirety of the list (not just change one item)
            # beginResetModel and endResetModel are class methods from the parent
            self.beginResetModel()
            self.__todos_reference = value
            self.endResetModel()

            # Connect signals in TodosViewModel
            # QtCore.QModelIndex() is because we don't have a parent list for the ListModel and we have index twice because we have a start and end index for insertion
            self.__todos_reference.pre_insert_todo.connect(
                lambda index: self.beginInsertRows(QtCore.QModelIndex(), index,
                                                   index))
            self.__todos_reference.post_insert_todo.connect(
                lambda: self.endInsertRows())

            # QtCore.QModelIndex() is because we don't have a parent list for the ListModel
            self.__todos_reference.pre_clear_todos.connect(
                lambda: self.beginRemoveRows(
                    QtCore.QModelIndex(), 0,
                    len(self.__todos_reference.todos) - 1)
            )  # Remove all todos from 0 to end
            self.__todos_reference.post_clear_todos.connect(
                lambda: self.endRemoveRows())

            # Tell the ListModel which row range and roles are being updated
            self.__todos_reference.todo_is_checked_changed.connect(
                self.todo_is_checked_changed_callback)

    # Override the required roleNames method from the parent ListModel class
    # Camel cased because it's a wrapper for a C++ function
    def roleNames(self):
        # Create a mapping of our enum ints to their JS object key names
        # In QML we can use them as model.name or model.isChecked where model is the ListModel
        return {self.NAME_ROLE: b'name', self.IS_CHECKED_ROLE: b'isChecked'}

    # Override the required rowCount method from the parent ListModel class
    def rowCount(self, parent=QtCore.QModelIndex()):
        if self.__todos_reference:
            # Prevents value from being returned if the list has a parent (is inside another list)
            if not parent.isValid():
                return len(self.__todos_reference.todos)

    # Override the required data method from the parent ListModel class
    def data(
        self,
        index,
        role=QtCore.Qt.DisplayRole
    ):  # DisplayRole is a default role (object key in QML) that returns the fallback value for the data function
        # Make sure the view model is connected (don't load in data before the UI loads)
        if self.__todos_reference:
            # Make sure the index is within the range of the row count (in the list)
            if index.isValid():
                # Get the data at the index for the ListModel to display
                todo = self.__todos_reference.todos[index.row(
                )]  # index is an object with row and column methods

                # Return the type of data that was requested based on the role enum values
                if role == self.NAME_ROLE:
                    return todo.name
                elif role == self.IS_CHECKED_ROLE:
                    return todo.is_checked

        # This is for DisplayRole or if the todos reference doesn't exist
        return None

    # Override the required setData method from the parent ListModel class
    def setData(self, index, value, role):
        # Make sure there is a reference to the TodosViewModel
        # Only allow changes to the checked value, we can't rename todos from within the todo item UI
        if self.__todos_reference and role == self.IS_CHECKED_ROLE:
            todo = self.__todos_reference.todos[
                index.row()]  # index is an object with row and column methods
            todo.is_checked = value

            # Use the ListModel dataChanged signal to indicate that the UI needs to be updated at this index
            # dataChanged requires two QModelIndex arguments and because we are overriding a default class method, they are already in the right format
            self.dataChanged.emit(
                index, index, [self.IS_CHECKED_ROLE]
            )  # Tell the ListModel which row range and roles are being updated

            return True

        return False

    # A slot is a function we can use in JS in QML
    @QtCore.Slot()
    def clearTodos(self):
        # Run the clear_todos function from the TodosViewModel in Python
        self.__todos_reference.clear_todos()

    todosReference = QtCore.Property(TodosViewModel, get_todos_reference,
                                     set_todos_reference)
Ejemplo n.º 27
0
class NetworkImage(QtQuick.QQuickItem):
    has_image_changed = QtCore.Signal()
    should_blank_on_new_source_changed = QtCore.Signal()
    source_changed = QtCore.Signal()

    NETWORK_MANAGER = None
    RAM_IMAGE_CACHE = {}

    def __init__(self, parent=None):
        QtQuick.QQuickItem.__init__(self, parent)

        self.__image = None
        self.__reply = None

        # Store Qt Scene Graph node that will display texture
        self.__node = None

        # This is set after _image is replaced with another source, and causes _image to be reconverted to a texture on the next updatePaintNode call
        self.__should_refresh_node_texture = False

        # Internal variables for Qt Properties
        self.__has_image = False  # Trying to check _image for None causes unintended behavior because of Python to C++ translation, so a separate variable is needed to check whether an image exists
        self.__should_blank_on_new_source = False
        self.__source: str = None

        # Tell Qt that this component should render onscreen
        self.setFlag(QtQuick.QQuickItem.ItemHasContents, True)

    def updatePaintNode(self, old_node, data):
        if self.__has_image:
            if self.__node is None:
                self.__node = QtQuick.QSGNode()
                new_texture_node = QtQuick.QSGSimpleTextureNode()
                self.__node.appendChildNode(new_texture_node)

            texture_node = self.__node.firstChild()

            if self.__should_refresh_node_texture:
                new_texture = self.window().createTextureFromImage(
                    self.__image)
                texture_node.setFiltering(QtQuick.QSGTexture.Linear)
                texture_node.setTexture(new_texture)
                self.__should_refresh_node_texture = False

            # Get size values for aspect ratio calculation
            bounding_rect = self.boundingRect()
            texture_size = texture_node.texture().textureSize()

            # Make the texture node (which is a child of the main node) fill the full component bounds
            texture_node.setRect(bounding_rect)

            if (
                    texture_size.width() > texture_size.height()
            ):  # Account for Spotify images that are wider than their container
                # Calculate portion of texture to use to correct for container's aspect ratio
                slice_width = (bounding_rect.width() /
                               bounding_rect.height()) * texture_size.height()
                slice_x = (texture_size.width() / 2) - (slice_width / 2)

                # Use calculated slice as area of texture to apply to the texture node
                texture_node.setSourceRect(slice_x, 0, slice_width,
                                           texture_size.height())
            else:  # Account for album images being applied to a wider container
                slice_height = (bounding_rect.height() /
                                bounding_rect.width()) * texture_size.width()
                slice_y = (texture_size.height() / 2) - (slice_height / 2)
                texture_node.setSourceRect(0, slice_y, texture_size.width(),
                                           slice_height)

        return self.__node

    def update_image(self, image):
        '''Refresh the paint node with new image'''

        self.__image = image
        self.__has_image = True
        self.__should_refresh_node_texture = True
        self.has_image_changed.emit()

        # Request update of paint node
        self.update()

    def handle_reply(self):
        '''Convert recieved network data into QImage and update'''

        if self.__reply:
            if self.__reply.error() == QtNetwork.QNetworkReply.NoError:
                image = QtGui.QImage.fromData(self.__reply.readAll())

                # Add image to cache if not in cache
                if self.__source not in NetworkImage.RAM_IMAGE_CACHE:
                    NetworkImage.RAM_IMAGE_CACHE[self.__source] = image

                self.update_image(image)

        # Delete reply as it's not needed anymore
        self.__reply = None

    def set_should_blank_on_new_source(self, value):
        self.__should_blank_on_new_source = value
        self.should_blank_on_new_source_changed.emit()

    def set_source(self, value):
        # Don't do anything if source is changed to the same value
        if not NetworkImage.NETWORK_MANAGER or value == self.__source:
            return

        if not value:
            return

        self.__source = value

        if self.__should_blank_on_new_source:
            self.__has_image = False
            self.has_image_changed.emit()

        # Cancel previous ongoing request if exists
        if self.__reply:
            self.__reply.abort()

        if self.__source in NetworkImage.RAM_IMAGE_CACHE:
            # Immediately set image to cached version if exists
            self.update_image(NetworkImage.RAM_IMAGE_CACHE[self.__source])
        else:
            # If cached image doesn't exist, tell network manager to request from source
            self.__reply = NetworkImage.NETWORK_MANAGER.get(
                QtNetwork.QNetworkRequest(self.__source))
            self.__reply.finished.connect(self.handle_reply)

    # Qt Properties

    hasImage = QtCore.Property(bool,
                               lambda self: self.__has_image,
                               notify=has_image_changed)

    shouldBlankOnNewSource = QtCore.Property(
        bool,
        lambda self: self.__should_blank_on_new_source,
        set_should_blank_on_new_source,
        notify=should_blank_on_new_source_changed
    )  # Controls whether the view should immediately blank or keep showing cached content when a new URL is set. Should be true when the view needs to swap between entirely different images. (e.g. album art view in track details) Needs additional view to cover image view like in Picture component.

    # Set the source of the view. QUrl doesn't allow blank string - only None/undefined.
    source = QtCore.Property('QUrl',
                             lambda self: self.__source,
                             set_source,
                             notify=source_changed)
Ejemplo n.º 28
0
class SkribblWidget(FramelessWindowMixin, QtWidgets.QWidget):
    guess_made = QtCore.Signal(str)
    choice_made = QtCore.Signal(int)
    message_sent = QtCore.Signal(str)
    start_requested = QtCore.Signal(object)

    def __init__(self, client, parent=None):
        """
        :param bin.app.Skribble client: the client linked to this instance
        :param QtWidgets.QObject parent:
        """
        super(SkribblWidget, self).__init__(parent=parent)

        self.client = client
        self._progress = 0

        self.players_wid = None
        self.paint_wid = None
        self.chat_wid = None
        self.choice_dial = None
        self.progress_animation = None

        self.setup_ui()
        self.make_connections()

    def get_progress(self):
        return self._progress

    def set_progress(self, value):
        self._progress = value
        self.time_progressbar.setValue(value)

    progress = QtCore.Property(int, get_progress, set_progress)

    def setup_ui(self):
        super(SkribblWidget, self).setup_ui()

        self.setWindowTitle("BETTER SKRIBBL v0.0.1")
        self.setObjectName("SkribblWidget")
        self.setProperty("elevation", "lowest")

        # Title

        self.title_lbl = QtWidgets.QLabel(self)
        self.title_lbl.setStyleSheet("margin: 0; padding:0;")
        self.title_lbl.setPixmap(
            QtGui.QPixmap(os.path.join(RESSOURCES_DIR, "game_header.png")))
        self.lyt.addWidget(self.title_lbl)

        # Main widget

        self.main_wid = QtWidgets.QWidget(self)
        self.main_wid.setObjectName("main_wid")
        self.main_wid.setProperty("elevation", "low")

        self.main_wid_lyt = QtWidgets.QVBoxLayout(self.main_wid)
        self.main_wid_lyt.setSpacing(16)
        self.main_wid_lyt.setObjectName("main_wid_lyt")
        self.main_wid_lyt.setContentsMargins(16, 16, 16, 16)

        # Progress bar

        self.time_progress_wid = QtWidgets.QWidget(self.main_wid)
        self.time_progress_wid.setObjectName("time_progress_wid")

        self.time_progress_wid_lyt = QtWidgets.QHBoxLayout(
            self.time_progress_wid)
        self.time_progress_wid_lyt.setObjectName("time_progress_wid_lyt")
        self.time_progress_wid_lyt.setContentsMargins(0, 0, 0, 0)

        self.time_progressbar = QtWidgets.QProgressBar(self.time_progress_wid)
        self.time_progressbar.setObjectName("time_progressbar")
        self.time_progressbar.setStyleSheet("color: white;")
        self.time_progressbar.setFont(
            QtGui.QFont("Arial", 16, QtGui.QFont.Bold))
        self.time_progressbar.setFixedHeight(36)
        self.time_progressbar.setMaximum(10000)
        self.time_progressbar.setAlignment(QtCore.Qt.AlignCenter)

        self.time_progress_wid_lyt.addWidget(self.time_progressbar)
        self.main_wid_lyt.addWidget(self.time_progress_wid)

        # Game widget

        self.game_wid = QtWidgets.QWidget(self.main_wid)
        self.game_wid.setObjectName("game_wid")

        self.game_wid_lyt = QtWidgets.QHBoxLayout(self.game_wid)
        self.game_wid_lyt.setSpacing(16)
        self.game_wid_lyt.setObjectName("game_wid_lyt")
        self.game_wid_lyt.setContentsMargins(0, 0, 0, 0)

        # Players widget

        self.players_wid_cont = QtWidgets.QWidget(self.game_wid)
        self.players_wid_cont.setObjectName("players_wid_container")
        self.players_wid_cont.setProperty("elevation", "medium")
        self.players_wid_cont.setLayout(QtWidgets.QVBoxLayout())

        self.game_wid_lyt.addWidget(self.players_wid_cont)

        self.players_wid = plywid.PlayersWidget(self.players_wid_cont)

        self.players_wid_cont.layout().addWidget(self.players_wid)

        # Paint Widget

        self.paint_and_guess_wid = QtWidgets.QWidget(self.game_wid)
        self.paint_and_guess_wid.setObjectName("paint_and_guess_wid")
        self.paint_and_guess_wid.setProperty("elevation", "medium")

        self.paint_and_guess_wid_lyt = QtWidgets.QVBoxLayout(
            self.paint_and_guess_wid)
        self.paint_and_guess_wid_lyt.setObjectName("paint_and_guess_wid_lyt")
        self.paint_and_guess_wid_lyt.setContentsMargins(16, 16, 16, 16)

        self.paint_wid = pntwid.PaintWidget(self.paint_and_guess_wid)
        self.paint_wid.setObjectName("paint_wid")

        self.paint_and_guess_wid_lyt.addWidget(self.paint_wid)

        # Paint overlay widgets

        self.paint_wid_lyt = QtWidgets.QVBoxLayout(self.paint_wid.paint_view)
        self.paint_wid_lyt.setContentsMargins(0, 0, 0, 0)

        self.paint_overlay_wid = QtWidgets.QFrame(self.paint_wid)
        self.paint_overlay_wid.setObjectName("paint_overlay_widget")
        self.paint_overlay_wid.setStyleSheet(
            "QFrame#paint_overlay_widget{"
            "    background-color: rgba(0, 0, 0, 180)}")
        self.paint_overlay_wid.setSizePolicy(
            QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding,
                                  QtWidgets.QSizePolicy.Expanding))
        self.paint_overlay_wid.hide()

        self.paint_wid_lyt.addWidget(self.paint_overlay_wid)

        self.paint_overlay_lyt = QtWidgets.QVBoxLayout(self.paint_overlay_wid)

        self.config_wid = configWidget.ConfigWidget(DEFAULT_CONFIG, self)
        self.config_wid.hide()

        self.paint_overlay_lyt.addWidget(self.config_wid, 0,
                                         QtCore.Qt.AlignCenter)

        self.player_has_found_wid = playerHasFoundWidget.PlayerHasFoundWidget(
            self)
        self.player_has_found_wid.hide()

        self.paint_overlay_lyt.addWidget(self.player_has_found_wid, 0,
                                         QtCore.Qt.AlignCenter)

        self.score_board = scoreBoard.ScoreBoard(self.paint_wid)
        self.score_board.hide()

        self.paint_overlay_lyt.addWidget(self.score_board, 0,
                                         QtCore.Qt.AlignCenter)

        self.guess_lyt = QtWidgets.QHBoxLayout()
        self.guess_lyt.setObjectName("guess_lyt")

        self.guess_lbl = QtWidgets.QLabel(self.paint_and_guess_wid)
        self.guess_lbl.setFont(QtGui.QFont("Arial", 12, QtGui.QFont.Black))
        self.guess_lbl.setObjectName("guess_lbl")
        self.guess_lbl.setText("Make a guess !")

        self.guess_lyt.addWidget(self.guess_lbl)

        self.guess_lne = textbar.HistoryTextBar(self.paint_and_guess_wid)
        self.guess_lne.setObjectName("guess_lne")
        self.guess_lne.setFont(QtGui.QFont("SansSerif", 11))
        self.guess_lne.setPlaceholderText(
            "Ex: Pierre qui roule n'amasse pas mousse")

        self.guess_lyt.addWidget(self.guess_lne)
        self.paint_and_guess_wid_lyt.addLayout(self.guess_lyt)
        self.game_wid_lyt.addWidget(self.paint_and_guess_wid)

        self.chat_wid = chtwid.ChatWidget(self.game_wid)
        self.chat_wid.setObjectName("chat_wid")
        self.chat_wid.setProperty("elevation", "medium")

        self.game_wid_lyt.addWidget(self.chat_wid)
        self.main_wid_lyt.addWidget(self.game_wid)
        self.lyt.addWidget(self.main_wid)

    def make_connections(self):
        super(SkribblWidget, self).make_connections()
        self.config_wid.start_btn.clicked.connect(self.request_start)
        self.guess_lne.text_sent.connect(self.make_guess)
        self.player_has_found_wid.dismiss.connect(
            self.dismiss_player_has_found_widget)

    def request_start(self):
        self.start_requested.emit(self.config_wid.config)

    def on_close(self):
        self.client.close()

    def update_players_from_game(self, game):
        self.players_wid.update_from_game(game)

    def clear_choice_dialog(self):
        if self.choice_dial:
            self.choice_dial.hide()
            self.choice_dial.deleteLater()
            self.choice_dial = None

    def start_config(self, config_dict):
        self.config_wid.config = config_dict.copy()
        self.config_wid.update_from_config()
        self.paint_overlay_wid.show()
        self.config_wid.show()

    def start_choosing(self, choices, time):
        # Conforming the window state
        self.choice_dial = chcdial.ChoiceWidget(self)
        rect = QtCore.QRect(0, 64, self.width(), self.height() - 64)

        self.choice_dial.setGeometry(rect)
        self.choice_dial.load_choices(choices)
        self.choice_dial.choice_made.connect(self.choice_made.emit)

        self.choice_dial.show()

        self.start_progress_timer(time)

    def wait_for_choice(self, time):
        # Conforming the window state
        self.paint_wid.lock()

        self.start_progress_timer(time)

    def start_drawing(self, word, time):
        # Conforming the window state
        self.clear_choice_dialog()
        self.paint_wid.paint_view.clear()
        self.paint_wid.unlock()

        self.set_current_word(word)
        self.start_progress_timer(time)

    def start_guessing(self, preview, time):
        # Conforming the window state
        self.paint_wid.lock()
        self.paint_wid.paint_view.clear()

        self.set_current_word(preview)
        self.start_progress_timer(time)

    def set_current_word(self, word):
        self.time_progressbar.setFormat(word)

    def start_game(self):
        self.paint_overlay_wid.hide()
        self.config_wid.hide()

    def start_round(self):
        self.paint_overlay_wid.hide()
        self.score_board.hide()

    def make_guess(self):
        self.guess_made.emit(self.guess_lne.text())
        self.guess_lne.clear()

    def guess_was_right(self, rank):
        self.player_has_found_wid.set_rank(rank)

        self.paint_overlay_wid.show()
        self.player_has_found_wid.show()

    def dismiss_player_has_found_widget(self):
        self.paint_overlay_wid.hide()
        self.player_has_found_wid.hide()

    def set_player_has_found(self, player_id):
        self.players_wid.set_player_has_found(player_id)

    def set_drawing_player(self, player_id):
        self.players_wid.set_drawing_player(player_id)

    def start_progress_timer(self, time):
        if self.progress_animation:
            self.progress_animation.stop()

        self.progress_animation = QtCore.QPropertyAnimation(self, b"progress")
        self.progress_animation.setStartValue(0)
        self.progress_animation.setEndValue(10000)
        self.progress_animation.setDuration(time)
        self.progress_animation.start()

    def end_round(self, game, word):
        self.player_has_found_wid.hide()
        self.paint_overlay_wid.show()

        self.score_board.update_from_game(game)
        self.score_board.show()

        self.set_current_word(word)
        self.start_progress_timer(game.config["score_time"])
Ejemplo n.º 29
0
class QObjectListModel(QtCore.QAbstractListModel):
    """
    QObjectListModel provides a more powerful, but still easy to use, alternative to using
    QObjectList lists as models for QML views. As a QAbstractListModel, it has the ability to
    automatically notify the view of specific changes to the list, such as adding or removing
    items. At the same time it provides QList-like convenience functions such as append, at,
    and removeAt for easily working with the model from Python.
    """
    ObjectRole = QtCore.Qt.UserRole

    def __init__(self, keyAttrName='', parent=None):
        """ Constructs an object list model with the given parent. """
        super(QObjectListModel, self).__init__(parent)

        self._objects = list()  # Internal list of objects
        self._keyAttrName = keyAttrName
        self._objectByKey = {}
        self.roles = QtCore.QAbstractListModel.roleNames(self)
        self.roles[self.ObjectRole] = b"object"

        self.requestDeletion.connect(self.onRequestDeletion,
                                     QtCore.Qt.QueuedConnection)

    def roleNames(self):
        return self.roles

    def __iter__(self):
        """ Enables iteration over the list of objects. """
        return iter(self._objects)

    def keys(self):
        return self._objectByKey.keys()

    def items(self):
        return self._objectByKey.items()

    def __len__(self):
        return self.size()

    def __bool__(self):
        return self.size() > 0

    def __getitem__(self, index):
        """ Enables the [] operator """
        return self._objects[index]

    def data(self, index, role):
        """ Returns data for the specified role, from the item with the
        given index. The only valid role is ObjectRole.

        If the view requests an invalid index or role, an invalid variant
        is returned.
        """
        if index.row() < 0 or index.row() >= len(self._objects):
            return None
        return self._objects[index.row()]

    def rowCount(self, parent):
        """ Returns the number of rows in the model. This value corresponds to the
        number of items in the model's internal object list.
        """
        return self.size()

    def objectList(self):
        """ Returns the object list used by the model to store data. """
        return self._objects

    def values(self):
        return self._objects

    def setObjectList(self, objects):
        """ Sets the model's internal objects list to objects. The model will
        notify any attached views that its underlying data has changed.
        """
        oldSize = self.size()
        self.beginResetModel()
        for obj in self._objects:
            self._dereferenceItem(obj)
        self._objects = objects
        for obj in self._objects:
            self._referenceItem(obj)
        self.endResetModel()
        self.dataChanged.emit(self.index(0), self.index(self.size() - 1), [])
        if self.size() != oldSize:
            self.countChanged.emit()

    # ######
    # BaseModel API
    # ######
    @property
    def objects(self):
        return self._objectByKey

    @QtCore.Slot(str, result=QtCore.QObject)
    def get(self, key):
        """
        Raises a KeyError if key is not in the map.
        :param key:
        :return:
        """
        return self._objectByKey[key]

    def add(self, obj):
        self.append(obj)

    def pop(self, key):
        obj = self.get(key)
        self.remove(obj)
        return obj

    ############
    # List API #
    ############
    def append(self, obj):
        """ Insert object at the end of the model. """
        self.extend([obj])

    def extend(self, iterable):
        """ Insert objects at the end of the model. """
        self.beginInsertRows(QtCore.QModelIndex(), self.size(),
                             self.size() + len(iterable) - 1)
        [self._referenceItem(obj) for obj in iterable]
        self._objects.extend(iterable)
        self.endInsertRows()
        self.countChanged.emit()

    def insert(self, i, toInsert):
        """  Inserts object(s) at index position i in the model and notifies
        any views. If i is 0, the object is prepended to the model. If i
        is size(), the object is appended to the list.
        Accepts both QObject and list of QObjects.
        """
        if not isinstance(toInsert, list):
            toInsert = [toInsert]
        self.beginInsertRows(QtCore.QModelIndex(), i, i + len(toInsert) - 1)
        for obj in reversed(toInsert):
            self._referenceItem(obj)
            self._objects.insert(i, obj)
        self.endInsertRows()
        self.countChanged.emit()

    @QtCore.Slot(int, result=QtCore.QObject)
    def at(self, i):
        """ Return the object at index i. """
        return self._objects[i]

    def replace(self, i, obj):
        """ Replaces the item at index position i with object and
        notifies any views. i must be a valid index position in the list
        (i.e., 0 <= i < size()).
        """
        self._dereferenceItem(self._objects[i])
        self._referenceItem(obj)
        self._objects[i] = obj
        self.dataChanged.emit(self.index(i), self.index(i), [])

    def move(self, fromIndex, toIndex):
        """ Moves the item at index position from to index position to
        and notifies any views.
        This function assumes that both from and to are at least 0 but less than
        size(). To avoid failure, test that both from and to are at
        least 0 and less than size().
        """
        value = toIndex
        if toIndex > fromIndex:
            value += 1
        if not self.beginMoveRows(QtCore.QModelIndex(), fromIndex, fromIndex,
                                  QtCore.QModelIndex(), value):
            return
        self._objects.insert(toIndex, self._objects.pop(fromIndex))
        self.endMoveRows()

    def removeAt(self, i, count=1):
        """  Removes count number of items from index position i and notifies any views.
        i must be a valid index position in the model (i.e., 0 <= i < size()), as
        must as i + count - 1.
        """
        self.beginRemoveRows(QtCore.QModelIndex(), i, i + count - 1)
        for cpt in range(count):
            obj = self._objects.pop(i)
            self._dereferenceItem(obj)
        self.endRemoveRows()
        self.countChanged.emit()

    def remove(self, obj):
        """ Removes the first occurrence of the given object. Raises a ValueError if not in list. """
        if not self.contains(obj):
            raise ValueError("QObjectListModel.remove(obj) : obj not in list")
        self.removeAt(self.indexOf(obj))

    def takeAt(self, i):
        """  Removes the item at index position i (notifying any views) and returns it.
        i must be a valid index position in the model (i.e., 0 <= i < size()).
        """
        self.beginRemoveRows(QtCore.QModelIndex(), i, i)
        obj = self._objects.pop(i)
        self._dereferenceItem(obj)
        self.endRemoveRows()
        self.countChanged.emit()
        return obj

    def clear(self):
        """ Removes all items from the model and notifies any views. """
        if not self._objects:
            return
        self.beginResetModel()
        for obj in self._objects:
            self._dereferenceItem(obj)
        self._objects = []
        self.endResetModel()
        self.countChanged.emit()

    def update(self, objects):
        self.extend(objects)

    def reset(self, objects):
        self.setObjectList(objects)

    @QtCore.Slot(QtCore.QObject, result=bool)
    def contains(self, obj):
        """ Returns true if the list contains an occurrence of object;
        otherwise returns false.
        """
        return obj in self._objects

    @QtCore.Slot(QtCore.QObject, result=int)
    def indexOf(self, matchObj, fromIndex=0, positive=True):
        """ Returns the index position of the first occurrence of object in
        the model, searching forward from index position from.
        If positive is True, will always return a positive index.
        """
        index = self._objects[fromIndex:].index(matchObj) + fromIndex
        if positive and index < 0:
            index += self.size()
        return index

    def lastIndexOf(self, matchObj, fromIndex=-1, positive=True):
        """    Returns the index position of the last occurrence of object in
        the list, searching backward from index position from. If
        from is -1 (the default), the search starts at the last item.
        If positive is True, will always return a positive index.
        """
        r = list(self._objects)
        r.reverse()
        index = -r[-fromIndex - 1:].index(matchObj) + fromIndex
        if positive and index < 0:
            index += self.size()
        return index

    def size(self):
        """ Returns the number of items in the model. """
        return len(self._objects)

    @QtCore.Slot(result=bool)
    def isEmpty(self):
        """ Returns true if the model contains no items; otherwise returns false. """
        return len(self._objects) == 0

    def _referenceItem(self, item):
        if not item.parent():
            # Take ownership of the object if not already parented
            item.setParent(self)
        if not self._keyAttrName:
            return
        key = getattr(item, self._keyAttrName, None)
        if key is None:
            return
        if key in self._objectByKey:
            raise ValueError("Object key {}:{} is not unique".format(
                self._keyAttrName, key))

        self._objectByKey[key] = item

    def _dereferenceItem(self, item):
        # Ask for object deletion if parented to the model
        if shiboken2.isValid(item) and item.parent() == self:
            # delay deletion until the next event loop
            # This avoids warnings when the QML engine tries to evaluate (but should not)
            # an object that has already been deleted
            self.requestDeletion.emit(item)

        if not self._keyAttrName:
            return
        key = getattr(item, self._keyAttrName, None)
        if key is None:
            return
        assert key in self._objectByKey
        del self._objectByKey[key]

    def onRequestDeletion(self, item):
        item.deleteLater()

    countChanged = QtCore.Signal()
    count = QtCore.Property(int, size, notify=countChanged)

    requestDeletion = QtCore.Signal(QtCore.QObject)
Ejemplo n.º 30
0
class CellWidget(QtWidgets.QPushButton):
    """ A `QPushButton` representing a single cell of chess board.
    CellWidget can be either a chess piece or an empty cell of the
    board. It can be marked with different colors.

    `CellWidget` by default represents an empty cell.
    """

    designated = QtCore.Signal(bool)
    """ Indicates that the setter of `marked` property has been called. """
    def __init__(self, parent=None):
        super().__init__(parent=parent)

        self._piece = None
        self._isInCheck = False
        self._isHighlighted = False
        self._isMarked = False
        self._justMoved = False

        self.setMouseTracking(True)
        self.setObjectName("cell_plain")
        self.setCheckable(False)

    def getPiece(self) -> Optional[chess.Piece]:
        return self._piece

    def isPiece(self) -> bool:
        """ Indicates the type of the cell.
        It is True if the cell is a chess piece
        and False if it is an empty cell.
        """
        return self._piece is not None

    def setPiece(self, piece: Optional[chess.Piece]) -> None:
        """ Sets the content of the cell.

        Parameters
        ----------
        piece : Optional[chess.Piece]
            The piece that will occupy this cell.

            The cell is emptied if the piece is None otherwise its
            object name is set to ``cell_`` + the name of the piece or ``plain``.
            For example the object name of an empty cell will be ``cell_plain`` and
            the object name of a cell occupied by a white pawn will be ``cell_white_pawn``

            Cells containing a piece are checkable, whereas those empty ones are not.
        """

        self._piece = piece

        if self._piece:
            self.setObjectName(
                f"cell_{chess.COLOR_NAMES[piece.color]}_{chess.PIECE_NAMES[piece.piece_type]}"
            )
            self.setCheckable(True)
        else:
            self.setObjectName("cell_plain")
            self.setCheckable(False)

        self.style().unpolish(self)
        self.style().polish(self)

    def isPlain(self) -> bool:
        """ A convenience property indicating if the cell is empty or not. """
        return not self._piece

    def toPlain(self) -> None:
        """ Empties the cell. """
        self.setPiece(None)

    @staticmethod
    def makePiece(piece: chess.Piece) -> "CellWidget":
        """ A static method that creates a `CellWidget` from the given piece.

        Parameters
        ----------
        piece : `chess.Piece`
            The piece that will occupy the cell. Note that the type of
            the piece cannot be NoneType as in the definition of the
            method `setPiece`, because by default cells are created empty.
         """

        assert isinstance(piece, chess.Piece)

        w = CellWidget()
        w.setPiece(piece)
        return w

    def isInCheck(self) -> bool:
        """ Indicates if the cell contains a king in check.

        Warnings
        --------
        Cells that aren't occupied by a king cannot be in check.

        Raises
        -------
        `NotAKingError`
            It is raised when the property is set to True for
            a cell not being occupied by a king.
        """
        return self._isInCheck

    def setInCheck(self, ck: bool) -> None:
        if self._piece and self._piece.piece_type == chess.KING:
            self._isInCheck = ck
            self.style().unpolish(self)
            self.style().polish(self)
        else:
            raise NotAKingError(
                "Trying to (un)check a cell that does not hold a king.")

    def check(self) -> None:
        """ A convenience method that sets the property `isInCheck` to True. """
        self.setInCheck(True)

    def uncheck(self) -> None:
        """ A convenience method that sets the property `isInCheck` to False. """
        self.setInCheck(False)

    def isHighlighted(self) -> bool:
        """ Indicates if the cell is highlighted.
        Highlighted cells are special cells that are used to indicate
        legal moves on the board.
        Highlighted cells are not checkable.
        """
        return self._isHighlighted

    def setHighlighted(self, highlighted: bool) -> None:
        if self._isHighlighted != highlighted:
            self._isHighlighted = highlighted

            if self._isHighlighted:
                self.setCheckable(False)
            else:
                self.setCheckable(bool(self._piece))
            self.style().unpolish(self)
            self.style().polish(self)

    def highlight(self) -> None:
        """ A convenience method that sets the property `highlighted` to True. """
        self.setHighlighted(True)

    def unhighlight(self) -> None:
        """ A convenience method that sets the property `highlighted` to False. """
        self.setHighlighted(False)

    def isMarked(self) -> bool:
        """ Indicates if a cell is marked.
        Marked cells can have a different stylesheet which will visually distinguish
        them from other cells.
        The `designated` signal is emitted when this property's setter is called.
        """
        return self._isMarked

    def setMarked(self, marked: bool) -> None:
        self._isMarked = marked
        self.designated.emit(self._isMarked)
        self.style().unpolish(self)
        self.style().polish(self)

    def mark(self):
        """ A convenience method that sets the property `marked` to True. """
        self.setMarked(True)

    def unmark(self):
        """ A convenience method that sets the property `marked` to False. """
        self.setMarked(False)

    def justMoved(self) -> bool:
        """ Indicates if the piece occupying this cell was just moved from/to this
        cell. """
        return self._justMoved

    def setJustMoved(self, jm: bool):
        self._justMoved = jm
        self.style().unpolish(self)
        self.style().polish(self)

    def mouseMoveEvent(self, e):
        e.ignore()

    piece = QtCore.Property(bool, isPiece, setPiece)
    plain = QtCore.Property(bool, isPlain)
    inCheck = QtCore.Property(bool, isInCheck, setInCheck)
    highlighted = QtCore.Property(bool, isHighlighted, setHighlighted)
    marked = QtCore.Property(bool, isMarked, setMarked, notify=designated)
    justMoved = QtCore.Property(bool, justMoved, setJustMoved)