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

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

Potentially problematic release.


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

Files changed (128) hide show
  1. boris/__init__.py +1 -1
  2. boris/__main__.py +1 -1
  3. boris/about.py +28 -39
  4. boris/add_modifier.py +122 -109
  5. boris/add_modifier_ui.py +239 -135
  6. boris/advanced_event_filtering.py +81 -45
  7. boris/analysis_plugins/__init__.py +0 -0
  8. boris/analysis_plugins/_latency.py +59 -0
  9. boris/analysis_plugins/irr_cohen_kappa.py +109 -0
  10. boris/analysis_plugins/irr_cohen_kappa_with_modifiers.py +112 -0
  11. boris/analysis_plugins/irr_weighted_cohen_kappa.py +157 -0
  12. boris/analysis_plugins/irr_weighted_cohen_kappa_with_modifiers.py +162 -0
  13. boris/analysis_plugins/list_of_dataframe_columns.py +22 -0
  14. boris/analysis_plugins/number_of_occurences.py +22 -0
  15. boris/analysis_plugins/number_of_occurences_by_independent_variable.py +54 -0
  16. boris/analysis_plugins/time_budget.py +61 -0
  17. boris/behav_coding_map_creator.py +228 -229
  18. boris/behavior_binary_table.py +33 -50
  19. boris/behaviors_coding_map.py +17 -18
  20. boris/boris_cli.py +6 -25
  21. boris/cmd_arguments.py +12 -1
  22. boris/coding_pad.py +42 -49
  23. boris/config.py +141 -65
  24. boris/config_file.py +58 -67
  25. boris/connections.py +107 -61
  26. boris/converters.py +13 -37
  27. boris/converters_ui.py +187 -110
  28. boris/cooccurence.py +250 -0
  29. boris/core.py +2373 -1786
  30. boris/core_qrc.py +15895 -10743
  31. boris/core_ui.py +943 -798
  32. boris/db_functions.py +17 -42
  33. boris/dev.py +109 -8
  34. boris/dialog.py +482 -236
  35. boris/duration_widget.py +9 -14
  36. boris/edit_event.py +61 -31
  37. boris/edit_event_ui.py +208 -97
  38. boris/event_operations.py +408 -293
  39. boris/events_cursor.py +25 -17
  40. boris/events_snapshots.py +36 -82
  41. boris/exclusion_matrix.py +4 -9
  42. boris/export_events.py +184 -223
  43. boris/export_observation.py +74 -100
  44. boris/external_processes.py +123 -98
  45. boris/geometric_measurement.py +644 -290
  46. boris/gui_utilities.py +91 -14
  47. boris/image_overlay.py +4 -4
  48. boris/import_observations.py +190 -98
  49. boris/ipc_mpv.py +325 -0
  50. boris/irr.py +20 -57
  51. boris/latency.py +31 -24
  52. boris/measurement_widget.py +14 -18
  53. boris/media_file.py +17 -19
  54. boris/menu_options.py +17 -6
  55. boris/modifier_coding_map_creator.py +1013 -0
  56. boris/modifiers_coding_map.py +7 -9
  57. boris/mpv.py +1 -0
  58. boris/mpv2.py +732 -705
  59. boris/observation.py +533 -221
  60. boris/observation_operations.py +1025 -390
  61. boris/observation_ui.py +572 -362
  62. boris/observations_list.py +71 -53
  63. boris/otx_parser.py +74 -68
  64. boris/param_panel.py +31 -16
  65. boris/param_panel_ui.py +254 -138
  66. boris/player_dock_widget.py +90 -60
  67. boris/plot_data_module.py +25 -33
  68. boris/plot_events.py +127 -90
  69. boris/plot_events_rt.py +17 -31
  70. boris/plot_spectrogram_rt.py +95 -30
  71. boris/plot_waveform_rt.py +32 -21
  72. boris/plugins.py +431 -0
  73. boris/portion/__init__.py +18 -8
  74. boris/portion/const.py +35 -18
  75. boris/portion/dict.py +5 -5
  76. boris/portion/func.py +2 -2
  77. boris/portion/interval.py +21 -41
  78. boris/portion/io.py +41 -32
  79. boris/preferences.py +306 -83
  80. boris/preferences_ui.py +684 -227
  81. boris/project.py +448 -293
  82. boris/project_functions.py +671 -238
  83. boris/project_import_export.py +213 -222
  84. boris/project_ui.py +674 -438
  85. boris/qrc_boris.py +6 -3
  86. boris/qrc_boris5.py +6 -3
  87. boris/select_modifiers.py +74 -48
  88. boris/select_observations.py +20 -198
  89. boris/select_subj_behav.py +67 -39
  90. boris/state_events.py +52 -35
  91. boris/subjects_pad.py +6 -9
  92. boris/synthetic_time_budget.py +45 -28
  93. boris/time_budget_functions.py +171 -171
  94. boris/time_budget_widget.py +84 -114
  95. boris/transitions.py +41 -47
  96. boris/utilities.py +627 -236
  97. boris/version.py +3 -3
  98. boris/video_equalizer.py +16 -14
  99. boris/video_equalizer_ui.py +199 -130
  100. boris/video_operations.py +95 -29
  101. boris/view_df.py +104 -0
  102. boris/view_df_ui.py +75 -0
  103. boris/write_event.py +538 -0
  104. boris_behav_obs-9.7.6.dist-info/METADATA +139 -0
  105. boris_behav_obs-9.7.6.dist-info/RECORD +109 -0
  106. {boris_behav_obs-8.12.dist-info → boris_behav_obs-9.7.6.dist-info}/WHEEL +1 -1
  107. boris_behav_obs-9.7.6.dist-info/entry_points.txt +2 -0
  108. boris/README.TXT +0 -22
  109. boris/add_modifier.ui +0 -323
  110. boris/converters.ui +0 -289
  111. boris/core.qrc +0 -36
  112. boris/core.ui +0 -1556
  113. boris/edit_event.ui +0 -233
  114. boris/icons/logo_eye.ico +0 -0
  115. boris/map_creator.py +0 -850
  116. boris/observation.ui +0 -814
  117. boris/param_panel.ui +0 -379
  118. boris/preferences.ui +0 -537
  119. boris/project.ui +0 -1069
  120. boris/project_server.py +0 -236
  121. boris/vlc.py +0 -10343
  122. boris/vlc_local.py +0 -90
  123. boris_behav_obs-8.12.dist-info/LICENSE.TXT +0 -674
  124. boris_behav_obs-8.12.dist-info/METADATA +0 -128
  125. boris_behav_obs-8.12.dist-info/RECORD +0 -108
  126. boris_behav_obs-8.12.dist-info/entry_points.txt +0 -3
  127. {boris → boris_behav_obs-9.7.6.dist-info/licenses}/LICENSE.TXT +0 -0
  128. {boris_behav_obs-8.12.dist-info → boris_behav_obs-9.7.6.dist-info}/top_level.txt +0 -0
boris/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,120 +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
109
+ # write to $HOME/boris_error.log
149
110
  try:
150
- with open(pl.Path.home() / "boris.log", "a") as f_out:
151
- f_out.write(error_text + "\n")
152
- f_out.write("-" * 80 + "\n")
111
+ with open(pl.Path.home() / "boris_error.log", "w") as f_error:
112
+ f_error.write(error_text)
153
113
  except Exception:
154
- print(f"Cannot write to {pl.Path.home() / 'boris.log'} file")
155
- logging.critical(f"Cannot write to {pl.Path.home() / 'boris.log'} file")
114
+ logging.critical(f"Impossible to write to {pl.Path.home() / 'boris_error.log'}")
156
115
 
157
116
  # copy to clipboard
158
117
  cb = QApplication.clipboard()
159
- cb.clear(mode=cb.Clipboard)
160
- cb.setText(error_text, mode=cb.Clipboard)
161
-
162
- error_text: str = error_text.replace("\r\n", "\n").replace("\n", "<br>")
118
+ cb.clear()
119
+ cb.setText(error_text)
163
120
 
164
121
  text: str = (
165
- f"<b>An error has occured</b>:<br><br>"
166
- f"{error_text}<br>"
167
- "to improve the software please report this problem at:<br>"
168
- '<a href="https://github.com/olivierfriard/BORIS/issues">'
169
- "https://github.com/olivierfriard/BORIS/issues</a><br>"
170
- "Please no screenshot, the error message was copied to the clipboard.<br><br>"
171
- "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}"
172
128
  )
