Esempio n. 1
0
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
Esempio n. 2
0
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())
Esempio n. 3
0
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.")
Esempio n. 4
0
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])
Esempio n. 5
0
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)
Esempio n. 6
0
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()
Esempio n. 7
0
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.")
Esempio n. 8
0
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()
Esempio n. 9
0
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])
Esempio n. 10
0
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])
Esempio n. 11
0
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)
Esempio n. 12
0
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)
Esempio n. 13
0
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()
Esempio n. 14
0
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()