Skip to content

Commit

Permalink
Merge pull request #1244 from benoit-pierre/dictionaries_save_as
Browse files Browse the repository at this point in the history
Add dictionaries "save as..." support
  • Loading branch information
benoit-pierre authored Apr 12, 2021
2 parents 8b30891 + 3230f0d commit a66583f
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 22 deletions.
4 changes: 4 additions & 0 deletions news.d/feature/1244.ui.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Add support for saving dictionaries:
- save a copy of each selected dictionary
- merge the selected dictionaries into a new one
- both operations support converting to another format
149 changes: 127 additions & 22 deletions plover/gui_qt/dictionaries_widget.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@

from contextlib import contextmanager
import functools
import os

from PyQt5.QtCore import (
Expand All @@ -21,6 +22,7 @@
from plover.dictionary.base import create_dictionary
from plover.engine import ErroredDictionary
from plover.misc import normalize_path
from plover.oslayer.config import CONFIG_DIR
from plover.registry import registry
from plover import log

Expand All @@ -47,6 +49,15 @@ def _dictionary_filters(include_readonly=True):
)
return ';; '.join(filters)

@contextmanager
def _new_dictionary(filename):
try:
d = create_dictionary(filename, threaded_save=False)
yield d
d.save()
except Exception as e:
raise Exception('creating dictionary %s failed. %s' % (filename, e)) from e


class DictionariesWidget(QWidget, Ui_DictionariesWidget):

