def test_qCleanupResources(self):
        """
        Test qCleanupResources.
        """

        file_before = QFile(':/oi/svg/document.svg')
        self.assertTrue(file_before.exists())

        qCleanupResources()

        file_after = QFile(':/oi/svg/document.svg')
        self.assertFalse(file_after.exists())
Example #2
0
    def saveFile(self, save_file_name=None):
        if save_file_name == None:
            save_file = QFile(str(STATUS.file))
        else:
            save_file = QFile(str(save_file_name))

        result = save_file.open(QFile.WriteOnly)
        if result:
            LOG.debug(f'---Save file: {save_file.fileName()}')
            save_stream = QTextStream(save_file)
            save_stream << self.toPlainText()
            save_file.close()
        else:
            LOG.debug("---save error")
Example #3
0
    def updateFrameSheet(self):
        if globals.applied_style == 'light':
            f = QFile(':/frameless-light.qss')
        elif globals.applied_style == 'dark':
            f = QFile(':/frameless-dark.qss')
        else:
            raise RuntimeError(
                'Set the app style theme before instantiating ModernWindow')
        f.open(QIODevice.ReadOnly | QIODevice.Text)
        text = QTextStream(f)
        text.setCodec('UTF-8')
        text = text.readAll()

        self.setStyleSheet(text)
        f.close()
Example #4
0
    def fromfile(cls, path):
        """Read a bloom filter from file-object `f' serialized with
        ``BloomFilter.tofile''. """
        f = QFile(path)
        if not f.open(QIODevice.ReadOnly):
            raise ValueError("unable to open file " + path)

        data = QDataStream(f)
        file_fmt = data.readBytes()
        if file_fmt != cls.FILE_FMT:
            raise ValueError('unexpected file format')

        error_rate = data.readFloat()
        num_slices = data.readInt()
        bits_per_slice = data.readInt()
        capacity = data.readInt()
        count = data.readInt()
        bitarray = QBitArray()

        filter = cls(1)  # Bogus instantiation, we will `_setup'.
        filter._setup(error_rate, num_slices, bits_per_slice, capacity, count)
        filter.bitarray = QBitArray()
        data >> filter.bitarray

        return filter
Example #5
0
    def rename(self):
        """renames the selected file or folder"""
        index = self.selectionModel().currentIndex()
        path = self.model.filePath(index)
        if path:
            file_info = QFileInfo(path)

            if file_info.isFile():
                filename = self.rename_dialog("file")

                if filename:
                    q_file = QFile(path)
                    file_info.absolutePath()
                    new_path = os.path.join(file_info.absolutePath(),
                                            str(filename))
                    q_file.rename(new_path)

            elif file_info.isDir():
                filename = self.rename_dialog("directory")

                if filename:
                    directory = QDir(path)
                    file_info.absolutePath()
                    new_path = os.path.join(file_info.absolutePath(),
                                            str(filename))
                    directory.rename(path, new_path)
Example #6
0
    def saveFileAs(self):
        open_file = QFile(str(STATUS.file))
        if open_file == None:
            return

        save_file = self.save_as_dialog(open_file.fileName())
        self.saveFile(save_file)
Example #7
0
 def reload_dark_style(self):
     f = QFile(":/dark_theme.qss")
     f.open(QFile.ReadOnly | QFile.Text)
     ts = QTextStream(f)
     qss = ts.readAll()
     # f = open(Config.get_resource_path('dark_theme.qss', 'resources/ui'), 'r')
     # qss = f.read()
     self.app.setStyleSheet(qss)
Example #8
0
def _load_stylesheet(qt_api=''):
    """
    Load the stylesheet based on QtPy abstraction layer environment variable.

    If the argument is not passed, it uses the current QT_API environment
    variable to make the imports of Qt bindings. If passed, it sets this
    variable then make the imports.

    Args:
        qt_api (str): qt binding name to set QT_API environment variable.
                      Default is ''. Possible values are pyside, pyside2
                      pyqt4, pyqt5. Not case sensitive.

    Note:
        - Note that the variable QT_API is read when first imported. So,
          pay attention to the import order.
        - If you are using another abstraction layer, i.e PyQtGraph to do
          imports on Qt things you must set both to use the same Qt
          binding (PyQt, PySide).
        - OS, binding and binding version number, and application specific
          patches are applied in this order.

    Returns:
        str: stylesheet string (css).
    """

    if qt_api:
        os.environ['QT_API'] = qt_api

    # Import is made after setting QT_API
    from qtpy.QtCore import QCoreApplication, QFile, QTextStream
    from qtpy.QtGui import QColor, QPalette

    # Then we import resources - binary qrc content
    from qdarkstyle import style_rc

    # Thus, by importing the binary we can access the resources
    package_dir = os.path.basename(PACKAGE_PATH)
    qss_rc_path = ":" + os.path.join(package_dir, QSS_FILE)

    # It gets the qss file from compiled style_rc that was import
    # not from the file QSS as we are using resources
    qss_file = QFile(qss_rc_path)

    if qss_file.exists():
        qss_file.open(QFile.ReadOnly | QFile.Text)
        text_stream = QTextStream(qss_file)
        stylesheet = text_stream.readAll()
    else:
        stylesheet = ""
        # Todo: check this raise type and add to docs
        raise FileNotFoundError("Unable to find QSS file '{}' "
                                "in resources.".format(qss_rc_path))

    # 4. Apply palette fix. See issue #139
    _apply_application_patches(QCoreApplication, QPalette, QColor)

    return stylesheet
    def test_qInitResources(self):
        """
        Test qInitResources.

        qInitResources is run during module import, but might be closed by another test so setup call it.
        """

        icon_file = QFile(':/oi/svg/document.svg')
        self.assertTrue(icon_file.exists())
