Example #1
0
class NumpyArrayDialog(QDialog):
    def __init__(self, parent=None, inline=True, offset=0, force_float=False):
        QDialog.__init__(self, parent=parent)
        self._parent = parent
        self._text = None
        self._valid = None
        self._offset = offset

        # TODO: add this as an option in the General Preferences?
        self._force_float = force_float

        self._help_inline = _("""
           <b>Numpy Array/Matrix Helper</b><br>
           Type an array in Matlab    : <code>[1 2;3 4]</code><br>
           or Spyder simplified syntax : <code>1 2;3 4</code>
           <br><br>
           Hit 'Enter' for array or 'Ctrl+Enter' for matrix.
           <br><br>
           <b>Hint:</b><br>
           Use two spaces or two tabs to generate a ';'.
           """)

        self._help_table = _("""
           <b>Numpy Array/Matrix Helper</b><br>
           Enter an array in the table. <br>
           Use Tab to move between cells.
           <br><br>
           Hit 'Enter' for array or 'Ctrl+Enter' for matrix.
           <br><br>
           <b>Hint:</b><br>
           Use two tabs at the end of a row to move to the next row.
           """)

        # Widgets
        self._button_warning = QToolButton()
        self._button_help = HelperToolButton()
        self._button_help.setIcon(ima.icon('MessageBoxInformation'))

        style = """
            QToolButton {
              border: 1px solid grey;
              padding:0px;
              border-radius: 2px;
              background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
                  stop: 0 #f6f7fa, stop: 1 #dadbde);
            }
            """
        self._button_help.setStyleSheet(style)

        if inline:
            self._button_help.setToolTip(self._help_inline)
            self._text = NumpyArrayInline(self)
            self._widget = self._text
        else:
            self._button_help.setToolTip(self._help_table)
            self._table = NumpyArrayTable(self)
            self._widget = self._table

        style = """
            QDialog {
              margin:0px;
              border: 1px solid grey;
              padding:0px;
              border-radius: 2px;
            }"""
        self.setStyleSheet(style)

        style = """
            QToolButton {
              margin:1px;
              border: 0px solid grey;
              padding:0px;
              border-radius: 0px;
            }"""
        self._button_warning.setStyleSheet(style)

        # widget setup
        self.setWindowFlags(Qt.Window | Qt.Dialog | Qt.FramelessWindowHint)
        self.setModal(True)
        self.setWindowOpacity(0.90)
        self._widget.setMinimumWidth(200)

        # layout
        self._layout = QHBoxLayout()
        self._layout.addWidget(self._widget)
        self._layout.addWidget(self._button_warning, 1, Qt.AlignTop)
        self._layout.addWidget(self._button_help, 1, Qt.AlignTop)
        self.setLayout(self._layout)

        self._widget.setFocus()

    def keyPressEvent(self, event):
        """
        Qt override.
        """
        QToolTip.hideText()
        ctrl = event.modifiers() & Qt.ControlModifier

        if event.key() in [Qt.Key_Enter, Qt.Key_Return]:
            if ctrl:
                self.process_text(array=False)
            else:
                self.process_text(array=True)
            self.accept()
        else:
            QDialog.keyPressEvent(self, event)

    def event(self, event):
        """
        Qt Override.

        Usefull when in line edit mode.
        """
        if event.type() == QEvent.KeyPress and event.key() == Qt.Key_Tab:
            return False
        return QWidget.event(self, event)

    def process_text(self, array=True):
        """
        Construct the text based on the entered content in the widget.
        """
        if array:
            prefix = 'np.array([['
        else:
            prefix = 'np.matrix([['

        suffix = ']])'
        values = self._widget.text().strip()

        if values != '':
            # cleans repeated spaces
            exp = r'(\s*)' + ROW_SEPARATOR + r'(\s*)'
            values = re.sub(exp, ROW_SEPARATOR, values)
            values = re.sub("\s+", " ", values)
            values = re.sub("]$", "", values)
            values = re.sub("^\[", "", values)
            values = re.sub(ROW_SEPARATOR + r'*$', '', values)

            # replaces spaces by commas
            values = values.replace(' ',  ELEMENT_SEPARATOR)

            # iterate to find number of rows and columns
            new_values = []
            rows = values.split(ROW_SEPARATOR)
            nrows = len(rows)
            ncols = []
            for row in rows:
                new_row = []
                elements = row.split(ELEMENT_SEPARATOR)
                ncols.append(len(elements))
                for e in elements:
                    num = e

                    # replaces not defined values
                    if num in NAN_VALUES:
                        num = 'np.nan'

                    # Convert numbers to floating point
                    if self._force_float:
                        try:
                            num = str(float(e))
                        except:
                            pass
                    new_row.append(num)
                new_values.append(ELEMENT_SEPARATOR.join(new_row))
            new_values = ROW_SEPARATOR.join(new_values)
            values = new_values

            # Check validity
            if len(set(ncols)) == 1:
                self._valid = True
            else:
                self._valid = False

            # Single rows are parsed as 1D arrays/matrices
            if nrows == 1:
                prefix = prefix[:-1]
                suffix = suffix.replace("]])", "])")

            # Fix offset
            offset = self._offset
            braces = BRACES.replace(' ', '\n' + ' '*(offset + len(prefix) - 1))

            values = values.replace(ROW_SEPARATOR,  braces)
            text = "{0}{1}{2}".format(prefix, values, suffix)

            self._text = text
        else:
            self._text = ''
        self.update_warning()

    def update_warning(self):
        """
        Updates the icon and tip based on the validity of the array content.
        """
        widget = self._button_warning
        if not self.is_valid():
            tip = _('Array dimensions not valid')
            widget.setIcon(ima.icon('MessageBoxWarning'))
            widget.setToolTip(tip)
            QToolTip.showText(self._widget.mapToGlobal(QPoint(0, 5)), tip)
        else:
            self._button_warning.setToolTip('')

    def is_valid(self):
        """
        Return if the current array state is valid.
        """
        return self._valid

    def text(self):
        """
        Return the parsed array/matrix text.
        """
        return self._text

    @property
    def array_widget(self):
        """
        Return the array builder widget.
        """
        return self._widget
