boris-behav-obs 8.16.5__py3-none-any.whl → 9.7.12__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 (126) hide show
  1. boris/__init__.py +1 -1
  2. boris/__main__.py +1 -1
  3. boris/about.py +28 -40
  4. boris/add_modifier.py +88 -80
  5. boris/add_modifier_ui.py +266 -144
  6. boris/advanced_event_filtering.py +23 -29
  7. boris/analysis_plugins/__init__.py +0 -0
  8. boris/analysis_plugins/_export_to_feral.py +225 -0
  9. boris/analysis_plugins/_latency.py +59 -0
  10. boris/analysis_plugins/irr_cohen_kappa.py +109 -0
  11. boris/analysis_plugins/irr_cohen_kappa_with_modifiers.py +112 -0
  12. boris/analysis_plugins/irr_weighted_cohen_kappa.py +157 -0
  13. boris/analysis_plugins/irr_weighted_cohen_kappa_with_modifiers.py +162 -0
  14. boris/analysis_plugins/list_of_dataframe_columns.py +22 -0
  15. boris/analysis_plugins/number_of_occurences.py +22 -0
  16. boris/analysis_plugins/number_of_occurences_by_independent_variable.py +54 -0
  17. boris/analysis_plugins/time_budget.py +61 -0
  18. boris/behav_coding_map_creator.py +235 -236
  19. boris/behavior_binary_table.py +33 -50
  20. boris/behaviors_coding_map.py +17 -18
  21. boris/boris_cli.py +6 -25
  22. boris/cmd_arguments.py +12 -1
  23. boris/coding_pad.py +19 -36
  24. boris/config.py +109 -50
  25. boris/config_file.py +58 -67
  26. boris/connections.py +105 -58
  27. boris/converters.py +13 -37
  28. boris/converters_ui.py +187 -110
  29. boris/cooccurence.py +250 -0
  30. boris/core.py +2174 -1303
  31. boris/core_qrc.py +15892 -10829
  32. boris/core_ui.py +941 -806
  33. boris/db_functions.py +17 -42
  34. boris/dev.py +27 -7
  35. boris/dialog.py +461 -242
  36. boris/duration_widget.py +9 -14
  37. boris/edit_event.py +61 -31
  38. boris/edit_event_ui.py +208 -97
  39. boris/event_operations.py +405 -281
  40. boris/events_cursor.py +25 -17
  41. boris/events_snapshots.py +36 -82
  42. boris/exclusion_matrix.py +4 -9
  43. boris/export_events.py +180 -203
  44. boris/export_observation.py +60 -73
  45. boris/external_processes.py +123 -98
  46. boris/geometric_measurement.py +427 -218
  47. boris/gui_utilities.py +91 -14
  48. boris/image_overlay.py +4 -4
  49. boris/import_observations.py +190 -98
  50. boris/ipc_mpv.py +325 -0
  51. boris/irr.py +20 -57
  52. boris/latency.py +31 -24
  53. boris/measurement_widget.py +14 -18
  54. boris/media_file.py +17 -19
  55. boris/menu_options.py +16 -6
  56. boris/modifier_coding_map_creator.py +1013 -0
  57. boris/modifiers_coding_map.py +7 -9
  58. boris/mpv2.py +128 -35
  59. boris/observation.py +501 -211
  60. boris/observation_operations.py +1037 -393
  61. boris/observation_ui.py +573 -363
  62. boris/observations_list.py +51 -58
  63. boris/otx_parser.py +74 -68
  64. boris/param_panel.py +45 -59
  65. boris/param_panel_ui.py +254 -138
  66. boris/player_dock_widget.py +91 -56
  67. boris/plot_data_module.py +20 -53
  68. boris/plot_events.py +56 -153
  69. boris/plot_events_rt.py +16 -30
  70. boris/plot_spectrogram_rt.py +83 -56
  71. boris/plot_waveform_rt.py +27 -49
  72. boris/plugins.py +468 -0
  73. boris/portion/__init__.py +18 -8
  74. boris/portion/const.py +35 -18
  75. boris/portion/dict.py +5 -5
  76. boris/portion/func.py +2 -2
  77. boris/portion/interval.py +21 -41
  78. boris/portion/io.py +41 -32
  79. boris/preferences.py +307 -123
  80. boris/preferences_ui.py +686 -227
  81. boris/project.py +294 -271
  82. boris/project_functions.py +626 -537
  83. boris/project_import_export.py +204 -213
  84. boris/project_ui.py +673 -441
  85. boris/qrc_boris.py +6 -3
  86. boris/qrc_boris5.py +6 -3
  87. boris/select_modifiers.py +62 -90
  88. boris/select_observations.py +19 -197
  89. boris/select_subj_behav.py +67 -39
  90. boris/state_events.py +51 -33
  91. boris/subjects_pad.py +7 -9
  92. boris/synthetic_time_budget.py +42 -26
  93. boris/time_budget_functions.py +169 -169
  94. boris/time_budget_widget.py +77 -89
  95. boris/transitions.py +41 -41
  96. boris/utilities.py +594 -226
  97. boris/version.py +3 -3
  98. boris/video_equalizer.py +16 -14
  99. boris/video_equalizer_ui.py +199 -130
  100. boris/video_operations.py +86 -28
  101. boris/view_df.py +104 -0
  102. boris/view_df_ui.py +75 -0
  103. boris/write_event.py +240 -136
  104. boris_behav_obs-9.7.12.dist-info/METADATA +139 -0
  105. boris_behav_obs-9.7.12.dist-info/RECORD +110 -0
  106. {boris_behav_obs-8.16.5.dist-info → boris_behav_obs-9.7.12.dist-info}/WHEEL +1 -1
  107. boris_behav_obs-9.7.12.dist-info/entry_points.txt +2 -0
  108. boris/README.TXT +0 -22
  109. boris/add_modifier.ui +0 -323
  110. boris/converters.ui +0 -289
  111. boris/core.qrc +0 -37
  112. boris/core.ui +0 -1571
  113. boris/edit_event.ui +0 -233
  114. boris/icons/logo_eye.ico +0 -0
  115. boris/map_creator.py +0 -982
  116. boris/observation.ui +0 -814
  117. boris/param_panel.ui +0 -379
  118. boris/preferences.ui +0 -537
  119. boris/project.ui +0 -1074
  120. boris/vlc_local.py +0 -90
  121. boris_behav_obs-8.16.5.dist-info/LICENSE.TXT +0 -674
  122. boris_behav_obs-8.16.5.dist-info/METADATA +0 -134
  123. boris_behav_obs-8.16.5.dist-info/RECORD +0 -107
  124. boris_behav_obs-8.16.5.dist-info/entry_points.txt +0 -2
  125. {boris → boris_behav_obs-9.7.12.dist-info/licenses}/LICENSE.TXT +0 -0
  126. {boris_behav_obs-8.16.5.dist-info → boris_behav_obs-9.7.12.dist-info}/top_level.txt +0 -0
