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())
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")
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()
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
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)
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)
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)
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())
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()
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))
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)
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)
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)
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")
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()
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()
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()
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()
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)
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_())
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()
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")
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
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
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
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())
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