boris-behav-obs 8.12__py3-none-any.whl → 9.7.6__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.

Potentially problematic release.


This version of boris-behav-obs might be problematic. Click here for more details.

Files changed (128) hide show
  1. boris/__init__.py +1 -1
  2. boris/__main__.py +1 -1
  3. boris/about.py +28 -39
  4. boris/add_modifier.py +122 -109
  5. boris/add_modifier_ui.py +239 -135
  6. boris/advanced_event_filtering.py +81 -45
  7. boris/analysis_plugins/__init__.py +0 -0
  8. boris/analysis_plugins/_latency.py +59 -0
  9. boris/analysis_plugins/irr_cohen_kappa.py +109 -0
  10. boris/analysis_plugins/irr_cohen_kappa_with_modifiers.py +112 -0
  11. boris/analysis_plugins/irr_weighted_cohen_kappa.py +157 -0
  12. boris/analysis_plugins/irr_weighted_cohen_kappa_with_modifiers.py +162 -0
  13. boris/analysis_plugins/list_of_dataframe_columns.py +22 -0
  14. boris/analysis_plugins/number_of_occurences.py +22 -0
  15. boris/analysis_plugins/number_of_occurences_by_independent_variable.py +54 -0
  16. boris/analysis_plugins/time_budget.py +61 -0
  17. boris/behav_coding_map_creator.py +228 -229
  18. boris/behavior_binary_table.py +33 -50
  19. boris/behaviors_coding_map.py +17 -18
  20. boris/boris_cli.py +6 -25
  21. boris/cmd_arguments.py +12 -1
  22. boris/coding_pad.py +42 -49
  23. boris/config.py +141 -65
  24. boris/config_file.py +58 -67
  25. boris/connections.py +107 -61
  26. boris/converters.py +13 -37
  27. boris/converters_ui.py +187 -110
  28. boris/cooccurence.py +250 -0
  29. boris/core.py +2373 -1786
  30. boris/core_qrc.py +15895 -10743
  31. boris/core_ui.py +943 -798
  32. boris/db_functions.py +17 -42
  33. boris/dev.py +109 -8
  34. boris/dialog.py +482 -236
  35. boris/duration_widget.py +9 -14
  36. boris/edit_event.py +61 -31
  37. boris/edit_event_ui.py +208 -97
  38. boris/event_operations.py +408 -293
  39. boris/events_cursor.py +25 -17
  40. boris/events_snapshots.py +36 -82
  41. boris/exclusion_matrix.py +4 -9
  42. boris/export_events.py +184 -223
  43. boris/export_observation.py +74 -100
  44. boris/external_processes.py +123 -98
  45. boris/geometric_measurement.py +644 -290
  46. boris/gui_utilities.py +91 -14
  47. boris/image_overlay.py +4 -4
  48. boris/import_observations.py +190 -98
  49. boris/ipc_mpv.py +325 -0
  50. boris/irr.py +20 -57
  51. boris/latency.py +31 -24
  52. boris/measurement_widget.py +14 -18
  53. boris/media_file.py +17 -19
  54. boris/menu_options.py +17 -6
  55. boris/modifier_coding_map_creator.py +1013 -0
  56. boris/modifiers_coding_map.py +7 -9
  57. boris/mpv.py +1 -0
  58. boris/mpv2.py +732 -705
  59. boris/observation.py +533 -221
  60. boris/observation_operations.py +1025 -390
  61. boris/observation_ui.py +572 -362
  62. boris/observations_list.py +71 -53
  63. boris/otx_parser.py +74 -68
  64. boris/param_panel.py +31 -16
  65. boris/param_panel_ui.py +254 -138
  66. boris/player_dock_widget.py +90 -60
  67. boris/plot_data_module.py +25 -33
  68. boris/plot_events.py +127 -90
  69. boris/plot_events_rt.py +17 -31
  70. boris/plot_spectrogram_rt.py +95 -30
  71. boris/plot_waveform_rt.py +32 -21
  72. boris/plugins.py +431 -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 +306 -83
  80. boris/preferences_ui.py +684 -227
  81. boris/project.py +448 -293
  82. boris/project_functions.py +671 -238
  83. boris/project_import_export.py +213 -222
  84. boris/project_ui.py +674 -438
  85. boris/qrc_boris.py +6 -3
  86. boris/qrc_boris5.py +6 -3
  87. boris/select_modifiers.py +74 -48
  88. boris/select_observations.py +20 -198
  89. boris/select_subj_behav.py +67 -39
  90. boris/state_events.py +52 -35
  91. boris/subjects_pad.py +6 -9
  92. boris/synthetic_time_budget.py +45 -28
  93. boris/time_budget_functions.py +171 -171
  94. boris/time_budget_widget.py +84 -114
  95. boris/transitions.py +41 -47
  96. boris/utilities.py +627 -236
  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 +95 -29
  101. boris/view_df.py +104 -0
  102. boris/view_df_ui.py +75 -0
  103. boris/write_event.py +538 -0
  104. boris_behav_obs-9.7.6.dist-info/METADATA +139 -0
  105. boris_behav_obs-9.7.6.dist-info/RECORD +109 -0
  106. {boris_behav_obs-8.12.dist-info → boris_behav_obs-9.7.6.dist-info}/WHEEL +1 -1
  107. boris_behav_obs-9.7.6.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 -36
  112. boris/core.ui +0 -1556
  113. boris/edit_event.ui +0 -233
  114. boris/icons/logo_eye.ico +0 -0
  115. boris/map_creator.py +0 -850
  116. boris/observation.ui +0 -814
  117. boris/param_panel.ui +0 -379
  118. boris/preferences.ui +0 -537
  119. boris/project.ui +0 -1069
  120. boris/project_server.py +0 -236
  121. boris/vlc.py +0 -10343
  122. boris/vlc_local.py +0 -90
  123. boris_behav_obs-8.12.dist-info/LICENSE.TXT +0 -674
  124. boris_behav_obs-8.12.dist-info/METADATA +0 -128
  125. boris_behav_obs-8.12.dist-info/RECORD +0 -108
  126. boris_behav_obs-8.12.dist-info/entry_points.txt +0 -3
  127. {boris → boris_behav_obs-9.7.6.dist-info/licenses}/LICENSE.TXT +0 -0
  128. {boris_behav_obs-8.12.dist-info → boris_behav_obs-9.7.6.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,23 +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
- try:
38
- if "libmpv.so.1" in mpv.sofile:
39
- from . import mpv as mpv
40
- except AttributeError:
41
- if "mpv-1.dll" in mpv.dll:
42
- from . import mpv as mpv
43
-
44
- except RuntimeError: # libmpv found but version too old
45
- from . import mpv as mpv
46
-
47
- from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR
48
- from PyQt5.QtGui import QPixmap
49
- from PyQt5.QtWidgets import QMessageBox
34
+ from PySide6.QtGui import QPixmap
35
+ from PySide6.QtWidgets import QMessageBox
50
36
 
51
37
 
52
38
  def actionAbout_activated(self):
@@ -54,25 +40,26 @@ def actionAbout_activated(self):
54
40
  About dialog
55
41
  """
56
42
 
57
- programs_versions = ["MPV media player"]
58
- if sys.platform.startswith("linux"):
59
- programs_versions.append(
60
- f"Library version: {'.'.join([str(x) for x in mpv._mpv_client_api_version()])} file: {mpv.sofile}"
61
- )
62
- if sys.platform.startswith("win"):
63
- programs_versions.append(
64
- 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()}"
65
51
  )
52
+ )
66
53
 
67
54
  # ffmpeg
68
55
  if self.ffmpeg_bin == "ffmpeg" and sys.platform.startswith("linux"):
69
- ffmpeg_true_path = subprocess.getoutput("which ffmpeg")
56
+ ffmpeg_true_path: str = subprocess.getoutput("which ffmpeg")
70
57
  else:
71
58
  ffmpeg_true_path = self.ffmpeg_bin
72
59
  programs_versions.extend(
73
60
  [
74
61
  "\nFFmpeg",
75
- subprocess.getoutput(f'"{self.ffmpeg_bin}" -version').split("\n")[0],
62
+ subprocess.getoutput(cmd=f'"{self.ffmpeg_bin}" -version').split(sep="\n")[0],
76
63
  f"Path: {ffmpeg_true_path}",
77
64
  "https://www.ffmpeg.org",
78
65
  ]
@@ -88,13 +75,11 @@ def actionAbout_activated(self):
88
75
  programs_versions.extend(["\nPandas", f"version {pd.__version__}", "https://pandas.pydata.org"])
89
76
 
90
77
  # graphviz
91
- gv_result = subprocess.getoutput("dot -V")
78
+ gv_result = subprocess.getoutput(cmd="dot -V")
92
79
 
93
- programs_versions.extend(
94
- ["\nGraphViz", gv_result if "graphviz" in gv_result else "not installed", "https://www.graphviz.org/"]
95
- )
80
+ programs_versions.extend(["\nGraphViz", gv_result if "graphviz" in gv_result else "not installed", "https://www.graphviz.org/"])
96
81
 
97
- about_dialog = QMessageBox()
82
+ about_dialog: QMessageBox = QMessageBox()
98
83
  about_dialog.setIconPixmap(QPixmap(":/boris_unito"))
99
84
 
100
85
  about_dialog.setWindowTitle(f"About {cfg.programName}")
@@ -105,28 +90,29 @@ def actionAbout_activated(self):
105
90
  about_dialog.setInformativeText(
106
91
  (
107
92
  f"<b>{cfg.programName}</b> v. {version.__version__} - {version.__version_date__}"
108
- "<p>Copyright &copy; 2012-2023 Olivier Friard - Marco Gamba<br>"
93
+ "<p>Copyright &copy; 2012-2025 Olivier Friard - Marco Gamba<br>"
109
94
  "Department of Life Sciences and Systems Biology<br>"
110
95
  "University of Torino - Italy<br>"
111
96
  "<br>"
112
- 'BORIS is released under the <a href="http://www.gnu.org/copyleft/gpl.html">GNU General Public License</a><br>'
113
- '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>'
114
99
  "<br>"
115
100
  "The authors would like to acknowledge Valentina Matteucci for her precious help."
116
101
  "<hr>"
117
102
  "How to cite BORIS:<br>"
118
103
  "Friard, O. and Gamba, M. (2016), BORIS: a free, versatile open-source event-logging software for video/audio "
119
104
  "coding and live observations. Methods Ecol Evol, 7: 1325–1330.<br>"
120
- '<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>'
121
106
  )
122
107
  )
108
+ """
123
109
  n = "\n"
124
110
  current_system = platform.uname()
125
111
  details = (
126
112
  f"Operating system: {current_system.system} {current_system.release} {current_system.version} \n"
127
113
  f"CPU: {current_system.machine} {current_system.processor}\n\n"
128
114
  f"Python {platform.python_version()} ({'64-bit' if sys.maxsize > 2**32 else '32-bit'})"
129
- f" - Qt {QT_VERSION_STR} - PyQt {PYQT_VERSION_STR}\n\n"
115
+ f"Qt {qVersion()} - PySide {PySide6.__version__}\n"
130
116
  )
131
117
 
132
118
  r, memory = util.mem_info()
@@ -135,8 +121,11 @@ def actionAbout_activated(self):
135
121
  f"Memory (RAM) Total: {memory.get('total_memory', 'Not available'):.2f} Mb "
136
122
  f"Free: {memory.get('free_memory', 'Not available'):.2f} Mb\n\n"
137
123
  )
