boris-behav-obs 8.9.16__py3-none-any.whl → 9.7.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (129) hide show
  1. boris/__init__.py +1 -1
  2. boris/__main__.py +1 -1
  3. boris/about.py +36 -39
  4. boris/add_modifier.py +122 -109
  5. boris/add_modifier_ui.py +239 -135
  6. boris/advanced_event_filtering.py +81 -45
  7. boris/analysis_plugins/__init__.py +0 -0
  8. boris/analysis_plugins/_latency.py +59 -0
  9. boris/analysis_plugins/irr_cohen_kappa.py +109 -0
  10. boris/analysis_plugins/irr_cohen_kappa_with_modifiers.py +112 -0
  11. boris/analysis_plugins/irr_weighted_cohen_kappa.py +157 -0
  12. boris/analysis_plugins/irr_weighted_cohen_kappa_with_modifiers.py +162 -0
  13. boris/analysis_plugins/list_of_dataframe_columns.py +22 -0
  14. boris/analysis_plugins/number_of_occurences.py +22 -0
  15. boris/analysis_plugins/number_of_occurences_by_independent_variable.py +54 -0
  16. boris/analysis_plugins/time_budget.py +61 -0
  17. boris/behav_coding_map_creator.py +228 -229
  18. boris/behavior_binary_table.py +33 -50
  19. boris/behaviors_coding_map.py +17 -18
  20. boris/boris_cli.py +6 -25
  21. boris/cmd_arguments.py +12 -1
  22. boris/coding_pad.py +42 -49
  23. boris/config.py +161 -77
  24. boris/config_file.py +63 -83
  25. boris/connections.py +112 -57
  26. boris/converters.py +13 -37
  27. boris/converters_ui.py +187 -110
  28. boris/cooccurence.py +250 -0
  29. boris/core.py +2511 -1824
  30. boris/core_qrc.py +15895 -10185
  31. boris/core_ui.py +946 -792
  32. boris/db_functions.py +21 -41
  33. boris/dev.py +134 -0
  34. boris/dialog.py +505 -244
  35. boris/duration_widget.py +15 -20
  36. boris/edit_event.py +84 -28
  37. boris/edit_event_ui.py +214 -78
  38. boris/event_operations.py +517 -415
  39. boris/events_cursor.py +25 -17
  40. boris/events_snapshots.py +36 -82
  41. boris/exclusion_matrix.py +4 -9
  42. boris/export_events.py +213 -583
  43. boris/export_observation.py +98 -611
  44. boris/external_processes.py +156 -97
  45. boris/geometric_measurement.py +652 -287
  46. boris/gui_utilities.py +91 -14
  47. boris/image_overlay.py +9 -9
  48. boris/import_observations.py +190 -98
  49. boris/ipc_mpv.py +325 -0
  50. boris/irr.py +26 -63
  51. boris/latency.py +34 -25
  52. boris/measurement_widget.py +14 -18
  53. boris/media_file.py +52 -84
  54. boris/menu_options.py +17 -6
  55. boris/modifier_coding_map_creator.py +1013 -0
  56. boris/modifiers_coding_map.py +7 -9
  57. boris/mpv.py +1 -0
  58. boris/mpv2.py +732 -705
  59. boris/observation.py +655 -310
  60. boris/observation_operations.py +1036 -404
  61. boris/observation_ui.py +584 -356
  62. boris/observations_list.py +71 -53
  63. boris/otx_parser.py +74 -80
  64. boris/param_panel.py +31 -16
  65. boris/param_panel_ui.py +254 -138
  66. boris/player_dock_widget.py +90 -60
  67. boris/plot_data_module.py +43 -46
  68. boris/plot_events.py +127 -90
  69. boris/plot_events_rt.py +17 -31
  70. boris/plot_spectrogram_rt.py +95 -30
  71. boris/plot_waveform_rt.py +32 -21
  72. boris/plugins.py +431 -0
  73. boris/portion/__init__.py +18 -8
  74. boris/portion/const.py +35 -18
  75. boris/portion/dict.py +5 -5
  76. boris/portion/func.py +2 -2
  77. boris/portion/interval.py +21 -41
  78. boris/portion/io.py +41 -32
  79. boris/preferences.py +306 -83
  80. boris/preferences_ui.py +685 -228
  81. boris/project.py +448 -293
  82. boris/project_functions.py +689 -254
  83. boris/project_import_export.py +213 -222
  84. boris/project_ui.py +674 -438
  85. boris/qrc_boris.py +6 -3
  86. boris/qrc_boris5.py +6 -3
  87. boris/select_modifiers.py +74 -48
  88. boris/select_observations.py +20 -199
  89. boris/select_subj_behav.py +67 -39
  90. boris/state_events.py +53 -37
  91. boris/subjects_pad.py +6 -9
  92. boris/synthetic_time_budget.py +45 -28
  93. boris/time_budget_functions.py +171 -171
  94. boris/time_budget_widget.py +84 -114
  95. boris/transitions.py +41 -47
  96. boris/utilities.py +766 -266
  97. boris/version.py +3 -3
  98. boris/video_equalizer.py +16 -14
  99. boris/video_equalizer_ui.py +199 -130
  100. boris/video_operations.py +125 -28
  101. boris/view_df.py +104 -0
  102. boris/view_df_ui.py +75 -0
  103. boris/write_event.py +538 -0
  104. boris_behav_obs-9.7.6.dist-info/METADATA +139 -0
  105. boris_behav_obs-9.7.6.dist-info/RECORD +109 -0
  106. {boris_behav_obs-8.9.16.dist-info → boris_behav_obs-9.7.6.dist-info}/WHEEL +1 -1
  107. boris_behav_obs-9.7.6.dist-info/entry_points.txt +2 -0
  108. boris/README.TXT +0 -22
  109. boris/add_modifier.ui +0 -323
  110. boris/boris_ui.py +0 -886
  111. boris/converters.ui +0 -289
  112. boris/core.qrc +0 -35
  113. boris/core.ui +0 -1543
  114. boris/edit_event.ui +0 -175
  115. boris/icons/logo_eye.ico +0 -0
  116. boris/map_creator.py +0 -850
  117. boris/observation.ui +0 -773
  118. boris/param_panel.ui +0 -379
  119. boris/preferences.ui +0 -537
  120. boris/project.ui +0 -1069
  121. boris/project_server.py +0 -236
  122. boris/vlc.py +0 -10343
  123. boris/vlc_local.py +0 -90
  124. boris_behav_obs-8.9.16.dist-info/LICENSE.TXT +0 -674
  125. boris_behav_obs-8.9.16.dist-info/METADATA +0 -129
  126. boris_behav_obs-8.9.16.dist-info/RECORD +0 -108
  127. boris_behav_obs-8.9.16.dist-info/entry_points.txt +0 -2
  128. {boris → boris_behav_obs-9.7.6.dist-info/licenses}/LICENSE.TXT +0 -0
  129. {boris_behav_obs-8.9.16.dist-info → boris_behav_obs-9.7.6.dist-info}/top_level.txt +0 -0
boris/dialog.py CHANGED
@@ -1,7 +1,7 @@
1
1
  """
2
2
  BORIS
3
3
  Behavioral Observation Research Interactive Software
4
- Copyright 2012-2023 Olivier Friard
4
+ Copyright 2012-2025 Olivier Friard
5
5
 
6
6
  This file is part of BORIS.
7
7
 
@@ -20,16 +20,18 @@ This file is part of BORIS.
20
20
 
21
21
  """
22
22
 
23
+ import datetime as dt
24
+ from decimal import Decimal as dec
23
25
  import logging
24
- import sys
26
+ import math
25
27
  import pathlib as pl
26
- import traceback
27
28
  import platform
28
- import datetime as dt
29
- from decimal import Decimal as dec
29
+ import sys
30
+ import traceback
31
+ from typing import Union
30
32
 
31
- from PyQt5.QtCore import Qt, pyqtSignal, QT_VERSION_STR, PYQT_VERSION_STR
32
- from PyQt5.QtWidgets import (
33
+ from PySide6.QtCore import Qt, Signal, qVersion, QRect, QTime, QDateTime, QSize
34
+ from PySide6.QtWidgets import (
33
35
  QApplication,
34
36
  QAbstractItemView,
35
37
  QCheckBox,
@@ -52,15 +54,32 @@ from PyQt5.QtWidgets import (
52
54
  QTableWidget,
53
55
  QVBoxLayout,
54
56
  QWidget,
57
+ QDateTimeEdit,
58
+ QTimeEdit,
59
+ QAbstractSpinBox,
60
+ QRadioButton,
61
+ QStackedWidget,
62
+ QFrame,
55
63
  )
64
+ from PySide6.QtGui import QFont, QTextCursor
56
65
 
57
66
  from . import config as cfg
58
- from . import duration_widget
59
67
  from . import version
68
+ from . import utilities as util
60
69
 
61
70
 
62
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
63
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
+ """
64
83
  message = QMessageBox()
65
84
  message.setWindowTitle(title)
66
85
  message.setText(text)
@@ -68,116 +87,64 @@ def MessageDialog(title: str, text: str, buttons: tuple) -> str:
68
87
  for button in buttons:
69
88
  message.addButton(button, QMessageBox.YesRole)
70
89
 
71
- # message.setWindowFlags(Qt.WindowStaysOnTopHint)
72
- message.exec_()
90
+ message.setWindowFlags(message.windowFlags() | Qt.WindowStaysOnTopHint)
91
+ message.exec()
73
92
  return message.clickedButton().text()
74
93
 
75
94
 
76
- '''
77
- def error_message_box(task, error_type, error_file_name, error_lineno):
78
- # do NOT use this function directly, use error_message function
79
- """
80
- show a critical dialog
81
-
82
- """
83
- QMessageBox.critical(None, cfg.programName, (f"BORIS version: {version.__version__}<br>"
84
- f"An error occured during the execution of <b>{task}</b>.<br>"
85
- f"Error: {error_type}<br>"
86
- f"in {error_file_name} "
87
- f"at line # {error_lineno}<br><br>"
88
- "to improve the software please report this problem at:<br>"
89
- '<a href="https://github.com/olivierfriard/BORIS/issues">'
90
- 'https://github.com/olivierfriard/BORIS/issues</a><br>'
91
- "or by email (See the About page on the BORIS web site.<br><br>"
92
- "Thank you for your collaboration!"))
93
- '''
94
-
95
-
96
- def error_message_box(error_traceback):
97
- # do NOT use this function directly, use error_message function
98
- """
99
- show a critical dialog
100
-
101
- """
102
- QMessageBox.critical(
103
- None,
104
- cfg.programName,
105
- (
106
- f"BORIS version: {version.__version__}<br><br>"
107
- f"<b>An error has occured</b>:<br>"
108
- f"{error_traceback}<br><br>"
109
- "to improve the software please report this problem at:<br>"
110
- '<a href="https://github.com/olivierfriard/BORIS/issues">'
111
- "https://github.com/olivierfriard/BORIS/issues</a><br>"
112
- "or by email (See the About page on the BORIS web site.<br><br>"
113
- "Thank you for your collaboration!"
114
- ),
115
- )
116
-
117
-
118
- def error_message() -> None:
119
- """
120
- Show details about the error in a message box
121
- write entry to log as CRITICAL
122
- """
123
-
124
- error_traceback = traceback.format_exc().replace("Traceback (most recent call last):", "").replace("\n", " ")
125
-
126
- logging.critical(error_traceback)
127
- error_message_box(error_traceback)
128
-
129
-
130
95
  def global_error_message(exception_type, exception_value, traceback_object):
131
96
  """
132
- global error management
133
- save error using loggin.critical and append error message to ~/boris.log
97
+ Global error management
98
+ save error using loggin.critical and stdout
134
99
  """
135
100
 
136
- error_text: str = (
137
- f"BORIS version: {version.__version__}\n"
138
- f"OS: {platform.uname().system} {platform.uname().release} {platform.uname().version}\n"
139
- f"CPU: {platform.uname().machine} {platform.uname().processor}\n"
140
- f"Python {platform.python_version()} ({'64-bit' if sys.maxsize > 2**32 else '32-bit'})\n"
141
- f"Qt {QT_VERSION_STR} - PyQt {PYQT_VERSION_STR}\n"
142
- f"{dt.datetime.now():%Y-%m-%d %H:%M}\n\n"
143
- )
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"
144
104
  error_text += "".join(traceback.format_exception(exception_type, exception_value, traceback_object))
145
105
 
106
+ # write to stdout
146
107
  logging.critical(error_text)
147
108
 
148
- # append to boris.log file
149
- with open(pl.Path("~").expanduser() / "boris.log", "a") as f_out:
150
- f_out.write(error_text + "\n")
151
- f_out.write("-" * 80 + "\n")
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'}")
152
115
 
153
116
  # copy to clipboard
154
117
  cb = QApplication.clipboard()
155
- cb.clear(mode=cb.Clipboard)
156
- cb.setText(error_text, mode=cb.Clipboard)
157
-
158
- error_text: str = error_text.replace("\r\n", "\n").replace("\n", "<br>")
118
+ cb.clear()
119
+ cb.setText(error_text)
159
120
 
160
121
  text: str = (
161
- f"<b>An error has occured</b>:<br><br>"
162
- f"{error_text}<br>"
163
- "to improve the software please report this problem at:<br>"
164
- '<a href="https://github.com/olivierfriard/BORIS/issues">'
165
- "https://github.com/olivierfriard/BORIS/issues</a><br>"
166
- "Please no screenshot, the error message was copied to the clipboard.<br><br>"
167
- "Thank you for your collaboration!"
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}"
168
128
  )
169
129
 
170
- errorbox = QMessageBox()
171
- errorbox.setWindowTitle("BORIS error occured")
172
- errorbox.setText(text)
173
- errorbox.setTextFormat(Qt.RichText)
174
- errorbox.setStandardButtons(QMessageBox.Abort)
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")
175
136
 
176
- _ = errorbox.addButton("Ignore and try to continue", QMessageBox.RejectRole)
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)
177
144
 
178
145
  ret = errorbox.exec_()
179
146
 
180
- if ret == QMessageBox.Abort:
147
+ if ret == 1: # Abort
181
148
  sys.exit(1)
182
149
 
183
150
 
@@ -194,6 +161,319 @@ class Info_widget(QWidget):
194
161
  self.setLayout(layout)
195
162
 
196
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
+
197
477
  class Video_overlay_dialog(QDialog):
198
478
  """
199
479
  dialog to ask image file and position for video overlay
@@ -203,28 +483,37 @@ class Video_overlay_dialog(QDialog):
203
483
  super().__init__()
204
484
 
205
485
  vlayout = QVBoxLayout()
206
- vlayout.addWidget(QLabel("File"))
486
+ self.cb_player = QComboBox()
487
+ vlayout.addWidget(self.cb_player)
488
+
207
489
  hbox = QHBoxLayout()
490
+ hbox.addWidget(QLabel("Image file"))
208
491
  self.le_file_path = QLineEdit()
209
492
  hbox.addWidget(self.le_file_path)
493
+ vlayout.addLayout(hbox)
494
+
495
+ hbox = QHBoxLayout()
210
496
  self.pb_browse = QPushButton("Browse", self, clicked=self.browse)
497
+ hbox.addItem(QSpacerItem(241, 20, QSizePolicy.Expanding, QSizePolicy.Minimum))
211
498
  hbox.addWidget(self.pb_browse)
212
499
  vlayout.addLayout(hbox)
213
500
 
214
- vlayout.addWidget(QLabel("Position: x,y"))
501
+ hbox = QHBoxLayout()
502
+ hbox.addWidget(QLabel("Position: x,y"))
215
503
  self.le_overlay_position = QLineEdit()
216
- vlayout.addWidget(self.le_overlay_position)
504
+ hbox.addWidget(self.le_overlay_position)
505
+ vlayout.addLayout(hbox)
217
506
 
218
- vlayout.addWidget(QLabel("Transparency %"))
507
+ hbox = QHBoxLayout()
508
+ hbox.addWidget(QLabel("Transparency %"))
219
509
  self.sb_overlay_transparency = QSpinBox()
220
510
  self.sb_overlay_transparency.setRange(0, 100)
221
511
  self.sb_overlay_transparency.setSingleStep(1)
222
- self.sb_overlay_transparency.setValue(0)
223
- vlayout.addWidget(self.sb_overlay_transparency)
224
- self.sb_overlay_transparency.setEnabled(False)
512
+ self.sb_overlay_transparency.setValue(90)
513
+ hbox.addWidget(self.sb_overlay_transparency)
514
+ vlayout.addLayout(hbox)
225
515
 
226
- self.cb_player = QComboBox()
227
- vlayout.addWidget(self.cb_player)
516
+ # self.sb_overlay_transparency.setEnabled(False)
228
517
 
229
518
  hbox = QHBoxLayout()
230
519
  self.pb_cancel = QPushButton("Cancel", self, clicked=self.reject)
@@ -239,8 +528,7 @@ class Video_overlay_dialog(QDialog):
239
528
  self.setLayout(vlayout)
240
529
 
241
530
  def browse(self):
242
- fn = QFileDialog().getOpenFileName(self, "Choose an image file", "", "PNG files (*.png);;All files (*)")
243
- file_name = fn[0] if type(fn) is tuple else fn
531
+ file_name, _ = QFileDialog.getOpenFileName(self, "Choose an image file", "", "PNG files (*.png);;All files (*)")
244
532
  if file_name:
245
533
  self.le_file_path.setText(file_name)
246
534
 
@@ -284,7 +572,8 @@ class Input_dialog(QDialog):
284
572
  dialog for user input. Elements can be:
285
573
  checkbox (cb): Tuple(str, str, bool)
286
574
  lineedit (le): Tuple(str, str)
287
- spinbox (sp)
575
+ spinbox (sb)
576
+ doubleSpinbox (dsb)
288
577
  items list (il)
289
578
 
290
579
  """
@@ -301,7 +590,6 @@ class Input_dialog(QDialog):
301
590
 
302
591
  self.elements: dict = {}
303
592
  for element in elements_list:
304
-
305
593
  if element[0] == "cb": # checkbox
306
594
  self.elements[element[1]] = QCheckBox(element[1])
307
595
  self.elements[element[1]].setChecked(element[2])
@@ -334,7 +622,7 @@ class Input_dialog(QDialog):
334
622
  # 3 - maximum value
335
623
  # 4 - step
336
624
  # 5 - initial value
337
- # 6 - number of decimas
625
+ # 6 - number of decimals
338
626
 
339
627
  lb = QLabel(element[1])
340
628
  hbox.addWidget(lb)
@@ -353,15 +641,15 @@ class Input_dialog(QDialog):
353
641
  self.elements[element[1]] = QComboBox()
354
642
  self.elements[element[1]].addItems([x[0] for x in element[2]]) # take first element of tuple
355
643
  try:
356
- self.elements[element[1]].setCurrentIndex(
357
- [idx for idx, x in enumerate(element[2]) if x[1] == "selected"][0]
358
- )
359
- except:
644
+ self.elements[element[1]].setCurrentIndex([idx for idx, x in enumerate(element[2]) if x[1] == "selected"][0])
645
+ except Exception:
360
646
  self.elements[element[1]].setCurrentIndex(0)
361
647
  hbox.addWidget(self.elements[element[1]])
362
648
 
363
649
  hbox2 = QHBoxLayout()
364
650
 
651
+ hbox2.addItem(QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum))
652
+
365
653
  self.pbCancel = QPushButton(cfg.CANCEL)
366
654
  self.pbCancel.clicked.connect(self.reject)
367
655
  hbox2.addWidget(self.pbCancel)
@@ -375,14 +663,13 @@ class Input_dialog(QDialog):
375
663
  self.setLayout(hbox)
376
664
 
377
665
 
378
- class DuplicateBehaviorCode(QDialog):
666
+ class Duplicate_items(QDialog):
379
667
  """
380
- let user show between behaviors that are coded by same key
668
+ let user show between behaviors/subjects that are coded by same key
381
669
  """
382
670
 
383
671
  def __init__(self, text, codes_list):
384
-
385
- super(DuplicateBehaviorCode, self).__init__()
672
+ super(Duplicate_items, self).__init__()
386
673
 
387
674
  self.setWindowTitle(cfg.programName)
388
675
  self.setWindowFlags(Qt.WindowStaysOnTopHint)
@@ -406,13 +693,17 @@ class DuplicateBehaviorCode(QDialog):
406
693
 
407
694
  Vlayout.addWidget(self.lw)
408
695
 
409
- pbCancel = QPushButton("Cancel")
410
- pbCancel.clicked.connect(self.reject)
411
- Vlayout.addWidget(pbCancel)
412
- pbOK = QPushButton("OK")
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)
413
703
  pbOK.setDefault(True)
414
- pbOK.clicked.connect(self.pbOK_clicked)
415
- Vlayout.addWidget(pbOK)
704
+ hlayout.addWidget(pbOK)
705
+
706
+ Vlayout.addLayout(hlayout)
416
707
 
417
708
  self.setLayout(Vlayout)
418
709
 
@@ -426,9 +717,8 @@ class DuplicateBehaviorCode(QDialog):
426
717
  """
427
718
  if self.lw.selectedItems():
428
719
  return self.lw.selectedItems()[0].text()
429
-
430
- def pbOK_clicked(self):
431
- self.accept()
720
+ else:
721
+ return None
432
722
 
433
723
 
434
724
  class ChooseObservationsToImport(QDialog):
@@ -437,7 +727,6 @@ class ChooseObservationsToImport(QDialog):
437
727
  """
438
728
 
439
729
  def __init__(self, text, observations_list):
440
-
441
730
  super(ChooseObservationsToImport, self).__init__()
442
731
 
443
732
  self.setWindowTitle(cfg.programName)
@@ -485,50 +774,13 @@ class ChooseObservationsToImport(QDialog):
485
774
  return [item.text() for item in self.lw.selectedItems()]
486
775
 
487
776
 
488
- class Ask_time(QDialog):
489
- """
490
- "Ask time" dialog box using duration widget in duration_widget module
491
- """
492
-
493
- def __init__(self, time_format):
494
- super().__init__()
495
- hbox = QVBoxLayout(self)
496
- self.label = QLabel()
497
- self.label.setText("Go to time")
498
- hbox.addWidget(self.label)
499
-
500
- self.time_widget = duration_widget.Duration_widget()
501
- if time_format == cfg.HHMMSS:
502
- self.time_widget.set_format_hhmmss()
503
- if time_format == cfg.S:
504
- self.time_widget.set_format_s()
505
-
506
- hbox.addWidget(self.time_widget)
507
-
508
- self.pbOK = QPushButton("OK")
509
- self.pbOK.clicked.connect(self.accept)
510
- self.pbOK.setDefault(True)
511
-
512
- self.pbCancel = QPushButton("Cancel")
513
- self.pbCancel.clicked.connect(self.reject)
514
-
515
- self.hbox2 = QHBoxLayout(self)
516
- self.hbox2.addItem(QSpacerItem(241, 20, QSizePolicy.Expanding, QSizePolicy.Minimum))
517
- self.hbox2.addWidget(self.pbCancel)
518
- self.hbox2.addWidget(self.pbOK)
519
- hbox.addLayout(self.hbox2)
520
- self.setLayout(hbox)
521
- self.setWindowTitle("Time")
522
-
523
-
524
777
  class FindInEvents(QWidget):
525
778
  """
526
779
  "find in events" dialog box
527
780
  """
528
781
 
529
- clickSignal = pyqtSignal(str)
530
-
531
- currentIdx = -1
782
+ clickSignal = Signal(str)
783
+ currentIdx: int = -1
532
784
 
533
785
  def __init__(self):
534
786
  super().__init__()
@@ -571,9 +823,10 @@ class FindInEvents(QWidget):
571
823
  hbox.addWidget(self.lb_message)
572
824
 
573
825
  hbox2 = QHBoxLayout()
826
+ hbox2.addItem(QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum))
574
827
  self.pbOK = QPushButton("Find")
575
828
  self.pbOK.clicked.connect(lambda: self.click("FIND"))
576
- self.pbCancel = QPushButton("Close")
829
+ self.pbCancel = QPushButton(cfg.CLOSE)
577
830
  self.pbCancel.clicked.connect(lambda: self.click("CLOSE"))
578
831
  hbox2.addWidget(self.pbCancel)
579
832
  hbox2.addWidget(self.pbOK)
@@ -590,10 +843,7 @@ class FindReplaceEvents(QWidget):
590
843
  "find replace events" dialog box
591
844
  """
592
845
 
593
- clickSignal = pyqtSignal(str)
594
- """
595
- sendEventSignal = pyqtSignal(QEvent)
596
- """
846
+ clickSignal = Signal(str)
597
847
 
598
848
  def __init__(self):
599
849
  super().__init__()
@@ -602,21 +852,9 @@ class FindReplaceEvents(QWidget):
602
852
 
603
853
  hbox = QVBoxLayout()
604
854
 
605
- self.cbSubject = QCheckBox("Subject")
606
- self.cbSubject.setChecked(False)
607
- hbox.addWidget(self.cbSubject)
608
-
609
- self.cbBehavior = QCheckBox("Behavior")
610
- self.cbBehavior.setChecked(False)
611
- hbox.addWidget(self.cbBehavior)
612
-
613
- self.cbModifier = QCheckBox("Modifiers")
614
- self.cbModifier.setChecked(False)
615
- hbox.addWidget(self.cbModifier)
616
-
617
- self.cbComment = QCheckBox("Comment")
618
- self.cbComment.setChecked(False)
619
- hbox.addWidget(self.cbComment)
855
+ self.combo_fields = QComboBox()
856
+ self.combo_fields.addItems(("Choose a field", "Subject", "Behavior", "Modifiers", "Comment"))
857
+ hbox.addWidget(self.combo_fields)
620
858
 
621
859
  self.lbFind = QLabel("Find")
622
860
  hbox.addWidget(self.lbFind)
@@ -630,7 +868,7 @@ class FindReplaceEvents(QWidget):
630
868
  self.replaceText = QLineEdit()
631
869
  hbox.addWidget(self.replaceText)
632
870
 
633
- self.cbFindInSelectedEvents = QCheckBox("Find/Replace in selected events")
871
+ self.cbFindInSelectedEvents = QCheckBox("Find/Replace only in selected events")
634
872
  self.cbFindInSelectedEvents.setChecked(False)
635
873
  hbox.addWidget(self.cbFindInSelectedEvents)
636
874
 
@@ -640,6 +878,8 @@ class FindReplaceEvents(QWidget):
640
878
 
641
879
  hbox2 = QHBoxLayout()
642
880
 
881
+ hbox2.addItem(QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum))
882
+
643
883
  self.pbCancel = QPushButton(cfg.CANCEL, clicked=lambda: self.click("CANCEL"))
644
884
  hbox2.addWidget(self.pbCancel)
645
885
 
@@ -650,68 +890,76 @@ class FindReplaceEvents(QWidget):
650
890
  hbox2.addWidget(self.pbFindReplaceAll)
651
891
 
652
892
  hbox.addLayout(hbox2)
893
+
894
+ self.lb_results = QLabel(" ")
895
+ hbox.addWidget(self.lb_results)
896
+
653
897
  self.setLayout(hbox)
654
898
 
655
899
  def click(self, msg):
656
900
  self.clickSignal.emit(msg)
657
901
 
658
902
 
659
- '''
660
- class explore_project_dialog(QDialog):
903
+ class Results_dialog(QDialog):
661
904
  """
662
- "explore project" dialog box
905
+ widget for visualizing text output in HTML
663
906
  """
664
907
 
665
908
  def __init__(self):
666
909
  super().__init__()
667
910
 
668
- self.setWindowTitle("Explore project")
669
-
670
- hbox = QVBoxLayout()
671
-
672
- hbox.addWidget(QLabel("Search in all observations"))
911
+ self.dataset = False
673
912
 
674
- self.lb_subject = QLabel("Subject")
675
- hbox.addWidget(self.lb_subject)
676
- self.find_subject = QLineEdit()
677
- hbox.addWidget(self.find_subject)
913
+ self.setWindowTitle("")
678
914
 
679
- self.lb_behav = QLabel("Behaviors")
680
- hbox.addWidget(self.lb_behav)
681
- self.find_behavior = QLineEdit()
682
- hbox.addWidget(self.find_behavior)
915
+ hbox = QVBoxLayout()
683
916
 
684
- self.lb_modifier = QLabel("Modifier")
685
- hbox.addWidget(self.lb_modifier)
686
- self.find_modifier = QLineEdit()
687
- hbox.addWidget(self.find_modifier)
917
+ self.lb = QLabel("")
918
+ hbox.addWidget(self.lb)
688
919
 
689
- self.lb_comment = QLabel("Comment")
690
- hbox.addWidget(self.lb_comment)
691
- self.find_comment = QLineEdit()
692
- hbox.addWidget(self.find_comment)
920
+ self.ptText = QPlainTextEdit()
921
+ self.ptText.setReadOnly(True)
922
+ hbox.addWidget(self.ptText)
693
923
 
694
- self.cb_case_sensitive = QCheckBox("Case sensitive")
695
- self.cb_case_sensitive.setChecked(False)
696
- hbox.addWidget(self.cb_case_sensitive)
924
+ hbox2 = QHBoxLayout()
925
+ hbox2.addItem(QSpacerItem(241, 20, QSizePolicy.Expanding, QSizePolicy.Minimum))
697
926
 
698
- self.lb_message = QLabel()
699
- hbox.addWidget(self.lb_message)
927
+ self.pbSave = QPushButton("Save results", clicked=self.save_results)
928
+ hbox2.addWidget(self.pbSave)
700
929
 
701
- hbox2 = QHBoxLayout()
702
- self.pbOK = QPushButton("Find")
703
- self.pbOK.clicked.connect(self.accept)
704
- self.pbCancel = QPushButton("Cancel")
705
- self.pbCancel.clicked.connect(self.reject)
930
+ self.pbCancel = QPushButton(cfg.CANCEL, clicked=self.reject)
706
931
  hbox2.addWidget(self.pbCancel)
932
+ self.pbCancel.setVisible(False)
933
+
934
+ self.pbOK = QPushButton(cfg.OK, clicked=self.accept)
707
935
  hbox2.addWidget(self.pbOK)
708
- hbox.addLayout(hbox2)
709
936
 
937
+ hbox.addLayout(hbox2)
710
938
  self.setLayout(hbox)
711
- '''
712
939
 
940
+ self.resize(800, 640)
713
941
 
714
- class Results_dialog(QDialog):
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):
715
963
  """
716
964
  widget for visualizing text output
717
965
  """
@@ -733,32 +981,35 @@ class Results_dialog(QDialog):
733
981
  hbox.addWidget(self.ptText)
734
982
 
735
983
  hbox2 = QHBoxLayout()
984
+ hbox2.addItem(QSpacerItem(241, 20, QSizePolicy.Expanding, QSizePolicy.Minimum))
985
+
736
986
  self.pbSave = QPushButton("Save results", clicked=self.save_results)
737
987
  hbox2.addWidget(self.pbSave)
738
988
 
739
- hbox2.addItem(QSpacerItem(241, 20, QSizePolicy.Expanding, QSizePolicy.Minimum))
989
+ self.pb1 = QPushButton("1", clicked=lambda: self.done_(1))
990
+ hbox2.addWidget(self.pb1)
740
991
 
741
- self.pbCancel = QPushButton(cfg.CANCEL, clicked=self.reject)
742
- hbox2.addWidget(self.pbCancel)
743
- self.pbCancel.setVisible(False)
992
+ self.pb2 = QPushButton("2", clicked=lambda: self.done_(2))
993
+ hbox2.addWidget(self.pb2)
744
994
 
745
- self.pbOK = QPushButton(cfg.OK)
746
- self.pbOK.clicked.connect(self.accept)
747
- hbox2.addWidget(self.pbOK)
995
+ self.pb3 = QPushButton("3", clicked=lambda: self.done_(3))
996
+ hbox2.addWidget(self.pb3)
748
997
 
749
998
  hbox.addLayout(hbox2)
750
999
  self.setLayout(hbox)
751
1000
 
752
1001
  self.resize(800, 640)
753
1002
 
1003
+ def done_(self, status):
1004
+ self.done(status)
1005
+
754
1006
  def save_results(self):
755
1007
  """
756
1008
  save content of self.ptText
757
1009
  """
758
1010
 
759
1011
  if not self.dataset:
760
- fn = QFileDialog().getSaveFileName(self, "Save results", "", "Text files (*.txt *.tsv);;All files (*)")
761
- file_name = fn[0] if type(fn) is tuple else fn
1012
+ file_name, _ = QFileDialog().getSaveFileName(self, "Save results", "", "Text files (*.txt *.tsv);;All files (*)")
762
1013
 
763
1014
  if not file_name:
764
1015
  return
@@ -772,9 +1023,9 @@ class Results_dialog(QDialog):
772
1023
  self.done(cfg.SAVE_DATASET)
773
1024
 
774
1025
 
775
- class View_data_head(QDialog):
1026
+ class View_data(QDialog):
776
1027
  """
777
- widget for visualizing first rows of data file
1028
+ widget for visualizing rows of data file
778
1029
  """
779
1030
 
780
1031
  def __init__(self):
@@ -788,8 +1039,18 @@ class View_data_head(QDialog):
788
1039
  vbox.addWidget(self.lb)
789
1040
 
790
1041
  self.tw = QTableWidget()
1042
+ self.tw.verticalHeader().hide()
791
1043
  vbox.addWidget(self.tw)
792
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
+
793
1054
  self.label = QLabel("Enter the column indices to plot (time, value) separated by comma (,)")
794
1055
  vbox.addWidget(self.label)
795
1056
 
@@ -798,17 +1059,19 @@ class View_data_head(QDialog):
798
1059
 
799
1060
  hbox2 = QHBoxLayout()
800
1061
 
801
- self.pbCancel = QPushButton("Cancel", clicked=self.reject)
1062
+ hbox2.addItem(QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum))
1063
+
1064
+ self.pbCancel = QPushButton(cfg.CANCEL, clicked=self.reject)
802
1065
  hbox2.addWidget(self.pbCancel)
803
1066
 
804
- self.pbOK = QPushButton("OK", clicked=self.accept)
1067
+ self.pbOK = QPushButton(cfg.OK, clicked=self.accept)
805
1068
  hbox2.addWidget(self.pbOK)
806
1069
 
807
1070
  vbox.addLayout(hbox2)
808
1071
 
809
1072
  self.setLayout(vbox)
810
1073
 
811
- self.resize(540, 640)
1074
+ self.resize(800, 640)
812
1075
 
813
1076
 
814
1077
  class View_explore_project_results(QWidget):
@@ -816,7 +1079,7 @@ class View_explore_project_results(QWidget):
816
1079
  widget for visualizing results of explore project
817
1080
  """
818
1081
 
819
- double_click_signal = pyqtSignal(str, int)
1082
+ double_click_signal = Signal(str, int)
820
1083
 
821
1084
  def __init__(self):
822
1085
  super().__init__()
@@ -834,14 +1097,12 @@ class View_explore_project_results(QWidget):
834
1097
  vbox.addWidget(self.tw)
835
1098
 
836
1099
  hbox2 = QHBoxLayout()
837
- hbox2.addWidget(QPushButton("OK", clicked=self.close))
1100
+ hbox2.addItem(QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum))
1101
+ hbox2.addWidget(QPushButton(cfg.CLOSE, clicked=self.close))
838
1102
 
839
1103
  vbox.addLayout(hbox2)
840
1104
 
841
1105
  self.setLayout(vbox)
842
1106
 
843
- # self.resize(540, 640)
844
-
845
1107
  def tw_cellDoubleClicked(self, r, c):
846
-
847
1108
  self.double_click_signal.emit(self.tw.item(r, 0).text(), int(self.tw.item(r, 1).text()))