PaIRS-UniNa 0.2.7__cp311-cp311-win_amd64.whl → 0.2.11__cp311-cp311-win_amd64.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.
Files changed (50) hide show
  1. PaIRS_UniNa/Calibration_Tab.py +40 -24
  2. PaIRS_UniNa/Changes.txt +50 -0
  3. PaIRS_UniNa/Explorer.py +257 -77
  4. PaIRS_UniNa/FolderLoop.py +196 -6
  5. PaIRS_UniNa/Input_Tab.py +160 -53
  6. PaIRS_UniNa/Input_Tab_CalVi.py +11 -12
  7. PaIRS_UniNa/Input_Tab_tools.py +30 -28
  8. PaIRS_UniNa/Output_Tab.py +1 -3
  9. PaIRS_UniNa/PaIRS_pypacks.py +171 -67
  10. PaIRS_UniNa/Process_Tab.py +19 -15
  11. PaIRS_UniNa/Process_Tab_Disp.py +8 -1
  12. PaIRS_UniNa/SPIVCalHelp.py +155 -0
  13. PaIRS_UniNa/Saving_tools.py +3 -0
  14. PaIRS_UniNa/TabTools.py +201 -9
  15. PaIRS_UniNa/Vis_Tab.py +221 -65
  16. PaIRS_UniNa/Vis_Tab_CalVi.py +139 -12
  17. PaIRS_UniNa/Whatsnew.py +4 -3
  18. PaIRS_UniNa/_PaIRS_PIV.pyd +0 -0
  19. PaIRS_UniNa/__init__.py +3 -3
  20. PaIRS_UniNa/addwidgets_ps.py +773 -97
  21. PaIRS_UniNa/calibView.py +5 -2
  22. PaIRS_UniNa/gPaIRS.py +307 -48
  23. PaIRS_UniNa/icons/closeAllFloat.png +0 -0
  24. PaIRS_UniNa/icons/defaultWinSize.png +0 -0
  25. PaIRS_UniNa/icons/dockVis.png +0 -0
  26. PaIRS_UniNa/icons/dockVis_disable.png +0 -0
  27. PaIRS_UniNa/icons/floatingVisSize.png +0 -0
  28. PaIRS_UniNa/icons/folder_loop_cleanup.png +0 -0
  29. PaIRS_UniNa/icons/folder_loop_cleanup_off.png +0 -0
  30. PaIRS_UniNa/icons/fullWinsize.png +0 -0
  31. PaIRS_UniNa/icons/icon_PaIRS.ico +0 -0
  32. PaIRS_UniNa/icons/information.png +0 -0
  33. PaIRS_UniNa/icons/information2.png +0 -0
  34. PaIRS_UniNa/icons/scan_path_loop.png +0 -0
  35. PaIRS_UniNa/icons/scan_path_loop_off.png +0 -0
  36. PaIRS_UniNa/icons/smallWinSize.png +0 -0
  37. PaIRS_UniNa/icons/spiv_setup_no.png +0 -0
  38. PaIRS_UniNa/icons/spiv_setup_ok.png +0 -0
  39. PaIRS_UniNa/icons/undockVis.png +0 -0
  40. PaIRS_UniNa/procTools.py +46 -1
  41. PaIRS_UniNa/rqrdpckgs.txt +7 -7
  42. PaIRS_UniNa/tabSplitter.py +6 -1
  43. PaIRS_UniNa/ui_Calibration_Tab.py +92 -59
  44. PaIRS_UniNa/ui_gPairs.py +92 -50
  45. PaIRS_UniNa/ui_infoPaIRS.py +8 -8
  46. PaIRS_UniNa/whatsnew.txt +2 -3
  47. {pairs_unina-0.2.7.dist-info → pairs_unina-0.2.11.dist-info}/METADATA +6 -8
  48. {pairs_unina-0.2.7.dist-info → pairs_unina-0.2.11.dist-info}/RECORD +50 -33
  49. {pairs_unina-0.2.7.dist-info → pairs_unina-0.2.11.dist-info}/WHEEL +1 -1
  50. {pairs_unina-0.2.7.dist-info → pairs_unina-0.2.11.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,155 @@
1
+ from PySide6 import QtWidgets, QtGui, QtCore
2
+ import sys
3
+ from .PaIRS_pypacks import icons_path, fontPixelSize
4
+
5
+ def showSPIVCalHelp(parent=None,disable_callback=None):
6
+ """
7
+ Shows an informational dialog explaining the correct calibration setup
8
+ for stereoscopic PIV in PaIRS, ensuring compatibility with disparity
9
+ correction and full stereoscopic reconstruction.
10
+ """
11
+ dlg = QtWidgets.QDialog(parent)
12
+ dlg.setWindowTitle("Guidelines for stereoscopic PIV calibration")
13
+ #dlg.resize(900, 750)
14
+ dlg.setMinimumWidth(900)
15
+ dlg.setMinimumHeight(750)
16
+
17
+ main_layout = QtWidgets.QVBoxLayout(dlg)
18
+
19
+ # --- Explanatory text (English, corrected axes) ---
20
+ text = (
21
+ "For stereoscopic PIV, PaIRS assumes that:<br><br>"
22
+
23
+ "&nbsp;&nbsp;• the <b>calibration plate defines "
24
+ "the x–y plane</b> of the calibration coordinate system;<br>"
25
+
26
+ "&nbsp;&nbsp;• the <b>x-axis</b> is <b>aligned with the stereoscopic baseline</b>, i.e. "
27
+ "the direction along which the projections of the two camera viewing rays diverge "
28
+ "on the calibration plate (the dominant disparity direction);<br>"
29
+
30
+ "&nbsp;&nbsp;• the <b>y-axis</b> is then defined as the axis <b>perpendicular to the plane containing "
31
+ "the two cameras</b> (i.e. perpendicular to the triangulation plane formed by the two "
32
+ "optical axes);<br>"
33
+
34
+ "&nbsp;&nbsp;• the <b>z-axis is normal to the plate</b> (typically pointing towards the cameras).<br><br>"
35
+
36
+ "To ensure full compatibility with the operations performed in the disparity correction step and the stereoscopic reconstruction,"
37
+ " the calibration procedure must always adhere to the above coordinate convention.<br>"
38
+ " The example below shows a <b>correct</b> configuration (left) and an <b>incorrect</b> one (right).<br>"
39
+ )
40
+
41
+ text_label = QtWidgets.QLabel()
42
+ text_label.setWordWrap(True)
43
+ text_label.setTextFormat(QtCore.Qt.RichText)
44
+ text_label.setText(f"<div>{text}</div>")
45
+ main_layout.addWidget(text_label)
46
+ font=dlg.font()
47
+ font.setPixelSize(fontPixelSize+4)
48
+ text_label.setFont(font)
49
+
50
+ # --- Side-by-side images ---
51
+ img_layout = QtWidgets.QHBoxLayout()
52
+ img_layout.setSpacing(10)
53
+ main_layout.addLayout(img_layout)
54
+
55
+ # Paths to images (adjust to match PaIRS resources folder)
56
+ img_ok_path = icons_path+"spiv_setup_ok.png"
57
+ img_no_path = icons_path+"spiv_setup_no.png"
58
+
59
+ # --- Correct configuration image ---
60
+ ok_widget = QtWidgets.QVBoxLayout()
61
+ ok_caption = QtWidgets.QLabel()
62
+ caption_text = "<b>Correct configuration (x–z stereo plane)</b>"
63
+ ok_caption.setText(f"<div>{caption_text}</div>")
64
+ ok_caption.setFont(font)
65
+ ok_caption.setTextFormat(QtCore.Qt.RichText)
66
+ ok_caption.setAlignment(QtCore.Qt.AlignCenter)
67
+
68
+ ok_label_img = QtWidgets.QLabel()
69
+ ok_label_img.setAlignment(QtCore.Qt.AlignCenter)
70
+ ok_label_img.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
71
+ ok_label_img.setScaledContents(True)
72
+
73
+ ok_pix = QtGui.QPixmap(img_ok_path)
74
+ if not ok_pix.isNull():
75
+ ok_pix = ok_pix.scaledToWidth(400, QtCore.Qt.SmoothTransformation)
76
+ ok_label_img.setPixmap(ok_pix)
77
+ ok_label_img.setFixedSize(ok_pix.width(),ok_pix.height())
78
+
79
+ ok_widget.addWidget(ok_caption)
80
+ ok_widget.addWidget(ok_label_img)
81
+ img_layout.addLayout(ok_widget)
82
+
83
+ # --- Incorrect configuration image ---
84
+ no_widget = QtWidgets.QVBoxLayout()
85
+ no_caption = QtWidgets.QLabel()
86
+ caption_text = "<b>Incorrect configuration</b>"
87
+ no_caption.setText(f"<div'>{caption_text}</div>")
88
+ no_caption.setFont(font)
89
+ no_caption.setTextFormat(QtCore.Qt.RichText)
90
+ no_caption.setAlignment(QtCore.Qt.AlignCenter)
91
+
92
+ no_label_img = QtWidgets.QLabel()
93
+ no_label_img.setAlignment(QtCore.Qt.AlignCenter)
94
+ no_label_img.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
95
+ no_label_img.setScaledContents(True)
96
+
97
+ no_pix = QtGui.QPixmap(img_no_path)
98
+ if not no_pix.isNull():
99
+ no_pix = no_pix.scaledToWidth(400, QtCore.Qt.SmoothTransformation)
100
+ no_label_img.setPixmap(no_pix)
101
+ no_label_img.setFixedSize(no_pix.width(),no_pix.height())
102
+
103
+ no_widget.addWidget(no_caption)
104
+ no_widget.addWidget(no_label_img)
105
+ img_layout.addLayout(no_widget)
106
+
107
+ # Prevent bottom clipping: allow images row to shrink/expand as needed
108
+ main_layout.setStretch(0, 1)
109
+ main_layout.setStretch(1, 0)
110
+
111
+ # Create a horizontal layout for buttons
112
+ button_layout = QtWidgets.QHBoxLayout()
113
+
114
+ # Spacer pushes OK to the right
115
+ button_layout.addStretch(1)
116
+
117
+ # Left button
118
+ if disable_callback is not None:
119
+ button_disable = QtWidgets.QPushButton("Don't show this message again")
120
+ def on_disable():
121
+ disable_callback()
122
+ dlg.accept()
123
+ button_disable.clicked.connect(on_disable)
124
+ button_layout.addWidget(button_disable)
125
+
126
+ # Right button (OK)
127
+ button_ok = QtWidgets.QPushButton("OK")
128
+ def on_ok():
129
+ dlg.accept()
130
+ button_ok.clicked.connect(on_ok)
131
+ button_layout.addWidget(button_ok)
132
+
133
+ # Add the layout to the dialog
134
+ main_layout.addSpacing(12)
135
+ main_layout.addLayout(button_layout)
136
+
137
+ button_ok.setDefault(True)
138
+ button_ok.setFocus()
139
+
140
+ main_layout.setContentsMargins(20, 25, 20, 25) # slightly larger bottom margin to avoid macOS clipping
141
+
142
+ dlg.exec()
143
+
144
+ if __name__ == "__main__":
145
+ # QApplication MUST be created before any QWidget
146
+ app = QtWidgets.QApplication(sys.argv)
147
+
148
+ # Parent window (optional)
149
+ main_window = QtWidgets.QMainWindow()
150
+ main_window.show()
151
+
152
+ # Show the SPIV calibration dialog
153
+ showSPIVCalHelp(parent=main_window)
154
+
155
+ sys.exit(app.exec())
@@ -51,6 +51,7 @@ class GPApar(TABpar):
51
51
  self.WindowState = None
52
52
  self.SplitterSizes = {}
53
53
  self.ScrollAreaValues = {}
54
+ self.tabWinPar = {}
54
55
 
55
56
  #legacy
56
57
  self.FloatGeometry = []
@@ -64,6 +65,8 @@ class GPApar(TABpar):
64
65
 
65
66
  self.printTypes = printTypes
66
67
  self.NumCores = 0
68
+ self.globalVals = {}
69
+ self.globalExceptions = {'Calibration': ['FlagSPIVCal']}
67
70
 
68
71
  self.stateFields=[f for f,_ in self.__dict__.items() if f not in self.infoFields]
69
72
 
PaIRS_UniNa/TabTools.py CHANGED
@@ -468,6 +468,7 @@ class gPaIRS_Tab(QWidget):
468
468
  FlagPreventAddPrev=False
469
469
  if callback1: FlagPreventAddPrev=debugFun(callback1,f'Error callback1 ({tip}): ')
470
470
  if callback2:
471
+ if FlagPreventAddPrev is None: FlagPreventAddPrev=False
471
472
  self.FlagAsyncCallEvaluation=True
472
473
  self.disableTab(True)
473
474
 
@@ -488,7 +489,7 @@ class gPaIRS_Tab(QWidget):
488
489
  return
489
490
  return callback
490
491
 
491
- @Slot(str)
492
+ @Slot(str,bool,bool)
492
493
  def callback2_end(self,tip,FlagSettingPar,FlagPreventAddPrev):
493
494
  pri.Coding.green(f'{"*"*50}\nCallback <{self.TABname}>: {tip}')
494
495
  if tip=='Null Callback':
@@ -523,6 +524,7 @@ class gPaIRS_Tab(QWidget):
523
524
  Messagge='This process step is currently in execution. To modify it, you need to stop processing and then reset it and all the subsequent steps.'
524
525
  warningDialog(self.gui,Messagge)
525
526
  elif flagRun!=0:
527
+ self.flagGuiSplash=True
526
528
  if self.gui.FlagRun:
527
529
  Messagge='This process step has already been executed. To modify it, you need to stop processing and then reset it and all the subsequent steps.'
528
530
  warningDialog(self.gui,Messagge)
@@ -531,11 +533,12 @@ class gPaIRS_Tab(QWidget):
531
533
  def reset_step_online():
532
534
  TABpar_ind.copyfrom(self.TABpar)
533
535
  self.gui.reset_step(self.TABpar.ind)
536
+ self.flagGuiSplash=False
534
537
  return
535
538
  warningDialog(self.gui,Messagge,addButton={'Reset step!': reset_step_online})
536
539
 
537
540
 
538
- if flagRun!=0 or len(self.TABpar.link)>0:
541
+ if (flagRun!=0 or len(self.TABpar.link)>0) and self.flagGuiSplash:
539
542
  self.TABpar.copyfrom(TABpar_ind)
540
543
  originalStyleSheet=self.gui.styleSheet()
541
544
  self.gui.setStyleSheet(f'background: {self.palette().color(QPalette.ColorRole.Text).name()} ;') #dcdcdc
@@ -706,16 +709,130 @@ class gPaIRS_Tab(QWidget):
706
709
  lab.setFont(font)
707
710
 
708
711
  def setTABWarnLabel(self):
709
- self.ui.name_tab.setFixedWidth(self.ui.name_tab.sizeHint().width())
710
- self.ui.label_done.setPixmap(self.pixmap_done if self.TABpar.OptionDone==1 else self.pixmap_warnc)
711
- self.ui.label_done.setToolTip(self.TABpar.warningMessage)
712
-
712
+ if hasattr(self.ui,'label_done'):
713
+ self.ui.name_tab.setFixedWidth(self.ui.name_tab.sizeHint().width())
714
+ self.ui.label_done.setPixmap(self.pixmap_done if self.TABpar.OptionDone==1 else self.pixmap_warnc)
715
+ self.ui.label_done.setToolTip(self.TABpar.warningMessage)
716
+
717
+ def syncPrevGlobalFields(self, ref_vals=None, include_bases=False, exceptions=[], FlagSync=True):
718
+ """
719
+ Sync class-level fields (declared in class bodies, e.g. PROpar.mode = mode_init)
720
+ across all TABpar-like instances inside self.TABpar_prev (nested lists / None / TABpar).
721
+
722
+ Parameters
723
+ ----------
724
+ ref : object | None
725
+ Reference TABpar instance whose values will be copied (default: self.TABpar).
726
+ include_bases : bool
727
+ If True, include class-level fields declared in base classes too.
728
+ If False, include ONLY fields declared in the concrete class (e.g., PROpar only).
729
+ """
730
+
731
+ ref = getattr(self, "TABpar", None)
732
+ if ref is None:
733
+ return []
734
+ ref_cls = type(ref)
735
+
736
+ # Decide which class we consider as "TABpar-like"
737
+ # If your tabs store subclasses of a known ParClass, use that; otherwise fallback to TABpar.
738
+ par_base = getattr(self, "ParClass", None)
739
+ if par_base is None:
740
+ par_base = TABpar # assumes TABpar is in scope in TabTools.py
741
+
742
+ # Reference object
743
+ if ref_vals is None:
744
+
745
+ # Collect class-level fields declared in class bodies
746
+ def _class_fields(cls):
747
+ if include_bases:
748
+ classes = [C for C in cls.mro() if C not in (object,)]
749
+ else:
750
+ classes = [cls]
751
+
752
+ out = []
753
+ for C in classes:
754
+ for name, val in C.__dict__.items():
755
+ if name.startswith("__"):
756
+ continue
757
+ # Skip methods / descriptors
758
+ if callable(val) or isinstance(val, (staticmethod, classmethod, property)):
759
+ continue
760
+ out.append(name)
761
+
762
+ # Unique preserving order
763
+ seen = set()
764
+ fields = []
765
+ for n in out:
766
+ if n not in seen:
767
+ seen.add(n)
768
+ fields.append(n)
769
+ return fields
770
+
771
+ fields = _class_fields(ref_cls)
772
+
773
+ # Build reference values (prefer instance override, otherwise class default)
774
+ ref_vals = {}
775
+ #pri.Info.green(f'{self.TABname}:')
776
+ for f in fields:
777
+ try:
778
+ ref_vals[f] = getattr(ref, f)
779
+ #pri.Info.green(f'{f} = {ref_vals[f]}')
780
+ except Exception:
781
+ pass
782
+ #pri.Info.green('\n')
783
+
784
+ if not FlagSync: return ref_vals
785
+
786
+ # Exclude exception fields (if any)
787
+ if exceptions:
788
+ exc = set(exceptions)
789
+ ref_vals = {k: v for k, v in ref_vals.items() if k not in exc}
790
+
791
+ # Walk nested structure and patch instances
792
+ def _walk(node, ParBase=par_base): # <-- bind ParBase safely here
793
+ if node is None:
794
+ return
795
+ if isinstance(node, ParBase):
796
+ for f, v in ref_vals.items():
797
+ try:
798
+ setattr(node, f, v)
799
+ except Exception:
800
+ pass
801
+ return
802
+ if isinstance(node, (list, tuple)):
803
+ for it in node:
804
+ _walk(it, ParBase)
805
+ return
806
+ if isinstance(node, dict):
807
+ for it in node.values():
808
+ _walk(it, ParBase)
809
+ return
810
+
811
+ _walk(getattr(self, "TABpar_prev", None))
812
+
813
+ # Set class-level (global) fields ONCE
814
+ for C in (ref_cls, self.TABpar, self.TABpar_old):
815
+ if C is None:
816
+ continue
817
+ for f, v in ref_vals.items():
818
+ try:
819
+ setattr(C, f, v)
820
+ except Exception:
821
+ pass
822
+ return ref_vals
823
+
713
824
  #*************************************************** Undo/redo
714
825
  def adjustTABparInd(self):
715
826
  TABpar_ind=self.TABpar_at(self.TABpar.ind)
716
827
  if TABpar_ind:
717
828
  TABpar_ind.copyfrom(self.TABpar)
718
829
 
830
+ def adjustFromTABparInd(self,ind=None):
831
+ if ind is None: ind=self.TABpar.ind
832
+ TABpar_ind=self.TABpar_at(ind)
833
+ if TABpar_ind:
834
+ self.TABpar.copyfrom(TABpar_ind)
835
+
719
836
  def gen_TABpar(self,ind,FlagSet=True,FlagEmptyPrev=False,FlagNone=False,FlagInsert=-1,Process=None,Step=None):
720
837
  Prev=prev=self.TABpar_prev if FlagSet else []
721
838
 
@@ -956,6 +1073,7 @@ class gPaIRS_Tab(QWidget):
956
1073
  d=0
957
1074
 
958
1075
  menu=QMenu(b)
1076
+ menu.setStyleSheet(gPaIRS_QMenu_style)
959
1077
  act=[]
960
1078
  nur=len(krange)
961
1079
  flag=nur==Num_Prevs_back_forw
@@ -989,7 +1107,6 @@ class gPaIRS_Tab(QWidget):
989
1107
  if self.TABpar.ind[:-1]==ind[:-1]:
990
1108
  self.TABpar.ind[-1]=0
991
1109
 
992
-
993
1110
  #*************************************************** Special spin boxes (x,y,w,h)
994
1111
  def setMinMaxSpinxywh(self):
995
1112
  self.ui.spin_x.setMinimum(0)
@@ -1062,9 +1179,16 @@ def setupWid(self:gPaIRS_Tab,FlagFontSize=True):
1062
1179
 
1063
1180
  if hasattr(self,'widgets'): widgets=self.widgets
1064
1181
  else: widgets=self.findChildren(QWidget)
1182
+ if isinstance(widgets[0],list):
1183
+ widgets=[w for wi in widgets for w in wi]
1065
1184
  widgets+=self.findChildren(CollapsibleBox)
1185
+ widgets+=self.findChildren(QTextEdit)
1066
1186
  for w in widgets:
1067
1187
  w:QToolButton
1188
+ try:
1189
+ w.objectName()
1190
+ except:
1191
+ continue
1068
1192
  if hasattr(w,'toolTip'):
1069
1193
  tooltip=toPlainText(w.toolTip())
1070
1194
  if hasattr(w,'shortcut'):
@@ -1076,15 +1200,58 @@ def setupWid(self:gPaIRS_Tab,FlagFontSize=True):
1076
1200
  w.setToolTip(tooltip)
1077
1201
  #if hasattr(w,'statusTip'):
1078
1202
  w.setStatusTip(tooltip)
1079
-
1080
1203
  if hasattr(w,'setup'):
1081
1204
  w.setup()
1082
1205
  if hasattr(w,'setup2'):
1083
1206
  w.setup2()
1084
1207
 
1085
- if isinstance(w,QToolButton) or isinstance(w,QPushButton):
1208
+ if isinstance(w,QToolButton) or isinstance(w,QPushButton) or isinstance(w,QCheckBox) or isinstance(w,QRadioButton) or isinstance(w,QComboBox):
1086
1209
  if w.cursor().shape()==Qt.CursorShape.ArrowCursor:
1087
1210
  w.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
1211
+ if isinstance(w,QComboBox):
1212
+ w.setView(QListView(w))
1213
+ w.setStyleSheet(gPaIRS_Completer_style.replace("QAbstractItemView","QComboBox QAbstractItemView"))
1214
+ if isinstance(w,QToolButton) or isinstance(w,QPushButton):
1215
+ if not w.icon().isNull():
1216
+ size = w.iconSize()
1217
+ new_size = QSize(
1218
+ max(1, size.width()),
1219
+ max(1, size.height())
1220
+ )
1221
+ w.setIconSize(new_size)
1222
+ if (isinstance(w,QToolButton) or isinstance(w,QPushButton) or w.objectName()=='logo') and not isinstance(w,HoverZoomToolButton) and w.metaObject().className() not in ('DraggableButton','RichTextPushButton') and w.objectName() not in ('binButton','CollapsibleBox_toggle','StartingPage_Button','button_dockVis'):
1223
+ if w.objectName() in ('logo','title_icon','workspace_icon'):
1224
+ apply_hover_glow_label(w)
1225
+ w.default_stylesheet=w.styleSheet()
1226
+ else:
1227
+ apply_native_hover_glow(w)
1228
+ if w.metaObject().className() == "RichTextPushButton" or isinstance(w,QComboBox):
1229
+ apply_native_hover_glow(w)
1230
+ if isinstance(w,QCheckBox) or isinstance(w,QRadioButton):
1231
+ style=f"{w.metaObject().className()}{f'::hover{{ background-color: {PaIRS_ghostblue}; border-radius: 6px;}}'}"
1232
+ w.setStyleSheet(style)
1233
+ if isinstance(w,QSlider):
1234
+ w.setMouseTracking(True)
1235
+ cursor_filter = SliderHandleCursorFilter(w)
1236
+ w.installEventFilter(cursor_filter)
1237
+ apply_native_hover_glow(w,borderRadius=6,glowSize=2)
1238
+ if w.objectName()=='log' and hasattr(self,'gui'):
1239
+ base=f"""
1240
+ QTextEdit {{
1241
+ background-color: #000000;
1242
+ color: #FFFFFF;
1243
+ border: 1px solid #2a2a2a;
1244
+ border-radius: 6px;
1245
+
1246
+ padding: 2px;
1247
+
1248
+ selection-background-color: {PaIRS_blue};
1249
+ selection-color: #FFFFFF;
1250
+ }}
1251
+ """
1252
+ w.setStyleSheet(base + "\n" + gPaIRS_QMenu_style)
1253
+ if isinstance(w,MyQLineEdit) or isinstance(w,QSpinBox) or isinstance(w,QDoubleSpinBox):
1254
+ install_right_click_menu(w)
1088
1255
 
1089
1256
  for sname in ('range_from','range_to','x','y','w','h'):
1090
1257
  if hasattr(self.ui,"spin_"+sname):
@@ -1202,6 +1369,31 @@ def adjustFont(self:QLabel):
1202
1369
 
1203
1370
  self.setFont(font)
1204
1371
 
1372
+ def mouseRightPressEvent(self, event):
1373
+ self.FlagRightButtonPress = event.type() == QEvent.MouseButtonPress and event.button() == Qt.RightButton
1374
+ return type(self).mousePressEvent(self, event)
1375
+
1376
+ def focusOutEvent_preserve_on_right_click(self, event):
1377
+ if getattr(self, "FlagRightButtonPress", True):
1378
+ event.accept()
1379
+ self.FlagRightButtonPress = False
1380
+ else:
1381
+ type(self).focusOutEvent(self, event)
1382
+ return
1383
+
1384
+ def mouseReleaseEvent_preserve_on_right_click(self, event):
1385
+ if getattr(self, "FlagRightButtonPress", True):
1386
+ event.accept()
1387
+ else:
1388
+ type(self).mouseReleaseEvent(self, event)
1389
+ return
1390
+
1391
+ def install_right_click_menu(w):
1392
+ w.FlagRightButtonPress = False
1393
+ w.mousePressEvent = types.MethodType(mouseRightPressEvent, w)
1394
+ w.focusOutEvent = types.MethodType(focusOutEvent_preserve_on_right_click, w)
1395
+ w.mouseReleaseEvent = types.MethodType(mouseReleaseEvent_preserve_on_right_click, w)
1396
+
1205
1397
  #*************************************************** Other
1206
1398
  def iterateList(l,value):
1207
1399
  if type(l)==list: