forked from IPKM/nmreval
		
	BUGFIX: VFT;
change to src layout
This commit is contained in:
		
							
								
								
									
										80
									
								
								src/gui_qt/lib/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								src/gui_qt/lib/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,80 @@
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
if sys.version_info < (3, 7):
 | 
			
		||||
    HAS_IMPORTLIB_RESOURCE = False
 | 
			
		||||
    from pkg_resources import resource_filename
 | 
			
		||||
else:
 | 
			
		||||
    HAS_IMPORTLIB_RESOURCE = True
 | 
			
		||||
    from importlib.resources import path
 | 
			
		||||
 | 
			
		||||
from ..Qt import QtGui, QtWidgets
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# def get_path_importlib(package, resource):
 | 
			
		||||
#     return path(package, resource)
 | 
			
		||||
#
 | 
			
		||||
#
 | 
			
		||||
# def _get_path_pkg(package, resource):
 | 
			
		||||
#     return resource_filename(package, resource)
 | 
			
		||||
#
 | 
			
		||||
#
 | 
			
		||||
# if HAS_IMPORTLIB_RESOURCE:
 | 
			
		||||
#     get_path = get_path_importlib
 | 
			
		||||
# else:
 | 
			
		||||
#     get_path = _get_path_pkg
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def make_action_icons(widget):
 | 
			
		||||
    global HAS_IMPORTLIB_RESOURCE
 | 
			
		||||
    icon_type = QtWidgets.QApplication.instance().theme
 | 
			
		||||
    from json import loads
 | 
			
		||||
 | 
			
		||||
    if HAS_IMPORTLIB_RESOURCE:
 | 
			
		||||
        with path('resources.icons', 'icons.json') as fp:
 | 
			
		||||
            with fp.open('r') as f:
 | 
			
		||||
                icon_list = loads(f.read())
 | 
			
		||||
                
 | 
			
		||||
        for ac, img in icon_list[widget.objectName()].items():
 | 
			
		||||
            dirname = 'resources.icons.%s_light' % icon_type
 | 
			
		||||
            with path(dirname, img+'.png') as imgpath:
 | 
			
		||||
                icon = QtGui.QIcon()
 | 
			
		||||
                icon.addPixmap(QtGui.QPixmap(str(imgpath)), QtGui.QIcon.Normal, QtGui.QIcon.Off)
 | 
			
		||||
                getattr(widget, ac).setIcon(icon)
 | 
			
		||||
 | 
			
		||||
    else:
 | 
			
		||||
        with open(resource_filename('resources.icons', 'icons.json'), 'r') as f:
 | 
			
		||||
            icon_list = loads(f.read())
 | 
			
		||||
 | 
			
		||||
        for ac, img in icon_list[widget.objectName()].items():
 | 
			
		||||
            dirname = 'resources.icons.%s_light' % icon_type
 | 
			
		||||
            imgpath = resource_filename(dirname, img+'.png')
 | 
			
		||||
            icon = QtGui.QIcon()
 | 
			
		||||
            icon.addPixmap(QtGui.QPixmap(str(imgpath)), QtGui.QIcon.Normal, QtGui.QIcon.Off)
 | 
			
		||||
            getattr(widget, ac).setIcon(icon)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_icon(icon_name):
 | 
			
		||||
    try:
 | 
			
		||||
        icon_type = QtWidgets.QApplication.instance().theme
 | 
			
		||||
    except AttributeError:
 | 
			
		||||
        icon_type = 'normal'
 | 
			
		||||
 | 
			
		||||
    global HAS_IMPORTLIB_RESOURCE
 | 
			
		||||
 | 
			
		||||
    if icon_name != 'logo':
 | 
			
		||||
        dirname = f'resources.icons.{icon_type}_light'
 | 
			
		||||
    else:
 | 
			
		||||
        dirname = 'resources.icons'
 | 
			
		||||
 | 
			
		||||
    if HAS_IMPORTLIB_RESOURCE:
 | 
			
		||||
        with path(dirname, icon_name+'.png') as imgpath:
 | 
			
		||||
            icon = QtGui.QIcon()
 | 
			
		||||
            icon.addPixmap(QtGui.QPixmap(str(imgpath)), QtGui.QIcon.Normal, QtGui.QIcon.Off)
 | 
			
		||||
 | 
			
		||||
            return icon
 | 
			
		||||
    else:
 | 
			
		||||
        imgpath = resource_filename(dirname, icon_name+'.png')
 | 
			
		||||
        icon = QtGui.QIcon()
 | 
			
		||||
        icon.addPixmap(QtGui.QPixmap(imgpath), QtGui.QIcon.Normal, QtGui.QIcon.Off)
 | 
			
		||||
 | 
			
		||||
        return icon
 | 
			
		||||
							
								
								
									
										262
									
								
								src/gui_qt/lib/codeeditor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										262
									
								
								src/gui_qt/lib/codeeditor.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,262 @@
 | 
			
		||||
# CodeEditor based on QT example, Python syntax highlighter found on Python site
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
from ..Qt import QtGui, QtCore, QtWidgets
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _make_textformats(color, style=''):
 | 
			
		||||
    """Return a QTextCharFormat with the given attributes.
 | 
			
		||||
    """
 | 
			
		||||
    _color = QtGui.QColor()
 | 
			
		||||
    _color.setNamedColor(color)
 | 
			
		||||
 | 
			
		||||
    _format = QtGui.QTextCharFormat()
 | 
			
		||||
    _format.setForeground(_color)
 | 
			
		||||
    if 'bold' in style:
 | 
			
		||||
        _format.setFontWeight(QtGui.QFont.Bold)
 | 
			
		||||
    if 'italic' in style:
 | 
			
		||||
        _format.setFontItalic(True)
 | 
			
		||||
 | 
			
		||||
    return _format
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Syntax styles that can be shared by all languages
 | 
			
		||||