boris/__init__.py CHANGED
@@ -2,7 +2,7 @@
2
2
  BORIS
3
3
  Behavioral Observation Research Interactive Software
4
4
 
5
- Copyright 2012-2023 Olivier Friard
5
+ Copyright 2012-2025 Olivier Friard
6
6
 
7
7
  This file is part of BORIS.
8
8
 
boris/__main__.py CHANGED
@@ -1,7 +1,7 @@
1
1
  """
2
2
  BORIS
3
3
  Behavioral Observation Research Interactive Software
4
- Copyright 2012-2023 Olivier Friard
4
+ Copyright 2012-2025 Olivier Friard
5
5
 
6
6
  This file is part of BORIS.
7
7
 
boris/about.py CHANGED
@@ -1,7 +1,7 @@
1
1
  """
2
2
  BORIS
3
3
  Behavioral Observation Research Interactive Software
4
- Copyright 2012-2023 Olivier Friard
4
+ Copyright 2012-2025 Olivier Friard
5
5
 
6
6
  This program is free software; you can redistribute it and/or modify
7
7
  it under the terms of the GNU General Public License as published by
@@ -30,24 +30,9 @@ from . import version
30
30
  from . import config as cfg
31
31
  from . import utilities as util
32
32
 
33
- try:
34
- from . import mpv2 as mpv
35
33
 
36
- # check if MPV API v. 1
37
- # is v. 1 use the old version of mpv.py
38
- try:
39
- if "libmpv.so.1" in mpv.sofile:
40
- from . import mpv as mpv
41
- except AttributeError:
42
- if "mpv-1.dll" in mpv.dll:
43
- from . import mpv as mpv
44
-
45
- except RuntimeError: # libmpv found but version too old
46
- from . import mpv as mpv
47
-
48
- from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR
49
- from PyQt5.QtGui import QPixmap
50
- from PyQt5.QtWidgets import QMessageBox
34
+ from PySide6.QtGui import QPixmap
35
+ from PySide6.QtWidgets import QMessageBox
51
36
 