124
+ """
125
+
126
+ details = util.get_systeminfo()
138
127
 
139
- details += n.join(programs_versions)
128
+ details += "\n".join(programs_versions)
140
129
  """
141
130
  memory_in_use = f"{utilities.rss_memory_used(self.pid)} Mb" if utilities.rss_memory_used(self.pid) != -1 else "Not available"
142
131
  percent_memory_in_use = (f"({utilities.rss_memory_percent_used(self.pid):.1f} % of total memory)"
@@ -151,4 +140,4 @@ def actionAbout_activated(self):
151
140
 
152
141
  about_dialog.setDetailedText(details)
153
142
 
154
- _ = 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
- self.leSetName.textChanged.connect(self.set_name_changed)
69
- self.le_set_description.textChanged.connect(self.set_description_changed)
68
+ self.le_name.textChanged.connect(self.set_name_changed)
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
 
@@ -86,11 +91,13 @@ class addModifierDialog(QDialog, Ui_Dialog):
86
91
  self.tabWidgetModifiersSets.addTab(QWidget(), f"Set #{int(idx) + 1}")
87
92
 
88
93
  if self.tabWidgetModifiersSets.currentIndex() == -1:
89
- for w in [
90
- self.lbSetName,
94
+ for w in (
95
+ self.lb_name,
96
+ self.le_name,
91
97
  self.lbType,
92
98
  self.lbValues,
93
- self.leSetName,
99
+ self.lb_description,
100
+ self.le_description,
94
101
  self.cbType,
95
102
  self.lwModifiers,
96
103
  self.pbMoveUp,
@@ -102,33 +109,29 @@ class addModifierDialog(QDialog, Ui_Dialog):
102
109
  self.pb_add_subjects,
103
110
  self.pb_load_file,
104
111
  self.pb_sort_modifiers,
105
- ]:
112
+ self.cb_ask_at_stop,
113
+ ):
106
114
  w.setVisible(False)
107
- for w in [self.leModifier, self.leCode, self.pbAddModifier, self.pbModifyModifier]:
115
+ for w in (self.leModifier, self.leCode, self.pbAddModifier, self.pbModifyModifier):
108
116
  w.setEnabled(False)
109
117
 
110
118
  # set first tab as active
111
119
  self.tabMem = 0
112
120
 
113
121
  def pb_pushed(self, button):
114
-
115
122
  if self.leModifier.text():
116
123
  if (
117
124
  dialog.MessageDialog(
118
125
  cfg.programName,
119
- (
120
- "You are working on a behavior.<br>"
121
- "If you close the window it will be lost.<br>"
122
- "Do you want to change modifiers set"
123
- ),
124
- ["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],
125
128
  )
126
129
  == cfg.CANCEL
127
130
  ):
128
131
  return
129
- if button == "ok":
132
+ if button == cfg.OK:
130
133
  self.accept()
131
- if button == "cancel":
134
+ if button == cfg.CANCEL:
132
135
  self.reject()
133
136
 
134
137
  def add_subjects(self):
@@ -151,43 +154,38 @@ class addModifierDialog(QDialog, Ui_Dialog):
151
154
  add modifiers from file
152
155
  """
153
156
 
154
- fn = QFileDialog().getOpenFileName(self, "Load modifiers from file", "", "All files (*)")
155
- file_name = fn[0] if type(fn) is tuple else fn
156
- if file_name:
157
- try:
158
- with open(file_name) as f_in:
159
- for line in f_in:
160
- if line.strip():
161
-
162
- for c in cfg.CHAR_FORBIDDEN_IN_MODIFIERS:
163
- if c in line.strip():
164
- QMessageBox.critical(
165
- self,
166
- cfg.programName,
167
- (
168
- f"The character <b>{c}</b> is not allowed.<br>"
169
- "The following characters are not allowed in modifiers:<br>"
170
- f"<b>{cfg.CHAR_FORBIDDEN_IN_MODIFIERS}</b>"
171
- ),
172
- )
173
- break
174
- else:
175
-
176
- if line.strip() not in [
177
- self.lwModifiers.item(x).text() for x in range(self.lwModifiers.count())
178
- ]:
179
-
180
- if self.itemPositionMem != -1:
181
- self.lwModifiers.insertItem(self.itemPositionMem, line.strip())
182
- else:
183
- self.lwModifiers.addItem(line.strip())
184
-
185
- self.modifiers_sets_dict[str(self.tabWidgetModifiersSets.currentIndex())]["values"] = [
186
- self.lwModifiers.item(x).text() for x in range(self.lwModifiers.count())
187
- ]
188
- except Exception:
189
- QMessageBox.warning(self, cfg.programName, f"Error reading modifiers from file:<br>{file_name}")
190
- 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}")
191
189
 
192
190
  def sort_modifiers(self):
193
191
  """
@@ -212,19 +210,15 @@ class addModifierDialog(QDialog, Ui_Dialog):
212
210
  """
213
211
  if not self.modifiers_sets_dict:
214
212
  self.modifiers_sets_dict["0"] = {"name": "", "description": "", "type": cfg.SINGLE_SELECTION, "values": []}
215
- self.modifiers_sets_dict[str(self.tabWidgetModifiersSets.currentIndex())][
216
- "name"
217
- ] = self.leSetName.text().strip()
213
+ self.modifiers_sets_dict[str(self.tabWidgetModifiersSets.currentIndex())]["name"] = self.le_name.text().strip()
218
214
 
219
215
  def set_description_changed(self):
220
216
  """
221
- set name changed
217
+ set description changed
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_set_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
  """
@@ -234,7 +228,7 @@ class addModifierDialog(QDialog, Ui_Dialog):
234
228
  self.modifiers_sets_dict["0"] = {"name": "", "description": "", "type": cfg.SINGLE_SELECTION, "values": []}
235
229
  self.modifiers_sets_dict[str(self.tabWidgetModifiersSets.currentIndex())]["type"] = self.cbType.currentIndex()
236
230
  # disable if modifier numeric or value from external data file
237
- for obj in [
231
+ for obj in (
238
232
  self.lbValues,
239
233
  self.lwModifiers,
240
234
  self.leModifier,
@@ -250,12 +244,18 @@ class addModifierDialog(QDialog, Ui_Dialog):
250
244
  self.pbModifyModifier,
251
245
  self.pb_load_file,
252
246
  self.pb_sort_modifiers,
253
- ]:
247
+ ):
254
248
  obj.setEnabled(self.cbType.currentIndex() not in [cfg.NUMERIC_MODIFIER, cfg.EXTERNAL_DATA_MODIFIER])
255
249
  if self.cbType.currentIndex() == cfg.EXTERNAL_DATA_MODIFIER:
256
- self.lbSetName.setText("Variable name")
250
+ self.lb_name.setText("Variable name")
257
251
  else:
258
- self.lbSetName.setText("Set name")
252
+ self.lb_name.setText("Set name")
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
259
 
260
260
  def moveSetLeft(self):
261
261
  """
@@ -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)}")
@@ -333,12 +333,13 @@ class addModifierDialog(QDialog, Ui_Dialog):
333
333
  self.tabMem = self.tabWidgetModifiersSets.currentIndex()