STYLES = {
 | 
			
		||||
    'keyword': _make_textformats('blue'),
 | 
			
		||||
    'operator': _make_textformats('black'),
 | 
			
		||||
    'brace': _make_textformats('black'),
 | 
			
		||||
    'defclass': _make_textformats('black', 'bold'),
 | 
			
		||||
    'string': _make_textformats('darkGreen'),
 | 
			
		||||
    'comment': _make_textformats('gray', 'italic'),
 | 
			
		||||
    'self': _make_textformats('brown', 'italic'),
 | 
			
		||||
    'property': _make_textformats('brown'),
 | 
			
		||||
    'numbers': _make_textformats('darkRed'),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PythonHighlighter(QtGui.QSyntaxHighlighter):
 | 
			
		||||
    """
 | 
			
		||||
    Syntax highlighter for the Python language.
 | 
			
		||||
    """
 | 
			
		||||
    # Python keywords
 | 
			
		||||
    keywords = [
 | 
			
		||||
        'and', 'assert', 'break', 'class', 'continue', 'def',
 | 
			
		||||
        'del', 'elif', 'else', 'except', 'exec', 'finally',
 | 
			
		||||
        'for', 'from', 'global', 'if', 'import', 'in',
 | 
			
		||||
        'is', 'lambda', 'not', 'or', 'pass', 'print',
 | 
			
		||||
        'raise', 'return', 'try', 'while', 'yield',
 | 
			
		||||
        'None', 'True', 'False', 'object'
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    def __init__(self, document):
 | 
			
		||||
        super().__init__(document)
 | 
			
		||||
 | 
			
		||||
        # Multi-line strings (expression, flag, style)
 | 
			
		||||
        # FIXME: The triple-quotes in these two lines will mess up the
 | 
			
		||||
        # syntax highlighting from this point onward
 | 
			
		||||
        self.tri_single = (QtCore.QRegExp("r'{3}"), 1, STYLES['string'])
 | 
			
		||||
        self.tri_double = (QtCore.QRegExp('r?"{3}'), 2, STYLES['string'])
 | 
			
		||||
 | 
			
		||||
        rules = []
 | 
			
		||||
 | 
			
		||||
        # Keyword, operator, and brace rules
 | 
			
		||||
        rules += [(rf'\b{w}\b', 0, STYLES['keyword']) for w in PythonHighlighter.keywords]
 | 
			
		||||
 | 
			
		||||
        # Other rules
 | 
			
		||||
        rules += [
 | 
			
		||||
            # 'self'
 | 
			
		||||
            (r'\bself\b', 0, STYLES['self']),
 | 
			
		||||
 | 
			
		||||
            # 'def' followed by an identifier
 | 
			
		||||
            (r'\bdef\b\s*(\w+)', 1, STYLES['defclass']),
 | 
			
		||||
            # 'class' followed by an identifier
 | 
			
		||||
            (r'\bclass\b\s*(\w+)', 1, STYLES['defclass']),
 | 
			
		||||
            # @ followed by a word
 | 
			
		||||
            (r'\s*@(\w+)\s*', 0, STYLES['property']),
 | 
			
		||||
 | 
			
		||||
            # Numeric literals
 | 
			
		||||
            (r'\b[+-]?\d+[lL]?\b', 0, STYLES['numbers']),
 | 
			
		||||
            (r'\b[+-]?0[xX][\dA-Fa-f]+[lL]?\b', 0, STYLES['numbers']),
 | 
			
		||||
            (r'\b[+-]?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?\b', 0, STYLES['numbers']),
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            # Double-quoted string, possibly containing escape sequences
 | 
			
		||||
            (r'[rf]?"[^"\\]*(\\.[^"\\]*)*"', 0, STYLES['string']),
 | 
			
		||||
            # Single-quoted string, possibly containing escape sequences
 | 
			
		||||
            (r"[rf]?'[^'\\]*(\\.[^'\\]*)*'", 0, STYLES['string']),
 | 
			
		||||
 | 
			
		||||
            # From '#' until a newline
 | 
			
		||||
            (r'#[^\n]*', 0, STYLES['comment']),
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
        # Build a QRegExp for each pattern
 | 
			
		||||
        self.rules = [(QtCore.QRegExp(pat), index, fmt) for (pat, index, fmt) in rules]
 | 
			
		||||
 | 
			
		||||
    def highlightBlock(self, text):
 | 
			
		||||
        """
 | 
			
		||||
        Apply syntax highlighting to the given block of text.
 | 
			
		||||
        """
 | 
			
		||||
        # Do other syntax formatting
 | 
			
		||||
        for expression, nth, rule in self.rules:
 | 
			
		||||
            index = expression.indexIn(text, 0)
 | 
			
		||||
 | 
			
		||||
            while index >= 0:
 | 
			
		||||
                # We actually want the index of the nth match
 | 
			
		||||
                index = expression.pos(nth)
 | 
			
		||||
                length = len(expression.cap(nth))
 | 
			
		||||
                self.setFormat(index, length, rule)
 | 
			
		||||
                index = expression.indexIn(text, index + length)
 | 
			
		||||
 | 
			
		||||
        self.setCurrentBlockState(0)
 | 
			
		||||
 | 
			
		||||
        # Do multi-line strings
 | 
			
		||||
        in_multiline = self.match_multiline(text, *self.tri_single)
 | 
			
		||||
        if not in_multiline:
 | 
			
		||||
            in_multiline = self.match_multiline(text, *self.tri_double)
 | 
			
		||||
 | 
			
		||||
    def match_multiline(self, text, delimiter, in_state, style):
 | 
			
		||||
        """
 | 
			
		||||
        Highlighting of multi-line strings. ``delimiter`` should be a
 | 
			
		||||
        ``QRegExp`` for triple-single-quotes or triple-double-quotes, and
 | 
			
		||||
        ``in_state`` should be a unique integer to represent the corresponding
 | 
			
		||||
        state changes when inside those strings. Returns True if we're still
 | 
			
		||||
        inside a multi-line string when this function is finished.
 | 
			
		||||
        """
 | 
			
		||||
        # If inside triple-single quotes, start at 0
 | 
			
		||||
        if self.previousBlockState() == in_state:
 | 
			
		||||
            start = 0
 | 
			
		||||
            add = 0
 | 
			
		||||
        # Otherwise, look for the delimiter on this line
 | 
			
		||||
        else:
 | 
			
		||||
            start = delimiter.indexIn(text)
 | 
			
		||||
            # Move past this match
 | 
			
		||||
            add = delimiter.matchedLength()
 | 
			
		||||
 | 
			
		||||
        # As long as there's a delimiter match on this line...
 | 
			
		||||
        while start >= 0:
 | 
			
		||||
            # Look for the ending delimiter
 | 
			
		||||
            end = delimiter.indexIn(text, start + add)
 | 
			
		||||
            # Ending delimiter on this line?
 | 
			
		||||
            if end >= add:
 | 
			
		||||
                length = end - start + add + delimiter.matchedLength()
 | 
			
		||||
                self.setCurrentBlockState(0)
 | 
			
		||||
            # No; multi-line string
 | 
			
		||||
            else:
 | 
			
		||||
                self.setCurrentBlockState(in_state)
 | 
			
		||||
                try:
 | 
			
		||||
                    length = text.length() - start + add
 | 
			
		||||
                except AttributeError:
 | 
			
		||||
                    length = len(text) - start + add
 | 
			
		||||
            # Apply formatting
 | 
			
		||||
            self.setFormat(start, length, style)
 | 
			
		||||
            # Look for the next match
 | 
			
		||||
            start = delimiter.indexIn(text, start + length)
 | 
			
		||||
 | 
			
		||||
        # Return True if still inside a multi-line string, False otherwise
 | 
			
		||||
        if self.currentBlockState() == in_state:
 | 
			
		||||
            return True
 | 
			
		||||
        else:
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LineNumbers(QtWidgets.QWidget):
 | 
			
		||||
    def __init__(self, editor):
 | 
			
		||||
        super().__init__(editor)
 | 
			
		||||
        self.editor = editor
 | 
			
		||||
 | 
			
		||||
    def sizeHint(self):
 | 
			
		||||
        return QtCore.QSize(self.editor.width_linenumber, 0)
 | 
			
		||||
 | 
			
		||||
    def paintEvent(self, event):
 | 
			
		||||
        self.editor.paintevent_linenumber(event)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CodeEditor(QtWidgets.QPlainTextEdit):
 | 
			
		||||
    # more or less a direct translation of the Qt example
 | 
			
		||||
    def __init__(self, parent):
 | 
			
		||||
        super().__init__(parent)
 | 
			
		||||
 | 
			
		||||
        self.current_linenumber = LineNumbers(self)
 | 
			
		||||
 | 
			
		||||
        self.blockCountChanged.connect(self.update_width_linenumber)
 | 
			
		||||
        self.updateRequest.connect(self.update_current_area)
 | 
			
		||||
        self.cursorPositionChanged.connect(self.highlight_current_line)
 | 
			
		||||
        self.update_width_linenumber(0)
 | 
			
		||||
 | 
			
		||||
        self.highlight = PythonHighlighter(self.document())
 | 
			
		||||
 | 
			
		||||
    def keyPressEvent(self, evt):
 | 
			
		||||
        if evt.key() == QtCore.Qt.Key_Tab:
 | 
			
		||||
            # use spaces instead of tab
 | 
			
		||||
            self.insertPlainText(' '*4)
 | 
			
		||||
        elif evt.key() == QtCore.Qt.Key_Insert:
 | 
			
		||||
            self.setOverwriteMode(not self.overwriteMode())
 | 
			
		||||
        else:
 | 
			
		||||
            super().keyPressEvent(evt)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def width_linenumber(self):
 | 
			
		||||
        digits = 1
 | 
			
		||||
        count = max(1, self.blockCount())
 | 
			
		||||
        while count >= 10:
 | 
			
		||||
            count /= 10
 | 
			
		||||
            digits += 1
 | 
			
		||||
        space = 6 + self.fontMetrics().width('9') * digits
 | 
			
		||||
 | 
			
		||||
        return space
 | 
			
		||||
 | 
			
		||||
    def update_width_linenumber(self, _):
 | 
			
		||||
        self.setViewportMargins(self.width_linenumber, 0, 0, 0)
 | 
			
		||||
 | 
			
		||||
    def update_current_area(self, rect, dy):
 | 
			
		||||
        if dy:
 | 
			
		||||
            self.current_linenumber.scroll(0, dy)
 | 
			
		||||
        else:
 | 
			
		||||
            self.current_linenumber.update(0, rect.y(), self.current_linenumber.width(), rect.height())
 | 
			
		||||
        if rect.contains(self.viewport().rect()):
 | 
			
		||||
            self.update_width_linenumber(0)
 | 
			
		||||
 | 
			
		||||
    def resizeEvent(self, evt):
 | 
			
		||||
        super().resizeEvent(evt)
 | 
			
		||||
 | 
			
		||||
        cr = self.contentsRect()
 | 
			
		||||
        self.current_linenumber.setGeometry(QtCore.QRect(cr.left(), cr.top(), self.width_linenumber, cr.height()))
 | 
			
		||||
 | 
			
		||||
    def paintevent_linenumber(self, evt):
 | 
			
		||||
        painter = QtGui.QPainter(self.current_linenumber)
 | 
			
		||||
        painter.fillRect(evt.rect(), QtCore.Qt.lightGray)
 | 
			
		||||
 | 
			
		||||
        block = self.firstVisibleBlock()
 | 
			
		||||
        block_number = block.blockNumber()
 | 
			
		||||
        top = self.blockBoundingGeometry(block).translated(self.contentOffset()).top()
 | 
			
		||||
        bottom = top + self.blockBoundingRect(block).height()
 | 
			
		||||
 | 
			
		||||
        # Just to make sure I use the right font
 | 
			
		||||
        height = self.fontMetrics().height()
 | 
			
		||||
        while block.isValid() and (top <= evt.rect().bottom()):
 | 
			
		||||
            if block.isVisible() and (bottom >= evt.rect().top()):
 | 
			
		||||
                number = str(block_number + 1)
 | 
			
		||||
                painter.setPen(QtCore.Qt.black)
 | 
			
		||||
                painter.drawText(0, top, self.current_linenumber.width() - 3, height,
 | 
			
		||||
                                 QtCore.Qt.AlignRight, number)
 | 
			
		||||
 | 
			
		||||
            block = block.next()
 | 
			
		||||
            top = bottom
 | 
			
		||||
            bottom = top + self.blockBoundingRect(block).height()
 | 
			
		||||
            block_number += 1
 | 
			
		||||
 | 
			
		||||
    def highlight_current_line(self):
 | 
			
		||||
        extra_selections = []
 | 
			
		||||
 | 
			
		||||
        if not self.isReadOnly():
 | 
			
		||||
            selection = QtWidgets.QTextEdit.ExtraSelection()
 | 
			
		||||
 | 
			
		||||
            line_color = QtGui.QColor(QtCore.Qt.yellow).lighter(180)
 | 
			
		||||
 | 
			
		||||
            selection.format.setBackground(line_color)
 | 
			
		||||
            selection.format.setProperty(QtGui.QTextFormat.FullWidthSelection, True)
 | 
			
		||||
            selection.cursor = self.textCursor()
 | 
			
		||||
            selection.cursor.clearSelection()
 | 
			
		||||
            extra_selections.append(selection)
 | 
			
		||||
 | 
			
		||||
        self.setExtraSelections(extra_selections)
 | 
			
		||||
							
								
								
									
										80
									
								
								src/gui_qt/lib/color_dialog.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								src/gui_qt/lib/color_dialog.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,80 @@
 | 
			
		||||
from nmreval.configs import config_paths
 | 
			
		||||
from nmreval.lib.colors import Colors, available_cycles
 | 
			
		||||
 | 
			
		||||
from ..Qt import QtWidgets, QtCore, QtGui
 | 
			
		||||
from .._py.color_palette import Ui_Dialog
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ColorDialog(QtWidgets.QDialog, Ui_Dialog):
 | 
			
		||||
 | 
			
		||||
    def __init__(self, parent=None):
 | 
			
		||||
        super().__init__(parent=parent)
 | 
			
		||||
        self.setupUi(self)
 | 
			
		||||
 | 
			
		||||
        self._palettes = available_cycles
 | 
			
		||||
        self.palette_combobox.addItems(self._palettes.keys())
 | 
			
		||||
 | 
			
		||||
        self.colorlist.installEventFilter(self)
 | 
			
		||||
 | 
			
		||||
    def eventFilter(self, src: QtCore.QObject, evt: QtCore.QEvent) -> bool:
 | 
			
		||||
        if evt.type() == evt.KeyPress:
 | 
			
		||||
            if evt.key() == QtCore.Qt.Key_Delete:
 | 
			
		||||
                self.colorlist.takeItem(self.colorlist.currentRow())
 | 
			
		||||
 | 
			
		||||
                return True
 | 
			
		||||
 | 
			
		||||
        return super().eventFilter(src, evt)
 | 
			
		||||
 | 
			
		||||
    @QtCore.pyqtSlot(name='on_add_palette_button_pressed')
 | 
			
		||||
    @QtCore.pyqtSlot(name='on_append_palette_button_pressed')
 | 
			
		||||
    def set_palette(self, clear: bool = False):
 | 
			
		||||
        if self.sender() == self.add_palette_button or clear:
 | 
			
		||||
            self.colorlist.clear()
 | 
			
		||||
 | 
			
		||||
        for color in self._palettes[self.palette_combobox.currentText()]:
 | 
			
		||||
            item = QtWidgets.QListWidgetItem(color.name)
 | 
			
		||||
            item.setData(QtCore.Qt.DecorationRole, QtGui.QColor.fromRgbF(*color.rgb(normed=True)))
 | 
			
		||||
            self.colorlist.addItem(item)
 | 
			
		||||
 | 
			
		||||
    @QtCore.pyqtSlot(name='on_add_color_button_pressed')
 | 
			
		||||
    def add_color(self):
 | 
			
		||||
        color = self.color_combobox.value
 | 
			
		||||
        item = QtWidgets.QListWidgetItem(color.name)
 | 
			
		||||
        item.setData(QtCore.Qt.DecorationRole, QtGui.QColor.fromRgbF(*color.rgb(normed=True)))
 | 
			
		||||
        self.colorlist.addItem(item)
 | 
			
		||||
 | 
			
		||||
    @QtCore.pyqtSlot(name='on_save_button_pressed')
 | 
			
		||||
    def save_new_palette(self):
 | 
			
		||||
        if not self.colorlist.count():
 | 
			
		||||
            _ = QtWidgets.QMessageBox.warning(self, 'Error', 'No colors to save.')
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        if not self.new_name_edit.text():
 | 
			
		||||
            _ = QtWidgets.QMessageBox.warning(self, 'Error', 'New list has no name.')
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        color_name = self.new_name_edit.text()  # type: str
 | 
			
		||||
        if color_name in self._palettes:
 | 
			
		||||
            _ = QtWidgets.QMessageBox.warning(self, 'Error', 'Name is already used.')
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        color_file = config_paths() / 'colors.cfg'
 | 
			
		||||
 | 
			
		||||
        new_palette = []
 | 
			
		||||
        with color_file.open('a') as f:
 | 
			
		||||
            f.write('#-- %s\n' % color_name)
 | 
			
		||||
            for i in range(self.colorlist.count()):
 | 
			
		||||
                item = self.colorlist.item(i)
 | 
			
		||||
                r, g, b = item.data(QtCore.Qt.DecorationRole).getRgbF()[:3]
 | 
			
		||||
                new_palette.append(Colors.from_rgb(r, g, b, normed=True))
 | 
			
		||||
                f.write(f'{r*255:.1f}, {g*255:.1f}, {b*255:.1f}\n')
 | 
			
		||||
            f.write('\n')
 | 
			
		||||
 | 
			
		||||
        self._palettes[color_name] = new_palette
 | 
			
		||||
        self.palette_combobox.addItem(color_name)
 | 
			
		||||
 | 
			
		||||
    def load_palette(self, name: str):
 | 
			
		||||
        idx = self.palette_combobox.findText(name)
 | 
			
		||||
        if idx != -1:
 | 
			
		||||
            self.palette_combobox.setCurrentIndex(idx)
 | 
			
		||||
            self.set_palette(clear=True)
 | 
			
		||||
							
								
								
									
										156
									
								
								src/gui_qt/lib/configurations.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										156
									
								
								src/gui_qt/lib/configurations.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,156 @@
 | 
			
		||||
import os
 | 
			
		||||
 | 
			
		||||
from nmreval.configs import *
 | 
			
		||||
from nmreval.io.graceeditor import GraceEditor
 | 
			
		||||
 | 
			
		||||
from ..Qt import QtWidgets, QtGui
 | 
			
		||||
from .._py.agroptiondialog import Ui_Dialog
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class QGraceSettings(QtWidgets.QDialog, Ui_Dialog):
 | 
			
		||||
    def __init__(self, parent=None):
 | 
			
		||||
        super().__init__(parent=parent)
 | 
			
		||||
 | 
			
		||||
        self.setupUi(self)
 | 
			
		||||
        self._default_path = config_paths().joinpath('Default.agr')
 | 
			
		||||
        self._default = GraceEditor(self._default_path)
 | 
			
		||||
 | 
			
		||||
        self.setup_ui()
 | 
			
		||||
 | 
			
		||||
    def setup_ui(self):
 | 
			
		||||
        page = self._default.size
 | 
			
		||||
        self.widthDoubleSpinBox.setValue(page[0])
 | 
			
		||||
        self.heightDoubleSpinBox.setValue(page[1])
 | 
			
		||||
 | 
			
		||||
        view = self._default.get_property(0, 'view')
 | 
			
		||||
        self.leftMarginDoubleSpinBox.setValue(self._default.convert(view[0], direction='abs'))
 | 
			
		||||
        self.bottomMarginDoubleSpinBox.setValue(self._default.convert(view[1], direction='abs'))
 | 
			
		||||
        self.rightMarginDoubleSpinBox.setValue(page[0]-self._default.convert(view[2], direction='abs'))
 | 
			
		||||
        self.topMarginDoubleSpinBox.setValue(page[1]-self._default.convert(view[3], direction='abs'))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class GraceMsgBox(QtWidgets.QDialog):
 | 
			
		||||
    def __init__(self, fname, parent=None):
 | 
			
		||||
        super(GraceMsgBox, self).__init__(parent=parent)
 | 
			
		||||
 | 
			
		||||
        agr = GraceEditor()
 | 
			
		||||
        agr.parse(fname)
 | 
			
		||||
 | 
			
		||||
        layout = QtWidgets.QGridLayout()
 | 
			
		||||
        layout.setContentsMargins(13, 13, 13, 13)
 | 
			
		||||
        self.setLayout(layout)
 | 
			
		||||
 | 
			
		||||
        label = QtWidgets.QLabel('%s already exists. Select one of the options or cancel:' % os.path.split(fname)[1])
 | 
			
		||||
        layout.addWidget(label, 1, 1, 1, 2)
 | 
			
		||||
 | 
			
		||||
        self.button_grp = QtWidgets.QButtonGroup(self)
 | 
			
		||||
 | 
			
		||||
        self.overwrite_radiobutton = QtWidgets.QRadioButton('Overwrite file', parent=self)
 | 
			
		||||
        self.overwrite_radiobutton.setChecked(True)
 | 
			
		||||
        self.button_grp.addButton(self.overwrite_radiobutton, id=0)
 | 
			
		||||
        layout.addWidget(self.overwrite_radiobutton, 2, 1, 1, 2)
 | 
			
		||||
 | 
			
		||||
        self.new_graph_button = QtWidgets.QRadioButton('Create new graph', parent=self)
 | 
			
		||||
        self.button_grp.addButton(self.new_graph_button, id=1)
 | 
			
		||||
        layout.addWidget(self.new_graph_button, 3, 1, 1, 2)
 | 
			
		||||
 | 
			
		||||
        self.addgraph_button = QtWidgets.QRadioButton('Add sets to graph', parent=self)
 | 
			
		||||
        self.button_grp.addButton(self.addgraph_button, id=3)
 | 
			
		||||
        layout.addWidget(self.addgraph_button, 4, 1, 1, 1)
 | 
			
		||||
 | 
			
		||||
        self.graph_combobox = QtWidgets.QComboBox(self)
 | 
			
		||||
        self.graph_combobox.addItems(['G' + str(g.idx) for g in agr.graphs])
 | 
			
		||||
        layout.addWidget(self.graph_combobox, 4, 2, 1, 1)
 | 
			
		||||
 | 
			
		||||
        self.buttonbox = QtWidgets.QDialogButtonBox(self)
 | 
			
		||||
        self.buttonbox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel | QtWidgets.QDialogButtonBox.Ok)
 | 
			
		||||
        layout.addWidget(self.buttonbox, 5, 1, 1, 2)
 | 
			
		||||
 | 
			
		||||
        self.buttonbox.rejected.connect(self.reject)
 | 
			
		||||
        self.buttonbox.accepted.connect(self.accept)
 | 
			
		||||
 | 
			
		||||
    def accept(self) -> None:
 | 
			
		||||
        super().accept()
 | 
			
		||||
        self.setResult(self.button_grp.checkedId())
 | 
			
		||||
        if self.button_grp.checkedId() == 3:
 | 
			
		||||
            self.setResult(int(self.graph_combobox.currentText()[1:]) + 2)
 | 
			
		||||
 | 
			
		||||
    def reject(self) -> None:
 | 
			
		||||
        super().reject()
 | 
			
		||||
        self.setResult(-1)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class GeneralConfiguration(QtWidgets.QDialog):
 | 
			
		||||
    def __init__(self, parent=None):
 | 
			
		||||
        super().__init__(parent=parent)
 | 
			
		||||
 | 
			
		||||
        layout = QtWidgets.QVBoxLayout()
 | 
			
		||||
        layout.setContentsMargins(3, 3, 3, 3)
 | 
			
		||||
 | 
			
		||||
        intro = QtWidgets.QLabel('Changes become active after restart.')
 | 
			
		||||
        layout.addWidget(intro)
 | 
			
		||||
 | 
			
		||||
        parser = read_configuration()
 | 
			
		||||
        for sec in parser.sections():
 | 
			
		||||
            group = QtWidgets.QGroupBox(sec, self)
 | 
			
		||||
 | 
			
		||||
            layout2 = QtWidgets.QGridLayout()
 | 
			
		||||
            layout2.setContentsMargins(3, 3, 3, 3)
 | 
			
		||||
            row = 0
 | 
			
		||||
            for key, value in parser.items(sec):
 | 
			
		||||
                label = QtWidgets.QLabel(key.capitalize(), self)
 | 
			
		||||
                layout2.addWidget(label, row, 0)
 | 
			
		||||
                if (sec, key) in allowed_values:
 | 
			
		||||
                    edit = QtWidgets.QComboBox(self)
 | 
			
		||||
                    edit.addItems(allowed_values[(sec, key)])
 | 
			
		||||
                    edit.setCurrentIndex(edit.findText(value))
 | 
			
		||||
                else:
 | 
			
		||||
                    edit = QtWidgets.QLineEdit(self)
 | 
			
		||||
                    edit.setText(value)
 | 
			
		||||
                    try:
 | 
			
		||||
                        _ = float(value)
 | 
			
		||||
                        edit.setValidator(QtGui.QDoubleValidator())
 | 
			
		||||
                    except ValueError:
 | 
			
		||||
                        pass
 | 
			
		||||
 | 
			
		||||
                layout2.addWidget(edit, row, 1)
 | 
			
		||||
                row += 1
 | 
			
		||||
 | 
			
		||||
            group.setLayout(layout2)
 | 
			
		||||
 | 
			
		||||
            layout.addWidget(group)
 | 
			
		||||
 | 
			
		||||
        self.buttonbox = QtWidgets.QDialogButtonBox(self)
 | 
			
		||||
        self.buttonbox.setStandardButtons(self.buttonbox.Ok|self.buttonbox.Cancel)
 | 
			
		||||
        self.buttonbox.rejected.connect(self.close)
 | 
			
		||||
        self.buttonbox.accepted.connect(self.accept)
 | 
			
		||||
        layout.addWidget(self.buttonbox)
 | 
			
		||||
 | 
			
		||||
        self.setLayout(layout)
 | 
			
		||||
 | 
			
		||||
    def accept(self):
 | 
			
		||||
        options = {}
 | 
			
		||||
        for section in self.findChildren(QtWidgets.QGroupBox):
 | 
			
		||||
            args = {}
 | 
			
		||||
 | 
			
		||||
            layout = section.layout()
 | 
			
		||||
            for row in range(layout.rowCount()):
 | 
			
		||||
                key = layout.itemAtPosition(row, 0).widget().text()
 | 
			
		||||
 | 
			
		||||
                value_widget = layout.itemAtPosition(row, 1).widget()
 | 
			
		||||
                if isinstance(value_widget, QtWidgets.QComboBox):
 | 
			
		||||
                    value = value_widget.currentText()
 | 
			
		||||
                elif isinstance(value_widget, QtWidgets.QLineEdit):
 | 
			
		||||
                    value = value_widget.text()
 | 
			
		||||
                elif isinstance(value_widget, QtWidgets.QDoubleSpinBox):
 | 
			
		||||
                    value = value_widget.text()
 | 
			
		||||
                else:
 | 
			
		||||
                    raise TypeError('Config key %s has unknown type %s' % (key, repr(value_widget)))
 | 
			
		||||
 | 
			
		||||
                args[key] = value
 | 
			
		||||
 | 
			
		||||
            options[section.title()] = args
 | 
			
		||||
 | 
			
		||||
        write_configuration(options)
 | 
			
		||||
 | 
			
		||||
        super().accept()
 | 
			
		||||
							
								
								
									
										55
									
								
								src/gui_qt/lib/decorators.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/gui_qt/lib/decorators.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,55 @@
 | 
			
		||||
from functools import wraps
 | 
			
		||||
 | 
			
		||||
from ..Qt import QtWidgets
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def update_indexes(func):
 | 
			
		||||
 | 
			
		||||
    @wraps(func)
 | 
			
		||||
    def wrapped(self, *args, **kwargs):
 | 
			
		||||
        ret_val = func(self, *args, **kwargs)
 | 
			
		||||
        self.blockSignals(True)
 | 
			
		||||
 | 
			
		||||
        iterator = QtWidgets.QTreeWidgetItemIterator(self)
 | 
			
		||||
        i = j = 0
 | 
			
		||||
        while iterator.value():
 | 
			
		||||
            item = iterator.value()
 | 
			
		||||
            if item is not None:
 | 
			
		||||
                if item.parent() is None:
 | 
			
		||||
                    item.setText(1, 'g[{}]'.format(i))
 | 
			
		||||
                    i += 1
 | 
			
		||||
                    j = 0
 | 
			
		||||
                else:
 | 
			
		||||
                    item.setText(1, '.s[{}]'.format(j))
 | 
			
		||||
                    j += 1
 | 
			
		||||
                iterator += 1
 | 
			
		||||
 | 
			
		||||
        self.blockSignals(False)
 | 
			
		||||
 | 
			
		||||
        return ret_val
 | 
			
		||||
 | 
			
		||||
    return wrapped
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def plot_update(func):
 | 
			
		||||
 | 
			
		||||
    @wraps(func)
 | 
			
		||||
    def wrapped(self, *args, **kwargs):
 | 
			
		||||
        ret_val = func(self, *args, **kwargs)
 | 
			
		||||
        m = self._data.mask
 | 
			
		||||
        _x = self._data.x
 | 
			
		||||
        _y = self._data.y
 | 
			
		||||
 | 
			
		||||
        self.plot_real.setData(x=_x[m], y=_y.real[m], name=self._data.name)
 | 
			
		||||
        if self.plot_imag is not None:
 | 
			
		||||
            self.plot_imag.setData(x=_x[m], y=_y.imag[m], name=self._data.name)
 | 
			
		||||
 | 
			
		||||
        if self.plot_error is not None:
 | 
			
		||||
            _y_err = self._data.y_err
 | 
			
		||||
            self.plot_error.setData(x=_x[m], y=_y.real[m], top=_y_err[m], bottom=_y_err[m])
 | 
			
		||||
 | 
			
		||||
        self.dataChanged.emit(self.id)
 | 
			
		||||
 | 
			
		||||
        return ret_val
 | 
			
		||||
 | 
			
		||||
    return wrapped
 | 
			
		||||
							
								
								
									
										235
									
								
								src/gui_qt/lib/delegates.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										235
									
								
								src/gui_qt/lib/delegates.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,235 @@
 | 
			
		||||
import re
 | 
			
		||||
 | 
			
		||||
from nmreval.lib.colors import BaseColor, Colors
 | 
			
		||||
from nmreval.lib.lines import LineStyle
 | 
			
		||||
from nmreval.lib.symbols import SymbolStyle, make_symbol_pixmap
 | 
			
		||||
 | 
			
		||||
from ..Qt import QtWidgets, QtGui, QtCore
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PropertyDelegate(QtWidgets.QStyledItemDelegate):
 | 
			
		||||
    def paint(self, painter: QtGui.QPainter, options: QtWidgets.QStyleOptionViewItem, idx: QtCore.QModelIndex):
 | 
			
		||||
        r = idx.data(QtCore.Qt.DisplayRole)
 | 
			
		||||
        if r is not None:
 | 
			
		||||
            if isinstance(r, BaseColor):
 | 
			
		||||
                painter.save()
 | 
			
		||||
                c = QtGui.QColor(*r.value)
 | 
			
		||||
                rect = options.rect
 | 
			
		||||
                painter.fillRect(rect, QtGui.QBrush(c))
 | 
			
		||||
                painter.restore()
 | 
			
		||||
 | 
			
		||||
            elif isinstance(r, LineStyle):
 | 
			
		||||
                painter.save()
 | 
			
		||||
                pen = QtGui.QPen()
 | 
			
		||||
                pal = QtGui.QGuiApplication.palette()
 | 
			
		||||
                pen.setColor(pal.color(QtGui.QPalette.Text))
 | 
			
		||||
                pen.setWidth(2)
 | 
			
		||||
                pen.setStyle(r.value)
 | 
			
		||||
                pen.setCapStyle(QtCore.Qt.RoundCap)
 | 
			
		||||
                painter.setPen(pen)
 | 
			
		||||
 | 
			
		||||
                rect = options.rect
 | 
			
		||||
                rect.adjust(5, 0, -5, 0)
 | 
			
		||||
                mid = (rect.bottom()+rect.top()) / 2
 | 
			
		||||
                painter.drawLine(rect.left(), mid, rect.right(), mid)
 | 
			
		||||
                painter.restore()
 | 
			
		||||
 | 
			
		||||
            elif isinstance(r, SymbolStyle):
 | 
			
		||||
                painter.save()
 | 
			
		||||
                pen = QtGui.QPen()
 | 
			
		||||
                pal = QtGui.QGuiApplication.palette()
 | 
			
		||||
                pen.setColor(pal.color(QtGui.QPalette.Text))
 | 
			
		||||
                painter.setPen(pen)
 | 
			
		||||
 | 
			
		||||
                pm = make_symbol_pixmap(r)
 | 
			
		||||
                painter.drawPixmap(options.rect.topLeft()+QtCore.QPoint(3, (options.rect.height()-pm.height())/2), pm)
 | 
			
		||||
 | 
			
		||||
                style = QtWidgets.QApplication.style()
 | 
			
		||||
                text_rect = style.subElementRect(QtWidgets.QStyle.SE_ItemViewItemText, options, None)
 | 
			
		||||
                text_rect.adjust(5+pm.width(), 0, 0, 0)
 | 
			
		||||
                painter.drawText(text_rect, options.displayAlignment, r.name)
 | 
			
		||||
 | 
			
		||||
                painter.restore()
 | 
			
		||||
 | 
			
		||||
            else:
 | 
			
		||||
                super().paint(painter, options, idx)
 | 
			
		||||
 | 
			
		||||
    def createEditor(self, parent: QtWidgets.QWidget,
 | 
			
		||||
                     options: QtWidgets.QStyleOptionViewItem, idx: QtCore.QModelIndex) -> QtWidgets.QWidget:
 | 
			
		||||
        data = idx.data()
 | 
			
		||||
        if isinstance(data, BaseColor):
 | 
			
		||||
            editor = ColorListEditor(parent)
 | 
			
		||||
 | 
			
		||||
        elif isinstance(data, LineStyle):
 | 
			
		||||
            editor = LineStyleEditor(parent)
 | 
			
		||||
 | 
			
		||||
        elif isinstance(data, SymbolStyle):
 | 
			
		||||
            editor = SymbolStyleEditor(parent)
 | 
			
		||||
 | 
			
		||||
        elif isinstance(data, float):
 | 
			
		||||
            editor = SpinBoxEditor(parent)
 | 
			
		||||
 | 
			
		||||
        else:
 | 
			
		||||
            editor = super().createEditor(parent, options, idx)
 | 
			
		||||
 | 
			
		||||
        return editor
 | 
			
		||||
 | 
			
		||||
    def setEditorData(self, editor: QtWidgets.QWidget, idx: QtCore.QModelIndex):
 | 
			
		||||
        data = idx.data()
 | 
			
		||||
        if isinstance(data, (BaseColor, LineStyle, SymbolStyle)):
 | 
			
		||||
            editor.value = data
 | 
			
		||||
        else:
 | 
			
		||||
            super().setEditorData(editor, idx)
 | 
			
		||||
 | 
			
		||||
    def setModelData(self, editor: QtWidgets.QWidget, model: QtCore.QAbstractItemModel, idx: QtCore.QModelIndex):
 | 
			
		||||
        data = idx.data()
 | 
			
		||||
        if isinstance(data, (BaseColor, LineStyle, SymbolStyle)):
 | 
			
		||||
            model.setData(idx, editor.value)
 | 
			
		||||
        else:
 | 
			
		||||
            super().setModelData(editor, model, idx)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ColorListEditor(QtWidgets.QComboBox):
 | 
			
		||||
    def __init__(self, parent=None):
 | 
			
		||||
        super().__init__(parent)
 | 
			
		||||
        self.populateList()
 | 
			
		||||
 | 
			
		||||
    @QtCore.pyqtProperty(BaseColor, user=True)
 | 
			
		||||
    def value(self):
 | 
			
		||||
        return self.itemData(self.currentIndex())
 | 
			
		||||
 | 
			
		||||
    @value.setter
 | 
			
		||||
    def value(self, val):
 | 
			
		||||
        for i in range(self.count()):
 | 
			
		||||
            if val.name == self.itemData(i).name:
 | 
			
		||||
                self.setCurrentIndex(i)
 | 
			
		||||
                break
 | 
			
		||||
 | 
			
		||||
    def populateList(self):
 | 
			
		||||
        for i, colorName in enumerate(Colors):
 | 
			
		||||
            color = QtGui.QColor(*colorName.value)
 | 
			
		||||
            self.insertItem(i, colorName.name)
 | 
			
		||||
            self.setItemData(i, colorName)
 | 
			
		||||
            px = QtGui.QPixmap(self.iconSize())
 | 
			
		||||
            px.fill(color)
 | 
			
		||||
            self.setItemData(i, QtGui.QIcon(px), QtCore.Qt.DecorationRole)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SpinBoxEditor(QtWidgets.QDoubleSpinBox):
 | 
			
		||||
    def __init__(self, parent=None):
 | 
			
		||||
        super().__init__(parent)
 | 
			
		||||
        self.setValue(1.0)
 | 
			
		||||
        self.setSingleStep(0.1)
 | 
			
		||||
        self.setDecimals(1)
 | 
			
		||||
 | 
			
		||||
    @QtCore.pyqtProperty(float, user=True)
 | 
			
		||||
    def value(self):
 | 
			
		||||
        return super().value()
 | 
			
		||||
 | 
			
		||||
    @value.setter
 | 
			
		||||
    def value(self, val):
 | 
			
		||||
        super().setValue(val)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LineStyleEditor(QtWidgets.QComboBox):
 | 
			
		||||
    def __init__(self, parent=None):
 | 
			
		||||
        super().__init__(parent=parent)
 | 
			
		||||
 | 
			
		||||
        self.setItemDelegate(LineStyleDelegate())
 | 
			
		||||
        self.populate()
 | 
			
		||||
 | 
			
		||||
    @QtCore.pyqtProperty(int, user=True)
 | 
			
		||||
    def value(self):
 | 
			
		||||
        return self.itemData(self.currentIndex())
 | 
			
		||||
 | 
			
		||||
    @value.setter
 | 
			
		||||
    def value(self, val):
 | 
			
		||||
        for i in range(self.count()):
 | 
			
		||||
            if val == self.itemData(i):
 | 
			
		||||
                self.setCurrentIndex(i)
 | 
			
		||||
                break
 | 
			
		||||
 | 
			
		||||
    def populate(self):
 | 
			
		||||
        for i, style in enumerate(LineStyle):
 | 
			
		||||
            self.insertItem(i, re.sub(r'([A-Z])', r' \g<1>', style.name))
 | 
			
		||||
            self.setItemData(i, style)
 | 
			
		||||
 | 
			
		||||
    def paintEvent(self, evt: QtGui.QPaintEvent):
 | 
			
		||||
        if self.currentData() is not None:
 | 
			
		||||
            painter = QtWidgets.QStylePainter(self)
 | 
			
		||||
            opt = QtWidgets.QStyleOptionComboBox()
 | 
			
		||||
            self.initStyleOption(opt)
 | 
			
		||||
            painter.drawComplexControl(QtWidgets.QStyle.CC_ComboBox, opt)
 | 
			
		||||
            pen = QtGui.QPen()
 | 
			
		||||
            pal = QtGui.QGuiApplication.palette()
 | 
			
		||||
            pen.setColor(pal.color(QtGui.QPalette.Text))
 | 
			
		||||
            pen.setWidth(2)
 | 
			
		||||
            pen.setStyle(self.currentData().value)
 | 
			
		||||
            pen.setCapStyle(QtCore.Qt.RoundCap)
 | 
			
		||||
            painter.setPen(pen)
 | 
			
		||||
 | 
			
		||||
            rect = painter.style().subControlRect(QtWidgets.QStyle.CC_ComboBox,
 | 
			
		||||
                                                  opt, QtWidgets.QStyle.SC_ComboBoxEditField, None)
 | 
			
		||||
            rect.adjust(+10, 0, -10, 0)
 | 
			
		||||
            mid = (rect.bottom() + rect.top()) / 2
 | 
			
		||||
            painter.drawLine(rect.left(), mid, rect.right(), mid)
 | 
			
		||||
            painter.end()
 | 
			
		||||
        else:
 | 
			
		||||
            super().paintEvent(evt)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LineStyleDelegate(QtWidgets.QStyledItemDelegate):
 | 
			
		||||
    def paint(self, painter, option, index):
 | 
			
		||||
        data = index.data(QtCore.Qt.UserRole)
 | 
			
		||||
 | 
			
		||||
        if data is not None:
 | 
			
		||||
            pen = QtGui.QPen()
 | 
			
		||||
            pal = QtGui.QGuiApplication.palette()
 | 
			
		||||
            pen.setColor(pal.color(QtGui.QPalette.Text))
 | 
			
		||||
            pen.setWidth(2)
 | 
			
		||||
            pen.setStyle(data.value)
 | 
			
		||||
            pen.setCapStyle(QtCore.Qt.RoundCap)
 | 
			
		||||
            painter.setPen(pen)
 | 
			
		||||
 | 
			
		||||
            rect = option.rect
 | 
			
		||||
            rect.adjust(+10, 0, -10, 0)
 | 
			
		||||
            mid = (rect.bottom()+rect.top()) / 2
 | 
			
		||||
            painter.drawLine(rect.left(), mid, rect.right(), mid)
 | 
			
		||||
        else:
 | 
			
		||||
            QtWidgets.QStyledItemDelegate.paint(self, painter, option, index)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SymbolStyleEditor(QtWidgets.QComboBox):
 | 
			
		||||
    def __init__(self, parent=None):
 | 
			
		||||
        super().__init__(parent=parent)
 | 
			
		||||
 | 
			
		||||
        self.populate()
 | 
			
		||||
 | 
			
		||||
    @QtCore.pyqtProperty(SymbolStyle, user=True)
 | 
			
		||||
    def value(self):
 | 
			
		||||
        return self.itemData(self.currentIndex())
 | 
			
		||||
 | 
			
		||||
    @value.setter
 | 
			
		||||
    def value(self, val):
 | 
			
		||||
        for i in range(self.count()):
 | 
			
		||||
            if val == self.itemData(i):
 | 
			
		||||
                self.setCurrentIndex(i)
 | 
			
		||||
                break
 | 
			
		||||
 | 
			
		||||
    def populate(self):
 | 
			
		||||
        for i, s in enumerate(SymbolStyle):
 | 
			
		||||
            self.insertItem(i, re.sub(r'([A-Z])', r' \g<1>', s.name))
 | 
			
		||||
            self.setItemData(i, s)
 | 
			
		||||
            self.setItemData(i, make_symbol_pixmap(s), QtCore.Qt.DecorationRole)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HeaderDelegate(QtWidgets.QStyledItemDelegate):
 | 
			
		||||
    def paint(self, painter: QtGui.QPainter, option: QtWidgets.QStyleOptionViewItem, index: QtCore.QModelIndex):
 | 
			
		||||
 | 
			
		||||
        header_option = QtWidgets.QStyleOptionHeader()
 | 
			
		||||
        header_option.rect = option.rect
 | 
			
		||||
 | 
			
		||||
        style = QtWidgets.QApplication.style()
 | 
			
		||||
        style.drawControl(QtWidgets.QStyle.CE_HeaderSection, header_option, painter)
 | 
			
		||||
        if option.state & QtWidgets.QStyle.State_Selected:
 | 
			
		||||
            painter.fillRect(option.rect, option.palette.highlight())
 | 
			
		||||
							
								
								
									
										65
									
								
								src/gui_qt/lib/expandablewidget.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								src/gui_qt/lib/expandablewidget.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,65 @@
 | 
			
		||||
from ..Qt import QtWidgets, QtCore
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ExpandableWidget(QtWidgets.QWidget):
 | 
			
		||||
    expansionChanged = QtCore.pyqtSignal()
 | 
			
		||||
 | 
			
		||||
    def __init__(self, parent=None):
 | 
			
		||||
        super().__init__(parent=parent)
 | 
			
		||||
 | 
			
		||||
        self._init_ui()
 | 
			
		||||
 | 
			
		||||
        self._widget = None
 | 
			
		||||
        self._expanded = False
 | 
			
		||||
 | 
			
		||||
        self.toolButton.clicked.connect(self.changeVisibility)
 | 
			
		||||
 | 
			
		||||
    def _init_ui(self):
 | 
			
		||||
        self.verticalLayout = QtWidgets.QVBoxLayout(self)
 | 
			
		||||
        self.verticalLayout.setContentsMargins(0, 0, 0, 0)
 | 
			
		||||
        self.verticalLayout.setSpacing(0)
 | 
			
		||||
 | 
			
		||||
        self.horizontalLayout = QtWidgets.QHBoxLayout()
 | 
			
		||||
        self.horizontalLayout.setSpacing(0)
 | 
			
		||||
 | 
			
		||||
        self.toolButton = QtWidgets.QToolButton(self)
 | 
			
		||||
        self.toolButton.setArrowType(QtCore.Qt.RightArrow)
 | 
			
		||||
        self.toolButton.setStyleSheet('border: 0')
 | 
			
		||||
        self.toolButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon)
 | 
			
		||||
        self.horizontalLayout.addWidget(self.toolButton)
 | 
			
		||||
 | 
			
		||||
        self.line = QtWidgets.QFrame(self)
 | 
			
		||||
        self.line.setFrameShape(QtWidgets.QFrame.HLine)
 | 
			
		||||
        self.horizontalLayout.addWidget(self.line)
 | 
			
		||||
 | 
			
		||||
        self.verticalLayout.addLayout(self.horizontalLayout)
 | 
			
		||||
 | 
			
		||||
    def addWidget(self, widget: QtWidgets.QWidget):
 | 
			
		||||
        self._widget = widget
 | 
			
		||||
        self._widget.setVisible(self._expanded)
 | 
			
		||||
        self.layout().addWidget(widget)
 | 
			
		||||
 | 
			
		||||
    def setText(self, text: str):
 | 
			
		||||
        self.toolButton.setText(text)
 | 
			
		||||
 | 
			
		||||
    def isExpanded(self):
 | 
			
		||||
        return self._expanded
 | 
			
		||||
 | 
			
		||||
    def changeVisibility(self):
 | 
			
		||||
        if not self._widget:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        self.setExpansion(not self._expanded)
 | 
			
		||||
 | 
			
		||||
        self.expansionChanged.emit()
 | 
			
		||||
 | 
			
		||||
    def setExpansion(self, state: bool):
 | 
			
		||||
        self.blockSignals(True)
 | 
			
		||||
        if state:
 | 
			
		||||
            self.toolButton.setArrowType(QtCore.Qt.DownArrow)
 | 
			
		||||
        else:
 | 
			
		||||
            self.toolButton.setArrowType(QtCore.Qt.RightArrow)
 | 
			
		||||
 | 
			
		||||
        self._expanded = state
 | 
			
		||||
        self._widget.setVisible(state)
 | 
			
		||||
        self.blockSignals(False)
 | 
			
		||||
							
								
								
									
										406
									
								
								src/gui_qt/lib/forms.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										406
									
								
								src/gui_qt/lib/forms.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,406 @@
 | 
			
		||||
from numpy import inf
 | 
			
		||||
 | 
			
		||||
from nmreval.utils.text import convert
 | 
			
		||||
 | 
			
		||||
from ..Qt import QtGui, QtCore, QtWidgets
 | 
			
		||||
from .._py.mean_form import Ui_mean_form
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class QDelayWidget(QtWidgets.QWidget):
 | 
			
		||||
    get_data = QtCore.pyqtSignal(str)
 | 
			
		||||
    newDelay = QtCore.pyqtSignal()
 | 
			
		||||
 | 
			
		||||
    def __init__(self, parent=None):
 | 
			
		||||
        super().__init__(parent=parent)
 | 
			
		||||
 | 
			
		||||
        self.vals = ''
 | 
			
		||||
        self.dim = 0
 | 
			
		||||
        self.plainTextEdit.setVisible(False)
 | 
			
		||||
 | 
			
		||||
    def __call__(self):
 | 
			
		||||
        self.vals = ''
 | 
			
		||||
        self.dim = 0
 | 
			
		||||
        self.comboBox.clear()
 | 
			
		||||
        self.comboBox.blockSignals(True)
 | 
			
		||||
        self.comboBox.addItem('New list...')
 | 
			
		||||
        self.comboBox.blockSignals(True)
 | 
			
		||||
        self.plainTextEdit.setVisible(False)
 | 
			
		||||
 | 
			
		||||
    def add_items(self, item):
 | 
			
		||||
        self.comboBox.blockSignals(True)
 | 
			
		||||
        if isinstance(item, list):
 | 
			
		||||
            self.comboBox.insertItems(0, item)
 | 
			
		||||
        else:
 | 
			
		||||
            self.comboBox.insertItem(0, item)
 | 
			
		||||
        self.comboBox.setCurrentIndex(0)
 | 
			
		||||
        self.comboBox.blockSignals(False)
 | 
			
		||||
 | 
			
		||||
    @QtCore.pyqtSlot(name='on_toolButton_clicked')
 | 
			
		||||
    def show_values(self):
 | 
			
		||||
        if self.plainTextEdit.isVisible():
 | 
			
		||||
            self.plainTextEdit.clear()
 | 
			
		||||
            self.plainTextEdit.setVisible(False)
 | 
			
		||||
        elif self.comboBox.currentText() == 'New list...':
 | 
			
		||||
            self.newDelay.emit()
 | 
			
		||||
        else:
 | 
			
		||||
            self.get_data.emit(self.comboBox.currentText())
 | 
			
		||||
            self.plainTextEdit.setVisible(True)
 | 
			
		||||
            self.plainTextEdit.setPlainText(self.vals)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LineEdit(QtWidgets.QLineEdit):
 | 
			
		||||
    values_requested = QtCore.pyqtSignal()
 | 
			
		||||
 | 
			
		||||
    def __init__(self, parent=None):
 | 
			
		||||
        super().__init__(parent=parent)
 | 
			
		||||
 | 
			
		||||
    def contextMenuEvent(self, evt):
 | 
			
		||||
        menu = self.createStandardContextMenu()
 | 
			
		||||
        request_action = menu.addAction('Use value of sets')
 | 
			
		||||
 | 
			
		||||
        action = menu.exec(evt.globalPos())
 | 
			
		||||
 | 
			
		||||
        if action == request_action:
 | 
			
		||||
            self.values_requested.emit()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LineEditPost(QtWidgets.QLineEdit):
 | 
			
		||||
    values_requested = QtCore.pyqtSignal()
 | 
			
		||||
 | 
			
		||||
    def __init__(self, parent=None):
 | 
			
		||||
        super().__init__(parent=parent)
 | 
			
		||||
 | 
			
		||||
        self.suffix = ''
 | 
			
		||||
        self.prefix = ''
 | 
			
		||||
 | 
			
		||||
        self.editingFinished.connect(self.add_fixes)
 | 
			
		||||
 | 
			
		||||
    def add_fixes(self):
 | 
			
		||||
        self.setText(self.prefix+super().text()+self.suffix)
 | 
			
		||||
 | 
			
		||||
    def text(self):
 | 
			
		||||
        text = super().text()
 | 
			
		||||
        if text.startswith(self.prefix):
 | 
			
		||||
            text = text[len(self.prefix):]
 | 
			
		||||
        if text.endswith(self.suffix):
 | 
			
		||||
            text = text[:-len(self.suffix)]
 | 
			
		||||
 | 
			
		||||
        return text
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FormWidget(QtWidgets.QWidget):
 | 
			
		||||
    types = {
 | 
			
		||||
        'float': (float, QtGui.QDoubleValidator),
 | 
			
		||||
        'int': (int, QtGui.QIntValidator),
 | 
			
		||||
        'str': (str, lambda: 0),
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    valueChanged = QtCore.pyqtSignal(object)
 | 
			
		||||
    stateChanged = QtCore.pyqtSignal(bool)
 | 
			
		||||
 | 
			
		||||
    def __init__(self, name: str, validator: str = 'float', fixable: bool = False, parent=None):
 | 
			
		||||
        super().__init__(parent=parent)
 | 
			
		||||
 | 
			
		||||
        self._init_ui()
 | 
			
		||||
 | 
			
		||||
        self._name = name
 | 
			
		||||
 | 
			
		||||
        self._type = FormWidget.types[validator][0]
 | 
			
		||||
        self.vals.setValidator(FormWidget.types[validator][1]())
 | 
			
		||||
        self.vals.textChanged.connect(lambda: self.valueChanged.emit(self.value) if self.value is not None else 0)
 | 
			
		||||
 | 
			
		||||
        self.label.setText(convert(name))
 | 
			
		||||
 | 
			
		||||
        self._checkable = fixable
 | 
			
		||||
        self.checkBox.setVisible(fixable)
 | 
			
		||||
        self.checkBox.stateChanged.connect(lambda x: self.stateChanged.emit(True if x else False))
 | 
			
		||||
 | 
			
		||||
    def _init_ui(self):
 | 
			
		||||
        layout = QtWidgets.QHBoxLayout(self)
 | 
			
		||||
        layout.setContentsMargins(1, 1, 1, 1)
 | 
			
		||||
        layout.setSpacing(3)
 | 
			
		||||
 | 
			
		||||
        self.label = QtWidgets.QLabel(self)
 | 
			
		||||
        layout.addWidget(self.label)
 | 
			
		||||
 | 
			
		||||
        layout.addSpacerItem(QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum))
 | 
			
		||||
 | 
			
		||||
        self.vals = QtWidgets.QLineEdit(self)
 | 
			
		||||
        layout.addWidget(self.vals)
 | 
			
		||||
 | 
			
		||||
        self.checkBox = QtWidgets.QCheckBox('Fix?', self)
 | 
			
		||||
        layout.addWidget(self.checkBox)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def value(self):
 | 
			
		||||
        try:
 | 
			
		||||
            return self._type(self.vals.text().replace(',', '.'))
 | 
			
		||||
        except ValueError:
 | 
			
		||||
            return {float: 0.0, int: 0, str: ''}[self._type]
 | 
			
		||||
 | 
			
		||||
    @value.setter
 | 
			
		||||
    def value(self, val):
 | 
			
		||||
        if self._type == 'str':
 | 
			
		||||
            self.vals.setText(val)
 | 
			
		||||
        elif self._type == 'int':
 | 
			
		||||
            self.vals.setText(f'{float(val):.0f}')
 | 
			
		||||
        else:
 | 
			
		||||
            self.vals.setText(f'{float(val):.5g}')
 | 
			
		||||
 | 
			
		||||
    def setChecked(self, enable):
 | 
			
		||||
        if self._checkable:
 | 
			
		||||
            self.checkBox.setCheckState(QtCore.Qt.Checked if enable else QtCore.Qt.Unchecked)
 | 
			
		||||
        else:
 | 
			
		||||
            print(f'Parameter {self._name} is not variable')
 | 
			
		||||
 | 
			
		||||
    def isChecked(self):
 | 
			
		||||
        return self.checkBox.isChecked()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SelectionWidget(QtWidgets.QWidget):
 | 
			
		||||
    selectionChanged = QtCore.pyqtSignal(str, object)
 | 
			
		||||
 | 
			
		||||
    def __init__(self, label: str, argname: str, opts: dict, parent=None):
 | 
			
		||||
        super().__init__(parent=parent)
 | 
			
		||||
 | 
			
		||||
        self._init_ui()
 | 
			
		||||
        self.label.setText(convert(label))
 | 
			
		||||
        for k in opts.keys():
 | 
			
		||||
            self.comboBox.addItem(k)
 | 
			
		||||
 | 
			
		||||
        self.argname = argname
 | 
			
		||||
        self.options = opts
 | 
			
		||||
 | 
			
		||||
        self.comboBox.currentIndexChanged.connect(lambda idx: self.selectionChanged.emit(self.argname, self.value))
 | 
			
		||||
 | 
			
		||||
    def _init_ui(self):
 | 
			
		||||
        layout = QtWidgets.QHBoxLayout(self)
 | 
			
		||||
        layout.setContentsMargins(1, 1, 1, 1)
 | 
			
		||||
        layout.setSpacing(2)
 | 
			
		||||
 | 
			
		||||
        self.label = QtWidgets.QLabel(self)
 | 
			
		||||
        layout.addWidget(self.label)
 | 
			
		||||
 | 
			
		||||
        layout.addSpacerItem(QtWidgets.QSpacerItem(65, 20,
 | 
			
		||||
                                                   QtWidgets.QSizePolicy.Expanding,
 | 
			
		||||
                                                   QtWidgets.QSizePolicy.Minimum))
 | 
			
		||||
 | 
			
		||||
        self.comboBox = QtWidgets.QComboBox(self)
 | 
			
		||||
        layout.addWidget(self.comboBox)
 | 
			
		||||
        self.setLayout(layout)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def value(self):
 | 
			
		||||
        return {self.argname: self.options[self.comboBox.currentText()]}
 | 
			
		||||
 | 
			
		||||
    def get_parameter(self):
 | 
			
		||||
        return str(self.comboBox.currentText())
 | 
			
		||||
 | 
			
		||||
    @value.setter
 | 
			
		||||
    def value(self, val):
 | 
			
		||||
        if isinstance(val, dict):
 | 
			
		||||
            val = list(val.values())[0]
 | 
			
		||||
        key = [k for k, v in self.options.items() if v == val][0]
 | 
			
		||||
        self.comboBox.setCurrentIndex(self.comboBox.findText(key))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Widget(QtWidgets.QWidget, Ui_mean_form):
 | 
			
		||||
    valueChanged = QtCore.pyqtSignal()
 | 
			
		||||
 | 
			
		||||
    def __init__(self, name: str, tree: dict, collapsing: bool = False, parent=None):
 | 
			
		||||
        super().__init__(parent=parent)
 | 
			
		||||
        self.setupUi(self)
 | 
			
		||||
 | 
			
		||||
        self._tree = {}
 | 
			
		||||
 | 
			
		||||
        self._collapse = collapsing
 | 
			
		||||
 | 
			
		||||
        self.label.setText(convert(name))
 | 
			
		||||
        self.lineEdit.setValidator(QtGui.QDoubleValidator())
 | 
			
		||||
 | 
			
		||||
        self.set_graphs(tree)
 | 
			
		||||
        self.change_graph(0)
 | 
			
		||||
 | 
			
		||||
        if self._collapse:
 | 
			
		||||
            self.digit_checkbox.hide()
 | 
			
		||||
            self.frame.hide()
 | 
			
		||||
            self.data_checkbox.stateChanged.connect(self.collapse_widgets)
 | 
			
		||||
        else:
 | 
			
		||||
            self.data_checkbox.stateChanged.connect(self.change_mode)
 | 
			
		||||
            self.digit_checkbox.stateChanged.connect(self.change_mode)
 | 
			
		||||
 | 
			
		||||
    def set_graphs(self, graph: dict):
 | 
			
		||||
        self.graph_combobox.blockSignals(True)
 | 
			
		||||
        self._tree.clear()
 | 
			
		||||
        for key, (name, _) in graph.items():
 | 
			
		||||
            self.graph_combobox.addItem(name, userData=key)
 | 
			
		||||
        self.graph_combobox.blockSignals(False)
 | 
			
		||||
        self._tree.update(graph)
 | 
			
		||||
        self.change_graph(0)
 | 
			
		||||
 | 
			
		||||
    @QtCore.pyqtSlot(int, name='on_graph_combobox_currentIndexChanged')
 | 
			
		||||
    def change_graph(self, idx: int):
 | 
			
		||||
        self.set_combobox.clear()
 | 
			
		||||
 | 
			
		||||
        key = self.graph_combobox.itemData(idx, QtCore.Qt.UserRole)
 | 
			
		||||
        if key is not None:
 | 
			
		||||
            for set_key, set_name in self._tree[key][1]:
 | 
			
		||||
                self.set_combobox.addItem(set_name, userData=set_key)
 | 
			
		||||
 | 
			
		||||
    def on_lineEdit_textChanged(self, text=''):
 | 
			
		||||
        if text:
 | 
			
		||||
            self.valueChanged.emit()
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def value(self):
 | 
			
		||||
        if self.data_checkbox.isChecked():
 | 
			
		||||
            return self.set_combobox.currentData()
 | 
			
		||||
        else:
 | 
			
		||||
            try:
 | 
			
		||||
                return float(self.lineEdit.text())
 | 
			
		||||
            except ValueError:
 | 
			
		||||
                return
 | 
			
		||||
 | 
			
		||||
    @QtCore.pyqtSlot(int)
 | 
			
		||||
    def change_mode(self, state: int):
 | 
			
		||||
        box = self.sender()
 | 
			
		||||
        other_box = self.data_checkbox if box == self.digit_checkbox else self.digit_checkbox
 | 
			
		||||
 | 
			
		||||
        if (state == QtCore.Qt.Unchecked) and (other_box.checkState() == QtCore.Qt.Unchecked):
 | 
			
		||||
            box.blockSignals(True)
 | 
			
		||||
            box.setCheckState(QtCore.Qt.Checked)
 | 
			
		||||
            box.blockSignals(False)
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        other_box.blockSignals(True)
 | 
			
		||||
        other_box.setChecked(False)
 | 
			
		||||
        other_box.blockSignals(False)
 | 
			
		||||
 | 
			
		||||
        self.valueChanged.emit()
 | 
			
		||||
 | 
			
		||||
    @QtCore.pyqtSlot(int)
 | 
			
		||||
    def collapse_widgets(self, state: int):
 | 
			
		||||
        data_is_checked = state == QtCore.Qt.Checked
 | 
			
		||||
        self.frame.setVisible(data_is_checked)
 | 
			
		||||
        self.lineEdit.setVisible(not data_is_checked)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CheckBoxHeader(QtWidgets.QHeaderView):
 | 
			
		||||
    clicked = QtCore.pyqtSignal(int, bool)
 | 
			
		||||
 | 
			
		||||
    _x_offset = 3
 | 
			
		||||
    _y_offset = 0  # This value is calculated later, based on the height of the paint rect
 | 
			
		||||
    _width = 20
 | 
			
		||||
    _height = 20
 | 
			
		||||
 | 
			
		||||
    def __init__(self, column_indices, orientation=QtCore.Qt.Horizontal, parent=None):
 | 
			
		||||
        super().__init__(orientation, parent)
 | 
			
		||||
        self.setSectionResizeMode(QtWidgets.QHeaderView.Stretch)
 | 
			
		||||
        # self.setClickable(True)
 | 
			
		||||
 | 
			
		||||
        if isinstance(column_indices, list) or isinstance(column_indices, tuple):
 | 
			
		||||
            self.column_indices = column_indices
 | 
			
		||||
        elif isinstance(column_indices, int):
 | 
			
		||||
            self.column_indices = [column_indices]
 | 
			
		||||
        else:
 | 
			
		||||
            raise RuntimeError('column_indices must be a list, tuple or integer')
 | 
			
		||||
 | 
			
		||||
        self.isChecked = {}
 | 
			
		||||
        for column in self.column_indices:
 | 
			
		||||
            self.isChecked[column] = 0
 | 
			
		||||
 | 
			
		||||
    def paintSection(self, painter, rect, logicalIndex):
 | 
			
		||||
        painter.save()
 | 
			
		||||
        super().paintSection(painter, rect, logicalIndex)
 | 
			
		||||
        painter.restore()
 | 
			
		||||
 | 
			
		||||
        self._y_offset = int((rect.height()-self._width)/2.)
 | 
			
		||||
 | 
			
		||||
        if logicalIndex in self.column_indices:
 | 
			
		||||
            option = QtWidgets.QStyleOptionButton()
 | 
			
		||||
            option.rect = QtCore.QRect(rect.x() + self._x_offset, rect.y() + self._y_offset, self._width, self._height)
 | 
			
		||||
            option.state = QtWidgets.QStyle.State_Enabled | QtWidgets.QStyle.State_Active
 | 
			
		||||
            if self.isChecked[logicalIndex] == 2:
 | 
			
		||||
                option.state |= QtWidgets.QStyle.State_NoChange
 | 
			
		||||
            elif self.isChecked[logicalIndex]:
 | 
			
		||||
                option.state |= QtWidgets.QStyle.State_On
 | 
			
		||||
            else:
 | 
			
		||||
                option.state |= QtWidgets.QStyle.State_Off
 | 
			
		||||
 | 
			
		||||
            self.style().drawControl(QtWidgets.QStyle.CE_CheckBox,option,painter)
 | 
			
		||||
 | 
			
		||||
    def updateCheckState(self, index, state):
 | 
			
		||||
        self.isChecked[index] = state
 | 
			
		||||
        self.viewport().update()
 | 
			
		||||
 | 
			
		||||
    def mousePressEvent(self, event):
 | 
			
		||||
        index = self.logicalIndexAt(event.pos())
 | 
			
		||||
        if 0 <= index < self.count():
 | 
			
		||||
            x = self.sectionPosition(index)
 | 
			
		||||
            if x + self._x_offset < event.pos().x() < x + self._x_offset + self._width and self._y_offset < event.pos().y() < self._y_offset + self._height:
 | 
			
		||||
                if self.isChecked[index] == 1:
 | 
			
		||||
                    self.isChecked[index] = 0
 | 
			
		||||
                else:
 | 
			
		||||
                    self.isChecked[index] = 1
 | 
			
		||||
 | 
			
		||||
                self.clicked.emit(index, self.isChecked[index])
 | 
			
		||||
                self.viewport().update()
 | 
			
		||||
            else:
 | 
			
		||||
                super().mousePressEvent(event)
 | 
			
		||||
        else:
 | 
			
		||||
            super().mousePressEvent(event)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CustomRangeDialog(QtWidgets.QDialog):
 | 
			
		||||
    """ Simple dialog to enter x and y limits to fit regions"""
 | 
			
		||||
    def __init__(self, parent=None):
 | 
			
		||||
        super().__init__(parent=parent)
 | 
			
		||||
 | 
			
		||||
        self.gl = QtWidgets.QGridLayout()
 | 
			
		||||
        self.gl.addWidget(QtWidgets.QLabel('Leave empty for complete range'), 0, 0, 1, 4)
 | 
			
		||||
 | 
			
		||||
        self._limits = [[], []]
 | 
			
		||||
        for i, orient in enumerate(['x', 'y'], start=1):
 | 
			
		||||
            self.gl.addWidget(QtWidgets.QLabel(orient + ' from'), i, 0)
 | 
			
		||||
            self.gl.addWidget(QtWidgets.QLabel('to'), i, 2)
 | 
			
		||||
            for j in [0, 1]:
 | 
			
		||||
                lim = QtWidgets.QLineEdit()
 | 
			
		||||
                lim.setValidator(QtGui.QDoubleValidator())
 | 
			
		||||
                self._limits[i-1].append(lim)
 | 
			
		||||
                self.gl.addWidget(lim, i, 2*j+1)
 | 
			
		||||
 | 
			
		||||
        self.buttonbox = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok)
 | 
			
		||||
        self.buttonbox.accepted.connect(self.accept)
 | 
			
		||||
 | 
			
		||||
        self.gl.addWidget(self.buttonbox, 3, 0, 1, 4)
 | 
			
		||||
        self.setLayout(self.gl)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def limits(self):
 | 
			
		||||
        ret_val = []
 | 
			
		||||
        for orient in self._limits:
 | 
			
		||||
            for i, w in enumerate(orient):
 | 
			
		||||
                val = w.text()
 | 
			
		||||
                if val == '':
 | 
			
		||||
                    ret_val.append(-inf if i == 0 else inf)
 | 
			
		||||
                else:
 | 
			
		||||
                    ret_val.append(float(val))
 | 
			
		||||
        return ret_val
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ElideComboBox(QtWidgets.QComboBox):
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
        self.view().setTextElideMode(QtCore.Qt.ElideRight)
 | 
			
		||||
 | 
			
		||||
    def paintEvent(self, evt: QtGui.QPaintEvent):
 | 
			
		||||
        opt = QtWidgets.QStyleOptionComboBox()
 | 
			
		||||
        self.initStyleOption(opt)
 | 
			
		||||
 | 
			
		||||
        painter = QtWidgets.QStylePainter(self)
 | 
			
		||||
        painter.drawComplexControl(QtWidgets.QStyle.CC_ComboBox, opt)
 | 
			
		||||
 | 
			
		||||
        rect = self.style().subControlRect(QtWidgets.QStyle.CC_ComboBox, opt, QtWidgets.QStyle.SC_ComboBoxEditField, self)
 | 
			
		||||
 | 
			
		||||
        opt.currentText = painter.fontMetrics().elidedText(opt.currentText, QtCore.Qt.ElideRight, rect.width())
 | 
			
		||||
        painter.drawControl(QtWidgets.QStyle.CE_ComboBoxLabel, opt)
 | 
			
		||||
							
								
								
									
										380
									
								
								src/gui_qt/lib/gol.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										380
									
								
								src/gui_qt/lib/gol.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,380 @@
 | 
			
		||||