52
37
 
53
38
  def actionAbout_activated(self):
@@ -55,25 +40,26 @@ def actionAbout_activated(self):
55
40
  About dialog
56
41
  """
57
42
 
58
- programs_versions = ["MPV media player"]
59
- if sys.platform.startswith("linux"):
60
- programs_versions.append(
61
- f"Library version: {'.'.join([str(x) for x in mpv._mpv_client_api_version()])} file: {mpv.sofile}"
62
- )
63
- if sys.platform.startswith("win"):
64
- programs_versions.append(
65
- f"Library version: {'.'.join([str(x) for x in mpv._mpv_client_api_version()])} file: {mpv.dll}"
43
+ programs_versions: list[str] = ["MPV media player"]
44
+
45
+ mpv_lib_version, mpv_lib_file_path, mpv_api_version = util.mpv_lib_version()
46
+ programs_versions.append(
47
+ (
48
+ f"Library version: {mpv_lib_version} file: {mpv_lib_file_path}\n"
49
+ f"MPV API version: {mpv_api_version}\n"
50
+ f"python-mpv version: {util.python_mpv_script_version()}"
66
51
  )
52
+ )
67
53
 
68
54
  # ffmpeg
69
55
  if self.ffmpeg_bin == "ffmpeg" and sys.platform.startswith("linux"):
70
- ffmpeg_true_path = subprocess.getoutput("which ffmpeg")
56
+ ffmpeg_true_path: str = subprocess.getoutput("which ffmpeg")
71
57
  else:
72
58
  ffmpeg_true_path = self.ffmpeg_bin
73
59
  programs_versions.extend(
74
60
  [
75
61
  "\nFFmpeg",
76
- subprocess.getoutput(f'"{self.ffmpeg_bin}" -version').split("\n")[0],
62
+ subprocess.getoutput(cmd=f'"{self.ffmpeg_bin}" -version').split(sep="\n")[0],
77
63
  f"Path: {ffmpeg_true_path}",
78
64
  "https://www.ffmpeg.org",
79
65
  ]
@@ -89,13 +75,11 @@ def actionAbout_activated(self):
89
75
  programs_versions.extend(["\nPandas", f"version {pd.__version__}", "https://pandas.pydata.org"])
90
76
 
91
77
  # graphviz
92
- gv_result = subprocess.getoutput("dot -V")
78
+ gv_result = subprocess.getoutput(cmd="dot -V")
93
79
 
94
- programs_versions.extend(
95
- ["\nGraphViz", gv_result if "graphviz" in gv_result else "not installed", "https://www.graphviz.org/"]
96
- )
80
+ programs_versions.extend(["\nGraphViz", gv_result if "graphviz" in gv_result else "not installed", "https://www.graphviz.org/"])
97
81
 
98
- about_dialog = QMessageBox()
82
+ about_dialog: QMessageBox = QMessageBox()
99
83
  about_dialog.setIconPixmap(QPixmap(":/boris_unito"))
100
84
 
101
85
  about_dialog.setWindowTitle(f"About {cfg.programName}")
@@ -106,28 +90,29 @@ def actionAbout_activated(self):
106
90
  about_dialog.setInformativeText(
107
91
  (
108
92
  f"<b>{cfg.programName}</b> v. {version.__version__} - {version.__version_date__}"
109
- "<p>Copyright &copy; 2012-2023 Olivier Friard - Marco Gamba<br>"
93
+ "<p>Copyright &copy; 2012-2025 Olivier Friard - Marco Gamba<br>"
110
94
  "Department of Life Sciences and Systems Biology<br>"
111
95
  "University of Torino - Italy<br>"
112
96
  "<br>"
113
- 'BORIS is released under the <a href="http://www.gnu.org/copyleft/gpl.html">GNU General Public License</a><br>'
114
- 'See <a href="http://www.boris.unito.it">www.boris.unito.it</a> for more details.<br>'
97
+ 'BORIS is released under the <a href="https://www.gnu.org/copyleft/gpl.html">GNU General Public License</a><br>'
98
+ 'See <a href="https://www.boris.unito.it">www.boris.unito.it</a> for more details.<br>'
115
99
  "<br>"
116
100
  "The authors would like to acknowledge Valentina Matteucci for her precious help."
117
101
  "<hr>"
118
102
  "How to cite BORIS:<br>"
119
103
  "Friard, O. and Gamba, M. (2016), BORIS: a free, versatile open-source event-logging software for video/audio "
120
104
  "coding and live observations. Methods Ecol Evol, 7: 1325–1330.<br>"
121
- '<a href="http://onlinelibrary.wiley.com/doi/10.1111/2041-210X.12584/abstract">DOI:10.1111/2041-210X.12584</a>'
105
+ '<a href="https://besjournals.onlinelibrary.wiley.com/doi/full/10.1111/2041-210X.12584">DOI:10.1111/2041-210X.12584</a>'
122
106
  )
123
107
  )
108
+ """
124
109
  n = "\n"
125
110
  current_system = platform.uname()
126
111
  details = (
127
112
  f"Operating system: {current_system.system} {current_system.release} {current_system.version} \n"
128
113
  f"CPU: {current_system.machine} {current_system.processor}\n\n"
129
114
  f"Python {platform.python_version()} ({'64-bit' if sys.maxsize > 2**32 else '32-bit'})"
130
- f" - Qt {QT_VERSION_STR} - PyQt {PYQT_VERSION_STR}\n\n"
115
+ f"Qt {qVersion()} - PySide {PySide6.__version__}\n"
131
116
  )
132
117
 
133
118
  r, memory = util.mem_info()
@@ -136,8 +121,11 @@ def actionAbout_activated(self):
136
121
  f"Memory (RAM) Total: {memory.get('total_memory', 'Not available'):.2f} Mb "
137
122
  f"Free: {memory.get('free_memory', 'Not available'):.2f} Mb\n\n"
138
123
  )
124
+ """
125
+
126
+ details = util.get_systeminfo()
139
127
 
140
- details += n.join(programs_versions)
128
+ details += "\n".join(programs_versions)
141
129
  """
142
130
  memory_in_use = f"{utilities.rss_memory_used(self.pid)} Mb" if utilities.rss_memory_used(self.pid) != -1 else "Not available"