334
334
 
335
335
  # set visible and available buttons and others elements
336
- for w in [
337
- self.lbSetName,
336
+ for w in (
337
+ self.lb_name,
338
338
  self.lbType,
339
339
  self.lbValues,
340
- self.leSetName,
341
- self.le_set_description,
340
+ self.le_name,
341
+ self.lb_description,
342
+ self.le_description,
342
343
  self.cbType,
343
344
  self.lwModifiers,
344
345
  self.pbMoveUp,
@@ -350,9 +351,11 @@ class addModifierDialog(QDialog, Ui_Dialog):
350
351
  self.pb_add_subjects,
351
352
  self.pb_load_file,
352
353
  self.pb_sort_modifiers,
353
- ]:
354
+ ):
354
355
  w.setVisible(True)
355
- for w in [self.leModifier, self.leCode, self.pbAddModifier, self.pbModifyModifier]:
356
+ self.cb_ask_at_stop.setVisible(self.ask_at_stop_enabled)
357
+
358
+ for w in (self.leModifier, self.leCode, self.pbAddModifier, self.pbModifyModifier):
356
359
  w.setEnabled(True)
357
360
  return
358
361
 
@@ -361,6 +364,7 @@ class addModifierDialog(QDialog, Ui_Dialog):
361
364
  "name": "",