Example #10
0
    def save(self):
        save_file = QFile(self.filename)

        result = save_file.open(QFile.WriteOnly)
        if result:
            save_stream = QTextStream(save_file)
            save_stream << self.text()

            save_file.close()
Example #11
0
 def save_settings(self) -> None:
     """Save Pyslvs settings (auto save when close event)."""
     if self.prefer.not_save_option:
         f = QFile(self.settings.fileName())
         if f.exists():
             f.remove()
         return
     self.settings.setValue("ENV", self.env)
     for field in fields(self.prefer):  # type: Field
         self.settings.setValue(field.name, getattr(self.prefer, field.name))
Example #12
0
 def reload_light_style(self):
     if CONFIG['light_theme_is_native']:
         self.set_style_to_stock()
         return
     f = QFile(":/light_theme.qss")
     f.open(QFile.ReadOnly | QFile.Text)
     ts = QTextStream(f)
     qss = ts.readAll()
     # f = open(Config.get_resource_path('light_theme.qss', 'resources/ui'), 'r')
     # qss = f.read()
     self.app.setStyleSheet(qss)
Example #13
0
    def transferFile(self, src_path):
        dest_path = self.model.filePath(self.rootIndex())

        src_file = QFile()
        src_file.setFileName(src_path)

        src_file_info = QFileInfo(src_path)

        dst_path = os.path.join(dest_path, src_file_info.fileName())

        src_file.copy(dst_path)
Example #14
0
    def newFile(self):
        """Create a new empty file"""
        path = self.model.filePath(self.rootIndex())
        new_file_path = os.path.join(path, "New File.ngc")

        count = 1
        while os.path.exists(new_file_path):
            new_file_path = os.path.join(path, "New File {}.ngc".format(count))
            count += 1

        new_file = QFile(new_file_path)
        new_file.open(QIODevice.ReadWrite)
Example #15
0
    def save(self):
        self.filename = str(STATUS.file)

        save_file = QFile(self.filename)

        result = save_file.open(QFile.WriteOnly)
        if result:
            save_stream = QTextStream(save_file)
            save_stream << self.text()
            save_file.close()
        else:
            print("save error")
Example #16
0
    def save(self):
        save_file = QFile(self.filename)

        result = save_file.open(QFile.WriteOnly)
        if result:
            LOG.debug("---self.text(): {}".format(self.text()))
            save_stream = QTextStream(save_file)
            save_stream << self.text()
            save_file.close()
            self.text_before_edit = ''
            self.somethingHasChanged.emit(False)
        else:
            LOG.debug("---save error")
def _apply_base_theme(app):
    """ Apply base theme to the application.

        Args:
            app (QApplication): QApplication instance.
    """

    if QT_VERSION < (5,):
        app.setStyle('plastique')
    else:
        app.setStyle('Fusion')

    f = QFile(':/style.qss')
    f.open(QIODevice.ReadOnly | QIODevice.Text)
    text = QTextStream(f)
    text.setCodec('UTF-8')
    app.setStyleSheet(text.readAll())
    f.close()
Example #18
0
    def saveAs(self):
        file_name = self.save_as_dialog(self.filename)

        if file_name is False:
            return

        original_file = QFileInfo(self.filename)
        path = original_file.path()

        new_absolute_path = os.path.join(path, file_name)
        new_file = QFile(new_absolute_path)

        result = new_file.open(QFile.WriteOnly)
        if result:
            save_stream = QTextStream(new_file)
            save_stream << self.text()

            new_file.close()
Example #19
0
    def deleteItem(self):
        """Delete the selected item (either a file or folder)."""
        # ToDo: use Move2Trash, instead of deleting the file
        index = self.selectionModel().currentIndex()
        path = self.model.filePath(index)
        if path:
            file_info = QFileInfo(path)
            if file_info.isFile():
                if not self.ask_dialog("Do you wan't to delete the selected file?"):
                    return
                q_file = QFile(path)
                q_file.remove()

            elif file_info.isDir():
                if not self.ask_dialog("Do you wan't to delete the selected directory?"):
                    return
                directory = QDir(path)
                directory.removeRecursively()
Example #20
0
 def tofile(self, path):
     """Write the bloom filter to file object `f'. Underlying bits
     are written as machine values. This is much more space
     efficient than pickling the object."""
     # f.write(pack(self.FILE_FMT, self.error_rate, self.num_slices,
     #              self.bits_per_slice, self.capacity, self.count))
     # f.write(self.bitarray.bits)
     f = QFile(path)
     if f.open(QIODevice.WriteOnly):
         out = QDataStream(f)
         out.writeBytes(self.FILE_FMT)
         out.writeFloat(self.error_rate)
         out.writeInt(self.num_slices)
         out.writeInt(self.bits_per_slice)
         out.writeInt(self.capacity)
         out.writeInt(self.count)
         out << self.bitarray
         f.flush()
         f.close()
Example #21
0
    def saveAs(self):
        file_name = self.save_as_dialog(self.filename)

        if file_name is False:
            print("saveAs file name error")
            return
        self.filename = str(STATUS.file)

        original_file = QFileInfo(self.filename)
        path = original_file.path()

        new_absolute_path = os.path.join(path, file_name)
        new_file = QFile(new_absolute_path)

        result = new_file.open(QFile.WriteOnly)
        if result:
            save_stream = QTextStream(new_file)
            save_stream << self.text()
            new_file.close()
        self.text_before_edit = ''
        self.somethingHasChanged.emit(False)
Example #22
0
def main():
    app = QApplication(sys.argv)

    app.setWindowIcon(QIcon(':/icons/app.svg'))

    fontDB = QFontDatabase()
    fontDB.addApplicationFont(':/fonts/Roboto-Regular.ttf')
    app.setFont(QFont('Roboto'))

    f = QFile(':/style.qss')
    f.open(QFile.ReadOnly | QFile.Text)
    app.setStyleSheet(QTextStream(f).readAll())
    f.close()

    translator = QTranslator()
    translator.load(':/translations/' + QLocale.system().name() + '.qm')
    app.installTranslator(translator)

    mw = MainWindow()
    mw.show()

    sys.exit(app.exec_())
Example #23
0
    def saveAs(self):
        #file_name = self.save_as_dialog(self.filename)
        options = QFileDialog.Options()
        options |= QFileDialog.DontUseNativeDialog
        file_name, _ = QFileDialog.getSaveFileName(self,"Save As","","All Files (*);;NGC Files (*.ngc)", options=options)

        if file_name is False:
            print("saveAs file name error")
            return
        self.filename = str(STATUS.file)

        original_file = QFileInfo(self.filename)
        path = original_file.path()

        new_absolute_path = os.path.join(path, file_name)
        new_file = QFile(new_absolute_path)

        result = new_file.open(QFile.WriteOnly)
        if result:
            save_stream = QTextStream(new_file)
            save_stream << self.text()
            new_file.close()
Example #24
0
    def save(self):
        reload_file = True
        self.filename = str(STATUS.file)
        # determine of have a file name
        if self.filename == '':
            reload_file = False
            self.filename = '/tmp/gcode_tmp.ngc'

        # save out the file
        save_file = QFile(self.filename)
        result = save_file.open(QFile.WriteOnly)
        if result:
            save_stream = QTextStream(save_file)
            save_stream << self.text()
            save_file.close()
            # file save worked, now either load fresh or reload
            if reload_file:
                reLoadProgram()
            else:
                loadProgram(self.filename)

        else:
            print("save error")
Example #25
0
def _load_stylesheet(qt_api='', palette=None):
    """
    Load the stylesheet based on QtPy abstraction layer environment variable.

    If the argument is not passed, it uses the current QT_API environment
    variable to make the imports of Qt bindings. If passed, it sets this
    variable then make the imports.

    Args:
        qt_api (str): qt binding name to set QT_API environment variable.
                      Default is ''. Possible values are pyside, pyside2
                      pyqt4, pyqt5. Not case sensitive.
        palette (Palette): Palette class that inherits from Palette.

    Note:
        - Note that the variable QT_API is read when first imported. So,
          pay attention to the import order.
        - If you are using another abstraction layer, i.e PyQtGraph to do
          imports on Qt things you must set both to use the same Qt
          binding (PyQt, PySide).
        - OS, binding and binding version number, and application specific
          patches are applied in this order.

    Returns:
        str: stylesheet string (css).
    """

    if qt_api:
        os.environ['QT_API'] = qt_api

    # Import is made after setting QT_API
    from qtpy.QtCore import QCoreApplication, QFile, QTextStream
    from qtpy.QtGui import QColor, QPalette
    from qtpy import QT_VERSION

    # Then we import resources - binary qrc content
    if palette is None:
        from qdarkstyle.dark import style_rc
        palette = DarkPalette
        _set_global_paths('dark')
    elif palette.ID == 'dark':
        from qdarkstyle.dark import style_rc
        palette = DarkPalette
        _set_global_paths('dark')
    elif palette.ID == 'light':
        from qdarkstyle.light import style_rc
        palette = LightPalette
        _set_global_paths('light')
    else:
        print("Not recognized ID for palette! Exiting!")
        sys.exit(1)

    # Thus, by importing the binary we can access the resources
    package_dir = os.path.basename(PACKAGE_PATH)
    qss_rc_path = ":" + os.path.join(package_dir, palette.ID, QSS_FILE)

    _logger.debug("Reading QSS file in: %s" % qss_rc_path)

    # It gets the qss file from compiled style_rc that was imported,
    # not from the file QSS as we are using resources
    qss_file = QFile(qss_rc_path)

    if qss_file.exists():
        qss_file.open(QFile.ReadOnly | QFile.Text)
        text_stream = QTextStream(qss_file)
        stylesheet = text_stream.readAll()
        _logger.info("QSS file sucessfuly loaded.")
    else:
        stylesheet = ""
        # Todo: check this raise type and add to docs
        raise FileNotFoundError("Unable to find QSS file '{}' "
                                "in resources.".format(qss_rc_path))

    _logger.debug("Checking patches for being applied.")

    # Todo: check execution order for these functions
    # 1. Apply OS specific patches
    stylesheet += _apply_os_patches(palette)

    # 2. Apply binding specific patches
    stylesheet += _apply_binding_patches()

    # 3. Apply binding version specific patches
    stylesheet += _apply_version_patches(QT_VERSION)

    # 4. Apply palette fix. See issue #139
    _apply_application_patches(QCoreApplication, QPalette, QColor, palette)

    return stylesheet
Example #26
0
 def get_ui_qfile(self, name):
     file = QFile(':/ui/{}'.format(name))
     if not file.exists():
         raise FileNotFoundError('ui file not found: ":/ui/{}"'.format(name))
     file.open(QFile.ReadOnly)
     return file
Example #27
0
def _load_stylesheet(qt_api='', style=''):
    """
    Load the stylesheet based on QtPy abstraction layer environment variable.

    If the argument is not passed, it uses the current QT_API environment
    variable to make the imports of Qt bindings. If passed, it sets this
    variable then make the imports.

    Args:
        qt_api (str): qt binding name to set QT_API environment variable.
                      Default is ''. Possible values are pyside2,
                      pyqt5. Not case sensitive.

    Note:
        - Note that the variable QT_API is read when first imported. So,
          pay attention to the import order.
        - OS, binding and binding version number, and application specific
          patches are applied in this order.

    Returns:
        str: stylesheet string (css).
    """

    if qt_api:
        os.environ['QT_API'] = qt_api

    # Import is made after setting QT_API
    from qtpy.QtCore import QCoreApplication, QFile, QTextStream
    from qtpy.QtGui import QColor, QPalette
    from qtpy import QT_VERSION

    # Search for style in styles directory
    style_dir = None

    available_styles = getAvailableStyles()
    _logger.debug(f"Available styles: {available_styles}")
    for stl in available_styles:
        if style.lower() == stl.lower():
            style_dir = stl
            break

    if style_dir is None:
        stylesheet = ""
        raise FileNotFoundError("Style " + style + " does not exists")

    # check if any style_rc was loaded before
    if "style_rc" in sys.modules:
        _logger.info("Found already imported style in sys.modules")

        # use qCleanupResources to remove all resource files
        global style_rc  # noqa
        style_rc.qCleanupResources()

        # remove imported modules
        for x in [
                module for module in sys.modules
                if module.startswith("style_rc")
        ]:
            del sys.modules[x]
            del style_rc

        # remove path to previously imported style from sys.path
        for stylepath in [
                path for path in sys.path if any(
                    style for style in getAvailableStyles() if style in path)
        ]:
            sys.path.remove(stylepath)
        _logger.debug("Removed all imported styles")

    try:
        _logger.debug("Loading style from directory: " + style_dir)
        old_working_dir = os.getcwd()
        # set style directory
        package_dir = os.path.join(STYLES_PATH, style_dir)
        os.chdir(package_dir)

        # append directory to sys.path and import style_rc
        sys.path.append(package_dir)
        try:
            import style_rc  # noqa
            # get palette
            palette = style_rc.palette

        except ModuleNotFoundError:
            raise ModuleNotFoundError(
                "Failed to import style_rc from directory: {}".format(
                    package_dir))

        finally:
            os.chdir(old_working_dir)

    except FileExistsError:
        raise FileNotFoundError("Missing style_rc.py file")

    _logger.info("Style resources imported successfully")

    # Thus, by importing the binary we can access the resources
    package_dir = os.path.basename(PACKAGE_PATH)
    qss_rc_path = ":" + os.path.join(package_dir, QSS_FILE)

    _logger.debug("Reading QSS file in: %s", qss_rc_path)

    # It gets the qss file from compiled style_rc that was import
    # not from the file QSS as we are using resources
    qss_file = QFile(qss_rc_path)

    if qss_file.exists():
        qss_file.open(QFile.ReadOnly | QFile.Text)
        text_stream = QTextStream(qss_file)
        stylesheet = text_stream.readAll()
        _logger.info("QSS file sucessfuly loaded.")
    else:
        stylesheet = ""
        # Todo: check this raise type and add to docs
        raise FileNotFoundError("Unable to find QSS file '{}' "
                                "in resources.".format(qss_rc_path))

    _logger.debug("Checking patches for being applied.")

    # Todo: check execution order for these functions
    # 1. Apply OS specific patches
    stylesheet += _apply_os_patches(palette)

    # 2. Apply binding specific patches
    stylesheet += _apply_binding_patches()

    # 3. Apply binding version specific patches
    stylesheet += _apply_version_patches(QT_VERSION)

    # 4. Apply palette fix. See issue #139
    _apply_application_patches(palette, QCoreApplication, QPalette, QColor)

    return stylesheet
Example #28
0
class QLed(QFrame, ShapeMap):
    """QLed class."""

    ShapeMap = ShapeMap
    Q_ENUMS(ShapeMap)

    abspath = _os.path.abspath(_os.path.dirname(__file__))
    shapesdict = dict()
    f = QFile(_os.path.join(abspath, 'resources/led_shapes/circle.svg'))
    if f.open(QFile.ReadOnly):
        shapesdict[ShapeMap.Circle] = str(f.readAll(), 'utf-8')
    f.close()
    f = QFile(_os.path.join(abspath, 'resources/led_shapes/round.svg'))
    if f.open(QFile.ReadOnly):
        shapesdict[ShapeMap.Round] = str(f.readAll(), 'utf-8')
    f.close()
    f = QFile(_os.path.join(abspath, 'resources/led_shapes/square.svg'))
    if f.open(QFile.ReadOnly):
        shapesdict[ShapeMap.Square] = str(f.readAll(), 'utf-8')
    f.close()
    f = QFile(_os.path.join(abspath, 'resources/led_shapes/triangle.svg'))
    if f.open(QFile.ReadOnly):
        shapesdict[ShapeMap.Triangle] = str(f.readAll(), 'utf-8')
    f.close()

    Green = QColor(15, 105, 0)
    Red = QColor(207, 0, 0)
    Gray = QColor(90, 90, 90)
    SelColor = QColor(0, 0, 0)
    NotSelColor1 = QColor(251, 244, 252)
    NotSelColor2 = QColor(173, 173, 173)

    clicked = Signal()
    selected = Signal(bool)

    def __init__(self, parent=None, **kwargs):
        """Class constructor."""
        super().__init__(parent, **kwargs)
        self.m_state = 0
        self.m_stateColors = [self.Red, self.Green]

        self.m_dsblColor = self.Gray
        self.m_shape = self.ShapeMap.Circle

        self._pressed = False
        self._isselected = False
        self.renderer = QSvgRenderer()

    def getState(self):
        """Value property getter."""
        return self.m_state

    def setState(self, value):
        """Value property setter."""
        self.m_state = value
        self.update()

    state = Property(bool, getState, setState)

    def getOnColor(self):
        """On color property getter."""
        return self.m_stateColors[1]

    def setOnColor(self, newColor):
        """On color property setter."""
        self.m_stateColors[1] = newColor
        self.update()

    onColor = Property(QColor, getOnColor, setOnColor)

    def getOffColor(self):
        """Off color property getter."""
        return self.m_stateColors[0]

    def setOffColor(self, newColor):
        """Off color property setter."""
        self.m_stateColors[0] = newColor
        self.update()

    offColor = Property(QColor, getOffColor, setOffColor)

    @property
    def stateColors(self):
        """Color list property getter."""
        return list(self.m_stateColors)

    @stateColors.setter
    def stateColors(self, new_colors):
        """Color list property setter."""
        if not isinstance(new_colors, (list, tuple)) or\
                len(new_colors) < 2 or not isinstance(new_colors[0], QColor):
            return
        self.m_stateColors = list(new_colors)

    def getDsblColor(self):
        """Disabled color property getter."""
        return self.m_dsblColor

    def setDsblColor(self, newColor):
        """Disabled color property setter."""
        self.m_dsblColor = newColor
        self.update()

    dsblColor = Property(QColor, getDsblColor, setDsblColor)

    def getShape(self):
        """Shape property getter."""
        return self.m_shape

    def setShape(self, newShape):
        """Shape property setter."""
        self.m_shape = newShape
        self.update()

    shape = Property(ShapeMap, getShape, setShape)

    def sizeHint(self):
        """Return the base size of the widget according to shape."""
        if self.m_shape == self.ShapeMap.Triangle:
            return QSize(48, 36)
        elif self.m_shape == self.ShapeMap.Round:
            return QSize(72, 36)
        return QSize(36, 36)

    def adjust(self, r, g, b):
        """Adjust the color to set on svg code."""
        def normalise(x):
            return x / 255.0

        def denormalise(x):
            if x <= 1:
                return int(x * 255.0)
            else:
                return 255.0

        (h, l, s) = rgb_to_hls(normalise(r), normalise(g), normalise(b))
        (nr, ng, nb) = hls_to_rgb(h, l * 1.5, s)

        return (denormalise(nr), denormalise(ng), denormalise(nb))

    def getRGBfromQColor(self, qcolor):
        """Convert QColors to a tupple of rgb colors to set on svg code."""
        redhex = qcolor.red()
        greenhex = qcolor.green()
        bluehex = qcolor.blue()
        return (redhex, greenhex, bluehex)

    def paintEvent(self, event):
        """Handle appearence of the widget on state updates."""
        self.style().unpolish(self)
        self.style().polish(self)
        option = QStyleOption()
        option.initFrom(self)

        h = option.rect.height()
        w = option.rect.width()
        if self.m_shape in (self.ShapeMap.Triangle, self.ShapeMap.Round):
            aspect = (4 /
                      3.0) if self.m_shape == self.ShapeMap.Triangle else 2.0
            ah = w / aspect
            aw = w
            if ah > h:
                ah = h
                aw = h * aspect
            x = abs(aw - w) / 2.0
            y = abs(ah - h) / 2.0
            bounds = QRectF(x, y, aw, ah)
        else:
            size = min(w, h)
            x = abs(size - w) / 2.0
            y = abs(size - h) / 2.0
            bounds = QRectF(x, y, size, size)

        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing, True)

        ind = self.m_state % len(self.m_stateColors)
        dark_r, dark_g, dark_b = self.getRGBfromQColor(self.m_stateColors[ind])
        if not self.isEnabled():
            dark_r, dark_g, dark_b = self.getRGBfromQColor(self.m_dsblColor)

        sel1_r, sel1_g, sel1_b = self.getRGBfromQColor(self.SelColor)
        sel2_r, sel2_g, sel2_b = self.getRGBfromQColor(self.SelColor)
        opc = '1.000'
        if not self.isSelected():
            sel1_r, sel1_g, sel1_b = self.getRGBfromQColor(self.NotSelColor1)
            sel2_r, sel2_g, sel2_b = self.getRGBfromQColor(self.NotSelColor2)
            opc = '0.145'

        dark_str = "rgb(%d,%d,%d)" % (dark_r, dark_g, dark_b)
        light_str = "rgb(%d,%d,%d)" % self.adjust(dark_r, dark_g, dark_b)
        sel1_str = "rgb(%d,%d,%d)" % (sel1_r, sel1_g, sel1_b)
        sel2_str = "rgb(%d,%d,%d)" % (sel2_r, sel2_g, sel2_b)

        shape_bytes = bytes(
            self.shapesdict[self.m_shape] %
            (sel1_str, opc, sel2_str, dark_str, light_str), 'utf-8')

        self.renderer.load(QByteArray(shape_bytes))
        self.renderer.render(painter, bounds)

    def mousePressEvent(self, event):
        """Handle mouse press event."""
        self._pressed = True
        super().mousePressEvent(event)

    def mouseReleaseEvent(self, event):
        """Handle mouse release event."""
        if self._pressed:
            self._pressed = False
            self.clicked.emit()
        super().mouseReleaseEvent(event)

    def toggleState(self):
        """Toggle state property."""
        self.m_state = 0 if self.m_state else 1
        self.update()

    def isSelected(self):
        """Return selected state of object."""
        return self._isselected

    def setSelected(self, sel):
        """Configure selected state of object."""
        self._isselected = bool(sel)
        self.selected.emit(self._isselected)
        self.update()

    def toggleSelected(self):
        """Toggle isSelected property."""
        self.setSelected(not self.isSelected())
Example #29
0
class PyDMStateButton(QFrame, PyDMWritableWidget):
    """
    A StateButton with support for Channels and much more from PyDM.

    It consists on QPushButton with internal state.

    Parameters
    ----------
    parent : QWidget
        The parent widget for the Label
    init_channel : str, optional
        The channel to be used by the widget.
    """

    class buttonShapeMap:
        """Enum class of shapes of button."""

        locals().update(**BUTTONSHAPE)

    Q_ENUMS(buttonShapeMap)

    # enumMap for buttonShapeMap
    locals().update(**BUTTONSHAPE)

    squaredbuttonstatesdict = dict()
    path = _os.path.abspath(_os.path.dirname(__file__))
    f = QFile(_os.path.join(path, 'resources/but_shapes/squared_on.svg'))
    if f.open(QFile.ReadOnly):
        squaredbuttonstatesdict['On'] = str(f.readAll(), 'utf-8')
    f.close()
    f = QFile(_os.path.join(path, 'resources/but_shapes/squared_off.svg'))
    if f.open(QFile.ReadOnly):
        squaredbuttonstatesdict['Off'] = str(f.readAll(), 'utf-8')
    f.close()
    f = QFile(_os.path.join(path, 'resources/but_shapes/squared_disconn.svg'))
    if f.open(QFile.ReadOnly):
        squaredbuttonstatesdict['Disconnected'] = str(f.readAll(), 'utf-8')
    f.close()

    roundedbuttonstatesdict = dict()
    f = QFile(_os.path.join(path, 'resources/but_shapes/rounded_on.svg'))
    if f.open(QFile.ReadOnly):
        roundedbuttonstatesdict['On'] = str(f.readAll(), 'utf-8')
    f.close()
    f = QFile(_os.path.join(path, 'resources/but_shapes/rounded_off.svg'))
    if f.open(QFile.ReadOnly):
        roundedbuttonstatesdict['Off'] = str(f.readAll(), 'utf-8')
    f.close()
    f = QFile(_os.path.join(path, 'resources/but_shapes/rounded_disconn.svg'))
    if f.open(QFile.ReadOnly):
        roundedbuttonstatesdict['Disconnected'] = str(f.readAll(), 'utf-8')
    f.close()

    clicked = Signal()
    DEFAULT_CONFIRM_MESSAGE = "Are you sure you want to proceed?"

    def __init__(self, parent=None, init_channel=None, invert=False):
        """Initialize all internal states and properties."""
        QFrame.__init__(self, parent)
        PyDMWritableWidget.__init__(self, init_channel=init_channel)

        self._off = 0
        self._on = 1
        self.invert = invert

        self._bit = -1
        self._bit_val = 0
        self.value = 0
        self.clicked.connect(self.send_value)
        self.shape = 0
        self.renderer = QSvgRenderer()

        self._show_confirm_dialog = False
        self._confirm_message = PyDMStateButton.DEFAULT_CONFIRM_MESSAGE
        self._password_protected = False
        self._password = ""
        self._protected_password = ""

    @Property(bool)
    def passwordProtected(self):
        """
        Whether or not this button is password protected.

        Returns
        -------
        bool
        """
        return self._password_protected

    @passwordProtected.setter
    def passwordProtected(self, value):
        """
        Whether or not this button is password protected.

        Parameters
        ----------
        value : bool
        """
        if self._password_protected != value:
            self._password_protected = value

    @Property(str)
    def password(self):
        """
        Password to be encrypted using SHA256.

        .. warning::
            To avoid issues exposing the password this method
            always returns an empty string.

        Returns
        -------
        str
        """
        return ""

    @password.setter
    def password(self, value):
        """
        Password to be encrypted using SHA256.

        Parameters
        ----------
        value : str
            The password to be encrypted
        """
        if value is not None and value != "":
            sha = _hashlib.sha256()
            sha.update(value.encode())
            # Use the setter as it also checks whether the existing password
            # is the same with the new one, and only updates if the new
            # password is different
            self.protectedPassword = sha.hexdigest()

    @Property(str)
    def protectedPassword(self):
        """
        The encrypted password.

        Returns
        -------
        str
        """
        return self._protected_password

    @protectedPassword.setter
    def protectedPassword(self, value):
        if self._protected_password != value:
            self._protected_password = value

    @Property(bool)
    def showConfirmDialog(self):
        """
        Wether or not to display a confirmation dialog.

        Returns
        -------
        bool
        """
        return self._show_confirm_dialog

    @showConfirmDialog.setter
    def showConfirmDialog(self, value):
        """
        Wether or not to display a confirmation dialog.

        Parameters
        ----------
        value : bool
        """
        if self._show_confirm_dialog != value:
            self._show_confirm_dialog = value

    @Property(str)
    def confirmMessage(self):
        """
        Message to be displayed at the Confirmation dialog.

        Returns
        -------
        str
        """
        return self._confirm_message

    @confirmMessage.setter
    def confirmMessage(self, value):
        """
        Message to be displayed at the Confirmation dialog.

        Parameters
        ----------
        value : str
        """
        if self._confirm_message != value:
            self._confirm_message = value

    def mouseReleaseEvent(self, ev):
        """Deal with mouse clicks. Only accept clicks within the figure."""
        cond = ev.button() == Qt.LeftButton
        cond &= ev.x() < self.width()/2+self.height()
        cond &= ev.x() > self.width()/2-self.height()
        if cond:
            self.clicked.emit()

    def value_changed(self, new_val):
        """
        Callback invoked when the Channel value is changed.

        Display the value of new_val accordingly. If :attr:'pvBit' is n>=0 or
        positive the button display the state of the n-th digit of the channel.

        Parameters
        ----------
        new_value : str, int, float, bool or np.ndarray
            The new value from the channel. The type depends on the channel.
        """
        if isinstance(new_val, _np.ndarray):
            _log.warning('PyDMStateButton received a numpy array to ' +
                         self.channel+' ('+str(new_val)+')!')
            return
        super(PyDMStateButton, self).value_changed(new_val)
        value = int(new_val)
        self.value = value
        if self._bit >= 0:
            value = (value >> self._bit) & 1
        self._bit_val = value
        self.update()

    def confirm_dialog(self):
        """
        Show the confirmation dialog with the proper message in case
        ```showConfirmMessage``` is True.

        Returns
        -------
        bool
            True if the message was confirmed or if ```showCofirmMessage```
            is False.
        """

        if not self._show_confirm_dialog:
            return True

        if self._confirm_message == "":
            self._confirm_message = PyDMStateButton.DEFAULT_CONFIRM_MESSAGE
        msg = QMessageBox()
        msg.setIcon(QMessageBox.Question)
        msg.setText(self._confirm_message)
        msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
        msg.setDefaultButton(QMessageBox.No)
        ret = msg.exec_()
        return not ret == QMessageBox.No

    def validate_password(self):
        """
        If the widget is ```passwordProtected```, this method will propmt
        the user for the correct password.

        Returns
        -------
        bool
            True in case the password was correct of if the widget is not
            password protected.
        """
        if not self._password_protected:
            return True

        pwd, ok = QInputDialog().getText(
           None, "Authentication", "Please enter your password:"******"")
        pwd = str(pwd)
        if not ok or pwd == "":
            return False

        sha = _hashlib.sha256()
        sha.update(pwd.encode())
        pwd_encrypted = sha.hexdigest()
        if pwd_encrypted != self._protected_password:
            msg = QMessageBox()
            msg.setIcon(QMessageBox.Critical)
            msg.setText("Invalid password.")
            msg.setWindowTitle("Error")
            msg.setStandardButtons(QMessageBox.Ok)
            msg.setDefaultButton(QMessageBox.Ok)
            msg.setEscapeButton(QMessageBox.Ok)
            msg.exec_()
            return False
        return True

    def send_value(self):
        """
        Emit a :attr:`send_value_signal` to update channel value.

        If :attr:'pvBit' is n>=0 or positive the button toggles the state of
        the n-th digit of the channel. Otherwise it toggles the whole value.
        """
        if not self._connected:
            return None
        if not self.confirm_dialog():
            return None
        if not self.validate_password():
            return None

        checked = not self._bit_val
        val = checked
        if self._bit >= 0:
            val = int(self.value)
            val ^= (-checked ^ val) & (1 << self._bit)
            # For explanation look:
            # https://stackoverflow.com/questions/47981/how-do-you-set-clear-and-toggle-a-single-bit
        self.send_value_signal[self.channeltype].emit(self.channeltype(val))

    def sizeHint(self):
        """Return size hint to define size on initialization."""
        return QSize(72, 36)

    def paintEvent(self, event):
        """Treat appearence changes based on connection state and value."""
        self.style().unpolish(self)
        self.style().polish(self)

        if not self.isEnabled():
            state = 'Disconnected'
        elif self._bit_val == self._on:
            state = 'On'
        elif self._bit_val == self._off:
            state = 'Off'
        else:
            state = 'Disconnected'

        if self.shape == 0:
            shape_dict = PyDMStateButton.squaredbuttonstatesdict
        elif self.shape == 1:
            shape_dict = PyDMStateButton.roundedbuttonstatesdict

        option = QStyleOption()
        option.initFrom(self)
        h = option.rect.height()
        w = option.rect.width()
        aspect = 2.0
        ah = w/aspect
        aw = w
        if ah > h:
            ah = h
            aw = h*aspect
        x = abs(aw-w)/2.0
        y = abs(ah-h)/2.0
        bounds = QRectF(x, y, aw, ah)

        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing, True)

        shape_str = shape_dict[state]
        buttonstate_bytearray = bytes(shape_str, 'utf-8')
        self.renderer.load(QByteArray(buttonstate_bytearray))
        self.renderer.render(painter, bounds)

    @Property(buttonShapeMap)
    def shape(self):
        """
        Property to define the shape of the button.

        Returns
        -------
        int
        """
        return self._shape

    @shape.setter
    def shape(self, new_shape):
        """
        Property to define the shape of the button.

        Parameters
        ----------
        value : int
        """
        if new_shape in [PyDMStateButton.Rounded, PyDMStateButton.Squared]:
            self._shape = new_shape
            self.update()
        else:
            raise ValueError('Button shape not defined!')

    @Property(int)
    def pvbit(self):
        """
        Property to define which PV bit to control.

        Returns
        -------
        int
        """
        return int(self._bit)

    @pvbit.setter
    def pvbit(self, bit):
        """
        Property to define which PV bit to control.

        Parameters
        ----------
        value : int
        """
        if bit >= 0:
            self._bit = int(bit)

    @Property(bool)
    def invert(self):
        """
        Property that indicates whether to invert button on/off representation.

        Return
        ------
        bool
        """
        return self._invert

    @invert.setter
    def invert(self, value):
        """
        Property that indicates whether to invert button on/off representation.

        Parameters
        ----------
        value: bool
        """
        self._invert = value
        if self._invert:
            self._on = 0
            self._off = 1
        else:
            self._on = 1
            self._off = 0