Skip to content

Commit 68ea866

Browse files
authored
Implement dynamic status update of the dictionaries (#1400)
1 parent e39d5b9 commit 68ea866

File tree

9 files changed

+65
-42
lines changed

9 files changed

+65
-42
lines changed

news.d/feature/1400.ui.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Dynamically update individual status icons while loading dictionaries.

plover/dictionary/loading_manager.py

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,19 @@
77
import time
88

99
from plover.dictionary.base import load_dictionary
10-
from plover.exception import DictionaryLoaderException
1110
from plover.resource import resource_timestamp
1211
from plover import log
1312

1413

1514
class DictionaryLoadingManager:
16-
def __init__(self):
15+
def __init__(self, state_change_callback):
16+
"""
17+
Parameters:
18+
state_change_callback -- A function that will be called when any dictionary is loaded
19+
with two parameters: the filename and the loaded StenoDictionary object
20+
(or an instance of ErroredDictionary if the load fails).
21+
"""
22+
self._state_change_callback = state_change_callback
1723
self.dictionaries = {}
1824

1925
def __len__(self):
@@ -32,7 +38,7 @@ def start_loading(self, filename):
3238
log.info(
3339
"%s dictionary: %s", "loading" if op is None else "reloading", filename
3440
)
35-
op = DictionaryLoadingOperation(filename)
41+
op = DictionaryLoadingOperation(filename, self._state_change_callback)
3642
self.dictionaries[filename] = op
3743
return op
3844

@@ -52,7 +58,15 @@ def load(self, filenames):
5258

5359

5460
class DictionaryLoadingOperation:
55-
def __init__(self, filename):
61+
def __init__(self, filename, state_change_callback):
62+
"""
63+
Parameters:
64+
filename -- Path to dictionary file.
65+
state_change_callback -- A function that will be called when the load is finished
66+
with two parameters: the filename and the loaded StenoDictionary object
67+
(or an instance of ErroredDictionary if the load fails).
68+
"""
69+
self._state_change_callback = state_change_callback
5670
self.loading_thread = threading.Thread(target=self.load)
5771
self.filename = filename
5872
self.result = None
@@ -87,8 +101,11 @@ def load(self):
87101
self.result = load_dictionary(self.filename)
88102
except Exception as e:
89103
log.debug("loading dictionary %s failed", self.filename, exc_info=True)
90-
self.result = DictionaryLoaderException(self.filename, e)
104+
from plover.engine import ErroredDictionary
105+
106+
self.result = ErroredDictionary(self.filename, e)
91107
self.result.timestamp = timestamp
108+
self._state_change_callback(self.filename, self.result)
92109

93110
def get(self):
94111
self.loading_thread.join()

plover/engine.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
from collections import namedtuple, OrderedDict
22
from functools import wraps
33
from queue import Queue
4+
import functools
45
import os
56
import shutil
67
import threading
78

89
from plover import log, system
910
from plover.dictionary.loading_manager import DictionaryLoadingManager
10-
from plover.exception import DictionaryLoaderException
1111
from plover.formatting import Formatter
1212
from plover.misc import shorten_path
1313
from plover.registry import registry
@@ -83,6 +83,7 @@ class StenoEngine:
8383
output_changed
8484
config_changed
8585
dictionaries_loaded
86+
dictionary_state_changed
8687
send_string
8788
send_backspaces
8889
send_key_combination
@@ -116,8 +117,10 @@ def __init__(self, config, controller, keyboard_emulation):
116117
self._translator = Translator()
117118
self._translator.add_listener(log.translation)
118119
self._translator.add_listener(self._formatter.format)
119-
self._dictionaries = self._translator.get_dictionary()
120-
self._dictionaries_manager = DictionaryLoadingManager()
120+
self._dictionaries = self._translator.get_dictionary() # type: StenoDictionaryCollection
121+
self._dictionaries_manager = DictionaryLoadingManager(
122+
functools.partial(self._trigger_hook, "dictionary_state_changed")
123+
)
121124
self._running_state = self._translator.get_state()
122125
self._translator.clear_state()
123126
self._keyboard_emulation = keyboard_emulation
@@ -282,18 +285,15 @@ def _update(self, config_update=None, full=False, reset_machine=False):
282285
)
283286
# And then (re)load all dictionaries.
284287
dictionaries = []
285-
for result in self._dictionaries_manager.load(config_dictionaries.keys()):
286-
if isinstance(result, DictionaryLoaderException):
287-
d = ErroredDictionary(result.path, result.exception)
288+
for d in self._dictionaries_manager.load(config_dictionaries.keys()):
289+
if isinstance(d, ErroredDictionary):
288290
# Only show an error if it's new.
289-
if d != self._dictionaries.get(result.path):
291+
if d != self._dictionaries.get(d.path):
290292
log.error(
291293
"loading dictionary `%s` failed: %s",
292-
shorten_path(result.path),
293-
str(result.exception),
294+
shorten_path(d.path),
295+
str(d.exception),
294296
)
295-
else:
296-
d = result
297297
d.enabled = config_dictionaries[d.path].enabled
298298
dictionaries.append(d)
299299
self._set_dictionaries(dictionaries)

plover/exception.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,3 @@ class InvalidConfigurationError(Exception):
1313
"Raised when there is something wrong in the configuration."
1414

1515
pass
16-
17-
18-
class DictionaryLoaderException(Exception):
19-
"""Dictionary file could not be loaded."""
20-
21-
def __init__(self, path, exception):
22-
super().__init__(path, exception)
23-
self.path = path
24-
self.exception = exception
25-
26-
def __str__(self):
27-
return "loading dictionary `%s` failed: %s" % (self.path, self.exception)