Expand All @@ -62,9 +73,12 @@ def __init__(self, *args, **kwargs):
self._config_dictionaries = {}
self._loaded_dictionaries = {}
self._reverse_order = False
# The save/open/new dialogs will open on that directory.
self._file_dialogs_directory = CONFIG_DIR
for action in (
self.action_Undo,
self.action_EditDictionaries,
self.action_SaveDictionaries,
self.action_RemoveDictionaries,
self.action_MoveDictionariesUp,
self.action_MoveDictionariesDown,
Expand All @@ -85,12 +99,22 @@ def __init__(self, *args, **kwargs):
# Add menu.
self.menu_AddDictionaries = QMenu(self.action_AddDictionaries.text())
self.menu_AddDictionaries.setIcon(self.action_AddDictionaries.icon())
self.menu_AddDictionaries.addAction(_(
self.menu_AddDictionaries.addAction(
_('Open dictionaries'),
)).triggered.connect(self._add_existing_dictionaries)
self.menu_AddDictionaries.addAction(_(
).triggered.connect(self._add_existing_dictionaries)
self.menu_AddDictionaries.addAction(
_('New dictionary'),
)).triggered.connect(self._create_new_dictionary)
).triggered.connect(self._create_new_dictionary)
# Save menu.
self.menu_SaveDictionaries = QMenu(self.action_SaveDictionaries.text())
self.menu_SaveDictionaries.setIcon(self.action_SaveDictionaries.icon())
self.menu_SaveDictionaries.addAction(
_('Create a copy of each dictionary'),
).triggered.connect(self._save_dictionaries)
self.menu_SaveDictionaries.addAction(
_('Merge dictionaries into a new one'),
).triggered.connect(functools.partial(self._save_dictionaries,
merge=True))
self.table.supportedDropActions = self._supported_drop_actions
self.table.dragEnterEvent = self._drag_enter_event
self.table.dragMoveEvent = self._drag_move_event
Expand Down Expand Up @@ -288,14 +312,24 @@ def _set_selection(self, row_list):
def on_selection_changed(self):
if self._updating:
return
enabled = bool(self.table.selectedItems())
for action in (
selection = self._get_selection()
has_selection = bool(selection)
for widget in (
self.action_RemoveDictionaries,
self.action_EditDictionaries,
self.action_MoveDictionariesUp,
self.action_MoveDictionariesDown,
):
action.setEnabled(enabled)
widget.setEnabled(has_selection)
has_live_selection = any(
self._config_dictionaries[row].path in self._loaded_dictionaries
for row in selection
)
for widget in (
self.action_EditDictionaries,
self.action_SaveDictionaries,
self.menu_SaveDictionaries,
):
widget.setEnabled(has_live_selection)

def on_dictionary_changed(self, item):
if self._updating:
Expand Down Expand Up @@ -327,6 +361,82 @@ def on_edit_dictionaries(self):
assert selection
self._edit([self._config_dictionaries[row] for row in selection])

def _get_dictionary_save_name(self, title, default_name=None,
default_extensions=(), initial_filename=None):
if default_name is not None:
# Default to a writable dictionary format.
writable_extensions = set(_dictionary_formats(include_readonly=False))
default_name += '.' + next((e for e in default_extensions
if e in writable_extensions),
'json')
default_name = os.path.join(self._file_dialogs_directory, default_name)
else:
default_name = self._file_dialogs_directory
new_filename = QFileDialog.getSaveFileName(
parent=self, caption=title, directory=default_name,
filter=_dictionary_filters(include_readonly=False),
)[0]
if not new_filename:
return None
new_filename = normalize_path(new_filename)
self._file_dialogs_directory = os.path.dirname(new_filename)
if new_filename == initial_filename:
return None
return new_filename

def _copy_dictionaries(self, dictionaries_list):
need_reload = False
title_template = _('Save a copy of {name} as...')
default_name_template = _('{name} - Copy')
for dictionary in dictionaries_list:
title = title_template.format(name=dictionary.short_path)
name, ext = os.path.splitext(os.path.basename(dictionary.path))
default_name = default_name_template.format(name=name)
new_filename = self._get_dictionary_save_name(title, default_name, [ext[1:]],
initial_filename=dictionary.path)
if new_filename is None:
continue
with _new_dictionary(new_filename) as d:
d.update(self._loaded_dictionaries[dictionary.path])
need_reload = True
return need_reload

def _merge_dictionaries(self, dictionaries_list):
names, exts = zip(*(
os.path.splitext(os.path.basename(d.path))
for d in dictionaries_list))
default_name = ' + '.join(names)
default_exts = list(dict.fromkeys(e[1:] for e in exts))
title = _('Merge {names} as...').format(names=default_name)
new_filename = self._get_dictionary_save_name(title, default_name, default_exts)
if new_filename is None:
return False
with _new_dictionary(new_filename) as d:
# Merge in reverse priority order, so higher
# priority entries overwrite lower ones.
for dictionary in reversed(dictionaries_list):
d.update(self._loaded_dictionaries[dictionary.path])
return True

def _save_dictionaries(self, merge=False):
selection = self._get_selection()
assert selection
dictionaries_list = [self._config_dictionaries[row]
for row in selection]
# Ignore dictionaries that are not loaded.
dictionaries_list = [dictionary
for dictionary in dictionaries_list
if dictionary.path in self._loaded_dictionaries]
if not dictionaries_list:
return
if merge:
save_fn = self._merge_dictionaries
else:
save_fn = self._copy_dictionaries
if save_fn(dictionaries_list):
# This will trigger a reload of any modified dictionary.
self._engine.config = {}

def on_remove_dictionaries(self):
selection = self._get_selection()
assert selection
Expand All @@ -340,11 +450,14 @@ def on_add_dictionaries(self):

def _add_existing_dictionaries(self):
new_filenames = QFileDialog.getOpenFileNames(
self, _('Add dictionaries'), None, _dictionary_filters(),
parent=self, caption=_('Add dictionaries'),
directory=self._file_dialogs_directory,
filter=_dictionary_filters(),
)[0]
dictionaries = self._config_dictionaries[:]
for filename in new_filenames:
filename = normalize_path(filename)
self._file_dialogs_directory = os.path.dirname(filename)
for d in dictionaries:
if d.path == filename:
break
Expand All @@ -353,19 +466,11 @@ def _add_existing_dictionaries(self):
self._update_dictionaries(dictionaries, keep_selection=False)

def _create_new_dictionary(self):
new_filename = QFileDialog.getSaveFileName(
self, _('New dictionary'), None,
_dictionary_filters(include_readonly=False),
)[0]
if not new_filename:
return
new_filename = normalize_path(new_filename)
try:
d = create_dictionary(new_filename, threaded_save=False)
d.save()
except:
log.error('creating dictionary %s failed', new_filename, exc_info=True)
new_filename = self._get_dictionary_save_name(_('New dictionary'))
if new_filename is None:
return
with _new_dictionary(new_filename) as d:
pass
dictionaries = self._config_dictionaries[:]
for d in dictionaries:
if d.path == new_filename:
Expand Down
15 changes: 15 additions & 0 deletions plover/gui_qt/dictionaries_widget.ui
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,21 @@
<string>Ctrl+E</string>
</property>
</action>
<action name="action_SaveDictionaries">
<property name="icon">
<iconset resource="resources/resources.qrc">
<normaloff>:/save.svg</normaloff>:/save.svg</iconset>
</property>
<property name="text">
<string>&amp;Save dictionaries as...</string>
</property>
<property name="toolTip">
<string>Save the selected dictionaries: create a new copy of each dictionary, or merge them into a new dictionary.</string>
</property>
<property name="shortcut">
<string>Ctrl+S</string>
</property>
</action>
<action name="action_RemoveDictionaries">
<property name="icon">
<iconset resource="resources/resources.qrc">
Expand Down
4 changes: 4 additions & 0 deletions plover/gui_qt/main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,14 @@ def __init__(self, engine, use_qt_notifications):
edit_menu.addSeparator()
edit_menu.addMenu(self.dictionaries.menu_AddDictionaries)
edit_menu.addAction(self.dictionaries.action_EditDictionaries)
edit_menu.addMenu(self.dictionaries.menu_SaveDictionaries)
edit_menu.addAction(self.dictionaries.action_RemoveDictionaries)
edit_menu.addSeparator()
edit_menu.addAction(self.dictionaries.action_MoveDictionariesUp)
edit_menu.addAction(self.dictionaries.action_MoveDictionariesDown)
self.dictionaries.setContextMenuPolicy(Qt.CustomContextMenu)
self.dictionaries.customContextMenuRequested.connect(
lambda p: edit_menu.exec_(self.dictionaries.mapToGlobal(p)))
# Tray icon.
self._trayicon = TrayIcon()
self._trayicon.enable()
Expand Down

0 comments on commit a66583f

Please sign in to comment.