-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathroll_main_window.py
4690 lines (3758 loc) · 264 KB
/
roll_main_window.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
"""
/***************************************************************************
Roll, a QGIS plugin for design and analysis of 3D seismic survey geometries
Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
begin : 2022-10-09
git sha : $Format:%H$
copyright : (C) 2022 by Duijndam.Dev
email : bart.duijndam@ziggo.nl
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
The following PyQt example shows the framework that was used as a reference, to build this AddIn
https://doc.qt.io/qtforpython-5/overviews/qtwidgets-mainwindows-application-example.html#application-example
To help Python development, I installed some VS Code plugins as recommended here:
https://www.youtube.com/watch?v=glXGae6TsY8
Note: To find out where libraries reside, use 'inspect':
>>>import inspect
>>>inspect.getfile(qgis.PyQt.QtCore)
'C:\\Program Files\\QGIS 3.28.1\\apps\\qgis\\python\\qgis\\PyQt\\QtCore.py'
"""
# Currently I am using PyLint to check for errors, which causes a few issues on its own.
# See: https://stackoverflow.com/questions/52123470/how-do-i-disable-pylint-unused-import-error-messages-in-vs-code
# See: https://gist.github.com/xen/6334976
# See: https://pylint.pycqa.org/en/latest/user_guide/messages/index.html
# See: http://pylint-messages.wikidot.com/all-codes for more codes. see also: https://manpages.org/pylint
# See: https://pylint.pycqa.org/en/latest/user_guide/messages/messages_overview.html#messages-overview for the official list
# See: https://gispofinland.medium.com/cooking-with-gispo-qgis-plugin-development-in-vs-code-19f95efb1977 IMPORTANT FOR A FULLY FUNCTIONING SETUP
# Note: Currently the only one language server supporting autocompletion for qgis and PyQt5 libraries is the Jedi language server
# Qgis and PyQt5 packages are using compiled python code and other language servers are having troubles parsing the API from those libraries (Actually this is for security reasons).
# In the following QGIS releases python stub files that describes the API are included in the qgis package so also much better Pylance language server can be then used
# If you have *.pyi files in C:/OSGeo4W64/apps/qgis-ltr/python/qgis go with the Pylance language server.
# This file is getting TOO BIG (> 3000 lines). Its main class has been (will further be) split into multiple documents.
# See: https://www.reddit.com/r/Python/comments/91wbhc/how_do_i_split_a_huge_class_over_multiple_files/?rdt=44096
# As of 3.32 scaling issues have popped up in QGIS. !
# See: https://github.com/qgis/QGIS/issues/53898
# Solution:
# 1) Right click 'qgis-bin.exe' in folder 'C:\Program Files\QGIS 3.36.3\bin'
# 2) Select the Compatibility tab
# 3) Select 'change high DPI settings'
# 4) Set the tickmark before 'Override high DPI ...'
# 5) Have scaling performed by 'Application'
# 6) In the same folder edit the file 'qgis-bin.env'
# 7) Add one line at the end:
# 8) QT_SCALE_FACTOR_ROUNDING_POLICY=Floor
# 9) Save the file in a different (user) folder as C:\Program Files is protected
# 10) Drag the edited file to the C:\Program Files\QGIS 3.36.3\bin folder
# 11) You'll be asked to confirm you want to overwrite the *.env file
# that solved my problems ! I use font size 9.0 and Icon size 24
# Extra toolbars have been added to be able to close the Display pane, use a toolbar instead.
# But in doing so, I don't want to duplicate all signals and slots !
# See: https://stackoverflow.com/questions/16703039/pyqt-can-a-qpushbutton-be-assigned-a-qaction
# See: https://stackoverflow.com/questions/4149117/how-can-i-implement-the-button-that-holds-an-action-qaction-and-can-connect-w
# See: https://stackoverflow.com/questions/38576380/difference-between-qpushbutton-and-qtoolbutton
# See: https://stackoverflow.com/questions/10368947/how-to-make-qmenu-item-checkable-pyqt4-python
# See: https://stackoverflow.com/questions/23388754/two-shortcuts-for-one-action
# See: https://stackoverflow.com/questions/53936403/two-shortcuts-for-one-button-in-pyqt
# See: https://doc.qt.io/qtforpython-5/PySide2/QtWidgets/QActionGroup.html#PySide2.QtWidgets.PySide2.QtWidgets.QActionGroup
import contextlib
import gc
import os
import os.path
import sys
import traceback
import typing
import winsound # make a sound when an exception ocurs
from datetime import timedelta
from enum import Enum
from math import atan2, ceil, degrees
from timeit import default_timer as timer
# PyQtGraph related imports
import numpy as np # Numpy functions needed for plot creation
import pyqtgraph as pg
from console import console
from numpy.compat import asstr
from qgis.PyQt import uic
from qgis.PyQt.QtCore import QDateTime, QEvent, QFile, QFileInfo, QIODevice, QItemSelection, QItemSelectionModel, QModelIndex, QPoint, QSettings, Qt, QTextStream, QThread
from qgis.PyQt.QtGui import QBrush, QColor, QFont, QIcon, QKeySequence, QTextCursor, QTextOption, QTransform
from qgis.PyQt.QtPrintSupport import QPrintDialog, QPrinter, QPrintPreviewDialog
from qgis.PyQt.QtWidgets import (
QAction,
QActionGroup,
QApplication,
QButtonGroup,
QCheckBox,
QDialogButtonBox,
QDockWidget,
QFileDialog,
QFrame,
QGraphicsEllipseItem,
QGroupBox,
QHBoxLayout,
QHeaderView,
QLabel,
QMainWindow,
QMessageBox,
QPlainTextEdit,
QProgressBar,
QPushButton,
QTabWidget,
QToolButton,
QVBoxLayout,
QWidget,
)
from qgis.PyQt.QtXml import QDomDocument
from . import config # used to pass initial settings
# from .event_lookup import event_lookup
from .find import Find
from .functions import aboutText, exampleSurveyXmlText, highDpiText, licenseText, rawcount
from .functions_numba import numbaAziInline, numbaAziX_line, numbaFilterSlice2D, numbaNdft_1D, numbaNdft_2D, numbaOffInline, numbaOffsetBin, numbaOffX_line, numbaSlice3D, numbaSliceStats, numbaSpiderBin
from .land_wizard import LandSurveyWizard
from .marine_wizard import MarineSurveyWizard
from .my_parameters import registerAllParameterTypes
from .qgis_interface import CreateQgisRasterLayer, ExportRasterLayerToQgis, exportPointLayerToQgis, exportSurveyOutlineToQgis, identifyQgisPointLayer, readQgisPointLayer
from .roll_binning import BinningType
from .roll_main_window_create_geom_tab import createGeomTab
from .roll_main_window_create_layout_tab import createLayoutTab
from .roll_main_window_create_pattern_tab import createPatternTab
from .roll_main_window_create_sps_tab import createSpsTab
from .roll_main_window_create_stack_response_tab import createStackResponseTab
from .roll_main_window_create_trace_table_tab import createTraceTableTab
from .roll_output import RollOutput
from .roll_survey import RollSurvey, SurveyType
from .settings import SettingsDialog, readSettings, writeSettings
from .sps_io_and_qc import (
calcMaxXPStraces,
calculateLineStakeTransform,
deletePntDuplicates,
deletePntOrphans,
deleteRelDuplicates,
deleteRelOrphans,
fileExportAsR01,
fileExportAsS01,
fileExportAsX01,
findRecOrphans,
findSrcOrphans,
getGeometry,
markUniqueRPSrecords,
markUniqueSPSrecords,
markUniqueXPSrecords,
pntType1,
readRPSFiles,
readSPSFiles,
readXPSFiles,
relType2,
)
from .worker_threads import BinFromGeometryWorker, BinningWorker, GeometryWorker
from .xml_code_editor import QCodeEditor, XMLHighlighter
class ImagType(Enum):
NoIm = 0
Fold = 1
MinO = 2
MaxO = 3
class MsgType(Enum):
Info = 0
Binning = 1
Geometry = 2
Debug = 3
Error = 4
Exception = 5
class Direction(Enum):
NA = 0
Up = 1
Dn = 2
Lt = 3
Rt = 4
# This loads your .ui file so that PyQt can populate your plugin with the elements from Qt Designer
FORM_CLASS, _ = uic.loadUiType(os.path.join(os.path.dirname(__file__), 'roll_main_window_base.ui'))
# See: https://gist.github.com/mistic100/dcbffbd9e9c15271dd14
class QButtonGroupEx(QButtonGroup):
def setCheckedId(self, id_) -> int:
for button in self.buttons():
if self.id(button) == id_:
button.setChecked(True)
return id_
return None
# See: https://groups.google.com/g/pyqtgraph/c/V01QJKvrUio/m/iUBp5NePCQAJ
class LineROI(pg.LineSegmentROI):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def addHandle(self, *args, **kwargs):
# Larger handle for improved visibility
self.handleSize = 8
super().addHandle(*args, **kwargs)
def checkPointMove(self, handle, pos, modifiers):
# needed to prevent 'eternal' range-jitter preventing the plot to complete
self.getViewBox().disableAutoRange(axis='xy')
return True
def generateSvg(self, nodes):
pass # for the time being don't do anything; just to keep PyLint happy
class QHLine(QFrame):
def __init__(self, parent=None):
super().__init__(parent)
self.setFrameShape(QFrame.HLine)
self.setFrameShadow(QFrame.Sunken)
def generateSvg(self, nodes):
pass # for the time being don't do anything; just to keep PyLint happy
def silentPrint(*_, **__):
pass
class RollMainWindow(QMainWindow, FORM_CLASS):
def __init__(self, parent=None):
"""Constructor."""
super(RollMainWindow, self).__init__(parent)
# Set up the user interface from Designer through FORM_CLASS.
# After self.setupUi() you can access any designer object by doing self.<objectname>,
# and you can use autoconnect slots - see http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html
# widgets-and-dialogs-with-auto-connect
# See also: https://doc.qt.io/qt-6/designer-using-a-ui-file-python.html
# See: https://docs.qgis.org/3.22/en/docs/documentation_guidelines/substitutions.html#toolbar-button-icons for QGIS Icons
self.setupUi(self)
# reset GUI when the plugin is restarted (not when restarted from minimized state on windows)
self.killMe = False
# GQIS interface
self.iface = None # access to QGis interface
# toolbar parameters
self.debug = False # use debug settings
self.XisY = True # equal x / y scaling
self.rect = False # zoom using a rectangle
self.glob = False # global coordinates
self.gridX = True # use grid lines
self.gridY = True # use grid lines
self.antiA = [False for i in range(12)] # anti-alias painting
self.ruler = False # show a ruler to measure distances
# exception handling
# self.oldExceptHook = sys.excepthook # make a copy before changing it
# sys.excepthook = self.exceptionHook # deal with uncaught exceptions using hook
# print handling
# self.oldPrint = builtins.print # need to be able to get back to 'normal'
# list with most recently used [mru] file actions
self.recentFileActions = []
self.recentFileList = []
# workerTread parameters
self.worker = None # 'moveToThread' object
self.thread = None # corresponding worker thread
self.startTime = None # thread start time
# statusbar widgets
self.posWidgetStatusbar = QLabel('(x, y): (0.00, 0.00)') # mouse' position label, in bottom right corner
self.progressLabel = QLabel('doing a lot of stuff in the background') # label next to progressbar indicating background process
self.progressBar = QProgressBar() # progressbar in statusbar
self.progressBar.setMaximumWidth(500) # to avoid 'jitter' when the mouse moves and posWidgetStatusbar changes width
height = self.posWidgetStatusbar.height() # needed to avoid statusbar 'growing' vertically by adding the progressbar
self.progressBar.setMaximumHeight(height) # to avoid ugly appearance on statusbar
# binning analysis
self.output = RollOutput() # contains result arrays and min/max values
self.binAreaChanged = False # set when binning area changes in property tree
# display parameters in Layout tab
self.imageType = 0 # 1 = fold map
self.layoutMax = 0.0 # max value for image's colorbar (minimum is always 0)
self.layoutImg = None # numpy array to be displayed; binOutput / minOffset / maxOffset / rmsOffset
# analysis numpy arrays
self.inlineStk = None # numpy array with inline Kr stack reponse
self.x_lineStk = None # numpy array with x_line Kr stack reponse
self.xyCellStk = None # numpy array with cell's KxKy stack response
self.xyPatResp = None # numpy array with pattern's KxKy response
# layout and analysis image-items
self.layoutImItem = None # pg ImageItems showing analysis result
self.stkTrkImItem = None
self.stkBinImItem = None
self.stkCelImItem = None
self.offAziImItem = None
self.kxyPatImItem = None
# corresponding color bars
self.layoutColorBar = None # colorBars, added to imageItem
self.stkTrkColorBar = None
self.stkBinColorBar = None
self.stkCelColorBar = None
self.offAziColorBar = None
self.kxyPatColorBar = None
# rps, sps, xps input arrays
self.rpsImport = None # numpy array with list of RPS records
self.spsImport = None # numpy array with list of SPS records
self.xpsImport = None # numpy array with list of XPS records
self.rpsLiveE = None # numpy array with list of live RPS coordinates
self.rpsLiveN = None # numpy array with list of live RPS coordinates
self.rpsDeadE = None # numpy array with list of dead RPS coordinates
self.rpsDeadN = None # numpy array with list of dead RPS coordinates
self.spsLiveE = None # numpy array with list of live SPS coordinates
self.spsLiveN = None # numpy array with list of live SPS coordinates
self.spsDeadE = None # numpy array with list of dead SPS coordinates
self.spsDeadN = None # numpy array with list of dead SPS coordinates
# rel, src, rel input arrays
self.recGeom = None # numpy array with list of REC records
self.srcGeom = None # numpy array with list of SRC records
self.relGeom = None # numpy array with list of REL records
self.recLiveE = None # numpy array with list of live REC coordinates
self.recLiveN = None # numpy array with list of live REC coordinates
self.recDeadE = None # numpy array with list of dead REC coordinates
self.recDeadN = None # numpy array with list of dead REC coordinates
self.srcLiveE = None # numpy array with list of live SRC coordinates
self.srcLiveN = None # numpy array with list of live SRC coordinates
self.srcDeadE = None # numpy array with list of dead SRC coordinates
self.srcDeadN = None # numpy array with list of dead SRC coordinates
# spider plot settings
self.spiderPoint = QPoint(-1, -1) # spider point 'out of scope'
self.spiderSrcX = None # numpy array with list of SRC part of spider plot
self.spiderSrcY = None # numpy array with list of SRC part of spider plot
self.spiderRecX = None # numpy array with list of REC part of spider plot
self.spiderRecY = None # numpy array with list of REC part of spider plot
self.spiderText = None # text label describing spider bin, stake, fold
self.actionSpider.setChecked(False) # reset spider plot to 'off'
# export layers to QGIS
self.spsLayer = None # QGIS layer for sps point I/O
self.rpsLayer = None # QGIS layer for rpr point I/O
self.srcLayer = None # QGIS layer for src point I/O
self.recLayer = None # QGIS layer for rec point I/O
self.spsField = None # QGIS field for sps point selection I/O
self.rpsField = None # QGIS field for rps point selection I/O
self.srcField = None # QGIS field for src point selection I/O
self.recField = None # QGIS field for rec point selection I/O
# ruler settings
self.lineROI = None # the ruler's dotted line
self.roiLabels = None # the ruler's three labels
self.rulerState = None # ruler's state, used to redisplay ruler at last used location
# warning dialogs that can be hidden
self.hideSpsCrsWarning = False # warning message: sps crs should be identical to project crs
# pattern information plotting parameters
self.patternLayout = True # True shows geometry (layout). False shows kxky response
icon_path = ':/plugins/roll/icon.png'
icon = QIcon(icon_path)
self.setWindowIcon(icon)
# See: https://gist.github.com/dgovil/d83e7ddc8f3fb4a28832ccc6f9c7f07b dealing with settings
# See also : https://doc.qt.io/qtforpython-5/PySide2/QtCore/QSettings.html
# QCoreApplication.setOrganizationName('Duijndam.Dev')
# QCoreApplication.setApplicationName('Roll')
# self.settings = QSettings() ## doesn't work as expected with QCoreApplication.setXXX
self.settings = QSettings(config.organization, config.application)
self.fileName = ''
self.workingDirectory = ''
self.importDirectory = ''
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# first docking pane, used to display geometry and analysis results
self.dockDisplay = QDockWidget('Display pane')
self.dockDisplay.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
# See: https://www.geeksforgeeks.org/pyqt5-qdockwidget-setting-style-sheet/ for styling
self.dockDisplay.setStyleSheet('QDockWidget::title {background : lightblue;}')
self.geometryChoice = QGroupBox('Geometry to display') # create display widget(s)
self.analysisChoice = QGroupBox('Analysis to display') # create display widget(s)
self.analysisToQgis = QGroupBox('Export to QGIS') # create display widget(s)
self.geometryChoice.setMinimumWidth(140)
self.analysisChoice.setMinimumWidth(140)
self.analysisToQgis.setMinimumWidth(140)
self.geometryChoice.setAlignment(Qt.AlignHCenter)
self.analysisChoice.setAlignment(Qt.AlignHCenter)
self.analysisToQgis.setAlignment(Qt.AlignHCenter)
# display pane
vbox0 = QVBoxLayout()
self.displayLayout = QHBoxLayout() # required layout
self.displayLayout.addStretch() # add some stretch to main center widget(s)
self.displayLayout.addLayout(vbox0)
self.displayLayout.addStretch() # add some stretch to main center widget(s)
vbox0.addStretch() # add some stretch to main center widget(s)
vbox0.addWidget(self.geometryChoice) # add main widget(s)
vbox0.addStretch() # add some stretch to main center widget(s)
vbox0.addWidget(self.analysisChoice) # add main widget(s)
vbox0.addStretch() # add some stretch to main center widget(s)
vbox0.addWidget(self.analysisToQgis) # add main widget(s)
vbox0.addStretch() # add some stretch to main center widget(s)
self.tbTemplat = QToolButton()
self.tbRecList = QToolButton()
self.tbSrcList = QToolButton()
self.tbRpsList = QToolButton()
self.tbSpsList = QToolButton()
self.tbAllList = QToolButton()
self.tbTemplat.setMinimumWidth(110)
self.tbRecList.setMinimumWidth(110)
self.tbSrcList.setMinimumWidth(110)
self.tbRpsList.setMinimumWidth(110)
self.tbSpsList.setMinimumWidth(110)
self.tbAllList.setMinimumWidth(110)
self.tbTemplat.setStyleSheet('QToolButton { selection-background-color: blue } QToolButton:checked { background-color: lightblue } QToolButton:pressed { background-color: red }')
self.tbRecList.setStyleSheet('QToolButton { selection-background-color: blue } QToolButton:checked { background-color: lightblue } QToolButton:pressed { background-color: red }')
self.tbSrcList.setStyleSheet('QToolButton { selection-background-color: blue } QToolButton:checked { background-color: lightblue } QToolButton:pressed { background-color: red }')
self.tbTemplat.setStyleSheet('QToolButton { selection-background-color: blue } QToolButton:checked { background-color: lightblue } QToolButton:pressed { background-color: red }')
self.tbRpsList.setStyleSheet('QToolButton { selection-background-color: blue } QToolButton:checked { background-color: lightblue } QToolButton:pressed { background-color: red }')
self.tbSpsList.setStyleSheet('QToolButton { selection-background-color: blue } QToolButton:checked { background-color: lightblue } QToolButton:pressed { background-color: red }')
self.tbAllList.setStyleSheet('QToolButton { selection-background-color: blue } QToolButton:checked { background-color: lightblue } QToolButton:pressed { background-color: red }')
self.tbTemplat.setDefaultAction(self.actionTemplates)
self.tbRecList.setDefaultAction(self.actionRecPoints)
self.tbSrcList.setDefaultAction(self.actionSrcPoints)
self.tbRpsList.setDefaultAction(self.actionRpsPoints)
self.tbSpsList.setDefaultAction(self.actionSpsPoints)
self.tbAllList.setDefaultAction(self.actionAllPoints)
self.actionTemplates.setChecked(True)
self.actionRecPoints.setEnabled(False)
self.actionSrcPoints.setEnabled(False)
self.actionRpsPoints.setEnabled(False)
self.actionSpsPoints.setEnabled(False)
self.actionAllPoints.setEnabled(False)
self.actionAllPoints.setChecked(True)
vbox1 = QVBoxLayout()
vbox1.addWidget(self.tbTemplat)
vbox1.addWidget(self.tbRecList)
vbox1.addWidget(self.tbSrcList)
vbox1.addWidget(self.tbRpsList)
vbox1.addWidget(self.tbSpsList)
vbox1.addWidget(QHLine())
vbox1.addWidget(self.tbAllList)
self.geometryChoice.setLayout(vbox1)
self.tbNone = QToolButton()
self.tbFold = QToolButton()
self.tbMinO = QToolButton()
self.tbMaxO = QToolButton()
self.tbRmsO = QToolButton()
self.tbNone.setMinimumWidth(110)
self.tbFold.setMinimumWidth(110)
self.tbMinO.setMinimumWidth(110)
self.tbMaxO.setMinimumWidth(110)
self.tbRmsO.setMinimumWidth(110)
self.tbNone.setStyleSheet('QToolButton { selection-background-color: blue } QToolButton:checked { background-color: lightblue } QToolButton:pressed { background-color: red }')
self.tbFold.setStyleSheet('QToolButton { selection-background-color: blue } QToolButton:checked { background-color: lightblue } QToolButton:pressed { background-color: red }')
self.tbMinO.setStyleSheet('QToolButton { selection-background-color: blue } QToolButton:checked { background-color: lightblue } QToolButton:pressed { background-color: red }')
self.tbMaxO.setStyleSheet('QToolButton { selection-background-color: blue } QToolButton:checked { background-color: lightblue } QToolButton:pressed { background-color: red }')
self.tbRmsO.setStyleSheet('QToolButton { selection-background-color: blue } QToolButton:checked { background-color: lightblue } QToolButton:pressed { background-color: red }')
self.actionNone.setChecked(True) # action coupled to tbNone
self.tbNone.setDefaultAction(self.actionNone) # coupling done here
self.tbFold.setDefaultAction(self.actionFold)
self.tbMinO.setDefaultAction(self.actionMinO)
self.tbMaxO.setDefaultAction(self.actionMaxO)
self.tbRmsO.setDefaultAction(self.actionRmsO)
vbox2 = QVBoxLayout()
vbox2.addWidget(self.tbNone)
vbox2.addWidget(self.tbFold)
vbox2.addWidget(self.tbMinO)
vbox2.addWidget(self.tbMaxO)
vbox2.addWidget(self.tbRmsO)
vbox2.addWidget(QHLine())
self.actionSpider.triggered.connect(self.handleSpiderPlot)
self.actionSpider.setEnabled(False)
self.tbSpider = QToolButton()
self.tbSpider.setMinimumWidth(110)
self.tbSpider.setStyleSheet('QToolButton { selection-background-color: blue } QToolButton:checked { background-color: lightblue } QToolButton:pressed { background-color: red }')
self.tbSpider.setDefaultAction(self.actionSpider)
vbox2.addWidget(self.tbSpider)
self.btnSpiderLt = QToolButton()
self.btnSpiderRt = QToolButton()
self.btnSpiderUp = QToolButton()
self.btnSpiderDn = QToolButton()
self.btnSpiderLt.setDefaultAction(self.actionMoveLt)
self.btnSpiderRt.setDefaultAction(self.actionMoveRt)
self.btnSpiderUp.setDefaultAction(self.actionMoveUp)
self.btnSpiderDn.setDefaultAction(self.actionMoveDn)
# Note: to use a stylesheet on buttons (=actions) in a toolbar, you needt to use the toolbar's stylesheet and select individual actions to 'style'
# See: https://stackoverflow.com/questions/32460193/how-to-change-qaction-background-color-using-stylesheets-css
self.btnSpiderLt.setStyleSheet('QToolButton { selection-background-color: blue } QToolButton:checked { background-color: lightblue } QToolButton:pressed { background-color: red }')
self.btnSpiderRt.setStyleSheet('QToolButton { selection-background-color: blue } QToolButton:checked { background-color: lightblue } QToolButton:pressed { background-color: red }')
self.btnSpiderUp.setStyleSheet('QToolButton { selection-background-color: blue } QToolButton:checked { background-color: lightblue } QToolButton:pressed { background-color: red }')
self.btnSpiderDn.setStyleSheet('QToolButton { selection-background-color: blue } QToolButton:checked { background-color: lightblue } QToolButton:pressed { background-color: red }')
self.actionMoveLt.triggered.connect(self.spiderGoLt)
self.actionMoveRt.triggered.connect(self.spiderGoRt)
self.actionMoveUp.triggered.connect(self.spiderGoUp)
self.actionMoveDn.triggered.connect(self.spiderGoDn)
self.actionMoveLt.setShortcuts(['Alt+Left', 'Alt+Shift+Left', 'Alt+Ctrl+Left', 'Alt+Shift+Ctrl+Left'])
self.actionMoveRt.setShortcuts(['Alt+Right', 'Alt+Shift+Right', 'Alt+Ctrl+Right', 'Alt+Shift+Ctrl+Right'])
self.actionMoveUp.setShortcuts(['Alt+Up', 'Alt+Shift+Up', 'Alt+Ctrl+Up', 'Alt+Shift+Ctrl+Up'])
self.actionMoveDn.setShortcuts(['Alt+Down', 'Alt+Shift+Down', 'Alt+Ctrl+Down', 'Alt+Shift+Ctrl+Down'])
hbox1 = QHBoxLayout()
hbox1.addStretch()
hbox1.addWidget(self.btnSpiderLt)
hbox1.addWidget(self.btnSpiderRt)
hbox1.addWidget(self.btnSpiderUp)
hbox1.addWidget(self.btnSpiderDn)
hbox1.addStretch()
vbox2.addLayout(hbox1)
self.analysisChoice.setLayout(vbox2)
self.analysisActionGroup = QActionGroup(self)
self.analysisActionGroup.addAction(self.actionNone)
self.analysisActionGroup.addAction(self.actionFold)
self.analysisActionGroup.addAction(self.actionMinO)
self.analysisActionGroup.addAction(self.actionMaxO)
self.analysisActionGroup.addAction(self.actionRmsO)
self.actionNone.setChecked(True)
self.actionNone.triggered.connect(self.onActionNoneTriggered)
self.actionFold.triggered.connect(self.onActionFoldTriggered)
self.actionMinO.triggered.connect(self.onActionMinOTriggered)
self.actionMaxO.triggered.connect(self.onActionMaxOTriggered)
self.actionRmsO.triggered.connect(self.onActionRmsOTriggered)
self.btnBinToQGIS = QPushButton('Fold Map')
self.btnMinToQGIS = QPushButton('Min Offset')
self.btnMaxToQGIS = QPushButton('Max Offset')
self.btnRmsToQGIS = QPushButton('Rms Offset')
self.btnBinToQGIS.setMinimumWidth(110)
self.btnMinToQGIS.setMinimumWidth(110)
self.btnMaxToQGIS.setMinimumWidth(110)
self.btnRmsToQGIS.setMinimumWidth(110)
self.btnBinToQGIS.setStyleSheet('background-color:lightgoldenrodyellow; font-weight:bold;')
self.btnMinToQGIS.setStyleSheet('background-color:lightgoldenrodyellow; font-weight:bold;')
self.btnMaxToQGIS.setStyleSheet('background-color:lightgoldenrodyellow; font-weight:bold;')
self.btnRmsToQGIS.setStyleSheet('background-color:lightgoldenrodyellow; font-weight:bold;')
vbox3 = QVBoxLayout()
vbox3.addWidget(self.btnBinToQGIS)
vbox3.addWidget(self.btnMinToQGIS)
vbox3.addWidget(self.btnMaxToQGIS)
vbox3.addWidget(self.btnRmsToQGIS)
self.analysisToQgis.setLayout(vbox3)
self.displayWidget = QWidget() # placeholder widget to generate a layout
self.displayWidget.setLayout(self.displayLayout) # add layout to widget
self.dockDisplay.setWidget(self.displayWidget) # set widget as main widget in docking panel
self.addDockWidget(Qt.LeftDockWidgetArea, self.dockDisplay) # add docking panel to main window
self.dockDisplay.toggleViewAction().setShortcut(QKeySequence('Ctrl+Alt+d'))
self.menu_View.addAction(self.dockDisplay.toggleViewAction()) # show/hide as requested
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# second docking pane
self.dockLogging = QDockWidget('Logging pane')
self.dockLogging.setAllowedAreas(Qt.BottomDockWidgetArea | Qt.TopDockWidgetArea)
# See: https://www.geeksforgeeks.org/pyqt5-qdockwidget-setting-style-sheet/ for styling
self.dockLogging.setStyleSheet('QDockWidget::title {background : lightblue;}')
# See: https://wiki.python.org/moin/PyQt/Handling%20context%20menus to change context menu, very informative
# See: https://stackoverflow.com/questions/32053072/pyqt-extend-existing-contextual-menu-when-editing-qstandarditems-text-in-a-qtr
# See: https://stackoverflow.com/questions/8676597/customising-location-sensitive-context-menu-in-qtextedit
# See: https://forum.qt.io/topic/60790/add-actions-from-qtextedit-to-edit-menu/10
self.logEdit = QPlainTextEdit()
self.logEdit.clear() # Should not be necessary between sessions
self.logEdit.setUndoRedoEnabled(False) # Don't allow undo on the logging pane
# self.logEdit.setReadOnly(True) # if we set this 'True' the context menu no longer allows 'delete', just 'select all' and 'copy'
self.logEdit.setLineWrapMode(QPlainTextEdit.NoWrap)
self.logEdit.setWordWrapMode(QTextOption.NoWrap)
self.logEdit.setStyleSheet('QPlainTextEdit { font-family: Courier New; font-weight: bold; font-size: 12px;}')
# self.logEdit.setFont(QFont("Ubuntu Mono", 8, QFont.Normal)) # Does not line up columns properly !
self.dockLogging.setWidget(self.logEdit)
self.addDockWidget(Qt.BottomDockWidgetArea, self.dockLogging)
self.dockLogging.toggleViewAction().setShortcut(QKeySequence('Ctrl+Alt+l'))
self.menu_View.addAction(self.dockLogging.toggleViewAction())
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
self.survey = RollSurvey() # (re)set the survey object; needed in property pane
self.mainTabWidget = QTabWidget()
self.mainTabWidget.setTabPosition(QTabWidget.South)
self.mainTabWidget.setTabShape(QTabWidget.Rounded)
self.mainTabWidget.setDocumentMode(False) # has only effect on OSX ?!
self.mainTabWidget.resize(300, 200)
self.analysisTabWidget = QTabWidget()
self.analysisTabWidget.setTabPosition(QTabWidget.South)
self.analysisTabWidget.setTabShape(QTabWidget.Rounded)
self.analysisTabWidget.setDocumentMode(False) # has only effect on OSX ?!
self.analysisTabWidget.resize(300, 200)
# See: https://stackoverflow.com/questions/69152935/adding-the-same-object-to-a-qtabwidget
# See: pyqtgraph/examples/RemoteSpeedTest.py to keep gui responsive when updating a plot (uses multiprocessing)
self.plotTitles = [
'New survey',
'Offsets for inline direction',
'Offsets for x-line direction',
'Azimuth for inline direction',
'Azimuth for x-line direction',
'Stack response for inline direction',
'Stack response for x-line direction',
'Kx-Ky single bin stack response',
'|Offset| distribution in binning area',
'Offset/azimuth distribution in binning area',
'Pattern information',
]
# these plotting widgets have "installEventFilter()" applied to catch the window 'Show' event in "eventFilter()"
# this makes it possible to reroute commands and status from the plotting toolbar buttons to the active plot
self.offTrkWidget = self.createPlotWidget(self.plotTitles[1], 'inline', 'offset', 'm', 'm') # False -> no fixed aspect ratio
self.offBinWidget = self.createPlotWidget(self.plotTitles[2], 'x-line', 'offset', 'm', 'm')
self.aziTrkWidget = self.createPlotWidget(self.plotTitles[3], 'inline', 'angle of incidence', 'm', 'deg', False)
self.aziBinWidget = self.createPlotWidget(self.plotTitles[4], 'x-line', 'angle of incidence', 'm', 'deg', False) # no fixed aspect ratio
self.stkTrkWidget = self.createPlotWidget(self.plotTitles[5], 'inline', '|Kr|', 'm', ' 1/km', False)
self.stkBinWidget = self.createPlotWidget(self.plotTitles[6], 'x-line', '|Kr|', 'm', ' 1/km', False)
self.stkCelWidget = self.createPlotWidget(self.plotTitles[7], 'Kx', 'Ky', '1/km', '1/km')
self.offsetWidget = self.createPlotWidget(self.plotTitles[8], '|offset|', 'frequency', 'm', ' #', False)
self.offAziWidget = self.createPlotWidget(self.plotTitles[9], 'azimuth', '|offset|', 'deg', 'm', False)
self.arraysWidget = self.createPlotWidget(self.plotTitles[10], 'inline', 'x-line', 'm', 'm')
# Create the various views (tabs) on the data
# Use QCodeEditor with a XmlHighlighter instead of a 'plain' QPlainTextEdit
# See: https://github.com/luchko/QCodeEditor/blob/master/QCodeEditor.py
self.textEdit = QCodeEditor(SyntaxHighlighter=XMLHighlighter) # only one widget on Xml-tab; add directly
self.textEdit.document().setModified(False)
self.textEdit.installEventFilter(self) # catch the 'Show' event to connect to toolbar buttons
# The following tabs have multiple widgets per page, start by giving them a simple QWidget
self.tabPatterns = QWidget()
self.tabGeom = QWidget()
self.tabSps = QWidget()
self.tabTraces = QWidget()
self.tabKxKyStack = QWidget()
self.tabGeom.installEventFilter(self) # catch the 'Show' event to connect to toolbar buttons
self.tabSps.installEventFilter(self) # catch the 'Show' event to connect to toolbar buttons
self.tabTraces.installEventFilter(self) # catch the 'Show' event to connect to toolbar buttons
# The following functions have been removed from this file's class definition, to reduce the size of 'roll_main_window.py'
# They now reside in separate source files. Therefore self.createLayoutTab() is now called as createLayoutTab(self) instead.
createLayoutTab(self)
createPatternTab(self)
createGeomTab(self)
createSpsTab(self)
createTraceTableTab(self)
createStackResponseTab(self)
# Add tabs to main tab widget
self.mainTabWidget.addTab(self.layoutWidget, 'Layout')
self.mainTabWidget.addTab(self.tabPatterns, 'Patterns')
self.mainTabWidget.addTab(self.textEdit, 'Xml')
self.mainTabWidget.addTab(self.tabGeom, 'Geometry')
self.mainTabWidget.addTab(self.tabSps, 'SPS import')
self.mainTabWidget.addTab(self.analysisTabWidget, 'Analysis')
self.mainTabWidget.currentChanged.connect(self.onMainTabChange) # active tab changed!
# Add tabs to analysis tab widget
self.analysisTabWidget.addTab(self.tabTraces, 'Trace table')
self.analysisTabWidget.addTab(self.offTrkWidget, 'Offset Inline')
self.analysisTabWidget.addTab(self.offBinWidget, 'Offset X-line')
self.analysisTabWidget.addTab(self.aziTrkWidget, 'Azi Inline')
self.analysisTabWidget.addTab(self.aziBinWidget, 'Azi X-line')
self.analysisTabWidget.addTab(self.stkTrkWidget, 'Stack Inline')
self.analysisTabWidget.addTab(self.stkBinWidget, 'Stack X-line')
self.analysisTabWidget.addTab(self.tabKxKyStack, 'Kx-Ky Stack')
self.analysisTabWidget.addTab(self.offsetWidget, '|O| Histogram')
self.analysisTabWidget.addTab(self.offAziWidget, 'O/A Histogram')
# self.arraysWidget is embedded in the layout of the 'pattern' tab
# self.analysisTabWidget.addTab(self.stkCelWidget, 'Kx-Ky Stack')
# self.analysisTabWidget.currentChanged.connect(self.onAnalysisTabChange) # active tab changed!
self.setCurrentFileName()
# connect actions
self.textEdit.document().modificationChanged.connect(self.setWindowModified) # forward signal to myself, and make some changes
self.setWindowModified(self.textEdit.document().isModified()) # update window status based on document status
self.textEdit.cursorPositionChanged.connect(self.cursorPositionChanged) # to show cursor position in statusbar
self.layoutWidget.scene().sigMouseMoved.connect(self.MouseMovedInPlot)
self.layoutWidget.getViewBox().sigRangeChangedManually.connect(self.mouseBeingDragged) # essential to find plotting state for LOD plotting
self.layoutWidget.plotItem.sigRangeChanged.connect(self.layoutRangeChanged) # to handle changes in tickmarks when zooming
self.actionDebug.setCheckable(True)
self.actionDebug.setChecked(self.debug)
self.actionDebug.setStatusTip('Show debug information in QGIS Python console')
self.actionDebug.triggered.connect(self.viewDebug)
# the following actions are related to the plotWidget
self.actionZoomAll.triggered.connect(self.layoutWidget.autoRange)
self.actionZoomRect.setCheckable(True)
self.actionZoomRect.setChecked(self.rect)
self.actionZoomRect.triggered.connect(self.plotZoomRect)
self.actionAspectRatio.setCheckable(True)
self.actionAspectRatio.setChecked(self.XisY)
self.actionAspectRatio.triggered.connect(self.plotAspectRatio)
self.actionAntiAlias.setCheckable(True)
self.actionAntiAlias.setChecked(self.antiA[0])
self.actionAntiAlias.triggered.connect(self.plotAntiAlias)
self.actionPlotGridX.setCheckable(True)
self.actionPlotGridX.setChecked(self.gridX)
self.actionPlotGridX.triggered.connect(self.plotGridX)
self.actionPlotGridY.setCheckable(True)
self.actionPlotGridY.setChecked(self.gridY)
self.actionPlotGridY.triggered.connect(self.plotGridY)
self.actionProjected.setCheckable(True)
self.actionProjected.setChecked(self.glob)
self.actionProjected.triggered.connect(self.plotProjected)
self.actionRuler.setCheckable(True)
self.actionRuler.setChecked(self.ruler)
self.actionRuler.triggered.connect(self.showRuler)
# actions related to the file menu
for i in range(config.maxRecentFiles):
self.recentFileActions.append(QAction(self, visible=False, triggered=self.fileOpenRecent))
self.menuOpenRecent.addAction(self.recentFileActions[i])
self.actionNew.triggered.connect(self.newFile)
self.actionNewLandSurvey.triggered.connect(self.fileNewLandSurvey)
self.actionNewMarineSurvey.triggered.connect(self.fileNewMarineSurvey)
self.actionOpen.triggered.connect(self.fileOpen)
self.actionImportSPS.triggered.connect(self.fileImportSPS)
self.actionPrint.triggered.connect(self.filePrint)
self.actionSave.triggered.connect(self.fileSave)
self.actionSaveAs.triggered.connect(self.fileSaveAs)
self.actionSettings.triggered.connect(self.fileSettings)
self.textEdit.document().modificationChanged.connect(self.actionSave.setEnabled)
# actions related to file -> export
self.actionExportFoldMap.triggered.connect(self.fileExportFoldMap)
self.actionExportMinOffsets.triggered.connect(self.fileExportMinOffsets)
self.actionExportMaxOffsets.triggered.connect(self.fileExportMaxOffsets)
self.actionExportAnaAsCsv.triggered.connect(self.fileExportAnaAsCsv)
self.actionExportRecAsCsv.triggered.connect(self.fileExportRecAsCsv)
self.actionExportSrcAsCsv.triggered.connect(self.fileExportSrcAsCsv)
self.actionExportRelAsCsv.triggered.connect(self.fileExportRelAsCsv)
self.actionExportRecAsR01.triggered.connect(self.fileExportRecAsR01)
self.actionExportSrcAsS01.triggered.connect(self.fileExportSrcAsS01)
self.actionExportRelAsX01.triggered.connect(self.fileExportRelAsX01)
self.actionExportRpsAsCsv.triggered.connect(self.fileExportRpsAsCsv)
self.actionExportSpsAsCsv.triggered.connect(self.fileExportSpsAsCsv)
self.actionExportXpsAsCsv.triggered.connect(self.fileExportXpsAsCsv)
self.actionExportRpsAsR01.triggered.connect(self.fileExportRpsAsR01)
self.actionExportSpsAsS01.triggered.connect(self.fileExportSpsAsS01)
self.actionExportXpsAsX01.triggered.connect(self.fileExportXpsAsX01)
self.actionExportFoldMapToQGIS.triggered.connect(self.exportBinToQGIS)
self.actionExportMinOffsetsToQGIS.triggered.connect(self.exportMinToQGIS)
self.actionExportMaxOffsetsToQGIS.triggered.connect(self.exportMaxToQGIS)
self.actionExportRmsOffsetsToQGIS.triggered.connect(self.exportRmsToQGIS)
self.actionQuit.triggered.connect(self.close) # closes the window and arrives at CloseEvent()
# actions related to the edit menu
# undo and redo are solely associated with the main xml textEdit
self.actionUndo.triggered.connect(self.textEdit.undo)
self.actionRedo.triggered.connect(self.textEdit.redo)
self.textEdit.document().undoAvailable.connect(self.actionUndo.setEnabled)
self.textEdit.document().redoAvailable.connect(self.actionRedo.setEnabled)
# copy, cut, paste and select-all must be managed by all active widgets
# See: https://stackoverflow.com/questions/40041131/pyqt-global-copy-paste-actions-for-custom-widgets
self.actionCut.triggered.connect(self.cut)
self.actionCopy.triggered.connect(self.copy)
self.actionFind.triggered.connect(self.find)
self.actionPaste.triggered.connect(self.paste)
self.actionSelectAll.triggered.connect(self.selectAll)
# the following setEnabled items need to be re-wired, they are still connected to the textEdit
self.textEdit.copyAvailable.connect(self.actionCut.setEnabled)
self.textEdit.copyAvailable.connect(self.actionCopy.setEnabled)
# actions related to the view menu
self.actionRefresh.triggered.connect(self.UpdateAllViews)
self.actionAbout.triggered.connect(self.OnAbout)
self.actionLicense.triggered.connect(self.OnLicense)
self.actionHighDpi.triggered.connect(self.OnHighDpi)
# actions related to the processing menu
self.actionBasicBinFromTemplates.triggered.connect(self.basicBinFromTemplates)
self.actionFullBinFromTemplates.triggered.connect(self.fullBinFromTemplates)
self.actionBasicBinFromGeometry.triggered.connect(self.basicBinFromGeometry)
self.actionFullBinFromGeometry.triggered.connect(self.fullBinFromGeometry)
self.actionBasicBinFromSps.triggered.connect(self.basicBinFromSps)
self.actionFullBinFromSps.triggered.connect(self.fullBinFromSps)
self.actionGeometryFromTemplates.triggered.connect(self.createGeometryFromTemplates)
self.actionStopThread.triggered.connect(self.stopWorkerThread)
self.enableProcessingMenuItems() # enables processing menu items except 'stop processing thread'
# actions related to geometry items to be displayed
self.actionTemplates.triggered.connect(self.plotLayout)
self.actionRecPoints.triggered.connect(self.plotLayout)
self.actionSrcPoints.triggered.connect(self.plotLayout)
self.actionRpsPoints.triggered.connect(self.plotLayout)
self.actionSpsPoints.triggered.connect(self.plotLayout)
self.actionAllPoints.triggered.connect(self.plotLayout)
# enable/disable various actions
self.actionClose.setEnabled(False)
self.actionSave.setEnabled(self.textEdit.document().isModified())
# self.actionSaveAs.setEnabled((self.textEdit.document().blockCount() > 1)) # need at least one line of text to save the document
self.actionSaveAs.setEnabled((True)) # need at least one line of text to save the document
self.actionUndo.setEnabled(self.textEdit.document().isUndoAvailable())
self.actionRedo.setEnabled(self.textEdit.document().isRedoAvailable())
self.actionCut.setEnabled(False)
self.actionCopy.setEnabled(False)
self.actionPaste.setEnabled(self.clipboardHasText())
self.updateMenuStatus(True) # keep menu status in sync with program's state
# make the main tab widget the central widget
self.setCentralWidget(self.mainTabWidget)
self.posWidgetStatusbar = QLabel('(x, y): (0.00, 0.00)')
self.statusbar.addPermanentWidget(self.posWidgetStatusbar, stretch=0) # widget in bottomright corner of statusbar
self.parseText(exampleSurveyXmlText())
self.textEdit.setPlainText(exampleSurveyXmlText())
self.textEdit.moveCursor(QTextCursor.Start)
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# third docking pane, used to display survey properties
# defined late, as it needs access the loaded survey object
self.dockProperty = QDockWidget('Property pane')
self.dockProperty.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
self.dockProperty.setStyleSheet('QDockWidget::title {background : lightblue;}')
# setup the ParameterTree object
self.paramTree = pg.parametertree.ParameterTree(showHeader=True) # define parameter tree widget
self.paramTree.header().setSectionResizeMode(QHeaderView.ResizeMode.Interactive)
self.paramTree.header().resizeSection(0, 280)
self.registerParameters()
self.resetSurveyProperties() # get the parameters into the parameter tree
self.propertyWidget = QWidget() # placeholder widget to generate a layout
self.propertyLayout = QVBoxLayout() # required vertical layout
buttons = QDialogButtonBox.Ok | QDialogButtonBox.Cancel | QDialogButtonBox.Apply
self.propertyButtonBox = QDialogButtonBox(buttons) # define 3 buttons to handle property changes
# connect 3 buttons (signals) to their event handlers (slots)
self.propertyButtonBox.accepted.connect(self.applyPropertyChangesAndHide)
self.propertyButtonBox.rejected.connect(self.resetSurveyProperties)
self.propertyButtonBox.button(QDialogButtonBox.Apply).clicked.connect(self.applyPropertyChanges)
self.propertyLayout.addWidget(self.paramTree) # add parameter tree to layout
self.propertyLayout.addStretch() # add some stretch towards 3 buttons
self.propertyLayout.addWidget(self.propertyButtonBox) # add 3 buttons
self.propertyWidget.setLayout(self.propertyLayout) # add layout to widget
self.dockProperty.setWidget(self.propertyWidget)
self.addDockWidget(Qt.RightDockWidgetArea, self.dockProperty) # add docking panel to main window
self.dockProperty.toggleViewAction().setShortcut(QKeySequence('Ctrl+Alt+p'))
self.menu_View.addAction(self.dockProperty.toggleViewAction()) # show/hide as requested
self.menu_View.addSeparator()
self.menu_View.addAction(self.fileBar.toggleViewAction())
self.menu_View.addAction(self.editBar.toggleViewAction())
self.menu_View.addAction(self.graphBar.toggleViewAction())
self.menu_View.addAction(self.moveBar.toggleViewAction())
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
self.plotLayout()
readSettings(self)
self.updateRecentFileActions() # update the MRU file menu actions, with info from readSettings()
self.appendLogMessage('Plugin : Started')
self.statusbar.showMessage('Ready', 3000)
# deal with pattern selection for display & kxky plotting
def onPattern1IndexChanged(self):
self.plotPatterns()
def onPattern2IndexChanged(self):
self.plotPatterns()
def onActionPatternLayoutTriggered(self):
self.patternLayout = True
self.plotPatterns()
def onActionPattern_kx_kyTriggered(self):
self.patternLayout = False
self.plotPatterns()
# deal with pattern selection for bin stack response
def onStackPatternIndexChanged(self):
nX = self.spiderPoint.x() # get x, y indices into bin array
nY = self.spiderPoint.y()
if self.spiderPoint.x() < 0:
return
if self.spiderPoint.y() < 0:
return
if self.survey.binTransform is None:
return
invBinTransform, _ = self.survey.binTransform.inverted() # need to go from bin nr's to cmp(x, y)
cmpX, cmpY = invBinTransform.map(nX, nY) # get local coordinates from line and point indices
stkX, stkY = self.survey.st2Transform.map(cmpX, cmpY) # get the corresponding bin and stake numbers
self.plotStkCel(nX, nY, stkX, stkY)
def eventFilter(self, source, event):
if event.type() == QEvent.Show: # do 'cheap' test first
if isinstance(source, pg.PlotWidget): # do 'expensive' test next
with contextlib.suppress(RuntimeError): # rewire zoomAll button
self.actionZoomAll.triggered.disconnect()
self.actionZoomAll.triggered.connect(source.autoRange)
plotIndex = self.getVisiblePlotIndex(source) # update toolbar status
if plotIndex is not None:
self.actionZoomAll.setEnabled(True) # useful for all plots
self.actionZoomRect.setEnabled(True) # useful for all plots
self.actionAspectRatio.setEnabled(True) # useful for all plots
self.actionAntiAlias.setEnabled(True) # useful for plots only
self.actionRuler.setEnabled(plotIndex == 0) # useful for 1st plot only
self.actionProjected.setEnabled(plotIndex == 0) # useful for 1st plot only
self.actionAntiAlias.setChecked(self.antiA[plotIndex]) # useful for all plots