173
129
 
174
- errorbox = QMessageBox()
175
- errorbox.setWindowTitle("BORIS error occured")
176
- errorbox.setText(text)
177
- errorbox.setTextFormat(Qt.RichText)
178
- 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")
179
136
 
180
- _ = 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)
181
144
 
182
145
  ret = errorbox.exec_()
183
146
 
184
- if ret == QMessageBox.Abort:
147
+ if ret == 1: # Abort
185
148
  sys.exit(1)
186
149
 
187
150
 
@@ -198,6 +161,319 @@ class Info_widget(QWidget):
198
161
  self.setLayout(layout)
199
162
 
200
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
+
201
477
  class Video_overlay_dialog(QDialog):
202
478
  """
203
479
  dialog to ask image file and position for video overlay
@@ -252,8 +528,7 @@ class Video_overlay_dialog(QDialog):
252
528
  self.setLayout(vlayout)
253
529
 
254
530
  def browse(self):
255
- fn = QFileDialog().getOpenFileName(self, "Choose an image file", "", "PNG files (*.png);;All files (*)")
256
- 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 (*)")
257
532
  if file_name:
258
533
  self.le_file_path.setText(file_name)
259
534
 
@@ -297,7 +572,8 @@ class Input_dialog(QDialog):
297
572
  dialog for user input. Elements can be:
298
573
  checkbox (cb): Tuple(str, str, bool)
299
574
  lineedit (le): Tuple(str, str)
300
- spinbox (sp)
575
+ spinbox (sb)
576
+ doubleSpinbox (dsb)
301
577
  items list (il)
302
578
 
303
579
  """
@@ -314,7 +590,6 @@ class Input_dialog(QDialog):
314
590
 
315
591
  self.elements: dict = {}
316
592
  for element in elements_list:
317
-
318
593
  if element[0] == "cb": # checkbox
319
594
  self.elements[element[1]] = QCheckBox(element[1])
320
595
  self.elements[element[1]].setChecked(element[2])
@@ -347,7 +622,7 @@ class Input_dialog(QDialog):
347
622
  # 3 - maximum value
348
623
  # 4 - step
349
624
  # 5 - initial value
350
- # 6 - number of decimas
625
+ # 6 - number of decimals
351
626
 
352
627
  lb = QLabel(element[1])
353
628
  hbox.addWidget(lb)
@@ -366,15 +641,15 @@ class Input_dialog(QDialog):
366
641
  self.elements[element[1]] = QComboBox()
367
642
  self.elements[element[1]].addItems([x[0] for x in element[2]]) # take first element of tuple
368
643
  try:
369
- self.elements[element[1]].setCurrentIndex(
370
- [idx for idx, x in enumerate(element[2]) if x[1] == "selected"][0]
371
- )
372
- except:
644
+ self.elements[element[1]].setCurrentIndex([idx for idx, x in enumerate(element[2]) if x[1] == "selected"][0])
645
+ except Exception:
373
646
  self.elements[element[1]].setCurrentIndex(0)
374
647
  hbox.addWidget(self.elements[element[1]])
375
648
 
376
649
  hbox2 = QHBoxLayout()
377
650
 
651
+ hbox2.addItem(QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum))
652
+
378
653
  self.pbCancel = QPushButton(cfg.CANCEL)
379
654
  self.pbCancel.clicked.connect(self.reject)
380
655
  hbox2.addWidget(self.pbCancel)