362
365
  "description": "",
363
366
  "type": cfg.SINGLE_SELECTION,
367
+ "ask at stop": False,
364
368
  "values": [],
365
369
  }
366
370
  self.tabWidgetModifiersSets.addTab(QWidget(), f"Set #{len(self.modifiers_sets_dict)}")
@@ -374,16 +378,13 @@ class addModifierDialog(QDialog, Ui_Dialog):
374
378
  "It is not possible to add a modifiers' set while the current modifiers' set is empty.",
375
379
  )
376
380
 
377
- def removeSet(self):
381
+ def remove_set_of_modifiers(self):
378
382
  """
379
383
  remove set of modifiers
380
384
  """
381
385
 
382
386
  if self.tabWidgetModifiersSets.currentIndex() != -1:
383
- if (
384
- dialog.MessageDialog(cfg.programName, "Confirm deletion of this set of modifiers?", [cfg.YES, cfg.NO])
385
- == cfg.YES
386
- ):
387
+ if dialog.MessageDialog(cfg.programName, "Confirm deletion of this set of modifiers?", [cfg.YES, cfg.NO]) == cfg.YES:
387
388
  index_to_delete = self.tabWidgetModifiersSets.currentIndex()
388
389
 
389
390
  for k in range(index_to_delete, len(self.modifiers_sets_dict) - 1):
