def _get_dictionary_module(filename): extension = splitext(filename)[1].lower()[1:] try: entrypoint = registry.get_plugin('dictionary', extension) except KeyError: raise DictionaryLoaderException( 'Unsupported extension: %s. Supported extensions: %s' % (extension, ', '.join(sorted(registry.list_plugins('dictionary'))))) return entrypoint.resolve()
def _get_dictionary_class(filename): extension = splitext(filename)[1].lower()[1:] try: dict_module = registry.get_plugin('dictionary', extension).obj except KeyError: raise ValueError( 'Unsupported extension: %s. Supported extensions: %s' % (extension, ', '.join(plugin.name for plugin in registry.list_plugins('dictionary')))) return dict_module
def _get_dictionary_module(filename): extension = splitext(filename)[1].lower()[1:] try: entrypoint = registry.get_plugin('dictionary', extension) except KeyError: raise DictionaryLoaderException( 'Unsupported extension: %s. Supported extensions: %s' % (extension, ', '.join(sorted( registry.list_plugins('dictionary'))))) return entrypoint.resolve()
def _machine_option(self, *args): machine_options = { plugin.name: plugin.obj for plugin in registry.list_plugins('gui.qt.machine_option') } machine_type = self._config['machine_type'] machine_class = registry.get_plugin('machine', machine_type).obj for klass in machine_class.mro(): # Look for `module_name:class_name` before `class_name`. for name in ( '%s:%s' % (klass.__module__, klass.__name__), klass.__name__, ): opt_class = machine_options.get(name) if opt_class is not None: return opt_class(*args) return NopeOption(*args)
def __init__(self, engine, use_qt_notifications): super(MainWindow, self).__init__() self.setupUi(self) if hasattr(self, 'setUnifiedTitleAndToolBarOnMac'): self.setUnifiedTitleAndToolBarOnMac(True) self._engine = engine self._active_dialogs = {} self._dialog_class = { 'about' : AboutDialog, 'configuration' : ConfigWindow, } all_actions = find_menu_actions(self.menubar) # Dictionaries. self.dictionaries = self.scroll_area.widget() self.dictionaries.add_translation.connect(self._add_translation) self.dictionaries.setFocus() edit_menu = all_actions['menu_Edit'].menu() edit_menu.addAction(self.dictionaries.action_Undo) edit_menu.addSeparator() edit_menu.addMenu(self.dictionaries.menu_AddDictionaries) edit_menu.addAction(self.dictionaries.action_EditDictionaries) edit_menu.addAction(self.dictionaries.action_RemoveDictionaries) edit_menu.addSeparator() edit_menu.addAction(self.dictionaries.action_MoveDictionariesUp) edit_menu.addAction(self.dictionaries.action_MoveDictionariesDown) # Tray icon. self._trayicon = TrayIcon() self._trayicon.enable() self._trayicon.clicked.connect(self._engine.toggle_output) if use_qt_notifications: handler = NotificationHandler() handler.emitSignal.connect(self._trayicon.log) log.add_handler(handler) popup_menu = QMenu() for action_name in ( 'action_ToggleOutput', 'action_Reconnect', '', 'menu_Tools', '', 'action_Configure', '', 'menu_Help', '', 'action_Show', 'action_Quit', ): if action_name: popup_menu.addAction(all_actions[action_name]) else: popup_menu.addSeparator() self._trayicon.set_menu(popup_menu) engine.signal_connect('machine_state_changed', self._trayicon.update_machine_state) engine.signal_connect('quit', self.on_quit) self.action_Quit.triggered.connect(engine.quit) # Populate tools bar/menu. tools_menu = all_actions['menu_Tools'].menu() # Toolbar popup menu for selecting which tools are shown. self.toolbar_menu = QMenu() self.toolbar.setContextMenuPolicy(Qt.CustomContextMenu) self.toolbar.customContextMenuRequested.connect( lambda: self.toolbar_menu.popup(QCursor.pos()) ) for tool_plugin in registry.list_plugins('gui.qt.tool'): tool = tool_plugin.obj action_parameters = [] if tool.ICON is not None: icon = tool.ICON # Internal QT resources start with a `:`. if not icon.startswith(':'): icon = resource_filename(icon) action_parameters.append(QIcon(icon)) action_parameters.append(tool.TITLE) toolbar_action = None for parent in (tools_menu, self.toolbar, self.toolbar_menu): action = parent.addAction(*action_parameters) action.setObjectName(tool_plugin.name) if tool.__doc__ is not None: action.setToolTip(tool.__doc__) if tool.SHORTCUT is not None: action.setShortcut(QKeySequence.fromString(tool.SHORTCUT)) if parent == self.toolbar_menu: action.setCheckable(True) action.setChecked(True) assert toolbar_action is not None action.toggled.connect(toolbar_action.setVisible) else: if parent == self.toolbar: toolbar_action = action action.triggered.connect(partial(self._activate_dialog, tool_plugin.name, args=())) self._dialog_class[tool_plugin.name] = tool engine.signal_connect('output_changed', self.on_output_changed) # Machine. self.machine_type.addItems( _(plugin.name) for plugin in registry.list_plugins('machine') ) engine.signal_connect('config_changed', self.on_config_changed) engine.signal_connect('machine_state_changed', lambda machine, state: self.machine_state.setText(_(state.capitalize())) ) self.restore_state() # Commands. engine.signal_connect('add_translation', partial(self._add_translation, manage_windows=True)) engine.signal_connect('focus', self._focus) engine.signal_connect('configure', partial(self._configure, manage_windows=True)) engine.signal_connect('lookup', partial(self._activate_dialog, 'lookup', manage_windows=True)) # Load the configuration (but do not start the engine yet). if not engine.load_config(): self.on_configure() # Apply configuration settings. config = self._engine.config self.machine_type.setCurrentText(config['machine_type']) self._configured = False self.dictionaries.on_config_changed(config) self.set_visible(not config['start_minimized']) # Start the engine. engine.start()
def list_plugins(self, plugin_type): return sorted(registry.list_plugins(plugin_type))
def _dictionary_formats(): return set(registry.list_plugins('dictionary'))
def __init__(self, output, engine): sub_commands = [ EnableDisableExtension(p.name, output, engine) for p in registry.list_plugins("extension") ] super().__init__("extensions", output, sub_commands)
def _dictionary_formats(include_readonly=True): return set(plugin.name for plugin in registry.list_plugins('dictionary') if include_readonly or not plugin.obj.readonly)
def _dictionary_formats(): return set(plugin.name for plugin in registry.list_plugins('dictionary'))
def __init__(self, output, engine) -> None: sub_commands = [MachineOptions(output, engine)] + [ SetMachine(p.name, output, engine) for p in registry.list_plugins("machine") ] super().__init__("machine", output, sub_commands)
def __init__(self, output, engine) -> None: sub_commands = [ SetSystem(p.name, output, engine) for p in registry.list_plugins("system") ] super().__init__("system", output, sub_commands)
def main(): """Launch plover.""" description = "Run the plover stenotype engine. This is a graphical application." parser = argparse.ArgumentParser(description=description) parser.add_argument('--version', action='version', version='%s %s' % (__software_name__.capitalize(), __version__)) parser.add_argument('-s', '--script', default=None, nargs=argparse.REMAINDER, help='use another plugin console script as main entrypoint, ' 'passing in the rest of the command line arguments, ' 'print list of available scripts when no argument is given') parser.add_argument('-l', '--log-level', choices=['debug', 'info', 'warning', 'error'], default=None, help='set log level') parser.add_argument('-g', '--gui', default=None, help='set gui') args = parser.parse_args(args=sys.argv[1:]) if args.log_level is not None: log.set_level(args.log_level.upper()) log.setup_platform_handler() log.info('Plover %s', __version__) log.info('configuration directory: %s', CONFIG_DIR) registry.update() if args.gui is None: gui_priority = { 'qt': 1, 'none': -1, } gui_list = sorted(registry.list_plugins('gui'), reverse=True, key=lambda gui: gui_priority.get(gui.name, 0)) gui = gui_list[0].obj else: gui = registry.get_plugin('gui', args.gui).obj try: if args.script is not None: if args.script: # Create a mapping of available console script, # with the following priorities (highest first): # - {project_name}-{version}:{script_name} # - {project_name}:{script_name} # - {script_name} console_scripts = {} for e in sorted(pkg_resources.iter_entry_points('console_scripts'), key=lambda e: (e.dist, e.name)): for key in ( '%s-%s:%s' % (e.dist.project_name, e.dist.version, e.name), '%s:%s' % (e.dist.project_name, e.name), e.name, ): console_scripts[key] = e entrypoint = console_scripts.get(args.script[0]) if entrypoint is None: log.error('no such script: %s', args.script[0]) code = 1 else: sys.argv = args.script try: code = entrypoint.load()() except SystemExit as e: code = e.code if code is None: code = 0 else: print('available script(s):') dist = None for e in sorted(pkg_resources.iter_entry_points('console_scripts'), key=lambda e: (str(e.dist), e.name)): if dist != e.dist: dist = e.dist print('%s:' % dist) print('- %s' % e.name) code = 0 os._exit(code) # Ensure only one instance of Plover is running at a time. with processlock.PloverLock(): if sys.platform.startswith('darwin'): appnope.nope() init_config_dir() # This must be done after calling init_config_dir, so # Plover's configuration directory actually exists. log.setup_logfile() config = Config() config.target_file = CONFIG_FILE code = gui.main(config) with open(config.target_file, 'wb') as f: config.save(f) except processlock.LockNotAcquiredException: gui.show_error('Error', 'Another instance of Plover is already running.') code = 1 except: gui.show_error('Unexpected error', traceback.format_exc()) code = 2 if code == -1: # Restart. args = sys.argv[:] if args[0].endswith('.py') or args[0].endswith('.pyc'): # We're running from source. assert args[0] == __file__ args[0:1] = [sys.executable, '-m', __spec__.name] # Execute atexit handlers. atexit._run_exitfuncs() if sys.platform.startswith('win32'): # Workaround https://bugs.python.org/issue19066 subprocess.Popen(args, cwd=os.getcwd()) code = 0 else: os.execv(args[0], args) os._exit(code)
def main(): """Launch plover.""" description = "Run the plover stenotype engine. This is a graphical application." parser = argparse.ArgumentParser(description=description) parser.add_argument('--version', action='version', version='%s %s' % (__software_name__.capitalize(), __version__)) parser.add_argument( '-s', '--script', default=None, nargs=argparse.REMAINDER, help='use another plugin console script as main entrypoint, ' 'passing in the rest of the command line arguments, ' 'print list of available scripts when no argument is given') parser.add_argument('-l', '--log-level', choices=['debug', 'info', 'warning', 'error'], default=None, help='set log level') parser.add_argument('-g', '--gui', default=None, help='set gui') args = parser.parse_args(args=sys.argv[1:]) if args.log_level is not None: log.set_level(args.log_level.upper()) log.setup_platform_handler() log.info('Plover %s', __version__) log.info('configuration directory: %s', CONFIG_DIR) log.info('plugins directory: %s', PLUGINS_DIR) registry.update() if args.gui is None: gui_priority = { 'qt': 1, 'none': -1, } gui_list = sorted(registry.list_plugins('gui'), reverse=True, key=lambda gui: gui_priority.get(gui.name, 0)) gui = gui_list[0].obj else: gui = registry.get_plugin('gui', args.gui).obj try: if args.script is not None: if args.script: # Create a mapping of available console script, # with the following priorities (highest first): # - {project_name}-{version}:{script_name} # - {project_name}:{script_name} # - {script_name} console_scripts = {} for e in sorted( pkg_resources.iter_entry_points('console_scripts'), key=lambda e: (e.dist, e.name)): for key in ( '%s-%s:%s' % (e.dist.project_name, e.dist.version, e.name), '%s:%s' % (e.dist.project_name, e.name), e.name, ): console_scripts[key] = e entrypoint = console_scripts.get(args.script[0]) if entrypoint is None: log.error('no such script: %s', args.script[0]) code = 1 else: sys.argv = args.script try: code = entrypoint.load()() except SystemExit as e: code = e.code if code is None: code = 0 else: print('available script(s):') dist = None for e in sorted( pkg_resources.iter_entry_points('console_scripts'), key=lambda e: (str(e.dist), e.name)): if dist != e.dist: dist = e.dist print('%s:' % dist) print('- %s' % e.name) code = 0 os._exit(code) # Ensure only one instance of Plover is running at a time. with plover.oslayer.processlock.PloverLock(): if sys.platform.startswith('darwin'): appnope.nope() init_config_dir() # This must be done after calling init_config_dir, so # Plover's configuration directory actually exists. log.setup_logfile() config = Config() config.target_file = CONFIG_FILE code = gui.main(config) with open(config.target_file, 'wb') as f: config.save(f) except plover.oslayer.processlock.LockNotAcquiredException: gui.show_error('Error', 'Another instance of Plover is already running.') code = 1 except: gui.show_error('Unexpected error', traceback.format_exc()) code = 2 if code == -1: # Restart. args = sys.argv[:] if args[0].endswith('.py') or args[0].endswith('.pyc'): # We're running from source. assert args[0] == __file__ args[0:1] = [sys.executable, '-m', __spec__.name] # Execute atexit handlers. atexit._run_exitfuncs() if sys.platform.startswith('win32'): # Workaround https://bugs.python.org/issue19066 subprocess.Popen(args, cwd=os.getcwd()) code = 0 else: os.execv(args[0], args) os._exit(code)
def __init__(self, engine, use_qt_notifications): super().__init__() self.setupUi(self) if hasattr(self, 'setUnifiedTitleAndToolBarOnMac'): self.setUnifiedTitleAndToolBarOnMac(True) self._engine = engine self._active_dialogs = {} self._dialog_class = { 'about': AboutDialog, 'configuration': ConfigWindow, } all_actions = find_menu_actions(self.menubar) # Dictionaries. self.dictionaries.add_translation.connect(self._add_translation) self.dictionaries.setup(engine) # Populate edit menu from dictionaries' own. edit_menu = all_actions['menu_Edit'].menu() for action in self.dictionaries.edit_menu.actions(): edit_menu.addAction(action) # Tray icon. self._trayicon = TrayIcon() self._trayicon.enable() self._trayicon.clicked.connect(self._engine.toggle_output) if use_qt_notifications: handler = NotificationHandler() handler.emitSignal.connect(self._trayicon.log) log.add_handler(handler) popup_menu = QMenu() for action_name in ( 'action_ToggleOutput', 'action_Reconnect', '', 'menu_Tools', '', 'action_Configure', 'action_OpenConfigFolder', '', 'menu_Help', '', 'action_Show', 'action_Quit', ): if action_name: popup_menu.addAction(all_actions[action_name]) else: popup_menu.addSeparator() self._trayicon.set_menu(popup_menu) engine.signal_connect('machine_state_changed', self._trayicon.update_machine_state) engine.signal_connect('quit', self.on_quit) self.action_Quit.triggered.connect(engine.quit) # Toolbar popup menu for selecting which tools are shown. self.toolbar_menu = QMenu() self.toolbar.setContextMenuPolicy(Qt.CustomContextMenu) self.toolbar.customContextMenuRequested.connect( lambda: self.toolbar_menu.popup(QCursor.pos())) # Populate tools bar/menu. tools_menu = all_actions['menu_Tools'].menu() for tool_plugin in registry.list_plugins('gui.qt.tool'): tool = tool_plugin.obj menu_action = tools_menu.addAction(tool.TITLE) if tool.SHORTCUT is not None: menu_action.setShortcut(QKeySequence.fromString(tool.SHORTCUT)) if tool.ICON is not None: icon = tool.ICON # Internal QT resources start with a `:`. if not icon.startswith(':'): icon = resource_filename(icon) menu_action.setIcon(QIcon(icon)) menu_action.triggered.connect( partial(self._activate_dialog, tool_plugin.name, args=())) toolbar_action = self.toolbar.addAction(menu_action.icon(), menu_action.text()) if tool.__doc__ is not None: toolbar_action.setToolTip(tool.__doc__) toolbar_action.triggered.connect(menu_action.trigger) toggle_action = self.toolbar_menu.addAction( menu_action.icon(), menu_action.text()) toggle_action.setObjectName(tool_plugin.name) toggle_action.setCheckable(True) toggle_action.setChecked(True) toggle_action.toggled.connect(toolbar_action.setVisible) self._dialog_class[tool_plugin.name] = tool engine.signal_connect('output_changed', self.on_output_changed) # Machine. for plugin in registry.list_plugins('machine'): self.machine_type.addItem(_(plugin.name), plugin.name) engine.signal_connect('config_changed', self.on_config_changed) engine.signal_connect( 'machine_state_changed', lambda machine, state: self.machine_state.setText(state.capitalize( ))) self.restore_state() # Commands. engine.signal_connect( 'add_translation', partial(self._add_translation, manage_windows=True)) engine.signal_connect('focus', self._focus) engine.signal_connect('configure', partial(self._configure, manage_windows=True)) engine.signal_connect( 'lookup', partial(self._activate_dialog, 'lookup', manage_windows=True)) engine.signal_connect( 'suggestions', partial(self._activate_dialog, 'suggestions', manage_windows=True)) # Load the configuration (but do not start the engine yet). if not engine.load_config(): self.on_configure() # Apply configuration settings. config = self._engine.config self._warn_on_hide_to_tray = not config['start_minimized'] self._update_machine(config['machine_type']) self._configured = False self.set_visible(not config['start_minimized']) # Process events before starting the engine # (to avoid display lag at window creation). QCoreApplication.processEvents() # Start the engine. engine.start()
def __init__(self, engine): super().__init__() self.setupUi(self) self._engine = engine machines = { plugin.name: _(plugin.name) for plugin in registry.list_plugins('machine') } mappings = ( # i18n: Widget: “ConfigWindow”. (_('Interface'), ( ConfigOption( _('Start minimized:'), 'start_minimized', BooleanOption, _('Minimize the main window to systray on startup.')), ConfigOption(_('Show paper tape:'), 'show_stroke_display', BooleanOption, _('Open the paper tape on startup.')), ConfigOption(_('Show suggestions:'), 'show_suggestions_display', BooleanOption, _('Open the suggestions dialog on startup.')), ConfigOption( _('Add translation dialog opacity:'), 'translation_frame_opacity', partial(IntOption, maximum=100, minimum=0), _('Set the translation dialog opacity:\n' '- 0 makes the dialog invisible\n' '- 100 is fully opaque')), ConfigOption( _('Dictionaries display order:'), 'classic_dictionaries_display_order', partial(BooleanAsDualChoiceOption, _('top-down'), _('bottom-up')), _('Set the display order for dictionaries:\n' '- top-down: match the search order; highest priority first\n' '- bottom-up: reverse search order; lowest priority first\n' )), )), # i18n: Widget: “ConfigWindow”. (_('Logging'), ( ConfigOption( _('Log file:'), 'log_file_name', partial(FileOption, _('Select a log file'), _('Log files (*.log)')), _('File to use for logging strokes/translations.')), ConfigOption(_('Log strokes:'), 'enable_stroke_logging', BooleanOption, _('Save strokes to the logfile.')), ConfigOption(_('Log translations:'), 'enable_translation_logging', BooleanOption, _('Save translations to the logfile.')), )), # i18n: Widget: “ConfigWindow”. (_('Machine'), ( ConfigOption( _('Machine:'), 'machine_type', partial(ChoiceOption, choices=machines), dependents=( ('machine_specific_options', self._update_machine_options), ('system_keymap', lambda v: self._update_keymap(machine_type=v)), )), ConfigOption(_('Options:'), 'machine_specific_options', self._machine_option), ConfigOption(_('Keymap:'), 'system_keymap', KeymapOption), )), # i18n: Widget: “ConfigWindow”. (_('Output'), ( ConfigOption(_('Enable at start:'), 'auto_start', BooleanOption, _('Enable output on startup.')), ConfigOption( _('Start attached:'), 'start_attached', BooleanOption, _('Disable preceding space on first output.\n' '\n' 'This option is only applicable when spaces are placed before.' )), ConfigOption(_('Start capitalized:'), 'start_capitalized', BooleanOption, _('Capitalize the first word.')), ConfigOption( _('Space placement:'), 'space_placement', partial(ChoiceOption, choices={ 'Before Output': _('Before Output'), 'After Output': _('After Output'), }), _('Set automatic space placement: before or after each word.' )), ConfigOption( _('Undo levels:'), 'undo_levels', partial(IntOption, maximum=10000, minimum=MINIMUM_UNDO_LEVELS), _('Set how many preceding strokes can be undone.\n' '\n' 'Note: the effective value will take into account the\n' 'dictionaries entry with the maximum number of strokes.') ), )), # i18n: Widget: “ConfigWindow”. (_('Plugins'), (ConfigOption( _('Extension:'), 'enabled_extensions', partial(MultipleChoicesOption, choices={ plugin.name: plugin.name for plugin in registry.list_plugins('extension') }, labels=(_('Name'), _('Enabled'))), _('Configure enabled plugin extensions.')), )), # i18n: Widget: “ConfigWindow”. (_('System'), (ConfigOption( _('System:'), 'system_name', partial(ChoiceOption, choices={ plugin.name: plugin.name for plugin in registry.list_plugins('system') }), dependents=( ('system_keymap', lambda v: self._update_keymap(system_name=v)), )), )), ) # Only keep supported options, to avoid messing with things like # dictionaries, that are handled by another (possibly concurrent) # dialog. self._supported_options = set() for section, option_list in mappings: self._supported_options.update(option.option_name for option in option_list) self._update_config() # Create and fill tabs. options = {} for section, option_list in mappings: layout = QFormLayout() for option in option_list: widget = self._create_option_widget(option) options[option.option_name] = option option.tab_index = self.tabs.count() option.layout = layout option.widget = widget label = QLabel(option.display_name) label.setToolTip(option.help_text) layout.addRow(label, widget) frame = QFrame() frame.setLayout(layout) scroll_area = QScrollArea() scroll_area.setWidgetResizable(True) scroll_area.setWidget(frame) self.tabs.addTab(scroll_area, section) # Update dependents. for option in options.values(): option.dependents = [ (options[option_name], update_fn) for option_name, update_fn in option.dependents ] buttons = self.findChild(QWidget, 'buttons') buttons.button(QDialogButtonBox.Ok).clicked.connect(self.on_apply) buttons.button(QDialogButtonBox.Apply).clicked.connect(self.on_apply) self.restore_state() self.finished.connect(self.save_state)
def main(): """Launch plover.""" description = "Run the plover stenotype engine. This is a graphical application." parser = argparse.ArgumentParser(description=description) parser.add_argument('--version', action='version', version='%s %s' % (__software_name__.capitalize(), __version__)) parser.add_argument( '-s', '--script', default=None, nargs=argparse.REMAINDER, help='use another plugin console script as main entrypoint, ' 'passing in the rest of the command line arguments, ' 'print list of available scripts when no argument is given') parser.add_argument('-l', '--log-level', choices=['debug', 'info', 'warning', 'error'], default=None, help='set log level') parser.add_argument('-g', '--gui', default=None, help='set gui') args = parser.parse_args(args=sys.argv[1:]) if args.log_level is not None: log.set_level(args.log_level.upper()) log.setup_platform_handler() log.info('Plover %s', __version__) log.info('configuration directory: %s', CONFIG_DIR) if PLATFORM == 'mac': # Fixes PyQt issue on macOS Big Sur. os.environ['QT_MAC_WANTS_LAYER'] = '1' registry.update() if args.gui is None: gui_priority = { 'qt': 1, 'none': -1, } gui_list = sorted(registry.list_plugins('gui'), reverse=True, key=lambda gui: gui_priority.get(gui.name, 0)) gui = gui_list[0].obj else: gui = registry.get_plugin('gui', args.gui).obj try: if args.script is not None: if args.script: # Create a mapping of available console script, # with the following priorities (highest first): # - {project_name}-{version}:{script_name} # - {project_name}:{script_name} # - {script_name} console_scripts = {} for e in sorted( pkg_resources.iter_entry_points('console_scripts'), key=lambda e: (e.dist, e.name)): for key in ( '%s-%s:%s' % (e.dist.project_name, e.dist.version, e.name), '%s:%s' % (e.dist.project_name, e.name), e.name, ): console_scripts[key] = e entrypoint = console_scripts.get(args.script[0]) if entrypoint is None: log.error('no such script: %s', args.script[0]) code = 1 else: sys.argv = args.script try: code = entrypoint.load()() except SystemExit as e: code = e.code if code is None: code = 0 else: print('available script(s):') dist = None for e in sorted( pkg_resources.iter_entry_points('console_scripts'), key=lambda e: (str(e.dist), e.name)): if dist != e.dist: dist = e.dist print('%s:' % dist) print('- %s' % e.name) code = 0 os._exit(code) # Ensure only one instance of Plover is running at a time. with Controller() as controller: if controller.is_owner: # Not other instance, regular startup. if PLATFORM == 'mac': import appnope appnope.nope() init_config_dir() # This must be done after calling init_config_dir, so # Plover's configuration directory actually exists. log.setup_logfile() config = Config(CONFIG_FILE) code = gui.main(config, controller) else: log.info( 'another instance is running, sending `focus` command') # Other instance? Try focusing the main window. try: controller.send_command('focus') except ConnectionRefusedError: log.error('connection to existing instance failed, ' 'force cleaning before restart') # Assume the previous instance died, leaving # a stray socket, try cleaning it... if not controller.force_cleanup(): raise # ...and restart. code = -1 else: code = 0 except: gui.show_error('Unexpected error', traceback.format_exc()) code = 2 if code == -1: # Restart. args = sys.argv[:] if args[0].endswith('.py') or args[0].endswith('.pyc'): # We're running from source. spec = sys.modules['__main__'].__spec__ assert sys.argv[0] == spec.origin args[0:1] = [sys.executable, '-m', spec.name] # Execute atexit handlers. atexit._run_exitfuncs() if PLATFORM == 'win': # Workaround https://bugs.python.org/issue19066 subprocess.Popen(args, cwd=os.getcwd()) code = 0 else: os.execv(args[0], args) os._exit(code)
def __init__(self, engine, use_qt_notifications): super(MainWindow, self).__init__() self.setupUi(self) if hasattr(self, 'setUnifiedTitleAndToolBarOnMac'): self.setUnifiedTitleAndToolBarOnMac(True) self._engine = engine self._active_dialogs = {} self._dialog_class = { 'about' : AboutDialog, 'configuration' : ConfigWindow, } all_actions = find_menu_actions(self.menubar) # Dictionaries. self.dictionaries = DictionariesWidget(engine) self.dictionaries.add_translation.connect(self._add_translation) self.scroll_area.setWidget(self.dictionaries) self.dictionaries.setFocus() edit_menu = all_actions['menu_Edit'].menu() edit_menu.addAction(self.dictionaries.action_Undo) edit_menu.addSeparator() edit_menu.addAction(self.dictionaries.action_AddDictionaries) edit_menu.addAction(self.dictionaries.action_EditDictionaries) edit_menu.addAction(self.dictionaries.action_RemoveDictionaries) edit_menu.addSeparator() edit_menu.addAction(self.dictionaries.action_MoveDictionariesUp) edit_menu.addAction(self.dictionaries.action_MoveDictionariesDown) # Tray icon. self._trayicon = TrayIcon() self._trayicon.enable() self._trayicon.clicked.connect(self._engine.toggle_output) if use_qt_notifications: handler = NotificationHandler() handler.emitSignal.connect(self._trayicon.log) log.add_handler(handler) popup_menu = QMenu() for action_name in ( 'action_ToggleOutput', 'action_Reconnect', '', 'menu_Tools', '', 'action_Configure', '', 'menu_Help', '', 'action_Show', 'action_Quit', ): if action_name: popup_menu.addAction(all_actions[action_name]) else: popup_menu.addSeparator() self._trayicon.set_menu(popup_menu) engine.signal_connect('machine_state_changed', self._trayicon.update_machine_state) engine.signal_connect('quit', self.on_quit) self.action_Quit.triggered.connect(engine.quit) # Populate tools bar/menu. tools_menu = all_actions['menu_Tools'].menu() # Toolbar popup menu for selecting which tools are shown. self.toolbar_menu = QMenu() self.toolbar.setContextMenuPolicy(Qt.CustomContextMenu) self.toolbar.customContextMenuRequested.connect( lambda: self.toolbar_menu.popup(QCursor.pos()) ) for tool_plugin in registry.list_plugins('gui.qt.tool'): tool = tool_plugin.obj action_parameters = [] if tool.ICON is not None: icon = tool.ICON # Internal QT resources start with a `:`. if not icon.startswith(':'): icon = resource_filename(icon) action_parameters.append(QIcon(icon)) action_parameters.append(tool.TITLE) toolbar_action = None for parent in (tools_menu, self.toolbar, self.toolbar_menu): action = parent.addAction(*action_parameters) action.setObjectName(tool_plugin.name) if tool.__doc__ is not None: action.setToolTip(tool.__doc__) if tool.SHORTCUT is not None: action.setShortcut(QKeySequence.fromString(tool.SHORTCUT)) if parent == self.toolbar_menu: action.setCheckable(True) action.setChecked(True) assert toolbar_action is not None action.toggled.connect(toolbar_action.setVisible) else: if parent == self.toolbar: toolbar_action = action action.triggered.connect(partial(self._activate_dialog, tool_plugin.name, args=())) self._dialog_class[tool_plugin.name] = tool engine.signal_connect('output_changed', self.on_output_changed) # Machine. self.machine_type.addItems( _(plugin.name) for plugin in registry.list_plugins('machine') ) engine.signal_connect('config_changed', self.on_config_changed) engine.signal_connect('machine_state_changed', lambda machine, state: self.machine_state.setText(_(state.capitalize())) ) self.restore_state() # Commands. engine.signal_connect('add_translation', partial(self._add_translation, manage_windows=True)) engine.signal_connect('focus', self._focus) engine.signal_connect('configure', partial(self._configure, manage_windows=True)) engine.signal_connect('lookup', partial(self._activate_dialog, 'lookup', manage_windows=True)) # Load the configuration (but do not start the engine yet). if not engine.load_config(): self.on_configure() # Apply configuration settings. config = self._engine.config self.machine_type.setCurrentText(config['machine_type']) self._configured = False self.dictionaries.on_config_changed(config) self.set_visible(not config['start_minimized']) # Start the engine. engine.start()
def __init__(self, engine): super().__init__() self.setupUi(self) self._engine = engine machines = { plugin.name: _(plugin.name) for plugin in registry.list_plugins('machine') } mappings = ( (_('Interface'), ( ConfigOption(_('Start minimized:'), 'start_minimized', BooleanOption, _('Minimize the main window to systray on startup.')), ConfigOption(_('Show paper tape:'), 'show_stroke_display', BooleanOption, _('Open the paper tape on startup.')), ConfigOption(_('Show suggestions:'), 'show_suggestions_display', BooleanOption, _('Open the suggestions dialog on startup.')), ConfigOption(_('Add translation dialog opacity:'), 'translation_frame_opacity', partial(IntOption, maximum=100, minimum=0), _('Set the translation dialog opacity:\n' '- 0 makes the dialog invisible\n' '- 100 is fully opaque')), ConfigOption(_('Dictionaries display order:'), 'classic_dictionaries_display_order', partial(BooleanAsDualChoiceOption, _('top-down'), _('bottom-up')), _('Set the display order for dictionaries:\n' '- top-down: match the search order; highest priority first\n' '- bottom-up: reverse search order; lowest priority first\n')), )), (_('Logging'), ( ConfigOption(_('Log file:'), 'log_file_name', partial(FileOption, _('Select a log file'), _('Log files') + ' (*.log)'), _('File to use for logging strokes/translations.')), ConfigOption(_('Log strokes:'), 'enable_stroke_logging', BooleanOption, _('Save strokes to the logfile.')), ConfigOption(_('Log translations:'), 'enable_translation_logging', BooleanOption, _('Save translations to the logfile.')), )), (_('Machine'), ( ConfigOption(_('Machine:'), 'machine_type', partial(ChoiceOption, choices=machines), dependents=( ('machine_specific_options', self._update_machine_options), ('system_keymap', lambda v: self._update_keymap(machine_type=v)), )), ConfigOption(_('Options:'), 'machine_specific_options', self._machine_option), ConfigOption(_('Keymap:'), 'system_keymap', KeymapOption), )), (_('Output'), ( ConfigOption(_('Enable at start:'), 'auto_start', BooleanOption, _('Enable output on startup.')), ConfigOption(_('Start attached:'), 'start_attached', BooleanOption, _('Disable preceding space on first output.\n' '\n' 'This option is only applicable when spaces are placed before.')), ConfigOption(_('Start capitalized:'), 'start_capitalized', BooleanOption, _('Capitalize the first word.')), ConfigOption(_('Space placement:'), 'space_placement', partial(ChoiceOption, choices={ 'Before Output': _('Before Output'), 'After Output': _('After Output'), }), _('Set automatic space placement: before or after each word.')), ConfigOption(_('Undo levels:'), 'undo_levels', partial(IntOption, maximum=10000, minimum=MINIMUM_UNDO_LEVELS), _('Set how many preceding strokes can be undone.\n' '\n' 'Note: the effective value will take into account the\n' 'dictionaries entry with the maximum number of strokes.')), )), (_('Plugins'), ( ConfigOption(_('Extension:'), 'enabled_extensions', partial(MultipleChoicesOption, choices={ plugin.name: plugin.name for plugin in registry.list_plugins('extension') }, labels=(_('Name'), _('Enabled'))), _('Configure enabled plugin extensions.')), )), (_('System'), ( ConfigOption(_('System:'), 'system_name', partial(ChoiceOption, choices={ plugin.name: plugin.name for plugin in registry.list_plugins('system') }), dependents=( ('system_keymap', lambda v: self._update_keymap(system_name=v)), )), )), ) # Only keep supported options, to avoid messing with things like # dictionaries, that are handled by another (possibly concurrent) # dialog. self._supported_options = set() for section, option_list in mappings: self._supported_options.update(option.option_name for option in option_list) self._update_config() # Create and fill tabs. options = {} for section, option_list in mappings: layout = QFormLayout() for option in option_list: widget = self._create_option_widget(option) options[option.option_name] = option option.tab_index = self.tabs.count() option.layout = layout option.widget = widget label = QLabel(option.display_name) label.setToolTip(option.help_text) layout.addRow(label, widget) frame = QFrame() frame.setLayout(layout) scroll_area = QScrollArea() scroll_area.setWidgetResizable(True) scroll_area.setWidget(frame) self.tabs.addTab(scroll_area, section) # Update dependents. for option in options.values(): option.dependents = [ (options[option_name], update_fn) for option_name, update_fn in option.dependents ] buttons = self.findChild(QWidget, 'buttons') buttons.button(QDialogButtonBox.Ok).clicked.connect(self.on_apply) buttons.button(QDialogButtonBox.Apply).clicked.connect(self.on_apply) self.restore_state() self.finished.connect(self.save_state)
def _dictionary_formats(include_readonly=True): return { plugin.name for plugin in registry.list_plugins('dictionary') if include_readonly or not plugin.obj.readonly }