@@ -388,14 +663,13 @@ class Input_dialog(QDialog):
388
663
  self.setLayout(hbox)
389
664
 
390
665
 
391
- class DuplicateBehaviorCode(QDialog):
666
+ class Duplicate_items(QDialog):
392
667
  """
393
- let user show between behaviors that are coded by same key
668
+ let user show between behaviors/subjects that are coded by same key
394
669
  """
395
670
 
396
671
  def __init__(self, text, codes_list):
397
-
398
- super(DuplicateBehaviorCode, self).__init__()
672
+ super(Duplicate_items, self).__init__()
399
673
 
400
674
  self.setWindowTitle(cfg.programName)
401
675
  self.setWindowFlags(Qt.WindowStaysOnTopHint)
@@ -419,13 +693,17 @@ class DuplicateBehaviorCode(QDialog):
419
693
 
420
694
  Vlayout.addWidget(self.lw)
421
695
 
422
- pbCancel = QPushButton("Cancel")
423
- pbCancel.clicked.connect(self.reject)
424
- Vlayout.addWidget(pbCancel)
425
- 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)
426
703
  pbOK.setDefault(True)
427
- pbOK.clicked.connect(self.pbOK_clicked)
428
- Vlayout.addWidget(pbOK)
704
+ hlayout.addWidget(pbOK)
705
+
706
+ Vlayout.addLayout(hlayout)
429
707
 
430
708
  self.setLayout(Vlayout)
431
709
 
@@ -439,9 +717,8 @@ class DuplicateBehaviorCode(QDialog):
439
717
  """
440
718
  if self.lw.selectedItems():
441
719
  return self.lw.selectedItems()[0].text()
442
-
443
- def pbOK_clicked(self):
444
- self.accept()
720
+ else:
721
+ return None
445
722
 
446
723
 
447
724
  class ChooseObservationsToImport(QDialog):
@@ -450,7 +727,6 @@ class ChooseObservationsToImport(QDialog):
450
727
  """
451
728
 
452
729
  def __init__(self, text, observations_list):
453
-
454
730
  super(ChooseObservationsToImport, self).__init__()
455
731
 
456
732
  self.setWindowTitle(cfg.programName)
@@ -498,50 +774,13 @@ class ChooseObservationsToImport(QDialog):
498
774
  return [item.text() for item in self.lw.selectedItems()]
499
775
 
500
776
 
501
- class Ask_time(QDialog):
502
- """
503
- "Ask time" dialog box using duration widget in duration_widget module
504
- """
505
-
506
- def __init__(self, time_format):
507
- super().__init__()
508
- hbox = QVBoxLayout(self)
509
- self.label = QLabel()
510
- self.label.setText("Go to time")
511
- hbox.addWidget(self.label)
512
-
513
- self.time_widget = duration_widget.Duration_widget()
514
- if time_format == cfg.HHMMSS:
515
- self.time_widget.set_format_hhmmss()
516
- if time_format == cfg.S:
517
- self.time_widget.set_format_s()
518
-
519
- hbox.addWidget(self.time_widget)
520
-
521
- self.pbOK = QPushButton("OK")
522
- self.pbOK.clicked.connect(self.accept)
523
- self.pbOK.setDefault(True)
524
-
525
- self.pbCancel = QPushButton("Cancel")
526
- self.pbCancel.clicked.connect(self.reject)
527
-
528
- self.hbox2 = QHBoxLayout(self)
529
- self.hbox2.addItem(QSpacerItem(241, 20, QSizePolicy.Expanding, QSizePolicy.Minimum))
530
- self.hbox2.addWidget(self.pbCancel)
531
- self.hbox2.addWidget(self.pbOK)
532
- hbox.addLayout(self.hbox2)
533
- self.setLayout(hbox)
534
- self.setWindowTitle("Time")
535
-
536
-
537
777
  class FindInEvents(QWidget):
538
778
  """
539
779
  "find in events" dialog box
540
780
  """
541
781
 