143
131
  percent_memory_in_use = (f"({utilities.rss_memory_percent_used(self.pid):.1f} % of total memory)"
@@ -152,4 +140,4 @@ def actionAbout_activated(self):
152
140
 
153
141
  about_dialog.setDetailedText(details)
154
142
 
155
- _ = about_dialog.exec_()
143
+ _ = about_dialog.exec()
boris/add_modifier.py CHANGED
@@ -1,7 +1,7 @@
1
1
  """
2
2
  BORIS
3
3
  Behavioral Observation Research Interactive Software
4
- Copyright 2012-2023 Olivier Friard
4
+ Copyright 2012-2025 Olivier Friard
5
5
 
6
6
  This file is part of BORIS.
7
7
 
@@ -16,15 +16,15 @@ This file is part of BORIS.
16
16
  GNU General Public License for more details.
17
17
 
18
18
  You should have received a copy of the GNU General Public License
19
- along with this program; if not see <http://www.gnu.org/licenses/>.
19
+ along with this program; if not see <http://www.gnu.org/licPbehav_enses/>.
20
20
 
21
21
  """
22
22
 
23
23
  import logging
24
24
  import json
25
25
 
26
- from PyQt5.QtGui import QIcon
27
- from PyQt5.QtWidgets import QDialog, QWidget, QFileDialog, QMessageBox
26
+ from PySide6.QtGui import QIcon
27
+ from PySide6.QtWidgets import QDialog, QWidget, QFileDialog, QMessageBox
28
28
 
29
29
  from . import dialog
30
30
  from .add_modifier_ui import Ui_Dialog
@@ -33,12 +33,10 @@ from .utilities import sorted_keys
33
33
 
34
34
 
35
35
  class addModifierDialog(QDialog, Ui_Dialog):
36
-
37
36
  tabMem = -1
38
37
  itemPositionMem = -1
39
38
 
40
- def __init__(self, modifiers_str, subjects=[], parent=None):
41
-
39
+ def __init__(self, modifiers_str: str, subjects: list = [], ask_at_stop_enabled: bool = False, parent=None):
42
40
  super().__init__()
43
41
  self.setupUi(self)
44
42
 
@@ -46,10 +44,12 @@ class addModifierDialog(QDialog, Ui_Dialog):
46
44
  if not self.subjects:
47
45
  self.pb_add_subjects.setEnabled(False)
48
46
 
47
+ self.ask_at_stop_enabled = ask_at_stop_enabled
48
+
49
49
  self.pbAddModifier.clicked.connect(self.addModifier)
50
50
  self.pbAddModifier.setIcon(QIcon(":/frame_forward"))
51
- self.pbAddSet.clicked.connect(self.addSet)
52
- self.pbRemoveSet.clicked.connect(self.removeSet)
51
+ self.pbAddSet.clicked.connect(self.add_set_of_modifiers)
52
+ self.pbRemoveSet.clicked.connect(self.remove_set_of_modifiers)
53
53
  self.pbModifyModifier.clicked.connect(self.modifyModifier)
54
54
  self.pbModifyModifier.setIcon(QIcon(":/frame_backward"))
55
55
 
@@ -62,22 +62,27 @@ class addModifierDialog(QDialog, Ui_Dialog):
62
62
  self.pb_add_subjects.clicked.connect(self.add_subjects)
63
63
  self.pb_load_file.clicked.connect(self.add_modifiers_from_file)
64
64
 
65
- self.pbOK.clicked.connect(lambda: self.pb_pushed("ok"))
66
- self.pbCancel.clicked.connect(lambda: self.pb_pushed("cancel"))
65
+ self.pbOK.clicked.connect(lambda: self.pb_pushed(cfg.OK))
66
+ self.pbCancel.clicked.connect(lambda: self.pb_pushed(cfg.CANCEL))
67
67
 
68
68
  self.le_name.textChanged.connect(self.set_name_changed)
69
69
  self.le_description.textChanged.connect(self.set_description_changed)
70
70
 
71
71
  self.cbType.currentIndexChanged.connect(self.type_changed)
72
72
 
73
- dummy_dict = json.loads(modifiers_str) if modifiers_str else {}
74
- modif_values = []
73
+ # self.cb_ask_at_stop.clicked.connect(self.ask_at_stop_changed)
74
+
75
+ dummy_dict: dict = json.loads(modifiers_str) if modifiers_str else {}
76
+ modif_values: list = []
75
77
  for idx in sorted_keys(dummy_dict):
76
78
  modif_values.append(dummy_dict[idx])
77
79
 
78
- self.modifiers_sets_dict = {}
80
+ self.modifiers_sets_dict: dict = {}
79
81
  for modif in modif_values:
80
82
  self.modifiers_sets_dict[str(len(self.modifiers_sets_dict))] = dict(modif)
83
+ if self.ask_at_stop_enabled:
84
+ if dict(modif).get("ask at stop", False):
85
+ self.cb_ask_at_stop.setChecked(True)
81
86
 
82
87
  self.tabWidgetModifiersSets.currentChanged.connect(self.tabWidgetModifiersSets_changed)
83
88
 
@@ -104,6 +109,7 @@ class addModifierDialog(QDialog, Ui_Dialog):
104
109
  self.pb_add_subjects,
105
110
  self.pb_load_file,
106
111
  self.pb_sort_modifiers,
112
+ self.cb_ask_at_stop,
107
113
  ):
108
114
  w.setVisible(False)
109
115
  for w in (self.leModifier, self.leCode, self.pbAddModifier, self.pbModifyModifier):
@@ -113,24 +119,19 @@ class addModifierDialog(QDialog, Ui_Dialog):
113
119
  self.tabMem = 0
114
120
 
115
121
  def pb_pushed(self, button):
116
-
117
122
  if self.leModifier.text():
118
123
  if (
119
124
  dialog.MessageDialog(
120
125
  cfg.programName,
121
- (
122
- "You are working on a behavior.<br>"
123
- "If you close the window it will be lost.<br>"
124
- "Do you want to change modifiers set"
125
- ),
126
- ["Close", cfg.CANCEL],
126
+ ("You are working on a behavior.<br>If you close the window it will be lost.<br>Do you want to change modifiers set"),
127
+ [cfg.CLOSE, cfg.CANCEL],
127
128
  )
128
129
  == cfg.CANCEL
129
130
  ):
130
131
  return
131
- if button == "ok":
132
+ if button == cfg.OK:
132
133
  self.accept()
133
- if button == "cancel":
134
+ if button == cfg.CANCEL:
134
135
  self.reject()