from __future__ import annotations
 | 
			
		||||
 | 
			
		||||
import numbers
 | 
			
		||||
import numpy as np
 | 
			
		||||
import sys
 | 
			
		||||
from itertools import accumulate
 | 
			
		||||
 | 
			
		||||
from ..Qt import QtWidgets, QtGui, QtCore
 | 
			
		||||
from .._py.gol import Ui_Form
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def circle(radius):
 | 
			
		||||
    pxl = []
 | 
			
		||||
    for x in range(int(np.ceil(radius/np.sqrt(2)))):
 | 
			
		||||
        y = round(np.sqrt(radius**2-x**2))
 | 
			
		||||
        pxl.extend([[x, y], [y, x], [x, -y], [y, -x],
 | 
			
		||||
                    [-x, -y], [-y, -x], [-x, y], [-y, x]])
 | 
			
		||||
 | 
			
		||||
    return np.array(pxl)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def square(a):
 | 
			
		||||
    pxl = []
 | 
			
		||||
    pxl.extend(list(zip(range(-a, a+1), [a]*(2*a+1))))
 | 
			
		||||
    pxl.extend(list(zip(range(-a, a+1), [-a]*(2*a+1))))
 | 
			
		||||
    pxl.extend(list(zip([a]*(2*a+1), range(-a, a+1))))
 | 
			
		||||
    pxl.extend(list(zip([-a]*(2*a+1), range(-a, a+1))))
 | 
			
		||||
 | 
			
		||||
    return np.array(pxl)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def diamond(a):
 | 
			
		||||
    pxl = []
 | 
			
		||||
    for x in range(int(a+1)):
 | 
			
		||||
        y = a-x
 | 
			
		||||
        pxl.extend([[x, y], [-x, y], [x, -y], [-x, -y]])
 | 
			
		||||
 | 
			
		||||
    return np.array(pxl)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def plus(a):
 | 
			
		||||
    pxl = np.zeros((4*int(a)+2, 2), dtype=int)
 | 
			
		||||
    pxl[:2*int(a)+1, 0] = np.arange(-a, a+1)
 | 
			
		||||
    pxl[2*int(a)+1:, 1] = np.arange(-a, a+1)
 | 
			
		||||
 | 
			
		||||
    return pxl
 | 
			
		||||
 | 
			
		||||
# birth, survival
 | 
			
		||||
