boris-behav-obs 9.7.7__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 (109) hide show
  1. boris/__init__.py +26 -0
  2. boris/__main__.py +25 -0
  3. boris/about.py +143 -0
  4. boris/add_modifier.py +635 -0
  5. boris/add_modifier_ui.py +303 -0
  6. boris/advanced_event_filtering.py +455 -0
  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 +1110 -0
  18. boris/behavior_binary_table.py +305 -0
  19. boris/behaviors_coding_map.py +239 -0
  20. boris/boris_cli.py +340 -0
  21. boris/cmd_arguments.py +49 -0
  22. boris/coding_pad.py +280 -0
  23. boris/config.py +785 -0
  24. boris/config_file.py +356 -0
  25. boris/connections.py +409 -0
  26. boris/converters.py +333 -0
  27. boris/converters_ui.py +225 -0
  28. boris/cooccurence.py +250 -0
  29. boris/core.py +5901 -0
  30. boris/core_qrc.py +15958 -0
  31. boris/core_ui.py +1107 -0
  32. boris/db_functions.py +324 -0
  33. boris/dev.py +134 -0
  34. boris/dialog.py +1108 -0
  35. boris/duration_widget.py +238 -0
  36. boris/edit_event.py +245 -0
  37. boris/edit_event_ui.py +233 -0
  38. boris/event_operations.py +1040 -0
  39. boris/events_cursor.py +61 -0
  40. boris/events_snapshots.py +596 -0
  41. boris/exclusion_matrix.py +141 -0
  42. boris/export_events.py +1006 -0
  43. boris/export_observation.py +1203 -0
  44. boris/external_processes.py +332 -0
  45. boris/geometric_measurement.py +941 -0
  46. boris/gui_utilities.py +135 -0
  47. boris/image_overlay.py +72 -0
  48. boris/import_observations.py +242 -0
  49. boris/ipc_mpv.py +325 -0
  50. boris/irr.py +634 -0
  51. boris/latency.py +244 -0
  52. boris/measurement_widget.py +161 -0
  53. boris/media_file.py +115 -0
  54. boris/menu_options.py +213 -0
  55. boris/modifier_coding_map_creator.py +1013 -0
  56. boris/modifiers_coding_map.py +157 -0
  57. boris/mpv.py +2016 -0
  58. boris/mpv2.py +2193 -0
  59. boris/observation.py +1453 -0
  60. boris/observation_operations.py +2538 -0
  61. boris/observation_ui.py +679 -0
  62. boris/observations_list.py +337 -0
  63. boris/otx_parser.py +442 -0
  64. boris/param_panel.py +201 -0
  65. boris/param_panel_ui.py +305 -0
  66. boris/player_dock_widget.py +198 -0
  67. boris/plot_data_module.py +536 -0
  68. boris/plot_events.py +634 -0
  69. boris/plot_events_rt.py +237 -0
  70. boris/plot_spectrogram_rt.py +316 -0
  71. boris/plot_waveform_rt.py +230 -0
  72. boris/plugins.py +431 -0
  73. boris/portion/__init__.py +31 -0
  74. boris/portion/const.py +95 -0
  75. boris/portion/dict.py +365 -0
  76. boris/portion/func.py +52 -0
  77. boris/portion/interval.py +581 -0
  78. boris/portion/io.py +181 -0
  79. boris/preferences.py +510 -0
  80. boris/preferences_ui.py +770 -0
  81. boris/project.py +2007 -0
  82. boris/project_functions.py +2041 -0
  83. boris/project_import_export.py +1096 -0
  84. boris/project_ui.py +794 -0
  85. boris/qrc_boris.py +10389 -0
  86. boris/qrc_boris5.py +2579 -0
  87. boris/select_modifiers.py +312 -0
  88. boris/select_observations.py +210 -0
  89. boris/select_subj_behav.py +286 -0
  90. boris/state_events.py +197 -0
  91. boris/subjects_pad.py +106 -0
  92. boris/synthetic_time_budget.py +290 -0
  93. boris/time_budget_functions.py +1136 -0
  94. boris/time_budget_widget.py +1039 -0
  95. boris/transitions.py +365 -0
  96. boris/utilities.py +1810 -0
  97. boris/version.py +24 -0
  98. boris/video_equalizer.py +159 -0
  99. boris/video_equalizer_ui.py +248 -0
  100. boris/video_operations.py +310 -0
  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.7.dist-info/METADATA +139 -0
  105. boris_behav_obs-9.7.7.dist-info/RECORD +109 -0
  106. boris_behav_obs-9.7.7.dist-info/WHEEL +5 -0
  107. boris_behav_obs-9.7.7.dist-info/entry_points.txt +2 -0
  108. boris_behav_obs-9.7.7.dist-info/licenses/LICENSE.TXT +674 -0
  109. boris_behav_obs-9.7.7.dist-info/top_level.txt +1 -0