135
136
 
136
137
  def add_subjects(self):
@@ -153,43 +154,38 @@ class addModifierDialog(QDialog, Ui_Dialog):
153
154
  add modifiers from file
154
155
  """
155
156
 
156
- fn = QFileDialog().getOpenFileName(self, "Load modifiers from file", "", "All files (*)")
157
- file_name = fn[0] if type(fn) is tuple else fn
158
- if file_name:
159
- try:
160
- with open(file_name) as f_in:
161
- for line in f_in:
162
- if line.strip():
163
-
164
- for c in cfg.CHAR_FORBIDDEN_IN_MODIFIERS:
165
- if c in line.strip():
166
- QMessageBox.critical(
167
- self,
168
- cfg.programName,
169
- (
170
- f"The character <b>{c}</b> is not allowed.<br>"
171
- "The following characters are not allowed in modifiers:<br>"
172
- f"<b>{cfg.CHAR_FORBIDDEN_IN_MODIFIERS}</b>"
173
- ),
174
- )
175
- break
176
- else:
177
-
178
- if line.strip() not in [
179
- self.lwModifiers.item(x).text() for x in range(self.lwModifiers.count())
180
- ]:
181
-
182
- if self.itemPositionMem != -1:
183
- self.lwModifiers.insertItem(self.itemPositionMem, line.strip())
184
- else:
185
- self.lwModifiers.addItem(line.strip())
186
-
187
- self.modifiers_sets_dict[str(self.tabWidgetModifiersSets.currentIndex())]["values"] = [
188
- self.lwModifiers.item(x).text() for x in range(self.lwModifiers.count())
189
- ]
190
- except Exception:
191
- QMessageBox.warning(self, cfg.programName, f"Error reading modifiers from file:<br>{file_name}")
192
- logging.warning(f"Error reading modifiers from file<br>{file_name}")
157
+ file_name, _ = QFileDialog.getOpenFileName(self, "Load modifiers from file", "", "All files (*)")
158
+ if not file_name:
159
+ return
160
+ try:
161
+ with open(file_name) as f_in:
162
+ for line in f_in:
163
+ if line.strip():
164
+ for c in cfg.CHAR_FORBIDDEN_IN_MODIFIERS:
165
+ if c in line.strip():
166
+ QMessageBox.critical(
167
+ self,
168
+ cfg.programName,
169
+ (
170
+ f"The character <b>{c}</b> is not allowed.<br>"
171
+ "The following characters are not allowed in modifiers:<br>"
172
+ f"<b>{cfg.CHAR_FORBIDDEN_IN_MODIFIERS}</b>"
173
+ ),
174
+ )
175
+ break
176
+ else:
177
+ if line.strip() not in [self.lwModifiers.item(x).text() for x in range(self.lwModifiers.count())]:
178
+ if self.itemPositionMem != -1:
179
+ self.lwModifiers.insertItem(self.itemPositionMem, line.strip())
180
+ else:
181
+ self.lwModifiers.addItem(line.strip())
182
+
183
+ self.modifiers_sets_dict[str(self.tabWidgetModifiersSets.currentIndex())]["values"] = [
184
+ self.lwModifiers.item(x).text() for x in range(self.lwModifiers.count())
185
+ ]
186
+ except Exception:
187
+ QMessageBox.warning(self, cfg.programName, f"Error reading modifiers from file:<br>{file_name}")
188
+ logging.warning(f"Error reading modifiers from file<br>{file_name}")
193
189
 
194
190
  def sort_modifiers(self):
195
191
  """
@@ -222,9 +218,7 @@ class addModifierDialog(QDialog, Ui_Dialog):
222
218
  """
223
219
  if not self.modifiers_sets_dict:
224
220
  self.modifiers_sets_dict["0"] = {"name": "", "description": "", "type": cfg.SINGLE_SELECTION, "values": []}
225
- self.modifiers_sets_dict[str(self.tabWidgetModifiersSets.currentIndex())][
226
- "description"
227
- ] = self.le_description.text().strip()
221
+ self.modifiers_sets_dict[str(self.tabWidgetModifiersSets.currentIndex())]["description"] = self.le_description.text().strip()
228
222
 
229
223
  def type_changed(self):
230
224
  """
@@ -257,6 +251,12 @@ class addModifierDialog(QDialog, Ui_Dialog):
257
251
  else:
258
252
  self.lb_name.setText("Set name")
259
253
 
254
+ # def ask_at_stop_changed(self):
255
+ # """
256
+ # value changed
257
+ # """
258
+ # self.modifiers_sets_dict[str(self.tabWidgetModifiersSets.currentIndex())]["ask at stop"] = self.cb_ask_at_stop.isChecked()
259
+
260
260
  def moveSetLeft(self):
261
261
  """
262
262
  move selected modifiers set left
@@ -277,7 +277,6 @@ class addModifierDialog(QDialog, Ui_Dialog):
277
277
  move selected modifiers set right
278
278
  """
279
279
  if self.tabWidgetModifiersSets.currentIndex() < self.tabWidgetModifiersSets.count() - 1:
280
-
281
280
  (
282
281
  self.modifiers_sets_dict[str(self.tabWidgetModifiersSets.currentIndex() + 1)],
283
282
  self.modifiers_sets_dict[str(self.tabWidgetModifiersSets.currentIndex())],
@@ -315,7 +314,7 @@ class addModifierDialog(QDialog, Ui_Dialog):
315
314
  self.lwModifiers.item(x).text() for x in range(self.lwModifiers.count())
316
315
  ]
317
316
 
318
- def addSet(self):
317
+ def add_set_of_modifiers(self):
319
318
  """
320
319
  Add a set of modifiers
321
320
  """
@@ -326,6 +325,7 @@ class addModifierDialog(QDialog, Ui_Dialog):
326
325
  "name": "",
327
326
  "description": "",
328
327
  "type": cfg.SINGLE_SELECTION,
328
+ "ask at stop": False,
329
329
  "values": [],
330
330
  }
331
331
  self.tabWidgetModifiersSets.addTab(QWidget(), f"Set #{len(self.modifiers_sets_dict)}")
@@ -353,6 +353,8 @@ class addModifierDialog(QDialog, Ui_Dialog):
353
353
  self.pb_sort_modifiers,
354
354
  ):
355
355
  w.setVisible(True)
356
+ self.cb_ask_at_stop.setVisible(self.ask_at_stop_enabled)
357
+
356
358
  for w in (self.leModifier, self.leCode, self.pbAddModifier, self.pbModifyModifier):
357
359
  w.setEnabled(True)
358
360
  return
@@ -362,6 +364,7 @@ class addModifierDialog(QDialog, Ui_Dialog):
362
364
  "name": "",
363
365
  "description": "",
364
366
  "type": cfg.SINGLE_SELECTION,
367
+ "ask at stop": False,
365
368
  "values": [],
366
369
  }
367
370
  self.tabWidgetModifiersSets.addTab(QWidget(), f"Set #{len(self.modifiers_sets_dict)}")
@@ -375,16 +378,13 @@ class addModifierDialog(QDialog, Ui_Dialog):
375
378
  "It is not possible to add a modifiers' set while the current modifiers' set is empty.",
376
379
  )
377
380
 
378
- def removeSet(self):
381
+ def remove_set_of_modifiers(self):
379
382
  """
380
383
  remove set of modifiers
381
384
  """
382
385
 
383
386
  if self.tabWidgetModifiersSets.currentIndex() != -1:
384
- if (
385
- dialog.MessageDialog(cfg.programName, "Confirm deletion of this set of modifiers?", [cfg.YES, cfg.NO])
386
- == cfg.YES
387
- ):
387
+ if dialog.MessageDialog(cfg.programName, "Confirm deletion of this set of modifiers?", [cfg.YES, cfg.NO]) == cfg.YES:
388
388
  index_to_delete = self.tabWidgetModifiersSets.currentIndex()
389
389
 
390
390
  for k in range(index_to_delete, len(self.modifiers_sets_dict) - 1):
@@ -417,14 +417,15 @@ class addModifierDialog(QDialog, Ui_Dialog):
417
417
  self.pbRemoveSet,
418
418
  self.pbMoveSetLeft,
419
419
  self.pbMoveSetRight,
420
+ self.cb_ask_at_stop,
420
421
  ):
421
422
  w.setVisible(False)
422
- for w in [self.leModifier, self.leCode, self.pbAddModifier, self.pbModifyModifier]:
423
+ for w in (self.leModifier, self.leCode, self.pbAddModifier, self.pbModifyModifier):
423
424
  w.setEnabled(False)
424
425
 