Example #2
0
class ShortcutEditor(QDialog):
    """A dialog for entering key sequences."""
    def __init__(self, parent, context, name, sequence, shortcuts):
        super(ShortcutEditor, self).__init__(parent)
        self._parent = parent

        self.context = context
        self.npressed = 0
        self.keys = set()
        self.key_modifiers = set()
        self.key_non_modifiers = list()
        self.key_text = list()
        self.sequence = sequence
        self.new_sequence = None
        self.edit_state = True
        self.shortcuts = shortcuts

        # Widgets
        self.label_info = QLabel()
        self.label_info.setText(_("Press the new shortcut and select 'Ok': \n"
             "(Press 'Tab' once to switch focus between the shortcut entry \n"
             "and the buttons below it)"))
        self.label_current_sequence = QLabel(_("Current shortcut:"))
        self.text_current_sequence = QLabel(sequence)
        self.label_new_sequence = QLabel(_("New shortcut:"))
        self.text_new_sequence = CustomLineEdit(self)
        self.text_new_sequence.setPlaceholderText(sequence)
        self.helper_button = HelperToolButton()
        self.helper_button.hide()
        self.label_warning = QLabel()
        self.label_warning.hide()

        bbox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        self.button_ok = bbox.button(QDialogButtonBox.Ok)
        self.button_cancel = bbox.button(QDialogButtonBox.Cancel)

        # Setup widgets
        self.setWindowTitle(_('Shortcut: {0}').format(name))
        self.button_ok.setFocusPolicy(Qt.NoFocus)
        self.button_ok.setEnabled(False)
        self.button_cancel.setFocusPolicy(Qt.NoFocus)
        self.helper_button.setToolTip('')
        self.helper_button.setFocusPolicy(Qt.NoFocus)
        style = """
            QToolButton {
              margin:1px;
              border: 0px solid grey;
              padding:0px;
              border-radius: 0px;
            }"""
        self.helper_button.setStyleSheet(style)
        self.text_new_sequence.setFocusPolicy(Qt.NoFocus)
        self.label_warning.setFocusPolicy(Qt.NoFocus)

        # Layout
        spacing = 5
        layout_sequence = QGridLayout()
        layout_sequence.addWidget(self.label_info, 0, 0, 1, 3)
        layout_sequence.addItem(QSpacerItem(spacing, spacing), 1, 0, 1, 2)
        layout_sequence.addWidget(self.label_current_sequence, 2, 0)
        layout_sequence.addWidget(self.text_current_sequence, 2, 2)
        layout_sequence.addWidget(self.label_new_sequence, 3, 0)
        layout_sequence.addWidget(self.helper_button, 3, 1)
        layout_sequence.addWidget(self.text_new_sequence, 3, 2)
        layout_sequence.addWidget(self.label_warning, 4, 2, 1, 2)

        layout = QVBoxLayout()
        layout.addLayout(layout_sequence)
        layout.addSpacing(spacing)
        layout.addWidget(bbox)
        self.setLayout(layout)

        # Signals
        bbox.accepted.connect(self.accept)
        bbox.rejected.connect(self.reject)

    def keyPressEvent(self, e):
        """Qt override."""
        key = e.key()
        # Check if valid keys
        if key not in VALID_KEYS:
            self.invalid_key_flag = True
            return

        self.npressed += 1
        self.key_non_modifiers.append(key)
        self.key_modifiers.add(key)
        self.key_text.append(e.text())
        self.invalid_key_flag = False

        debug_print('key {0}, npressed: {1}'.format(key, self.npressed))

        if key == Qt.Key_unknown:
            return

        # The user clicked just and only the special keys
        # Ctrl, Shift, Alt, Meta.
        if (key == Qt.Key_Control or
                key == Qt.Key_Shift or
                key == Qt.Key_Alt or
                key == Qt.Key_Meta):
            return

        modifiers = e.modifiers()
        if modifiers & Qt.ShiftModifier:
            key += Qt.SHIFT
        if modifiers & Qt.ControlModifier:
            key += Qt.CTRL
            if sys.platform == 'darwin':
                self.npressed -= 1
            debug_print('decrementing')
        if modifiers & Qt.AltModifier:
            key += Qt.ALT
        if modifiers & Qt.MetaModifier:
            key += Qt.META

        self.keys.add(key)

    def toggle_state(self):
        """Switch between shortcut entry and Accept/Cancel shortcut mode."""
        self.edit_state = not self.edit_state

        if not self.edit_state:
            self.text_new_sequence.setEnabled(False)
            if self.button_ok.isEnabled():
                self.button_ok.setFocus()
            else:
                self.button_cancel.setFocus()
        else:
            self.text_new_sequence.setEnabled(True)
            self.text_new_sequence.setFocus()

    def nonedit_keyrelease(self, e):
        """Key release event for non-edit state."""
        key = e.key()
        if key in [Qt.Key_Escape]:
            self.close()
            return

        if key in [Qt.Key_Left, Qt.Key_Right, Qt.Key_Up,
                   Qt.Key_Down]:
            if self.button_ok.hasFocus():
                self.button_cancel.setFocus()
            else:
                self.button_ok.setFocus()

    def keyReleaseEvent(self, e):
        """Qt override."""
        self.npressed -= 1
        if self.npressed <= 0:
            key = e.key()

            if len(self.keys) == 1 and key == Qt.Key_Tab:
                self.toggle_state()
                return

            if len(self.keys) == 1 and key == Qt.Key_Escape:
                self.set_sequence('')
                self.label_warning.setText(_("Please introduce a different "
                                             "shortcut"))

            if len(self.keys) == 1 and key in [Qt.Key_Return, Qt.Key_Enter]:
                self.toggle_state()
                return

            if not self.edit_state:
                self.nonedit_keyrelease(e)
            else:
                debug_print('keys: {}'.format(self.keys))
                if self.keys and key != Qt.Key_Escape:
                    self.validate_sequence()
                self.keys = set()
                self.key_modifiers = set()
                self.key_non_modifiers = list()
                self.key_text = list()
                self.npressed = 0

    def check_conflicts(self):
        """Check shortcuts for conflicts."""
        conflicts = []
        for index, shortcut in enumerate(self.shortcuts):
            sequence = str(shortcut.key)
            if sequence == self.new_sequence and \
                (shortcut.context == self.context or shortcut.context == '_' or
                 self.context == '_'):
                conflicts.append(shortcut)
        return conflicts

    def update_warning(self, warning_type=NO_WARNING, conflicts=[]):
        """Update warning label to reflect conflict status of new shortcut"""
        if warning_type == NO_WARNING:
            warn = False
            tip = 'This shortcut is correct!'
        elif warning_type == SEQUENCE_CONFLICT:
            template = '<i>{0}<b>{1}</b></i>'
            tip_title = _('The new shorcut conflicts with:') + '<br>'
            tip_body = ''
            for s in conflicts:
                tip_body += ' - {0}: {1}<br>'.format(s.context, s.name)
            tip_body = tip_body[:-4]  # Removing last <br>
            tip = template.format(tip_title, tip_body)
            warn = True
        elif warning_type == SEQUENCE_LENGTH:
            # Sequences with 5 keysequences (i.e. Ctrl+1, Ctrl+2, Ctrl+3,
            # Ctrl+4, Ctrl+5) are invalid
            template = '<i>{0}</i>'
            tip = _('A compound sequence can have {break} a maximum of '
                    '4 subsequences.{break}').format(**{'break': '<br>'})
            warn = True
        elif warning_type == INVALID_KEY:
            template = '<i>{0}</i>'
            tip = _('Invalid key entered') + '<br>'
            warn = True

        self.helper_button.show()
        if warn:
            self.label_warning.show()
            self.helper_button.setIcon(get_std_icon('MessageBoxWarning'))
            self.button_ok.setEnabled(False)
        else:
            self.helper_button.setIcon(get_std_icon('DialogApplyButton'))

        self.label_warning.setText(tip)

    def set_sequence(self, sequence):
        """Set the new shortcut and update buttons."""
        if not sequence or self.sequence == sequence:
            self.button_ok.setEnabled(False)
            different_sequence = False
        else:
            self.button_ok.setEnabled(True)
            different_sequence = True

        self.text_new_sequence.setText(sequence)
        self.new_sequence = sequence

        conflicts = self.check_conflicts()
        if conflicts and different_sequence:
            warning_type = SEQUENCE_CONFLICT
        else:
            warning_type = NO_WARNING

        self.update_warning(warning_type=warning_type, conflicts=conflicts)

    def validate_sequence(self):
        """Provide additional checks for accepting or rejecting shortcuts."""
        if self.invalid_key_flag:
            self.update_warning(warning_type=INVALID_KEY)
            return

        for mod in MODIFIERS:
            non_mod = set(self.key_non_modifiers)
            non_mod.discard(mod)
            if mod in self.key_non_modifiers:
                self.key_non_modifiers.remove(mod)

        self.key_modifiers = self.key_modifiers - non_mod

        while u'' in self.key_text:
            self.key_text.remove(u'')

        self.key_text = [k.upper() for k in self.key_text]

        # Fix Backtab, Tab issue
        if os.name == 'nt':
            if Qt.Key_Backtab in self.key_non_modifiers:
                idx = self.key_non_modifiers.index(Qt.Key_Backtab)
                self.key_non_modifiers[idx] = Qt.Key_Tab

        if len(self.key_modifiers) == 0:
            # Filter single key allowed
            if self.key_non_modifiers[0] not in VALID_SINGLE_KEYS:
                return
            # Filter
            elif len(self.key_non_modifiers) > 1:
                return

        # QKeySequence accepts a maximum of 4 different sequences
        if len(self.keys) > 4:
            # Update warning
            self.update_warning(warning_type=SEQUENCE_LENGTH)
            return

        keys = []
        for i in range(len(self.keys)):
            key_seq = 0
            for m in self.key_modifiers:
                key_seq += MODIFIERS[m]
            key_seq += self.key_non_modifiers[i]
            keys.append(key_seq)

        sequence = QKeySequence(*keys)

        self.set_sequence(sequence.toString())
