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.
- boris/__init__.py +26 -0
- boris/__main__.py +25 -0
- boris/about.py +143 -0
- boris/add_modifier.py +635 -0
- boris/add_modifier_ui.py +303 -0
- boris/advanced_event_filtering.py +455 -0
- boris/analysis_plugins/__init__.py +0 -0
- boris/analysis_plugins/_latency.py +59 -0
- boris/analysis_plugins/irr_cohen_kappa.py +109 -0
- boris/analysis_plugins/irr_cohen_kappa_with_modifiers.py +112 -0
- boris/analysis_plugins/irr_weighted_cohen_kappa.py +157 -0
- boris/analysis_plugins/irr_weighted_cohen_kappa_with_modifiers.py +162 -0
- boris/analysis_plugins/list_of_dataframe_columns.py +22 -0
- boris/analysis_plugins/number_of_occurences.py +22 -0
- boris/analysis_plugins/number_of_occurences_by_independent_variable.py +54 -0
- boris/analysis_plugins/time_budget.py +61 -0
- boris/behav_coding_map_creator.py +1110 -0
- boris/behavior_binary_table.py +305 -0
- boris/behaviors_coding_map.py +239 -0
- boris/boris_cli.py +340 -0
- boris/cmd_arguments.py +49 -0
- boris/coding_pad.py +280 -0
- boris/config.py +785 -0
- boris/config_file.py +356 -0
- boris/connections.py +409 -0
- boris/converters.py +333 -0
- boris/converters_ui.py +225 -0
- boris/cooccurence.py +250 -0
- boris/core.py +5901 -0
- boris/core_qrc.py +15958 -0
- boris/core_ui.py +1107 -0
- boris/db_functions.py +324 -0
- boris/dev.py +134 -0
- boris/dialog.py +1108 -0
- boris/duration_widget.py +238 -0
- boris/edit_event.py +245 -0
- boris/edit_event_ui.py +233 -0
- boris/event_operations.py +1040 -0
- boris/events_cursor.py +61 -0
- boris/events_snapshots.py +596 -0
- boris/exclusion_matrix.py +141 -0
- boris/export_events.py +1006 -0
- boris/export_observation.py +1203 -0
- boris/external_processes.py +332 -0
- boris/geometric_measurement.py +941 -0
- boris/gui_utilities.py +135 -0
- boris/image_overlay.py +72 -0
- boris/import_observations.py +242 -0
- boris/ipc_mpv.py +325 -0
- boris/irr.py +634 -0
- boris/latency.py +244 -0
- boris/measurement_widget.py +161 -0
- boris/media_file.py +115 -0
- boris/menu_options.py +213 -0
- boris/modifier_coding_map_creator.py +1013 -0
- boris/modifiers_coding_map.py +157 -0
- boris/mpv.py +2016 -0
- boris/mpv2.py +2193 -0
- boris/observation.py +1453 -0
- boris/observation_operations.py +2538 -0
- boris/observation_ui.py +679 -0
- boris/observations_list.py +337 -0
- boris/otx_parser.py +442 -0
- boris/param_panel.py +201 -0
- boris/param_panel_ui.py +305 -0
- boris/player_dock_widget.py +198 -0
- boris/plot_data_module.py +536 -0
- boris/plot_events.py +634 -0
- boris/plot_events_rt.py +237 -0
- boris/plot_spectrogram_rt.py +316 -0
- boris/plot_waveform_rt.py +230 -0
- boris/plugins.py +431 -0
- boris/portion/__init__.py +31 -0
- boris/portion/const.py +95 -0
- boris/portion/dict.py +365 -0
- boris/portion/func.py +52 -0
- boris/portion/interval.py +581 -0
- boris/portion/io.py +181 -0
- boris/preferences.py +510 -0
- boris/preferences_ui.py +770 -0
- boris/project.py +2007 -0
- boris/project_functions.py +2041 -0
- boris/project_import_export.py +1096 -0
- boris/project_ui.py +794 -0
- boris/qrc_boris.py +10389 -0
- boris/qrc_boris5.py +2579 -0
- boris/select_modifiers.py +312 -0
- boris/select_observations.py +210 -0
- boris/select_subj_behav.py +286 -0
- boris/state_events.py +197 -0
- boris/subjects_pad.py +106 -0
- boris/synthetic_time_budget.py +290 -0
- boris/time_budget_functions.py +1136 -0
- boris/time_budget_widget.py +1039 -0
- boris/transitions.py +365 -0
- boris/utilities.py +1810 -0
- boris/version.py +24 -0
- boris/video_equalizer.py +159 -0
- boris/video_equalizer_ui.py +248 -0
- boris/video_operations.py +310 -0
- boris/view_df.py +104 -0
- boris/view_df_ui.py +75 -0
- boris/write_event.py +538 -0
- boris_behav_obs-9.7.7.dist-info/METADATA +139 -0
- boris_behav_obs-9.7.7.dist-info/RECORD +109 -0
- boris_behav_obs-9.7.7.dist-info/WHEEL +5 -0
- boris_behav_obs-9.7.7.dist-info/entry_points.txt +2 -0
- boris_behav_obs-9.7.7.dist-info/licenses/LICENSE.TXT +674 -0
- boris_behav_obs-9.7.7.dist-info/top_level.txt +1 -0
boris/dialog.py
ADDED
|
@@ -0,0 +1,1108 @@
|
|
|
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 datetime as dt
|
|
24
|
+
from decimal import Decimal as dec
|
|
25
|
+
import logging
|
|
26
|
+
import math
|
|
27
|
+
import pathlib as pl
|
|
28
|
+
import platform
|
|
29
|
+
import sys
|
|
30
|
+
import traceback
|
|
31
|
+
from typing import Union
|
|
32
|
+
|
|
33
|
+
from PySide6.QtCore import Qt, Signal, qVersion, QRect, QTime, QDateTime, QSize
|
|
34
|
+
from PySide6.QtWidgets import (
|
|
35
|
+
QApplication,
|
|
36
|
+
QAbstractItemView,
|
|
37
|
+
QCheckBox,
|
|
38
|
+
QComboBox,
|
|
39
|
+
QDialog,
|
|
40
|
+
QFileDialog,
|
|
41
|
+
QHBoxLayout,
|
|
42
|
+
QLabel,
|
|
43
|
+
QLineEdit,
|
|
44
|
+
QListWidget,
|
|
45
|
+
QListWidgetItem,
|
|
46
|
+
QMessageBox,
|
|
47
|
+
QPlainTextEdit,
|
|
48
|
+
QPushButton,
|
|
49
|
+
QSizePolicy,
|
|
50
|
+
QSpacerItem,
|
|
51
|
+
QSpinBox,
|
|
52
|
+
QDoubleSpinBox,
|
|
53
|
+
QTableView,
|
|
54
|
+
QTableWidget,
|
|
55
|
+
QVBoxLayout,
|
|
56
|
+
QWidget,
|
|
57
|
+
QDateTimeEdit,
|
|
58
|
+
QTimeEdit,
|
|
59
|
+
QAbstractSpinBox,
|
|
60
|
+
QRadioButton,
|
|
61
|
+
QStackedWidget,
|
|
62
|
+
QFrame,
|
|
63
|
+
)
|
|
64
|
+
from PySide6.QtGui import QFont, QTextCursor
|
|
65
|
+
|
|
66
|
+
from . import config as cfg
|
|
67
|
+
from . import version
|
|
68
|
+
from . import utilities as util
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def MessageDialog(title: str, text: str, buttons: tuple) -> str:
|
|
72
|
+
"""
|
|
73
|
+
show a generic message dialog and returns the text of the clicked button
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
title (str): Title of the dialog box
|
|
77
|
+
text (str): text of the dialog box
|
|
78
|
+
buttons (tuple): text for buttons
|
|
79
|
+
|
|
80
|
+
Return
|
|
81
|
+
str: text of the clicked button
|
|
82
|
+
"""
|
|
83
|
+
message = QMessageBox()
|
|
84
|
+
message.setWindowTitle(title)
|
|
85
|
+
message.setText(text)
|
|
86
|
+
message.setIcon(QMessageBox.Question)
|
|
87
|
+
for button in buttons:
|
|
88
|
+
message.addButton(button, QMessageBox.YesRole)
|
|
89
|
+
|
|
90
|
+
message.setWindowFlags(message.windowFlags() | Qt.WindowStaysOnTopHint)
|
|
91
|
+
message.exec()
|
|
92
|
+
return message.clickedButton().text()
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def global_error_message(exception_type, exception_value, traceback_object):
|
|
96
|
+
"""
|
|
97
|
+
Global error management
|
|
98
|
+
save error using loggin.critical and stdout
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
error_text = "\n\nSystem info\n===========\n\n"
|
|
102
|
+
error_text += util.get_systeminfo()
|
|
103
|
+
error_text += f"Error succeded at {dt.datetime.now():%Y-%m-%d %H:%M}\n\n"
|
|
104
|
+
error_text += "".join(traceback.format_exception(exception_type, exception_value, traceback_object))
|
|
105
|
+
|
|
106
|
+
# write to stdout
|
|
107
|
+
logging.critical(error_text)
|
|
108
|
+
|
|
109
|
+
# write to $HOME/boris_error.log
|
|
110
|
+
try:
|
|
111
|
+
with open(pl.Path.home() / "boris_error.log", "w") as f_error:
|
|
112
|
+
f_error.write(error_text)
|
|
113
|
+
except Exception:
|
|
114
|
+
logging.critical(f"Impossible to write to {pl.Path.home() / 'boris_error.log'}")
|
|
115
|
+
|
|
116
|
+
# copy to clipboard
|
|
117
|
+
cb = QApplication.clipboard()
|
|
118
|
+
cb.clear()
|
|
119
|
+
cb.setText(error_text)
|
|
120
|
+
|
|
121
|
+
text: str = (
|
|
122
|
+
f"An error has occured!\n\n"
|
|
123
|
+
"to improve the software please report this problem at:\n"
|
|
124
|
+
"https://github.com/olivierfriard/BORIS/issues\n\n"
|
|
125
|
+
"Please no screenshot, the error message was copied to the clipboard.\n\n"
|
|
126
|
+
"Thank you for your collaboration!\n\n"
|
|
127
|
+
f"{error_text}"
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
errorbox = Results_dialog()
|
|
131
|
+
|
|
132
|
+
errorbox.setWindowTitle("BORIS - An error occured")
|
|
133
|
+
errorbox.pbOK.setText("Abort")
|
|
134
|
+
errorbox.pbCancel.setVisible(True)
|
|
135
|
+
errorbox.pbCancel.setText("Ignore and try to continue")
|
|
136
|
+
|
|
137
|
+
font = QFont()
|
|
138
|
+
font.setFamily("monospace")
|
|
139
|
+
errorbox.ptText.setFont(font)
|
|
140
|
+
errorbox.ptText.clear()
|
|
141
|
+
errorbox.ptText.appendPlainText(text)
|
|
142
|
+
|
|
143
|
+
errorbox.ptText.moveCursor(QTextCursor.Start)
|
|
144
|
+
|
|
145
|
+
ret = errorbox.exec_()
|
|
146
|
+
|
|
147
|
+
if ret == 1: # Abort
|
|
148
|
+
sys.exit(1)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class Info_widget(QWidget):
|
|
152
|
+
def __init__(self, parent=None):
|
|
153
|
+
super().__init__(parent)
|
|
154
|
+
|
|
155
|
+
self.setWindowTitle("BORIS")
|
|
156
|
+
layout = QVBoxLayout()
|
|
157
|
+
self.label = QLabel()
|
|
158
|
+
layout.addWidget(self.label)
|
|
159
|
+
self.lwi = QListWidget()
|
|
160
|
+
layout.addWidget(self.lwi)
|
|
161
|
+
self.setLayout(layout)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class get_time_widget(QWidget):
|
|
165
|
+
"""
|
|
166
|
+
widget for selecting a time in various formats: seconds, HH:MM:SS:ZZZ or YYYY-mm-DD HH:MM:SS:ZZZ
|
|
167
|
+
"""
|
|
168
|
+
|
|
169
|
+
def __init__(self, time_value=dec(0), parent=None):
|
|
170
|
+
super().__init__(parent)
|
|
171
|
+
|
|
172
|
+
self.setWindowTitle("BORIS")
|
|
173
|
+
|
|
174
|
+
self.widget = QWidget()
|
|
175
|
+
self.widget.setObjectName("widget")
|
|
176
|
+
self.widget.setGeometry(QRect(130, 220, 302, 63))
|
|
177
|
+
self.verticalLayout_3 = QVBoxLayout(self.widget)
|
|
178
|
+
self.verticalLayout_3.setObjectName("verticalLayout_3")
|
|
179
|
+
self.verticalLayout_3.setContentsMargins(0, 0, 0, 0)
|
|
180
|
+
self.horizontalLayout_3 = QHBoxLayout()
|
|
181
|
+
self.horizontalLayout_3.setObjectName("horizontalLayout_3")
|
|
182
|
+
self.verticalLayout_2 = QVBoxLayout()
|
|
183
|
+
self.verticalLayout_2.setObjectName("verticalLayout_2")
|
|
184
|
+
self.label = QLabel(self.widget)
|
|
185
|
+
self.label.setObjectName("label")
|
|
186
|
+
|
|
187
|
+
self.verticalLayout_2.addWidget(self.label)
|
|
188
|
+
|
|
189
|
+
self.pb_sign = QPushButton(self.widget)
|
|
190
|
+
self.pb_sign.setObjectName("pb_sign")
|
|
191
|
+
self.pb_sign.setMaximumSize(QSize(40, 16777215))
|
|
192
|
+
|
|
193
|
+
self.verticalLayout_2.addWidget(self.pb_sign)
|
|
194
|
+
|
|
195
|
+
self.horizontalLayout_3.addLayout(self.verticalLayout_2)
|
|
196
|
+
|
|
197
|
+
self.verticalLayout = QVBoxLayout()
|
|
198
|
+
self.verticalLayout.setObjectName("verticalLayout")
|
|
199
|
+
self.horizontalLayout = QHBoxLayout()
|
|
200
|
+
self.horizontalLayout.setObjectName("horizontalLayout")
|
|
201
|
+
self.rb_seconds = QRadioButton(self.widget)
|
|
202
|
+
self.rb_seconds.setObjectName("rb_seconds")
|
|
203
|
+
|
|
204
|
+
self.horizontalLayout.addWidget(self.rb_seconds)
|
|
205
|
+
|
|
206
|
+
self.rb_time = QRadioButton(self.widget)
|
|
207
|
+
self.rb_time.setObjectName("rb_time")
|
|
208
|
+
|
|
209
|
+
self.horizontalLayout.addWidget(self.rb_time)
|
|
210
|
+
|
|
211
|
+
self.rb_datetime = QRadioButton(self.widget)
|
|
212
|
+
self.rb_datetime.setObjectName("rb_datetime")
|
|
213
|
+
|
|
214
|
+
self.horizontalLayout.addWidget(self.rb_datetime)
|
|
215
|
+
|
|
216
|
+
self.horizontalLayout.addStretch()
|
|
217
|
+
|
|
218
|
+
self.verticalLayout.addLayout(self.horizontalLayout)
|
|
219
|
+
|
|
220
|
+
self.stackedWidget = QStackedWidget(self.widget)
|
|
221
|
+
self.stackedWidget.setObjectName("stackedWidget")
|
|
222
|
+
self.stackedWidget.setFrameShape(QFrame.NoFrame)
|
|
223
|
+
self.seconds = QWidget()
|
|
224
|
+
self.seconds.setObjectName("seconds")
|
|
225
|
+
self.widget1 = QWidget(self.seconds)
|
|
226
|
+
self.widget1.setObjectName("widget1")
|
|
227
|
+
# self.widget1.setGeometry(QRect(10, 0, 163, 27))
|
|
228
|
+
self.horizontalLayout_4 = QHBoxLayout(self.widget1)
|
|
229
|
+
self.horizontalLayout_4.setObjectName("horizontalLayout_4")
|
|
230
|
+
self.horizontalLayout_4.setContentsMargins(0, 0, 0, 0)
|
|
231
|
+
self.le_seconds = QLineEdit(self.widget1)
|
|
232
|
+
self.le_seconds.setObjectName("le_seconds")
|
|
233
|
+
|
|
234
|
+
self.horizontalLayout_4.addWidget(self.le_seconds)
|
|
235
|
+
|
|
236
|
+
self.lb_seconds = QLabel(self.widget1)
|
|
237
|
+
self.lb_seconds.setObjectName("lb_seconds")
|
|
238
|
+
|
|
239
|
+
self.horizontalLayout_4.addWidget(self.lb_seconds)
|
|
240
|
+
|
|
241
|
+
self.horizontalLayout_4.addStretch()
|
|
242
|
+
|
|
243
|
+
self.stackedWidget.addWidget(self.seconds)
|
|
244
|
+
self.hhmmss = QWidget()
|
|
245
|
+
self.hhmmss.setObjectName("hhmmss")
|
|
246
|
+
self.widget2 = QWidget(self.hhmmss)
|
|
247
|
+
self.widget2.setMinimumWidth(500)
|
|
248
|
+
self.widget2.setObjectName("widget2")
|
|
249
|
+
self.widget2.setGeometry(QRect(0, 0, 213, 28))
|
|
250
|
+
self.horizontalLayout_2 = QHBoxLayout(self.widget2)
|
|
251
|
+
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
|
|
252
|
+
self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0)
|
|
253
|
+
self.lb_hour = QLabel(self.widget2)
|
|
254
|
+
self.lb_hour.setObjectName("lb_hour")
|
|
255
|
+
|
|
256
|
+
self.horizontalLayout_2.addWidget(self.lb_hour)
|
|
257
|
+
|
|
258
|
+
self.sb_hour = QSpinBox(self.widget2)
|
|
259
|
+
self.sb_hour.setObjectName("sb_hour")
|
|
260
|
+
self.sb_hour.setButtonSymbols(QAbstractSpinBox.NoButtons)
|
|
261
|
+
|
|
262
|
+
self.horizontalLayout_2.addWidget(self.sb_hour)
|
|
263
|
+
|
|
264
|
+
self.lb_hhmmss = QLabel(self.widget2)
|
|
265
|
+
self.lb_hhmmss.setObjectName("lb_hhmmss")
|
|
266
|
+
|
|
267
|
+
self.horizontalLayout_2.addWidget(self.lb_hhmmss)
|
|
268
|
+
|
|
269
|
+
self.te_time = QTimeEdit(self.widget2)
|
|
270
|
+
self.te_time.setObjectName("te_time")
|
|
271
|
+
self.te_time.adjustSize()
|
|
272
|
+
# self.te_time.setMinimumWidth(200)
|
|
273
|
+
# self.widget2.adjustSize()
|
|
274
|
+
|
|
275
|
+
self.horizontalLayout_2.addWidget(self.te_time)
|
|
276
|
+
|
|
277
|
+
self.horizontalLayout_2.addStretch()
|
|
278
|
+
|
|
279
|
+
self.stackedWidget.addWidget(self.hhmmss)
|
|
280
|
+
self.page_2 = QWidget()
|
|
281
|
+
self.page_2.setObjectName("page_2")
|
|
282
|
+
self.dte = QDateTimeEdit(self.page_2)
|
|
283
|
+
|
|
284
|
+
self.dte.setObjectName("dte")
|
|
285
|
+
# self.dte.setGeometry(QRect(10, 0, 164, 26))
|
|
286
|
+
self.stackedWidget.addWidget(self.page_2)
|
|
287
|
+
|
|
288
|
+
self.verticalLayout.addWidget(self.stackedWidget)
|
|
289
|
+
|
|
290
|
+
self.horizontalLayout_3.addLayout(self.verticalLayout)
|
|
291
|
+
|
|
292
|
+
self.verticalLayout_3.addLayout(self.horizontalLayout_3)
|
|
293
|
+
|
|
294
|
+
self.line = QFrame(self.widget)
|
|
295
|
+
self.line.setObjectName("line")
|
|
296
|
+
self.line.setFrameShape(QFrame.HLine)
|
|
297
|
+
self.line.setFrameShadow(QFrame.Sunken)
|
|
298
|
+
|
|
299
|
+
self.verticalLayout_3.addWidget(self.line)
|
|
300
|
+
|
|
301
|
+
self.setLayout(self.verticalLayout_3)
|
|
302
|
+
|
|
303
|
+
self.stackedWidget.setCurrentIndex(0)
|
|
304
|
+
|
|
305
|
+
self.label.setText("")
|
|
306
|
+
self.pb_sign.setText("+")
|
|
307
|
+
self.rb_seconds.setText("Seconds")
|
|
308
|
+
self.rb_time.setText("hh:mm:ss")
|
|
309
|
+
self.rb_datetime.setText("Date time")
|
|
310
|
+
self.le_seconds.setText("")
|
|
311
|
+
self.lb_seconds.setText("seconds")
|
|
312
|
+
self.lb_hour.setText("hour")
|
|
313
|
+
self.lb_hhmmss.setText("mm:ss.ms")
|
|
314
|
+
self.te_time.setDisplayFormat("mm:ss.zzz")
|
|
315
|
+
self.dte.setDisplayFormat("yyyy-MM-dd hh:mm:ss:zzz")
|
|
316
|
+
self.dte.adjustSize()
|
|
317
|
+
font = QFont()
|
|
318
|
+
font.setPointSize(14)
|
|
319
|
+
self.pb_sign.setFont(font)
|
|
320
|
+
|
|
321
|
+
self.rb_seconds.toggled.connect(self.format_changed)
|
|
322
|
+
self.rb_time.toggled.connect(self.format_changed)
|
|
323
|
+
self.rb_datetime.toggled.connect(self.format_changed)
|
|
324
|
+
self.sb_hour.setMaximum(cfg.HOUR_CUTOFF)
|
|
325
|
+
self.pb_sign.clicked.connect(self.pb_sign_clicked)
|
|
326
|
+
|
|
327
|
+
if time_value:
|
|
328
|
+
self.set_time(time_value)
|
|
329
|
+
|
|
330
|
+
self.format_changed()
|
|
331
|
+
|
|
332
|
+
self.adjustSize()
|
|
333
|
+
|
|
334
|
+
def format_changed(self):
|
|
335
|
+
if self.rb_seconds.isChecked():
|
|
336
|
+
self.stackedWidget.setCurrentIndex(0)
|
|
337
|
+
if self.rb_time.isChecked():
|
|
338
|
+
self.stackedWidget.setCurrentIndex(1)
|
|
339
|
+
if self.rb_datetime.isChecked():
|
|
340
|
+
self.stackedWidget.setCurrentIndex(2)
|
|
341
|
+
|
|
342
|
+
self.le_seconds.setEnabled(self.rb_seconds.isChecked())
|
|
343
|
+
self.le_seconds.adjustSize()
|
|
344
|
+
self.lb_seconds.setEnabled(self.rb_seconds.isChecked())
|
|
345
|
+
self.sb_hour.setEnabled(self.rb_time.isChecked())
|
|
346
|
+
self.te_time.setEnabled(self.rb_time.isChecked())
|
|
347
|
+
self.lb_hour.setEnabled(self.rb_time.isChecked())
|
|
348
|
+
self.lb_hhmmss.setEnabled(self.rb_time.isChecked())
|
|
349
|
+
self.dte.setEnabled(self.rb_datetime.isChecked())
|
|
350
|
+
|
|
351
|
+
def pb_sign_clicked(self):
|
|
352
|
+
if self.pb_sign.text() == "+":
|
|
353
|
+
self.pb_sign.setText("-")
|
|
354
|
+
else:
|
|
355
|
+
self.pb_sign.setText("+")
|
|
356
|
+
|
|
357
|
+
def get_time(self) -> Union[dec, None]:
|
|
358
|
+
"""
|
|
359
|
+
Get time from the selected format in the time widget
|
|
360
|
+
|
|
361
|
+
Returns:
|
|
362
|
+
dec: time in seconds (None if no format selected)
|
|
363
|
+
"""
|
|
364
|
+
|
|
365
|
+
time_sec = dec("NaN")
|
|
366
|
+
|
|
367
|
+
if self.rb_seconds.isChecked():
|
|
368
|
+
try:
|
|
369
|
+
time_sec = float(self.le_seconds.text())
|
|
370
|
+
except Exception:
|
|
371
|
+
QMessageBox.warning(
|
|
372
|
+
None,
|
|
373
|
+
cfg.programName,
|
|
374
|
+
f"The value of seconds ({self.le_seconds.text()}) is not a decimal number",
|
|
375
|
+
QMessageBox.Ok | QMessageBox.Default,
|
|
376
|
+
QMessageBox.NoButton,
|
|
377
|
+
)
|
|
378
|
+
return dec("NaN")
|
|
379
|
+
|
|
380
|
+
if self.rb_time.isChecked():
|
|
381
|
+
time_sec = self.sb_hour.value() * 3600
|
|
382
|
+
time_sec += self.te_time.time().msecsSinceStartOfDay() / 1000
|
|
383
|
+
|
|
384
|
+
if self.rb_datetime.isChecked():
|
|
385
|
+
time_sec = self.dte.dateTime().toMSecsSinceEpoch() / 1000
|
|
386
|
+
|
|
387
|
+
if self.pb_sign.text() == "-":
|
|
388
|
+
time_sec = -time_sec
|
|
389
|
+
|
|
390
|
+
return dec(time_sec).quantize(dec("0.001")) # if time_sec is not None else None
|
|
391
|
+
|
|
392
|
+
def set_time(self, new_time: dec) -> None:
|
|
393
|
+
"""
|
|
394
|
+
set time on time widget
|
|
395
|
+
"""
|
|
396
|
+
|
|
397
|
+
if math.isnan(new_time):
|
|
398
|
+
return
|
|
399
|
+
|
|
400
|
+
self.pb_sign.setText("-" if new_time < 0 else "+")
|
|
401
|
+
|
|
402
|
+
# seconds
|
|
403
|
+
self.le_seconds.setText(f"{new_time:0.3f}")
|
|
404
|
+
|
|
405
|
+
if new_time <= cfg.DATE_CUTOFF: # hh:mm:ss.zzz
|
|
406
|
+
h = int(abs(new_time) // 3600)
|
|
407
|
+
m = int((abs(new_time) - h * 3600) // 60)
|
|
408
|
+
s = int((abs(new_time) - h * 3600 - m * 60))
|
|
409
|
+
ms = round((abs(new_time) - h * 3600 - m * 60 - s) * 1000)
|
|
410
|
+
|
|
411
|
+
self.sb_hour.setValue(h)
|
|
412
|
+
self.te_time.setTime(QTime(0, m, s, ms))
|
|
413
|
+
else:
|
|
414
|
+
self.sb_hour.setValue(0)
|
|
415
|
+
self.te_time.setTime(QTime(0, 0, 0, 0))
|
|
416
|
+
|
|
417
|
+
self.dte.setDateTime(QDateTime().fromMSecsSinceEpoch(int(new_time * 1000)))
|
|
418
|
+
self.rb_datetime.setChecked(True)
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
class Ask_time(QDialog):
|
|
422
|
+
"""
|
|
423
|
+
Qdialog class for asking time to user
|
|
424
|
+
User can select a time format between seconds, HHMMSS.zzz or YYY-mm-DD HH:MM:SS.zzz
|
|
425
|
+
"""
|
|
426
|
+
|
|
427
|
+
def __init__(self, time_value=0):
|
|
428
|
+
super().__init__()
|
|
429
|
+
self.setWindowTitle("")
|
|
430
|
+
|
|
431
|
+
hbox = QVBoxLayout(self)
|
|
432
|
+
self.label = QLabel()
|
|
433
|
+
self.label.setText("")
|
|
434
|
+
hbox.addWidget(self.label)
|
|
435
|
+
|
|
436
|
+
self.time_widget = get_time_widget(time_value)
|
|
437
|
+
|
|
438
|
+
hbox.addWidget(self.time_widget)
|
|
439
|
+
|
|
440
|
+
self.pbOK = QPushButton(cfg.OK, clicked=self.pb_ok_clicked)
|
|
441
|
+
self.pbOK.setDefault(True)
|
|
442
|
+
|
|
443
|
+
self.pbCancel = QPushButton(cfg.CANCEL)
|
|
444
|
+
self.pbCancel.clicked.connect(self.reject)
|
|
445
|
+
|
|
446
|
+
self.hbox2 = QHBoxLayout(self)
|
|
447
|
+
self.hbox2.addItem(QSpacerItem(241, 20, QSizePolicy.Expanding, QSizePolicy.Minimum))
|
|
448
|
+
self.hbox2.addWidget(self.pbCancel)
|
|
449
|
+
self.hbox2.addWidget(self.pbOK)
|
|
450
|
+
hbox.addLayout(self.hbox2)
|
|
451
|
+
self.setLayout(hbox)
|
|
452
|
+
|
|
453
|
+
# if time_value:
|
|
454
|
+
# self.time_widget.set_time(time_value)
|
|
455
|
+
|
|
456
|
+
def pb_ok_clicked(self):
|
|
457
|
+
if (
|
|
458
|
+
not self.time_widget.rb_seconds.isChecked()
|
|
459
|
+
and not self.time_widget.rb_time.isChecked()
|
|
460
|
+
and not self.time_widget.rb_datetime.isChecked()
|
|
461
|
+
):
|
|
462
|
+
QMessageBox.warning(
|
|
463
|
+
None,
|
|
464
|
+
cfg.programName,
|
|
465
|
+
"Select an option",
|
|
466
|
+
QMessageBox.Ok | QMessageBox.Default,
|
|
467
|
+
QMessageBox.NoButton,
|
|
468
|
+
)
|
|
469
|
+
return
|
|
470
|
+
# test time value
|
|
471
|
+
if self.time_widget.get_time().is_nan():
|
|
472
|
+
return
|
|
473
|
+
|
|
474
|
+
self.accept()
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
class Video_overlay_dialog(QDialog):
|
|
478
|
+
"""
|
|
479
|
+
dialog to ask image file and position for video overlay
|
|
480
|
+
"""
|
|
481
|
+
|
|
482
|
+
def __init__(self):
|
|
483
|
+
super().__init__()
|
|
484
|
+
|
|
485
|
+
vlayout = QVBoxLayout()
|
|
486
|
+
self.cb_player = QComboBox()
|
|
487
|
+
vlayout.addWidget(self.cb_player)
|
|
488
|
+
|
|
489
|
+
hbox = QHBoxLayout()
|
|
490
|
+
hbox.addWidget(QLabel("Image file"))
|
|
491
|
+
self.le_file_path = QLineEdit()
|
|
492
|
+
hbox.addWidget(self.le_file_path)
|
|
493
|
+
vlayout.addLayout(hbox)
|
|
494
|
+
|
|
495
|
+
hbox = QHBoxLayout()
|
|
496
|
+
self.pb_browse = QPushButton("Browse", self, clicked=self.browse)
|
|
497
|
+
hbox.addItem(QSpacerItem(241, 20, QSizePolicy.Expanding, QSizePolicy.Minimum))
|
|
498
|
+
hbox.addWidget(self.pb_browse)
|
|
499
|
+
vlayout.addLayout(hbox)
|
|
500
|
+
|
|
501
|
+
hbox = QHBoxLayout()
|
|
502
|
+
hbox.addWidget(QLabel("Position: x,y"))
|
|
503
|
+
self.le_overlay_position = QLineEdit()
|
|
504
|
+
hbox.addWidget(self.le_overlay_position)
|
|
505
|
+
vlayout.addLayout(hbox)
|
|
506
|
+
|
|
507
|
+
hbox = QHBoxLayout()
|
|
508
|
+
hbox.addWidget(QLabel("Transparency %"))
|
|
509
|
+
self.sb_overlay_transparency = QSpinBox()
|
|
510
|
+
self.sb_overlay_transparency.setRange(0, 100)
|
|
511
|
+
self.sb_overlay_transparency.setSingleStep(1)
|
|
512
|
+
self.sb_overlay_transparency.setValue(90)
|
|
513
|
+
hbox.addWidget(self.sb_overlay_transparency)
|
|
514
|
+
vlayout.addLayout(hbox)
|
|
515
|
+
|
|
516
|
+
# self.sb_overlay_transparency.setEnabled(False)
|
|
517
|
+
|
|
518
|
+
hbox = QHBoxLayout()
|
|
519
|
+
self.pb_cancel = QPushButton("Cancel", self, clicked=self.reject)
|
|
520
|
+
hbox.addWidget(self.pb_cancel)
|
|
521
|
+
self.pb_OK = QPushButton("OK", clicked=self.ok)
|
|
522
|
+
|
|
523
|
+
self.pb_OK.setDefault(True)
|
|
524
|
+
hbox.addWidget(self.pb_OK)
|
|
525
|
+
|
|
526
|
+
vlayout.addLayout(hbox)
|
|
527
|
+
|
|
528
|
+
self.setLayout(vlayout)
|
|
529
|
+
|
|
530
|
+
def browse(self):
|
|
531
|
+
file_name, _ = QFileDialog.getOpenFileName(self, "Choose an image file", "", "PNG files (*.png);;All files (*)")
|
|
532
|
+
if file_name:
|
|
533
|
+
self.le_file_path.setText(file_name)
|
|
534
|
+
|
|
535
|
+
def ok(self):
|
|
536
|
+
if not self.le_file_path.text():
|
|
537
|
+
QMessageBox.warning(
|
|
538
|
+
None,
|
|
539
|
+
cfg.programName,
|
|
540
|
+
"Select a file containing a PNG image",
|
|
541
|
+
QMessageBox.Ok | QMessageBox.Default,
|
|
542
|
+
QMessageBox.NoButton,
|
|
543
|
+
)
|
|
544
|
+
return
|
|
545
|
+
|
|
546
|
+
if self.le_overlay_position.text() and "," not in self.le_overlay_position.text():
|
|
547
|
+
QMessageBox.warning(
|
|
548
|
+
None,
|
|
549
|
+
cfg.programName,
|
|
550
|
+
"The overlay position must be in x,y format",
|
|
551
|
+
QMessageBox.Ok | QMessageBox.Default,
|
|
552
|
+
QMessageBox.NoButton,
|
|
553
|
+
)
|
|
554
|
+
return
|
|
555
|
+
if self.le_overlay_position.text():
|
|
556
|
+
try:
|
|
557
|
+
[int(x.strip()) for x in self.le_overlay_position.text().split(",")]
|
|
558
|
+
except Exception:
|
|
559
|
+
QMessageBox.warning(
|
|
560
|
+
None,
|
|
561
|
+
cfg.programName,
|
|
562
|
+
"The overlay position must be in x,y format",
|
|
563
|
+
QMessageBox.Ok | QMessageBox.Default,
|
|
564
|
+
QMessageBox.NoButton,
|
|
565
|
+
)
|
|
566
|
+
return
|
|
567
|
+
self.accept()
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
class Input_dialog(QDialog):
|
|
571
|
+
"""
|
|
572
|
+
dialog for user input. Elements can be:
|
|
573
|
+
checkbox (cb): Tuple(str, str, bool)
|
|
574
|
+
lineedit (le): Tuple(str, str)
|
|
575
|
+
spinbox (sb)
|
|
576
|
+
doubleSpinbox (dsb)
|
|
577
|
+
items list (il)
|
|
578
|
+
|
|
579
|
+
"""
|
|
580
|
+
|
|
581
|
+
def __init__(self, label_caption: str, elements_list: list, title: str = ""):
|
|
582
|
+
super().__init__()
|
|
583
|
+
|
|
584
|
+
self.setWindowTitle(title)
|
|
585
|
+
|
|
586
|
+
hbox = QVBoxLayout()
|
|
587
|
+
self.label = QLabel()
|
|
588
|
+
self.label.setText(label_caption)
|
|
589
|
+
hbox.addWidget(self.label)
|
|
590
|
+
|
|
591
|
+
self.elements: dict = {}
|
|
592
|
+
for element in elements_list:
|
|
593
|
+
if element[0] == "cb": # checkbox
|
|
594
|
+
self.elements[element[1]] = QCheckBox(element[1])
|
|
595
|
+
self.elements[element[1]].setChecked(element[2])
|
|
596
|
+
hbox.addWidget(self.elements[element[1]])
|
|
597
|
+
|
|
598
|
+
if element[0] == "le": # line edit
|
|
599
|
+
lb = QLabel(element[1])
|
|
600
|
+
hbox.addWidget(lb)
|
|
601
|
+
self.elements[element[1]] = QLineEdit()
|
|
602
|
+
hbox.addWidget(self.elements[element[1]])
|
|
603
|
+
|
|
604
|
+
if element[0] == "sb": # spinbox
|
|
605
|
+
# 1 - Label
|
|
606
|
+
# 2 - minimum value
|
|
607
|
+
# 3 - maximum value
|
|
608
|
+
# 4 - step
|
|
609
|
+
# 5 - initial value
|
|
610
|
+
|
|
611
|
+
lb = QLabel(element[1])
|
|
612
|
+
hbox.addWidget(lb)
|
|
613
|
+
self.elements[element[1]] = QSpinBox()
|
|
614
|
+
self.elements[element[1]].setRange(element[2], element[3])
|
|
615
|
+
self.elements[element[1]].setSingleStep(element[4])
|
|
616
|
+
self.elements[element[1]].setValue(element[5])
|
|
617
|
+
hbox.addWidget(self.elements[element[1]])
|
|
618
|
+
|
|
619
|
+
if element[0] == "dsb": # doubleSpinbox
|
|
620
|
+
# 1 - Label
|
|
621
|
+
# 2 - minimum value
|
|
622
|
+
# 3 - maximum value
|
|
623
|
+
# 4 - step
|
|
624
|
+
# 5 - initial value
|
|
625
|
+
# 6 - number of decimals
|
|
626
|
+
|
|
627
|
+
lb = QLabel(element[1])
|
|
628
|
+
hbox.addWidget(lb)
|
|
629
|
+
self.elements[element[1]] = QDoubleSpinBox()
|
|
630
|
+
self.elements[element[1]].setRange(element[2], element[3])
|
|
631
|
+
self.elements[element[1]].setSingleStep(element[4])
|
|
632
|
+
self.elements[element[1]].setValue(element[5])
|
|
633
|
+
self.elements[element[1]].setDecimals(element[6])
|
|
634
|
+
hbox.addWidget(self.elements[element[1]])
|
|
635
|
+
|
|
636
|
+
if element[0] == "il": # items list
|
|
637
|
+
# 1 - Label
|
|
638
|
+
# 2 - Values (tuple of tuple: 0 - value; 1 - "", "selected")
|
|
639
|
+
lb = QLabel(element[1])
|
|
640
|
+
hbox.addWidget(lb)
|
|
641
|
+
self.elements[element[1]] = QComboBox()
|
|
642
|
+
self.elements[element[1]].addItems([x[0] for x in element[2]]) # take first element of tuple
|
|
643
|
+
try:
|
|
644
|
+
self.elements[element[1]].setCurrentIndex([idx for idx, x in enumerate(element[2]) if x[1] == "selected"][0])
|
|
645
|
+
except Exception:
|
|
646
|
+
self.elements[element[1]].setCurrentIndex(0)
|
|
647
|
+
hbox.addWidget(self.elements[element[1]])
|
|
648
|
+
|
|
649
|
+
hbox2 = QHBoxLayout()
|
|
650
|
+
|
|
651
|
+
hbox2.addItem(QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum))
|
|
652
|
+
|
|
653
|
+
self.pbCancel = QPushButton(cfg.CANCEL)
|
|
654
|
+
self.pbCancel.clicked.connect(self.reject)
|
|
655
|
+
hbox2.addWidget(self.pbCancel)
|
|
656
|
+
|
|
657
|
+
self.pbOK = QPushButton(cfg.OK, clicked=self.accept)
|
|
658
|
+
self.pbOK.setDefault(True)
|
|
659
|
+
hbox2.addWidget(self.pbOK)
|
|
660
|
+
|
|
661
|
+
hbox.addLayout(hbox2)
|
|
662
|
+
|
|
663
|
+
self.setLayout(hbox)
|
|
664
|
+
|
|
665
|
+
|
|
666
|
+
class Duplicate_items(QDialog):
|
|
667
|
+
"""
|
|
668
|
+
let user show between behaviors/subjects that are coded by same key
|
|
669
|
+
"""
|
|
670
|
+
|
|
671
|
+
def __init__(self, text, codes_list):
|
|
672
|
+
super(Duplicate_items, self).__init__()
|
|
673
|
+
|
|
674
|
+
self.setWindowTitle(cfg.programName)
|
|
675
|
+
self.setWindowFlags(Qt.WindowStaysOnTopHint)
|
|
676
|
+
|
|
677
|
+
Vlayout = QVBoxLayout()
|
|
678
|
+
widget = QWidget(self)
|
|
679
|
+
widget.setLayout(Vlayout)
|
|
680
|
+
|
|
681
|
+
label = QLabel()
|
|
682
|
+
label.setText(text)
|
|
683
|
+
Vlayout.addWidget(label)
|
|
684
|
+
|
|
685
|
+
self.lw = QListWidget(widget)
|
|
686
|
+
self.lw.setObjectName("lw_modifiers")
|
|
687
|
+
# TODO: to be enabled
|
|
688
|
+
# lw.installEventFilter(self)
|
|
689
|
+
|
|
690
|
+
for code in codes_list:
|
|
691
|
+
item = QListWidgetItem(code)
|
|
692
|
+
self.lw.addItem(item)
|
|
693
|
+
|
|
694
|
+
Vlayout.addWidget(self.lw)
|
|
695
|
+
|
|
696
|
+
hlayout = QHBoxLayout()
|
|
697
|
+
hlayout.addItem(QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum))
|
|
698
|
+
|
|
699
|
+
pbCancel = QPushButton("Cancel", clicked=self.reject)
|
|
700
|
+
hlayout.addWidget(pbCancel)
|
|
701
|
+
|
|
702
|
+
pbOK = QPushButton("OK", clicked=self.accept)
|
|
703
|
+
pbOK.setDefault(True)
|
|
704
|
+
hlayout.addWidget(pbOK)
|
|
705
|
+
|
|
706
|
+
Vlayout.addLayout(hlayout)
|
|
707
|
+
|
|
708
|
+
self.setLayout(Vlayout)
|
|
709
|
+
|
|
710
|
+
# self.installEventFilter(self)
|
|
711
|
+
|
|
712
|
+
self.setMaximumSize(1024, 960)
|
|
713
|
+
|
|
714
|
+
def getCode(self):
|
|
715
|
+
"""
|
|
716
|
+
get selected behavior code
|
|
717
|
+
"""
|
|
718
|
+
if self.lw.selectedItems():
|
|
719
|
+
return self.lw.selectedItems()[0].text()
|
|
720
|
+
else:
|
|
721
|
+
return None
|
|
722
|
+
|
|
723
|
+
|
|
724
|
+
class ChooseObservationsToImport(QDialog):
|
|
725
|
+
"""
|
|
726
|
+
dialog for selectiong items
|
|
727
|
+
"""
|
|
728
|
+
|
|
729
|
+
def __init__(self, text, observations_list):
|
|
730
|
+
super(ChooseObservationsToImport, self).__init__()
|
|
731
|
+
|
|
732
|
+
self.setWindowTitle(cfg.programName)
|
|
733
|
+
|
|
734
|
+
Vlayout = QVBoxLayout()
|
|
735
|
+
widget = QWidget(self)
|
|
736
|
+
widget.setLayout(Vlayout)
|
|
737
|
+
|
|
738
|
+
label = QLabel()
|
|
739
|
+
label.setText(text)
|
|
740
|
+
Vlayout.addWidget(label)
|
|
741
|
+
|
|
742
|
+
self.lw = QListWidget(widget)
|
|
743
|
+
self.lw.setObjectName("lw_observations")
|
|
744
|
+
self.lw.setSelectionMode(QAbstractItemView.ExtendedSelection)
|
|
745
|
+
# TODO: to be enabled
|
|
746
|
+
# lw.installEventFilter(self)
|
|
747
|
+
|
|
748
|
+
for code in observations_list:
|
|
749
|
+
item = QListWidgetItem(code)
|
|
750
|
+
self.lw.addItem(item)
|
|
751
|
+
|
|
752
|
+
Vlayout.addWidget(self.lw)
|
|
753
|
+
|
|
754
|
+
hlayout = QHBoxLayout()
|
|
755
|
+
hlayout.addItem(QSpacerItem(241, 20, QSizePolicy.Expanding, QSizePolicy.Minimum))
|
|
756
|
+
pbCancel = QPushButton("Cancel", clicked=self.reject)
|
|
757
|
+
hlayout.addWidget(pbCancel)
|
|
758
|
+
pbOK = QPushButton("OK", clicked=self.accept)
|
|
759
|
+
pbOK.setDefault(True)
|
|
760
|
+
hlayout.addWidget(pbOK)
|
|
761
|
+
|
|
762
|
+
Vlayout.addLayout(hlayout)
|
|
763
|
+
|
|
764
|
+
self.setLayout(Vlayout)
|
|
765
|
+
|
|
766
|
+
# self.installEventFilter(self)
|
|
767
|
+
|
|
768
|
+
self.setMaximumSize(1024, 960)
|
|
769
|
+
|
|
770
|
+
def get_selected_observations(self):
|
|
771
|
+
"""
|
|
772
|
+
get selected_observations
|
|
773
|
+
"""
|
|
774
|
+
return [item.text() for item in self.lw.selectedItems()]
|
|
775
|
+
|
|
776
|
+
|
|
777
|
+
class FindInEvents(QWidget):
|
|
778
|
+
"""
|
|
779
|
+
"find in events" dialog box
|
|
780
|
+
"""
|
|
781
|
+
|
|
782
|
+
clickSignal = Signal(str)
|
|
783
|
+
currentIdx: int = -1
|
|
784
|
+
|
|
785
|
+
def __init__(self):
|
|
786
|
+
super().__init__()
|
|
787
|
+
|
|
788
|
+
self.setWindowTitle("Find in events")
|
|
789
|
+
|
|
790
|
+
hbox = QVBoxLayout()
|
|
791
|
+
|
|
792
|
+
self.cbSubject = QCheckBox("Subject")
|
|
793
|
+
self.cbSubject.setChecked(True)
|
|
794
|
+
hbox.addWidget(self.cbSubject)
|
|
795
|
+
|
|
796
|
+
self.cbBehavior = QCheckBox("Behavior")
|
|
797
|
+
self.cbBehavior.setChecked(True)
|
|
798
|
+
hbox.addWidget(self.cbBehavior)
|
|
799
|
+
|
|
800
|
+
self.cbModifier = QCheckBox("Modifiers")
|
|
801
|
+
self.cbModifier.setChecked(True)
|
|
802
|
+
hbox.addWidget(self.cbModifier)
|
|
803
|
+
|
|
804
|
+
self.cbComment = QCheckBox("Comment")
|
|
805
|
+
self.cbComment.setChecked(True)
|
|
806
|
+
hbox.addWidget(self.cbComment)
|
|
807
|
+
|
|
808
|
+
self.lbFind = QLabel("Find")
|
|
809
|
+
hbox.addWidget(self.lbFind)
|
|
810
|
+
|
|
811
|
+
self.findText = QLineEdit()
|
|
812
|
+
hbox.addWidget(self.findText)
|
|
813
|
+
|
|
814
|
+
self.cbFindInSelectedEvents = QCheckBox("Find in selected events")
|
|
815
|
+
self.cbFindInSelectedEvents.setChecked(False)
|
|
816
|
+
hbox.addWidget(self.cbFindInSelectedEvents)
|
|
817
|
+
|
|
818
|
+
self.cb_case_sensitive = QCheckBox("Case sensitive")
|
|
819
|
+
self.cb_case_sensitive.setChecked(False)
|
|
820
|
+
hbox.addWidget(self.cb_case_sensitive)
|
|
821
|
+
|
|
822
|
+
self.lb_message = QLabel()
|
|
823
|
+
hbox.addWidget(self.lb_message)
|
|
824
|
+
|
|
825
|
+
hbox2 = QHBoxLayout()
|
|
826
|
+
hbox2.addItem(QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum))
|
|
827
|
+
self.pbOK = QPushButton("Find")
|
|
828
|
+
self.pbOK.clicked.connect(lambda: self.click("FIND"))
|
|
829
|
+
self.pbCancel = QPushButton(cfg.CLOSE)
|
|
830
|
+
self.pbCancel.clicked.connect(lambda: self.click("CLOSE"))
|
|
831
|
+
hbox2.addWidget(self.pbCancel)
|
|
832
|
+
hbox2.addWidget(self.pbOK)
|
|
833
|
+
hbox.addLayout(hbox2)
|
|
834
|
+
|
|
835
|
+
self.setLayout(hbox)
|
|
836
|
+
|
|
837
|
+
def click(self, msg):
|
|
838
|
+
self.clickSignal.emit(msg)
|
|
839
|
+
|
|
840
|
+
|
|
841
|
+
class FindReplaceEvents(QWidget):
|
|
842
|
+
"""
|
|
843
|
+
"find replace events" dialog box
|
|
844
|
+
"""
|
|
845
|
+
|
|
846
|
+
clickSignal = Signal(str)
|
|
847
|
+
|
|
848
|
+
def __init__(self):
|
|
849
|
+
super().__init__()
|
|
850
|
+
|
|
851
|
+
self.setWindowTitle("Find/Replace events")
|
|
852
|
+
|
|
853
|
+
hbox = QVBoxLayout()
|
|
854
|
+
|
|
855
|
+
self.combo_fields = QComboBox()
|
|
856
|
+
self.combo_fields.addItems(("Choose a field", "Subject", "Behavior", "Modifiers", "Comment"))
|
|
857
|
+
hbox.addWidget(self.combo_fields)
|
|
858
|
+
|
|
859
|
+
self.lbFind = QLabel("Find")
|
|
860
|
+
hbox.addWidget(self.lbFind)
|
|
861
|
+
|
|
862
|
+
self.findText = QLineEdit()
|
|
863
|
+
hbox.addWidget(self.findText)
|
|
864
|
+
|
|
865
|
+
self.lbReplace = QLabel("Replace")
|
|
866
|
+
hbox.addWidget(self.lbReplace)
|
|
867
|
+
|
|
868
|
+
self.replaceText = QLineEdit()
|
|
869
|
+
hbox.addWidget(self.replaceText)
|
|
870
|
+
|
|
871
|
+
self.cbFindInSelectedEvents = QCheckBox("Find/Replace only in selected events")
|
|
872
|
+
self.cbFindInSelectedEvents.setChecked(False)
|
|
873
|
+
hbox.addWidget(self.cbFindInSelectedEvents)
|
|
874
|
+
|
|
875
|
+
self.cb_case_sensitive = QCheckBox("Case sensitive")
|
|
876
|
+
self.cb_case_sensitive.setChecked(False)
|
|
877
|
+
hbox.addWidget(self.cb_case_sensitive)
|
|
878
|
+
|
|
879
|
+
hbox2 = QHBoxLayout()
|
|
880
|
+
|
|
881
|
+
hbox2.addItem(QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum))
|
|
882
|
+
|
|
883
|
+
self.pbCancel = QPushButton(cfg.CANCEL, clicked=lambda: self.click("CANCEL"))
|
|
884
|
+
hbox2.addWidget(self.pbCancel)
|
|
885
|
+
|
|
886
|
+
self.pbOK = QPushButton("Find and replace", clicked=lambda: self.click("FIND_REPLACE"))
|
|
887
|
+
hbox2.addWidget(self.pbOK)
|
|
888
|
+
|
|
889
|
+
self.pbFindReplaceAll = QPushButton("Find and replace all", clicked=lambda: self.click("FIND_REPLACE_ALL"))
|
|
890
|
+
hbox2.addWidget(self.pbFindReplaceAll)
|
|
891
|
+
|
|
892
|
+
hbox.addLayout(hbox2)
|
|
893
|
+
|
|
894
|
+
self.lb_results = QLabel(" ")
|
|
895
|
+
hbox.addWidget(self.lb_results)
|
|
896
|
+
|
|
897
|
+
self.setLayout(hbox)
|
|
898
|
+
|
|
899
|
+
def click(self, msg):
|
|
900
|
+
self.clickSignal.emit(msg)
|
|
901
|
+
|
|
902
|
+
|
|
903
|
+
class Results_dialog(QDialog):
|
|
904
|
+
"""
|
|
905
|
+
widget for visualizing text output in HTML
|
|
906
|
+
"""
|
|
907
|
+
|
|
908
|
+
def __init__(self):
|
|
909
|
+
super().__init__()
|
|
910
|
+
|
|
911
|
+
self.dataset = False
|
|
912
|
+
|
|
913
|
+
self.setWindowTitle("")
|
|
914
|
+
|
|
915
|
+
hbox = QVBoxLayout()
|
|
916
|
+
|
|
917
|
+
self.lb = QLabel("")
|
|
918
|
+
hbox.addWidget(self.lb)
|
|
919
|
+
|
|
920
|
+
self.ptText = QPlainTextEdit()
|
|
921
|
+
self.ptText.setReadOnly(True)
|
|
922
|
+
hbox.addWidget(self.ptText)
|
|
923
|
+
|
|
924
|
+
hbox2 = QHBoxLayout()
|
|
925
|
+
hbox2.addItem(QSpacerItem(241, 20, QSizePolicy.Expanding, QSizePolicy.Minimum))
|
|
926
|
+
|
|
927
|
+
self.pbSave = QPushButton("Save results", clicked=self.save_results)
|
|
928
|
+
hbox2.addWidget(self.pbSave)
|
|
929
|
+
|
|
930
|
+
self.pbCancel = QPushButton(cfg.CANCEL, clicked=self.reject)
|
|
931
|
+
hbox2.addWidget(self.pbCancel)
|
|
932
|
+
self.pbCancel.setVisible(False)
|
|
933
|
+
|
|
934
|
+
self.pbOK = QPushButton(cfg.OK, clicked=self.accept)
|
|
935
|
+
hbox2.addWidget(self.pbOK)
|
|
936
|
+
|
|
937
|
+
hbox.addLayout(hbox2)
|
|
938
|
+
self.setLayout(hbox)
|
|
939
|
+
|
|
940
|
+
self.resize(800, 640)
|
|
941
|
+
|
|
942
|
+
def save_results(self):
|
|
943
|
+
"""
|
|
944
|
+
save content of self.ptText
|
|
945
|
+
"""
|
|
946
|
+
|
|
947
|
+
if not self.dataset:
|
|
948
|
+
file_name, _ = QFileDialog().getSaveFileName(self, "Save results", "", "Text files (*.txt *.tsv);;All files (*)")
|
|
949
|
+
|
|
950
|
+
if not file_name:
|
|
951
|
+
return
|
|
952
|
+
try:
|
|
953
|
+
with open(file_name, "w") as f:
|
|
954
|
+
f.write(self.ptText.toPlainText())
|
|
955
|
+
except Exception:
|
|
956
|
+
QMessageBox.critical(self, cfg.programName, f"The file {file_name} can not be saved")
|
|
957
|
+
|
|
958
|
+
else:
|
|
959
|
+
self.done(cfg.SAVE_DATASET)
|
|
960
|
+
|
|
961
|
+
|
|
962
|
+
class Results_dialog_exit_code(QDialog):
|
|
963
|
+
"""
|
|
964
|
+
widget for visualizing text output
|
|
965
|
+
"""
|
|
966
|
+
|
|
967
|
+
def __init__(self):
|
|
968
|
+
super().__init__()
|
|
969
|
+
|
|
970
|
+
self.dataset = False
|
|
971
|
+
|
|
972
|
+
self.setWindowTitle("")
|
|
973
|
+
|
|
974
|
+
hbox = QVBoxLayout()
|
|
975
|
+
|
|
976
|
+
self.lb = QLabel("")
|
|
977
|
+
hbox.addWidget(self.lb)
|
|
978
|
+
|
|
979
|
+
self.ptText = QPlainTextEdit()
|
|
980
|
+
self.ptText.setReadOnly(True)
|
|
981
|
+
hbox.addWidget(self.ptText)
|
|
982
|
+
|
|
983
|
+
hbox2 = QHBoxLayout()
|
|
984
|
+
hbox2.addItem(QSpacerItem(241, 20, QSizePolicy.Expanding, QSizePolicy.Minimum))
|
|
985
|
+
|
|
986
|
+
self.pbSave = QPushButton("Save results", clicked=self.save_results)
|
|
987
|
+
hbox2.addWidget(self.pbSave)
|
|
988
|
+
|
|
989
|
+
self.pb1 = QPushButton("1", clicked=lambda: self.done_(1))
|
|
990
|
+
hbox2.addWidget(self.pb1)
|
|
991
|
+
|
|
992
|
+
self.pb2 = QPushButton("2", clicked=lambda: self.done_(2))
|
|
993
|
+
hbox2.addWidget(self.pb2)
|
|
994
|
+
|
|
995
|
+
self.pb3 = QPushButton("3", clicked=lambda: self.done_(3))
|
|
996
|
+
hbox2.addWidget(self.pb3)
|
|
997
|
+
|
|
998
|
+
hbox.addLayout(hbox2)
|
|
999
|
+
self.setLayout(hbox)
|
|
1000
|
+
|
|
1001
|
+
self.resize(800, 640)
|
|
1002
|
+
|
|
1003
|
+
def done_(self, status):
|
|
1004
|
+
self.done(status)
|
|
1005
|
+
|
|
1006
|
+
def save_results(self):
|
|
1007
|
+
"""
|
|
1008
|
+
save content of self.ptText
|
|
1009
|
+
"""
|
|
1010
|
+
|
|
1011
|
+
if not self.dataset:
|
|
1012
|
+
file_name, _ = QFileDialog().getSaveFileName(self, "Save results", "", "Text files (*.txt *.tsv);;All files (*)")
|
|
1013
|
+
|
|
1014
|
+
if not file_name:
|
|
1015
|
+
return
|
|
1016
|
+
try:
|
|
1017
|
+
with open(file_name, "w") as f:
|
|
1018
|
+
f.write(self.ptText.toPlainText())
|
|
1019
|
+
except Exception:
|
|
1020
|
+
QMessageBox.critical(self, cfg.programName, f"The file {file_name} can not be saved")
|
|
1021
|
+
|
|
1022
|
+
else:
|
|
1023
|
+
self.done(cfg.SAVE_DATASET)
|
|
1024
|
+
|
|
1025
|
+
|
|
1026
|
+
class View_data(QDialog):
|
|
1027
|
+
"""
|
|
1028
|
+
widget for visualizing rows of data file
|
|
1029
|
+
"""
|
|
1030
|
+
|
|
1031
|
+
def __init__(self):
|
|
1032
|
+
super().__init__()
|
|
1033
|
+
|
|
1034
|
+
self.setWindowTitle("")
|
|
1035
|
+
|
|
1036
|
+
vbox = QVBoxLayout()
|
|
1037
|
+
|
|
1038
|
+
self.lb = QLabel("")
|
|
1039
|
+
vbox.addWidget(self.lb)
|
|
1040
|
+
|
|
1041
|
+
self.tw = QTableWidget()
|
|
1042
|
+
self.tw.verticalHeader().hide()
|
|
1043
|
+
vbox.addWidget(self.tw)
|
|
1044
|
+
|
|
1045
|
+
vbox.addWidget(QLabel("Descriptive statistics"))
|
|
1046
|
+
|
|
1047
|
+
self.stats = QPlainTextEdit()
|
|
1048
|
+
font = QFont()
|
|
1049
|
+
font.setFamily("Monospace")
|
|
1050
|
+
self.stats.setFont(font)
|
|
1051
|
+
|
|
1052
|
+
vbox.addWidget(self.stats)
|
|
1053
|
+
|
|
1054
|
+
self.label = QLabel("Enter the column indices to plot (time, value) separated by comma (,)")
|
|
1055
|
+
vbox.addWidget(self.label)
|
|
1056
|
+
|
|
1057
|
+
self.le = QLineEdit()
|
|
1058
|
+
vbox.addWidget(self.le)
|
|
1059
|
+
|
|
1060
|
+
hbox2 = QHBoxLayout()
|
|
1061
|
+
|
|
1062
|
+
hbox2.addItem(QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum))
|
|
1063
|
+
|
|
1064
|
+
self.pbCancel = QPushButton(cfg.CANCEL, clicked=self.reject)
|
|
1065
|
+
hbox2.addWidget(self.pbCancel)
|
|
1066
|
+
|
|
1067
|
+
self.pbOK = QPushButton(cfg.OK, clicked=self.accept)
|
|
1068
|
+
hbox2.addWidget(self.pbOK)
|
|
1069
|
+
|
|
1070
|
+
vbox.addLayout(hbox2)
|
|
1071
|
+
|
|
1072
|
+
self.setLayout(vbox)
|
|
1073
|
+
|
|
1074
|
+
self.resize(800, 640)
|
|
1075
|
+
|
|
1076
|
+
|
|
1077
|
+
class View_explore_project_results(QWidget):
|
|
1078
|
+
"""
|
|
1079
|
+
widget for visualizing results of explore project
|
|
1080
|
+
"""
|
|
1081
|
+
|
|
1082
|
+
double_click_signal = Signal(str, int)
|
|
1083
|
+
|
|
1084
|
+
def __init__(self):
|
|
1085
|
+
super().__init__()
|
|
1086
|
+
|
|
1087
|
+
self.setWindowTitle("")
|
|
1088
|
+
|
|
1089
|
+
vbox = QVBoxLayout()
|
|
1090
|
+
|
|
1091
|
+
self.lb = QLabel("")
|
|
1092
|
+
vbox.addWidget(self.lb)
|
|
1093
|
+
|
|
1094
|
+
self.tw = QTableWidget()
|
|
1095
|
+
self.tw.setSelectionBehavior(QTableView.SelectRows)
|
|
1096
|
+
self.tw.cellDoubleClicked[int, int].connect(self.tw_cellDoubleClicked)
|
|
1097
|
+
vbox.addWidget(self.tw)
|
|
1098
|
+
|
|
1099
|
+
hbox2 = QHBoxLayout()
|
|
1100
|
+
hbox2.addItem(QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum))
|
|
1101
|
+
hbox2.addWidget(QPushButton(cfg.CLOSE, clicked=self.close))
|
|
1102
|
+
|
|
1103
|
+
vbox.addLayout(hbox2)
|
|
1104
|
+
|
|
1105
|
+
self.setLayout(vbox)
|
|
1106
|
+
|
|
1107
|
+
def tw_cellDoubleClicked(self, r, c):
|
|
1108
|
+
self.double_click_signal.emit(self.tw.item(r, 0).text(), int(self.tw.item(r, 1).text()))
|