predefined_rules = {
 | 
			
		||||
    'Conway': ('23', '3'),
 | 
			
		||||
    'Life34': ('34', '34'),
 | 
			
		||||
    'Coagulation': ('235678', '378'),
 | 
			
		||||
    'Corrosion': ('12345', '45'),
 | 
			
		||||
    'Long life': ('5', '345'),
 | 
			
		||||
    'Maze': ('12345', '3'),
 | 
			
		||||
    'Coral': ('45678', '3'),
 | 
			
		||||
    'Pseudo life': ('238', '357'),
 | 
			
		||||
    'Flakes': ('012345678', '3'),
 | 
			
		||||
    'Gnarl': ('1', '1'),
 | 
			
		||||
    'Fabric': ('12', '1'),
 | 
			
		||||
    'Assimilation': ('4567', '345'),
 | 
			
		||||
    'Diamoeba': ('5678', '35678'),
 | 
			
		||||
    'High life': ('23', '36'),
 | 
			
		||||
    'More Maze': ('1235', '3'),
 | 
			
		||||
    'Replicator': ('1357', '1357'),
 | 
			
		||||
    'Seed': ('', '2'),
 | 
			
		||||
    'Serviette': ('', '234'),
 | 
			
		||||
    'More coagulation': ('235678', '3678'),
 | 
			
		||||
    'Domino': ('125', '36'),
 | 
			
		||||
    'Anneal': ('35678', '4678'),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class GameOfLife:
 | 
			
		||||
    colors = [
 | 
			
		||||
        [31, 119, 180],
 | 
			
		||||
        [255, 127, 14],
 | 
			
		||||
        [44, 160, 44],
 | 
			
		||||
        [214, 39, 40],
 | 
			
		||||
        [148, 103, 189],
 | 
			
		||||
        [140, 86, 75]
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    def __init__(self,
 | 
			
		||||
                 size: tuple = (400, 400),
 | 
			
		||||
                 pattern: np.ndarray = None,
 | 
			
		||||
                 fill: float | list[float] = 0.05,
 | 
			
		||||
                 num_pop: int = 1,
 | 
			
		||||
                 rules: str | tuple = ('23', '3')
 | 
			
		||||
                 ):
 | 
			
		||||
        self.populations = num_pop if pattern is None else 1
 | 
			
		||||
        self._size = size
 | 
			
		||||
 | 
			
		||||
        self._world = np.zeros(shape=(*self._size, self.populations), dtype=np.uint8)
 | 
			
		||||
        self._neighbors = np.zeros(shape=self._world.shape, dtype=np.uint8)
 | 
			
		||||
        self._drawing = 255 * np.zeros(shape=(*self._size, 3), dtype=np.uint8)
 | 
			
		||||
 | 
			
		||||
        self.fill = np.zeros(self.populations)
 | 
			
		||||
        self._populate(fill, pattern)
 | 
			
		||||
 | 
			
		||||
        if isinstance(rules, str):
 | 
			
		||||
            try:
 | 
			
		||||
                b_rule, s_rule = predefined_rules[rules]
 | 
			
		||||
            except KeyError:
 | 
			
		||||
                raise ValueError('Rule is not predefined')
 | 
			
		||||
        else:
 | 
			
		||||
            b_rule, s_rule = rules
 | 
			
		||||
 | 
			
		||||
        self.survival_condition = np.array([int(c) for c in s_rule])
 | 
			
		||||
        self.birth_condition = np.array([int(c) for c in b_rule])
 | 
			
		||||
 | 
			
		||||
        # indexes for neighbors
 | 
			
		||||
        self._neighbor_idx = [
 | 
			
		||||
            [[slice(None), slice(1, None)], [slice(None), slice(0, -1)]],      # N (:, 1:), S (:, _-1)
 | 
			
		||||
            [[slice(1, None), slice(1, None)], [slice(0, -1), slice(0, -1)]],  # NE (1:, 1:), SW (:-1, :-1)
 | 
			
		||||
            [[slice(1, None), slice(None)], [slice(0, -1), slice(None)]],      # E (1:, :), W (:-1, :)
 | 
			
		||||
            [[slice(1, None), slice(0, -1)], [slice(0, -1), slice(1, None)]]   # SE (1:, :-1), NW (:-1:, 1:)
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
    def _populate(self, fill, pattern):
 | 
			
		||||
        if pattern is None:
 | 
			
		||||
            if isinstance(fill, numbers.Number):
 | 
			
		||||
                fill = [fill]*self.populations
 | 
			
		||||
 | 
			
		||||
            prob = (np.random.default_rng().random(size=self._size))
 | 
			
		||||
            lower_lim = 0
 | 
			
		||||
            for i, upper_lim in enumerate(accumulate(fill)):
 | 
			
		||||
                self._world[:, :, i] = (lower_lim <= prob) & (prob < upper_lim)
 | 
			
		||||
                lower_lim = upper_lim
 | 
			
		||||
 | 
			
		||||
        else:
 | 
			
		||||
            pattern = np.asarray(pattern)
 | 
			
		||||
 | 
			
		||||
            x_step = self._size[0]//(self.populations+1)
 | 
			
		||||
            y_step = self._size[1]//(self.populations+1)
 | 
			
		||||
 | 
			
		||||
            for i in range(self.populations):
 | 
			
		||||
                self._world[-pattern[:, 0]+(i+1)*x_step, pattern[:, 1]+(i+1)*y_step, i] = 1
 | 
			
		||||
 | 
			
		||||
        for i in range(self.populations):
 | 
			
		||||
            self.fill[i] = self._world[:, :, i].sum() / (self._size[0]*self._size[1])
 | 
			
		||||
 | 
			
		||||
    def tick(self):
 | 
			
		||||
        n = self._neighbors
 | 
			
		||||
        w = self._world
 | 
			
		||||
 | 
			
		||||
        n[...] = 0
 | 
			
		||||
        for idx_1, idx_2 in self._neighbor_idx:
 | 
			
		||||
            n[tuple(idx_1)] += w[tuple(idx_2)]
 | 
			
		||||
            n[tuple(idx_2)] += w[tuple(idx_1)]
 | 
			
		||||
 | 
			
		||||
        birth = ((np.in1d(n, self.birth_condition).reshape(n.shape)) & (w.sum(axis=-1) == 0)[:, :, None])
 | 
			
		||||
        survive = ((np.in1d(n, self.survival_condition).reshape(n.shape)) & (w == 1))
 | 
			
		||||
 | 
			
		||||
        w[...] = 0
 | 
			
		||||
        w[birth | survive] = 1
 | 
			
		||||
 | 
			
		||||
    def draw(self, shade: int):
 | 
			
		||||
        if shade == 0:
 | 
			
		||||
            self._drawing[...] = 0
 | 
			
		||||
        elif shade == 1:
 | 
			
		||||
            self._drawing -= (self._drawing/4).astype(np.uint8)
 | 
			
		||||
            self._drawing = self._drawing.clip(0, 127)
 | 
			
		||||
 | 
			
		||||
        for i in range(self.populations):
 | 
			
		||||
            self._drawing[(self._world[:, :, i] == 1)] = self.colors[i]
 | 
			
		||||
            self.fill[i] = self._world[:, :, i].sum() / (self._size[0]*self._size[1])
 | 
			
		||||
 | 
			
		||||
        return self._drawing
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class QGameOfLife(QtWidgets.QDialog, Ui_Form):
 | 
			
		||||
    SPEEDS = [0.5, 1, 2, 5, 7.5, 10, 12.5, 15, 20, 25, 30, 40, 50]
 | 
			
		||||
 | 
			
		||||
    def __init__(self, parent=None):
 | 
			
		||||
        super().__init__(parent=parent)
 | 
			
		||||
        self.setupUi(self)
 | 
			
		||||
 | 
			
		||||
        self._size = (100, 100)
 | 
			
		||||
        self.game = None
 | 
			
		||||
        self._step = 0
 | 
			
		||||
        self._shading = 1
 | 
			
		||||
        self._speed = 5
 | 
			
		||||
 | 
			
		||||
        self.timer = QtCore.QTimer()
 | 
			
		||||
        self.timer.setInterval(100)
 | 
			
		||||
        self.timer.timeout.connect(self.tick)
 | 
			
		||||
 | 
			
		||||
        self._init_ui()
 | 
			
		||||
 | 
			
		||||
    def _init_ui(self):
 | 
			
		||||
        self.item = None
 | 
			
		||||
        self.scene = QtWidgets.QGraphicsScene()
 | 
			
		||||
        self.item = self.scene.addPixmap(QtGui.QPixmap())
 | 
			
		||||
        self.view.setScene(self.scene)
 | 
			
		||||
 | 
			
		||||
        self.rule_cb.addItems(list(predefined_rules.keys()))
 | 
			
		||||
 | 
			
		||||
        self.random_widgets = []
 | 
			
		||||
        for _ in range(6):
 | 
			
		||||
            w = QSliderText(15, parent=self)
 | 
			
		||||
            w.slider.valueChanged.connect(self.set_max_population)
 | 
			
		||||
            self.verticalLayout.addWidget(w)
 | 
			
		||||
            self.random_widgets.append(w)
 | 
			
		||||
 | 
			
		||||
        self.birth_line.setValidator(QtGui.QIntValidator())
 | 
			
		||||
        self.survival_line.setValidator(QtGui.QIntValidator())
 | 
			
		||||
 | 
			
		||||
        for w in self.random_widgets[1:] + [self.object_widget]:
 | 
			
		||||
            w.hide()
 | 
			
		||||
 | 
			
		||||
        self.setGeometry(QtWidgets.QStyle.alignedRect(QtCore.Qt.LeftToRight, QtCore.Qt.AlignCenter,
 | 
			
		||||
                                                      self.size(), QtWidgets.qApp.desktop().availableGeometry()))
 | 
			
		||||
 | 
			
		||||
        self.view.resizeEvent = self.resizeEvent
 | 
			
		||||
 | 
			
		||||
    @QtCore.pyqtSlot(int)
 | 
			
		||||
    def on_object_combobox_currentIndexChanged(self, idx: int):
 | 
			
		||||
        for w in self.random_widgets + [self.object_widget, self.rand_button_wdgt]:
 | 
			
		||||
            w.hide()
 | 
			
		||||
 | 
			
		||||
        if idx == 0:
 | 
			
		||||
            self.random_widgets[0].show()
 | 
			
		||||
            self.rand_button_wdgt.show()
 | 
			
		||||
        else:
 | 
			
		||||
            self.object_widget.show()
 | 
			
		||||
 | 
			
		||||
    @QtCore.pyqtSlot()
 | 
			
		||||
    def on_add_random_button_clicked(self):
 | 
			
		||||
        if self.object_combobox.currentIndex() != 0:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        for w in self.random_widgets[1:]:
 | 
			
		||||
            if not w.isVisible():
 | 
			
		||||
                w.show()
 | 
			
		||||
                break
 | 
			
		||||
 | 
			
		||||
    @QtCore.pyqtSlot()
 | 
			
		||||
    def on_remove_random_button_clicked(self):
 | 
			
		||||
        if self.object_combobox.currentIndex() != 0:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        for w in reversed(self.random_widgets[1:]):
 | 
			
		||||
            if w.isVisible():
 | 
			
		||||
                w.hide()
 | 
			
		||||
                break
 | 
			
		||||
 | 
			
		||||
    @QtCore.pyqtSlot(str)
 | 
			
		||||
    def on_rule_cb_currentIndexChanged(self, entry: str):
 | 
			
		||||
        rule = predefined_rules[entry]
 | 
			
		||||
        self.birth_line.setText(rule[1])
 | 
			
		||||
        self.survival_line.setText(rule[0])
 | 
			
		||||
 | 
			
		||||
    @QtCore.pyqtSlot(int)
 | 
			
		||||
    def set_max_population(self, _: int):
 | 
			
		||||
        over_population = -100
 | 
			
		||||
        num_tribes = -1
 | 
			
		||||
        for w in self.random_widgets:
 | 
			
		||||
            if w.isVisible():
 | 
			
		||||
                over_population += w.slider.value()
 | 
			
		||||
                num_tribes += 1
 | 
			
		||||
 | 
			
		||||
        if over_population > 0:
 | 
			
		||||
            for w in self.random_widgets:
 | 
			
		||||
                if w == self.sender() or w.isHidden():
 | 
			
		||||
                    continue
 | 
			
		||||
                w.setValue(max(0, int(w.slider.value()-over_population/num_tribes)))
 | 
			
		||||
 | 
			
		||||
    @QtCore.pyqtSlot()
 | 
			
		||||
    def on_start_button_clicked(self):
 | 
			
		||||
        self.pause_button.setChecked(False)
 | 
			
		||||
        self._step = 0
 | 
			
		||||
        self.current_step.setText(f'{self._step} steps')
 | 
			
		||||
        self._size = (int(self.height_box.value()), int(self.width_box.value()))
 | 
			
		||||
 | 
			
		||||
        pattern = None
 | 
			
		||||
        num_pop = 0
 | 
			
		||||
        fill = []
 | 
			
		||||
        pattern_size = self.object_size.value()
 | 
			
		||||
        if 2*pattern_size >= max(self._size):
 | 
			
		||||
            pattern_size = int(np.floor(max(self._size[0]-1, self._size[1]-1) / 2))
 | 
			
		||||
 | 
			
		||||
        idx = self.object_combobox.currentIndex()
 | 
			
		||||
        if idx == 0:
 | 
			
		||||
            for w in self.random_widgets:
 | 
			
		||||
                if w.isVisible():
 | 
			
		||||
                    num_pop += 1
 | 
			
		||||
                    fill.append(w.slider.value()/100)
 | 
			
		||||
        else:
 | 
			
		||||
            pattern = [None, circle, square, diamond, plus][idx](pattern_size)
 | 
			
		||||
 | 
			
		||||
        self.game = GameOfLife(self._size, pattern=pattern, fill=fill, num_pop=num_pop,
 | 
			
		||||
                               rules=(self.birth_line.text(), self.survival_line.text()))
 | 
			
		||||
        self.draw()
 | 
			
		||||
        self.view.fitInView(self.item, QtCore.Qt.KeepAspectRatio)
 | 
			
		||||
 | 
			
		||||
        self.timer.start()
 | 
			
		||||
 | 
			
		||||
    def tick(self):
 | 
			
		||||
        self.game.tick()
 | 
			
		||||
        self.draw()
 | 
			
		||||
 | 
			
		||||
        self._step += 1
 | 
			
		||||
        self.current_step.setText(f'{self._step} steps')
 | 
			
		||||
        self.cover_label.setText('\n'.join([f'Color {i+1}: {f*100:.2f} %' for i, f in enumerate(self.game.fill)]))
 | 
			
		||||
 | 
			
		||||
    @QtCore.pyqtSlot()
 | 
			
		||||
    def on_faster_button_clicked(self):
 | 
			
		||||
        self._speed = min(self._speed+1, len(QGameOfLife.SPEEDS)-1)
 | 
			
		||||
        new_speed = QGameOfLife.SPEEDS[self._speed]
 | 
			
		||||
        self.timer.setInterval(int(1000/new_speed))
 | 
			
		||||
        self.velocity_label.setText(f'{new_speed:.1f} steps/s')
 | 
			
		||||
 | 
			
		||||
    @QtCore.pyqtSlot()
 | 
			
		||||
    def on_slower_button_clicked(self):
 | 
			
		||||
        self._speed = max(self._speed-1, 0)
 | 
			
		||||
        new_speed = QGameOfLife.SPEEDS[self._speed]
 | 
			
		||||
        self.timer.setInterval(int(1000/new_speed))
 | 
			
		||||
        self.velocity_label.setText(f'{new_speed:.1f} steps/s')
 | 
			
		||||
 | 
			
		||||
    @QtCore.pyqtSlot()
 | 
			
		||||
    def on_pause_button_clicked(self):
 | 
			
		||||
        if self.pause_button.isChecked():
 | 
			
		||||
            self.timer.stop()
 | 
			
		||||
        else:
 | 
			
		||||
            self.timer.start()
 | 
			
		||||
 | 
			
		||||
    @QtCore.pyqtSlot(QtWidgets.QAbstractButton)
 | 
			
		||||
    def on_buttonGroup_buttonClicked(self, button):
 | 
			
		||||
        self._shading = [self.radioButton, self.vanish_shadow, self.full_shadow].index(button)
 | 
			
		||||
 | 
			
		||||
    def draw(self):
 | 
			
		||||
        bitmap = self.game.draw(shade=self._shading)
 | 
			
		||||
        h, w, c = bitmap.shape
 | 
			
		||||
        image = QtGui.QImage(bitmap.tobytes(), w, h, w*c, QtGui.QImage.Format_RGB888)
 | 
			
		||||
        self.scene.removeItem(self.item)
 | 
			
		||||
        pixmap = QtGui.QPixmap.fromImage(image)
 | 
			
		||||
        self.item = self.scene.addPixmap(pixmap)
 | 
			
		||||
 | 
			
		||||
    @QtCore.pyqtSlot(int)
 | 
			
		||||
    def on_hide_button_stateChanged(self, state: int):
 | 
			
		||||
        self.option_frame.setVisible(not state)
 | 
			
		||||
        self.view.fitInView(self.scene.sceneRect(), QtCore.Qt.KeepAspectRatio)
 | 
			
		||||
 | 
			
		||||
    def resizeEvent(self, evt):
 | 
			
		||||
        super().resizeEvent(evt)
 | 
			
		||||
        self.view.fitInView(self.item, QtCore.Qt.KeepAspectRatio)
 | 
			
		||||
 | 
			
		||||
    def showEvent(self, evt):
 | 
			
		||||
        super().showEvent(evt)
 | 
			
		||||
        self.view.fitInView(self.scene.sceneRect(), QtCore.Qt.KeepAspectRatio)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class QSliderText(QtWidgets.QWidget):
 | 
			
		||||
    def __init__(self, value: int, parent=None):
 | 
			
		||||
        super().__init__(parent=parent)
 | 
			
		||||
        layout = QtWidgets.QHBoxLayout()
 | 
			
		||||
        layout.setContentsMargins(0, 0, 0, 0)
 | 
			
		||||
        layout.setSpacing(3)
 | 
			
		||||
 | 
			
		||||
        self.slider = QtWidgets.QSlider(self)
 | 
			
		||||
        self.slider.setOrientation(QtCore.Qt.Horizontal)
 | 
			
		||||
        self.slider.setMaximum(100)
 | 
			
		||||
        self.slider.setTickPosition(self.slider.TicksBothSides)
 | 
			
		||||
        self.slider.setTickInterval(5)
 | 
			
		||||
 | 
			
		||||
        self.value = QtWidgets.QLabel(self)
 | 
			
		||||
 | 
			
		||||
        self.slider.valueChanged.connect(lambda x: self.value.setText(f'{x} %'))
 | 
			
		||||
 | 
			
		||||
        layout.addWidget(self.slider)
 | 
			
		||||
        layout.addWidget(self.value)
 | 
			
		||||
 | 
			
		||||
        self.setLayout(layout)
 | 
			
		||||
 | 
			
		||||
        self.setValue(value)
 | 
			
		||||
 | 
			
		||||
    def setValue(self, value: int):
 | 
			
		||||
        self.slider.setValue(value)
 | 
			
		||||
        self.value.setText(f'{value} %')
 | 
			
		||||
							
								
								
									
										216
									
								
								src/gui_qt/lib/namespace.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										216
									
								
								src/gui_qt/lib/namespace.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,216 @@
 | 
			
		||||
import inspect
 | 
			
		||||
import re
 | 
			
		||||
from collections import namedtuple
 | 
			
		||||
 | 
			
		||||
import numpy as np
 | 
			
		||||
 | 
			
		||||
from nmreval import models
 | 
			
		||||
from nmreval.configs import config_paths
 | 
			
		||||
from nmreval.lib.importer import find_models, import_
 | 
			
		||||
from nmreval.utils import constants as constants
 | 
			
		||||
from nmreval.utils.text import convert
 | 
			
		||||
 | 
			
		||||
from ..Qt import QtWidgets, QtCore
 | 
			
		||||
from .._py.namespace_widget import Ui_Form
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Namespace:
 | 
			
		||||
    def __init__(self, fitfuncs=False, const=False, basic=False):
 | 
			
		||||
        self.namespace = {}
 | 
			
		||||
        self.groupings = {}
 | 
			
		||||
        self.top_levels = {}
 | 
			
		||||
 | 
			
		||||
        if basic:
 | 
			
		||||
            self.add_namespace({'x': (None, 'x values'), 'y': (None, 'x values'), 'y_err': (None, 'y error values'),
 | 
			
		||||
                                'fit': (None, 'dictionary of fit parameter', 'fit["PIKA"]'), 'np': (np, 'numpy module')},
 | 
			
		||||
                               parents=('Basic', 'General'))
 | 
			
		||||
 | 
			
		||||
            self.add_namespace({'sin': (np.sin, 'Sine', 'sin(PIKA)'), 'cos': (np.cos, 'Cosine', 'cos(PIKA)'),
 | 
			
		||||
                                'tan': (np.tan, 'Tangens', 'tan(PIKA)'), 'ln': (np.log, 'Natural Logarithm', 'ln(PIKA)'),
 | 
			
		||||
                                'log': (np.log10, 'Logarithm (base 10)', 'log(PIKA)'),
 | 
			
		||||
                                'exp': (np.exp, 'Exponential', 'exp(PIKA)'), 'sqrt': (np.sqrt, 'Root', 'sqrt(PIKA)'),
 | 
			
		||||
                                'lin_range': (np.linspace, 'N evenly spaced over interval [start, stop]',
 | 
			
		||||
                                              'lin_range(start, stop, N)'),
 | 
			
		||||
                                'log_range': (np.geomspace, 'N evenly spaced (log-scale) over interval [start, stop]',
 | 
			
		||||
                                              'lin_range(start, stop, N)')},
 | 
			
		||||
                               parents=('Basic', 'Functions'))
 | 
			
		||||
            
 | 
			
		||||
            self.add_namespace({'max': (np.max, 'Maximum value', 'max(PIKA)'),
 | 
			
		||||
                                'min': (np.min, 'Minimum value', 'min(PIKA)'),
 | 
			
		||||
                                'argmax': (np.argmax, 'Index of maximum value', 'argmax(PIKA)'),
 | 
			
		||||
                                'argmin': (np.argmax, 'Index of minimum value', 'argmin(PIKA)')},
 | 
			
		||||
                               parents=('Basic', 'Values'))
 | 
			
		||||
 | 
			
		||||
        if const:
 | 
			
		||||
            self.add_namespace({'e': (constants.e, 'e / As'), 'eps0': (constants.epsilon0, 'epsilon0 / As/Vm'),
 | 
			
		||||
                                'Eu': (constants.Eu,), 'h': (constants.h, 'h / eVs'),
 | 
			
		||||
                                'hbar': (constants.hbar, 'hbar / eVs'), 'kB': (constants.kB, 'kB / eV/K'),
 | 
			
		||||
                                'mu0': (constants.mu0, 'mu0 / Vs/Am'), 'NA': (constants.NA, 'NA / 1/mol'),
 | 
			
		||||
                                'pi': (constants.pi,), 'R': (constants.R, 'R / eV')},
 | 
			
		||||
                               parents=('Constants', 'Maybe useful'))
 | 
			
		||||
 | 
			
		||||
            self.add_namespace({f'gamma["{k}"]': (v, k, f'gamma["{k}"]') for k, v in constants.gamma.items()},
 | 
			
		||||
                               parents=('Constants', 'Magnetogyric ratios (in 1/(sT))'))
 | 
			
		||||
 | 
			
		||||
        if fitfuncs:
 | 
			
		||||
            self.make_dict_from_fitmodule(models)
 | 
			
		||||
        try:
 | 
			
		||||
            usermodels = import_(config_paths() / 'usermodels.py')
 | 
			
		||||
            self.make_dict_from_fitmodule(usermodels, parent='User-defined')
 | 
			
		||||
        except FileNotFoundError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        ret = '\n'.join([f'{k}: {v}' for k, v in self.groupings.items()])
 | 
			
		||||
        return ret
 | 
			
		||||
 | 
			
		||||
    def make_dict_from_fitmodule(self, module, parent='Fit function'):
 | 
			
		||||
        fitfunc_dict = {}
 | 
			
		||||
        for funcs in find_models(module):
 | 
			
		||||
            name = funcs.name
 | 
			
		||||
            if funcs.type not in fitfunc_dict:
 | 
			
		||||
                fitfunc_dict[funcs.type] = {}
 | 
			
		||||
 | 
			
		||||
            func_list = fitfunc_dict[funcs.type]
 | 
			
		||||
 | 
			
		||||
            func_args = 'x, ' + ', '.join(convert(p, old='latex', new='str', brackets=False) for p in funcs.params)
 | 
			
		||||
 | 
			
		||||
            # keywords arguments need names in function
 | 
			
		||||
            sig = inspect.signature(funcs.func)
 | 
			
		||||
            for p in sig.parameters.values():
 | 
			
		||||
                if p.default != p.empty:
 | 
			
		||||
                    func_args += ', ' + str(p)
 | 
			
		||||
 | 
			
		||||
            func_list[f"{funcs.__name__}"] = (funcs.func, name, f"{funcs.__name__}({func_args})")
 | 
			
		||||
 | 
			
		||||
        for k, v in fitfunc_dict.items():
 | 
			
		||||
            self.add_namespace(v, parents=(parent, k))
 | 
			
		||||
 | 
			
		||||
    def add_namespace(self, namespace, parents):
 | 
			
		||||
        if parents[0] in self.top_levels:
 | 
			
		||||
            if parents[1] not in self.top_levels[parents[0]]:
 | 
			
		||||
                self.top_levels[parents[0]].append(parents[1])
 | 
			
		||||
        else:
 | 
			
		||||
            self.top_levels[parents[0]] = [parents[1]]
 | 
			
		||||
        if (parents[0], parents[1]) not in self.groupings:
 | 
			
		||||
            self.groupings[(parents[0], parents[1])] = list(namespace.keys())
 | 
			
		||||
        else:
 | 
			
		||||
            self.groupings[(parents[0], parents[1])].extend(list(namespace.keys()))
 | 
			
		||||
 | 
			
		||||
        self.namespace.update(namespace)
 | 
			
		||||
 | 
			
		||||
    def flatten(self):
 | 
			
		||||
        ret_dic = {}
 | 
			
		||||
        graph = namedtuple('graphs', ['s'])
 | 
			
		||||
        sets = namedtuple('sets', ['x', 'y', 'y_err', 'value', 'fit'], defaults=(None,))
 | 
			
		||||
 | 
			
		||||
        gs = re.compile(r'g\[(\d+)].s\[(\d+)].(x|y(?:_err)*|value|fit)')
 | 
			
		||||
 | 
			
		||||
        for k, v in self.namespace.items():
 | 
			
		||||
            m = gs.match(k)
 | 
			
		||||
            if m:
 | 
			
		||||
                if 'g' not in ret_dic:
 | 
			
		||||
                    ret_dic['g'] = {}
 | 
			
		||||
 | 
			
		||||
                g_num, s_num, pos = m.groups()
 | 
			
		||||
                if int(g_num) not in ret_dic['g']:
 | 
			
		||||
                    ret_dic['g'][int(g_num)] = graph({})
 | 
			
		||||
 | 
			
		||||
                gg = ret_dic['g'][int(g_num)]
 | 
			
		||||
                if int(s_num) not in gg.s:
 | 
			
		||||
                    gg.s[int(s_num)] = sets('', '', '', '')
 | 
			
		||||
 | 
			
		||||
                ss = gg.s[int(s_num)]
 | 
			
		||||
                if pos == 'fit':
 | 
			
		||||
                    if ss.fit is None:
 | 
			
		||||
                        gg.s[int(s_num)] = ss._replace(**{pos: {}})
 | 
			
		||||
                        ss = gg.s[int(s_num)]
 | 
			
		||||
                    ss.fit[k[m.end()+2:-2]] = v[0]
 | 
			
		||||
 | 
			
		||||
                else:
 | 
			
		||||
                    ret_dic['g'][int(g_num)].s[int(s_num)] = ss._replace(**{pos: v[0]})
 | 
			
		||||
 | 
			
		||||
            else:
 | 
			
		||||
                ret_dic[k] = v[0]
 | 
			
		||||
 | 
			
		||||
        return ret_dic
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class QNamespaceWidget(QtWidgets.QWidget, Ui_Form):
 | 
			
		||||
    selected = QtCore.pyqtSignal(str)
 | 
			
		||||
    sendKey = QtCore.pyqtSignal(str)
 | 
			
		||||
 | 
			
		||||
    def __init__(self, parent=None):
 | 
			
		||||
        super().__init__(parent=parent)
 | 
			
		||||
 | 
			
		||||
        self.setupUi(self)
 | 
			
		||||
        self.namespace = None
 | 
			
		||||
 | 
			
		||||
    def make_namespace(self):
 | 
			
		||||
        self.set_namespace(Namespace(fitfuncs=True, const=True, basic=True))
 | 
			
		||||
 | 
			
		||||
    @QtCore.pyqtSlot(int, name='on_groups_comboBox_currentIndexChanged')
 | 
			
		||||
    def show_subgroup(self, idx: int):
 | 
			
		||||
        name = self.groups_comboBox.itemText(idx)
 | 
			
		||||
 | 
			
		||||
        self.subgroups_comboBox.blockSignals(True)
 | 
			
		||||
        self.subgroups_comboBox.clear()
 | 
			
		||||
        for item in self.namespace.top_levels[name]:
 | 
			
		||||
            self.subgroups_comboBox.addItem(item)
 | 
			
		||||
        self.subgroups_comboBox.blockSignals(False)
 | 
			
		||||
 | 
			
		||||
        self.show_namespace(0)
 | 
			
		||||
 | 
			
		||||
    @QtCore.pyqtSlot(int, name='on_subgroups_comboBox_currentIndexChanged')
 | 
			
		||||
    def show_namespace(self, idx: int):
 | 
			
		||||
        subspace = self.namespace.groupings[(self.groups_comboBox.currentText(), self.subgroups_comboBox.itemText(idx))]
 | 
			
		||||
 | 
			
		||||
        self.namespace_table.clear()
 | 
			
		||||
        self.namespace_table.setRowCount(0)
 | 
			
		||||
 | 
			
		||||
        for entry in subspace:
 | 
			
		||||
            key_item = QtWidgets.QTableWidgetItem(entry)
 | 
			
		||||
            key_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
 | 
			
		||||
 | 
			
		||||
            vals = self.namespace.namespace[entry]
 | 
			
		||||
 | 
			
		||||
            if len(vals) < 3:
 | 
			
		||||
                alias = entry
 | 
			
		||||
            else:
 | 
			
		||||
                alias = vals[2]
 | 
			
		||||
 | 
			
		||||
            if len(vals) < 2:
 | 
			
		||||
                display = entry
 | 
			
		||||
            else:
 | 
			
		||||
                display = vals[1]
 | 
			
		||||
 | 
			
		||||
            value_item = QtWidgets.QTableWidgetItem(display)
 | 
			
		||||
            value_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
 | 
			
		||||
 | 
			
		||||
            key_item.setData(QtCore.Qt.UserRole, alias)
 | 
			
		||||
            key_item.setData(QtCore.Qt.UserRole+1, entry)
 | 
			
		||||
            value_item.setData(QtCore.Qt.UserRole, alias)
 | 
			
		||||
            value_item.setData(QtCore.Qt.UserRole+1, entry)
 | 
			
		||||
 | 
			
		||||
            row = self.namespace_table.rowCount()
 | 
			
		||||
            self.namespace_table.setRowCount(row+1)
 | 
			
		||||
            self.namespace_table.setItem(row, 0, key_item)
 | 
			
		||||
            self.namespace_table.setItem(row, 1, value_item)
 | 
			
		||||
 | 
			
		||||
        self.namespace_table.resizeColumnsToContents()
 | 
			
		||||
 | 
			
		||||
    def set_namespace(self, namespace):
 | 
			
		||||
        self.namespace = namespace
 | 
			
		||||
 | 
			
		||||
        self.groups_comboBox.blockSignals(True)
 | 
			
		||||
        self.groups_comboBox.clear()
 | 
			
		||||
        for k in self.namespace.top_levels.keys():
 | 
			
		||||
            self.groups_comboBox.addItem(k)
 | 
			
		||||
        self.groups_comboBox.blockSignals(False)
 | 
			
		||||
 | 
			
		||||
        self.show_subgroup(0)
 | 
			
		||||
 | 
			
		||||
    @QtCore.pyqtSlot(QtWidgets.QTableWidgetItem, name='on_namespace_table_itemDoubleClicked')
 | 
			
		||||
    def item_selected(self, item: QtWidgets.QTableWidgetItem):
 | 
			
		||||
        self.selected.emit(item.data(QtCore.Qt.UserRole))
 | 
			
		||||
        self.sendKey.emit(item.data(QtCore.Qt.UserRole+1))
 | 
			
		||||
							
								
								
									
										463
									
								
								src/gui_qt/lib/pg_objects.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										463
									
								
								src/gui_qt/lib/pg_objects.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,463 @@
 | 
			
		||||
import numpy as np
 | 
			
		||||
from pyqtgraph import (
 | 
			
		||||
    InfiniteLine,
 | 
			
		||||
    ErrorBarItem,
 | 
			
		||||
    LinearRegionItem, mkBrush,
 | 
			
		||||
    mkColor, mkPen,
 | 
			
		||||
    PlotDataItem,
 | 
			
		||||
    LegendItem,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
from nmreval.lib.colors import BaseColor, Colors
 | 
			
		||||
from nmreval.lib.lines import LineStyle
 | 
			
		||||
from nmreval.lib.symbols import SymbolStyle
 | 
			
		||||
 | 
			
		||||
from ..Qt import QtCore, QtGui
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
Subclasses of pyqtgraph items, mostly to take care of log-scaling. 
 | 
			
		||||
pyqtgraph looks for function "setLogMode" for logarithmic axes, so needs to be implemented.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LogInfiniteLine(InfiniteLine):
 | 
			
		||||
    def __init__(self, **kwargs):
 | 
			
		||||
        super().__init__(**kwargs)
 | 
			
		||||
        self.logmode = [False, False]
 | 
			
		||||
 | 
			
		||||
    def setLogMode(self, xmode, ymode):
 | 
			
		||||
        """
 | 
			
		||||
        Does only work for vertical and horizontal lines
 | 
			
		||||
        """
 | 
			
		||||
        if self.logmode == [xmode, ymode]:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        new_p = list(self.p[:])
 | 
			
		||||
        if (self.angle == 90) and (self.logmode[0] != xmode):
 | 
			
		||||
            if xmode:
 | 
			
		||||
                new_p[0] = np.log10(new_p[0]+np.finfo(float).eps)
 | 
			
		||||
            else:
 | 
			
		||||
                new_p[0] = 10**new_p[0]
 | 
			
		||||
 | 
			
		||||
        if (self.angle == 0) and (self.logmode[1] != ymode):
 | 
			
		||||
            if ymode:
 | 
			
		||||
                new_p[1] = np.log10(new_p[1]+np.finfo(float).eps)
 | 
			
		||||
            else:
 | 
			
		||||
                new_p[1] = 10**new_p[1]
 | 
			
		||||
 | 
			
		||||
        self.logmode = [xmode, ymode]
 | 
			
		||||
 | 
			
		||||
        if np.all(np.isfinite(new_p)):
 | 
			
		||||
            self.setPos(new_p)
 | 
			
		||||
        else:
 | 
			
		||||
            self.setPos(self.p)
 | 
			
		||||
            self.sigPositionChanged.emit(self)
 | 
			
		||||
 | 
			
		||||
    def setValue(self, v):
 | 
			
		||||
        if isinstance(v, QtCore.QPointF):
 | 
			
		||||
            v = [v.x(), v.y()]
 | 
			
		||||
 | 
			
		||||
        with np.errstate(divide='ignore'):
 | 
			
		||||
            if isinstance(v, (list, tuple)):
 | 
			
		||||
                for i in [0, 1]:
 | 
			
		||||
                    if self.logmode[i]:
 | 
			
		||||
                        v[i] = np.log10(v[i]+np.finfo(float).eps)
 | 
			
		||||
            else:
 | 
			
		||||
                if self.angle == 90:
 | 
			
		||||
                    if self.logmode[0]:
 | 
			
		||||
                        v = [np.log10(v+np.finfo(float).eps), 0]
 | 
			
		||||
                    else:
 | 
			
		||||
                        v = [v, 0]
 | 
			
		||||
                elif self.angle == 0:
 | 
			
		||||
                    if self.logmode[1]:
 | 
			
		||||
                        v = [0, np.log10(v+np.finfo(float).eps)]
 | 
			
		||||
                    else:
 | 
			
		||||
                        v = [0, v]
 | 
			
		||||
                else:
 | 
			
		||||
                    raise ValueError('LogInfiniteLine: Diagonal lines need two values')
 | 
			
		||||
 | 
			
		||||
        self.setPos(v)
 | 
			
		||||
 | 
			
		||||
    def value(self):
 | 
			
		||||
        p = self.getPos()
 | 
			
		||||
        if self.angle == 0:
 | 
			
		||||
            return 10**p[1] if self.logmode[1] else p[1]
 | 
			
		||||
        elif self.angle == 90:
 | 
			
		||||
            return 10**p[0] if self.logmode[0] else p[0]
 | 
			
		||||
        else:
 | 
			
		||||
            if self.logmode[0]:
 | 
			
		||||
                p[0] = 10**p[0]
 | 
			
		||||
            if self.logmode[1]:
 | 
			
		||||
                p[1] = 10**p[1]
 | 
			
		||||
            return p
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ErrorBars(ErrorBarItem):
 | 
			
		||||
    def __init__(self, **opts):
 | 
			
		||||
        self.log = [False, False]
 | 
			
		||||
 | 
			
		||||
        opts['xData'] = opts.get('x', None)
 | 
			
		||||
        opts['yData'] = opts.get('y', None)
 | 
			
		||||
        opts['topData'] = opts.get('top', None)
 | 
			
		||||
        opts['bottomData'] = opts.get('bottom', None)
 | 
			
		||||
 | 
			
		||||
        super().__init__(**opts)
 | 
			
		||||
 | 
			
		||||
    def setLogMode(self, x_mode, y_mode):
 | 
			
		||||
        if self.log == [x_mode, y_mode]:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        self._make_log_scale(x_mode, y_mode)
 | 
			
		||||
 | 
			
		||||
        self.log[0] = x_mode
 | 
			
		||||
        self.log[1] = y_mode
 | 
			
		||||
 | 
			
		||||
        super().setData()
 | 
			
		||||
 | 
			
		||||
    def setData(self, **opts):
 | 
			
		||||
        self.opts.update(opts)
 | 
			
		||||
 | 
			
		||||
        self.opts['xData'] = opts.get('x', self.opts['xData'])
 | 
			
		||||
        self.opts['yData'] = opts.get('y', self.opts['yData'])
 | 
			
		||||
        self.opts['topData'] = opts.get('top', self.opts['topData'])
 | 
			
		||||
        self.opts['bottomData'] = opts.get('bottom', self.opts['bottomData'])
 | 
			
		||||
 | 
			
		||||
        if any(self.log):
 | 
			
		||||
            self._make_log_scale(*self.log)
 | 
			
		||||
 | 
			
		||||
        super().setData()
 | 
			
		||||
 | 
			
		||||
    def _make_log_scale(self, x_mode, y_mode):
 | 
			
		||||
        _x = self.opts['xData']
 | 
			
		||||
        _xmask = np.logical_not(np.isnan(_x))
 | 
			
		||||
 | 
			
		||||
        if x_mode:
 | 
			
		||||
            with np.errstate(all='ignore'):
 | 
			
		||||
                _x = np.log10(_x)
 | 
			
		||||
                _xmask = np.logical_not(np.isnan(_x))
 | 
			
		||||
 | 
			
		||||
        _y = self.opts['yData']
 | 
			
		||||
        _ymask = np.ones(_y.size, dtype=bool)
 | 
			
		||||
        _top = self.opts['topData']
 | 
			
		||||
        _bottom = self.opts['bottomData']
 | 
			
		||||
 | 
			
		||||
        if y_mode:
 | 
			
		||||
            with np.errstate(all='ignore'):
 | 
			
		||||
                logtop = np.log10(self.opts['topData']+_y)
 | 
			
		||||
                logbottom = np.log10(_y-self.opts['bottomData'])
 | 
			
		||||
 | 
			
		||||
                _y = np.log10(_y)
 | 
			
		||||
                _ymask = np.logical_not(np.isnan(_y))
 | 
			
		||||
 | 
			
		||||
                logbottom[logbottom == -np.inf] = _y[logbottom == -np.inf]
 | 
			
		||||
                _bottom = np.nan_to_num(np.maximum(_y-logbottom, 0))
 | 
			
		||||
                logtop[logtop == -np.inf] = _y[logtop == -np.inf]
 | 
			
		||||
                _top = np.nan_to_num(np.maximum(logtop-_y, 0))
 | 
			
		||||
 | 
			
		||||
        _mask = np.logical_and(_xmask, _ymask)
 | 
			
		||||
 | 
			
		||||
        self.opts['x'] = _x[_mask]
 | 
			
		||||
        self.opts['y'] = _y[_mask]
 | 
			
		||||
        self.opts['top'] = _top[_mask]
 | 
			
		||||
        self.opts['bottom'] = _bottom[_mask]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PlotItem(PlotDataItem):
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
        self.opts['linecolor'] = (0, 0, 0)
 | 
			
		||||
        self.opts['symbolcolor'] = (0, 0, 0)
 | 
			
		||||
 | 
			
		||||
        if self.opts['pen'] is not None:
 | 
			
		||||
            pen = self.opts['pen']
 | 
			
		||||
            if isinstance(pen, tuple):
 | 
			
		||||
                self.opts['linecolor'] = pen
 | 
			
		||||
            else:
 | 
			
		||||
                c = pen.color()
 | 
			
		||||
                self.opts['linecolor'] = c.red(), c.green(), c.blue()
 | 
			
		||||
 | 
			
		||||
        if self.symbol != SymbolStyle.No:
 | 
			
		||||
            c = self.opts['symbolBrush'].color()
 | 
			
		||||
            self.opts['symbolcolor'] = c.red(), c.green(), c.blue()
 | 
			
		||||
 | 
			
		||||
    def __getitem__(self, item):
 | 
			
		||||
        return self.opts.get(item, None)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def symbol(self):
 | 
			
		||||
        return SymbolStyle.from_str(self.opts['symbol'])
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def symbolcolor(self):
 | 
			
		||||
        sc = self.opts['symbolcolor']
 | 
			
		||||
        if isinstance(sc, tuple):
 | 
			
		||||
            return Colors(sc)
 | 
			
		||||
        elif isinstance(sc, str):
 | 
			
		||||
            return Colors.from_str(sc)
 | 
			
		||||
        else:
 | 
			
		||||
            return sc
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def symbolsize(self):
 | 
			
		||||
        return self.opts['symbolSize']
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def linestyle(self) -> LineStyle:
 | 
			
		||||
        pen = self.opts['pen']
 | 
			
		||||
        if pen is None:
 | 
			
		||||
            return LineStyle.No
 | 
			
		||||
        else:
 | 
			
		||||
            return LineStyle(pen.style())
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def linewidth(self) -> float:
 | 
			
		||||
        pen = self.opts['pen']
 | 
			
		||||
        if pen is None:
 | 
			
		||||
            return 1.
 | 
			
		||||
        else:
 | 
			
		||||
            return pen.widthF()
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def linecolor(self) -> Colors:
 | 
			
		||||
        lc = self.opts['linecolor']
 | 
			
		||||
        if isinstance(lc, tuple):
 | 
			
		||||
            return Colors(lc)
 | 
			
		||||
        elif isinstance(lc, str):
 | 
			
		||||
            return Colors.from_str(lc)
 | 
			
		||||
        else:
 | 
			
		||||
            return lc
 | 
			
		||||
 | 
			
		||||
    def updateItems(self):
 | 
			
		||||
        """
 | 
			
		||||
        We override this function so that curves with nan/inf values can be displayed.
 | 
			
		||||
        Newer versions close this bug differently (https://github.com/pyqtgraph/pyqtgraph/pull/1058)
 | 
			
		||||
        but this works somewhat.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        curveArgs = {}
 | 
			
		||||
        for k, v in [('pen', 'pen'), ('shadowPen', 'shadowPen'), ('fillLevel', 'fillLevel'),
 | 
			
		||||
                     ('fillOutline', 'fillOutline'), ('fillBrush', 'brush'), ('antialias', 'antialias'),
 | 
			
		||||
                     ('connect', 'connect'), ('stepMode', 'stepMode')]:
 | 
			
		||||
            curveArgs[v] = self.opts[k]
 | 
			
		||||
 | 
			
		||||
        scatterArgs = {}
 | 
			
		||||
        for k, v in [('symbolPen', 'pen'), ('symbolBrush', 'brush'), ('symbol', 'symbol'), ('symbolSize', 'size'),
 | 
			
		||||
                     ('data', 'data'), ('pxMode', 'pxMode'), ('antialias', 'antialias')]:
 | 
			
		||||
            if k in self.opts:
 | 
			
		||||
                scatterArgs[v] = self.opts[k]
 | 
			
		||||
 | 
			
		||||
        x, y = self.getData()
 | 
			
		||||
        if x is None:
 | 
			
		||||
            x = []
 | 
			
		||||
        if y is None:
 | 
			
		||||
            y = []
 | 
			
		||||
 | 
			
		||||
        if curveArgs['pen'] is not None or (curveArgs['brush'] is not None and curveArgs['fillLevel'] is not None):
 | 
			
		||||
            is_finite = np.isfinite(x) & np.isfinite(y)
 | 
			
		||||
            all_finite = np.all(is_finite)
 | 
			
		||||
            if not all_finite:
 | 
			
		||||
                # remove all bad values
 | 
			
		||||
                x = x[is_finite]
 | 
			
		||||
                y = y[is_finite]
 | 
			
		||||
            self.curve.setData(x=x, y=y, **curveArgs)
 | 
			
		||||
            self.curve.show()
 | 
			
		||||
        else:
 | 
			
		||||
            self.curve.hide()
 | 
			
		||||
 | 
			
		||||
        if scatterArgs['symbol'] is not None:
 | 
			
		||||
            if self.opts.get('stepMode', False) is True:
 | 
			
		||||
                x = 0.5 * (x[:-1] + x[1:])
 | 
			
		||||
            self.scatter.setData(x=x, y=y, **scatterArgs)
 | 
			
		||||
            self.scatter.show()
 | 
			
		||||
        else:
 | 
			
		||||
            self.scatter.hide()
 | 
			
		||||
 | 
			
		||||
    def set_symbol(self, symbol=None, size=None, color=None):
 | 
			
		||||
        print(symbol, type(symbol), isinstance(symbol, SymbolStyle))
 | 
			
		||||
        if symbol is not None:
 | 
			
		||||
            if isinstance(symbol, int):
 | 
			
		||||
                self.setSymbol(SymbolStyle(symbol).to_str())
 | 
			
		||||
            elif isinstance(symbol, SymbolStyle):
 | 
			
		||||
                self.setSymbol(symbol.to_str())
 | 
			
		||||
            else:
 | 
			
		||||
                self.setSymbol(symbol)
 | 
			
		||||
 | 
			
		||||
        if color is not None:
 | 
			
		||||
            self.set_color(color, symbol=True)
 | 
			
		||||
 | 
			
		||||
        if size is not None:
 | 
			
		||||
            self.setSymbolSize(size)
 | 
			
		||||
 | 
			
		||||
    def set_color(self, color, symbol=False, line=False):
 | 
			
		||||
        if isinstance(color, BaseColor):
 | 
			
		||||
            color = color.rgb()
 | 
			
		||||
        elif isinstance(color, QtGui.QColor):
 | 
			
		||||
            color = color.getRgb()[:3]
 | 
			
		||||
 | 
			
		||||
        if symbol:
 | 
			
		||||
            self.setSymbolBrush(mkBrush(color))
 | 
			
		||||
            self.setSymbolPen(mkPen(color=color))
 | 
			
		||||
            self.opts['symbolcolor'] = color
 | 
			
		||||
 | 
			
		||||
        if line:
 | 
			
		||||
            pen = self.opts['pen']
 | 
			
		||||
            self.opts['linecolor'] = color
 | 
			
		||||
            if pen is not None:
 | 
			
		||||
                pen.setColor(mkColor(color))
 | 
			
		||||
            self.opts['pen'] = pen
 | 
			
		||||
            self.updateItems()
 | 
			
		||||
 | 
			
		||||
    def set_line(self, style=None, width=None, color=None):
 | 
			
		||||
        pen = self.opts['pen']
 | 
			
		||||
        if pen is None:
 | 
			
		||||
            pen = mkPen(style=QtCore.Qt.NoPen)
 | 
			
		||||
 | 
			
		||||
        if width is not None:
 | 
			
		||||
            pen.setWidthF(width)
 | 
			
		||||
 | 
			
		||||
        if style is not None:
 | 
			
		||||
            if isinstance(style, LineStyle):
 | 
			
		||||
                style = style.value
 | 
			
		||||
 | 
			
		||||
            pen.setStyle(style)
 | 
			
		||||
 | 
			
		||||
        self.opts['pen'] = pen
 | 
			
		||||
        self.updateItems()
 | 
			
		||||
 | 
			
		||||
        if color is not None:
 | 
			
		||||
            self.set_color(color, symbol=False, line=True)
 | 
			
		||||
 | 
			
		||||
    def get_data_opts(self) -> dict:
 | 
			
		||||
        x, y = self.xData, self.yData
 | 
			
		||||
        if (x is None) or (len(x) == 0):
 | 
			
		||||
            return {}
 | 
			
		||||
 | 
			
		||||
        opts = self.opts
 | 
			
		||||
        item_dic = {
 | 
			
		||||
            'x': x, 'y': y,
 | 
			
		||||
            'name': opts['name'],
 | 
			
		||||
            'symbolsize': opts['symbolSize']
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if opts['symbol'] is None:
 | 
			
		||||
            item_dic['symbol'] = SymbolStyle.No
 | 
			
		||||
            item_dic['symbolcolor'] = None
 | 
			
		||||
        else:
 | 
			
		||||
            item_dic['symbol'] = SymbolStyle.from_str(opts['symbol'])
 | 
			
		||||
            item_dic['symbolcolor'] = opts['symbolcolor']
 | 
			
		||||
 | 
			
		||||
        pen = opts['pen']
 | 
			
		||||
        if pen is not None:
 | 
			
		||||
            item_dic['linestyle'] = LineStyle(pen.style())
 | 
			
		||||
            item_dic['linecolor'] = opts['linecolor']
 | 
			
		||||
            item_dic['linewidth'] = pen.widthF()
 | 
			
		||||
        else:
 | 
			
		||||
            item_dic['linestyle'] = LineStyle.No
 | 
			
		||||
            item_dic['linecolor'] = None
 | 
			
		||||
            item_dic['linewidth'] = 0.0
 | 
			
		||||
 | 
			
		||||
        if item_dic['linecolor'] is None and item_dic['symbolcolor'] is None:
 | 
			
		||||
            item_dic['symbolcolor'] = Colors.Black.rgb()
 | 
			
		||||
        elif item_dic['linecolor'] is None:
 | 
			
		||||
            item_dic['linecolor'] = item_dic['symbolcolor']
 | 
			
		||||
        elif item_dic['symbolcolor'] is None:
 | 
			
		||||
            item_dic['symbolcolor'] = item_dic['linecolor']
 | 
			
		||||
 | 
			
		||||
        return item_dic
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RegionItem(LinearRegionItem):
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        self.mode = kwargs.pop('mode', 'half')
 | 
			
		||||
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
        self.logmode = False
 | 
			
		||||
        self.first = True
 | 
			
		||||
 | 
			
		||||
    def setLogMode(self, xmode, _):
 | 
			
		||||
        if self.logmode == xmode:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        if xmode:
 | 
			
		||||
            new_region = [np.log10(self.lines[0].value()), np.log10(self.lines[1].value())]
 | 
			
		||||
 | 
			
		||||
            if np.isnan(new_region[1]):
 | 
			
		||||
                new_region[1] = self.lines[1].value()
 | 
			
		||||
 | 
			
		||||
            if np.isnan(new_region[0]):
 | 
			
		||||
                new_region[0] = new_region[1]/10.
 | 
			
		||||
 | 
			
		||||
        else:
 | 
			
		||||
            new_region = [10**self.lines[0].value(), 10**self.lines[1].value()]
 | 
			
		||||
 | 
			
		||||
        self.logmode = xmode
 | 
			
		||||
        self.setRegion(new_region)
 | 
			
		||||
 | 
			
		||||
    def dataBounds(self, axis, frac=1.0, orthoRange=None):
 | 
			
		||||
        if axis == self._orientation_axis[self.orientation]:
 | 
			
		||||
            r = self.getRegion()
 | 
			
		||||
            if self.logmode:
 | 
			
		||||
                r = np.log10(r[0]), np.log10(r[1])
 | 
			
		||||
            return r
 | 
			
		||||
        else:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
    def getRegion(self):
 | 
			
		||||
        region = super().getRegion()
 | 
			
		||||
        if self.logmode:
 | 
			
		||||
            return 10**region[0], 10**region[1]
 | 
			
		||||
        else:
 | 
			
		||||
            return region
 | 
			
		||||
 | 
			
		||||
    def boundingRect(self):
 | 
			
		||||
        # overwrite to draw correct rect in logmode
 | 
			
		||||
 | 
			
		||||
        br = self.viewRect()  # bounds of containing ViewBox mapped to local coords.
 | 
			
		||||
 | 
			
		||||
        rng = self.getRegion()
 | 
			
		||||
        if self.logmode:
 | 
			
		||||
            rng = np.log10(rng[0]), np.log10(rng[1])
 | 
			
		||||
 | 
			
		||||
        if self.orientation in ('vertical', LinearRegionItem.Vertical):
 | 
			
		||||
            br.setLeft(rng[0])
 | 
			
		||||
            br.setRight(rng[1])
 | 
			
		||||
            length = br.height()
 | 
			
		||||
            br.setBottom(br.top() + length * self.span[1])
 | 
			
		||||
            br.setTop(br.top() + length * self.span[0])
 | 
			
		||||
        else:
 | 
			
		||||
            br.setTop(rng[0])
 | 
			
		||||
            br.setBottom(rng[1])
 | 
			
		||||
            length = br.width()
 | 
			
		||||
            br.setRight(br.left() + length * self.span[1])
 | 
			
		||||
            br.setLeft(br.left() + length * self.span[0])
 | 
			
		||||
 | 
			
		||||
        br = br.normalized()
 | 
			
		||||
 | 
			
		||||
        if self._bounds != br:
 | 
			
		||||
            self._bounds = br
 | 
			
		||||
            self.prepareGeometryChange()
 | 
			
		||||
 | 
			
		||||
        return br
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LegendItemBlock(LegendItem):
 | 
			
		||||
    """
 | 
			
		||||
    Simple subclass that stops dragging legend outside of view
 | 
			
		||||
    """
 | 
			
		||||
    def __init__(self, **kwargs):
 | 
			
		||||
        super().__init__(**kwargs)
 | 
			
		||||
        self.layout.setContentsMargins(1, 1, 1, 1)
 | 
			
		||||
 | 
			
		||||
    def mouseDragEvent(self, ev):
 | 
			
		||||
        if ev.button() == QtCore.Qt.LeftButton:
 | 
			
		||||
            ev.accept()
 | 
			
		||||
            dpos = ev.pos() - ev.lastPos()
 | 
			
		||||
            vb_rect = self.parentItem().rect()
 | 
			
		||||
            pos = self.pos()
 | 
			
		||||
            # upper left corner and a point a little more to the bottom right must be inside
 | 
			
		||||
            if vb_rect.contains(pos+dpos) and vb_rect.contains(pos+dpos+QtCore.QPointF(20., 20.)):
 | 
			
		||||
                self.autoAnchor(pos + dpos)
 | 
			
		||||
            else:
 | 
			
		||||
                self.autoAnchor(pos)
 | 
			
		||||
							
								
								
									
										110
									
								
								src/gui_qt/lib/randpok.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								src/gui_qt/lib/randpok.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,110 @@
 | 
			
		||||
import os.path
 | 
			
		||||
import json
 | 
			
		||||
import urllib.request
 | 
			
		||||
import webbrowser
 | 
			
		||||
import random
 | 
			
		||||
 | 
			
		||||
from ..Qt import QtGui, QtCore, QtWidgets
 | 
			
		||||
from .._py.pokemon import Ui_Dialog
 | 
			
		||||
 | 
			
		||||
random.seed()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class QPokemon(QtWidgets.QDialog, Ui_Dialog):
 | 
			
		||||
    def __init__(self, number=None, parent=None):
 | 
			
		||||
        super().__init__(parent=parent)
 | 
			
		||||
        self.setupUi(self)
 | 
			
		||||
        self._js = json.load(open(os.path.join(path_to_module, 'utils', 'pokemon.json'), 'r'), encoding='UTF-8')
 | 
			
		||||
        self._id = 0
 | 
			
		||||
 | 
			
		||||
        if number is not None and number in range(1, len(self._js)+1):
 | 
			
		||||
            poke_nr = f'{number:03d}'
 | 
			
		||||
            self._id = number
 | 
			
		||||
        else:
 | 
			
		||||
            poke_nr = f'{random.randint(1, len(self._js)):03d}'
 | 
			
		||||
            self._id = int(poke_nr)
 | 
			
		||||
 | 
			
		||||
        self._pokemon = None
 | 
			
		||||
        self.show_pokemon(poke_nr)
 | 
			
		||||
        self.label_15.linkActivated.connect(lambda x: webbrowser.open(x))
 | 
			
		||||
 | 
			
		||||
        self.buttonBox.clicked.connect(self.randomize)
 | 
			
		||||
        self.next_button.clicked.connect(self.show_next)
 | 
			
		||||
        self.prev_button.clicked.connect(self.show_prev)
 | 
			
		||||
 | 
			
		||||
    def show_pokemon(self, nr):
 | 
			
		||||
        self._pokemon = self._js[nr]
 | 
			
		||||
        self.setWindowTitle('Pokémon: ' + self._pokemon['Deutsch'])
 | 
			
		||||
 | 
			
		||||
        for i in range(self.tabWidget.count(), -1, -1):
 | 
			
		||||
            print('i', self.tabWidget.count(), i)
 | 
			
		||||
            try:
 | 
			
		||||
                self.tabWidget.widget(i).deleteLater()
 | 
			
		||||
            except AttributeError:
 | 
			
		||||
                pass
 | 
			
		||||
 | 
			
		||||
        for n, img in self._pokemon['Bilder']:
 | 
			
		||||
            w = QtWidgets.QWidget()
 | 
			
		||||
            vl = QtWidgets.QVBoxLayout()
 | 
			
		||||
            l = QtWidgets.QLabel(self)
 | 
			
		||||
            l.setAlignment(QtCore.Qt.AlignHCenter)
 | 
			
		||||
            pixmap = QtGui.QPixmap()
 | 
			
		||||
 | 
			
		||||
            try:
 | 
			
		||||
                pixmap.loadFromData(urllib.request.urlopen(img, timeout=0.5).read())
 | 
			
		||||
            except IOError:
 | 
			
		||||
                l.setText(n)
 | 
			
		||||
            else:
 | 
			
		||||
                sc_pixmap = pixmap.scaled(256, 256, QtCore.Qt.KeepAspectRatio)
 | 
			
		||||
                l.setPixmap(sc_pixmap)
 | 
			
		||||
 | 
			
		||||
            vl.addWidget(l)
 | 
			
		||||
            w.setLayout(vl)
 | 
			
		||||
            self.tabWidget.addTab(w, n)
 | 
			
		||||
 | 
			
		||||
        if len(self._pokemon['Bilder']) <= 1:
 | 
			
		||||
            self.tabWidget.tabBar().setVisible(False)
 | 
			
		||||
        else:
 | 
			
		||||
            self.tabWidget.tabBar().setVisible(True)
 | 
			
		||||
        self.tabWidget.adjustSize()
 | 
			
		||||
 | 
			
		||||
        self.name.clear()
 | 
			
		||||
        keys = ['National-Dex', 'Kategorie', 'Typ', 'Größe', 'Gewicht', 'Farbe', 'Link']
 | 
			
		||||
        label_list = [self.pokedex_nr, self.category, self.poketype, self.weight, self.height, self.color, self.info]
 | 
			
		||||
        for (k, label) in zip(keys, label_list):
 | 
			
		||||
            v = self._pokemon[k]
 | 
			
		||||
            if isinstance(v, list):
 | 
			
		||||
                v = os.path.join('', *v)
 | 
			
		||||
 | 
			
		||||
            if k == 'Link':
 | 
			
		||||
                v = '<a href={}>{}</a>'.format(v, v)
 | 
			
		||||
 | 
			
		||||
            label.setText(v)
 | 
			
		||||
 | 
			
		||||
        for k in ['Deutsch', 'Japanisch', 'Englisch', 'Französisch']:
 | 
			
		||||
            v = self._pokemon[k]
 | 
			
		||||
            self.name.addItem(k + ': ' + v)
 | 
			
		||||
 | 
			
		||||
        self.adjustSize()
 | 
			
		||||
 | 
			
		||||
    def randomize(self, idd):
 | 
			
		||||
        if idd.text() == 'Retry':
 | 
			
		||||
            new_number = f'{random.randint(1, len(self._js)):03d}'
 | 
			
		||||
            self._id = int(new_number)
 | 
			
		||||
            self.show_pokemon(new_number)
 | 
			
		||||
        else:
 | 
			
		||||
            self.close()
 | 
			
		||||
 | 
			
		||||
    def show_next(self):
 | 
			
		||||
        new_number = self._id + 1
 | 
			
		||||
        if new_number > len(self._js):
 | 
			
		||||
            new_number = 1
 | 
			
		||||
        self._id = new_number
 | 
			
		||||
        self.show_pokemon(f'{new_number:03d}')
 | 
			
		||||
 | 
			
		||||
    def show_prev(self):
 | 
			
		||||
        new_number = self._id - 1
 | 
			
		||||
        if new_number == 0:
 | 
			
		||||
            new_number = len(self._js)
 | 
			
		||||
        self._id = new_number
 | 
			
		||||
        self.show_pokemon(f'{new_number:03d}')
 | 
			
		||||
							
								
								
									
										45
									
								
								src/gui_qt/lib/spinboxes.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								src/gui_qt/lib/spinboxes.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
			
		||||
from math import inf
 | 
			
		||||
 | 
			
		||||
from ..Qt import QtWidgets, QtGui
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SciSpinBox(QtWidgets.QDoubleSpinBox):
 | 
			
		||||
    def __init__(self, parent=None):
 | 
			
		||||
        super().__init__(parent=parent)
 | 
			
		||||
 | 
			
		||||
        self.validator = QtGui.QDoubleValidator(self)
 | 
			
		||||
 | 
			
		||||
        self.setMinimum(-inf)
 | 
			
		||||
        self.setMaximum(inf)
 | 
			
		||||
        self.setDecimals(1000)
 | 
			
		||||
        self.precision = 0.001
 | 
			
		||||
 | 
			
		||||
        self._prev_value = float(self.lineEdit().text())
 | 
			
		||||
 | 
			
		||||
    def valueFromText(self, text: str) -> float:
 | 
			
		||||
        try:
 | 
			
		||||
            self._prev_value = float(self.cleanText())
 | 
			
		||||
        except ValueError:
 | 
			
		||||
            pass
 | 
			
		||||
        return self._prev_value
 | 
			
		||||
 | 
			
		||||
    def textFromValue(self, value: float) -> str:
 | 
			
		||||
        if value == 0:
 | 
			
		||||
            return '0'
 | 
			
		||||
        else:
 | 
			
		||||
            return f'{value:.3e}'
 | 
			
		||||
 | 
			
		||||
    def stepBy(self, step: int):
 | 
			
		||||
        self._prev_value = self.value()
 | 
			
		||||
 | 
			
		||||
        new_value = self._prev_value
 | 
			
		||||
        if new_value != 0.0:
 | 
			
		||||
            new_value *= 10**(step/19.)
 | 
			
		||||
        else:
 | 
			
		||||
            new_value = 0.001
 | 
			
		||||
 | 
			
		||||
        self.setValue(new_value)
 | 
			
		||||
        self.lineEdit().setText(f'{new_value:.3e}')
 | 
			
		||||
 | 
			
		||||
    def validate(self, text, pos):
 | 
			
		||||
        return self.validator.validate(text, pos)
 | 
			
		||||
							
								
								
									
										777
									
								
								src/gui_qt/lib/stuff.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										777
									
								
								src/gui_qt/lib/stuff.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,777 @@
 | 
			
		||||
import random
 | 
			
		||||
import time
 | 
			
		||||
 | 
			
		||||
import numpy as np
 | 
			
		||||
 | 
			
		||||
from ..Qt import QtWidgets, QtCore, QtGui
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
__all__ = ['Game']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Game(QtWidgets.QDialog):
 | 
			
		||||
    def __init__(self, mode, parent=None):
 | 
			
		||||
        super().__init__(parent=parent)
 | 
			
		||||
 | 
			
		||||
        layout = QtWidgets.QVBoxLayout()
 | 
			
		||||
        layout.setContentsMargins(3, 3, 3, 3)
 | 
			
		||||
 | 
			
		||||
        self.label = QtWidgets.QLabel(self)
 | 
			
		||||
        layout.addWidget(self.label)
 | 
			
		||||
 | 
			
		||||
        self.startbutton = QtWidgets.QPushButton('Start', self)
 | 
			
		||||
        self.startbutton.clicked.connect(self.start)
 | 
			
		||||
        layout.addWidget(self.startbutton)
 | 
			
		||||
 | 
			
		||||
        if mode == 'tetris':
 | 
			
		||||
            self._setup_tetris()
 | 
			
		||||
        else:
 | 
			
		||||
            self._setup_snake()
 | 
			
		||||
 | 
			
		||||
        layout.addWidget(self.board)
 | 
			
		||||
        self.setStyleSheet("""
 | 
			
		||||
        Board {
 | 
			
		||||
            border: 5px solid black;
 | 
			
		||||
        }
 | 
			
		||||
        QPushButton {
 | 
			
		||||
            font-weight: bold;
 | 
			
		||||
        }
 | 
			
		||||
        """)
 | 
			
		||||
 | 
			
		||||
        self.setLayout(layout)
 | 
			
		||||
 | 
			
		||||
        self.board.new_status.connect(self.new_message)
 | 
			
		||||
 | 
			
		||||
    def _setup_tetris(self):
 | 
			
		||||
        self.board = TetrisBoard(self)
 | 
			
		||||
        self.setGeometry(200, 100, 276, 546+self.startbutton.height()+self.label.height())
 | 
			
		||||
        self.setWindowTitle('Totally not Tetris')
 | 
			
		||||
 | 
			
		||||
    def _setup_snake(self):
 | 
			
		||||
        self.board = SnakeBoard(self)
 | 
			
		||||
        self.setGeometry(200, 100, 406, 406+self.startbutton.height()+self.label.height())
 | 
			
		||||
        self.setWindowTitle('Snakey')
 | 
			
		||||
 | 
			
		||||
    def start(self):
 | 
			
		||||
 | 
			
		||||
        self.board.start()
 | 
			
		||||
 | 
			
		||||
    @QtCore.pyqtSlot(str)
 | 
			
		||||
    def new_message(self, msg: str):
 | 
			
		||||
        self.label.setText(msg)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Board(QtWidgets.QFrame):
 | 
			
		||||
    new_status = QtCore.pyqtSignal(str)
 | 
			
		||||
 | 
			
		||||
    SPEED = 1000
 | 
			
		||||
    WIDTH = 10
 | 
			
		||||
    HEIGHT = 10
 | 
			
		||||
 | 
			
		||||
    def __init__(self, parent=None):
 | 
			
		||||
        super().__init__(parent=parent)
 | 
			
		||||
 | 
			
		||||
        self.timer = QtCore.QTimer(self)
 | 
			
		||||
        self.timer.timeout.connect(self.next_move)
 | 
			
		||||
 | 
			
		||||
        self.score = 0
 | 
			
		||||
        self._speed = self.SPEED
 | 
			
		||||
        self._ispaused = False
 | 
			
		||||
        self._isdead = True
 | 
			
		||||
 | 
			
		||||
        self.setFrameStyle(QtWidgets.QFrame.Box | QtWidgets.QFrame.Raised)
 | 
			
		||||
        self.setLineWidth(3)
 | 
			
		||||
 | 
			
		||||
        self.setFocusPolicy(QtCore.Qt.StrongFocus)
 | 
			
		||||
        self.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
 | 
			
		||||
 | 
			
		||||
    def _init_game(self):
 | 
			
		||||
        raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def cellwidth(self):
 | 
			
		||||
        return int(self.contentsRect().width() // self.WIDTH)
 | 
			
		||||
 | 
			
		||||
    # square height
 | 
			
		||||
    @property
 | 
			
		||||
    def cellheight(self):
 | 
			
		||||
        return int(self.contentsRect().height() // self.HEIGHT)
 | 
			
		||||
 | 
			
		||||
    def start(self):
 | 
			
		||||
        if self._isdead:
 | 
			
		||||
            self._init_game()
 | 
			
		||||
            self.new_status.emit(f'Score: {self.score}')
 | 
			
		||||
            self.timer.start(self._speed)
 | 
			
		||||
            self._isdead = False
 | 
			
		||||
            self.setFocus()
 | 
			
		||||
 | 
			
		||||
    def stop(self, msg):
 | 
			
		||||
        self.new_status.emit(f'Score {self.score} // ' + msg)
 | 
			
		||||
        self._isdead = True
 | 
			
		||||
        self.timer.stop()
 | 
			
		||||
 | 
			
		||||
    def pause(self):
 | 
			
		||||
        if self._ispaused and not self._isdead:
 | 
			
		||||
            self.new_status.emit(f'Score {self.score}')
 | 
			
		||||
            self.timer.start(self._speed)
 | 
			
		||||
        else:
 | 
			
		||||
            self.new_status.emit(f'Score {self.score} // Paused')
 | 
			
		||||
            self.timer.stop()
 | 
			
		||||
 | 
			
		||||
        self._ispaused = not self._ispaused
 | 
			
		||||
 | 
			
		||||
    def next_move(self):
 | 
			
		||||
        raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
    def draw_square(self, painter, x, y, color):
 | 
			
		||||
        color = QtGui.QColor(color)
 | 
			
		||||
        painter.fillRect(x+1, y+1, self.cellwidth-2, self.cellheight-2, color)
 | 
			
		||||
 | 
			
		||||
    def draw_circle(self, painter, x, y, color):
 | 
			
		||||
        painter.save()
 | 
			
		||||
 | 
			
		||||
        color = QtGui.QColor(color)
 | 
			
		||||
        painter.setPen(QtGui.QPen(color))
 | 
			
		||||
        painter.setBrush(QtGui.QBrush(color))
 | 
			
		||||
        painter.drawEllipse(x+1, y+1, self.cellwidth, self.cellheight)
 | 
			
		||||
 | 
			
		||||
        painter.restore()
 | 
			
		||||
 | 
			
		||||
    def keyPressEvent(self, evt):
 | 
			
		||||
        if evt.key() == QtCore.Qt.Key_P:
 | 
			
		||||
            self.pause()
 | 
			
		||||
        else:
 | 
			
		||||
            super().keyPressEvent(evt)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SnakeBoard(Board):
 | 
			
		||||
    SPEED = 125
 | 
			
		||||
    WIDTH = 35
 | 
			
		||||
    HEIGHT = 35
 | 
			
		||||
 | 
			
		||||
    def __init__(self, parent=None):
 | 
			
		||||
        super().__init__(parent=parent)
 | 
			
		||||
 | 
			
		||||
        self.snake = [[int(SnakeBoard.WIDTH//2), int(SnakeBoard.HEIGHT//2)],
 | 
			
		||||
                      [int(SnakeBoard.WIDTH//2)+1, int(SnakeBoard.HEIGHT//2)]]
 | 
			
		||||
        self.current_x_head, self.current_y_head = self.snake[0]
 | 
			
		||||
        self.direction = 'l'
 | 
			
		||||
        self.next_direction = []
 | 
			
		||||
 | 
			
		||||
        self._direction_pairs = {'l': 'r', 'u': 'd', 'r': 'l', 'd': 'u'}
 | 
			
		||||
 | 
			
		||||
        self.food = None
 | 
			
		||||
        self.grow_snake = False
 | 
			
		||||
 | 
			
		||||
    def _init_game(self):
 | 
			
		||||
        self.snake = [[int(SnakeBoard.WIDTH//2), int(SnakeBoard.HEIGHT//2)],
 | 
			
		||||
                      [int(SnakeBoard.WIDTH//2)+1, int(SnakeBoard.HEIGHT//2)]]
 | 
			
		||||
        self.current_x_head, self.current_y_head = self.snake[0]
 | 
			
		||||
        self.direction = 'l'
 | 
			
		||||
 | 
			
		||||
        self.food = None
 | 
			
		||||
        self.grow_snake = False
 | 
			
		||||
        self.new_food()
 | 
			
		||||
 | 
			
		||||
    def next_move(self):
 | 
			
		||||
        self.snake_move()
 | 
			
		||||
        self.got_food()
 | 
			
		||||
        self.check_death()
 | 
			
		||||
        self.update()
 | 
			
		||||
 | 
			
		||||
    def snake_move(self):
 | 
			
		||||
        if self.next_direction:
 | 
			
		||||
            turn = self.next_direction.pop()
 | 
			
		||||
            if self.direction != self._direction_pairs[turn]:
 | 
			
		||||
                self.direction = turn
 | 
			
		||||
 | 
			
		||||
        if self.direction == 'l':
 | 
			
		||||
            self.current_x_head -= 1
 | 
			
		||||
        elif self.direction == 'r':
 | 
			
		||||
            self.current_x_head += 1
 | 
			
		||||
 | 
			
		||||
        # y increases top to bottom
 | 
			
		||||
        elif self.direction == 'u':
 | 
			
		||||
            self.current_y_head -= 1
 | 
			
		||||
        elif self.direction == 'd':
 | 
			
		||||
            self.current_y_head += 1
 | 
			
		||||
 | 
			
		||||
        head = (self.current_x_head, self.current_y_head)
 | 
			
		||||
        self.snake.insert(0, head)
 | 
			
		||||
 | 
			
		||||
        if not self.grow_snake:
 | 
			
		||||
            self.snake.pop()
 | 
			
		||||
        else:
 | 
			
		||||
            self.new_status.emit(f'Score: {self.score}')
 | 
			
		||||
            self.grow_snake = False
 | 
			
		||||
 | 
			
		||||
    def got_food(self):
 | 
			
		||||
        head = self.snake[0]
 | 
			
		||||
        if self.food == head:
 | 
			
		||||
            self.new_food()
 | 
			
		||||
            self.grow_snake = True
 | 
			
		||||
            self.score += 1
 | 
			
		||||
 | 
			
		||||
    def new_food(self):
 | 
			
		||||
        x = random.randint(3, SnakeBoard.WIDTH-3)
 | 
			
		||||
        y = random.randint(3, SnakeBoard.HEIGHT-3)
 | 
			
		||||
 | 
			
		||||
        while (x, y) in self.snake:
 | 
			
		||||
            x = random.randint(3, SnakeBoard.WIDTH-3)
 | 
			
		||||
            y = random.randint(3, SnakeBoard.HEIGHT-3)
 | 
			
		||||
 | 
			
		||||
        self.food = (x, y)
 | 
			
		||||
 | 
			
		||||
    def check_death(self):
 | 
			
		||||
        rip_message = ''
 | 
			
		||||
        is_dead = False
 | 
			
		||||
        if (self.current_x_head < 1) or (self.current_x_head > SnakeBoard.WIDTH-2) or \
 | 
			
		||||
                (self.current_y_head < 1) or (self.current_y_head > SnakeBoard.HEIGHT-2):
 | 
			
		||||
            rip_message = 'Snake found wall :('
 | 
			
		||||
            is_dead = True
 | 
			
		||||
 | 
			
		||||
        head = self.snake[0]
 | 
			
		||||
        for snake_i in self.snake[1:]:
 | 
			
		||||
            if snake_i == head:
 | 
			
		||||
                rip_message = 'Snake bit itself :('
 | 
			
		||||
                is_dead = True
 | 
			
		||||
                break
 | 
			
		||||
 | 
			
		||||
        if is_dead:
 | 
			
		||||
            self.stop(rip_message)
 | 
			
		||||
 | 
			
		||||
    def keyPressEvent(self, event):
 | 
			
		||||
        key = event.key()
 | 
			
		||||
        if key in (QtCore.Qt.Key_Left, QtCore.Qt.Key_A):
 | 
			
		||||
            self.next_direction.append('l')
 | 
			
		||||
 | 
			
		||||
        elif key in (QtCore.Qt.Key_Right, QtCore.Qt.Key_D):
 | 
			
		||||
            self.next_direction.append('r')
 | 
			
		||||
 | 
			
		||||
        elif key in (QtCore.Qt.Key_Down, QtCore.Qt.Key_S):
 | 
			
		||||
            self.next_direction.append('d')
 | 
			
		||||
 | 
			
		||||
        elif key in (QtCore.Qt.Key_Up, QtCore.Qt.Key_W):
 | 
			
		||||
            self.next_direction.append('u')
 | 
			
		||||
 | 
			
		||||
        else:
 | 
			
		||||
            return super().keyPressEvent(event)
 | 
			
		||||
 | 
			
		||||
    def paintEvent(self, event):
 | 
			
		||||
        painter = QtGui.QPainter(self)
 | 
			
		||||
 | 
			
		||||
        rect = self.contentsRect()
 | 
			
		||||
        boardtop = rect.bottom() - SnakeBoard.HEIGHT * self.cellheight
 | 
			
		||||
        boardleft = rect.left()
 | 
			
		||||
 | 
			
		||||
        for pos in self.snake:
 | 
			
		||||
            self.draw_circle(painter,
 | 
			
		||||
                             int(boardleft+pos[0]*self.cellwidth),
 | 
			
		||||
                             int(boardtop+pos[1]*self.cellheight),
 | 
			
		||||
                             'blue')
 | 
			
		||||
        if self.food is not None:
 | 
			
		||||
            self.draw_square(painter,
 | 
			
		||||
                             int(boardleft+self.food[0]*self.cellwidth),
 | 
			
		||||
                             int(boardtop+self.food[1]*self.cellheight),
 | 
			
		||||
                             'orange')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TetrisBoard(Board):
 | 
			
		||||
    WIDTH = 10
 | 
			
		||||
    HEIGHT = 20
 | 
			
		||||
    SPEED = 300
 | 
			
		||||
 | 
			
		||||
    def __init__(self, parent=None):
 | 
			
		||||
        super().__init__(parent=parent)
 | 
			
		||||
 | 
			
		||||
        self._shapes = {
 | 
			
		||||
            1: ZShape,
 | 
			
		||||
            2: SShape,
 | 
			
		||||
            3: Line,
 | 
			
		||||
            4: TShape,
 | 
			
		||||
            5: Square,
 | 
			
		||||
            6: MirrorL
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self._init_game()
 | 
			
		||||
 | 
			
		||||
    def _init_game(self):
 | 
			
		||||
        self.curr_x = 0
 | 
			
		||||
        self.curr_y = 0
 | 
			
		||||
        self.curr_piece = None
 | 
			
		||||
        self.board = np.zeros((TetrisBoard.WIDTH, TetrisBoard.HEIGHT + 2), dtype=int)
 | 
			
		||||
 | 
			
		||||
    def next_move(self):
 | 
			
		||||
        if self.curr_piece is None:
 | 
			
		||||
            self.make_new_piece()
 | 
			
		||||
        else:
 | 
			
		||||
            self.move_down()
 | 
			
		||||
 | 
			
		||||
    def try_move(self, piece, x, y):
 | 
			
		||||
        if piece is None:
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        if x+piece.x.min() < 0 or x+piece.x.max() >= TetrisBoard.WIDTH:
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        if y-piece.y.max() < 0 or y-piece.y.min() >= TetrisBoard.HEIGHT+2:
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        if np.any(self.board[piece.x+x, y-piece.y]) != 0:
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        if piece != self.curr_piece:
 | 
			
		||||
            self.curr_piece = piece
 | 
			
		||||
 | 
			
		||||
        self.curr_x = x
 | 
			
		||||
        self.curr_y = y
 | 
			
		||||
 | 
			
		||||
        self.update()
 | 
			
		||||
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def make_new_piece(self):
 | 
			
		||||
        new_piece = self._shapes[random.randint(1, len(self._shapes))]()
 | 
			
		||||
 | 
			
		||||
        startx = TetrisBoard.WIDTH//2
 | 
			
		||||
        starty = TetrisBoard.HEIGHT+2 - 1 + new_piece.y.min()
 | 
			
		||||
        if not self.try_move(new_piece, startx, starty):
 | 
			
		||||
            self.stop('Game over :(')
 | 
			
		||||
 | 
			
		||||
    def move_down(self):
 | 
			
		||||
        if not self.try_move(self.curr_piece, self.curr_x, self.curr_y-1):
 | 
			
		||||
            self.final_destination_reached()
 | 
			
		||||
 | 
			
		||||
    def drop_to_bottom(self):
 | 
			
		||||
        new_y = self.curr_y
 | 
			
		||||
 | 
			
		||||
        while new_y > 0:
 | 
			
		||||
            if not self.try_move(self.curr_piece, self.curr_x, new_y-1):
 | 
			
		||||
                break
 | 
			
		||||
            new_y -= 1
 | 
			
		||||
 | 
			
		||||
        self.final_destination_reached()
 | 
			
		||||
 | 
			
		||||
    def final_destination_reached(self):
 | 
			
		||||
        x = self.curr_x+self.curr_piece.x
 | 
			
		||||
        y = self.curr_y-self.curr_piece.y
 | 
			
		||||
        self.board[x, y] = next(k for k, v in self._shapes.items() if isinstance(self.curr_piece, v))
 | 
			
		||||
 | 
			
		||||
        self.remove_lines()
 | 
			
		||||
 | 
			
		||||
        self.curr_piece = None
 | 
			
		||||
        self.make_new_piece()
 | 
			
		||||
 | 
			
		||||
    def remove_lines(self):
 | 
			
		||||
        full_rows = np.where(np.all(self.board, axis=0))[0]
 | 
			
		||||
        num_rows = len(full_rows)
 | 
			
		||||
 | 
			
		||||
        if num_rows:
 | 
			
		||||
            temp = np.zeros_like(self.board)
 | 
			
		||||
            temp[:, :temp.shape[1]-num_rows] = np.delete(self.board, full_rows, axis=1)
 | 
			
		||||
            self.board = temp
 | 
			
		||||
 | 
			
		||||
            self.score += num_rows
 | 
			
		||||
            self.new_status.emit(f'Lines: {self.score}')
 | 
			
		||||
 | 
			
		||||
            if self.score % 10 == 0:
 | 
			
		||||
                self._speed += 0.9
 | 
			
		||||
                self.timer.setInterval(int(self._speed))
 | 
			
		||||
 | 
			
		||||
            self.update()
 | 
			
		||||
 | 
			
		||||
    def keyPressEvent(self, event):
 | 
			
		||||
        key = event.key()
 | 
			
		||||
 | 
			
		||||
        if self.curr_piece is None:
 | 
			
		||||
            return super().keyPressEvent(event)
 | 
			
		||||
 | 
			
		||||
        if key == QtCore.Qt.Key_Left:
 | 
			
		||||
            self.try_move(self.curr_piece, self.curr_x-1, self.curr_y)
 | 
			
		||||
 | 
			
		||||
        elif key == QtCore.Qt.Key_Right:
 | 
			
		||||
            self.try_move(self.curr_piece, self.curr_x+1, self.curr_y)
 | 
			
		||||
 | 
			
		||||
        elif key == QtCore.Qt.Key_Down:
 | 
			
		||||
            if not self.try_move(self.curr_piece.rotate(), self.curr_x, self.curr_y):
 | 
			
		||||
                self.curr_piece.rotate(clockwise=False)
 | 
			
		||||
 | 
			
		||||
        elif key == QtCore.Qt.Key_Up:
 | 
			
		||||
            if not self.try_move(self.curr_piece.rotate(clockwise=False), self.curr_x, self.curr_y):
 | 
			
		||||
                self.curr_piece.rotate()
 | 
			
		||||
 | 
			
		||||
        elif key == QtCore.Qt.Key_Space:
 | 
			
		||||
            self.drop_to_bottom()
 | 
			
		||||
 | 
			
		||||
        else:
 | 
			
		||||
            super().keyPressEvent(event)
 | 
			
		||||
 | 
			
		||||
    def paintEvent(self, event):
 | 
			
		||||
        painter = QtGui.QPainter(self)
 | 
			
		||||
        rect = self.contentsRect()
 | 
			
		||||
        board_top = rect.bottom() - TetrisBoard.HEIGHT*self.cellheight
 | 
			
		||||
 | 
			
		||||
        for i in range(TetrisBoard.WIDTH):
 | 
			
		||||
            for j in range(TetrisBoard.HEIGHT):
 | 
			
		||||
                shape = self.board[i, j]
 | 
			
		||||
 | 
			
		||||
                if shape:
 | 
			
		||||
                    color = self._shapes[shape].color
 | 
			
		||||
                    self.draw_square(painter,
 | 
			
		||||
                                     rect.left() + i*self.cellwidth,
 | 
			
		||||
                                     board_top + (TetrisBoard.HEIGHT-j-1)*self.cellheight, color)
 | 
			
		||||
 | 
			
		||||
        if self.curr_piece is not None:
 | 
			
		||||
            x = self.curr_x + self.curr_piece.x
 | 
			
		||||
            y = self.curr_y - self.curr_piece.y
 | 
			
		||||
 | 
			
		||||
            for i in range(4):
 | 
			
		||||
                if TetrisBoard.HEIGHT < y[i]+1:
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                self.draw_square(painter, rect.left() + x[i] * self.cellwidth,
 | 
			
		||||
                                 board_top + (TetrisBoard.HEIGHT-y[i]-1) * self.cellheight,
 | 
			
		||||
                                 self.curr_piece.color)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Tetromino:
 | 
			
		||||
    SHAPE = np.array([[0], [0]])
 | 
			
		||||
    color = None
 | 
			
		||||
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        self.shape = self.SHAPE
 | 
			
		||||
 | 
			
		||||
    def rotate(self, clockwise: bool = True):
 | 
			
		||||
        if clockwise:
 | 
			
		||||
            self.shape = np.vstack((-self.shape[1], self.shape[0]))
 | 
			
		||||
        else:
 | 
			
		||||
            self.shape = np.vstack((self.shape[1], -self.shape[0]))
 | 
			
		||||
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def x(self):
 | 
			
		||||
        return self.shape[0]
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def y(self):
 | 
			
		||||
        return self.shape[1]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ZShape(Tetromino):
 | 
			
		||||
    SHAPE = np.array([[0, 0, -1, -1],
 | 
			
		||||
                      [-1, 0, 0, 1]])
 | 
			
		||||
    color = 'green'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SShape(Tetromino):
 | 
			
		||||
    SHAPE = np.array([[0, 0, 1, 1],
 | 
			
		||||
                      [-1, 0, 0, 1]])
 | 
			
		||||
    color = 'purple'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Line(Tetromino):
 | 
			
		||||
    SHAPE = np.array([[0, 0, 0, 0],
 | 
			
		||||
                      [-1, 0, 1, 2]])
 | 
			
		||||
    color = 'red'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TShape(Tetromino):
 | 
			
		||||
    SHAPE = np.array([[-1, 0, 1, 0],
 | 
			
		||||
                      [0, 0, 0, 1]])
 | 
			
		||||
    color = 'orange'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Square(Tetromino):
 | 
			
		||||
    SHAPE = np.array([[0, 1, 0, 1],
 | 
			
		||||
                      [0, 0, 1, 1]])
 | 
			
		||||
    color = 'yellow'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LShape(Tetromino):
 | 
			
		||||
    SHAPE = np.array([[-1, 0, 0, 0],
 | 
			
		||||
                      [1, -1, 0, 1]])
 | 
			
		||||
    color = 'blue'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MirrorL(Tetromino):
 | 
			
		||||
    SHAPE = np.array([[1, 0, 0, 0],
 | 
			
		||||
                      [-1, -1, 0, 1]])
 | 
			
		||||
    color = 'lightGray'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Field(QtWidgets.QToolButton):
 | 
			
		||||
    NUM_COLORS = {
 | 
			
		||||
        1: '#1e46a4',
 | 
			
		||||
        2: '#f28e2b',
 | 
			
		||||
        3: '#e15759',
 | 
			
		||||
        4: '#76b7b2',
 | 
			
		||||
        5: '#59a14f',
 | 
			
		||||
        6: '#edc948',
 | 
			
		||||
        7: '#b07aa1',
 | 
			
		||||
        8: '#ff9da7',
 | 
			
		||||
        'X': '#ff0000',
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    flag_change = QtCore.pyqtSignal(bool)
 | 
			
		||||
 | 
			
		||||
    def __init__(self, x, y, parent=None):
 | 
			
		||||
        super(Field, self).__init__(parent=parent)
 | 
			
		||||
 | 
			
		||||
        self.setFixedSize(QtCore.QSize(30, 30))
 | 
			
		||||
        f = self.font()
 | 
			
		||||
        f.setPointSize(24)
 | 
			
		||||
        f.setWeight(75)
 | 
			
		||||
        self.setFont(f)
 | 
			
		||||
 | 
			
		||||
        self.x = x
 | 
			
		||||
        self.y = y
 | 
			
		||||
 | 
			
		||||
        self.neighbors = []
 | 
			
		||||
        self.grid = self.parent()
 | 
			
		||||
 | 
			
		||||
        _size = self.grid.gridsize
 | 
			
		||||
        for x_i in range(max(0, self.x-1), min(self.x+2, _size[0])):
 | 
			
		||||
            for y_i in range(max(0, self.y-1), min(self.y+2, _size[1])):
 | 
			
		||||
                if (x_i, y_i) == (self.x, self.y):
 | 
			
		||||
                    continue
 | 
			
		||||
                self.neighbors.append((x_i, y_i))
 | 
			
		||||
 | 
			
		||||
        self.is_mine = False
 | 
			
		||||
        self.is_flagged = False
 | 
			
		||||
        self.is_known = False
 | 
			
		||||
        self.has_died = False
 | 
			
		||||
 | 
			
		||||
    def mousePressEvent(self, evt: QtGui.QMouseEvent) -> None:
 | 
			
		||||
        if self.grid.status == 'finished':
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        if evt.button() == QtCore.Qt.RightButton:
 | 
			
		||||
            self.set_flag()
 | 
			
		||||
        elif evt.button() == QtCore.Qt.LeftButton:
 | 
			
		||||
            self.select()
 | 
			
		||||
        else:
 | 
			
		||||
            super().mousePressEvent(evt)
 | 
			
		||||
 | 
			
		||||
    def __str__(self) -> str:
 | 
			
		||||
        if self.is_mine:
 | 
			
		||||
            return 'X'
 | 
			
		||||
        else:
 | 
			
		||||
            return str(self.visible_mines)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def visible_mines(self) -> int:
 | 
			
		||||
        return sum(self.grid.map[x_i][y_i].is_mine for x_i, y_i in self.neighbors)
 | 
			
		||||
 | 
			
		||||
    @QtCore.pyqtSlot()
 | 
			
		||||
    def select(self):
 | 
			
		||||
        if self.is_flagged:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        if self.is_mine:
 | 
			
		||||
            self.setText('X')
 | 
			
		||||
            num = 'X'
 | 
			
		||||
            self.has_died = True
 | 
			
		||||
            self.setStyleSheet(f'color: {self.NUM_COLORS[num]}')
 | 
			
		||||
        else:
 | 
			
		||||
            self.is_known = True
 | 
			
		||||
            self.setEnabled(False)
 | 
			
		||||
            self.setAutoRaise(True)
 | 
			
		||||
 | 
			
		||||
            num = self.visible_mines
 | 
			
		||||
            if num != 0:
 | 
			
		||||
                self.setText(str(num))
 | 
			
		||||
                self.setStyleSheet(f'color: {self.NUM_COLORS[num]}')
 | 
			
		||||
            else:
 | 
			
		||||
                self.grid.reveal_neighbors(self)
 | 
			
		||||
 | 
			
		||||
        self.clicked.emit()
 | 
			
		||||
 | 
			
		||||
    @QtCore.pyqtSlot()
 | 
			
		||||
    def set_flag(self):
 | 
			
		||||
        if self.is_flagged:
 | 
			
		||||
            self.setText('')
 | 
			
		||||
        else:
 | 
			
		||||
            self.setText('!')
 | 
			
		||||
        self.is_flagged = not self.is_flagged
 | 
			
		||||
        self.has_died = False
 | 
			
		||||
 | 
			
		||||
        self.clicked.emit()
 | 
			
		||||
        self.flag_change.emit(self.is_flagged)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class QMines(QtWidgets.QMainWindow):
 | 
			
		||||
    LEVELS = {
 | 
			
		||||
        'easy': ((9, 9), 10),
 | 
			
		||||
        'middle': ((16, 16), 40),
 | 
			
		||||
        'hard': ((16, 30), 99),
 | 
			
		||||
        'very hard': ((16, 30), 10),
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    def __init__(self, parent=None):
 | 
			
		||||
        super().__init__(parent=parent)
 | 
			
		||||
 | 
			
		||||
        self.map = []
 | 
			
		||||
 | 
			
		||||
        self.status = 'waiting'
 | 
			
		||||
        self.open_fields = 0
 | 
			
		||||
        self.num_flags = 0
 | 
			
		||||
 | 
			
		||||
        self._start = 0
 | 
			
		||||
 | 
			
		||||
        self.gridsize, self.mines = (1, 1), 1
 | 
			
		||||
 | 
			
		||||
        self._init_ui()
 | 
			
		||||
 | 
			
		||||
    def _init_ui(self):
 | 
			
		||||
        layout = QtWidgets.QGridLayout()
 | 
			
		||||
        layout.setSpacing(0)
 | 
			
		||||
        self.central = QtWidgets.QWidget()
 | 
			
		||||
        self.setCentralWidget(self.central)
 | 
			
		||||
 | 
			
		||||
        layout.addItem(QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding),
 | 
			
		||||
                       0, 0)
 | 
			
		||||
 | 
			
		||||
        self.grid_layout = QtWidgets.QGridLayout()
 | 
			
		||||
        self.grid_layout.setSpacing(0)
 | 
			
		||||
        self.map_widget = QtWidgets.QFrame()
 | 
			
		||||
        self.map_widget.setFrameStyle(2)
 | 
			
		||||
        self.map_widget.setLayout(self.grid_layout)
 | 
			
		||||
        layout.addWidget(self.map_widget, 1, 1)
 | 
			
		||||
 | 
			
		||||
        self.new_game = QtWidgets.QPushButton('New game')
 | 
			
		||||
        self.new_game.pressed.connect(self._init_map)
 | 
			
		||||
        layout.addWidget(self.new_game, 0, 1)
 | 
			
		||||
 | 
			
		||||
        layout.addItem(QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding),
 | 
			
		||||
                       2, 2)
 | 
			
		||||
 | 
			
		||||
        self.central.setLayout(layout)
 | 
			
		||||
 | 
			
		||||
        self.timer = QtCore.QTimer()
 | 
			
		||||
        self.timer.setInterval(1000)
 | 
			
		||||
        self.timer.timeout.connect(self.update_time)
 | 
			
		||||
 | 
			
		||||
        self.timer_message = QtWidgets.QLabel('0 s')
 | 
			
		||||
        self.statusBar().addWidget(self.timer_message)
 | 
			
		||||
 | 
			
		||||
        self.mine_message = QtWidgets.QLabel(f'0 / {self.mines}')
 | 
			
		||||
        self.statusBar().addWidget(self.mine_message)
 | 
			
		||||
 | 
			
		||||
        self.dead_message = QtWidgets.QLabel('')
 | 
			
		||||
        self.statusBar().addWidget(self.dead_message)
 | 
			
		||||
 | 
			
		||||
        self.level_cb = QtWidgets.QComboBox()
 | 
			
		||||
        self.level_cb.addItems(list(self.LEVELS.keys()))
 | 
			
		||||
        self.level_cb.currentTextChanged.connect(self._init_map)
 | 
			
		||||
        self.statusBar().addPermanentWidget(self.level_cb)
 | 
			
		||||
 | 
			
		||||
        self._init_map('easy')
 | 
			
		||||
 | 
			
		||||
    def _init_map(self, lvl: str = None):
 | 
			
		||||
        if lvl is None:
 | 
			
		||||
            lvl = self.level_cb.currentText()
 | 
			
		||||
 | 
			
		||||
        self._clear_map()
 | 
			
		||||
 | 
			
		||||
        self.gridsize, self.mines = QMines.LEVELS[lvl]
 | 
			
		||||
        w, h = self.gridsize
 | 
			
		||||
        self.map = [[None] * h for _ in range(w)]
 | 
			
		||||
 | 
			
		||||
        for x in range(w):
 | 
			
		||||
            for y in range(h):
 | 
			
		||||
                pos = Field(x, y, parent=self)
 | 
			
		||||
                pos.clicked.connect(self.start_game)
 | 
			
		||||
                pos.clicked.connect(self.update_status)
 | 
			
		||||
                pos.flag_change.connect(self.update_flag)
 | 
			
		||||
                self.grid_layout.addWidget(pos, x+1, y+1)
 | 
			
		||||
                self.map[x][y] = pos
 | 
			
		||||
 | 
			
		||||
        self.set_mines()
 | 
			
		||||
 | 
			
		||||
    def _clear_map(self):
 | 
			
		||||
        self.status = 'waiting'
 | 
			
		||||
        self.open_fields = 0
 | 
			
		||||
        self.num_flags = 0
 | 
			
		||||
 | 
			
		||||
        self._start = 0
 | 
			
		||||
 | 
			
		||||
        self.map = []
 | 
			
		||||
 | 
			
		||||
        while self.grid_layout.count():
 | 
			
		||||
            child = self.grid_layout.takeAt(0)
 | 
			
		||||
            w = child.widget()
 | 
			
		||||
            if w:
 | 
			
		||||
                self.grid_layout.removeWidget(w)
 | 
			
		||||
                w.deleteLater()
 | 
			
		||||
            else:
 | 
			
		||||
                self.grid_layout.removeItem(child)
 | 
			
		||||
 | 
			
		||||
    def set_mines(self):
 | 
			
		||||
        count = 0
 | 
			
		||||
        w, h = self.gridsize
 | 
			
		||||
        while count < self.mines:
 | 
			
		||||
            n = random.randint(0, w * h - 1)
 | 
			
		||||
            row = n // h
 | 
			
		||||
            col = n % h
 | 
			
		||||
            pos = self.map[row][col]  # type: Field
 | 
			
		||||
            if pos.is_mine:
 | 
			
		||||
                continue
 | 
			
		||||
            pos.is_mine = True
 | 
			
		||||
            count += 1
 | 
			
		||||
 | 
			
		||||
        self.status = 'waiting'
 | 
			
		||||
        self.open_fields = 0
 | 
			
		||||
        self.num_flags = 0
 | 
			
		||||
        self._start = 0
 | 
			
		||||
 | 
			
		||||
        self.timer_message.setText('0 s')
 | 
			
		||||
        self.mine_message.setText(f'0 / {self.mines}')
 | 
			
		||||
        self.dead_message.setText('')
 | 
			
		||||
 | 
			
		||||
    def reveal_neighbors(self, pos: Field):
 | 
			
		||||
        for x_i, y_i in pos.neighbors:
 | 
			
		||||
            field_i = self.map[x_i][y_i]  # type: Field
 | 
			
		||||
            if field_i.isEnabled():
 | 
			
		||||
                field_i.select()
 | 
			
		||||
 | 
			
		||||
    def start_game(self):
 | 
			
		||||
        if self.status == 'waiting':
 | 
			
		||||
            self.status = 'running'
 | 
			
		||||
            self._start = time.time()
 | 
			
		||||
            self.timer.start(1000)
 | 
			
		||||
 | 
			
		||||
    def update_time(self):
 | 
			
		||||
        if self.status == 'running':
 | 
			
		||||
            self.timer_message.setText(f'{time.time()-self._start:3.0f} s')
 | 
			
		||||
 | 
			
		||||
    def update_status(self):
 | 
			
		||||
        if self.status == 'finished':
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        pos = self.sender()  # type: Field
 | 
			
		||||
        if pos.is_known:
 | 
			
		||||
            self.open_fields += 1
 | 
			
		||||
            if self.open_fields == self.gridsize[0] * self.gridsize[1] - self.mines:
 | 
			
		||||
                self.timer.stop()
 | 
			
		||||
 | 
			
		||||
                _ = QtWidgets.QMessageBox.information(self, 'Game finished', 'Game finished!!!')
 | 
			
		||||
                self.status = 'finished'
 | 
			
		||||
 | 
			
		||||
        elif pos.has_died:
 | 
			
		||||
            dead_cnt = self.dead_message.text()
 | 
			
		||||
            if dead_cnt == '':
 | 
			
		||||
                self.dead_message.setText('(Deaths: 1)')
 | 
			
		||||
            else:
 | 
			
		||||
                self.dead_message.setText(f'(Deaths: {int(dead_cnt[9:-1])+1})')
 | 
			
		||||
 | 
			
		||||
    @QtCore.pyqtSlot(bool)
 | 
			
		||||
    def update_flag(self, state: bool):
 | 
			
		||||
        num_mines = int(self.mine_message.text().split()[0])
 | 
			
		||||
        if state:
 | 
			
		||||
            num_mines += 1
 | 
			
		||||
        else:
 | 
			
		||||
            num_mines -= 1
 | 
			
		||||
 | 
			
		||||
        self.mine_message.setText(f'{num_mines} / {self.mines}')
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										102
									
								
								src/gui_qt/lib/styles.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								src/gui_qt/lib/styles.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,102 @@
 | 
			
		||||
from . import HAS_IMPORTLIB_RESOURCE
 | 
			
		||||
from ..Qt import QtGui, QtWidgets
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DarkPalette(QtGui.QPalette):
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        super().__init__()
 | 
			
		||||
 | 
			
		||||
        self.setColor(QtGui.QPalette.Base, QtGui.QColor(42, 42, 42))
 | 
			
		||||
        self.setColor(QtGui.QPalette.AlternateBase, QtGui.QColor(66, 66, 66))
 | 
			
		||||
        self.setColor(QtGui.QPalette.Window, QtGui.QColor(93, 93, 93))
 | 
			
		||||
        self.setColor(QtGui.QPalette.ToolTipBase, QtGui.QColor(93, 93, 93))
 | 
			
		||||
        self.setColor(QtGui.QPalette.Button, QtGui.QColor(93, 93, 93))
 | 
			
		||||
        
 | 
			
		||||
        self.setColor(QtGui.QPalette.WindowText, QtGui.QColor(220, 220, 220))
 | 
			
		||||
        self.setColor(QtGui.QPalette.ToolTipText, QtGui.QColor(220, 220, 220))
 | 
			
		||||
        self.setColor(QtGui.QPalette.Text, QtGui.QColor(220, 220, 220))
 | 
			
		||||
        self.setColor(QtGui.QPalette.BrightText, QtGui.QColor(220, 220, 220))
 | 
			
		||||
        self.setColor(QtGui.QPalette.ButtonText, QtGui.QColor(220, 220, 220))
 | 
			
		||||
        self.setColor(QtGui.QPalette.HighlightedText, QtGui.QColor(220, 220, 220))
 | 
			
		||||
        
 | 
			
		||||
        self.setColor(QtGui.QPalette.Shadow, QtGui.QColor(20, 20, 20))
 | 
			
		||||
        self.setColor(QtGui.QPalette.Dark, QtGui.QColor(35, 35, 35))
 | 
			
		||||
        
 | 
			
		||||
        self.setColor(QtGui.QPalette.Highlight, QtGui.QColor(42, 130, 218))
 | 
			
		||||
        self.setColor(QtGui.QPalette.Link, QtGui.QColor(220, 220, 220))
 | 
			
		||||
        self.setColor(QtGui.QPalette.LinkVisited, QtGui.QColor(108, 180, 218))
 | 
			
		||||
        
 | 
			
		||||
        # disabled
 | 
			
		||||
        self.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.Base, QtGui.QColor(80, 80, 80))
 | 
			
		||||
        self.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.Window, QtGui.QColor(80, 80, 80))
 | 
			
		||||
        
 | 
			
		||||
        self.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.Text, QtGui.QColor(127, 127, 127))
 | 
			
		||||
        self.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.ButtonText, QtGui.QColor(127, 127, 127))
 | 
			
		||||
        self.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.HighlightedText, QtGui.QColor(127, 127, 127))
 | 
			
		||||
        self.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.WindowText, QtGui.QColor(127, 127, 127))
 | 
			
		||||
        
 | 
			
		||||
        self.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.Highlight, QtGui.QColor(80, 80, 80))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LightPalette(QtGui.QPalette):
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        super().__init__()
 | 
			
		||||
        
 | 
			
		||||
        self.setColor(QtGui.QPalette.Base, QtGui.QColor(237, 237, 237))
 | 
			
		||||
        self.setColor(QtGui.QPalette.AlternateBase, QtGui.QColor(225, 225, 225))
 | 
			
		||||
        self.setColor(QtGui.QPalette.Window, QtGui.QColor(240, 240, 240))
 | 
			
		||||
        self.setColor(QtGui.QPalette.ToolTipBase, QtGui.QColor(240, 240, 240))
 | 
			
		||||
        self.setColor(QtGui.QPalette.Button, QtGui.QColor(240, 240, 240))
 | 
			
		||||
        
 | 
			
		||||
        self.setColor(QtGui.QPalette.WindowText, QtGui.QColor(0, 0, 0))
 | 
			
		||||
        self.setColor(QtGui.QPalette.Text, QtGui.QColor(0, 0, 0))
 | 
			
		||||
        self.setColor(QtGui.QPalette.BrightText, QtGui.QColor(0, 0, 0))
 | 
			
		||||
        self.setColor(QtGui.QPalette.ButtonText, QtGui.QColor(0, 0, 0))
 | 
			
		||||
        self.setColor(QtGui.QPalette.ToolTipText, QtGui.QColor(0, 0, 0))
 | 
			
		||||
        self.setColor(QtGui.QPalette.HighlightedText, QtGui.QColor(0, 0, 0))
 | 
			
		||||
        
 | 
			
		||||
        self.setColor(QtGui.QPalette.Shadow, QtGui.QColor(20, 20, 20))
 | 
			
		||||
        self.setColor(QtGui.QPalette.Dark, QtGui.QColor(225, 225, 225))
 | 
			
		||||
        
 | 
			
		||||
        self.setColor(QtGui.QPalette.Highlight, QtGui.QColor(218, 66, 42))
 | 
			
		||||
        self.setColor(QtGui.QPalette.Link, QtGui.QColor(0, 162, 232))
 | 
			
		||||
        self.setColor(QtGui.QPalette.LinkVisited, QtGui.QColor(222, 222, 222))
 | 
			
		||||
        
 | 
			
		||||
        # disabled
 | 
			
		||||
        self.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.Base, QtGui.QColor(115, 115, 115))
 | 
			
		||||
        self.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.Window, QtGui.QColor(115, 115, 115))
 | 
			
		||||
        
 | 
			
		||||
        self.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.WindowText, QtGui.QColor(115, 115, 115))
 | 
			
		||||
        self.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.Text, QtGui.QColor(115, 115, 115))
 | 
			
		||||
        self.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.ButtonText, QtGui.QColor(115, 115, 115))
 | 
			
		||||
        self.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.HighlightedText, QtGui.QColor(115, 115, 115))
 | 
			
		||||
        
 | 
			
		||||
        self.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.Highlight, QtGui.QColor(190, 190, 190))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MyProxyStyle(QtWidgets.QProxyStyle):
 | 
			
		||||
    def __init__(self, color):
 | 
			
		||||
        super().__init__()
 | 
			
		||||
 | 
			
		||||
        if color == 'dark':
 | 
			
		||||
            self._palette = DarkPalette()
 | 
			
		||||
        else:
 | 
			
		||||
            self._palette = LightPalette()
 | 
			
		||||
 | 
			
		||||
    def polish(self, obj):
 | 
			
		||||
        if isinstance(obj, QtGui.QPalette):
 | 
			
		||||
            return self._palette
 | 
			
		||||
 | 
			
		||||
        elif isinstance(obj, QtWidgets.QApplication):
 | 
			
		||||
            if HAS_IMPORTLIB_RESOURCE:
 | 
			
		||||
                from importlib.resources import path
 | 
			
		||||
                with path('resources.icons', 'style.qss') as fp:
 | 
			
		||||
                    with fp.open('r') as f:
 | 
			
		||||
                        obj.setStyleSheet(f.read())
 | 
			
		||||
            else:
 | 
			
		||||
                from pkg_resources import resource_filename
 | 
			
		||||
                with open(resource_filename('resources.icons', 'style.qss'), 'r') as f:
 | 
			
		||||
                    obj.setStyleSheet(f.read())
 | 
			
		||||
 | 
			
		||||
        else:
 | 
			
		||||
            return super().polish(obj)
 | 
			
		||||
							
								
								
									
										31
									
								
								src/gui_qt/lib/tables.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/gui_qt/lib/tables.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
			
		||||
from ..Qt import QtWidgets, QtGui, QtCore
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TreeWidget(QtWidgets.QTreeWidget):
 | 
			
		||||
    def keyPressEvent(self, evt: QtGui.QKeyEvent):
 | 
			
		||||
        if evt.key() == QtCore.Qt.Key_Space:
 | 
			
		||||
            sets = []
 | 
			
		||||
            from_parent = []
 | 
			
		||||
 | 
			
		||||
            for idx in self.selectedIndexes():
 | 
			
		||||
                if idx.column() != 0:
 | 
			
		||||
                    continue
 | 
			
		||||
                item = self.itemFromIndex(idx)
 | 
			
		||||
 | 
			
		||||
                if item.parent() is None:
 | 
			
		||||
                    is_selected = item.checkState(0)
 | 
			
		||||
                    self.blockSignals(True)
 | 
			
		||||
                    for i in range(item.childCount()):
 | 
			
		||||
                        child = item.child(i)
 | 
			
		||||
                        # child.setCheckState(0, is_selected)
 | 
			
		||||
                        from_parent.append(child)
 | 
			
		||||
                    self.blockSignals(False)
 | 
			
		||||
                    item.setCheckState(0, QtCore.Qt.Unchecked if is_selected == QtCore.Qt.Checked else QtCore.Qt.Checked)
 | 
			
		||||
                else:
 | 
			
		||||
                    sets.append(item)
 | 
			
		||||
            for it in sets:
 | 
			
		||||
                if it in from_parent:
 | 
			
		||||
                    continue
 | 
			
		||||
                it.setCheckState(0, QtCore.Qt.Unchecked if it.checkState(0) == QtCore.Qt.Checked else QtCore.Qt.Checked)
 | 
			
		||||
        else:
 | 
			
		||||
            super().keyPressEvent(evt)
 | 
			
		||||
							
								
								
									
										245
									
								
								src/gui_qt/lib/undos.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										245
									
								
								src/gui_qt/lib/undos.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,245 @@
 | 
			
		||||
import copy
 | 
			
		||||
 | 
			
		||||
from numpy import argsort
 | 
			
		||||
 | 
			
		||||
from ..Qt import QtWidgets, QtCore
 | 
			
		||||
from ..data.container import FitContainer
 | 
			
		||||
from ..graphs.graphwindow import QGraphWindow
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ApodizationCommand(QtWidgets.QUndoCommand):
 | 
			
		||||
    def __init__(self, data, apod_values: list, apod_func: object):
 | 
			
		||||
        super().__init__('Apodization')
 | 
			
		||||
 | 
			
		||||
        self.__data = data
 | 
			
		||||
        self.__y = copy.deepcopy(data.data.y)
 | 
			
		||||
        self.__apod_func = apod_func
 | 
			
		||||
        self.__apod_values = apod_values
 | 
			
		||||
 | 
			
		||||
    def undo(self):
 | 
			
		||||
        # doing a copy (again) to ensure two different objects
 | 
			
		||||
        self.__data.y = copy.deepcopy(self.__y)
 | 
			
		||||
 | 
			
		||||
    def redo(self):
 | 
			
		||||
        self.__data.apply('ap', (self.__apod_values, self.__apod_func))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CutCommand(QtWidgets.QUndoCommand):
 | 
			
		||||
    def __init__(self, data, *limits):
 | 
			
		||||
        super().__init__('Apodization')
 | 
			
		||||
 | 
			
		||||
        self.__data = data
 | 
			
		||||
        self.__data_data = copy.deepcopy(data.data)
 | 
			
		||||
        self.__limits = limits
 | 
			
		||||
 | 
			
		||||
    def undo(self):
 | 
			
		||||
        # doing a copy (again) to ensure two different objects
 | 
			
		||||
        self.__data.data = copy.deepcopy(self.__data_data)
 | 
			
		||||
 | 
			
		||||
    def redo(self):
 | 
			
		||||
        self.__data.apply('cut', self.__limits)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PhaseCommand(QtWidgets.QUndoCommand):
 | 
			
		||||
    def __init__(self, data, ph0: float, ph1: float, pvt: float):
 | 
			
		||||
        super().__init__('Phase correction')
 | 
			
		||||
 | 
			
		||||
        self.__phase = (ph0, ph1, pvt)
 | 
			
		||||
        self.__data = data
 | 
			
		||||
 | 
			
		||||
    def undo(self):
 | 
			
		||||
        self.__data.apply('ph', (-self.__phase[0], -self.__phase[1], self.__phase[2]))
 | 
			
		||||
 | 
			
		||||
    def redo(self):
 | 
			
		||||
        self.__data.apply('ph', self.__phase)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ShiftCommand(QtWidgets.QUndoCommand):
 | 
			
		||||
    def __init__(self, data, value, mode):
 | 
			
		||||
        super().__init__('Fourier')
 | 
			
		||||
 | 
			
		||||
        self.__data = data
 | 
			
		||||
        self.__original = copy.deepcopy(self.__data.data)
 | 
			
		||||
        self.__args = (value, mode)
 | 
			
		||||
 | 
			
		||||
    def undo(self):
 | 
			
		||||
        self.__data.data = copy.deepcopy(self.__original)
 | 
			
		||||
 | 
			
		||||
    def redo(self):
 | 
			
		||||
        self.__data.apply('ls', self.__args)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NormCommand(QtWidgets.QUndoCommand):
 | 
			
		||||
    def __init__(self, data, mode):
 | 
			
		||||
        super().__init__('Normalize')
 | 
			
		||||
 | 
			
		||||
        self.__data = data
 | 
			
		||||
        self.__mode = mode
 | 
			
		||||
        self.__scale = 1.
 | 
			
		||||
 | 
			
		||||
    def undo(self):
 | 
			
		||||
        self.__data.y *= self.__scale
 | 
			
		||||
        self.__data.y_err *= self.__scale
 | 
			
		||||
 | 
			
		||||
    def redo(self):
 | 
			
		||||
        max_value = self.__data.y.max()
 | 
			
		||||
        self.__data.apply('norm', (self.__mode,))
 | 
			
		||||
        self.__scale = max_value / self.__data.y.max()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CenterCommand(QtWidgets.QUndoCommand):
 | 
			
		||||
    def __init__(self, data):
 | 
			
		||||
        super().__init__('Normalize')
 | 
			
		||||
 | 
			
		||||
        self.__data = data
 | 
			
		||||
        self.__offset = 0.
 | 
			
		||||
 | 
			
		||||
    def undo(self):
 | 
			
		||||
        _x = self.__data.data.x
 | 
			
		||||
        _x += self.__offset
 | 
			
		||||
        self.__data.x = _x
 | 
			
		||||
 | 
			
		||||
    def redo(self):
 | 
			
		||||
        x0 = self.__data.x[0]
 | 
			
		||||
        self.__data.apply('center', ())
 | 
			
		||||
        self.__offset = x0 - self.__data.x[0]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ZerofillCommand(QtWidgets.QUndoCommand):
 | 
			
		||||
    def __init__(self, data):
 | 
			
		||||
        super().__init__('Zero filling')
 | 
			
		||||
 | 
			
		||||
        self.__data = data
 | 
			
		||||
 | 
			
		||||
    def undo(self):
 | 
			
		||||
        self.__data.apply('zf', (-1,))
 | 
			
		||||
 | 
			
		||||
    def redo(self):
 | 
			
		||||
        self.__data.apply('zf', (1,))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BaselineCommand(QtWidgets.QUndoCommand):
 | 
			
		||||
    def __init__(self, data):
 | 
			
		||||
        super().__init__('Baseline correction')
 | 
			
		||||
 | 
			
		||||
        self.__baseline = None
 | 
			
		||||
        self.__data = data
 | 
			
		||||
 | 
			
		||||
    def undo(self):
 | 
			
		||||
        self.__data.y += self.__baseline
 | 
			
		||||
 | 
			
		||||
    def redo(self):
 | 
			
		||||
        y_prev = self.__data.y[-1]
 | 
			
		||||
        self.__data.apply('bl', tuple())
 | 
			
		||||
        self.__baseline = y_prev - self.__data.y[-1]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BaselineSplineCommand(QtWidgets.QUndoCommand):
 | 
			
		||||
    def __init__(self, data, baseline):
 | 
			
		||||
        super().__init__('Baseline correction')
 | 
			
		||||
 | 
			
		||||
        self.__baseline = baseline
 | 
			
		||||
        self.__data = data
 | 
			
		||||
 | 
			
		||||
    def undo(self):
 | 
			
		||||
        self.__data.apply('bls', (-self.__baseline,))
 | 
			
		||||
 | 
			
		||||
    def redo(self):
 | 
			
		||||
        self.__data.apply('bls', (self.__baseline,))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FourierCommand(QtWidgets.QUndoCommand):
 | 
			
		||||
    def __init__(self, data):
 | 
			
		||||
        super().__init__('Fourier')
 | 
			
		||||
 | 
			
		||||
        self.__data = data
 | 
			
		||||
        self.__original = copy.deepcopy(self.__data.data)
 | 
			
		||||
 | 
			
		||||
    def undo(self):
 | 
			
		||||
        self.__data.data = copy.deepcopy(self.__original)
 | 
			
		||||
 | 
			
		||||
    def redo(self):
 | 
			
		||||
        self.__data.apply('ft', tuple())
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SortCommand(QtWidgets.QUndoCommand):
 | 
			
		||||
    def __init__(self, data):
 | 
			
		||||
        super().__init__('Sort')
 | 
			
		||||
 | 
			
		||||
        self.__data = data
 | 
			
		||||
        self.__sort = None
 | 
			
		||||
 | 
			
		||||
    def undo(self):
 | 
			
		||||
        self.__data.unsort(self.__sort)
 | 
			
		||||
 | 
			
		||||
    def redo(self):
 | 
			
		||||
        self.__sort = argsort(argsort(self.__data.data.x))
 | 
			
		||||
        self.__data.apply('sort', tuple())
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DeleteGraphCommand(QtWidgets.QUndoCommand):
 | 
			
		||||
    def __init__(self, container: dict, key: str,
 | 
			
		||||
                 signal1: QtCore.pyqtSignal, signal2: QtCore.pyqtSignal):
 | 
			
		||||
        super().__init__('Delete graph')
 | 
			
		||||
        # Deletion of GraphWindow is more complicated because C++ object is destroyed
 | 
			
		||||
 | 
			
		||||
        self.__container = container
 | 
			
		||||
        _value = self.__container[key]
 | 
			
		||||
        self.__value = self.__container[key].get_state()
 | 
			
		||||
        self.__key = key
 | 
			
		||||
        self.__signal_add = signal1
 | 
			
		||||
        self.__signal_remove = signal2
 | 
			
		||||
 | 
			
		||||
    def redo(self):
 | 
			
		||||
        self.__signal_remove.emit(self.__key)
 | 
			
		||||
        del self.__container[self.__key]
 | 
			
		||||
 | 
			
		||||
    def undo(self):
 | 
			
		||||
        q = QGraphWindow().set_state(self.__value)
 | 
			
		||||
        self.__container[self.__key] = q
 | 
			
		||||
        self.__signal_add.emit(self.__key)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DeleteCommand(QtWidgets.QUndoCommand):
 | 
			
		||||
    def __init__(self, container, key, signal1, signal2):
 | 
			
		||||
        super().__init__('Delete data')
 | 
			
		||||
 | 
			
		||||
        self.__container = container
 | 
			
		||||
        self.__value = self.__container[key]
 | 
			
		||||
        self.__key = key
 | 
			
		||||
        self.__signal_add = signal1
 | 
			
		||||
        self.__signal_remove = signal2
 | 
			
		||||
 | 
			
		||||
    def redo(self):
 | 
			
		||||
        self.__signal_remove.emit(self.__key)
 | 
			
		||||
        if isinstance(self.__value, FitContainer):
 | 
			
		||||
            try:
 | 
			
		||||
                self.__container[self.__value.fitted_key]._fits.remove(self.__key)
 | 
			
		||||
            except KeyError:
 | 
			
		||||
                pass
 | 
			
		||||
        del self.__container[self.__key]
 | 
			
		||||
 | 
			
		||||
    def undo(self):
 | 
			
		||||
        self.__container[self.__key] = self.__value
 | 
			
		||||
        if isinstance(self.__value, FitContainer):
 | 
			
		||||
            try:
 | 
			
		||||
                self.__container[self.__value.fitted_key]._fits.append(self.__key)
 | 
			
		||||
            except KeyError:
 | 
			
		||||
                pass
 | 
			
		||||
 | 
			
		||||
        self.__signal_add.emit([self.__key], self.__value.graph)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EvalCommand(QtWidgets.QUndoCommand):
 | 
			
		||||
    def __init__(self, container: dict, key: str, new_data, title: str):
 | 
			
		||||
        super().__init__(title)
 | 
			
		||||
        self.__container = container
 | 
			
		||||
        self.__value = copy.deepcopy(self.__container[key].data)
 | 
			
		||||
        self.__replacement = new_data
 | 
			
		||||
        self.__key = key
 | 
			
		||||
 | 
			
		||||
    def redo(self):
 | 
			
		||||
        self.__container[self.__key].data = self.__replacement
 | 
			
		||||
 | 
			
		||||
    def undo(self):
 | 
			
		||||
        self.__container[self.__key].data = self.__value
 | 
			
		||||
							
								
								
									
										132
									
								
								src/gui_qt/lib/usermodeleditor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								src/gui_qt/lib/usermodeleditor.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,132 @@
 | 
			
		||||
from __future__ import annotations
 | 
			
		||||
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
 | 
			
		||||
from ..Qt import QtWidgets, QtCore, QtGui
 | 
			
		||||
from ..lib.codeeditor import CodeEditor
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class QUsermodelEditor(QtWidgets.QMainWindow):
 | 
			
		||||
    modelsChanged = QtCore.pyqtSignal()
 | 
			
		||||
 | 
			
		||||
    def __init__(self, fname: str | Path = None, parent=None):
 | 
			
		||||
        super().__init__(parent=parent)
 | 
			
		||||
 | 
			
		||||
        self._init_gui()
 | 
			
		||||
 | 
			
		||||
        self.fname = None
 | 
			
		||||
        self._dir = None
 | 
			
		||||
        if fname is not None:
 | 
			
		||||
            self.read_file(fname)
 | 
			
		||||
 | 
			
		||||
    def _init_gui(self):
 | 
			
		||||
        self.centralwidget = QtWidgets.QWidget(self)
 | 
			
		||||
 | 
			
		||||
        layout = QtWidgets.QVBoxLayout(self.centralwidget)
 | 
			
		||||
        layout.setContentsMargins(3, 3, 3, 3)
 | 
			
		||||
        layout.setSpacing(3)
 | 
			
		||||
 | 
			
		||||
        self.edit_field = CodeEditor(self.centralwidget)
 | 
			
		||||
        font = QtGui.QFont('default')
 | 
			
		||||
        font.setStyleHint(font.Monospace)
 | 
			
		||||
        font.setPointSize(10)
 | 
			
		||||
        self.edit_field.setFont(font)
 | 
			
		||||
 | 
			
		||||
        layout.addWidget(self.edit_field)
 | 
			
		||||
        self.setCentralWidget(self.centralwidget)
 | 
			
		||||
 | 
			
		||||
        self.statusbar = QtWidgets.QStatusBar(self)
 | 
			
		||||
        self.setStatusBar(self.statusbar)
 | 
			
		||||
 | 
			
		||||
        self.menubar = self.menuBar()
 | 
			
		||||
 | 
			
		||||
        self.menuFile = QtWidgets.QMenu('File', self.menubar)
 | 
			
		||||
        self.menubar.addMenu(self.menuFile)
 | 
			
		||||
 | 
			
		||||
        self.menuFile.addAction('Open...', self.open_file, QtGui.QKeySequence.Open)
 | 
			
		||||
        self.menuFile.addAction('Save', self.overwrite_file, QtGui.QKeySequence.Save)
 | 
			
		||||
        self.menuFile.addAction('Save as...', self.save_file, QtGui.QKeySequence('Ctrl+Shift+S'))
 | 
			
		||||
        self.menuFile.addSeparator()
 | 
			
		||||
        self.menuFile.addAction('Close', self.close, QtGui.QKeySequence.Quit)
 | 
			
		||||
 | 
			
		||||
        self.resize(800, 600)
 | 
			
		||||
        self.setGeometry(QtWidgets.QStyle.alignedRect(
 | 
			
		||||
            QtCore.Qt.LeftToRight, QtCore.Qt.AlignCenter,
 | 
			
		||||
            self.size(), QtWidgets.qApp.desktop().availableGeometry()
 | 
			
		||||
        ))
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def is_modified(self):
 | 
			
		||||
        return self.edit_field.document().isModified()
 | 
			
		||||
 | 
			
		||||
    @is_modified.setter
 | 
			
		||||
    def is_modified(self, val: bool):
 | 
			
		||||
        self.edit_field.document().setModified(val)
 | 
			
		||||
 | 
			
		||||
    @QtCore.pyqtSlot()
 | 
			
		||||
    def open_file(self):
 | 
			
		||||
        overwrite = self.changes_saved
 | 
			
		||||
 | 
			
		||||
        if overwrite:
 | 
			
		||||
            fname, _ = QtWidgets.QFileDialog.getOpenFileName(directory=str(self._dir))
 | 
			
		||||
            if fname:
 | 
			
		||||
                self.read_file(fname)
 | 
			
		||||
 | 
			
		||||
    def read_file(self, fname: str | Path):
 | 
			
		||||
        self.set_fname_opts(fname)
 | 
			
		||||
 | 
			
		||||
        with self.fname.open('r') as f:
 | 
			
		||||
            self.edit_field.setPlainText(f.read())
 | 
			
		||||
 | 
			
		||||
    def set_fname_opts(self, fname: str | Path):
 | 
			
		||||
        self.fname = Path(fname)
 | 
			
		||||
        self._dir = self.fname.parent
 | 
			
		||||
        self.setWindowTitle('Edit ' + str(fname))
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def changes_saved(self) -> bool:
 | 
			
		||||
        if not self.is_modified:
 | 
			
		||||
            return True
 | 
			
		||||
 | 
			
		||||
        ret = QtWidgets.QMessageBox.question(self, 'Time to think',
 | 
			
		||||
                                             '<h4><p>The document was modified.</p>\n'
 | 
			
		||||
                                             '<p>Do you want to save changes?</p></h4>',
 | 
			
		||||
                                             QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No |
 | 
			
		||||
                                             QtWidgets.QMessageBox.Cancel)
 | 
			
		||||
        if ret == QtWidgets.QMessageBox.Yes:
 | 
			
		||||
            self.save_file()
 | 
			
		||||
 | 
			
		||||
        if ret == QtWidgets.QMessageBox.No:
 | 
			
		||||
            self.is_modified = False
 | 
			
		||||
 | 
			
		||||
        return not self.is_modified
 | 
			
		||||
 | 
			
		||||
    @QtCore.pyqtSlot()
 | 
			
		||||
    def save_file(self):
 | 
			
		||||
        outfile, _ = QtWidgets.QFileDialog().getSaveFileName(parent=self, caption='Save file', directory=str(self._dir))
 | 
			
		||||
 | 
			
		||||
        if outfile:
 | 
			
		||||
            with open(outfile, 'w') as f:
 | 
			
		||||
                f.write(self.edit_field.toPlainText())
 | 
			
		||||
 | 
			
		||||
            self.set_fname_opts(outfile)
 | 
			
		||||
 | 
			
		||||
            self.is_modified = False
 | 
			
		||||
 | 
			
		||||
        return self.is_modified
 | 
			
		||||
 | 
			
		||||
    @QtCore.pyqtSlot()
 | 
			
		||||
    def overwrite_file(self):
 | 
			
		||||
        if self.fname is not None:
 | 
			
		||||
            with self.fname.open('w') as f:
 | 
			
		||||
                f.write(self.edit_field.toPlainText())
 | 
			
		||||
 | 
			
		||||
        self.modelsChanged.emit()
 | 
			
		||||
 | 
			
		||||
        self.is_modified = False
 | 
			
		||||
 | 
			
		||||
    def closeEvent(self, evt: QtGui.QCloseEvent):
 | 
			
		||||
        if not self.changes_saved:
 | 
			
		||||
            evt.ignore()
 | 
			
		||||
        else:
 | 
			
		||||
            super().closeEvent(evt)
 | 
			
		||||
							
								
								
									
										50
									
								
								src/gui_qt/lib/utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/gui_qt/lib/utils.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,50 @@
 | 
			
		||||
from contextlib import contextmanager
 | 
			
		||||
from numpy import linspace
 | 
			
		||||
from scipy.interpolate import interp1d
 | 
			
		||||
 | 
			
		||||
from ..Qt import QtGui, QtWidgets, QtCore
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@contextmanager
 | 
			
		||||
def busy_cursor():
 | 
			
		||||
    try:
 | 
			
		||||
        cursor = QtGui.QCursor(QtCore.Qt.ForbiddenCursor)
 | 
			
		||||
        QtWidgets.QApplication.setOverrideCursor(cursor)
 | 
			
		||||
        yield
 | 
			
		||||
 | 
			
		||||
    finally:
 | 
			
		||||
        QtWidgets.QApplication.restoreOverrideCursor()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RdBuCMap:
 | 
			
		||||
    # taken from Excel sheet from colorbrewer.org
 | 
			
		||||
    _rdbu = [
 | 
			
		||||
        (103, 0, 31),
 | 
			
		||||
        (178, 24, 43),
 | 
			
		||||
        (214, 96, 77),
 | 
			
		||||
        (244, 165, 130),
 | 
			
		||||
        (253, 219, 199),
 | 
			
		||||
        (247, 247, 247),
 | 
			
		||||
        (209, 229, 240),
 | 
			
		||||
        (146, 197, 222),
 | 
			
		||||
        (67, 147, 195),
 | 
			
		||||
        (33, 102, 172),
 | 
			
		||||
        (5, 48, 97)
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    def __init__(self, vmin=-1., vmax=1.):
 | 
			
		||||
        self.min = vmin
 | 
			
		||||
        self.max = vmax
 | 
			
		||||
 | 
			
		||||
        self.spline = [interp1d(linspace(self.max, self.min, num=11), [rgb[i] for rgb in RdBuCMap._rdbu])
 | 
			
		||||
                       for i in range(3)]
 | 
			
		||||
 | 
			
		||||
    def color(self, val: float):
 | 
			
		||||
        if val > self.max:
 | 
			
		||||
            col = QtGui.QColor.fromRgb(*RdBuCMap._rdbu[0])
 | 
			
		||||
        elif val < self.min:
 | 
			
		||||
            col = QtGui.QColor.fromRgb(*RdBuCMap._rdbu[-1])
 | 
			
		||||
        else:
 | 
			
		||||
            col = QtGui.QColor.fromRgb(*(float(self.spline[i](val)) for i in range(3)))
 | 
			
		||||
 | 
			
		||||
        return col
 | 
			
		||||
		Reference in New Issue
	
	Block a user