From c098a8aa9138483f5521f7ad3e28369f7946ccf9 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Fri, 16 Oct 2020 21:27:31 +0700 Subject: [PATCH 01/12] Add "Save as..." feature for dictionary conversion --- plover/gui_qt/dictionaries_table.py | 12 ++++++++++++ plover/gui_qt/dictionaries_widget.py | 16 ++++++++++++++++ plover/gui_qt/dictionaries_widget.ui | 9 ++++++++- 3 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 plover/gui_qt/dictionaries_table.py diff --git a/plover/gui_qt/dictionaries_table.py b/plover/gui_qt/dictionaries_table.py new file mode 100644 index 000000000..adc527134 --- /dev/null +++ b/plover/gui_qt/dictionaries_table.py @@ -0,0 +1,12 @@ +from PyQt5.QtWidgets import QWidget, QMenu, QAction, QTableWidget + +class DictionariesTable(QTableWidget): + def contextMenuEvent(self, event: "QContextMenuEvent"): + menu = QMenu(self) + saveAsAction = QAction("Save as...", self) + row = self.rowAt(event.y()) + assert row >= 0 + saveAsAction.triggered.connect(lambda: self.parent().on_save_as(row)) + menu.addAction(saveAsAction) + menu.popup(event.globalPos()) + diff --git a/plover/gui_qt/dictionaries_widget.py b/plover/gui_qt/dictionaries_widget.py index 2408274e6..5ed22f243 100644 --- a/plover/gui_qt/dictionaries_widget.py +++ b/plover/gui_qt/dictionaries_widget.py @@ -377,6 +377,22 @@ def _create_new_dictionary(self): self._update_dictionaries(dictionaries, keep_selection=False, loaded_dictionaries=self._loaded_dictionaries) + def on_save_as(self, row: int) -> None: + new_filename = QFileDialog.getSaveFileName( + self, _('Save dictionary as'), 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.update(self._loaded_dictionaries[self._config_dictionaries[row].path]) + d.save() + except: + log.error('creating dictionary %s failed', new_filename, exc_info=True) + return + def on_add_translation(self): selection = self._get_selection() if selection: diff --git a/plover/gui_qt/dictionaries_widget.ui b/plover/gui_qt/dictionaries_widget.ui index bdc028f91..341a4d326 100644 --- a/plover/gui_qt/dictionaries_widget.ui +++ b/plover/gui_qt/dictionaries_widget.ui @@ -33,7 +33,7 @@ 0 - + QFrame::Box @@ -173,6 +173,13 @@ + + + DictionariesTable + QTableWidget +
plover.gui_qt.dictionaries_table
+
+
From ad252b9c33e67388c8c793ad00874b01e33a3828 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Fri, 16 Oct 2020 22:44:03 +0700 Subject: [PATCH 02/12] Remove type hints --- plover/gui_qt/dictionaries_table.py | 2 +- plover/gui_qt/dictionaries_widget.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plover/gui_qt/dictionaries_table.py b/plover/gui_qt/dictionaries_table.py index adc527134..f0a3b37b7 100644 --- a/plover/gui_qt/dictionaries_table.py +++ b/plover/gui_qt/dictionaries_table.py @@ -1,7 +1,7 @@ from PyQt5.QtWidgets import QWidget, QMenu, QAction, QTableWidget class DictionariesTable(QTableWidget): - def contextMenuEvent(self, event: "QContextMenuEvent"): + def contextMenuEvent(self, event): menu = QMenu(self) saveAsAction = QAction("Save as...", self) row = self.rowAt(event.y()) diff --git a/plover/gui_qt/dictionaries_widget.py b/plover/gui_qt/dictionaries_widget.py index 5ed22f243..0e63b7d6a 100644 --- a/plover/gui_qt/dictionaries_widget.py +++ b/plover/gui_qt/dictionaries_widget.py @@ -377,7 +377,7 @@ def _create_new_dictionary(self): self._update_dictionaries(dictionaries, keep_selection=False, loaded_dictionaries=self._loaded_dictionaries) - def on_save_as(self, row: int) -> None: + def on_save_as(self, row): new_filename = QFileDialog.getSaveFileName( self, _('Save dictionary as'), None, _dictionary_filters(include_readonly=False), From 3791aa76c0b69b44c457d965cb1f755148343b95 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Fri, 16 Oct 2020 22:46:01 +0700 Subject: [PATCH 03/12] Reword context menu entry name --- plover/gui_qt/dictionaries_table.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plover/gui_qt/dictionaries_table.py b/plover/gui_qt/dictionaries_table.py index f0a3b37b7..4042039b9 100644 --- a/plover/gui_qt/dictionaries_table.py +++ b/plover/gui_qt/dictionaries_table.py @@ -3,7 +3,7 @@ class DictionariesTable(QTableWidget): def contextMenuEvent(self, event): menu = QMenu(self) - saveAsAction = QAction("Save as...", self) + saveAsAction = QAction("Save a Copy As...", self) row = self.rowAt(event.y()) assert row >= 0 saveAsAction.triggered.connect(lambda: self.parent().on_save_as(row)) From 39368759a72c23581246d653ee1624d5a29a18d3 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Fri, 16 Oct 2020 22:53:04 +0700 Subject: [PATCH 04/12] Set default file name of dictionary save-as feature --- plover/gui_qt/dictionaries_widget.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plover/gui_qt/dictionaries_widget.py b/plover/gui_qt/dictionaries_widget.py index 0e63b7d6a..040d53207 100644 --- a/plover/gui_qt/dictionaries_widget.py +++ b/plover/gui_qt/dictionaries_widget.py @@ -378,8 +378,9 @@ def _create_new_dictionary(self): loaded_dictionaries=self._loaded_dictionaries) def on_save_as(self, row): + dictionary_path = self._config_dictionaries[row].path new_filename = QFileDialog.getSaveFileName( - self, _('Save dictionary as'), None, + self, _('Save dictionary as'), dictionary_path, _dictionary_filters(include_readonly=False), )[0] if not new_filename: @@ -387,7 +388,7 @@ def on_save_as(self, row): new_filename = normalize_path(new_filename) try: d = create_dictionary(new_filename, threaded_save=False) - d.update(self._loaded_dictionaries[self._config_dictionaries[row].path]) + d.update(self._loaded_dictionaries[dictionary_path]) d.save() except: log.error('creating dictionary %s failed', new_filename, exc_info=True) From d42f5310e95a7692e764883348d8b30377ddf157 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Fri, 16 Oct 2020 22:54:09 +0700 Subject: [PATCH 05/12] Fix save-a-copy-as dialog box title accordingly --- plover/gui_qt/dictionaries_widget.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plover/gui_qt/dictionaries_widget.py b/plover/gui_qt/dictionaries_widget.py index 040d53207..bfa756696 100644 --- a/plover/gui_qt/dictionaries_widget.py +++ b/plover/gui_qt/dictionaries_widget.py @@ -380,7 +380,7 @@ def _create_new_dictionary(self): def on_save_as(self, row): dictionary_path = self._config_dictionaries[row].path new_filename = QFileDialog.getSaveFileName( - self, _('Save dictionary as'), dictionary_path, + self, _('Save a Copy As...'), dictionary_path, _dictionary_filters(include_readonly=False), )[0] if not new_filename: From db99fbf70f54af7387964d1fd54c109c1132fab7 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Fri, 16 Oct 2020 23:08:29 +0700 Subject: [PATCH 06/12] Disable save a copy as action if there are more than one rows selected --- plover/gui_qt/dictionaries_table.py | 10 ++-------- plover/gui_qt/dictionaries_widget.py | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/plover/gui_qt/dictionaries_table.py b/plover/gui_qt/dictionaries_table.py index 4042039b9..b7e8301c3 100644 --- a/plover/gui_qt/dictionaries_table.py +++ b/plover/gui_qt/dictionaries_table.py @@ -1,12 +1,6 @@ -from PyQt5.QtWidgets import QWidget, QMenu, QAction, QTableWidget +from PyQt5.QtWidgets import QTableWidget class DictionariesTable(QTableWidget): def contextMenuEvent(self, event): - menu = QMenu(self) - saveAsAction = QAction("Save a Copy As...", self) row = self.rowAt(event.y()) - assert row >= 0 - saveAsAction.triggered.connect(lambda: self.parent().on_save_as(row)) - menu.addAction(saveAsAction) - menu.popup(event.globalPos()) - + self.parent().on_table_context_menu(row, event.globalPos()) diff --git a/plover/gui_qt/dictionaries_widget.py b/plover/gui_qt/dictionaries_widget.py index bfa756696..be8e5102a 100644 --- a/plover/gui_qt/dictionaries_widget.py +++ b/plover/gui_qt/dictionaries_widget.py @@ -15,6 +15,7 @@ QMenu, QTableWidgetItem, QWidget, + QAction, ) from plover.config import DictionaryConfig @@ -377,6 +378,21 @@ def _create_new_dictionary(self): self._update_dictionaries(dictionaries, keep_selection=False, loaded_dictionaries=self._loaded_dictionaries) + def on_table_context_menu(self, row, global_pos): + if row == -1: + # when the user right-clicks in the empty area of the table + return + + menu = QMenu(self) + saveAsAction = QAction("Save a Copy As...", self) + saveAsAction.triggered.connect(lambda: self.on_save_as(row)) + + selected_rows = self._get_selection() + assert row in selected_rows + saveAsAction.setDisabled(len(selected_rows) != 1) + menu.addAction(saveAsAction) + menu.popup(global_pos) + def on_save_as(self, row): dictionary_path = self._config_dictionaries[row].path new_filename = QFileDialog.getSaveFileName( From aad67282a7c24c62eab8c50e7bef4fa30b556a97 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Sat, 17 Oct 2020 08:18:04 +0700 Subject: [PATCH 07/12] Apply suggested changes (single quote, i18n) --- plover/gui_qt/dictionaries_widget.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plover/gui_qt/dictionaries_widget.py b/plover/gui_qt/dictionaries_widget.py index be8e5102a..6f07b4acd 100644 --- a/plover/gui_qt/dictionaries_widget.py +++ b/plover/gui_qt/dictionaries_widget.py @@ -384,7 +384,7 @@ def on_table_context_menu(self, row, global_pos): return menu = QMenu(self) - saveAsAction = QAction("Save a Copy As...", self) + saveAsAction = QAction(_('Save a Copy As...'), self) saveAsAction.triggered.connect(lambda: self.on_save_as(row)) selected_rows = self._get_selection() From 188131b4153b336ed6d36a3d19af78e9a538d013 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Sat, 17 Oct 2020 10:34:58 +0700 Subject: [PATCH 08/12] Reload dictionaries on dictionary save-a-copy --- plover/gui_qt/dictionaries_widget.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plover/gui_qt/dictionaries_widget.py b/plover/gui_qt/dictionaries_widget.py index 6f07b4acd..458d68ca5 100644 --- a/plover/gui_qt/dictionaries_widget.py +++ b/plover/gui_qt/dictionaries_widget.py @@ -409,6 +409,10 @@ def on_save_as(self, row): except: log.error('creating dictionary %s failed', new_filename, exc_info=True) return + # Note: pass in `loaded_dictionaries` to force update (use case: + # the user decided to overwrite an already loaded dictionary). + self._update_dictionaries(self._config_dictionaries, + loaded_dictionaries=self._loaded_dictionaries) def on_add_translation(self): selection = self._get_selection() From a09eb02856b846d75ab05c1341442b4c14716adc Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Sat, 17 Oct 2020 11:42:49 +0700 Subject: [PATCH 09/12] Fix memory leak, keep a reference to the row to save instead of the row index --- plover/gui_qt/dictionaries_widget.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/plover/gui_qt/dictionaries_widget.py b/plover/gui_qt/dictionaries_widget.py index 458d68ca5..80bb01f20 100644 --- a/plover/gui_qt/dictionaries_widget.py +++ b/plover/gui_qt/dictionaries_widget.py @@ -383,9 +383,17 @@ def on_table_context_menu(self, row, global_pos): # when the user right-clicks in the empty area of the table return + dictionary_path = self._config_dictionaries[row].path menu = QMenu(self) saveAsAction = QAction(_('Save a Copy As...'), self) - saveAsAction.triggered.connect(lambda: self.on_save_as(row)) + saveAsAction.triggered.connect(lambda: self.on_save_as( + default_name=dictionary_path, + dictionary=self._loaded_dictionaries[dictionary_path])) + + def cleanup(): + menu.deleteLater() + saveAsAction.deleteLater() + menu.aboutToHide.connect(cleanup) selected_rows = self._get_selection() assert row in selected_rows @@ -393,10 +401,9 @@ def on_table_context_menu(self, row, global_pos): menu.addAction(saveAsAction) menu.popup(global_pos) - def on_save_as(self, row): - dictionary_path = self._config_dictionaries[row].path + def on_save_as(self, default_name, dictionary): new_filename = QFileDialog.getSaveFileName( - self, _('Save a Copy As...'), dictionary_path, + self, _('Save a Copy As...'), default_name, _dictionary_filters(include_readonly=False), )[0] if not new_filename: @@ -404,7 +411,7 @@ def on_save_as(self, row): new_filename = normalize_path(new_filename) try: d = create_dictionary(new_filename, threaded_save=False) - d.update(self._loaded_dictionaries[dictionary_path]) + d.update(dictionary) d.save() except: log.error('creating dictionary %s failed', new_filename, exc_info=True) From dc2903f918d0004c524e1f7ff94af91861d001f3 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Tue, 20 Oct 2020 14:50:17 +0700 Subject: [PATCH 10/12] Add towncrier snippet --- news.d/feature/1149.ui.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 news.d/feature/1149.ui.rst diff --git a/news.d/feature/1149.ui.rst b/news.d/feature/1149.ui.rst new file mode 100644 index 000000000..bb60ad0c0 --- /dev/null +++ b/news.d/feature/1149.ui.rst @@ -0,0 +1 @@ +Add "Save a Copy As..." feature to export dictionary to a different format. From ed5a0521778922b3fb7cc32ad4a2357640b6f43e Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Sun, 4 Apr 2021 13:55:14 +0700 Subject: [PATCH 11/12] Convert news snippet to Markdown --- news.d/feature/{1149.ui.rst => 1149.ui.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename news.d/feature/{1149.ui.rst => 1149.ui.md} (100%) diff --git a/news.d/feature/1149.ui.rst b/news.d/feature/1149.ui.md similarity index 100% rename from news.d/feature/1149.ui.rst rename to news.d/feature/1149.ui.md From 6143b1fcf66fc6e0db9756ffd62935fc76b63fef Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Sun, 4 Apr 2021 14:26:09 +0700 Subject: [PATCH 12/12] Implement 'Merge and Save a Copy As' feature --- plover/gui_qt/dictionaries_widget.py | 30 ++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/plover/gui_qt/dictionaries_widget.py b/plover/gui_qt/dictionaries_widget.py index 80bb01f20..b103df840 100644 --- a/plover/gui_qt/dictionaries_widget.py +++ b/plover/gui_qt/dictionaries_widget.py @@ -383,25 +383,38 @@ def on_table_context_menu(self, row, global_pos): # when the user right-clicks in the empty area of the table return - dictionary_path = self._config_dictionaries[row].path + selected_rows = self._get_selection() + assert list(selected_rows) == sorted(selected_rows) + if row not in selected_rows: + # in some cases (Ctrl+right click, for example), the current row might not be selected + return + menu = QMenu(self) - saveAsAction = QAction(_('Save a Copy As...'), self) + + # the path of the dictionary on the row the mouse clicked on + dictionary_path = self._config_dictionaries[row].path + + if len(selected_rows) == 1: + saveAsAction = QAction(_('Save a Copy As...'), self) + else: + saveAsAction = QAction(_('Merge and Save a Copy As...'), self) + saveAsAction.triggered.connect(lambda: self.on_save_as( default_name=dictionary_path, - dictionary=self._loaded_dictionaries[dictionary_path])) + dictionaries=[ + self._loaded_dictionaries[self._config_dictionaries[row].path] + for row in selected_rows] + )) def cleanup(): menu.deleteLater() saveAsAction.deleteLater() menu.aboutToHide.connect(cleanup) - selected_rows = self._get_selection() - assert row in selected_rows - saveAsAction.setDisabled(len(selected_rows) != 1) menu.addAction(saveAsAction) menu.popup(global_pos) - def on_save_as(self, default_name, dictionary): + def on_save_as(self, default_name, dictionaries): new_filename = QFileDialog.getSaveFileName( self, _('Save a Copy As...'), default_name, _dictionary_filters(include_readonly=False), @@ -411,7 +424,8 @@ def on_save_as(self, default_name, dictionary): new_filename = normalize_path(new_filename) try: d = create_dictionary(new_filename, threaded_save=False) - d.update(dictionary) + for dictionary in reversed(dictionaries): + d.update(dictionary) d.save() except: log.error('creating dictionary %s failed', new_filename, exc_info=True)