plover/gui_qt/dictionaries_widget.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,9 @@ def __init__(self, engine, icons, max_undo=20):
132132
config = engine.config
133133
engine.signal_connect("config_changed", self._on_config_changed)
134134
engine.signal_connect("dictionaries_loaded", self._on_dictionaries_loaded)
135+
engine.signal_connect(
136+
"dictionary_state_changed", self._on_dictionary_state_changed
137+
)
135138
self._reset_items(
136139
config["dictionaries"],
137140
config["classic_dictionaries_display_order"],
@@ -248,6 +251,12 @@ def _on_dictionaries_loaded(self, loaded_dictionaries):
248251
updated_rows.update(self._update_favorite())
249252
self._updated_rows(updated_rows)
250253

254+
def _on_dictionary_state_changed(self, filename, d):
255+
[item] = [item for item in self._from_row if item.path == filename]
256+
if item.loaded != d:
257+
item.loaded = d
258+
self._updated_rows([item.row])
259+
251260
def _move(self, index_list, step):
252261
row_list = sorted(self._normalized_row_list(index_list))
253262
if not row_list:

plover/gui_qt/engine.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@ class Engine(StenoEngine, QThread):
1414
signal_machine_state_changed = Signal(str, str)
1515
signal_output_changed = Signal(bool)
1616
signal_config_changed = Signal(object)
17-
signal_dictionaries_loaded = Signal(object)
17+
signal_dictionary_state_changed = Signal(
18+
str, object
19+
) # Some dictionary has finished loading. Refer to class DictionaryLoadingManager for argument description.
20+
signal_dictionaries_loaded = Signal(
21+
object
22+
) # All dictionaries are loaded. Argument is a StenoDictionaryCollection instance.
1823
signal_send_string = Signal(str)
1924
signal_send_backspaces = Signal(int)
2025
signal_send_key_combination = Signal(str)

test/gui_qt/test_dictionaries_widget.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,11 @@ def model_test(monkeypatch, request):
222222
""".split():
223223
getattr(model, slot).connect(getattr(signals, slot))
224224
connections = dict(call.args for call in engine.signal_connect.mock_calls)
225-
assert connections.keys() == {"config_changed", "dictionaries_loaded"}
225+
assert connections.keys() == {
226+
"config_changed",
227+
"dictionaries_loaded",
228+
"dictionary_state_changed",
229+
}
226230
config.reset_mock()
227231
engine.reset_mock()
228232
# Test helper.

test/test_engine.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,11 @@ def test_engine_lifecycle(engine):
177177

178178
def test_loading_dictionaries(tmp_path, engine):
179179
def check_loaded_events(actual_events, expected_events):
180-
assert len(actual_events) == len(expected_events)
181-
for n, event in enumerate(actual_events):
180+
filtered_events = [
181+
event for event in actual_events if event[0] != "dictionary_state_changed"
182+
]
183+
assert len(filtered_events) == len(expected_events)
184+
for n, event in enumerate(filtered_events):
182185
event_type, event_args, event_kwargs = event
183186
msg = "event %u: %r" % (n, event)
184187
assert event_type == "dictionaries_loaded", msg
@@ -230,12 +233,8 @@ def check_loaded_events(actual_events, expected_events):
230233
(valid_dict_1, False),
231234
(invalid_dict_1, True),
232235
],
233-
[
234-
(valid_dict_1, False, False),
235-
(invalid_dict_1, True, True),
236-
],
237236
],
238-
# Replace invalid dictonary with another invalid one.
237+
# Replace invalid dictionary with another invalid one.
239238
[
240239
[
241240
(valid_dict_1, False),

test/test_loading_manager.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
import pytest
1111

12-
from plover.exception import DictionaryLoaderException
12+
from plover.engine import ErroredDictionary
1313
import plover.dictionary.loading_manager as loading_manager
1414

1515

@@ -66,7 +66,7 @@ def df(name):
6666

6767
loader = MockLoader(dictionaries)
6868
monkeypatch.setattr("plover.dictionary.loading_manager.load_dictionary", loader)
69-
manager = loading_manager.DictionaryLoadingManager()
69+
manager = loading_manager.DictionaryLoadingManager(lambda filename, result: None)
7070
manager.start_loading(df("a")).get()
7171
manager.start_loading(df("b")).get()
7272
results = manager.load([df("c"), df("b")])
@@ -82,15 +82,15 @@ def df(name):
8282
assert df("b") in manager
8383
assert df("c") in manager
8484
assert results == [manager[df("c")], manager[df("b")]]
85-
# Return a DictionaryLoaderException for load errors.
85+
# Return a ErroredDictionary for load errors.
8686
results = manager.load([df("c"), df("e"), df("b"), df("f")])
8787
assert len(results) == 4
8888
assert results[0] == "ccccc"
8989
assert results[2] == "bbbbb"
90-
assert isinstance(results[1], DictionaryLoaderException)
90+
assert isinstance(results[1], ErroredDictionary)
9191
assert results[1].path == df("e")
9292
assert isinstance(results[1].exception, Exception)
93-
assert isinstance(results[3], DictionaryLoaderException)
93+
assert isinstance(results[3], ErroredDictionary)
9494
assert results[3].path == df("f")
9595
assert isinstance(results[3].exception, Exception)
9696
# Only loaded the files once.

0 commit comments

Comments
 (0)