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.
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
@@ -0,0 +1,312 @@
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/licenses/>.
20
+
21
+ """
22
+
23
+ import re
24
+
25
+ from PySide6.QtCore import Qt, QEvent
26
+ from PySide6.QtWidgets import (
27
+ QDialog,
28
+ QVBoxLayout,
29
+ QLabel,
30
+ QListWidget,
31
+ QHBoxLayout,
32
+ QLineEdit,
33
+ QPushButton,
34
+ QListWidgetItem,
35
+ QSizePolicy,
36
+ QSpacerItem,
37
+ QAbstractItemView,
38
+ QMessageBox,
39
+ )
40
+
41
+ from . import config as cfg
42
+ from . import utilities as util
43
+
44
+
45
+ class ModifiersList(QDialog):
46
+ """
47
+ class for selection the modifier(s)
48
+ """
49
+
50
+ def __init__(self, code: str, modifiers_dict: dict, currentModifier: str):
51
+ super().__init__()
52
+ self.setWindowTitle(cfg.programName)
53
+ self.setWindowFlags(Qt.WindowStaysOnTopHint)
54
+
55
+ self.modifiers_dict = dict(modifiers_dict)
56
+ currentModifierList = currentModifier.split("|")
57
+
58
+ V1layout = QVBoxLayout()
59
+ label = QLabel()
60
+ label.setText(f"Choose the modifier{'s' * (len(self.modifiers_dict) > 1)} for <b>{code}</b> behavior")
61
+ V1layout.addWidget(label)
62
+
63
+ Hlayout = QHBoxLayout()
64
+ self.modifiersSetNumber = 0
65
+
66
+ for idx in util.sorted_keys(modifiers_dict):
67
+ if self.modifiers_dict[idx]["type"] not in (
68
+ cfg.SINGLE_SELECTION,
69
+ cfg.MULTI_SELECTION,
70
+ cfg.NUMERIC_MODIFIER,
71
+ ):
72
+ continue
73
+
74
+ V2layout = QVBoxLayout()
75
+
76
+ self.modifiersSetNumber += 1
77
+
78
+ if self.modifiers_dict[idx].get("name", ""):
79
+ lb1 = QLabel(f"Modifier <b>{self.modifiers_dict[idx].get('name', '')}</b>")
80
+ V2layout.addWidget(lb1)
81
+
82
+ if self.modifiers_dict[idx].get("description", ""):
83
+ lb2 = QLabel(f"<small>{self.modifiers_dict[idx].get('description', '')[:50]}</small>")
84
+ V2layout.addWidget(lb2)
85
+
86
+ if self.modifiers_dict[idx]["type"] in (
87
+ cfg.SINGLE_SELECTION,
88
+ cfg.MULTI_SELECTION,
89
+ ):
90
+ lw = QListWidget()
91
+ self.modifiers_dict[idx]["widget"] = lw
92
+ lw.setObjectName(f"lw_modifiers_({self.modifiers_dict[idx]['type']})")
93
+ lw.installEventFilter(self)
94
+
95
+ if self.modifiers_dict[idx]["type"] == cfg.SINGLE_SELECTION:
96
+ item = QListWidgetItem("None")
97
+ lw.addItem(item)
98
+ item.setSelected(True)
99
+
100
+ for modifier in self.modifiers_dict[idx]["values"]:
101
+ item = QListWidgetItem(modifier)
102
+ if self.modifiers_dict[idx]["type"] == cfg.MULTI_SELECTION:
103
+ item.setCheckState(Qt.Unchecked)
104
+
105
+ # previously selected
106
+ try:
107
+ if currentModifierList != [""] and re.sub(r" \(.\)", "", modifier) in currentModifierList[int(idx)].split(","):
108
+ item.setCheckState(Qt.Checked)
109
+ except Exception: # for old projects due to a fixed bug
110
+ pass
111
+
112
+ lw.addItem(item)
113
+
114
+ if self.modifiers_dict[idx]["type"] == cfg.SINGLE_SELECTION:
115
+ try:
116
+ if currentModifierList != [""] and re.sub(r" \(.\)", "", modifier) == currentModifierList[int(idx)]:
117
+ item.setSelected(True)
118
+ except Exception: # for old projects due to a fixed bug
119
+ pass
120
+ V2layout.addWidget(lw)
121
+
122
+ if self.modifiers_dict[idx]["type"] in [cfg.NUMERIC_MODIFIER]:
123
+ le = QLineEdit()
124
+ self.modifiers_dict[idx]["widget"] = le
125
+ le.setObjectName(f"le_modifiers_({self.modifiers_dict[idx]['type']})")
126
+
127
+ if currentModifierList != [""] and currentModifierList[int(idx)] != "None":
128
+ le.setText(currentModifierList[int(idx)])
129
+
130
+ V2layout.addWidget(le)
131
+
132
+ # vertical spacer
133
+ spacerItem = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
134
+ V2layout.addItem(spacerItem)
135
+
136
+ Hlayout.addLayout(V2layout)
137
+
138
+ V1layout.addLayout(Hlayout)
139
+
140
+ H2layout = QHBoxLayout()
141
+ H2layout.addStretch(1)
142
+
143
+ pbCancel = QPushButton(cfg.CANCEL)
144
+ pbCancel.clicked.connect(self.reject)
145
+ H2layout.addWidget(pbCancel)
146
+
147
+ pbOK = QPushButton(cfg.OK)
148
+ pbOK.setDefault(True)
149
+ pbOK.clicked.connect(self.pbOK_clicked)
150
+ H2layout.addWidget(pbOK)
151
+
152
+ V1layout.addLayout(H2layout)
153
+ self.setLayout(V1layout)
154
+
155
+ self.installEventFilter(self)
156
+ self.setMaximumSize(1024, 960)
157
+
158
+ def eventFilter(self, receiver, event):
159
+ """
160
+ send event (if keypress) to main window
161
+ """
162
+ if event.type() == QEvent.KeyPress:
163
+ ek, ek_text = event.key(), event.text()
164
+
165
+ # reject and close dialog if escape pressed
166
+ if ek == Qt.Key_Escape: # close
167
+ self.reject()
168
+ return False
169
+
170
+ # accept and close dialog if enter pressed
171
+ if ek == Qt.Key_Enter or ek == Qt.Key_Return: # enter or enter from numeric pad
172
+ if not self.pbOK_clicked():
173
+ return False
174
+ return True
175
+
176
+ modifiersSetIndex = 0
177
+ for widget in self.children():
178
+ if "_modifiers" in widget.objectName():
179
+ modifiersSetIndex = modifiersSetIndex + 1
180
+ if "lw_modifiers" in widget.objectName():
181
+ if self.modifiersSetNumber == 1:
182
+ # check if modifiers have code
183
+ for index in range(widget.count()):
184
+ if "(" in widget.item(index).text():
185
+ break
186
+ else:
187
+ # modifiers have no associated code: the modifier starting with hit key will be selected
188
+ if ek not in (Qt.Key_Down, Qt.Key_Up):
189
+ if ek == Qt.Key_Space and f"({cfg.MULTI_SELECTION})" in widget.objectName(): # checking using SPACE bar
190
+ if widget.item(widget.currentRow()).checkState() == Qt.Checked:
191
+ widget.item(widget.currentRow()).setCheckState(Qt.Unchecked)
192
+ else:
193
+ widget.item(widget.currentRow()).setCheckState(Qt.Checked)
194
+
195
+ else:
196
+ for index in range(widget.count()):
197
+ if widget.item(index).text().upper().startswith(ek_text.upper()):
198
+ widget.setCurrentRow(index)
199
+ widget.scrollToItem(
200
+ widget.item(index),
201
+ QAbstractItemView.EnsureVisible,
202
+ )
203
+ return True
204
+ else: # up / down keys
205
+ try:
206
+ if ek == Qt.Key_Down and widget.currentRow() < widget.count() - 1:
207
+ widget.setCurrentRow(widget.currentRow() + 1)
208
+ if ek == Qt.Key_Up and widget.currentRow() > 0:
209
+ widget.setCurrentRow(widget.currentRow() - 1)
210
+ except Exception:
211
+ return
212
+
213
+ for index in range(widget.count()):
214
+ # check function kesy (F1, F2...)
215
+ if ek in cfg.function_keys:
216
+ if f"({cfg.function_keys[ek]})" in widget.item(index).text().upper():
217
+ if f"({cfg.SINGLE_SELECTION})" in widget.objectName():
218
+ widget.item(index).setSelected(True)
219
+ # close dialog if one set of modifiers
220
+ if self.modifiersSetNumber == 1:
221
+ self.accept()
222
+ return True
223
+ # else move to next set of mofifiers
224
+ elif modifiersSetIndex != self.modifiersSetNumber:
225
+ widget.parent().focusNextChild()
226
+ return True
227
+
228
+ if f"({cfg.MULTI_SELECTION})" in widget.objectName():
229
+ if widget.item(index).checkState() == Qt.Checked:
230
+ widget.item(index).setCheckState(Qt.Unchecked)
231
+ else:
232
+ widget.item(index).setCheckState(Qt.Checked)
233
+
234
+ if ek < 1114112 and f"({ek_text})" in widget.item(index).text():
235
+ if f"({cfg.SINGLE_SELECTION})" in widget.objectName():
236
+ widget.item(index).setSelected(True)
237
+ # close dialog if one set of modifiers
238
+ if self.modifiersSetNumber == 1:
239
+ self.accept()
240
+ return True
241
+ # else move to next set of mofifiers
242
+ elif modifiersSetIndex != self.modifiersSetNumber:
243
+ widget.parent().focusNextChild()
244
+ return True
245
+
246
+ if f"({cfg.MULTI_SELECTION})" in widget.objectName():
247
+ if widget.item(index).checkState() == Qt.Checked:
248
+ widget.item(index).setCheckState(Qt.Unchecked)
249
+ else:
250
+ widget.item(index).setCheckState(Qt.Checked)
251
+
252
+ return True
253
+ else:
254
+ return False
255
+
256
+ def get_modifiers(self):
257
+ """
258
+ get modifiers
259
+ returns list of selected modifiers
260
+ """
261
+
262
+ for idx in util.sorted_keys(self.modifiers_dict):
263
+ if self.modifiers_dict[idx]["type"] in [
264
+ cfg.SINGLE_SELECTION,
265
+ cfg.MULTI_SELECTION,
266
+ cfg.NUMERIC_MODIFIER,
267
+ ]:
268
+ self.modifiers_dict[idx]["selected"] = []
269
+
270
+ if self.modifiers_dict[idx]["type"] == cfg.MULTI_SELECTION:
271
+ for j in range(self.modifiers_dict[idx]["widget"].count()):
272
+ if self.modifiers_dict[idx]["widget"].item(j).checkState() == Qt.Checked:
273
+ self.modifiers_dict[idx]["selected"].append(
274
+ re.sub(
275
+ r" \(.*\)",
276
+ "",
277
+ self.modifiers_dict[idx]["widget"].item(j).text(),
278
+ )
279
+ )
280
+
281
+ if not self.modifiers_dict[idx]["selected"]:
282
+ self.modifiers_dict[idx]["selected"].append("None")
283
+
284
+ if self.modifiers_dict[idx]["type"] == cfg.SINGLE_SELECTION:
285
+ for item in self.modifiers_dict[idx]["widget"].selectedItems():
286
+ self.modifiers_dict[idx]["selected"].append(re.sub(r" \(.*\)", "", item.text()))
287
+
288
+ if self.modifiers_dict[idx]["type"] == cfg.NUMERIC_MODIFIER:
289
+ self.modifiers_dict[idx]["selected"] = (
290
+ self.modifiers_dict[idx]["widget"].text() if self.modifiers_dict[idx]["widget"].text() else "None"
291
+ )
292
+
293
+ return self.modifiers_dict
294
+
295
+ def pbOK_clicked(self):
296
+ """
297
+ OK button is clicked
298
+ """
299
+ for idx in util.sorted_keys(self.modifiers_dict):
300
+ if self.modifiers_dict[idx]["type"] == cfg.NUMERIC_MODIFIER:
301
+ if self.modifiers_dict[idx]["widget"].text():
302
+ try:
303
+ _ = float(self.modifiers_dict[idx]["widget"].text())
304
+ except Exception:
305
+ QMessageBox.warning(
306
+ self,
307
+ cfg.programName,
308
+ f"<b>{self.modifiers_dict[idx]['widget'].text()}</b> is not a numeric value",
309
+ )
310
+ return False
311
+
312
+ self.accept()
@@ -0,0 +1,210 @@
1
+ """
2
+ BORIS
3
+ Behavioral Observation Research Interactive Software
4
+ Copyright 2012-2025 Olivier Friard
5
+
6
+
7
+ This program is free software; you can redistribute it and/or modify
8
+ it under the terms of the GNU General Public License as published by
9
+ the Free Software Foundation; either version 2 of the License, or
10
+ (at your option) any later version.
11
+
12
+ This program is distributed in the hope that it will be useful,
13
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ GNU General Public License for more details.
16
+
17
+ You should have received a copy of the GNU General Public License
18
+ along with this program; if not, write to the Free Software
19
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
20
+ MA 02110-1301, USA.
21
+
22
+ """
23
+
24
+ import logging
25
+ from typing import Tuple
26
+
27
+ from PySide6.QtCore import Qt
28
+ from PySide6.QtWidgets import QAbstractItemView
29
+
30
+ from . import config as cfg
31
+ from . import gui_utilities, observations_list, project_functions
32
+ from . import utilities as util
33
+
34
+
35
+ def select_observations2(self, mode: str, windows_title: str = "") -> Tuple[str, list]:
36
+ """
37
+ allow user to select observations
38
+ mode: accepted values: OPEN, EDIT, SINGLE, MULTIPLE, SELECT1
39
+
40
+ Args:
41
+ pj (dict): BORIS project dictionary
42
+ mode (str): mode for selection: OPEN, EDIT, SINGLE, MULTIPLE, SELECT1
43
+ windows_title (str): title for windows
44
+
45
+ Returns:
46
+ str: selected mode: OPEN, EDIT, VIEW
47
+ list: list of selected observations
48
+ """
49
+
50
+ pj = self.pj
51
+
52
+ fields_list = ["id", "date", "description", "subjects", "observation duration", "exhaustivity %", "media"]
53
+ indep_var_header, column_type = [], [cfg.TEXT, cfg.TEXT, cfg.TEXT, cfg.TEXT, cfg.NUMERIC, cfg.NUMERIC, cfg.TEXT]
54
+
55
+ if cfg.INDEPENDENT_VARIABLES in pj:
56
+ for idx in util.sorted_keys(pj[cfg.INDEPENDENT_VARIABLES]):
57
+ indep_var_header.append(pj[cfg.INDEPENDENT_VARIABLES][idx]["label"])
58
+ column_type.append(pj[cfg.INDEPENDENT_VARIABLES][idx]["type"])
59
+
60
+ state_events_list = util.state_behavior_codes(pj[cfg.ETHOGRAM])
61
+
62
+ data: list = []
63
+ not_paired: list = []
64
+
65
+ # check if observations changed
66
+ if hash(str(self.pj[cfg.OBSERVATIONS])) != self.mem_hash_obs:
67
+ logging.debug("observations changed")
68
+
69
+ for obs in sorted(list(pj[cfg.OBSERVATIONS].keys())):
70
+ date = pj[cfg.OBSERVATIONS][obs]["date"].replace("T", " ")
71
+ descr = util.eol2space(pj[cfg.OBSERVATIONS][obs][cfg.DESCRIPTION])
72
+
73
+ # subjects
74
+ observed_subjects = [cfg.NO_FOCAL_SUBJECT if x == "" else x for x in project_functions.extract_observed_subjects(pj, [obs])]
75
+
76
+ subjectsList = ", ".join(observed_subjects)
77
+
78
+ # observed time interval
79
+ interval = project_functions.observed_interval(pj[cfg.OBSERVATIONS][obs])
80
+ observed_interval_str = str(round(interval[1] - interval[0], 3))
81
+
82
+ # media
83
+ media: str = ""
84
+ if pj[cfg.OBSERVATIONS][obs][cfg.TYPE] == cfg.MEDIA:
85
+ media_list: list = []
86
+ if pj[cfg.OBSERVATIONS][obs][cfg.FILE]:
87
+ for player in sorted(pj[cfg.OBSERVATIONS][obs][cfg.FILE].keys()):
88
+ for media in pj[cfg.OBSERVATIONS][obs][cfg.FILE][player]:
89
+ media_list.append(f"#{player}: {media}")
90
+
91
+ if len(media_list) > 8:
92
+ media = " ".join(media_list)
93
+ else:
94
+ media = "\n".join(media_list)
95
+
96
+ if pj[cfg.OBSERVATIONS][obs][cfg.TYPE] == cfg.LIVE:
97
+ media = cfg.LIVE
98
+
99
+ if pj[cfg.OBSERVATIONS][obs][cfg.TYPE] == cfg.IMAGES:
100
+ dir_list: list = []
101
+ for dir_path in pj[cfg.OBSERVATIONS][obs].get(cfg.DIRECTORIES_LIST, []):
102
+ dir_list.append(dir_path)
103
+ media = "; ".join(dir_list)
104
+
105
+ # independent variables
106
+ indepvar: list = []
107
+ if cfg.INDEPENDENT_VARIABLES in pj[cfg.OBSERVATIONS][obs]:
108
+ for var_label in indep_var_header:
109
+ if var_label in pj[cfg.OBSERVATIONS][obs][cfg.INDEPENDENT_VARIABLES]:
110
+ indepvar.append(pj[cfg.OBSERVATIONS][obs][cfg.INDEPENDENT_VARIABLES][var_label])
111
+ else:
112
+ indepvar.append("")
113
+
114
+ # check unpaired events
115
+ ok, _ = project_functions.check_state_events_obs(obs, pj[cfg.ETHOGRAM], pj[cfg.OBSERVATIONS][obs], cfg.HHMMSS)
116
+ if not ok:
117
+ not_paired.append(obs)
118
+
119
+ # exhaustivity
120
+ if pj[cfg.OBSERVATIONS][obs][cfg.TYPE] in (cfg.MEDIA, cfg.LIVE):
121
+ # check exhaustivity of observation
122
+ exhaustivity = project_functions.check_observation_exhaustivity(pj[cfg.OBSERVATIONS][obs][cfg.EVENTS], state_events_list)
123
+ elif pj[cfg.OBSERVATIONS][obs][cfg.TYPE] == cfg.IMAGES:
124
+ exhaustivity = project_functions.check_observation_exhaustivity_pictures(pj[cfg.OBSERVATIONS][obs])
125
+
126
+ data.append([obs, date, descr, subjectsList, observed_interval_str, str(exhaustivity), media] + indepvar)
127
+
128
+ obsList = observations_list.observationsList_widget(
129
+ data, header=fields_list + indep_var_header, column_type=column_type, not_paired=not_paired
130
+ )
131
+ self.data = data
132
+ self.not_paired = not_paired
133
+ self.mem_hash_obs = hash(str(self.pj[cfg.OBSERVATIONS]))
134
+
135
+ else:
136
+ obsList = observations_list.observationsList_widget(
137
+ self.data, header=fields_list + indep_var_header, column_type=column_type, not_paired=self.not_paired
138
+ )
139
+
140
+ if windows_title:
141
+ obsList.setWindowTitle(windows_title)
142
+
143
+ obsList.pbOpen.setVisible(False)
144
+ obsList.pbView.setVisible(False)
145
+ obsList.pbEdit.setVisible(False)
146
+ obsList.pbOk.setVisible(False)
147
+ obsList.pbSelectAll.setVisible(False)
148
+ obsList.pbUnSelectAll.setVisible(False)
149
+ obsList.mode = mode
150
+
151
+ if mode == cfg.OPEN:
152
+ obsList.view.setSelectionMode(QAbstractItemView.SingleSelection)
153
+ obsList.pbOpen.setVisible(True)
154
+
155
+ if mode == cfg.VIEW:
156
+ obsList.view.setSelectionMode(QAbstractItemView.SingleSelection)
157
+ obsList.pbView.setVisible(True)
158
+
159
+ if mode == cfg.EDIT:
160
+ obsList.view.setSelectionMode(QAbstractItemView.SingleSelection)
161
+ obsList.pbEdit.setVisible(True)
162
+
163
+ if mode == cfg.SINGLE:
164
+ obsList.view.setSelectionMode(QAbstractItemView.SingleSelection)
165
+ obsList.pbOpen.setVisible(True)
166
+ obsList.pbView.setVisible(True)
167
+ obsList.pbEdit.setVisible(True)
168
+
169
+ if mode == cfg.MULTIPLE:
170
+ obsList.view.setSelectionMode(QAbstractItemView.MultiSelection)
171
+ obsList.pbOk.setVisible(True)
172
+ obsList.pbSelectAll.setVisible(True)
173
+ obsList.pbUnSelectAll.setVisible(True)
174
+
175
+ if mode == cfg.SELECT1:
176
+ obsList.view.setSelectionMode(QAbstractItemView.SingleSelection)
177
+ obsList.pbOk.setVisible(True)
178
+
179
+ # restore window geometry
180
+ gui_utilities.restore_geometry(obsList, "observations list", (900, 600))
181
+
182
+ obsList.view.sortItems(0, Qt.AscendingOrder)
183
+ for row in range(obsList.view.rowCount()):
184
+ obsList.view.resizeRowToContents(row)
185
+
186
+ selected_observations = []
187
+
188
+ result = obsList.exec_()
189
+
190
+ # saving window geometry in ini file
191
+ gui_utilities.save_geometry(obsList, "observations list")
192
+
193
+ if result:
194
+ if obsList.view.selectedIndexes():
195
+ for idx in obsList.view.selectedIndexes():
196
+ if idx.column() == 0: # first column
197
+ selected_observations.append(idx.data())
198
+
199
+ if result == 0: # cancel
200
+ resultStr = ""
201
+ if result == 1: # select
202
+ resultStr = "ok"
203
+ if result == 2: # open
204
+ resultStr = cfg.OPEN
205
+ if result == 3: # edit
206
+ resultStr = cfg.EDIT
207
+ if result == 4: # view
208
+ resultStr = cfg.VIEW
209
+
210
+ return resultStr, selected_observations