plover 5.1.0__py3-none-any.whl → 5.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. plover/__init__.py +5 -3
  2. plover/config.py +58 -14
  3. plover/dictionary/loading_manager.py +1 -1
  4. plover/engine.py +205 -37
  5. plover/formatting.py +5 -2
  6. plover/gui_qt/about_dialog_ui.py +1 -1
  7. plover/gui_qt/add_translation_dialog_ui.py +1 -1
  8. plover/gui_qt/add_translation_widget_ui.py +1 -1
  9. plover/gui_qt/appearance.py +49 -0
  10. plover/gui_qt/config_file_widget_ui.py +1 -1
  11. plover/gui_qt/config_keyboard_widget_ui.py +1 -1
  12. plover/gui_qt/config_plover_hid_widget_ui.py +1 -1
  13. plover/gui_qt/config_serial_widget_ui.py +2 -11
  14. plover/gui_qt/config_window.py +139 -33
  15. plover/gui_qt/config_window_ui.py +2 -2
  16. plover/gui_qt/console_widget.py +1 -1
  17. plover/gui_qt/console_widget_ui.py +1 -1
  18. plover/gui_qt/dictionaries_widget_ui.py +1 -1
  19. plover/gui_qt/dictionary_editor_ui.py +1 -1
  20. plover/gui_qt/lookup_dialog_ui.py +1 -1
  21. plover/gui_qt/machine_options.py +1 -19
  22. plover/gui_qt/main_window.py +13 -1
  23. plover/gui_qt/main_window_ui.py +15 -8
  24. plover/gui_qt/paper_tape_ui.py +1 -1
  25. plover/gui_qt/plugins_manager.py +9 -4
  26. plover/gui_qt/plugins_manager_ui.py +2 -2
  27. plover/gui_qt/resources_rc.py +29 -29
  28. plover/gui_qt/run_dialog_ui.py +1 -1
  29. plover/gui_qt/suggestions_dialog_ui.py +1 -1
  30. plover/gui_qt/trayicon.py +0 -2
  31. plover/gui_qt/utils.py +0 -1
  32. plover/i18n.py +1 -1
  33. plover/key_combo.py +3 -1
  34. plover/log.py +2 -2
  35. plover/machine/base.py +12 -2
  36. plover/machine/keyboard_capture/__init__.py +33 -10
  37. plover/machine/keymap.py +86 -10
  38. plover/machine/plover_hid.py +46 -25
  39. plover/machine/procat.py +0 -3
  40. plover/machine/stentura.py +5 -5
  41. plover/messages/es/LC_MESSAGES/plover.mo +0 -0
  42. plover/messages/es/LC_MESSAGES/plover.po +2 -2
  43. plover/messages/fr/LC_MESSAGES/plover.mo +0 -0
  44. plover/messages/fr/LC_MESSAGES/plover.po +2 -2
  45. plover/messages/it/LC_MESSAGES/plover.mo +0 -0
  46. plover/messages/it/LC_MESSAGES/plover.po +2 -2
  47. plover/messages/nl/LC_MESSAGES/plover.mo +0 -0
  48. plover/messages/nl/LC_MESSAGES/plover.po +2 -2
  49. plover/messages/plover.pot +1 -1
  50. plover/messages/zh_tw/LC_MESSAGES/plover.mo +0 -0
  51. plover/messages/zh_tw/LC_MESSAGES/plover.po +2 -2
  52. plover/orthography.py +2 -1
  53. plover/oslayer/controller.py +1 -1
  54. plover/oslayer/linux/keyboardcontrol.py +4 -2
  55. plover/oslayer/linux/keyboardcontrol_uinput.py +107 -343
  56. plover/oslayer/linux/keyboardcontrol_x11.py +22 -13
  57. plover/oslayer/linux/keyboardlayout_wayland.py +403 -0
  58. plover/oslayer/linux/log.py +3 -1
  59. plover/oslayer/linux/wayland_connection.py +293 -0
  60. plover/oslayer/osx/keyboardlayout.py +1 -1
  61. plover/plugins_manager/registry.py +4 -3
  62. plover/registry.py +1 -1
  63. plover/scripts/main.py +6 -3
  64. plover/system/__init__.py +7 -7
  65. {plover-5.1.0.dist-info → plover-5.2.0.dist-info}/METADATA +5 -4
  66. {plover-5.1.0.dist-info → plover-5.2.0.dist-info}/RECORD +76 -74
  67. {plover-5.1.0.dist-info → plover-5.2.0.dist-info}/WHEEL +1 -1
  68. plover_build_utils/check_requirements.py +0 -3
  69. plover_build_utils/functions.sh +0 -11
  70. plover_build_utils/testing/__init__.py +9 -0
  71. plover_build_utils/testing/blackbox.py +3 -1
  72. plover_build_utils/testing/steno_dictionary.py +2 -2
  73. plover_build_utils/deps.sh +0 -2
  74. {plover-5.1.0.dist-info → plover-5.2.0.dist-info}/entry_points.txt +0 -0
  75. {plover-5.1.0.dist-info → plover-5.2.0.dist-info}/licenses/LICENSE.txt +0 -0
  76. {plover-5.1.0.dist-info → plover-5.2.0.dist-info}/top_level.txt +0 -0
  77. {plover-5.1.0.dist-info → plover-5.2.0.dist-info}/zip-safe +0 -0