542
- clickSignal = pyqtSignal(str)
543
-
544
- currentIdx = -1
782
+ clickSignal = Signal(str)
783
+ currentIdx: int = -1
545
784
 
546
785
  def __init__(self):
547
786
  super().__init__()
@@ -584,9 +823,10 @@ class FindInEvents(QWidget):
584
823
  hbox.addWidget(self.lb_message)
585
824
 
586
825
  hbox2 = QHBoxLayout()
826
+ hbox2.addItem(QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum))
587
827
  self.pbOK = QPushButton("Find")
588
828
  self.pbOK.clicked.connect(lambda: self.click("FIND"))
589
- self.pbCancel = QPushButton("Close")
829
+ self.pbCancel = QPushButton(cfg.CLOSE)
590
830
  self.pbCancel.clicked.connect(lambda: self.click("CLOSE"))
591
831
  hbox2.addWidget(self.pbCancel)
592
832
  hbox2.addWidget(self.pbOK)
@@ -603,10 +843,7 @@ class FindReplaceEvents(QWidget):
603
843
  "find replace events" dialog box
604
844
  """
605
845
 
606
- clickSignal = pyqtSignal(str)
607
- """
608
- sendEventSignal = pyqtSignal(QEvent)
609
- """
846
+ clickSignal = Signal(str)
610
847
 
611
848
  def __init__(self):
612
849
  super().__init__()
@@ -615,21 +852,9 @@ class FindReplaceEvents(QWidget):
615
852
 
616
853
  hbox = QVBoxLayout()
617
854
 
618
- self.cbSubject = QCheckBox("Subject")
619
- self.cbSubject.setChecked(False)
620
- hbox.addWidget(self.cbSubject)
621
-
622
- self.cbBehavior = QCheckBox("Behavior")
623
- self.cbBehavior.setChecked(False)
624
- hbox.addWidget(self.cbBehavior)
625
-
626
- self.cbModifier = QCheckBox("Modifiers")
627
- self.cbModifier.setChecked(False)
628
- hbox.addWidget(self.cbModifier)
629
-
630
- self.cbComment = QCheckBox("Comment")
631
- self.cbComment.setChecked(False)
632
- 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)
633
858
 
634
859
  self.lbFind = QLabel("Find")
635
860
  hbox.addWidget(self.lbFind)
@@ -643,7 +868,7 @@ class FindReplaceEvents(QWidget):
643
868
  self.replaceText = QLineEdit()
644
869
  hbox.addWidget(self.replaceText)
645
870
 
646
- self.cbFindInSelectedEvents = QCheckBox("Find/Replace in selected events")
871
+ self.cbFindInSelectedEvents = QCheckBox("Find/Replace only in selected events")
647
872
  self.cbFindInSelectedEvents.setChecked(False)
648
873
  hbox.addWidget(self.cbFindInSelectedEvents)
649
874
 
@@ -653,6 +878,8 @@ class FindReplaceEvents(QWidget):
653
878
 
654
879
  hbox2 = QHBoxLayout()
655
880
 
881
+ hbox2.addItem(QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum))
882
+
656
883
  self.pbCancel = QPushButton(cfg.CANCEL, clicked=lambda: self.click("CANCEL"))
657
884
  hbox2.addWidget(self.pbCancel)
658
885
 
@@ -663,68 +890,76 @@ class FindReplaceEvents(QWidget):
663
890
  hbox2.addWidget(self.pbFindReplaceAll)
664
891
 
665
892
  hbox.addLayout(hbox2)
893
+
894
+ self.lb_results = QLabel(" ")
895
+ hbox.addWidget(self.lb_results)
896
+
666
897
  self.setLayout(hbox)
667
898
 
668
899
  def click(self, msg):
669
900
  self.clickSignal.emit(msg)
670
901
 
671
902
 
672
- '''
673
- class explore_project_dialog(QDialog):
903
+ class Results_dialog(QDialog):
674
904
  """