boris/add_modifier.py ADDED
@@ -0,0 +1,635 @@
1
+ """
2
+ BORIS
3
+ Behavioral Observation Research Interactive Software
4
+ Copyright 2012-2025 Olivier Friard
5
+
6
+ This file is part of BORIS.
7
+
8
+ BORIS is free software; you can redistribute it and/or modify
9
+ it under the terms of the GNU General Public License as published by
10
+ the Free Software Foundation; either version 3 of the License, or
11
+ any later version.
12
+
13
+ BORIS is distributed in the hope that it will be useful,
14
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ GNU General Public License for more details.
17
+
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/licPbehav_enses/>.
20
+
21
+ """
22
+
23
+ import logging
24
+ import json
25
+
26
+ from PySide6.QtGui import QIcon
27
+ from PySide6.QtWidgets import QDialog, QWidget, QFileDialog, QMessageBox
28
+
29
+ from . import dialog
30
+ from .add_modifier_ui import Ui_Dialog
31
+ from . import config as cfg
32
+ from .utilities import sorted_keys
33
+
34
+
35
+ class addModifierDialog(QDialog, Ui_Dialog):
36
+ tabMem = -1
37
+ itemPositionMem = -1
38
+
39
+ def __init__(self, modifiers_str: str, subjects: list = [], ask_at_stop_enabled: bool = False, parent=None):
40
+ super().__init__()
41
+ self.setupUi(self)
42
+
43
+ self.subjects = subjects
44
+ if not self.subjects:
45
+ self.pb_add_subjects.setEnabled(False)
46
+
47
+ self.ask_at_stop_enabled = ask_at_stop_enabled
48
+
49
+ self.pbAddModifier.clicked.connect(self.addModifier)
50
+ self.pbAddModifier.setIcon(QIcon(":/frame_forward"))
51
+ self.pbAddSet.clicked.connect(self.add_set_of_modifiers)
52
+ self.pbRemoveSet.clicked.connect(self.remove_set_of_modifiers)
53
+ self.pbModifyModifier.clicked.connect(self.modifyModifier)
54
+ self.pbModifyModifier.setIcon(QIcon(":/frame_backward"))
55
+
56
+ self.pbMoveUp.clicked.connect(self.moveModifierUp)
57
+ self.pbMoveDown.clicked.connect(self.moveModifierDown)
58
+ self.pbMoveSetLeft.clicked.connect(self.moveSetLeft)
59
+ self.pbMoveSetRight.clicked.connect(self.moveSetRight)
60
+ self.pbRemoveModifier.clicked.connect(self.removeModifier)
61
+ self.pb_sort_modifiers.clicked.connect(self.sort_modifiers)
62
+ self.pb_add_subjects.clicked.connect(self.add_subjects)
63
+ self.pb_load_file.clicked.connect(self.add_modifiers_from_file)
64
+
65
+ self.pbOK.clicked.connect(lambda: self.pb_pushed(cfg.OK))
66
+ self.pbCancel.clicked.connect(lambda: self.pb_pushed(cfg.CANCEL))
67
+
68
+ self.le_name.textChanged.connect(self.set_name_changed)
69
+ self.le_description.textChanged.connect(self.set_description_changed)
70
+
71
+ self.cbType.currentIndexChanged.connect(self.type_changed)
72
+
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 = []
77
+ for idx in sorted_keys(dummy_dict):
78
+ modif_values.append(dummy_dict[idx])
79
+
80
+ self.modifiers_sets_dict: dict = {}
81
+ for modif in modif_values:
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)
86
+
87
+ self.tabWidgetModifiersSets.currentChanged.connect(self.tabWidgetModifiersSets_changed)
88
+
89
+ # create tab
90
+ for idx in sorted_keys(self.modifiers_sets_dict):
91
+ self.tabWidgetModifiersSets.addTab(QWidget(), f"Set #{int(idx) + 1}")
92
+
93
+ if self.tabWidgetModifiersSets.currentIndex() == -1:
94
+ for w in (
95
+ self.lb_name,
96
+ self.le_name,
97
+ self.lbType,
98
+ self.lbValues,
99
+ self.lb_description,
100
+ self.le_description,
101
+ self.cbType,
102
+ self.lwModifiers,
103
+ self.pbMoveUp,
104
+ self.pbMoveDown,
105
+ self.pbRemoveModifier,
106
+ self.pbRemoveSet,
107
+ self.pbMoveSetLeft,
108
+ self.pbMoveSetRight,
109
+ self.pb_add_subjects,
110
+ self.pb_load_file,
111
+ self.pb_sort_modifiers,
112
+ self.cb_ask_at_stop,
113
+ ):
114
+ w.setVisible(False)
115
+ for w in (self.leModifier, self.leCode, self.pbAddModifier, self.pbModifyModifier):
116
+ w.setEnabled(False)
117
+
118
+ # set first tab as active
119
+ self.tabMem = 0
120
+
121
+ def pb_pushed(self, button):
122
+ if self.leModifier.text():
123
+ if (
124
+ dialog.MessageDialog(
125
+ cfg.programName,
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],
128
+ )
129
+ == cfg.CANCEL
130
+ ):
131
+ return
132
+ if button == cfg.OK:
133
+ self.accept()
134
+ if button == cfg.CANCEL:
135
+ self.reject()
136
+
137
+ def add_subjects(self):
138
+ """
139
+ add subjects as modifiers
140
+ """
141
+
142
+ for subject, key in self.subjects:
143
+ if self.itemPositionMem != -1:
144
+ self.lwModifiers.insertItem(self.itemPositionMem, f"{subject} ({key})" if key else subject)
145
+ else:
146
+ self.lwModifiers.addItem(f"{subject} ({key})" if key else subject)
147
+
148
+ self.modifiers_sets_dict[str(self.tabWidgetModifiersSets.currentIndex())]["values"] = [
149
+ self.lwModifiers.item(x).text() for x in range(self.lwModifiers.count())
150
+ ]
151
+
152
+ def add_modifiers_from_file(self):
153
+ """
154
+ add modifiers from file
155
+ """
156
+
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}")
189
+
190
+ def sort_modifiers(self):
191
+ """
192
+ sort modifiers
193
+ """
194
+
195
+ modifiers = sorted([self.lwModifiers.item(x).text() for x in range(self.lwModifiers.count())])
196
+ self.lwModifiers.clear()
197
+ for modifier in modifiers:
198
+ if self.itemPositionMem != -1:
199
+ self.lwModifiers.insertItem(self.itemPositionMem, modifier)
200
+ else:
201
+ self.lwModifiers.addItem(modifier)
202
+
203
+ self.modifiers_sets_dict[str(self.tabWidgetModifiersSets.currentIndex())]["values"] = [
204
+ self.lwModifiers.item(x).text() for x in range(self.lwModifiers.count())
205
+ ]
206
+
207
+ def set_name_changed(self):
208
+ """
209
+ set name changed
210
+ """
211
+ if not self.modifiers_sets_dict:
212
+ self.modifiers_sets_dict["0"] = {"name": "", "description": "", "type": cfg.SINGLE_SELECTION, "values": []}
213
+ self.modifiers_sets_dict[str(self.tabWidgetModifiersSets.currentIndex())]["name"] = self.le_name.text().strip()
214
+
215
+ def set_description_changed(self):
216
+ """
217
+ set description changed
218
+ """
219
+ if not self.modifiers_sets_dict:
220
+ self.modifiers_sets_dict["0"] = {"name": "", "description": "", "type": cfg.SINGLE_SELECTION, "values": []}
221
+ self.modifiers_sets_dict[str(self.tabWidgetModifiersSets.currentIndex())]["description"] = self.le_description.text().strip()
222
+
223
+ def type_changed(self):
224
+ """
225
+ type changed
226
+ """
227
+ if not self.modifiers_sets_dict:
228
+ self.modifiers_sets_dict["0"] = {"name": "", "description": "", "type": cfg.SINGLE_SELECTION, "values": []}
229
+ self.modifiers_sets_dict[str(self.tabWidgetModifiersSets.currentIndex())]["type"] = self.cbType.currentIndex()
230
+ # disable if modifier numeric or value from external data file
231
+ for obj in (
232
+ self.lbValues,
233
+ self.lwModifiers,
234
+ self.leModifier,
235
+ self.leCode,
236
+ self.lbModifier,
237
+ self.lbCode,
238
+ self.lbCodeHelp,
239
+ self.pbMoveUp,
240
+ self.pbMoveDown,
241
+ self.pbRemoveModifier,
242
+ self.pb_add_subjects,
243
+ self.pbAddModifier,
244
+ self.pbModifyModifier,
245
+ self.pb_load_file,
246
+ self.pb_sort_modifiers,
247
+ ):
248
+ obj.setEnabled(self.cbType.currentIndex() not in [cfg.NUMERIC_MODIFIER, cfg.EXTERNAL_DATA_MODIFIER])
249
+ if self.cbType.currentIndex() == cfg.EXTERNAL_DATA_MODIFIER:
250
+ self.lb_name.setText("Variable name")
251
+ else:
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
+
260
+ def moveSetLeft(self):
261
+ """
262
+ move selected modifiers set left
263
+ """
264
+ if self.tabWidgetModifiersSets.currentIndex():
265
+ (
266
+ self.modifiers_sets_dict[str(self.tabWidgetModifiersSets.currentIndex() - 1)],
267
+ self.modifiers_sets_dict[str(self.tabWidgetModifiersSets.currentIndex())],
268
+ ) = (
269
+ dict(self.modifiers_sets_dict[str(self.tabWidgetModifiersSets.currentIndex())]),
270
+ dict(self.modifiers_sets_dict[str(self.tabWidgetModifiersSets.currentIndex() - 1)]),
271
+ )
272
+ self.tabWidgetModifiersSets.setCurrentIndex(self.tabWidgetModifiersSets.currentIndex() - 1)
273
+ self.tabMem = self.tabWidgetModifiersSets.currentIndex()
274
+
275
+ def moveSetRight(self):
276
+ """
277
+ move selected modifiers set right
278
+ """
279
+ if self.tabWidgetModifiersSets.currentIndex() < self.tabWidgetModifiersSets.count() - 1:
280
+ (
281
+ self.modifiers_sets_dict[str(self.tabWidgetModifiersSets.currentIndex() + 1)],
282
+ self.modifiers_sets_dict[str(self.tabWidgetModifiersSets.currentIndex())],
283
+ ) = (
284
+ dict(self.modifiers_sets_dict[str(self.tabWidgetModifiersSets.currentIndex())]),
285
+ dict(self.modifiers_sets_dict[str(self.tabWidgetModifiersSets.currentIndex() + 1)]),
286
+ )
287
+
288
+ self.tabWidgetModifiersSets.setCurrentIndex(self.tabWidgetModifiersSets.currentIndex() + 1)
289
+ self.tabMem = self.tabWidgetModifiersSets.currentIndex()
290
+
291
+ def moveModifierUp(self):
292
+ """
293
+ move up the selected modifier
294
+ """
295
+ if self.lwModifiers.currentRow() >= 0:
296
+ currentRow = self.lwModifiers.currentRow()
297
+ currentItem = self.lwModifiers.takeItem(currentRow)
298
+ self.lwModifiers.insertItem(currentRow - 1, currentItem)
299
+ self.lwModifiers.setCurrentItem(currentItem)
300
+ self.modifiers_sets_dict[str(self.tabWidgetModifiersSets.currentIndex())]["values"] = [
301
+ self.lwModifiers.item(x).text() for x in range(self.lwModifiers.count())
302
+ ]
303
+
304
+ def moveModifierDown(self):
305
+ """
306
+ move down the selected modifier
307
+ """
308
+ if self.lwModifiers.currentRow() >= 0:
309
+ currentRow = self.lwModifiers.currentRow()
310
+ currentItem = self.lwModifiers.takeItem(currentRow)
311
+ self.lwModifiers.insertItem(currentRow + 1, currentItem)
312
+ self.lwModifiers.setCurrentItem(currentItem)
313
+ self.modifiers_sets_dict[str(self.tabWidgetModifiersSets.currentIndex())]["values"] = [
314
+ self.lwModifiers.item(x).text() for x in range(self.lwModifiers.count())
315
+ ]
316
+
317
+ def add_set_of_modifiers(self):
318
+ """
319
+ Add a set of modifiers
320
+ """
321
+
322
+ # no modifiers set
323
+ if self.tabWidgetModifiersSets.currentIndex() == -1:
324
+ self.modifiers_sets_dict[str(len(self.modifiers_sets_dict))] = {
325
+ "name": "",
326
+ "description": "",
327
+ "type": cfg.SINGLE_SELECTION,
328
+ "ask at stop": False,
329
+ "values": [],
330
+ }
331
+ self.tabWidgetModifiersSets.addTab(QWidget(), f"Set #{len(self.modifiers_sets_dict)}")
332
+ self.tabWidgetModifiersSets.setCurrentIndex(self.tabWidgetModifiersSets.count() - 1)
333
+ self.tabMem = self.tabWidgetModifiersSets.currentIndex()
334
+
335
+ # set visible and available buttons and others elements
336
+ for w in (
337
+ self.lb_name,
338
+ self.lbType,
339
+ self.lbValues,
340
+ self.le_name,
341
+ self.lb_description,
342
+ self.le_description,
343
+ self.cbType,
344
+ self.lwModifiers,
345
+ self.pbMoveUp,
346
+ self.pbMoveDown,
347
+ self.pbRemoveModifier,
348
+ self.pbRemoveSet,
349
+ self.pbMoveSetLeft,
350
+ self.pbMoveSetRight,
351
+ self.pb_add_subjects,
352
+ self.pb_load_file,
353
+ self.pb_sort_modifiers,
354
+ ):
355
+ w.setVisible(True)
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):
359
+ w.setEnabled(True)
360
+ return
361
+
362
+ if len(self.modifiers_sets_dict[str(self.tabWidgetModifiersSets.currentIndex())]):
363
+ self.modifiers_sets_dict[str(len(self.modifiers_sets_dict))] = {
364
+ "name": "",
365
+ "description": "",
366
+ "type": cfg.SINGLE_SELECTION,
367
+ "ask at stop": False,
368
+ "values": [],
369
+ }
370
+ self.tabWidgetModifiersSets.addTab(QWidget(), f"Set #{len(self.modifiers_sets_dict)}")
371
+ self.tabWidgetModifiersSets.setCurrentIndex(self.tabWidgetModifiersSets.count() - 1)
372
+ self.tabMem = self.tabWidgetModifiersSets.currentIndex()
373
+
374
+ else:
375
+ QMessageBox.information(
376
+ self,
377
+ cfg.programName,
378
+ "It is not possible to add a modifiers' set while the current modifiers' set is empty.",
379
+ )
380
+
381
+ def remove_set_of_modifiers(self):
382
+ """
383
+ remove set of modifiers
384
+ """
385
+
386
+ if self.tabWidgetModifiersSets.currentIndex() != -1:
387
+ if dialog.MessageDialog(cfg.programName, "Confirm deletion of this set of modifiers?", [cfg.YES, cfg.NO]) == cfg.YES:
388
+ index_to_delete = self.tabWidgetModifiersSets.currentIndex()
389
+
390
+ for k in range(index_to_delete, len(self.modifiers_sets_dict) - 1):
391
+ self.modifiers_sets_dict[str(k)] = self.modifiers_sets_dict[str(k + 1)]
392
+ # del last key
393
+ del self.modifiers_sets_dict[str(len(self.modifiers_sets_dict) - 1)]
394
+
395
+ # remove all tabs
396
+ while self.tabWidgetModifiersSets.count():
397
+ self.tabWidgetModifiersSets.removeTab(0)
398
+
399
+ # recreate tabs
400
+ for idx in sorted_keys(self.modifiers_sets_dict):
401
+ self.tabWidgetModifiersSets.addTab(QWidget(), f"Set #{int(idx) + 1}")
402
+
403
+ # set not visible and not available buttons and others elements
404
+ if self.tabWidgetModifiersSets.currentIndex() == -1:
405
+ for w in (
406
+ self.lb_name,
407
+ self.le_name,
408
+ self.lbType,
409
+ self.lbValues,
410
+ self.lb_description,
411
+ self.le_description,
412
+ self.cbType,
413
+ self.lwModifiers,
414
+ self.pbMoveUp,
415
+ self.pbMoveDown,
416
+ self.pbRemoveModifier,
417
+ self.pbRemoveSet,
418
+ self.pbMoveSetLeft,
419
+ self.pbMoveSetRight,
420
+ self.cb_ask_at_stop,
421
+ ):
422
+ w.setVisible(False)
423
+ for w in (self.leModifier, self.leCode, self.pbAddModifier, self.pbModifyModifier):
424
+ w.setEnabled(False)
425
+
426
+ if not len(self.modifiers_sets_dict):
427
+ # set invisible and unavailable buttons and others elements
428
+ for w in (
429
+ self.lb_name,
430
+ self.le_name,
431
+ self.lbType,
432
+ self.lbValues,
433
+ self.lb_description,
434
+ self.le_description,
435
+ self.cbType,
436
+ self.lwModifiers,
437
+ self.pbMoveUp,
438
+ self.pbMoveDown,
439
+ self.pbRemoveModifier,
440
+ self.pbRemoveSet,
441
+ self.pbMoveSetLeft,
442
+ self.pbMoveSetRight,
443
+ self.pb_add_subjects,
444
+ self.pb_load_file,
445
+ self.pb_sort_modifiers,
446
+ ):
447
+ w.setVisible(False)
448
+ for w in [self.leModifier, self.leCode, self.pbAddModifier, self.pbModifyModifier]:
449
+ w.setEnabled(False)
450
+ return
451
+
452
+ else:
453
+ QMessageBox.information(self, cfg.programName, "It is not possible to remove the last modifiers' set.")
454
+
455
+ def modifyModifier(self):
456
+ """
457
+ modify modifier <- arrow
458
+ """
459
+
460
+ if self.lwModifiers.currentRow() >= 0:
461
+ txt = self.lwModifiers.currentItem().text()
462
+ code = ""
463
+ if "(" in txt and ")" in txt:
464
+ code = txt.split("(")[1].split(")")[0]
465
+
466
+ self.leModifier.setText(txt.split("(")[0].strip())
467
+ self.leCode.setText(code)
468
+
469
+ self.modifiers_sets_dict[str(self.tabWidgetModifiersSets.currentIndex())]["values"].remove(
470
+ self.lwModifiers.currentItem().text()
471
+ )
472
+
473
+ self.itemPositionMem = self.lwModifiers.currentRow()
474
+ self.lwModifiers.takeItem(self.lwModifiers.currentRow())
475
+ else:
476
+ QMessageBox.information(self, cfg.programName, "Select a modifier to modify from the modifiers set")
477
+
478
+ def removeModifier(self):
479
+ """
480
+ remove modifier from set
481
+ """
482
+ if self.lwModifiers.currentIndex().row() >= 0:
483
+ self.lwModifiers.takeItem(self.lwModifiers.currentIndex().row())
484
+ self.modifiers_sets_dict[str(self.tabWidgetModifiersSets.currentIndex())]["values"] = [
485
+ self.lwModifiers.item(x).text() for x in range(self.lwModifiers.count())
486
+ ]
487
+
488
+ def addModifier(self):
489
+ """
490
+ add a modifier to set
491
+ """
492
+
493
+ txt = self.leModifier.text().strip()
494
+ for c in cfg.CHAR_FORBIDDEN_IN_MODIFIERS:
495
+ if c in txt:
496
+ QMessageBox.critical(
497
+ self,
498
+ cfg.programName,
499
+ (
500
+ f"The character <b>{c}</b> is not allowed.<br>"
501
+ "The following characters are not allowed in modifiers:<br>"
502
+ f"<b>{cfg.CHAR_FORBIDDEN_IN_MODIFIERS}</b>"
503
+ ),
504
+ )
505
+ self.leModifier.setFocus()
506
+ return
507
+
508
+ if txt:
509
+ if txt in [self.lwModifiers.item(x).text() for x in range(self.lwModifiers.count())]:
510
+ QMessageBox.critical(self, cfg.programName, f"The modifier <b>{txt}</b> is already in the list")
511
+ return
512
+
513
+ if not self.modifiers_sets_dict:
514
+ self.modifiers_sets_dict["0"] = {
515
+ "name": "",
516
+ "description": "",
517
+ "type": cfg.SINGLE_SELECTION,
518
+ "ask at stop": False,
519
+ "values": [],
520
+ }
521
+
522
+ if len(self.leCode.text().strip()) > 1:
523
+ if self.leCode.text().strip().upper() not in cfg.function_keys.values():
524
+ QMessageBox.critical(
525
+ self,
526
+ cfg.programName,
527
+ "The modifier key code can not exceed one key\nSelect one key or a function key (F1, F2 ... F12)",
528
+ )
529
+ self.leCode.setFocus()
530
+ return
531
+
532
+ if self.leCode.text().strip():
533
+ for c in cfg.CHAR_FORBIDDEN_IN_MODIFIERS:
534
+ if c in self.leCode.text().strip():
535
+ QMessageBox.critical(
536
+ self,
537
+ cfg.programName,
538
+ f"The modifier key code is not allowed {cfg.CHAR_FORBIDDEN_IN_MODIFIERS}!",
539
+ )
540
+ self.leCode.setFocus()
541
+ return
542
+
543
+ # check if code already exists
544
+ if not self.modifiers_sets_dict:
545
+ self.modifiers_sets_dict[str(self.tabWidgetModifiersSets.currentIndex())] = {
546
+ "name": "",
547
+ "description": "",
548
+ "type": cfg.SINGLE_SELECTION,
549
+ "ask at stop": False,
550
+ "values": [],
551
+ }
552
+
553
+ if "(" + self.leCode.text().strip() + ")" in " ".join(
554
+ self.modifiers_sets_dict[str(self.tabWidgetModifiersSets.currentIndex())]["values"]
555
+ ):
556
+ QMessageBox.critical(self, cfg.programName, f"The shortcut code <b>{self.leCode.text().strip()}</b> already exists!")
557
+ self.leCode.setFocus()
558
+ return
559
+ txt += f" ({self.leCode.text().strip()})"
560
+
561
+ if self.itemPositionMem != -1:
562
+ self.lwModifiers.insertItem(self.itemPositionMem, txt)
563
+ else:
564
+ self.lwModifiers.addItem(txt)
565
+
566
+ self.modifiers_sets_dict[str(self.tabWidgetModifiersSets.currentIndex())]["values"] = [
567
+ self.lwModifiers.item(x).text() for x in range(self.lwModifiers.count())
568
+ ]
569
+ self.leModifier.setText("")
570
+ self.leCode.setText("")
571
+
572
+ else:
573
+ QMessageBox.critical(self, cfg.programName, "No modifier to add!")
574
+ self.leModifier.setFocus()
575
+
576
+ def tabWidgetModifiersSets_changed(self, tabIndex):
577
+ """
578
+ user changed the tab widget
579
+ """
580
+
581
+ # check if modifier field empty
582
+ if self.leModifier.text() and tabIndex != self.tabMem:
583
+ if (
584
+ dialog.MessageDialog(
585
+ cfg.programName,
586
+ (
587
+ "You are working on a behavior.<br>"
588
+ "If you change the modifier's set it will be lost.<br>"
589
+ "Do you want to change modifiers set"
590
+ ),
591
+ [cfg.YES, cfg.NO],
592
+ )
593
+ == cfg.NO
594
+ ):
595
+ self.tabWidgetModifiersSets.setCurrentIndex(self.tabMem)
596
+ return
597
+
598
+ if tabIndex != self.tabMem:
599
+ self.lwModifiers.clear()
600
+ self.leCode.clear()
601
+ self.leModifier.clear()
602
+ # if self.ask_at_stop_enabled:
603
+ # self.cb_ask_at_stop.setChecked(False)
604
+
605
+ self.tabMem = tabIndex
606
+
607
+ if tabIndex != -1:
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", ""))
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
+
614
+ self.lwModifiers.addItems(self.modifiers_sets_dict[str(tabIndex)]["values"])
615
+
616
+ def get_modifiers(self) -> str:
617
+ """
618
+ returns modifiers as string
619
+ """
620
+ keys_to_delete: list = []
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
626
+ if (
627
+ self.modifiers_sets_dict[idx]["type"] in (cfg.SINGLE_SELECTION, cfg.MULTI_SELECTION)
628
+ and not self.modifiers_sets_dict[idx]["values"]
629
+ ):
630
+ keys_to_delete.append(idx)
631
+
632
+ for idx in keys_to_delete:
633
+ del self.modifiers_sets_dict[idx]
634
+
635
+ return json.dumps(self.modifiers_sets_dict) if self.modifiers_sets_dict else ""