@@ -0,0 +1,49 @@
1
+ from typing import Optional
2
+ from plover import log
3
+
4
+ from PySide6.QtCore import Qt
5
+ from PySide6.QtGui import QGuiApplication
6
+
7
+ MODE_SYSTEM = "system"
8
+ MODE_LIGHT = "light"
9
+ MODE_DARK = "dark"
10
+
11
+
12
+ def _set_mode(mode: Optional[str]) -> None:
13
+ """Apply the requested mode to the current Qt application.
14
+
15
+ This function tweaks Qt's idea of the platform color scheme via
16
+ QStyleHints.setColorScheme / unsetColorScheme so that other code can
17
+ query styleHints().colorScheme() and get the effective setting.
18
+
19
+ - "system": follow the operating system setting again
20
+ - "light": force a light color scheme
21
+ - "dark": force a dark color scheme
22
+ - None: no-op
23
+ """
24
+
25
+ if mode is None:
26
+ # Explicitly do nothing when no mode was provided.
27
+ return
28
+
29
+ app = QGuiApplication.instance()
30
+ if not isinstance(app, QGuiApplication):
31
+ # No GUI application instance (or unexpected type); nothing to do.
32
+ return
33
+
34
+ # Ensure the style is initialized.
35
+ hints = app.styleHints()
36
+
37
+ if mode == MODE_SYSTEM:
38
+ # Remove any explicit override so Qt follows the OS again.
39
+ hints.unsetColorScheme()
40
+ elif mode == MODE_LIGHT:
41
+ hints.setColorScheme(Qt.ColorScheme.Light)
42
+ elif mode == MODE_DARK:
43
+ hints.setColorScheme(Qt.ColorScheme.Dark)
44
+ else:
45
+ log.warning(f"invalid mode config value: {mode}")
46
+
47
+
48
+ def update(config):
49
+ _set_mode(config.get("appearance_mode"))
@@ -4,7 +4,7 @@ _ = __import__(__package__.split(".", 1)[0])._
4
4
  ################################################################################
5
5
  ## Form generated from reading UI file 'config_file_widget.ui'
6
6
  ##
7
- ## Created by: Qt User Interface Compiler version 6.10.0
7
+ ## Created by: Qt User Interface Compiler version 6.10.1
8
8
  ##
9
9
  ## WARNING! All changes made in this file will be lost when recompiling UI file!
10
10
  ################################################################################
@@ -4,7 +4,7 @@ _ = __import__(__package__.split(".", 1)[0])._
4
4
  ################################################################################
5
5
  ## Form generated from reading UI file 'config_keyboard_widget.ui'
6
6
  ##
7
- ## Created by: Qt User Interface Compiler version 6.10.0
7
+ ## Created by: Qt User Interface Compiler version 6.10.1
8
8
  ##
9
9
  ## WARNING! All changes made in this file will be lost when recompiling UI file!
10
10
  ################################################################################
@@ -4,7 +4,7 @@ _ = __import__(__package__.split(".", 1)[0])._
4
4
  ################################################################################
5
5
  ## Form generated from reading UI file 'config_plover_hid_widget.ui'
6
6
  ##
7
- ## Created by: Qt User Interface Compiler version 6.10.0
7
+ ## Created by: Qt User Interface Compiler version 6.10.1
8
8
  ##
9
9
  ## WARNING! All changes made in this file will be lost when recompiling UI file!
10
10
  ################################################################################
@@ -4,7 +4,7 @@ _ = __import__(__package__.split(".", 1)[0])._
4
4
  ################################################################################
5
5
  ## Form generated from reading UI file 'config_serial_widget.ui'
6
6
  ##
7
- ## Created by: Qt User Interface Compiler version 6.10.0
7
+ ## Created by: Qt User Interface Compiler version 6.10.1
8
8
  ##
9
9
  ## WARNING! All changes made in this file will be lost when recompiling UI file!
10
10
  ################################################################################
@@ -159,13 +159,6 @@ class Ui_SerialWidget(object):
159
159
 
