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