675
- "explore project" dialog box
905
+ widget for visualizing text output in HTML
676
906
  """
677
907
 
678
908
  def __init__(self):
679
909
  super().__init__()
680
910
 
681
- self.setWindowTitle("Explore project")
682
-
683
- hbox = QVBoxLayout()
684
-
685
- hbox.addWidget(QLabel("Search in all observations"))
911
+ self.dataset = False
686
912
 
687
- self.lb_subject = QLabel("Subject")
688
- hbox.addWidget(self.lb_subject)
689
- self.find_subject = QLineEdit()
690
- hbox.addWidget(self.find_subject)
913
+ self.setWindowTitle("")
691
914
 
692
- self.lb_behav = QLabel("Behaviors")
693
- hbox.addWidget(self.lb_behav)
694
- self.find_behavior = QLineEdit()
695
- hbox.addWidget(self.find_behavior)
915
+ hbox = QVBoxLayout()
696
916
 
697
- self.lb_modifier = QLabel("Modifier")
698
- hbox.addWidget(self.lb_modifier)
699
- self.find_modifier = QLineEdit()
700
- hbox.addWidget(self.find_modifier)
917
+ self.lb = QLabel("")
918
+ hbox.addWidget(self.lb)
701
919
 
702
- self.lb_comment = QLabel("Comment")
703
- hbox.addWidget(self.lb_comment)
704
- self.find_comment = QLineEdit()
705
- hbox.addWidget(self.find_comment)
920
+ self.ptText = QPlainTextEdit()
921
+ self.ptText.setReadOnly(True)
922
+ hbox.addWidget(self.ptText)
706
923
 
707
- self.cb_case_sensitive = QCheckBox("Case sensitive")
708
- self.cb_case_sensitive.setChecked(False)
709
- hbox.addWidget(self.cb_case_sensitive)
924
+ hbox2 = QHBoxLayout()
925
+ hbox2.addItem(QSpacerItem(241, 20, QSizePolicy.Expanding, QSizePolicy.Minimum))
710
926
 
711
- self.lb_message = QLabel()
712
- hbox.addWidget(self.lb_message)
927
+ self.pbSave = QPushButton("Save results", clicked=self.save_results)
928
+ hbox2.addWidget(self.pbSave)
713
929
 
714
- hbox2 = QHBoxLayout()
715
- self.pbOK = QPushButton("Find")
716
- self.pbOK.clicked.connect(self.accept)
717
- self.pbCancel = QPushButton("Cancel")
718
- self.pbCancel.clicked.connect(self.reject)
930
+ self.pbCancel = QPushButton(cfg.CANCEL, clicked=self.reject)
719
931
  hbox2.addWidget(self.pbCancel)
932
+ self.pbCancel.setVisible(False)
933
+
934
+ self.pbOK = QPushButton(cfg.OK, clicked=self.accept)
720
935
  hbox2.addWidget(self.pbOK)
721
- hbox.addLayout(hbox2)
722
936
 
937
+ hbox.addLayout(hbox2)
723
938
  self.setLayout(hbox)
724
- '''
725
939
 
940
+ self.resize(800, 640)
726
941
 
727
- 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):
728
963
  """
729
964
  widget for visualizing text output
730
965
  """
@@ -746,32 +981,35 @@ class Results_dialog(QDialog):
746
981
  hbox.addWidget(self.ptText)
747
982
 
748
983
  hbox2 = QHBoxLayout()
984
+ hbox2.addItem(QSpacerItem(241, 20, QSizePolicy.Expanding, QSizePolicy.Minimum))
985
+
749
986
  self.pbSave = QPushButton("Save results", clicked=self.save_results)
750
987
  hbox2.addWidget(self.pbSave)
751
988
 
752
- hbox2.addItem(QSpacerItem(241, 20, QSizePolicy.Expanding, QSizePolicy.Minimum))
989
+ self.pb1 = QPushButton("1", clicked=lambda: self.done_(1))
990
+ hbox2.addWidget(self.pb1)
753
991
 
754
- self.pbCancel = QPushButton(cfg.CANCEL, clicked=self.reject)
755
- hbox2.addWidget(self.pbCancel)
756
- self.pbCancel.setVisible(False)
992
+ self.pb2 = QPushButton("2", clicked=lambda: self.done_(2))
993
+ hbox2.addWidget(self.pb2)
757
994
 
758
- self.pbOK = QPushButton(cfg.OK)
759
- self.pbOK.clicked.connect(self.accept)
760
- hbox2.addWidget(self.pbOK)
995
+ self.pb3 = QPushButton("3", clicked=lambda: self.done_(3))
996
+ hbox2.addWidget(self.pb3)
761
997
 
762
998
  hbox.addLayout(hbox2)
763
999
  self.setLayout(hbox)
764
1000
 
765
1001
  self.resize(800, 640)
766
1002
 
1003
+ def done_(self, status):
1004
+ self.done(status)
1005
+
767
1006
  def save_results(self):
768
1007
  """