425
426
  if not len(self.modifiers_sets_dict):
426
427
  # set invisible and unavailable buttons and others elements
427
- for w in [
428
+ for w in (
428
429
  self.lb_name,
429
430
  self.le_name,
430
431
  self.lbType,
@@ -442,7 +443,7 @@ class addModifierDialog(QDialog, Ui_Dialog):
442
443
  self.pb_add_subjects,
443
444
  self.pb_load_file,
444
445
  self.pb_sort_modifiers,
445
- ]:
446
+ ):
446
447
  w.setVisible(False)
447
448
  for w in [self.leModifier, self.leCode, self.pbAddModifier, self.pbModifyModifier]:
448
449
  w.setEnabled(False)
@@ -505,7 +506,6 @@ class addModifierDialog(QDialog, Ui_Dialog):
505
506
  return
506
507
 
507
508
  if txt:
508
-
509
509
  if txt in [self.lwModifiers.item(x).text() for x in range(self.lwModifiers.count())]:
510
510
  QMessageBox.critical(self, cfg.programName, f"The modifier <b>{txt}</b> is already in the list")
511
511
  return
@@ -515,6 +515,7 @@ class addModifierDialog(QDialog, Ui_Dialog):
515
515
  "name": "",
516
516
  "description": "",
517
517
  "type": cfg.SINGLE_SELECTION,
518
+ "ask at stop": False,
518
519
  "values": [],
519
520
  }
520
521
 
@@ -545,16 +546,14 @@ class addModifierDialog(QDialog, Ui_Dialog):
545
546
  "name": "",
546
547
  "description": "",
547
548
  "type": cfg.SINGLE_SELECTION,
549
+ "ask at stop": False,
548
550
  "values": [],
549
551
  }
550
552
 
551
553
  if "(" + self.leCode.text().strip() + ")" in " ".join(
552
554
  self.modifiers_sets_dict[str(self.tabWidgetModifiersSets.currentIndex())]["values"]
553
555
  ):
554
-
555
- QMessageBox.critical(
556
- self, cfg.programName, f"The shortcut code <b>{self.leCode.text().strip()}</b> already exists!"
557
- )
556
+ QMessageBox.critical(self, cfg.programName, f"The shortcut code <b>{self.leCode.text().strip()}</b> already exists!")
558
557
  self.leCode.setFocus()
559
558
  return
560
559
  txt += f" ({self.leCode.text().strip()})"
@@ -600,6 +599,8 @@ class addModifierDialog(QDialog, Ui_Dialog):
600
599
  self.lwModifiers.clear()
601
600
  self.leCode.clear()
602
601
  self.leModifier.clear()
602
+ # if self.ask_at_stop_enabled:
603
+ # self.cb_ask_at_stop.setChecked(False)
603
604
 
604
605
  self.tabMem = tabIndex
605
606
 
@@ -607,14 +608,21 @@ class addModifierDialog(QDialog, Ui_Dialog):
607
608
  self.le_name.setText(self.modifiers_sets_dict[str(tabIndex)]["name"])
608
609
  self.le_description.setText(self.modifiers_sets_dict[str(tabIndex)].get("description", ""))
609
610
  self.cbType.setCurrentIndex(self.modifiers_sets_dict[str(tabIndex)]["type"])
611
+ # if self.ask_at_stop_enabled:
612
+ # self.cb_ask_at_stop.setChecked(self.modifiers_sets_dict[str(tabIndex)].get("ask at stop", False))
613
+
610
614
  self.lwModifiers.addItems(self.modifiers_sets_dict[str(tabIndex)]["values"])
611
615
 
612
- def getModifiers(self) -> str:
616
+ def get_modifiers(self) -> str:
613
617
  """
614
618
  returns modifiers as string
615
619
  """
616
- keys_to_delete = []
620
+ keys_to_delete: list = []
617
621
  for idx in self.modifiers_sets_dict:
622
+ # add ask_at_stop value (boolean) to each set of modifiers
623
+ if self.ask_at_stop_enabled:
624
+ self.modifiers_sets_dict[idx]["ask at stop"] = self.cb_ask_at_stop.isChecked()
625
+ # delete modifiers without values for selection
618
626
  if (
619
627
  self.modifiers_sets_dict[idx]["type"] in (cfg.SINGLE_SELECTION, cfg.MULTI_SELECTION)
620
628
  and not self.modifiers_sets_dict[idx]["values"]