160
160
  self.gridLayout.addWidget(self.timeout, 0, 1, 1, 1)
161
161
 
162
- self.use_timeout = QCheckBox(self.groupBox_3)
163
- self.use_timeout.setObjectName(u"use_timeout")
164
- sizePolicy3.setHeightForWidth(self.use_timeout.sizePolicy().hasHeightForWidth())
165
- self.use_timeout.setSizePolicy(sizePolicy3)
166
-
167
- self.gridLayout.addWidget(self.use_timeout, 1, 0, 1, 2)
168
-
169
162
 
170
163
  self.horizontalLayout_2.addWidget(self.groupBox_3)
171
164
 
@@ -209,7 +202,6 @@ class Ui_SerialWidget(object):
209
202
  self.parity.textActivated.connect(SerialWidget.update_parity)
210
203
  self.port.editTextChanged.connect(SerialWidget.update_port)
211
204
  self.stopbits.textActivated.connect(SerialWidget.update_stopbits)
212
- self.use_timeout.clicked["bool"].connect(SerialWidget.update_use_timeout)
213
205
  self.xonxoff.clicked["bool"].connect(SerialWidget.update_xonxoff)
214
206
  self.rtscts.clicked["bool"].connect(SerialWidget.update_rtscts)
215
207
  self.timeout.valueChanged.connect(SerialWidget.update_timeout)
@@ -254,14 +246,13 @@ class Ui_SerialWidget(object):
254
246
  self.groupBox_3.setAccessibleName("")
255
247
  #endif // QT_CONFIG(accessibility)
256
248
  self.groupBox_3.setTitle(QCoreApplication.translate("SerialWidget", u"Timeout", None))
257
- self.label_6.setText(QCoreApplication.translate("SerialWidget", u"Duration", None))
249
+ self.label_6.setText(QCoreApplication.translate("SerialWidget", u"Duration (s)", None))
258
250
  #if QT_CONFIG(accessibility)
259
251
  self.timeout.setAccessibleName(QCoreApplication.translate("SerialWidget", u"Duration", None))
260
252
  #endif // QT_CONFIG(accessibility)
261
253
  #if QT_CONFIG(accessibility)
262
254
  self.timeout.setAccessibleDescription(QCoreApplication.translate("SerialWidget", u"Timeout duration in seconds.", None))
263
255
  #endif // QT_CONFIG(accessibility)
264
- self.use_timeout.setText(QCoreApplication.translate("SerialWidget", u"Use timeout", None))
265
256
  self.groupBox_4.setTitle(QCoreApplication.translate("SerialWidget", u"Flow control", None))
266
257
  self.xonxoff.setText(QCoreApplication.translate("SerialWidget", u"Xon/Xoff", None))
267
258
  self.rtscts.setText(QCoreApplication.translate("SerialWidget", u"RTS/CTS", None))
@@ -7,7 +7,10 @@ from PySide6.QtCore import (
7
7
  Signal,
8
8
  Slot,
9
9
  )