769
1008
  save content of self.ptText
770
1009
  """
771
1010
 
772
1011
  if not self.dataset:
773
- fn = QFileDialog().getSaveFileName(self, "Save results", "", "Text files (*.txt *.tsv);;All files (*)")
774
- 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 (*)")
775
1013
 
776
1014
  if not file_name:
777
1015
  return
@@ -785,7 +1023,7 @@ class Results_dialog(QDialog):
785
1023
  self.done(cfg.SAVE_DATASET)
786
1024
 
787
1025
 
788
- class View_data_head(QDialog):
1026
+ class View_data(QDialog):
789
1027
  """
790
1028
  widget for visualizing rows of data file
791
1029
  """
@@ -801,8 +1039,18 @@ class View_data_head(QDialog):
801
1039
  vbox.addWidget(self.lb)
802
1040
 
803
1041
  self.tw = QTableWidget()
1042
+ self.tw.verticalHeader().hide()
804
1043
  vbox.addWidget(self.tw)
805
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
+
806
1054
  self.label = QLabel("Enter the column indices to plot (time, value) separated by comma (,)")
807
1055
  vbox.addWidget(self.label)
808
1056
 
@@ -813,17 +1061,17 @@ class View_data_head(QDialog):
813
1061
 
814
1062
  hbox2.addItem(QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum))
815
1063
 
816
- self.pbCancel = QPushButton("Cancel", clicked=self.reject)
1064
+ self.pbCancel = QPushButton(cfg.CANCEL, clicked=self.reject)
817
1065
  hbox2.addWidget(self.pbCancel)
818
1066
 
819
- self.pbOK = QPushButton("OK", clicked=self.accept)
1067
+ self.pbOK = QPushButton(cfg.OK, clicked=self.accept)
820
1068
  hbox2.addWidget(self.pbOK)
821
1069
 
822
1070
  vbox.addLayout(hbox2)
823
1071
 
824
1072
  self.setLayout(vbox)
825
1073
 
826
- self.resize(540, 640)
1074
+ self.resize(800, 640)
827
1075
 
828
1076
 
829
1077
  class View_explore_project_results(QWidget):
@@ -831,7 +1079,7 @@ class View_explore_project_results(QWidget):
831
1079
  widget for visualizing results of explore project
832
1080
  """
833
1081
 
834
- double_click_signal = pyqtSignal(str, int)
1082
+ double_click_signal = Signal(str, int)
835
1083
 
836
1084
  def __init__(self):
837
1085
  super().__init__()
@@ -849,14 +1097,12 @@ class View_explore_project_results(QWidget):
849
1097
  vbox.addWidget(self.tw)
850
1098
 
851
1099
  hbox2 = QHBoxLayout()
852
- 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))
853
1102
 
854
1103
  vbox.addLayout(hbox2)
855
1104
 
856
1105
  self.setLayout(vbox)
857
1106
 
858
- # self.resize(540, 640)
859
-
860
1107
  def tw_cellDoubleClicked(self, r, c):
861
-
862
1108
  self.double_click_signal.emit(self.tw.item(r, 0).text(), int(self.tw.item(r, 1).text()))