class DetectDialog(*ui_common.inherits_from_ui_file_with_name("detectdialog")): """ The DetectDialog lets the user select window properties of a chosen window. The dialog shows the window title and window class of the chosen window and lets the user select one of those two options. """ def __init__(self, parent: QWidget): super(DetectDialog, self).__init__(parent) self.setupUi(self) self.window_title = "" self.window_class = "" def populate(self, window_info: Tuple[str, str]): self.window_title, self.window_class = window_info self.detected_title.setText(self.window_title) self.detected_class.setText(self.window_class) logger.info( "Detected window with properties title: {}, window class: {}". format(self.window_title, self.window_class)) def get_choice(self) -> str: # This relies on autoExclusive being set to true in the ui file. if self.classButton.isChecked(): logger.debug("User has chosen the window class: {}".format( self.window_class)) return self.window_class else: logger.debug("User has chosen the window title: {}".format( self.window_title)) return self.window_title
class RecordDialog(*ui_common.inherits_from_ui_file_with_name("record_dialog") ): def __init__(self, parent, closure): super().__init__(parent) self.setupUi(self) self.closure = closure def get_record_keyboard(self): return self.record_keyboard_button.isChecked() def get_record_mouse(self): return self.record_mouse_button.isChecked() def get_delay(self): return self.delay_recording_start_seconds_spin_box.value() def accept(self): super().accept() logger.info( "Dialog accepted: Record keyboard: {}, record mouse: {}, delay: {} s" .format(self.get_record_keyboard(), self.get_record_mouse(), self.get_delay())) self.closure(True, self.get_record_keyboard(), self.get_record_mouse(), self.get_delay()) def reject(self): super().reject() logger.info( "Dialog closed (rejected/aborted): Record keyboard: {}, record mouse: {}, delay: {} s" .format(self.get_record_keyboard(), self.get_record_mouse(), self.get_delay())) self.closure(False, self.get_record_keyboard(), self.get_record_mouse(), self.get_delay())
class SettingsDialog(*common.inherits_from_ui_file_with_name("settingsdialog")): def __init__(self, parent: QWidget=None): super(SettingsDialog, self).__init__(parent) self.setupUi(self) logger.info("Settings dialog window created.") @pyqtSlot() # Avoid the slot being called twice, by both signals clicked() and clicked(bool). def on_show_general_settings_button_clicked(self): logger.debug("User views general settings") self.settings_pages.setCurrentWidget(self.general_settings_page) @pyqtSlot() # Avoid the slot being called twice, by both signals clicked() and clicked(bool). def on_show_special_hotkeys_button_clicked(self): logger.debug("User views special hotkeys settings") self.settings_pages.setCurrentWidget(self.special_hotkeys_page) @pyqtSlot() # Avoid the slot being called twice, by both signals clicked() and clicked(bool). def on_show_script_engine_button_clicked(self): logger.debug("User views script engine settings") self.settings_pages.setCurrentWidget(self.script_engine_page) def accept(self): logger.info("User requested to save the settings.") app = QApplication.instance() # type: Application self.general_settings_page.save() self.special_hotkeys_page.save() self.script_engine_page.save() app.configManager.config_altered(True) app.update_notifier_visibility() app.notifier.reset_tray_icon() super(SettingsDialog, self).accept() logger.debug("Save completed, dialog window hidden.")
class FolderPage(*ui_common.inherits_from_ui_file_with_name("folderpage")): def __init__(self): super(FolderPage, self).__init__() self.setupUi(self) self.current_folder = None # type: Folder def load(self, folder: Folder): self.current_folder = folder self.showInTrayCheckbox.setChecked(folder.show_in_tray_menu) self.settingsWidget.load(folder) if self.is_new_item(): self.urlLabel.setEnabled(False) self.urlLabel.setText("(Unsaved)") # TODO: i18n else: ui_common.set_url_label(self.urlLabel, self.current_folder.path) def save(self): self.current_folder.show_in_tray_menu = self.showInTrayCheckbox.isChecked() self.settingsWidget.save() self.current_folder.persist() ui_common.set_url_label(self.urlLabel, self.current_folder.path) return not self.current_folder.path.startswith(cm.CONFIG_DEFAULT_FOLDER) def set_item_title(self, title: str): self.current_folder.title = title def rebuild_item_path(self): self.current_folder.rebuild_path() def is_new_item(self): return self.current_folder.path is None def reset(self): self.load(self.current_folder) def validate(self): # Check settings errors = self.settingsWidget.validate() if errors: msg = PROBLEM_MSG_SECONDARY.format('\n'.join([str(e) for e in errors])) QMessageBox.critical(self.window(), PROBLEM_MSG_PRIMARY, msg) return not bool(errors) def set_dirty(self): self.window().set_dirty() # --- Signal handlers def on_showInTrayCheckbox_stateChanged(self, state: bool): self.set_dirty() @staticmethod def on_urlLabel_leftClickedUrl(url: str=None): if url: subprocess.Popen(["/usr/bin/xdg-open", url])
class AboutAutokeyDialog(*ui_common.inherits_from_ui_file_with_name("about_autokey_dialog")): def __init__(self, parent: QWidget = None): super(AboutAutokeyDialog, self).__init__(parent) self.setupUi(self) icon = ui_common.load_icon(ui_common.AutoKeyIcon.AUTOKEY) pixmap = icon.pixmap(icon.actualSize(QSize(1024, 1024))) self.autokey_icon.setPixmap(pixmap) self.autokey_version_label.setText(autokey.common.VERSION)
class HotkeySettingsDialog( *ui_common.inherits_from_ui_file_with_name("hotkeysettings")): KEY_MAP = { ' ': "<space>", } REVERSE_KEY_MAP = {value: key for key, value in KEY_MAP.items()} DEFAULT_RECORDED_KEY_LABEL_CONTENT = "(None)" """ This signal is emitted whenever the key is assigned/deleted. This happens when the user records a key or cancels a key recording. """ key_assigned = pyqtSignal(bool, name="key_assigned") recording_finished = pyqtSignal(bool, name="recording_finished") def __init__(self, parent): super(HotkeySettingsDialog, self).__init__(parent) self.setupUi(self) # Enable the Ok button iff a correct key combination is assigned. This guides the user and obsoletes an error # message that was shown when the user did something invalid. self.key_assigned.connect( self.buttonBox.button(QDialogButtonBox.Ok).setEnabled) self.recording_finished.connect( self.record_combination_button.setEnabled) self.key = "" self._update_key( None ) # Use _update_key to emit the key_assigned signal and disable the Ok button. self.target_item = None # type: Item self.grabber = None # type: iomediator.KeyGrabber def _update_key(self, key): self.key = key if key is None: self.recorded_key_label.setText("Key: {}".format( self.DEFAULT_RECORDED_KEY_LABEL_CONTENT)) # TODO: i18n self.key_assigned.emit(False) else: self.recorded_key_label.setText( "Key: {}".format(key)) # TODO: i18n self.key_assigned.emit(True) def on_record_combination_button_pressed(self): """ Start recording a key combination when the user clicks on the record_combination_button. The button itself is automatically disabled during the recording process. """ self.recorded_key_label.setText( "Press a key or combination...") # TODO: i18n logger.debug("User starts to record a key combination.") self.grabber = iomediator.KeyGrabber(self) self.grabber.start() def load(self, item: Item): self.target_item = item if model.TriggerMode.HOTKEY in item.modes: self.mod_control_button.setChecked(Key.CONTROL in item.modifiers) self.mod_alt_button.setChecked(Key.ALT in item.modifiers) self.mod_shift_button.setChecked(Key.SHIFT in item.modifiers) self.mod_super_button.setChecked(Key.SUPER in item.modifiers) self.mod_hyper_button.setChecked(Key.HYPER in item.modifiers) self.mod_meta_button.setChecked(Key.META in item.modifiers) key = item.hotKey if key in self.KEY_MAP: key_text = self.KEY_MAP[key] else: key_text = key self._update_key(key_text) logger.debug("Loaded item {}, key: {}, modifiers: {}".format( item, key_text, item.modifiers)) else: self.reset() def save(self, item): item.modes.append(model.TriggerMode.HOTKEY) # Build modifier list modifiers = self.build_modifiers() if self.key in self.REVERSE_KEY_MAP: key = self.REVERSE_KEY_MAP[self.key] else: key = self.key if key is None: raise RuntimeError("Attempt to set hotkey with no key") logger.info("Item {} updated with hotkey {} and modifiers {}".format( item, key, modifiers)) item.set_hotkey(modifiers, key) def reset(self): self.mod_control_button.setChecked(False) self.mod_alt_button.setChecked(False) self.mod_shift_button.setChecked(False) self.mod_super_button.setChecked(False) self.mod_hyper_button.setChecked(False) self.mod_meta_button.setChecked(False) self._update_key(None) def set_key(self, key, modifiers: typing.List[Key] = None): """This is called when the user successfully finishes recording a key combination.""" if modifiers is None: modifiers = [] # type: typing.List[Key] if key in self.KEY_MAP: key = self.KEY_MAP[key] self._update_key(key) self.mod_control_button.setChecked(Key.CONTROL in modifiers) self.mod_alt_button.setChecked(Key.ALT in modifiers) self.mod_shift_button.setChecked(Key.SHIFT in modifiers) self.mod_super_button.setChecked(Key.SUPER in modifiers) self.mod_hyper_button.setChecked(Key.HYPER in modifiers) self.mod_meta_button.setChecked(Key.META in modifiers) self.recording_finished.emit(True) def cancel_grab(self): """ This is called when the user cancels a recording. Canceling is done by clicking with the left mouse button. """ logger.debug("User canceled hotkey recording.") self.recording_finished.emit(True) def build_modifiers(self): modifiers = [] if self.mod_control_button.isChecked(): modifiers.append(Key.CONTROL) if self.mod_alt_button.isChecked(): modifiers.append(Key.ALT) if self.mod_shift_button.isChecked(): modifiers.append(Key.SHIFT) if self.mod_super_button.isChecked(): modifiers.append(Key.SUPER) if self.mod_hyper_button.isChecked(): modifiers.append(Key.HYPER) if self.mod_meta_button.isChecked(): modifiers.append(Key.META) modifiers.sort() return modifiers def reject(self): self.load(self.target_item) super().reject()
class EngineSettings(*common.inherits_from_ui_file_with_name("enginesettings") ): """ The EngineSettings class is used inside the AutoKey configuration dialog. It allows the user to select and add a custom Python module search path entry. """ def __init__(self, parent: QWidget = None): super(EngineSettings, self).__init__(parent) self.setupUi(self) # Save the path label text stored in the Qt UI file. # It is used to reset the label to this value if a custom module path is currently set and the user deletes it. # Do not hard-code it to prevent possible inconsistencies. self.initial_folder_label_text = self.folder_label.text() self.config_manager = QApplication.instance().configManager self.path = self.config_manager.userCodeDir self.clear_button.setEnabled(self.path is not None) if self.config_manager.userCodeDir is not None: self.folder_label.setText(self.config_manager.userCodeDir) logger.debug( "EngineSettings widget initialised, custom module search path is set to: {}" .format(self.path)) def save(self): """This function is called by the parent dialog window when the user selects to save the settings.""" if self.path is None: # Delete requested, so remove the current path from sys.path, if present if self.config_manager.userCodeDir is not None: sys.path.remove(self.config_manager.userCodeDir) self.config_manager.userCodeDir = None logger.info( "Removed custom module search path from configuration and sys.path." ) else: if self.path != self.config_manager.userCodeDir: if self.config_manager.userCodeDir is not None: sys.path.remove(self.config_manager.userCodeDir) sys.path.append(self.path) self.config_manager.userCodeDir = self.path logger.info( "Saved custom module search path and added it to sys.path: {}" .format(self.path)) @pyqtSlot() def on_browse_button_pressed(self): """ PyQt slot called when the user hits the "Browse" button. Display a directory selection dialog and store the returned path. """ path = QFileDialog.getExistingDirectory( self.parentWidget(), "Choose a directory containing Python modules") if path: # Non-empty means the user chose a path and clicked on OK self.path = path self.clear_button.setEnabled(True) self.folder_label.setText(path) logger.debug("User selects a custom module search path: {}".format( self.path)) @pyqtSlot() def on_clear_button_pressed(self): """ PyQt slot called when the user hits the "Clear" button. Removes any set custom module search path. """ self.path = None self.clear_button.setEnabled(False) self.folder_label.setText(self.initial_folder_label_text) logger.debug("User selects to clear the custom module search path.")
class AbbrSettingsDialog( *ui_common.inherits_from_ui_file_with_name("abbrsettings")): def __init__(self, parent): super().__init__(parent) self.setupUi() self._reset_word_char_combobox() def setupUi(self): self.setObjectName( "Form") # TODO: needed? Maybe use a better name than 'Form' super().setupUi(self) def on_addButton_pressed(self): logger.info("New abbreviation added.") item = AbbrListItem("") self.abbrListWidget.addItem(item) self.abbrListWidget.editItem(item) self.removeButton.setEnabled(True) def on_removeButton_pressed(self): item = self.abbrListWidget.takeItem(self.abbrListWidget.currentRow()) if item is not None: logger.info("User deletes abbreviation with text: {}".format( item.text())) if self.abbrListWidget.count() == 0: logger.debug( "Last abbreviation deleted, disabling delete and OK buttons.") self.removeButton.setEnabled(False) # The user can only accept the dialog if the content is valid self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False) def on_abbrListWidget_itemChanged(self, item): if ui_common.EMPTY_FIELD_REGEX.match(item.text()): row = self.abbrListWidget.row(item) self.abbrListWidget.takeItem(row) logger.debug( "User deleted abbreviation content. Deleted empty list element." ) del item else: # The item is non-empty. Therefore there is at least one element in the list, thus input is valid. # Allow the user to accept his edits. self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(True) if self.abbrListWidget.count() == 0: logger.debug( "Last abbreviation deleted, disabling delete and OK buttons.") self.removeButton.setEnabled(False) # The user can only accept the dialog if the content is valid self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False) def on_abbrListWidget_itemDoubleClicked(self, item): self.abbrListWidget.editItem(item) def on_ignoreCaseCheckbox_stateChanged(self, state): if not state: self.matchCaseCheckbox.setChecked(False) def on_matchCaseCheckbox_stateChanged(self, state): if state: self.ignoreCaseCheckbox.setChecked(True) def on_immediateCheckbox_stateChanged(self, state): if state: self.omitTriggerCheckbox.setChecked(False) self.omitTriggerCheckbox.setEnabled(False) self.wordCharCombo.setEnabled(False) else: self.omitTriggerCheckbox.setEnabled(True) self.wordCharCombo.setEnabled(True) def load(self, item): self.targetItem = item self.abbrListWidget.clear() if model.TriggerMode.ABBREVIATION in item.modes: for abbr in item.abbreviations: self.abbrListWidget.addItem(AbbrListItem(abbr)) self.removeButton.setEnabled(True) self.abbrListWidget.setCurrentRow(0) else: self.removeButton.setEnabled(False) self.removeTypedCheckbox.setChecked(item.backspace) self._reset_word_char_combobox() wordCharRegex = item.get_word_chars() if wordCharRegex in list(WORD_CHAR_OPTIONS.values()): # Default wordchar regex used for desc, regex in WORD_CHAR_OPTIONS.items(): if item.get_word_chars() == regex: self.wordCharCombo.setCurrentIndex( WORD_CHAR_OPTIONS_ORDERED.index(desc)) break else: # Custom wordchar regex used self.wordCharCombo.addItem(model.extract_wordchars(wordCharRegex)) self.wordCharCombo.setCurrentIndex(len(WORD_CHAR_OPTIONS)) if isinstance(item, model.Folder): self.omitTriggerCheckbox.setVisible(False) else: self.omitTriggerCheckbox.setVisible(True) self.omitTriggerCheckbox.setChecked(item.omitTrigger) if isinstance(item, model.Phrase): self.matchCaseCheckbox.setVisible(True) self.matchCaseCheckbox.setChecked(item.matchCase) else: self.matchCaseCheckbox.setVisible(False) self.ignoreCaseCheckbox.setChecked(item.ignoreCase) self.triggerInsideCheckbox.setChecked(item.triggerInside) self.immediateCheckbox.setChecked(item.immediate) # Enable the OK button only if there are abbreviations in the loaded list. Otherwise only cancel is available # to the user until they add a non-empty abbreviation. self.buttonBox.button(QDialogButtonBox.Ok).setEnabled( bool(self.get_abbrs())) def save(self, item): item.modes.append(model.TriggerMode.ABBREVIATION) item.clear_abbreviations() item.abbreviations = self.get_abbrs() item.backspace = self.removeTypedCheckbox.isChecked() option = str(self.wordCharCombo.currentText()) if option in WORD_CHAR_OPTIONS: item.set_word_chars(WORD_CHAR_OPTIONS[option]) else: item.set_word_chars(model.make_wordchar_re(option)) if not isinstance(item, model.Folder): item.omitTrigger = self.omitTriggerCheckbox.isChecked() if isinstance(item, model.Phrase): item.matchCase = self.matchCaseCheckbox.isChecked() item.ignoreCase = self.ignoreCaseCheckbox.isChecked() item.triggerInside = self.triggerInsideCheckbox.isChecked() item.immediate = self.immediateCheckbox.isChecked() def reset(self): self.removeButton.setEnabled(False) self.abbrListWidget.clear() self._reset_word_char_combobox() self.omitTriggerCheckbox.setChecked(False) self.removeTypedCheckbox.setChecked(True) self.matchCaseCheckbox.setChecked(False) self.ignoreCaseCheckbox.setChecked(False) self.triggerInsideCheckbox.setChecked(False) self.immediateCheckbox.setChecked(False) def _reset_word_char_combobox(self): self.wordCharCombo.clear() for item in WORD_CHAR_OPTIONS_ORDERED: self.wordCharCombo.addItem(item) self.wordCharCombo.setCurrentIndex(0) def get_abbrs(self): ret = [] for i in range(self.abbrListWidget.count()): text = self.abbrListWidget.item(i).text() ret.append(str(text)) return ret def get_abbrs_readable(self): abbrs = self.get_abbrs() if len(abbrs) == 1: return abbrs[0] else: return "[%s]" % ','.join(abbrs) def reset_focus(self): self.addButton.setFocus() def accept(self): super().accept() def reject(self): self.load(self.targetItem) super().reject()
class PhrasePage(*ui_common.inherits_from_ui_file_with_name("phrasepage")): def __init__(self): super(PhrasePage, self).__init__() self.setupUi(self) self.initialising = True l = list(model.SEND_MODES.keys()) l.sort() for val in l: self.sendModeCombo.addItem(val) self.initialising = False def load(self, phrase): self.currentPhrase = phrase self.phraseText.setPlainText(phrase.phrase) self.showInTrayCheckbox.setChecked(phrase.show_in_tray_menu) for k, v in model.SEND_MODES.items(): if v == phrase.sendMode: self.sendModeCombo.setCurrentIndex( self.sendModeCombo.findText(k)) break if self.is_new_item(): self.urlLabel.setEnabled(False) self.urlLabel.setText("(Unsaved)") # TODO: i18n else: ui_common.set_url_label(self.urlLabel, self.currentPhrase.path) # TODO - re-enable me if restoring predictive functionality #self.predictCheckbox.setChecked(model.TriggerMode.PREDICTIVE in phrase.modes) self.promptCheckbox.setChecked(phrase.prompt) self.settingsWidget.load(phrase) def save(self): self.settingsWidget.save() self.currentPhrase.phrase = str(self.phraseText.toPlainText()) self.currentPhrase.show_in_tray_menu = self.showInTrayCheckbox.isChecked( ) self.currentPhrase.sendMode = model.SEND_MODES[str( self.sendModeCombo.currentText())] # TODO - re-enable me if restoring predictive functionality #if self.predictCheckbox.isChecked(): # self.currentPhrase.modes.append(model.TriggerMode.PREDICTIVE) self.currentPhrase.prompt = self.promptCheckbox.isChecked() self.currentPhrase.persist() ui_common.set_url_label(self.urlLabel, self.currentPhrase.path) return False def set_item_title(self, title): self.currentPhrase.description = title def rebuild_item_path(self): self.currentPhrase.rebuild_path() def is_new_item(self): return self.currentPhrase.path is None def reset(self): self.load(self.currentPhrase) def validate(self): errors = [] # Check phrase content phrase = str(self.phraseText.toPlainText()) if ui_common.EMPTY_FIELD_REGEX.match(phrase): errors.append("The phrase content can't be empty") # TODO: i18n # Check settings errors += self.settingsWidget.validate() if errors: msg = PROBLEM_MSG_SECONDARY.format('\n'.join( [str(e) for e in errors])) QMessageBox.critical(self.window(), PROBLEM_MSG_PRIMARY, msg) return not bool(errors) def set_dirty(self): self.window().set_dirty() def undo(self): self.phraseText.undo() def redo(self): self.phraseText.redo() def insert_token(self, token): self.phraseText.insertPlainText(token) # --- Signal handlers def on_phraseText_textChanged(self): self.set_dirty() def on_phraseText_undoAvailable(self, state): self.window().set_undo_available(state) def on_phraseText_redoAvailable(self, state): self.window().set_redo_available(state) def on_predictCheckbox_stateChanged(self, state): self.set_dirty() def on_promptCheckbox_stateChanged(self, state): self.set_dirty() def on_showInTrayCheckbox_stateChanged(self, state): self.set_dirty() def on_sendModeCombo_currentIndexChanged(self, index): if not self.initialising: self.set_dirty() def on_urlLabel_leftClickedUrl(self, url=None): if url: subprocess.Popen(["/usr/bin/xdg-open", url])
class ScriptPage(*ui_common.inherits_from_ui_file_with_name("scriptpage")): def __init__(self): super(ScriptPage, self).__init__() self.setupUi(self) self.scriptCodeEditor.setUtf8(1) lex = Qsci.QsciLexerPython(self) api = Qsci.QsciAPIs(lex) api.load(API_FILE) api.prepare() self.current_script = None # type: model.Script self.scriptCodeEditor.setLexer(lex) self.scriptCodeEditor.setBraceMatching( Qsci.QsciScintilla.SloppyBraceMatch) self.scriptCodeEditor.setAutoIndent(True) self.scriptCodeEditor.setBackspaceUnindents(True) self.scriptCodeEditor.setIndentationWidth(4) self.scriptCodeEditor.setIndentationGuides(True) self.scriptCodeEditor.setIndentationsUseTabs(False) self.scriptCodeEditor.setAutoCompletionThreshold(3) self.scriptCodeEditor.setAutoCompletionSource( Qsci.QsciScintilla.AcsAll) self.scriptCodeEditor.setCallTipsStyle( Qsci.QsciScintilla.CallTipsNoContext) lex.setFont(ui_common.monospace_font()) def load(self, script: model.Script): self.current_script = script self.scriptCodeEditor.clear() self.scriptCodeEditor.append(script.code) self.showInTrayCheckbox.setChecked(script.show_in_tray_menu) self.promptCheckbox.setChecked(script.prompt) self.settingsWidget.load(script) self.window().set_undo_available(False) self.window().set_redo_available(False) if self.is_new_item(): self.urlLabel.setEnabled(False) self.urlLabel.setText("(Unsaved)") # TODO: i18n else: ui_common.set_url_label(self.urlLabel, self.current_script.path) def save(self): self.settingsWidget.save() self.current_script.code = str(self.scriptCodeEditor.text()) self.current_script.show_in_tray_menu = self.showInTrayCheckbox.isChecked( ) self.current_script.prompt = self.promptCheckbox.isChecked() self.current_script.persist() ui_common.set_url_label(self.urlLabel, self.current_script.path) return False def get_current_item(self): """Returns the currently held item.""" return self.current_script def set_item_title(self, title): self.current_script.description = title def rebuild_item_path(self): self.current_script.rebuild_path() def is_new_item(self): return self.current_script.path is None def reset(self): self.load(self.current_script) self.window().set_undo_available(False) self.window().set_redo_available(False) def set_dirty(self): self.window().set_dirty() def start_record(self): self.scriptCodeEditor.append("\n") def start_key_sequence(self): self.scriptCodeEditor.append("keyboard.send_keys(\"") def end_key_sequence(self): self.scriptCodeEditor.append("\")\n") def append_key(self, key): self.scriptCodeEditor.append(key) def append_hotkey(self, key, modifiers): keyString = self.current_script.get_hotkey_string(key, modifiers) self.scriptCodeEditor.append(keyString) def append_mouseclick(self, xCoord, yCoord, button, windowTitle): self.scriptCodeEditor.append( "mouse.click_relative(%d, %d, %d) # %s\n" % (xCoord, yCoord, int(button), windowTitle)) def undo(self): self.scriptCodeEditor.undo() self.window().set_undo_available( self.scriptCodeEditor.isUndoAvailable()) def redo(self): self.scriptCodeEditor.redo() self.window().set_redo_available( self.scriptCodeEditor.isRedoAvailable()) def validate(self): errors = [] # Check script code code = str(self.scriptCodeEditor.text()) if ui_common.EMPTY_FIELD_REGEX.match(code): errors.append("The script code can't be empty") # TODO: i18n # Check settings errors += self.settingsWidget.validate() if errors: msg = PROBLEM_MSG_SECONDARY.format('\n'.join( [str(e) for e in errors])) header = PROBLEM_MSG_PRIMARY QMessageBox.critical(self.window(), header, msg) return not bool(errors) # --- Signal handlers def on_scriptCodeEditor_textChanged(self): self.set_dirty() self.window().set_undo_available( self.scriptCodeEditor.isUndoAvailable()) self.window().set_redo_available( self.scriptCodeEditor.isRedoAvailable()) def on_promptCheckbox_stateChanged(self, state): self.set_dirty() def on_showInTrayCheckbox_stateChanged(self, state): self.set_dirty() def on_urlLabel_leftClickedUrl(self, url=None): if url: subprocess.Popen(["/usr/bin/xdg-open", url])
class WindowFilterSettingsDialog(*ui_common.inherits_from_ui_file_with_name("window_filter_settings_dialog")): def __init__(self, parent): super(WindowFilterSettingsDialog, self).__init__(parent) self.setupUi(self) self.target_item = None self.grabber = None # type: iomediator.WindowGrabber def load(self, item: model.Item): self.target_item = item if not isinstance(item, model.Folder): self.apply_recursive_check_box.hide() else: self.apply_recursive_check_box.show() if not item.has_filter(): self.reset() else: self.trigger_regex_line_edit.setText(item.get_filter_regex()) self.apply_recursive_check_box.setChecked(item.isRecursive) def save(self, item): try: item.set_window_titles(self.get_filter_text()) except re.error: # TODO: Warn user. Currently just doesn't save regex on error. pass item.set_filter_recursive(self.get_is_recursive()) def get_is_recursive(self): return self.apply_recursive_check_box.isChecked() def reset(self): self.trigger_regex_line_edit.clear() self.apply_recursive_check_box.setChecked(False) def reset_focus(self): self.trigger_regex_line_edit.setFocus() def get_filter_text(self): return str(self.trigger_regex_line_edit.text()) def receive_window_info(self, info): self.parentWidget().window().app.exec_in_main(self._receiveWindowInfo, info) def _receiveWindowInfo(self, info): dlg = DetectDialog(self) dlg.populate(info) dlg.exec_() if dlg.result() == QDialog.Accepted: self.trigger_regex_line_edit.setText(dlg.get_choice()) self.detect_window_properties_button.setEnabled(True) # --- Signal handlers --- def on_detect_window_properties_button_pressed(self): self.detect_window_properties_button.setEnabled(False) self.grabber = iomediator.WindowGrabber(self) self.grabber.start() # --- event handlers --- def slotButtonClicked(self, button): if button == QDialog.Cancel: self.load(self.targetItem) QDialog.slotButtonClicked(self, button)
class GeneralSettings( *ui_common.inherits_from_ui_file_with_name("generalsettings")): """This widget implements the "general settings" widget and is used in the settings dialog.""" GUI_TABLE = (("autokey-qt.desktop", "Qt5"), ("autokey-gtk.desktop", "GTK+")) ICON_TABLE = ((0, common.ICON_FILE_NOTIFICATION), (1, common.ICON_FILE_NOTIFICATION_DARK)) def __init__(self, parent: QWidget = None): super(GeneralSettings, self).__init__(parent) self.setupUi(self) self.prompt_to_save_checkbox.setChecked( cm.ConfigManager.SETTINGS[cm.PROMPT_TO_SAVE]) self.show_tray_checkbox.setChecked( cm.ConfigManager.SETTINGS[cm.SHOW_TRAY_ICON]) # self.allow_kb_nav_checkbox.setChecked(cm.ConfigManager.SETTINGS[cm.MENU_TAKES_FOCUS]) self.allow_kb_nav_checkbox.setVisible(False) self.sort_by_usage_checkbox.setChecked( cm.ConfigManager.SETTINGS[cm.SORT_BY_USAGE_COUNT]) self.enable_undo_checkbox.setChecked( cm.ConfigManager.SETTINGS[cm.UNDO_USING_BACKSPACE]) self._fill_notification_icon_combobox_user_data() self._load_system_tray_icon_theme() self._fill_autostart_gui_selection_combobox() self.autostart_settings = cm.get_autostart() self._load_autostart_settings() logger.debug("Created widget and loaded current settings: " + self._settings_str()) def save(self): """Called by the parent settings dialog when the user clicks on the Save button. Stores the current settings in the ConfigManager.""" logger.debug("User requested to save settings. New settings: " + self._settings_str()) cm.ConfigManager.SETTINGS[ cm.PROMPT_TO_SAVE] = self.prompt_to_save_checkbox.isChecked() cm.ConfigManager.SETTINGS[ cm.SHOW_TRAY_ICON] = self.show_tray_checkbox.isChecked() # cm.ConfigManager.SETTINGS[cm.MENU_TAKES_FOCUS] = self.allow_kb_nav_checkbox.isChecked() cm.ConfigManager.SETTINGS[ cm.SORT_BY_USAGE_COUNT] = self.sort_by_usage_checkbox.isChecked() cm.ConfigManager.SETTINGS[ cm.UNDO_USING_BACKSPACE] = self.enable_undo_checkbox.isChecked() cm.ConfigManager.SETTINGS[ cm. NOTIFICATION_ICON] = self.system_tray_icon_theme_combobox.currentData( Qt.UserRole) # TODO: After saving the notification icon, apply it to the currently running instance. self._save_autostart_settings() def _settings_str(self): """Returns a human readable settings representation for logging purposes.""" settings = "Prompt to save: {}, " \ "Show tray icon: {}, " \ "Allow keyboard navigation: {}, " \ "Sort by usage count: {}, " \ "Enable undo using backspace: {}, " \ "Tray icon theme: {}".format( self.prompt_to_save_checkbox.isChecked(), self.show_tray_checkbox.isChecked(), self.allow_kb_nav_checkbox.isChecked(), self.sort_by_usage_checkbox.isChecked(), self.enable_undo_checkbox.isChecked(), self.system_tray_icon_theme_combobox.currentData(Qt.UserRole) ) return settings def _fill_autostart_gui_selection_combobox(self): combobox = self.autostart_interface_choice_combobox # type: QComboBox for desktop_file, name in GeneralSettings.GUI_TABLE: try: cm.get_source_desktop_file(desktop_file) except FileNotFoundError: # Skip unavailable GUIs pass else: combobox.addItem(name, desktop_file) def _fill_notification_icon_combobox_user_data(self): combo_box = self.system_tray_icon_theme_combobox # type: QComboBox for index, icon_name in GeneralSettings.ICON_TABLE: combo_box.setItemData(index, icon_name, Qt.UserRole) def _load_system_tray_icon_theme(self): combo_box = self.system_tray_icon_theme_combobox # type: QComboBox data = cm.ConfigManager.SETTINGS[cm.NOTIFICATION_ICON] combo_box_index = combo_box.findData(data, Qt.UserRole) if combo_box_index == -1: # Invalid data in user configuration. TODO: should this be a warning or error? # Just revert to theme at index 0 (light) combo_box_index = 0 combo_box.setCurrentIndex(combo_box_index) def _load_autostart_settings(self): combobox = self.autostart_interface_choice_combobox # type: QComboBox self.autostart_groupbox.setChecked( self.autostart_settings.desktop_file_name is not None) if self.autostart_settings.desktop_file_name is not None: combobox.setCurrentIndex( combobox.findData(self.autostart_settings.desktop_file_name)) self.autostart_show_main_window_checkbox.setChecked( self.autostart_settings.switch_show_configure) def _save_autostart_settings(self): combobox = self.autostart_interface_choice_combobox # type: QComboBox desktop_entry = None if not self.autostart_groupbox.isChecked( ) else combobox.currentData(Qt.UserRole) show_main_window = self.autostart_show_main_window_checkbox.isChecked() new_settings = cm.AutostartSettings(desktop_entry, show_main_window) if new_settings != self.autostart_settings: # Only write if settings changed to preserve eventual user-made modifications. cm.set_autostart_entry(new_settings)
class SettingsWidget(*inherits_from_ui_file_with_name("settingswidget")): """ The SettingsWidget is used to configure model items. It allows display, assigning and clearing of abbreviations, hotkeys and window filters. """ KEY_MAP = HotkeySettingsDialog.KEY_MAP REVERSE_KEY_MAP = HotkeySettingsDialog.REVERSE_KEY_MAP def __init__(self, parent): super(SettingsWidget, self).__init__(parent) self.setupUi(self) self.abbr_settings_dialog = AbbrSettingsDialog(self) self.hotkey_settings_dialog = HotkeySettingsDialog(self) self.window_filter_dialog = WindowFilterSettingsDialog(self) self.current_item = None # type: model.Item self.abbreviation_enabled = False self.hotkey_enabled = False self.window_filter_enabled = False def load(self, item: model.Item): self.current_item = item self._load_abbreviation_data(item) self._load_hotkey_data(item) self._load_window_filter_data(item) def _load_abbreviation_data(self, item: model.Item): self.abbr_settings_dialog.load(item) item_has_abbreviation = model.TriggerMode.ABBREVIATION in item.modes self.abbreviation_label.setText(item.get_abbreviations( ) if item_has_abbreviation else "(None configured)") self.clear_abbreviation_button.setEnabled(item_has_abbreviation) self.abbreviation_enabled = item_has_abbreviation def _load_hotkey_data(self, item: model.Item): self.hotkey_settings_dialog.load(item) item_has_hotkey = model.TriggerMode.HOTKEY in item.modes self.hotkey_label.setText(item.get_hotkey_string( ) if item_has_hotkey else "(None configured)") self.clear_hotkey_button.setEnabled(item_has_hotkey) self.hotkey_enabled = item_has_hotkey def _load_window_filter_data(self, item: model.Item): self.window_filter_dialog.load(item) item_has_window_filter = item.has_filter() or item.inherits_filter() self.window_filter_label.setText(item.get_filter_regex( ) if item_has_window_filter else "(None configured)") self.window_filter_enabled = item_has_window_filter self.clear_window_filter_button.setEnabled(item_has_window_filter) if item.inherits_filter(): # Inherited window filters can’t be deleted on specific items. self.clear_window_filter_button.setEnabled(False) self.window_filter_enabled = False def save(self): # Perform hotkey ungrab if model.TriggerMode.HOTKEY in self.current_item.modes: self.window().app.hotkey_removed(self.current_item) self.current_item.set_modes([]) if self.abbreviation_enabled: self.abbr_settings_dialog.save(self.current_item) if self.hotkey_enabled: self.hotkey_settings_dialog.save(self.current_item) if self.window_filter_enabled: self.window_filter_dialog.save(self.current_item) else: self.current_item.set_window_titles(None) if self.hotkey_enabled: self.window().app.hotkey_created(self.current_item) def set_dirty(self): self.window().set_dirty() def validate(self): # Start by getting all applicable information if self.abbreviation_enabled: abbreviations = self.abbr_settings_dialog.get_abbrs() else: abbreviations = [] if self.hotkey_enabled: modifiers = self.hotkey_settings_dialog.build_modifiers() key = self.hotkey_settings_dialog.key else: modifiers = [] key = None filter_expression = None if self.window_filter_enabled: filter_expression = self.window_filter_dialog.get_filter_text() elif self.current_item.parent is not None: r = self.current_item.parent.get_applicable_regex(True) if r is not None: filter_expression = r.pattern # Validate ret = [] config_manager = self.window().app.configManager for abbr in abbreviations: unique, conflicting = config_manager.check_abbreviation_unique( abbr, filter_expression, self.current_item) if not unique: f = conflicting.get_applicable_regex() # TODO: i18n if f is None: msg = "The abbreviation {abbreviation} is already in use by the {conflicting_item}.".format( abbreviation=abbr, conflicting_item=str(conflicting)) else: msg = "The abbreviation {abbreviation} is already in use by the {conflicting_item} " \ "for windows matching '{matching_pattern}'.".format( abbreviation=abbr, conflicting_item=str(conflicting), matching_pattern=f.pattern ) ret.append(msg) unique, conflicting = config_manager.check_hotkey_unique( modifiers, key, filter_expression, self.current_item) if not unique: f = conflicting.get_applicable_regex() # TODO: i18n if f is None: msg = "The hotkey '{hotkey}' is already in use by the {conflicting_item}.".format( hotkey=conflicting.get_hotkey_string(), conflicting_item=str(conflicting)) else: msg = "The hotkey '{hotkey}' is already in use by the {conflicting_item} " \ "for windows matching '{matching_pattern}.".format( hotkey=conflicting.get_hotkey_string(), conflicting_item=str(conflicting), matching_pattern=f.pattern ) ret.append(msg) return ret # ---- Signal handlers def on_set_abbreviation_button_pressed(self): self.abbr_settings_dialog.exec_() if self.abbr_settings_dialog.result() == QDialog.Accepted: self.set_dirty() self.abbreviation_enabled = True self.abbreviation_label.setText( self.abbr_settings_dialog.get_abbrs_readable()) self.clear_abbreviation_button.setEnabled(True) def on_clear_abbreviation_button_pressed(self): self.set_dirty() self.abbreviation_enabled = False self.clear_abbreviation_button.setEnabled(False) self.abbreviation_label.setText("(None configured)") # TODO: i18n self.abbr_settings_dialog.reset() def on_set_hotkey_button_pressed(self): self.hotkey_settings_dialog.exec_() if self.hotkey_settings_dialog.result() == QDialog.Accepted: self.set_dirty() self.hotkey_enabled = True key = self.hotkey_settings_dialog.key modifiers = self.hotkey_settings_dialog.build_modifiers() self.hotkey_label.setText( self.current_item.get_hotkey_string(key, modifiers)) self.clear_hotkey_button.setEnabled(True) def on_clear_hotkey_button_pressed(self): self.set_dirty() self.hotkey_enabled = False self.clear_hotkey_button.setEnabled(False) self.hotkey_label.setText("(None configured)") # TODO: i18n self.hotkey_settings_dialog.reset() def on_set_window_filter_button_pressed(self): self.window_filter_dialog.exec_() if self.window_filter_dialog.result() == QDialog.Accepted: self.set_dirty() filter_text = self.window_filter_dialog.get_filter_text() if filter_text: self.window_filter_enabled = True self.clear_window_filter_button.setEnabled(True) self.window_filter_label.setText(filter_text) else: self.window_filter_enabled = False self.clear_window_filter_button.setEnabled(False) if self.current_item.inherits_filter(): text = self.current_item.parent.get_child_filter() else: text = "(None configured)" # TODO: i18n self.window_filter_label.setText(text) def on_clear_window_filter_button_pressed(self): self.set_dirty() self.window_filter_enabled = False self.clear_window_filter_button.setEnabled(False) if self.current_item.inherits_filter(): text = self.current_item.parent.get_child_filter() else: text = "(None configured)" # TODO: i18n self.window_filter_label.setText(text) self.window_filter_dialog.reset()
class SpecialHotkeySettings(*ui_common.inherits_from_ui_file_with_name("specialhotkeysettings")): """ The SpecialHotkeySettings class is used inside the AutoKey configuration dialog. It allows the user to select or clear global hotkeys. Currently has two hotkeys: - use_service enables/disables the autokey background service - use_config shows the autokey config/main window, if hidden. """ KEY_MAP = GlobalHotkeyDialog.KEY_MAP REVERSE_KEY_MAP = GlobalHotkeyDialog.REVERSE_KEY_MAP def __init__(self, parent: QWidget=None): super(SpecialHotkeySettings, self).__init__(parent) self.setupUi(self) self.show_config_dlg = GlobalHotkeyDialog(parent) self.toggle_monitor_dlg = GlobalHotkeyDialog(parent) self.use_config_hotkey = False self.use_service_hotkey = False app = QApplication.instance() # type: Application self.config_manager = app.configManager self.use_config_hotkey = self._load_hotkey(self.config_manager.configHotkey, self.config_key_label, self.show_config_dlg, self.clear_config_button) self.use_service_hotkey = self._load_hotkey(self.config_manager.toggleServiceHotkey, self.monitor_key_label, self.toggle_monitor_dlg, self.clear_monitor_button) @staticmethod def _load_hotkey(item, label: QLabel, dialog: GlobalHotkeyDialog, clear_button: QPushButton): dialog.load(item) if item.enabled: key = item.hotKey label.setText(item.get_hotkey_string(key, item.modifiers)) clear_button.setEnabled(True) return True else: label.setText("(None configured)") clear_button.setEnabled(False) return False def save(self): config_hotkey = self.config_manager.configHotkey toggle_hotkey = self.config_manager.toggleServiceHotkey app = QApplication.instance() # type: Application if config_hotkey.enabled: app.hotkey_removed(config_hotkey) config_hotkey.enabled = self.use_config_hotkey if self.use_config_hotkey: self.show_config_dlg.save(config_hotkey) app.hotkey_created(config_hotkey) if toggle_hotkey.enabled: app.hotkey_removed(toggle_hotkey) toggle_hotkey.enabled = self.use_service_hotkey if self.use_service_hotkey: self.toggle_monitor_dlg.save(toggle_hotkey) app.hotkey_created(toggle_hotkey) # ---- Signal handlers def on_set_config_button_pressed(self): self.show_config_dlg.exec_() if self.show_config_dlg.result() == QDialog.Accepted: self.use_config_hotkey = True key = self.show_config_dlg.key modifiers = self.show_config_dlg.build_modifiers() self.config_key_label.setText(self.show_config_dlg.target_item.get_hotkey_string(key, modifiers)) self.clear_config_button.setEnabled(True) def on_clear_config_button_pressed(self): self.use_config_hotkey = False self.clear_config_button.setEnabled(False) self.config_key_label.setText("(None configured)") self.show_config_dlg.reset() def on_set_monitor_button_pressed(self): self.toggle_monitor_dlg.exec_() if self.toggle_monitor_dlg.result() == QDialog.Accepted: self.use_service_hotkey = True key = self.toggle_monitor_dlg.key modifiers = self.toggle_monitor_dlg.build_modifiers() self.monitor_key_label.setText(self.toggle_monitor_dlg.target_item.get_hotkey_string(key, modifiers)) self.clear_monitor_button.setEnabled(True) def on_clear_monitor_button_pressed(self): self.use_service_hotkey = False self.clear_monitor_button.setEnabled(False) self.monitor_key_label.setText("(None configured)") self.toggle_monitor_dlg.reset()