Example #3
0
class NumpyArrayDialog(QDialog):
    """ """
    def __init__(self, parent, inline=True, offset=0):
        QDialog.__init__(self, parent)
        self._parent = parent
        self._text = None
        self._valid = None
        self._offset = offset

        # TODO: add this as an option in the General Preferences?
        self._force_float = False

        self._help_inline = _("""
           <b>Numpy Array/Matrix Helper</b><br>
           Type an array in Matlab    : <code>[1 2;3 4]</code><br>
           or Spyder simplified syntax : <code>1 2;3 4</code>
           <br><br>
           Hit 'Enter' for array or 'Ctrl+Enter' for matrix.
           <br><br>
           <b>Hint:</b><br>
           Use two spaces or two tabs to generate a ';'.
           """)

        self._help_table = _("""
           <b>Numpy Array/Matrix Helper</b><br>
           Enter an array in the table. <br>
           Use Tab to move between cells.
           <br><br>
           Hit 'Enter' for array or 'Ctrl+Enter' for matrix.
           <br><br>
           <b>Hint:</b><br>
           Use two tabs at the end of a row to move to the next row.
           """)

        # widgets
        self._button_warning = QToolButton()
        self._button_help = HelperToolButton()
        self._button_help.setIcon(ima.icon('MessageBoxInformation'))

        style = """
            QToolButton {
              border: 1px solid grey;
              padding:0px;
              border-radius: 2px;
              background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
                  stop: 0 #f6f7fa, stop: 1 #dadbde);
            }
            """
        self._button_help.setStyleSheet(style)

        if inline:
            self._button_help.setToolTip(self._help_inline)
            self._text = NumpyArrayInline(self)
            self._widget = self._text
        else:
            self._button_help.setToolTip(self._help_table)
            self._table = NumpyArrayTable(self)
            self._widget = self._table

        style = """
            QDialog {
              margin:0px;
              border: 1px solid grey;
              padding:0px;
              border-radius: 2px;
            }"""
        self.setStyleSheet(style)

        style = """
            QToolButton {
              margin:1px;
              border: 0px solid grey;
              padding:0px;
              border-radius: 0px;
            }"""
        self._button_warning.setStyleSheet(style)

        # widget setup
        self.setWindowFlags(Qt.Window | Qt.Dialog | Qt.FramelessWindowHint)
        self.setModal(True)
        self.setWindowOpacity(0.90)
        self._widget.setMinimumWidth(200)

        # layout
        self._layout = QHBoxLayout()
        self._layout.addWidget(self._widget)
        self._layout.addWidget(self._button_warning, 1, Qt.AlignTop)
        self._layout.addWidget(self._button_help, 1, Qt.AlignTop)
        self.setLayout(self._layout)

        self._widget.setFocus()

    def keyPressEvent(self, event):
        """Override Qt method"""
        QToolTip.hideText()
        ctrl = event.modifiers() & Qt.ControlModifier

        if event.key() in [Qt.Key_Enter, Qt.Key_Return]:
            if ctrl:
                self.process_text(array=False)
            else:
                self.process_text(array=True)
            self.accept()
        else:
            QDialog.keyPressEvent(self, event)

    def event(self, event):
        if event.type() == QEvent.KeyPress and event.key() == Qt.Key_Tab:
            return False
        return QWidget.event(self, event)

    def process_text(self, array=True):
        """ """
        if array:
            prefix = 'np.array([['
        else:
            prefix = 'np.matrix([['

        suffix = ']])'
        values = self._widget.text().strip()

        if values != '':
            # cleans repeated spaces
            exp = r'(\s*)' + ROW_SEPARATOR + r'(\s*)'
            values = re.sub(exp, ROW_SEPARATOR, values)
            values = re.sub("\s+", " ", values)
            values = re.sub("]$", "", values)
            values = re.sub("^\[", "", values)
            values = re.sub(ROW_SEPARATOR + r'*$', '', values)

            # replaces spaces by commas
            values = values.replace(' ', ELEMENT_SEPARATOR)

            # iterate to find number of rows and columns
            new_values = []
            rows = values.split(ROW_SEPARATOR)
            nrows = len(rows)
            ncols = []
            for row in rows:
                new_row = []
                elements = row.split(ELEMENT_SEPARATOR)
                ncols.append(len(elements))
                for e in elements:
                    num = e

                    # replaces not defined values
                    if num in NAN_VALUES:
                        num = 'np.nan'

                    # Convert numbers to floating point
                    if self._force_float:
                        try:
                            num = str(float(e))
                        except:
                            pass
                    new_row.append(num)
                new_values.append(ELEMENT_SEPARATOR.join(new_row))
            new_values = ROW_SEPARATOR.join(new_values)
            values = new_values

            # Check validity
            if len(set(ncols)) == 1:
                self._valid = True
            else:
                self._valid = False

            # Single rows are parsed as 1D arrays/matrices
            if nrows == 1:
                prefix = prefix[:-1]
                suffix = suffix.replace("]])", "])")

            # Fix offset
            offset = self._offset
            braces = BRACES.replace(' ',
                                    '\n' + ' ' * (offset + len(prefix) - 1))

            values = values.replace(ROW_SEPARATOR, braces)
            text = "{0}{1}{2}".format(prefix, values, suffix)

            self._text = text
        else:
            self._text = ''
        self.update_warning()

    def update_warning(self):
        """ """
        widget = self._button_warning
        if not self.is_valid():
            tip = _('Array dimensions not valid')
            widget.setIcon(ima.icon('MessageBoxWarning'))
            widget.setToolTip(tip)
            QToolTip.showText(self._widget.mapToGlobal(QPoint(0, 5)), tip)
        else:
            self._button_warning.setToolTip('')

    def is_valid(self):
        """ """
        return self._valid

    def text(self):
        """ """
        return self._text

    def mousePressEvent(self, event):
        """ """
        pass