myokit 1.35.0__py3-none-any.whl → 1.35.2__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.
- myokit/__init__.py +11 -14
- myokit/__main__.py +0 -3
- myokit/_config.py +1 -3
- myokit/_datablock.py +914 -12
- myokit/_model_api.py +1 -3
- myokit/_myokit_version.py +1 -1
- myokit/_protocol.py +14 -28
- myokit/_sim/cable.c +1 -1
- myokit/_sim/cable.py +3 -2
- myokit/_sim/cmodel.h +1 -0
- myokit/_sim/cvodessim.c +79 -42
- myokit/_sim/cvodessim.py +20 -8
- myokit/_sim/fiber_tissue.c +1 -1
- myokit/_sim/fiber_tissue.py +3 -2
- myokit/_sim/openclsim.c +1 -1
- myokit/_sim/openclsim.py +8 -11
- myokit/_sim/pacing.h +121 -106
- myokit/_unit.py +1 -1
- myokit/formats/__init__.py +178 -0
- myokit/formats/axon/_abf.py +911 -841
- myokit/formats/axon/_atf.py +62 -59
- myokit/formats/axon/_importer.py +2 -2
- myokit/formats/heka/__init__.py +38 -0
- myokit/formats/heka/_importer.py +39 -0
- myokit/formats/heka/_patchmaster.py +2512 -0
- myokit/formats/wcp/_wcp.py +318 -133
- myokit/gui/datablock_viewer.py +144 -77
- myokit/gui/datalog_viewer.py +212 -231
- myokit/tests/ansic_event_based_pacing.py +3 -3
- myokit/tests/{ansic_fixed_form_pacing.py → ansic_time_series_pacing.py} +6 -6
- myokit/tests/data/formats/abf-v2.abf +0 -0
- myokit/tests/test_datablock.py +84 -0
- myokit/tests/test_datalog.py +2 -1
- myokit/tests/test_formats_axon.py +589 -136
- myokit/tests/test_formats_wcp.py +191 -22
- myokit/tests/test_pacing_system_c.py +51 -23
- myokit/tests/test_pacing_system_py.py +18 -0
- myokit/tests/test_simulation_1d.py +62 -22
- myokit/tests/test_simulation_cvodes.py +52 -3
- myokit/tests/test_simulation_fiber_tissue.py +35 -4
- myokit/tests/test_simulation_opencl.py +28 -4
- {myokit-1.35.0.dist-info → myokit-1.35.2.dist-info}/LICENSE.txt +1 -1
- {myokit-1.35.0.dist-info → myokit-1.35.2.dist-info}/METADATA +1 -1
- {myokit-1.35.0.dist-info → myokit-1.35.2.dist-info}/RECORD +47 -44
- {myokit-1.35.0.dist-info → myokit-1.35.2.dist-info}/WHEEL +0 -0
- {myokit-1.35.0.dist-info → myokit-1.35.2.dist-info}/entry_points.txt +0 -0
- {myokit-1.35.0.dist-info → myokit-1.35.2.dist-info}/top_level.txt +0 -0
myokit/gui/datablock_viewer.py
CHANGED
|
@@ -28,7 +28,7 @@ SETTINGS_FILE = os.path.join(myokit.DIR_USER, 'DataBlockViewer.ini')
|
|
|
28
28
|
N_RECENT_FILES = 5
|
|
29
29
|
|
|
30
30
|
# About
|
|
31
|
-
ABOUT = '<h1>
|
|
31
|
+
ABOUT = f'<h1>{TITLE}</h1>' + f"""
|
|
32
32
|
<p>
|
|
33
33
|
Myokit's DataBlock viewer is a utility to examine
|
|
34
34
|
<code>DataBlock1d</code> and <code>DataBlock2d</code> objects
|
|
@@ -42,10 +42,10 @@ ABOUT = '<h1>' + TITLE + '</h1>' + """
|
|
|
42
42
|
</p>
|
|
43
43
|
<p>
|
|
44
44
|
System info:
|
|
45
|
-
<br />Python:
|
|
46
|
-
<br />Using the
|
|
45
|
+
<br />Python: {sys.version}
|
|
46
|
+
<br />Using the {myokit.gui.backend} GUI backend.
|
|
47
47
|
</p>
|
|
48
|
-
"""
|
|
48
|
+
"""
|
|
49
49
|
|
|
50
50
|
# License
|
|
51
51
|
LICENSE = myokit.LICENSE_HTML
|
|
@@ -194,7 +194,7 @@ class DataBlockViewer(myokit.gui.MyokitApplication):
|
|
|
194
194
|
# Variable selection
|
|
195
195
|
self._variable_select = QtWidgets.QComboBox()
|
|
196
196
|
self._variable_select.activated.connect(self.event_variable_selected)
|
|
197
|
-
self._variable_select.setMinimumWidth(
|
|
197
|
+
self._variable_select.setMinimumWidth(160)
|
|
198
198
|
|
|
199
199
|
# Colormap selection
|
|
200
200
|
self._colormap = next(iter(myokit.ColorMap.names()))
|
|
@@ -204,6 +204,26 @@ class DataBlockViewer(myokit.gui.MyokitApplication):
|
|
|
204
204
|
self._colormap_select.activated.connect(self.event_colormap_selected)
|
|
205
205
|
self._colormap_select.setMinimumWidth(120)
|
|
206
206
|
|
|
207
|
+
self._colormap_lower_label = QtWidgets.QLabel('Range')
|
|
208
|
+
self._colormap_lower_label.setMaximumWidth(50)
|
|
209
|
+
self._colormap_lower_label.setAlignment(
|
|
210
|
+
Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
|
|
211
|
+
self._colormap_lower_field = AutoFloatField()
|
|
212
|
+
self._colormap_lower_field.setMaxLength(6)
|
|
213
|
+
self._colormap_lower_field.setMaximumWidth(80)
|
|
214
|
+
self._colormap_lower_field.editingFinished.connect(
|
|
215
|
+
self.event_variable_selected)
|
|
216
|
+
|
|
217
|
+
self._colormap_upper_label = QtWidgets.QLabel('to')
|
|
218
|
+
self._colormap_upper_label.setMaximumWidth(20)
|
|
219
|
+
self._colormap_upper_label.setAlignment(
|
|
220
|
+
Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter)
|
|
221
|
+
self._colormap_upper_field = AutoFloatField()
|
|
222
|
+
self._colormap_upper_field.setMaxLength(6)
|
|
223
|
+
self._colormap_upper_field.setMaximumWidth(80)
|
|
224
|
+
self._colormap_upper_field.editingFinished.connect(
|
|
225
|
+
self.event_variable_selected)
|
|
226
|
+
|
|
207
227
|
# Control layout
|
|
208
228
|
self._control_layout = QtWidgets.QHBoxLayout()
|
|
209
229
|
self._control_layout.addWidget(self._play_button)
|
|
@@ -216,6 +236,10 @@ class DataBlockViewer(myokit.gui.MyokitApplication):
|
|
|
216
236
|
self._control_layout.addWidget(self._graph_clear_button)
|
|
217
237
|
self._control_layout.addWidget(self._variable_select)
|
|
218
238
|
self._control_layout.addWidget(self._colormap_select)
|
|
239
|
+
self._control_layout.addWidget(self._colormap_lower_label)
|
|
240
|
+
self._control_layout.addWidget(self._colormap_lower_field)
|
|
241
|
+
self._control_layout.addWidget(self._colormap_upper_label)
|
|
242
|
+
self._control_layout.addWidget(self._colormap_upper_field)
|
|
219
243
|
|
|
220
244
|
# Graph area
|
|
221
245
|
self._graph_area = GraphArea()
|
|
@@ -256,8 +280,8 @@ class DataBlockViewer(myokit.gui.MyokitApplication):
|
|
|
256
280
|
self.update_window_title()
|
|
257
281
|
|
|
258
282
|
# Set controls to correct values
|
|
259
|
-
self._colormap_select.setCurrentIndex(
|
|
260
|
-
self._colormap))
|
|
283
|
+
self._colormap_select.setCurrentIndex(
|
|
284
|
+
self._colormap_select.findText(self._colormap))
|
|
261
285
|
self._rate_field.setText(str(self._timer_interval))
|
|
262
286
|
self._timer.setInterval(self._timer_interval)
|
|
263
287
|
|
|
@@ -297,20 +321,21 @@ class DataBlockViewer(myokit.gui.MyokitApplication):
|
|
|
297
321
|
self._timer.start()
|
|
298
322
|
|
|
299
323
|
def action_extract_colormap_image(self):
|
|
300
|
-
"""
|
|
301
|
-
|
|
302
|
-
"""
|
|
324
|
+
""" Extracts the current colormap to an image file. """
|
|
325
|
+
|
|
303
326
|
if self._data is None:
|
|
304
327
|
QtWidgets.QMessageBox.warning(
|
|
305
328
|
self, TITLE,
|
|
306
329
|
'<h1>No data to export.</h1>'
|
|
307
330
|
'<p>Please open a data file first.</p>')
|
|
308
331
|
return
|
|
332
|
+
|
|
309
333
|
fname = QtWidgets.QFileDialog.getSaveFileName(
|
|
310
334
|
self,
|
|
311
335
|
'Extract colormap to image file',
|
|
312
336
|
self._path,
|
|
313
337
|
filter=FILTER_IMG)[0]
|
|
338
|
+
|
|
314
339
|
if fname:
|
|
315
340
|
fname = str(fname)
|
|
316
341
|
ext = os.path.splitext(fname)[1][1:].upper()
|
|
@@ -318,22 +343,28 @@ class DataBlockViewer(myokit.gui.MyokitApplication):
|
|
|
318
343
|
QtWidgets.QMessageBox.warning(
|
|
319
344
|
self, TITLE,
|
|
320
345
|
'<h1>Image type not recognized.</h1>'
|
|
321
|
-
'<p>Unknown image type "
|
|
346
|
+
f'<p>Unknown image type "{ext}".</p>')
|
|
322
347
|
return
|
|
323
|
-
|
|
324
|
-
data = self._data.get2d(self._variable)
|
|
325
|
-
lower = np.min(data)
|
|
326
|
-
upper = np.max(data)
|
|
348
|
+
|
|
327
349
|
# Create image
|
|
328
350
|
nx = 200
|
|
329
351
|
ny = 800
|
|
330
352
|
image = myokit.ColorMap.image(self._colormap, nx, ny)
|
|
331
353
|
image = QtGui.QImage(
|
|
332
354
|
image, nx, ny, QtGui.QImage.Format.Format_ARGB32)
|
|
355
|
+
|
|
356
|
+
# Add lower and upper bounds
|
|
357
|
+
lower = self._colormap_lower_field.value()
|
|
358
|
+
upper = self._colormap_upper_field.value()
|
|
359
|
+
if lower is None or upper is None:
|
|
360
|
+
data = self._data.get2d(self._variable)
|
|
361
|
+
lower = np.min(data) if lower is None else lower
|
|
362
|
+
upper = np.max(data) if upper is None else upper
|
|
333
363
|
painter = QtGui.QPainter(image)
|
|
334
364
|
painter.drawText(10, 15, str(upper))
|
|
335
365
|
painter.drawText(10, ny - 5, str(lower))
|
|
336
366
|
painter.end()
|
|
367
|
+
|
|
337
368
|
# Save
|
|
338
369
|
image.save(fname, ext)
|
|
339
370
|
|
|
@@ -379,7 +410,7 @@ class DataBlockViewer(myokit.gui.MyokitApplication):
|
|
|
379
410
|
QtWidgets.QMessageBox.warning(
|
|
380
411
|
self, TITLE,
|
|
381
412
|
'<h1>Image type not recognized.</h1>'
|
|
382
|
-
'<p>Unknown image type "
|
|
413
|
+
f'<p>Unknown image type "{ext}".</p>')
|
|
383
414
|
return
|
|
384
415
|
nt, ny, nx = self._data.shape()
|
|
385
416
|
image = self._video_frames[self._video_iframe]
|
|
@@ -474,17 +505,23 @@ class DataBlockViewer(myokit.gui.MyokitApplication):
|
|
|
474
505
|
def action_set_colormap(self, name):
|
|
475
506
|
"""
|
|
476
507
|
Loads the ColorMap specified by ``name``.
|
|
508
|
+
|
|
509
|
+
If data is loaded, this will also call :meth:`action_set_variable` to
|
|
510
|
+
update the video frames.
|
|
477
511
|
"""
|
|
478
512
|
name = str(name)
|
|
479
513
|
if not myokit.ColorMap.exists(name):
|
|
480
514
|
return # Silent return?
|
|
515
|
+
|
|
481
516
|
# Set colormap
|
|
482
517
|
self._colormap = name
|
|
518
|
+
|
|
483
519
|
# Update colormap controls
|
|
484
|
-
self._colormap_select.setCurrentIndex(
|
|
485
|
-
self._colormap))
|
|
520
|
+
self._colormap_select.setCurrentIndex(
|
|
521
|
+
self._colormap_select.findText(self._colormap))
|
|
522
|
+
|
|
523
|
+
# Update colorbar
|
|
486
524
|
if self._data is not None:
|
|
487
|
-
# Update colorbar
|
|
488
525
|
nx = self._colorbar_width
|
|
489
526
|
ny = self._colorbar_height
|
|
490
527
|
image = myokit.ColorMap.image(self._colormap, nx, ny)
|
|
@@ -497,21 +534,31 @@ class DataBlockViewer(myokit.gui.MyokitApplication):
|
|
|
497
534
|
def action_set_variable(self, var):
|
|
498
535
|
"""
|
|
499
536
|
Loads the variable specified by the name ``var`` into the main display.
|
|
537
|
+
|
|
538
|
+
This method is also responsible for converting the data into frames to
|
|
539
|
+
be displayed on the video widget.
|
|
500
540
|
"""
|
|
501
541
|
if self._data is None:
|
|
502
542
|
return
|
|
503
543
|
self._variable = str(var)
|
|
504
|
-
self._variable_select.setCurrentIndex(
|
|
505
|
-
self._variable))
|
|
544
|
+
self._variable_select.setCurrentIndex(
|
|
545
|
+
self._variable_select.findText(self._variable))
|
|
506
546
|
self.action_pause_timer()
|
|
507
547
|
self._video_frames = self._data.images(
|
|
508
|
-
self._variable,
|
|
548
|
+
self._variable,
|
|
549
|
+
self._colormap,
|
|
550
|
+
self._colormap_lower_field.value(),
|
|
551
|
+
self._colormap_upper_field.value()
|
|
552
|
+
)
|
|
509
553
|
self.action_set_frame(self._video_iframe)
|
|
510
554
|
self.action_depause_timer()
|
|
511
555
|
|
|
512
556
|
def action_set_frame(self, frame):
|
|
513
557
|
"""
|
|
514
558
|
Move to a specific frame.
|
|
559
|
+
|
|
560
|
+
This method updates the display to show a video frame created by an
|
|
561
|
+
earlier call to :meth:`action_set_variable`.
|
|
515
562
|
"""
|
|
516
563
|
# Check frame index
|
|
517
564
|
nt, ny, nx = self._data.shape()
|
|
@@ -551,9 +598,7 @@ class DataBlockViewer(myokit.gui.MyokitApplication):
|
|
|
551
598
|
self._play_button.setIcon(self._play_icon_play)
|
|
552
599
|
|
|
553
600
|
def closeEvent(self, event=None):
|
|
554
|
-
|
|
555
|
-
Called when window is closed.)
|
|
556
|
-
"""
|
|
601
|
+
# Called when window is closed.
|
|
557
602
|
self.save_config()
|
|
558
603
|
if event:
|
|
559
604
|
event.accept()
|
|
@@ -630,18 +675,14 @@ class DataBlockViewer(myokit.gui.MyokitApplication):
|
|
|
630
675
|
self._menu_help.addAction(self._tool_license)
|
|
631
676
|
|
|
632
677
|
def display_exception(self):
|
|
633
|
-
"""
|
|
634
|
-
Displays the last exception.
|
|
635
|
-
"""
|
|
678
|
+
""" Displays the last exception in a messagebox. """
|
|
636
679
|
QtWidgets.QMessageBox.warning(
|
|
637
680
|
self, TITLE,
|
|
638
681
|
'<h1>An error has occurred.</h1>'
|
|
639
|
-
'<pre>
|
|
682
|
+
f'<pre>{traceback.format_exc()}</pre>')
|
|
640
683
|
|
|
641
684
|
def event_colormap_selected(self):
|
|
642
|
-
"""
|
|
643
|
-
Colormap is selected by user.
|
|
644
|
-
"""
|
|
685
|
+
""" Colormap is selected by user. """
|
|
645
686
|
self.action_set_colormap(str(self._colormap_select.currentText()))
|
|
646
687
|
|
|
647
688
|
def event_graph_mouse_move(self, x, y):
|
|
@@ -649,29 +690,21 @@ class DataBlockViewer(myokit.gui.MyokitApplication):
|
|
|
649
690
|
Graph cursur moved: Display the current cursor position on the graph
|
|
650
691
|
scene in the status bar.
|
|
651
692
|
"""
|
|
652
|
-
|
|
653
|
-
x, y = F.format(x), F.format(y)
|
|
654
|
-
self._label_cursor.setText('(' + x + ', ' + y + ')')
|
|
693
|
+
self._label_cursor.setText(f'({x:< 1.6g}, {y:< 1.6g})')
|
|
655
694
|
|
|
656
695
|
def event_rate_changed(self, e=None):
|
|
657
|
-
"""
|
|
658
|
-
User changed frame interval.
|
|
659
|
-
"""
|
|
696
|
+
""" User changed frame interval. """
|
|
660
697
|
self._timer_interval = int(self._rate_field.text())
|
|
661
698
|
self._timer.setInterval(self._timer_interval)
|
|
662
699
|
|
|
663
700
|
def event_variable_selected(self):
|
|
664
|
-
"""
|
|
665
|
-
Variable is selected by user.
|
|
666
|
-
"""
|
|
701
|
+
""" Variable is selected by user. """
|
|
667
702
|
self._variable = self._variable_select.currentText()
|
|
668
703
|
if self._data is not None:
|
|
669
704
|
self.action_set_variable(self._variable)
|
|
670
705
|
|
|
671
706
|
def event_video_single_click(self, x, y):
|
|
672
|
-
"""
|
|
673
|
-
Video clicked: Add a graph at the location of the click
|
|
674
|
-
"""
|
|
707
|
+
""" Video clicked: Add a graph at the location of the click. """
|
|
675
708
|
self._graph_area.graph(self._variable, x, y)
|
|
676
709
|
|
|
677
710
|
def event_video_double_click(self, x, y):
|
|
@@ -689,19 +722,16 @@ class DataBlockViewer(myokit.gui.MyokitApplication):
|
|
|
689
722
|
# Cursor position is in scene coordinates, so already matches datablock
|
|
690
723
|
# dimensions!
|
|
691
724
|
if self._data is not None:
|
|
692
|
-
F = '{:< 1.6g}'
|
|
693
725
|
try:
|
|
694
726
|
z = self._data.get2d(self._variable)[self._video_iframe, y, x]
|
|
695
|
-
z =
|
|
727
|
+
z = '{:< 1.6g}'.format(z)
|
|
696
728
|
except IndexError:
|
|
697
729
|
z = '?'
|
|
698
|
-
x, y
|
|
699
|
-
self._label_cursor.setText('(' + x + ', ' + y + ', ' + z + ')')
|
|
730
|
+
self._label_cursor.setText(f'({x:< 1.6g}, {y:< 1.6g}, {z}')
|
|
700
731
|
|
|
701
732
|
def keyPressEvent(self, e):
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
"""
|
|
733
|
+
# A key has been pressed
|
|
734
|
+
|
|
705
735
|
if e.key() == Qt.Key.Key_Space:
|
|
706
736
|
self.action_start_stop()
|
|
707
737
|
|
|
@@ -797,14 +827,16 @@ class DataBlockViewer(myokit.gui.MyokitApplication):
|
|
|
797
827
|
try:
|
|
798
828
|
data = myokit.DataBlock2d.load(fname, progress=reporter)
|
|
799
829
|
del reporter
|
|
800
|
-
except myokit.DataBlockReadError:
|
|
830
|
+
except myokit.DataBlockReadError as e:
|
|
801
831
|
pd.reset()
|
|
802
832
|
self.statusBar().showMessage('Load failed.')
|
|
803
833
|
QtWidgets.QMessageBox.warning(
|
|
804
834
|
self, TITLE,
|
|
805
835
|
'<h1>Unable to read file.</h1>'
|
|
806
|
-
'<p>The given filename <code>
|
|
807
|
-
' could not be read as a <code>myokit.DataBlock2d</code>.</p>'
|
|
836
|
+
f'<p>The given filename <code>{fname}</code>'
|
|
837
|
+
' could not be read as a <code>myokit.DataBlock2d</code>.</p>'
|
|
838
|
+
f'<p>{e}</p>'
|
|
839
|
+
)
|
|
808
840
|
return
|
|
809
841
|
except Exception:
|
|
810
842
|
pd.reset()
|
|
@@ -822,7 +854,7 @@ class DataBlockViewer(myokit.gui.MyokitApplication):
|
|
|
822
854
|
QtWidgets.QMessageBox.warning(
|
|
823
855
|
self, TITLE,
|
|
824
856
|
'<h1>Unable to read file.</h1>'
|
|
825
|
-
'<p>The given filename <code>
|
|
857
|
+
f'<p>The given filename <code>{fname}</code>'
|
|
826
858
|
' does not contain any 2d data.</p>')
|
|
827
859
|
return
|
|
828
860
|
|
|
@@ -853,8 +885,7 @@ class DataBlockViewer(myokit.gui.MyokitApplication):
|
|
|
853
885
|
|
|
854
886
|
# Add empty video item to video scene
|
|
855
887
|
self._video_pixmap = QtGui.QPixmap(nx, ny)
|
|
856
|
-
self._video_item = QtWidgets.QGraphicsPixmapItem(
|
|
857
|
-
self._video_pixmap)
|
|
888
|
+
self._video_item = QtWidgets.QGraphicsPixmapItem(self._video_pixmap)
|
|
858
889
|
self._video_scene.addItem(self._video_item)
|
|
859
890
|
|
|
860
891
|
# Update colormap scene
|
|
@@ -944,7 +975,7 @@ class DataBlockViewer(myokit.gui.MyokitApplication):
|
|
|
944
975
|
"""
|
|
945
976
|
for k, fname in enumerate(self._recent_files):
|
|
946
977
|
t = self._recent_file_tools[k]
|
|
947
|
-
t.setText(
|
|
978
|
+
t.setText(f'{k + 1}. {os.path.basename(fname)}')
|
|
948
979
|
t.setData(fname)
|
|
949
980
|
t.setVisible(True)
|
|
950
981
|
for i in range(len(self._recent_files), N_RECENT_FILES):
|
|
@@ -954,15 +985,22 @@ class DataBlockViewer(myokit.gui.MyokitApplication):
|
|
|
954
985
|
"""
|
|
955
986
|
Sets this window's title based on the current state.
|
|
956
987
|
"""
|
|
957
|
-
title = TITLE
|
|
988
|
+
title = f'{TITLE} {myokit.__version__}'
|
|
958
989
|
if self._file:
|
|
959
|
-
title = os.path.basename(self._file)
|
|
990
|
+
title = f'{os.path.basename(self._file)} - {title}'
|
|
960
991
|
self.setWindowTitle(title)
|
|
961
992
|
|
|
962
993
|
|
|
963
994
|
class VideoScene(QtWidgets.QGraphicsScene):
|
|
964
995
|
"""
|
|
965
996
|
Color data display scene.
|
|
997
|
+
|
|
998
|
+
Note that, despite the name, this item does not manage the conversion from
|
|
999
|
+
data to the image. The actual drawing happens by calling ``setPixmap`` on a
|
|
1000
|
+
``QGraphicsPixmapItem`` that gets added to this scene.
|
|
1001
|
+
|
|
1002
|
+
See :meth:`DataBlockViewer.action_set_variable()` and
|
|
1003
|
+
:meth:`DataBlockViewer.action_set_frame()`.
|
|
966
1004
|
"""
|
|
967
1005
|
# Signals
|
|
968
1006
|
# Somebody moved the mouse
|
|
@@ -984,11 +1022,9 @@ class VideoScene(QtWidgets.QGraphicsScene):
|
|
|
984
1022
|
self.resize(1, 1)
|
|
985
1023
|
|
|
986
1024
|
def mousePressEvent(self, event):
|
|
987
|
-
|
|
988
|
-
Single-click
|
|
989
|
-
"""
|
|
1025
|
+
# Single-click event
|
|
990
1026
|
if event.button() == Qt.MouseButton.LeftButton:
|
|
991
|
-
if event.modifiers() == Qt.
|
|
1027
|
+
if event.modifiers() == Qt.KeyboardModifier.NoModifier:
|
|
992
1028
|
p = event.scenePos()
|
|
993
1029
|
x, y = int(p.x()), int(p.y())
|
|
994
1030
|
if x >= 0 and x < self._w and y >= 0 and y < self._h:
|
|
@@ -996,11 +1032,9 @@ class VideoScene(QtWidgets.QGraphicsScene):
|
|
|
996
1032
|
return
|
|
997
1033
|
|
|
998
1034
|
def mouseDoubleClickEvent(self, event):
|
|
999
|
-
|
|
1000
|
-
Double-click
|
|
1001
|
-
"""
|
|
1035
|
+
# Double-click event
|
|
1002
1036
|
if event.button() == Qt.MouseButton.LeftButton:
|
|
1003
|
-
if event.modifiers() == Qt.
|
|
1037
|
+
if event.modifiers() == Qt.KeyboardModifier.NoModifier:
|
|
1004
1038
|
p = event.scenePos()
|
|
1005
1039
|
x, y = int(p.x()), int(p.y())
|
|
1006
1040
|
if x >= 0 and x < self._w and y >= 0 and y < self._h:
|
|
@@ -1008,17 +1042,13 @@ class VideoScene(QtWidgets.QGraphicsScene):
|
|
|
1008
1042
|
return
|
|
1009
1043
|
|
|
1010
1044
|
def mouseMoveEvent(self, event):
|
|
1011
|
-
|
|
1012
|
-
Show mouse position in status bar
|
|
1013
|
-
"""
|
|
1045
|
+
# The mouse has moved!
|
|
1014
1046
|
p = event.scenePos()
|
|
1015
1047
|
x, y = int(p.x()), int(p.y())
|
|
1016
1048
|
self.mouse_moved.emit(x, y)
|
|
1017
1049
|
|
|
1018
1050
|
def resize(self, w, h):
|
|
1019
|
-
"""
|
|
1020
|
-
Resizes the scene to match the given dimensions.
|
|
1021
|
-
"""
|
|
1051
|
+
""" Resize the scene to match the given dimensions. """
|
|
1022
1052
|
self._w = float(w)
|
|
1023
1053
|
self._h = float(h)
|
|
1024
1054
|
self.setSceneRect(0, 0, self._w, self._h)
|
|
@@ -1081,9 +1111,8 @@ class VideoView(QtWidgets.QGraphicsView):
|
|
|
1081
1111
|
self.centerOn(rect.center())
|
|
1082
1112
|
|
|
1083
1113
|
def resizeEvent(self, event=None):
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
"""
|
|
1114
|
+
# Called when the view is resized.
|
|
1115
|
+
|
|
1087
1116
|
# Tell others a resize is happening
|
|
1088
1117
|
# (This is used to pause the video playback)
|
|
1089
1118
|
self.resize_event.emit()
|
|
@@ -1365,3 +1394,41 @@ class GraphArea(QtWidgets.QWidget):
|
|
|
1365
1394
|
Tells Qt that this widget shout expand.
|
|
1366
1395
|
"""
|
|
1367
1396
|
return QtCore.QSizePolicy.Expanding
|
|
1397
|
+
|
|
1398
|
+
|
|
1399
|
+
class AutoFloatField(QtWidgets.QLineEdit):
|
|
1400
|
+
"""
|
|
1401
|
+
A QLineEdit that requires floats as input, but will show "(Auto)" when a
|
|
1402
|
+
non-float is entered and return ``None`` as its value.
|
|
1403
|
+
"""
|
|
1404
|
+
def __init__(self, parent=None):
|
|
1405
|
+
super().__init__(parent)
|
|
1406
|
+
|
|
1407
|
+
# Colors
|
|
1408
|
+
self._color_ok = QtGui.QTextCharFormat().foreground().color().getRgb()
|
|
1409
|
+
self._color_auto = (127, 127, 127)
|
|
1410
|
+
self._auto_text = '(Auto)'
|
|
1411
|
+
|
|
1412
|
+
# Show text
|
|
1413
|
+
self.editingFinished.connect(self._autofy)
|
|
1414
|
+
self._autofy()
|
|
1415
|
+
|
|
1416
|
+
def _autofy(self):
|
|
1417
|
+
""" Put (Auto) if not valid """
|
|
1418
|
+
if self.value() is None:
|
|
1419
|
+
self.setText(self._auto_text)
|
|
1420
|
+
c = self._color_auto
|
|
1421
|
+
else:
|
|
1422
|
+
c = self._color_ok
|
|
1423
|
+
self.setStyleSheet(f'color: rgb({c[0]}, {c[1]}, {c[2]})')
|
|
1424
|
+
|
|
1425
|
+
def focusInEvent(self, event):
|
|
1426
|
+
super().focusInEvent(event)
|
|
1427
|
+
QtCore.QTimer.singleShot(0, self.selectAll)
|
|
1428
|
+
|
|
1429
|
+
def value(self):
|
|
1430
|
+
try:
|
|
1431
|
+
return float(super().text())
|
|
1432
|
+
except ValueError:
|
|
1433
|
+
return None
|
|
1434
|
+
|