@@ -401,11 +402,13 @@ class addModifierDialog(QDialog, Ui_Dialog):
401
402
 
402
403
  # set not visible and not available buttons and others elements
403
404
  if self.tabWidgetModifiersSets.currentIndex() == -1:
404
- for w in [
405
- self.lbSetName,
405
+ for w in (
406
+ self.lb_name,
407
+ self.le_name,
406
408
  self.lbType,
407
409
  self.lbValues,
408
- self.leSetName,
410
+ self.lb_description,
411
+ self.le_description,
409
412
  self.cbType,
410
413
  self.lwModifiers,
411
414
  self.pbMoveUp,
@@ -414,18 +417,21 @@ class addModifierDialog(QDialog, Ui_Dialog):
414
417
  self.pbRemoveSet,
415
418
  self.pbMoveSetLeft,
416
419
  self.pbMoveSetRight,
417
- ]:
420
+ self.cb_ask_at_stop,
421
+ ):
418
422
  w.setVisible(False)
419
- for w in [self.leModifier, self.leCode, self.pbAddModifier, self.pbModifyModifier]:
423
+ for w in (self.leModifier, self.leCode, self.pbAddModifier, self.pbModifyModifier):
420
424
  w.setEnabled(False)
421
425
 
422
426
  if not len(self.modifiers_sets_dict):