10
+ from typing import Set
11
+
10
12
  from PySide6.QtWidgets import (
13
+ QAbstractScrollArea,
11
14
  QCheckBox,
12
15
  QComboBox,
13
16
  QDialog,
@@ -15,9 +18,11 @@ from PySide6.QtWidgets import (
15
18
  QFileDialog,
16
19
  QFormLayout,
17
20
  QFrame,
21
+ QHeaderView,
18
22
  QGroupBox,
19
23
  QLabel,
20
24
  QScrollArea,
25
+ QSizePolicy,
21
26
  QSpinBox,
22
27
  QStyledItemDelegate,
23
28
  QTableWidget,
@@ -26,8 +31,10 @@ from PySide6.QtWidgets import (
26
31
 
27
32
  from plover import _
28
33
  from plover.config import MINIMUM_UNDO_LEVELS, MINIMUM_TIME_BETWEEN_KEY_PRESSES
34
+ from plover.gui_qt import appearance
29
35
  from plover.misc import expand_path, shorten_path
30
36
  from plover.registry import registry
37
+ from plover.oslayer.config import PLATFORM
31
38
 
32
39
  from plover.gui_qt.config_window_ui import Ui_ConfigWindow
33
40
  from plover.gui_qt.config_file_widget_ui import Ui_FileWidget
@@ -118,7 +125,7 @@ class FileOption(QGroupBox, Ui_FileWidget):
118
125
  class TableOption(QTableWidget):
119
126
  def __init__(self):
120
127
  super().__init__()
121
- self.horizontalHeader().setStretchLastSection(True)
128
+ self.horizontalHeader().setStretchLastSection(False)
122
129
  self.setSelectionMode(self.SelectionMode.SingleSelection)
123
130
  self.setTabKeyNavigation(False)
124
131
  self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
@@ -210,10 +217,10 @@ class MultipleChoicesOption(TableOption):
210
217
  valueChanged = Signal(object)
211
218
 
212
219
  LABELS = (
213
- # i18n: Widget: “MultipleChoicesOption”.
214
- _("Choice"),
215
220
  # i18n: Widget: “MultipleChoicesOption”.
216
221
  _("Selected"),
222
+ # i18n: Widget: “MultipleChoicesOption”.
223
+ _("Choice"),
217
224
  )
218
225
 
219
226
  # i18n: Widget: “MultipleChoicesOption”.
@@ -221,20 +228,29 @@ class MultipleChoicesOption(TableOption):
221
228
  super().__init__()
222
229
  if labels is None:
223
230
  labels = self.LABELS
224
- self._value = {}
231
+ self._value: Set[str] = set()
225
232
  self._updating = False
226
233
  self._choices = {} if choices is None else choices
227
234
  self._reversed_choices = {
228
235
  translation: choice for choice, translation in choices.items()
229
236
  }
237
+ self.setSizeAdjustPolicy(
238
+ QAbstractScrollArea.SizeAdjustPolicy.AdjustToContentsOnFirstShow
239
+ )
240
+ self.setSizePolicy(
241
+ QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
242
+ )
230
243
  self.setColumnCount(2)
231
244
  self.setHorizontalHeaderLabels(labels)
245
+ header = self.horizontalHeader()
246
+ header.setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents)
247
+ header.setSectionResizeMode(1, QHeaderView.ResizeMode.ResizeToContents)
248
+ header.setStretchLastSection(True)
249
+ header.setStretchLastSection(True)
232
250
  self.cellChanged.connect(self._on_cell_changed)
233
251
 
234
252
  def setValue(self, value):
235
253
  self._updating = True
236
- self.resizeColumnsToContents()
237
- self.setMinimumSize(self.viewportSizeHint())
238
254
  self.setRowCount(0)
239
255
  if value is None:
240
256
  value = set()
@@ -246,9 +262,6 @@ class MultipleChoicesOption(TableOption):
246
262
  for choice in sorted(self._reversed_choices):
247
263
  row += 1
248
264
  self.insertRow(row)
249
- item = QTableWidgetItem(self._choices[choice])
250
- item.setFlags(item.flags() & ~Qt.ItemFlag.ItemIsEditable)
251
- self.setItem(row, 0, item)
252
265
  item = QTableWidgetItem()
253
266
  item.setFlags(
254
267
  (item.flags() & ~Qt.ItemFlag.ItemIsEditable)
@@ -257,19 +270,28 @@ class MultipleChoicesOption(TableOption):
257
270
  item.setCheckState(
258
271
  Qt.CheckState.Checked if choice in value else Qt.CheckState.Unchecked
259
272
  )
273
+ self.setItem(row, 0, item)
274
+ item = QTableWidgetItem(self._choices[choice])
275
+ item.setFlags(item.flags() & ~Qt.ItemFlag.ItemIsEditable)
260
276
  self.setItem(row, 1, item)
261
277
  self.resizeColumnsToContents()
262
- self.setMinimumSize(self.viewportSizeHint())
263
278
  self._updating = False
264
279
 
265
280
  def _on_cell_changed(self, row, column):
266
281
  if self._updating:
267
282
  return
268
- assert column == 1
269
- choice = self._reversed_choices[
270
- self.item(row, 0).data(Qt.ItemDataRole.DisplayRole)
271
- ]
272
- if self.item(row, 1).checkState():
283
+ assert column == 0
284
+ name_item = self.item(row, 1)
285
+ state_item = self.item(row, 0)
286
+ if name_item is None or state_item is None:
287
+ return
288
+ name = name_item.data(Qt.ItemDataRole.DisplayRole)
289
+ if name is None:
290
+ return
291
+ choice = self._reversed_choices.get(name)
292
+ if choice is None:
293
+ return
294
+ if state_item.checkState() == Qt.CheckState.Checked:
273
295
  self._value.add(choice)
274
296
  else:
275
297
  self._value.discard(choice)
@@ -284,9 +306,23 @@ class BooleanAsDualChoiceOption(ChoiceOption):
284
306
  super().__init__(choices)
285
307
 
286
308
 
309
+ class TextWrapQLabel(QLabel):
310
+ """Simple QLabel wrapper that enables wordWrap"""
311
+
312
+ def __init__(self, *args):
313
+ super().__init__(*args)
314
+ self.setWordWrap(True)
315
+
316
+
287
317
  class ConfigOption:
288
318
  def __init__(
289
- self, display_name, option_name, widget_class, help_text="", dependents=()
319
+ self,
320
+ display_name,
321
+ option_name,
322
+ widget_class,
323
+ help_text="",
324
+ dependents=(),
325
+ additional_widget_classes=[],
290
326
  ):
291
327
  self.display_name = display_name
292
328
  self.option_name = option_name
@@ -296,6 +332,9 @@ class ConfigOption:
296
332
  self.layout = None
297
333
  self.widget = None
298
334
  self.label = None
335
+ # Other widgets to be added immediately after the main widget_class
336
+ # Does not work in dependents
337
+ self.additional_widget_classes = additional_widget_classes
299
338
 
300
339
 
301
340
  class ConfigWindow(QDialog, Ui_ConfigWindow, WindowStateMixin):
@@ -313,6 +352,24 @@ class ConfigWindow(QDialog, Ui_ConfigWindow, WindowStateMixin):
313
352
  (
314
353
  _("Interface"),
315
354
  (
355
+ ConfigOption(
356
+ _("Appearance:"),
357
+ "appearance_mode",
358
+ partial(
359
+ ChoiceOption,
360
+ choices={
361
+ "system": _("System"),
362
+ "light": _("Light"),
363
+ "dark": _("Dark"),
364
+ },
365
+ ),
366
+ _(
367
+ "Set the application appearance:\n"
368
+ "- System: follow the operating system mode\n"
369
+ "- Light: force light mode\n"
370
+ "- Dark: force dark mode"
371
+ ),
372
+ ),
316
373
  ConfigOption(
317
374
  _("Start minimized:"),
318
375
  "start_minimized",
@@ -481,12 +538,26 @@ class ConfigWindow(QDialog, Ui_ConfigWindow, WindowStateMixin):
481
538
  "colemak": "colemak",
482
539
  "colemak-dh": "colemak-dh",
483
540
  "dvorak": "dvorak",
541
+ "wayland-auto": "wayland-auto",
484
542
  },
485
543
  ),
486
544
  _(
487
- "Set the keyboard layout configurad in your system.\n"
488
- "This only applies when using Linux/BSD and not using X11."
545
+ "Set the keyboard layout configured in your system.\n"
546
+ "This only applies when using Linux/BSD and not using X11.\n\n"
547
+ "When wayland-auto is selected,"
548
+ "Plover is only able detect the first keyboard layout\n"
549
+ "and can not detect to layout switches."
489
550
  ),
551
+ additional_widget_classes=[
552
+ partial(
553
+ TextWrapQLabel,
554
+ _(
555
+ "When wayland-auto is selected, "
556
+ "Plover is only able detect the first keyboard layout "
557
+ "and can not detect to layout switches."
558
+ ),
559
+ )
560
+ ],
490
561
  ),
491
562
  ),
492
563
  ),
@@ -495,7 +566,7 @@ class ConfigWindow(QDialog, Ui_ConfigWindow, WindowStateMixin):
495
566
  _("Plugins"),
496
567
  (
497
568
  ConfigOption(
498
- _("Extension:"),
569
+ _("Extensions:"),
499
570
  "enabled_extensions",
500
571
  partial(
501
572
  MultipleChoicesOption,
@@ -503,7 +574,7 @@ class ConfigWindow(QDialog, Ui_ConfigWindow, WindowStateMixin):
503
574
  plugin.name: plugin.name
504
575
  for plugin in registry.list_plugins("extension")
505
576
  },
506
- labels=(_("Name"), _("Enabled")),
577
+ labels=(_("Enabled"), _("Name")),
507
578
  ),
508
579
  _("Configure enabled plugin extensions."),
509
580
  ),
@@ -539,7 +610,7 @@ class ConfigWindow(QDialog, Ui_ConfigWindow, WindowStateMixin):
539
610
  self._supported_options = set()
540
611
  for section, option_list in mappings:
541
612
  self._supported_options.update(option.option_name for option in option_list)
542
- self._update_config()
613
+ self._load_config()
543
614
  # Create and fill tabs.
544
615
  option_by_name = {}
545
616
  for section, option_list in mappings:
@@ -552,6 +623,8 @@ class ConfigWindow(QDialog, Ui_ConfigWindow, WindowStateMixin):
552
623
  option.label.setToolTip(option.help_text)
553
624
  option.label.setBuddy(option.widget)
554
625
  layout.addRow(option.label, option.widget)
626
+ for additional_widget_class in option.additional_widget_classes:
627
+ layout.addRow(None, additional_widget_class())
555
628
  frame = QFrame()
556
629
  frame.setLayout(layout)
557
630
  frame.setAccessibleName(section)
@@ -561,6 +634,29 @@ class ConfigWindow(QDialog, Ui_ConfigWindow, WindowStateMixin):
561
634
  scroll_area.setWidget(frame)
562
635
  scroll_area.setFocusProxy(frame)
563
636
  self.tabs.addTab(scroll_area, section)
637
+
638
+ # platform specific overwrites
639
+ if PLATFORM == "linux":
640
+ # Appearance overwrite doesn't work on Linux so we're not showing the option
641
+ appearance_option = option_by_name.get("appearance_mode")
642
+ if appearance_option is not None:
643
+ appearance_option.label.hide()
644
+ appearance_option.widget.hide()
645
+ if PLATFORM != "linux":
646
+ # Keyboard layout is only relevant on Linux
647
+ keyboard_layout_option = option_by_name.get("keyboard_layout")
648
+ if keyboard_layout_option is not None:
649
+ keyboard_layout_option.label.hide()
650
+ keyboard_layout_option.widget.hide()
651
+
652
+ # temporary hiding start_minimized setting on macOS due to bug in
653
+ # macOS 26, see https://github.com/openstenoproject/plover/issues/1782
654
+ if PLATFORM == "mac":
655
+ start_minimized_option = option_by_name.get("start_minimized")
656
+ if start_minimized_option is not None:
657
+ start_minimized_option.label.hide()
658
+ start_minimized_option.widget.hide()
659
+
564
660
  # Update dependents.
565
661
  for option in option_by_name.values():
566
662
  option.dependents = [
@@ -577,18 +673,27 @@ class ConfigWindow(QDialog, Ui_ConfigWindow, WindowStateMixin):
577
673
  self.restore_state()
578
674
  self.finished.connect(self.save_state)
579
675
 
580
- def _update_config(self, save=False):
676
+ def _update_appearance(self):
677
+ appearance.update(self._config.maps[0])
678
+
679
+ def _save_config(self):
680
+ """Persist the overrides from this dialog back into the engine config.
681
+
682
+ This uses the engine's async config update mechanism; the engine thread
683
+ will apply the update after it processes the queued job.
684
+ """
581
685
  with self._engine:
582
- if save:
583
- self._engine.config = self._config.maps[0]
584
- self._config = ChainMap(
585
- {},
586
- {
587
- name: value
588
- for name, value in self._engine.config.items()
589
- if name in self._supported_options
590
- },
591
- )
686
+ self._engine.config = self._config.maps[0]
687
+
688
+ def _load_config(self):
689
+ """Load the current config from the engine into this dialog."""
690
+ with self._engine:
691
+ base_config = {
692
+ name: value
693
+ for name, value in self._engine.config.items()
694
+ if name in self._supported_options
695
+ }
696
+ self._config = ChainMap({}, base_config)
592
697
 
593
698
  def _machine_option(self, *args):
594
699
  machine_options = {
@@ -651,4 +756,5 @@ class ConfigWindow(QDialog, Ui_ConfigWindow, WindowStateMixin):
651
756
  dependent.widget = widget
652
757
 
653
758
  def on_apply(self):
654
- self._update_config(save=True)
759
+ self._update_appearance()
760
+ self._save_config()
@@ -4,7 +4,7 @@ _ = __import__(__package__.split(".", 1)[0])._
4
4
  ################################################################################
5
5
  ## Form generated from reading UI file 'config_window.ui'
6
6
  ##
7
- ## Created by: Qt User Interface Compiler version 6.10.0
7
+ ## Created by: Qt User Interface Compiler version 6.10.1
8
8
  ##
9
9
  ## WARNING! All changes made in this file will be lost when recompiling UI file!
10
10
  ################################################################################
@@ -24,7 +24,7 @@ class Ui_ConfigWindow(object):
24
24
  def setupUi(self, ConfigWindow):
25
25
  if not ConfigWindow.objectName():
26
26
  ConfigWindow.setObjectName(u"ConfigWindow")
27
- ConfigWindow.resize(471, 480)
27
+ ConfigWindow.resize(500, 500)
28
28
  icon = QIcon()
29
29
  icon.addFile(u":/resources/plover.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
30
30
  ConfigWindow.setWindowIcon(icon)
@@ -62,7 +62,7 @@ class ConsoleWidget(QWidget, Ui_ConsoleWidget):
62
62
  while True:
63
63
  try:
64
64
  line = self._proc.stdout.readline()
65
- except:
65
+ except Exception:
66
66
  break
67
67
  if not line:
68
68
  break
@@ -4,7 +4,7 @@ _ = __import__(__package__.split(".", 1)[0])._
4
4
  ################################################################################
5
5
  ## Form generated from reading UI file 'console_widget.ui'
6
6
  ##
7
- ## Created by: Qt User Interface Compiler version 6.10.0
7
+ ## Created by: Qt User Interface Compiler version 6.10.1
8
8
  ##
9
9
  ## WARNING! All changes made in this file will be lost when recompiling UI file!
10
10
  ################################################################################
@@ -4,7 +4,7 @@ _ = __import__(__package__.split(".", 1)[0])._
4
4
  ################################################################################
5
5
  ## Form generated from reading UI file 'dictionaries_widget.ui'
6
6
  ##
7
- ## Created by: Qt User Interface Compiler version 6.10.0
7
+ ## Created by: Qt User Interface Compiler version 6.10.1
8
8
  ##
9
9
  ## WARNING! All changes made in this file will be lost when recompiling UI file!
10
10
  ################################################################################
@@ -4,7 +4,7 @@ _ = __import__(__package__.split(".", 1)[0])._
4
4
  ################################################################################
5
5
  ## Form generated from reading UI file 'dictionary_editor.ui'
6
6
  ##
7
- ## Created by: Qt User Interface Compiler version 6.10.0
7
+ ## Created by: Qt User Interface Compiler version 6.10.1
8
8
  ##
9
9
  ## WARNING! All changes made in this file will be lost when recompiling UI file!
10
10
  ################################################################################
@@ -4,7 +4,7 @@ _ = __import__(__package__.split(".", 1)[0])._
4
4
  ################################################################################
5
5
  ## Form generated from reading UI file 'lookup_dialog.ui'
6
6
  ##
7
- ## Created by: Qt User Interface Compiler version 6.10.0
7
+ ## Created by: Qt User Interface Compiler version 6.10.1
8
8
  ##
9
9
  ## WARNING! All changes made in this file will be lost when recompiling UI file!
10
10
  ################################################################################
@@ -89,7 +89,6 @@ class SerialOption(QGroupBox, Ui_SerialWidget):
89
89
  if not details:
90
90
  return
91
91
  cursor.insertFrame(self._details_frame_format)
92
- details_list = cursor.createList(self._details_list_format)
93
92
  for n, part in enumerate(details):
94
93
  if n:
95
94
  cursor.insertBlock()
@@ -140,15 +139,7 @@ class SerialOption(QGroupBox, Ui_SerialWidget):
140
139
  self.parity.setCurrentText(value["parity"])
141
140
  self.stopbits.addItems(map(str, Serial.STOPBITS))
142
141
  self.stopbits.setCurrentText(str(value["stopbits"]))
143
- timeout = value["timeout"]
144
- if timeout is None:
145
- self.use_timeout.setChecked(False)
146
- self.timeout.setValue(0.0)
147
- self.timeout.setEnabled(False)
148
- else:
149
- self.use_timeout.setChecked(True)
150
- self.timeout.setValue(timeout)
151
- self.timeout.setEnabled(True)
142
+ self.timeout.setValue(value["timeout"])
152
143
  for setting in ("xonxoff", "rtscts"):
153
144
  widget = getattr(self, setting)
154
145
  if setting in value:
@@ -188,15 +179,6 @@ class SerialOption(QGroupBox, Ui_SerialWidget):
188
179
  def update_timeout(self, value):
189
180
  self._update("timeout", value)
190
181
 
191
- @Slot(bool)
192
- def update_use_timeout(self, value):
193
- if value:
194
- timeout = self.timeout.value()
195
- else:
196
- timeout = None
197
- self.timeout.setEnabled(value)
198
- self._update("timeout", timeout)
199
-
200
182
  @Slot(bool)
201
183
  def update_xonxoff(self, value):
202
184
  self._update("xonxoff", value)
@@ -22,6 +22,7 @@ from plover.gui_qt.config_window import ConfigWindow
22
22
  from plover.gui_qt.about_dialog import AboutDialog
23
23
  from plover.gui_qt.trayicon import TrayIcon
24
24
  from plover.gui_qt.utils import WindowStateMixin
25
+ from plover.gui_qt import appearance
25
26
 
26
27
 
27
28
  class MainWindow(QMainWindow, Ui_MainWindow, WindowStateMixin):
@@ -141,10 +142,21 @@ class MainWindow(QMainWindow, Ui_MainWindow, WindowStateMixin):
141
142
  self.configure()
142
143
  # Apply configuration settings.
143
144
  config = self._engine.config
145
+
146
+ # Set the initial appearance based on the loaded configuration.
147
+ appearance.update(config)
148
+
144
149
  self._warn_on_hide_to_tray = not config["start_minimized"]
145
150
  self._update_machine(config["machine_type"])
146
151
  self._configured = False
147
- self.set_visible(not config["start_minimized"])
152
+
153
+ # temporary ignoring start_minimized setting on macOS due to bug in
154
+ # macOS 26, see https://github.com/openstenoproject/plover/issues/1782
155
+ if PLATFORM == "mac":
156
+ self.set_visible(True)
157
+ else:
158
+ self.set_visible(not config["start_minimized"])
159
+
148
160
  # Process events before starting the engine
149
161
  # (to avoid display lag at window creation).
150
162
  QCoreApplication.processEvents()
@@ -4,7 +4,7 @@ _ = __import__(__package__.split(".", 1)[0])._
4
4
  ################################################################################
5
5
  ## Form generated from reading UI file 'main_window.ui'
6
6
  ##
7
- ## Created by: Qt User Interface Compiler version 6.10.0
7
+ ## Created by: Qt User Interface Compiler version 6.10.1
8
8
  ##
9
9
  ## WARNING! All changes made in this file will be lost when recompiling UI file!
10
10
  ################################################################################
@@ -19,8 +19,8 @@ from PySide6.QtGui import (QAction, QBrush, QColor, QConicalGradient,
19
19
  QTransform)
20
20
  from PySide6.QtWidgets import (QApplication, QComboBox, QGridLayout, QGroupBox,
21
21
  QLabel, QMainWindow, QMenu, QMenuBar,
22
- QPushButton, QRadioButton, QSizePolicy, QToolBar,
23
- QWidget)
22
+ QPushButton, QRadioButton, QSizePolicy, QSpacerItem,
23
+ QToolBar, QWidget)
24
24
 
25
25
  from plover.gui_qt.dictionaries_widget import DictionariesWidget
26
26
  from . import resources_rc
@@ -30,7 +30,7 @@ class Ui_MainWindow(object):
30
30
  if not MainWindow.objectName():
31
31
  MainWindow.setObjectName(u"MainWindow")
32
32
  MainWindow.setEnabled(True)
33
- MainWindow.resize(329, 427)
33
+ MainWindow.resize(700, 500)
34
34
  MainWindow.setMinimumSize(QSize(250, 0))
35
35
  MainWindow.setWindowTitle(u"Plover")
36
36
  icon = QIcon()
@@ -70,6 +70,10 @@ class Ui_MainWindow(object):
70
70
  self.centralwidget.setMinimumSize(QSize(0, 0))
71
71
  self.gridLayout = QGridLayout(self.centralwidget)
72
72
  self.gridLayout.setObjectName(u"gridLayout")
73
+ self.topSpacer = QSpacerItem(0, 4, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed)
74
+
75
+ self.gridLayout.addItem(self.topSpacer, 0, 0, 1, 3)
76
+
73
77
  self.groupBox = QGroupBox(self.centralwidget)
74
78
  self.groupBox.setObjectName(u"groupBox")
75
79
  sizePolicy1 = QSizePolicy(QSizePolicy.Policy.MinimumExpanding, QSizePolicy.Policy.Preferred)
@@ -106,12 +110,16 @@ class Ui_MainWindow(object):
106
110
  self.gridLayout_2.addWidget(self.machine_type, 0, 0, 1, 2)
107
111
 
108
112
 
109
- self.gridLayout.addWidget(self.groupBox, 0, 0, 1, 1)
113
+ self.gridLayout.addWidget(self.groupBox, 1, 0, 1, 1)
114
+
115
+ self.verticalSpacer = QSpacerItem(0, 10, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed)
116
+
117
+ self.gridLayout.addItem(self.verticalSpacer, 2, 0, 1, 3)
110
118
 
111
119
  self.dictionaries = DictionariesWidget(self.centralwidget)
112
120
  self.dictionaries.setObjectName(u"dictionaries")
113
121
 
114
- self.gridLayout.addWidget(self.dictionaries, 2, 0, 1, 3)
122
+ self.gridLayout.addWidget(self.dictionaries, 3, 0, 1, 3)
115
123
 
116
124
  self.groupBox1 = QGroupBox(self.centralwidget)
117
125
  self.groupBox1.setObjectName(u"groupBox1")
@@ -134,12 +142,11 @@ class Ui_MainWindow(object):
134
142
  self.gridLayout_3.addWidget(self.output_disable, 1, 0, 1, 1)
135
143
 
136
144
 
137
- self.gridLayout.addWidget(self.groupBox1, 0, 1, 1, 1)
145
+ self.gridLayout.addWidget(self.groupBox1, 1, 1, 1, 1)
138
146
 
139
147
  MainWindow.setCentralWidget(self.centralwidget)
140
148
  self.menubar = QMenuBar(MainWindow)
141
149
  self.menubar.setObjectName(u"menubar")
142
- self.menubar.setGeometry(QRect(0, 0, 329, 25))
143
150
  self.menu_File = QMenu(self.menubar)
144
151
  self.menu_File.setObjectName(u"menu_File")
145
152
  self.menu_Tools = QMenu(self.menubar)