423
427
  # set invisible and unavailable buttons and others elements
424
- for w in [
425
- self.lbSetName,
428
+ for w in (
429
+ self.lb_name,
430
+ self.le_name,
426
431
  self.lbType,
427
432
  self.lbValues,
428
- self.leSetName,
433
+ self.lb_description,
434
+ self.le_description,
429
435
  self.cbType,
430
436
  self.lwModifiers,
431
437
  self.pbMoveUp,
@@ -437,7 +443,7 @@ class addModifierDialog(QDialog, Ui_Dialog):
437
443
  self.pb_add_subjects,
438
444
  self.pb_load_file,
439
445
  self.pb_sort_modifiers,
440
- ]:
446
+ ):
441
447
  w.setVisible(False)
442
448
  for w in [self.leModifier, self.leCode, self.pbAddModifier, self.pbModifyModifier]:
443
449
  w.setEnabled(False)
@@ -500,7 +506,6 @@ class addModifierDialog(QDialog, Ui_Dialog):
500
506
  return
501
507
 
502
508
  if txt:
503
-
504
509
  if txt in [self.lwModifiers.item(x).text() for x in range(self.lwModifiers.count())]:
505
510
  QMessageBox.critical(self, cfg.programName, f"The modifier <b>{txt}</b> is already in the list")
506
511
  return
@@ -510,6 +515,7 @@ class addModifierDialog(QDialog, Ui_Dialog):
510
515
  "name": "",
511
516
  "description": "",
512
517
  "type": cfg.SINGLE_SELECTION,
518
+ "ask at stop": False,
513
519
  "values": [],
514
520
  }
515
521
 
@@ -540,16 +546,14 @@ class addModifierDialog(QDialog, Ui_Dialog):
540
546
  "name": "",
541
547
  "description": "",
542
548
  "type": cfg.SINGLE_SELECTION,
549
+ "ask at stop": False,
543
550
  "values": [],
544
551
  }
545
552
 
546
553
  if "(" + self.leCode.text().strip() + ")" in " ".join(
547
554
  self.modifiers_sets_dict[str(self.tabWidgetModifiersSets.currentIndex())]["values"]
548
555
  ):
549
-
550
- QMessageBox.critical(
551
- self, cfg.programName, f"The shortcut code <b>{self.leCode.text().strip()}</b> already exists!"
552
- )
556
+ QMessageBox.critical(self, cfg.programName, f"The shortcut code <b>{self.leCode.text().strip()}</b> already exists!")
553
557
  self.leCode.setFocus()
554
558
  return
555
559
  txt += f" ({self.leCode.text().strip()})"
@@ -595,21 +599,30 @@ class addModifierDialog(QDialog, Ui_Dialog):
595
599
  self.lwModifiers.clear()
596
600
  self.leCode.clear()
597
601
  self.leModifier.clear()
602
+ # if self.ask_at_stop_enabled:
603
+ # self.cb_ask_at_stop.setChecked(False)
598
604
 
599
605
  self.tabMem = tabIndex
600
606
 
601
607
  if tabIndex != -1:
602
- self.leSetName.setText(self.modifiers_sets_dict[str(tabIndex)]["name"])
603
- self.le_set_description.setText(self.modifiers_sets_dict[str(tabIndex)].get("description", ""))
608
+ self.le_name.setText(self.modifiers_sets_dict[str(tabIndex)]["name"])
609
+ self.le_description.setText(self.modifiers_sets_dict[str(tabIndex)].get("description", ""))
604
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
+
605
614
  self.lwModifiers.addItems(self.modifiers_sets_dict[str(tabIndex)]["values"])
606
615
 
607
- def getModifiers(self) -> str:
616
+ def get_modifiers(self) -> str:
608
617
  """
609
618
  returns modifiers as string
610
619
  """
611
- keys_to_delete = []
620
+ keys_to_delete: list = []
612
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
613
626
  if (
614
627
  self.modifiers_sets_dict[idx]["type"] in (cfg.SINGLE_SELECTION, cfg.MULTI_SELECTION)
615
628
  and not self.modifiers_sets_dict[idx]["values"]