PaIRS-UniNa 0.2.7__cp312-cp312-macosx_11_0_universal2.whl → 0.2.10__cp312-cp312-macosx_11_0_universal2.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 (38) hide show
  1. PaIRS_UniNa/Calibration_Tab.py +16 -0
  2. PaIRS_UniNa/Changes.txt +39 -0
  3. PaIRS_UniNa/Explorer.py +311 -75
  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 +17 -15
  8. PaIRS_UniNa/Output_Tab.py +1 -3
  9. PaIRS_UniNa/PaIRS_pypacks.py +63 -65
  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 +2 -0
  14. PaIRS_UniNa/TabTools.py +165 -6
  15. PaIRS_UniNa/Vis_Tab.py +50 -22
  16. PaIRS_UniNa/Vis_Tab_CalVi.py +1 -2
  17. PaIRS_UniNa/Whatsnew.py +4 -3
  18. PaIRS_UniNa/_PaIRS_PIV.so +0 -0
  19. PaIRS_UniNa/__init__.py +3 -3
  20. PaIRS_UniNa/addwidgets_ps.py +570 -70
  21. PaIRS_UniNa/gPaIRS.py +118 -17
  22. PaIRS_UniNa/icons/folder_loop_cleanup.png +0 -0
  23. PaIRS_UniNa/icons/folder_loop_cleanup_off.png +0 -0
  24. PaIRS_UniNa/icons/information.png +0 -0
  25. PaIRS_UniNa/icons/information2.png +0 -0
  26. PaIRS_UniNa/icons/scan_path_loop.png +0 -0
  27. PaIRS_UniNa/icons/scan_path_loop_off.png +0 -0
  28. PaIRS_UniNa/icons/spiv_setup_no.png +0 -0
  29. PaIRS_UniNa/icons/spiv_setup_ok.png +0 -0
  30. PaIRS_UniNa/procTools.py +46 -1
  31. PaIRS_UniNa/rqrdpckgs.txt +7 -7
  32. PaIRS_UniNa/ui_Calibration_Tab.py +92 -59
  33. PaIRS_UniNa/ui_gPairs.py +8 -8
  34. PaIRS_UniNa/whatsnew.txt +2 -3
  35. {pairs_unina-0.2.7.dist-info → pairs_unina-0.2.10.dist-info}/METADATA +7 -8
  36. {pairs_unina-0.2.7.dist-info → pairs_unina-0.2.10.dist-info}/RECORD +38 -30
  37. {pairs_unina-0.2.7.dist-info → pairs_unina-0.2.10.dist-info}/WHEEL +0 -0
  38. {pairs_unina-0.2.7.dist-info → pairs_unina-0.2.10.dist-info}/top_level.txt +0 -0
PaIRS_UniNa/Explorer.py CHANGED
@@ -14,11 +14,11 @@ MONTH_ABBREVIATIONS = {
14
14
  1: "Jan", 2: "Feb", 3: "Mar", 4: "Apr", 5: "May", 6: "Jun",
15
15
  7: "Jul", 8: "Aug", 9: "Sep", StepTypes.cal: "Oct", 11: "Nov", 12: "Dec"
16
16
  }
17
- projectActionButtonSize=[20,24]
18
- processActionButtonSize=[20,24]
17
+ projectActionButtonSize=[20,25]
18
+ processActionButtonSize=[20,25]
19
19
  actionButtonSpacing=3
20
- processButtonSize=[40,40] #icon, button
21
- stepButtonSize=[32,32]
20
+ processButtonSize=[40,50] #icon, button
21
+ stepButtonSize=[32,40]
22
22
  headerHeight=24
23
23
  titleHeight=22
24
24
  subtitleHeight=14
@@ -323,12 +323,28 @@ class ModernSwitch(QWidget):
323
323
  self._checked = True
324
324
  par.FlagQueue=self._checked
325
325
 
326
+ # --- HOVER SETUP ---
327
+ self.setAttribute(Qt.WA_Hover, True)
328
+ self.setMouseTracking(True)
329
+ self._hovered = False
330
+ self._border_width = 1
331
+ self._border_width_hover = 2
332
+ self._border_color = QColor(0, 0, 0)
333
+ self._border_color_hover = QColor(240, 116, 35) # highlight
334
+
326
335
  # Load image for the handle and scale it to fit the ellipse
327
336
  self.handle_image = QPixmap(icons_path+"gear.png") # Replace with your image path
328
337
  self.handle_image = self.handle_image.scaledToHeight(self.height() - self.handleMargin*2, Qt.SmoothTransformation)
329
338
 
330
339
  self.setCursor(QCursor(QtCore.Qt.PointingHandCursor))
331
340
  self.FlagAnimation=False
341
+
342
+ self.FlagRunning = False
343
+ self.gearMovie: QMovie = None
344
+
345
+ self._bg_color = QColor(46, 204, 113) if self._checked else QColor(175, 175, 175, 128)
346
+ self._handle_position = 0 # inizializzato in setSwitchLayout()
347
+
332
348
  self.setSwitchLayout()
333
349
 
334
350
  self.animation = QPropertyAnimation(self, b"handle_position", self)
@@ -357,20 +373,35 @@ class ModernSwitch(QWidget):
357
373
  if par and self.gui.procdata:
358
374
  if self.gui.procdata.ind[:-2]==par.ind[:-2]:
359
375
  self.startTimer()
360
- #self.timer.start(50) # Aggiorna ogni 50 ms
361
376
 
377
+ # ----------------- Hover events -----------------
378
+ def enterEvent(self, event):
379
+ self._hovered = True
380
+ self.update()
381
+ super().enterEvent(event)
382
+
383
+ def leaveEvent(self, event):
384
+ self._hovered = False
385
+ self.update()
386
+ super().leaveEvent(event)
387
+
388
+ # ----------------- Timer control -----------------
362
389
  def startTimer(self):
363
390
  if not self.FlagRunning and hasattr(self.gui,'gearMovie'):
364
391
  self.FlagRunning=True
365
392
  self.gearMovie=self.gui.gearMovie
366
393
  self.timer.start(self.timerTime)
367
- pri.Coding.yellow('Start switch timer')
394
+ try:
395
+ pri.Coding.yellow('Start switch timer')
396
+ except Exception:
397
+ pass
368
398
 
369
399
  def stopTimer(self):
370
400
  if self.FlagRunning:
371
401
  self.FlagRunning=False
372
402
  self.timer.stop()
373
403
 
404
+ # ----------------- Property animation -----------------
374
405
  def set_handle_position(self, pos):
375
406
  self._handle_position = pos
376
407
  self.update()
@@ -380,90 +411,98 @@ class ModernSwitch(QWidget):
380
411
 
381
412
  handle_position = Property(int, fget=get_handle_position, fset=set_handle_position)
382
413
 
414
+ # ----------------- Painting -----------------
383
415
  def paintEvent(self, event):
384
416
  painter = QPainter(self)
385
417
  painter.setRenderHint(QPainter.Antialiasing)
386
418
 
387
- # Draw background ellipse with border
388
- bg_rect = QRect(0, 0, self.width(), self.height())
389
-
390
- # Draw filled background ellipse
419
+ # Background rect (rounded)
420
+ bg_rect = QRect(0, 0, self.width(), self.height()).adjusted(1, 1, -1, -1)
421
+
422
+ # Border pen (changes on hover)
423
+ if not self.isEnabled():
424
+ pen = QPen(self._border_color)
425
+ pen.setWidth(self._border_width)
426
+ else:
427
+ pen = QPen(self._border_color_hover if self._hovered else self._border_color)
428
+ pen.setWidth(self._border_width_hover if self._hovered else self._border_width)
429
+
430
+ painter.setPen(pen)
391
431
  painter.setBrush(self._bg_color)
392
- painter.drawRoundedRect(bg_rect.adjusted(1, 1, -1, -1), self.height() / 2, self.height() / 2)
432
+ painter.drawRoundedRect(bg_rect, self.height() / 2, self.height() / 2)
393
433
 
394
- # Calculate position and scale for image
434
+ # Handle position and size
395
435
  handle_x = self._handle_position
396
436
  handle_y = self.handleMargin
397
- handle_width = self.handle_image.width()
398
- handle_height = self.handle_image.height()
437
+ handle_w = self.handle_image.width()
438
+ handle_h = self.handle_image.height()
439
+ handle_rect = QRect(handle_x, handle_y, handle_w, handle_h)
399
440
 
400
- """
401
- if self._checked:
402
- # Draw handle image
403
- handle_rect = QRect(handle_x, handle_y, handle_width, handle_height)
404
- painter.drawPixmap(handle_rect, self.handle_image)
405
- else:
406
- # Draw handle circle
407
- handle_radius = self.height() - self.handleMargin*2
408
- handle_rect = QRect(self._handle_position, self.handleMargin, handle_radius, handle_radius)
409
- painter.setBrush(Qt.white)
410
- painter.drawEllipse(handle_rect)
411
- """
412
- handle_rect = QRect(handle_x, handle_y, handle_width, handle_height)
413
- #painter.drawPixmap(handle_rect, self.handle_image)
414
- if self.FlagRunning:
415
- painter.drawImage(handle_rect,self.gearMovie.currentImage())
441
+ # Draw gear (static or animated)
442
+ if self.FlagRunning and self.gearMovie is not None:
443
+ painter.drawImage(handle_rect, self.gearMovie.currentImage())
416
444
  else:
417
- painter.drawImage(handle_rect,self.handle_image.toImage())
418
-
445
+ painter.drawImage(handle_rect, self.handle_image.toImage())
446
+
447
+ # Draw label text
419
448
  if self.FlagAnimation:
420
449
  self.text_label.setText("")
421
450
  else:
422
451
  if self._checked:
423
452
  self.text_label.setText("run ")
424
- self.text_label.move(self.handleMargin*2, (self.height() - self.text_label.height()) // 2)
453
+ self.text_label.adjustSize()
454
+ self.text_label.move(self.handleMargin * 2, (self.height() - self.text_label.height()) // 2)
425
455
  else:
426
456
  self.text_label.setText("skip")
427
- self.text_label.move(self.width() - self.height() - self.handleMargin*2, (self.height() - self.text_label.height()) // 2)
457
+ self.text_label.adjustSize()
458
+ self.text_label.move(self.width() - self.height() - self.handleMargin * 2,
459
+ (self.height() - self.text_label.height()) // 2)
428
460
 
461
+ # ----------------- Interaction -----------------
429
462
  def mousePressEvent(self, event):
430
- if event.button() == Qt.LeftButton and not self.FlagAnimation:
463
+ if event.button() == Qt.LeftButton and not self.FlagAnimation and self.isEnabled():
431
464
  self.toggle()
432
465
 
433
466
  def toggle(self):
434
467
  self._checked = not self._checked
435
- if self.par: self.par.FlagQueue = self._checked
468
+ if self.par:
469
+ self.par.FlagQueue = self._checked
470
+
436
471
  self.toggled.emit(self._checked)
472
+
473
+ self.animation.stop()
474
+ self.animation.setStartValue(self._handle_position)
437
475
  self.animation.setEndValue(self.switchHandlePosition())
438
- self.FlagAnimation=True
476
+ self.FlagAnimation = True
439
477
  self.animation.start()
440
-
478
+
479
+ # ----------------- Layout helpers -----------------
441
480
  def switchHandlePosition(self):
442
481
  if self._checked:
443
- handle_position = self.width() - self.height() + self.handleMargin
444
- else:
445
- handle_position = self.handleMargin
446
- return handle_position
482
+ return self.width() - self.height() + self.handleMargin
483
+ return self.handleMargin
447
484
 
448
485
  def setSwitchLayout(self):
449
- self.FlagAnimation=False
486
+ self.FlagAnimation = False
450
487
  if self._checked:
451
- self._bg_color = QColor(46, 204, 113) #QColor(0, 122, 204) ù
452
- tip=f'{self.name if self.name else "Item"} added to run queue.'
488
+ self._bg_color = QColor(46, 204, 113)
489
+ tip = f'{self.name if self.name else "Item"} added to run queue.'
453
490
  else:
454
- tip=f'{self.name if self.name else "Item"} excluded from run queue.'
455
- #text_color = self.palette().color(QPalette.WindowText)
456
491
  self._bg_color = QColor(175, 175, 175)
457
- self._bg_color.setAlpha(0.5)
492
+ self._bg_color.setAlpha(64)
493
+ tip = f'{self.name if self.name else "Item"} excluded from run queue.'
494
+
458
495
  self.setToolTip(tip)
459
496
  self.setStatusTip(tip)
460
497
  self.set_handle_position(self.switchHandlePosition())
461
498
 
462
- def setSwitch(self,FlagChecked):
463
- if FlagChecked!=self._checked:
464
- self._checked=FlagChecked
499
+ def setSwitch(self, FlagChecked: bool):
500
+ if FlagChecked != self._checked:
501
+ self._checked = FlagChecked
502
+ if self.par:
503
+ self.par.FlagQueue = self._checked
465
504
  self.setSwitchLayout()
466
-
505
+
467
506
  class ActionButtonBar(QWidget):
468
507
  FlagShortCuts = True
469
508
 
@@ -495,7 +534,7 @@ class ActionButtonBar(QWidget):
495
534
  setattr(self,'sep_'+icon_name[1:],separator)
496
535
  self.buttonLayout.addWidget(separator)
497
536
  else:
498
- b = QToolButton(self)
537
+ b = HoverZoomToolButton(self)
499
538
  b.setObjectName('button_'+icon_name)
500
539
  b.setCursor(Qt.CursorShape.PointingHandCursor)
501
540
  if FlagInvisible:
@@ -560,6 +599,7 @@ class ActionButtonBar(QWidget):
560
599
  tree.FlagContexMenu=True
561
600
 
562
601
  menu=QMenu(tree)
602
+ menu.setStyleSheet(self.window().ui.menu.styleSheet())
563
603
  name=[]
564
604
  act=[]
565
605
  fun=[]
@@ -578,7 +618,7 @@ class ActionButtonBar(QWidget):
578
618
  if FlagNamedBar and 'class' in bar.buttonData[type]:
579
619
  if 'PIV'!=nameAction[:3]: nameAction=nameAction[:1].lower()+nameAction[1:]
580
620
  if b.isCheckable():
581
- nameAction=f"{'De-activate' if b.isChecked() else 'Activate'} {nameAction}"
621
+ nameAction=b.toolTip()
582
622
  else:
583
623
  nameAction='Add '+nameAction
584
624
  act.append(QAction(b.icon(),nameAction,self))
@@ -1039,7 +1079,63 @@ class ProjectTree(PaIRSTree):
1039
1079
  self.actionBar.button_close.setEnabled(FlagEnabled)
1040
1080
  self.actionBar.button_clean.setEnabled(self.topLevelItemCount()>0)
1041
1081
 
1042
- class DraggableButton(QToolButton):
1082
+ class HoverZoomToolButton(QToolButton):
1083
+ def __init__(self, parent=None, base_icon=24, zoom=1.25):
1084
+ super().__init__(parent)
1085
+
1086
+ self.zoom_factor = zoom
1087
+ self.base_size = QSize(base_icon, base_icon)
1088
+ self.zoom_size = QSize(
1089
+ int(base_icon * zoom),
1090
+ int(base_icon * zoom)
1091
+ )
1092
+
1093
+ self.setIconSize(self.base_size)
1094
+
1095
+ self.anim = QPropertyAnimation(self, b"iconSize", self)
1096
+ self.anim.setDuration(120)
1097
+ self.anim.setEasingCurve(QEasingCurve.OutCubic)
1098
+
1099
+ # --- OVERRIDE ---
1100
+ def setIconSize(self, size: QSize):
1101
+ """
1102
+ Override setIconSize to automatically update base and zoom sizes.
1103
+ """
1104
+ super().setIconSize(size)
1105
+ self.updateFromCurrentIconSize()
1106
+
1107
+ def updateFromCurrentIconSize(self, zoom: float | None = None):
1108
+ """
1109
+ Recompute base_size and zoom_size starting from the current iconSize().
1110
+ Optionally updates the zoom factor.
1111
+ """
1112
+ if zoom is not None:
1113
+ self.zoom_factor = zoom
1114
+
1115
+ current = self.iconSize()
1116
+ self.base_size = QSize(current.width(), current.height())
1117
+ self.zoom_size = QSize(
1118
+ int(current.width() * self.zoom_factor),
1119
+ int(current.height() * self.zoom_factor)
1120
+ )
1121
+
1122
+ def enterEvent(self, event):
1123
+ if self.isEnabled():
1124
+ self.anim.stop()
1125
+ self.anim.setStartValue(self.iconSize())
1126
+ self.anim.setEndValue(self.zoom_size)
1127
+ self.anim.start()
1128
+ super().enterEvent(event)
1129
+
1130
+ def leaveEvent(self, event):
1131
+ if self.isEnabled():
1132
+ self.anim.stop()
1133
+ self.anim.setStartValue(self.iconSize())
1134
+ self.anim.setEndValue(self.base_size)
1135
+ self.anim.start()
1136
+ super().leaveEvent(event)
1137
+
1138
+ class DraggableButton(HoverZoomToolButton):
1043
1139
  def __init__(self, data: dict = {}, buttonSize: list = processButtonSize, FlagInvisible=True, parent=None):
1044
1140
  super().__init__(parent)
1045
1141
  self.buttonData = data
@@ -1052,6 +1148,7 @@ class DraggableButton(QToolButton):
1052
1148
  self.setIconSize(QSize(self.buttonSize[0], self.buttonSize[0]))
1053
1149
  self.setIcon(self.buttonIcon)
1054
1150
  self.setFixedSize(self.buttonSize[1], self.buttonSize[1])
1151
+
1055
1152
  if FlagInvisible:
1056
1153
  self.setCursor(Qt.CursorShape.OpenHandCursor)
1057
1154
  self.setStyleSheet("QToolButton { border: none; background: none;} QToolButton::menu-indicator { image: none; }")
@@ -1103,7 +1200,7 @@ class ProcessButtonBar(QWidget):
1103
1200
  super().__init__()
1104
1201
  self.buttonLayout = QHBoxLayout()
1105
1202
  self.buttonLayout.setContentsMargins(0, 0, 0, 0)
1106
- self.buttonLayout.setSpacing(int(buttonSize[0] / 2))
1203
+ self.buttonLayout.setSpacing(int(buttonSize[0]/4))
1107
1204
  self.setLayout(self.buttonLayout)
1108
1205
  self.buttonData=buttonData
1109
1206
  self.buttonSize = buttonSize
@@ -1123,7 +1220,7 @@ class StepButtonBar(QWidget):
1123
1220
  super().__init__()
1124
1221
  self.buttonLayout = QVBoxLayout()
1125
1222
  self.buttonLayout.setContentsMargins(10, headerHeight+5, 0, 10)
1126
- self.buttonLayout.setSpacing(int(buttonSize[0]/2))
1223
+ self.buttonLayout.setSpacing(int(buttonSize[0]/4))
1127
1224
  self.setLayout(self.buttonLayout)
1128
1225
  self.buttonData=buttonData
1129
1226
  self.buttonSize = buttonSize
@@ -1135,8 +1232,7 @@ class StepButtonBar(QWidget):
1135
1232
  data=buttonData[type]
1136
1233
 
1137
1234
  if all([f in list(data) for f in ('icon', 'name', 'type')]):
1138
- button = QToolButton(self)
1139
- button.setIconSize(QSize(self.buttonSize[0], self.buttonSize[0]))
1235
+ button = HoverZoomToolButton(self, base_icon=self.buttonSize[0], zoom=1.25)
1140
1236
  button.setFixedSize(self.buttonSize[1], self.buttonSize[1])
1141
1237
 
1142
1238
  if FlagInvisible:
@@ -1223,7 +1319,7 @@ class ProcessTree(PaIRSTree):
1223
1319
 
1224
1320
  if self.FlagBin:
1225
1321
  columns=["#","Deleted processes"]
1226
- self.setStyleSheet("""
1322
+ self.setStyleSheet(self.styleSheet() + """
1227
1323
  QHeaderView::section {
1228
1324
  color: red;
1229
1325
  }
@@ -1452,8 +1548,8 @@ class ProcessTree(PaIRSTree):
1452
1548
  ITE.FlagQueue=self.TREpar.FlagQueue
1453
1549
  ITE.ind=[self.TREpar.project,int(self.FlagBin),ind,0,0]
1454
1550
 
1551
+ processNames=[item[0].name for item in self.itemList[0]]+[item[0].name for item in self.restoreTree.itemList[0]]
1455
1552
  if name is None:
1456
- processNames=[item[0].name for item in self.itemList[0]]+[item[0].name for item in self.restoreTree.itemList[0]]
1457
1553
  nameInd=1
1458
1554
  ITE.name=f'{ITE.basename} {nameInd}'
1459
1555
  while ITE.name in processNames:
@@ -1461,6 +1557,10 @@ class ProcessTree(PaIRSTree):
1461
1557
  ITE.name=f'{ITE.basename} {nameInd}'
1462
1558
  else:
1463
1559
  ITE.name=name
1560
+ nameInd=1
1561
+ while ITE.name in processNames:
1562
+ nameInd+=1
1563
+ ITE.name=f'{name} ({nameInd})'
1464
1564
  self.createParPrevs(ITE)
1465
1565
  self.itemList[0].insert(ind,[ITE])
1466
1566
  else:
@@ -1917,12 +2017,12 @@ class ProcessTree(PaIRSTree):
1917
2017
  pixmap_list.append(icons_path+ITE.icon)
1918
2018
  name_list.append(ITE.name)
1919
2019
  flag_list.append(ITE.Step!=StepTypes.cal)
1920
- func=lambda i, opts: self.process_loop(paths,processType,ind,ITEs[0].name,i,opts)
2020
+ func=lambda i, opts, cleanup_flag, rescan_flag: self.process_loop(paths,processType,ind,ITEs[0].name,i,opts,cleanup_flag,rescan_flag)
1921
2021
  dialog = FolderLoopDialog(pixmap_list, name_list, flag_list, parent=self, paths=paths, func=func, process_name=ITEs[0].name)
1922
2022
  dialog.exec()
1923
2023
  return
1924
2024
 
1925
- def process_loop(self,paths,processType,ind,name0,i,opts):
2025
+ def process_loop(self,paths,processType,ind,name0,i,opts,cleanup_flag,rescan_flag):
1926
2026
  nProcess=self.topLevelItemCount()
1927
2027
  path=paths[i]
1928
2028
  name=name0+f" (.../{os.path.basename(path)}/)"
@@ -1933,6 +2033,7 @@ class ProcessTree(PaIRSTree):
1933
2033
  ind_slave[2]=nProcess
1934
2034
  ind_slave[-1]=0
1935
2035
 
2036
+ FlagWarning=False
1936
2037
  for j in range(len(opts)):
1937
2038
  ind_master[3]=j
1938
2039
  ind_slave[3]=j
@@ -1944,10 +2045,17 @@ class ProcessTree(PaIRSTree):
1944
2045
  if opts[j]==2:
1945
2046
  INP: INPpar=self.gui.w_Input.TABpar_at(ind_new)
1946
2047
  INP.path=myStandardPath(path)
1947
- self.gui.w_Input.scanImList(ind_new)
2048
+ if rescan_flag:
2049
+ flagWarning=self.rescanInputPath(ind_master,ind_new,cleanup_flag)
2050
+ else:
2051
+ flagWarning=self.gui.w_Input.scanImList(ind_new)
2052
+ if cleanup_flag: self.gui.w_Input.purgeImList(ind_new)
2053
+ INP.nimg=0
2054
+ if len(INP.imList[0]):
2055
+ if len(INP.imList[0][0]):
2056
+ INP.nimg=len(INP.imList[0][0])
1948
2057
  self.gui.w_Input.checkINPpar(ind_new)
1949
2058
  self.gui.w_Input.setINPwarn(ind_new)
1950
- self.Explorer.setITElayout(ITE)
1951
2059
 
1952
2060
  TABname=self.gui.w_Input.TABname
1953
2061
  self.gui.bridge(TABname,ind_new)
@@ -1966,12 +2074,54 @@ class ProcessTree(PaIRSTree):
1966
2074
  w.TABpar.copyfrom(currpar)
1967
2075
  self.gui.bridge(w.TABname,ind_new)
1968
2076
  TABpar.FlagSettingPar=FlagSettingPar
2077
+ self.Explorer.setITElayout(ITE)
2078
+ FlagWarning=FlagWarning or flagWarning or ITE.OptionDone==0
2079
+
1969
2080
  #item_child=item.child(j)
1970
2081
  #item_child.setSelected(True)
1971
2082
  #self.setCurrentItem(item_child)
1972
2083
  else:
1973
2084
  self.gui.link_pars(ind_slave,ind_master,FlagSet=False)
1974
- return
2085
+ return FlagWarning
2086
+
2087
+ def rescanInputPath(self, ind_master, ind_new, FlagNoWarning):
2088
+ """Rescan input on slave using master patterns, then rebuild imList/imEx."""
2089
+ INP: INPpar = self.gui.w_Input.TABpar_at(ind_new) # slave
2090
+ INPm: INPpar = self.gui.w_Input.TABpar_at(ind_master) # master
2091
+
2092
+ # Build A (for frame_1) and B (for frame_2) from master's pattern at master's frames
2093
+ patm = getattr(INPm.imSet, "pattern", [])
2094
+ ncam = INP.inp_ncam
2095
+ def _pat_list(frames,ind0=0):
2096
+ out=[];
2097
+ for k in range(ncam):
2098
+ f = frames[k]-ind0 if k < len(frames) else -1
2099
+ out.append(patm[f] if isinstance(f,int) and 0 <= f < len(patm) else None)
2100
+ return out
2101
+ A = _pat_list(INPm.frame_1)
2102
+ B = _pat_list(INPm.frame_2,1)
2103
+
2104
+ # Rescan slave input path with patterns=A,B (maps patterns -> frame indices on slave)
2105
+ self.gui.w_Input.scanInputPath(ind_new, patterns=[A, B], FlagNoWarning=FlagNoWarning)
2106
+
2107
+ # Rebuild imList/imEx from frames computed on slave
2108
+ INP.imList, INP.imEx = INP.imSet.genListsFromFrame(
2109
+ INP.frame_1, INP.frame_2, INP.ind_in, INP.npairs, INP.step, INP.FlagTR_Import
2110
+ )
2111
+ # Compare slave vs master lists
2112
+ def _lists_differ(a, b):
2113
+ if len(a) != len(b): return True
2114
+ for x, y in zip(a, b):
2115
+ if x != y: return True
2116
+ return False
2117
+
2118
+ FlagWarning = (
2119
+ _lists_differ(INP.imList, INPm.imList) or
2120
+ _lists_differ(INP.imEx, INPm.imEx)
2121
+ )
2122
+
2123
+ return FlagWarning
2124
+
1975
2125
 
1976
2126
  def button_delete_action(self):
1977
2127
  self.blockSignals(True)
@@ -2296,7 +2446,8 @@ class PaIRS_Explorer(gPaIRS_Tab):
2296
2446
  self.main_layout.addWidget(self.Explorer_main_splitter)
2297
2447
 
2298
2448
  # Creazione del pulsante checkable
2299
- self.binButton = QToolButton(self.processButtonBar)
2449
+ self.binButton = HoverZoomToolButton(self.processButtonBar)
2450
+ self.binButton.setObjectName('binButton')
2300
2451
  self.binButton.setIconSize(QSize(self.processButtonBar.buttonSize[0], self.processButtonBar.buttonSize[0]))
2301
2452
  self.binButton.setFixedSize(self.processButtonBar.buttonSize[1], self.processButtonBar.buttonSize[1])
2302
2453
  if self.processButtonBar.FlagInvisible:
@@ -2377,7 +2528,7 @@ class PaIRS_Explorer(gPaIRS_Tab):
2377
2528
  tree.setCurrentItem(child)
2378
2529
  self.processTree_item_selection(tree)
2379
2530
  else:
2380
- showTip(self,'Current step is disabled! Please, reset the subsequemt step in the process to access it.')
2531
+ show_mouse_tooltip(self,'Current step is disabled! Please, reset the subsequemt step in the process to access it.')
2381
2532
  for t,b in self.stepButtonBar.buttons.items():
2382
2533
  b:QToolButton
2383
2534
  b.clicked.connect(lambda flag, butt=b, type=t: stepButtonAction(butt, type))
@@ -2492,13 +2643,22 @@ class PaIRS_Explorer(gPaIRS_Tab):
2492
2643
  if item.childCount()>ind:
2493
2644
  item_child=item.child(ind)
2494
2645
  item_child.setHidden(not ITEs[ind+1].active)
2495
- b.setVisible(c not in ITEs[0].mandatory) #b.setVisible(True)
2496
- lab.setVisible(c in ITEs[0].mandatory)
2646
+ b.setVisible(True)
2647
+ #b.setVisible(c not in ITEs[0].mandatory) #b.setVisible(True)
2648
+ #lab.setVisible(c in ITEs[0].mandatory)
2497
2649
  flagRunnable=all([ITEs[j].flagRun==0 for j in range(ind+2,nsteps+1)]) if ind<nsteps else True
2498
2650
  flagRunnable=flagRunnable and ITEs[ind+1].flagRun==0 and ITEs[0].flagRun!=-2 #and len(ITEs[ind+1].link)==0
2499
2651
  b.setEnabled(flagRunnable)
2500
- lab.setEnabled(flagRunnable)
2652
+ #lab.setEnabled(flagRunnable)
2501
2653
  b.setChecked(ITEs[ind+1].active)
2654
+ nameAction=ITEs[ind+1].name
2655
+ if 'PIV'!=nameAction[:3]: nameAction=nameAction[:1].lower()+nameAction[1:]
2656
+ if c not in ITEs[0].mandatory:
2657
+ nameAction=f"{'De-activate' if b.isChecked() else 'Activate'} {nameAction}"
2658
+ else:
2659
+ nameAction=f"{'Go to and edit'} {nameAction}"
2660
+ b.setToolTip(nameAction)
2661
+ b.setStatusTip(nameAction)
2502
2662
  else:
2503
2663
  flagRunnable=False
2504
2664
  b.setVisible(False)
@@ -2508,10 +2668,27 @@ class PaIRS_Explorer(gPaIRS_Tab):
2508
2668
  b.setChecked(False)
2509
2669
  b.setButtonIcon()
2510
2670
  if self.stepPage:
2511
- self.stepPage.items[c].setVisible(c in ITEs[0].children) #and b.isChecked())
2671
+ FlagChild=c in ITEs[0].children
2672
+ self.stepPage.items[c].setVisible(FlagChild) #and b.isChecked())
2673
+ FlagEnabled=b.isEnabled() or b.isChecked()
2674
+ self.stepPage.items[c].setEnabled(FlagEnabled)
2512
2675
  pageButton:QPushButton=self.stepPage.items[c].findChildren(QPushButton)[0]
2676
+
2677
+ if FlagChild:
2678
+ ind=list(ITEs[0].children).index(c)
2679
+ nameAction=ITEs[ind+1].name
2680
+ nameAction_lowerCase=nameAction[:1].lower()+nameAction[1:] if 'PIV'!=nameAction[:3] else nameAction
2681
+ if b.isEnabled():
2682
+ toolTip="Go to and edit " + nameAction_lowerCase if b.isChecked() else b.toolTip()
2683
+ else:
2684
+ toolTip="Go to and view " + nameAction_lowerCase if b.isChecked() else nameAction+" step was excluded from the process!"
2685
+ self.stepPage.items[c].setToolTip(toolTip)
2686
+ self.stepPage.items[c].setStatusTip(toolTip)
2687
+
2688
+ self.stepPage.setProcessTextActive(not (b.isEnabled() and not b.isChecked()),c)
2689
+
2513
2690
  if flagRunnable:
2514
- pageButton.setIcon(b.icon())
2691
+ pageButton.setIcon(b.icon())
2515
2692
  else:
2516
2693
  pageButton.setIcon(b.iconOn)
2517
2694
  else:
@@ -2953,13 +3130,21 @@ class StartingPage(QFrame):
2953
3130
  button_margin=self.ITEM_HEIGHT-self.ICON_SIZE-self.BUTTON_LAYOUT_TOP_MARGIN
2954
3131
  CAPTION_HEIGHT=self.ITEM_HEIGHT-self.NAME_LABEL_HEIGHT-self.TEXT_LAYOUT_SPACING
2955
3132
  self.items={}
3133
+ self.textItems={}
2956
3134
  # Itera sui dizionari nella lista
2957
3135
  for n, process in processes.items():
2958
3136
  # Layout orizzontale per ogni processo
2959
3137
  widget=QWidget()
3138
+ widget.setObjectName("process_item")
3139
+ widget.setStyleSheet("""
3140
+ QWidget#process_item:hover {
3141
+ background-color: rgba(0, 116, 255, 0.1);
3142
+ border-radius: 10px;
3143
+ }
3144
+ """)
2960
3145
  process_layout = QHBoxLayout(widget)
2961
3146
  process_layout.setSpacing(self.LAYOUT_SPACING)
2962
- process_layout.setContentsMargins(0, 0, 0, 0)
3147
+ process_layout.setContentsMargins(10, 10, 10, 0)
2963
3148
 
2964
3149
  # Pulsante con icona
2965
3150
  button_layout = QHBoxLayout()
@@ -2967,6 +3152,7 @@ class StartingPage(QFrame):
2967
3152
  button_layout.setContentsMargins(0, self.BUTTON_LAYOUT_TOP_MARGIN, 0, button_margin)
2968
3153
 
2969
3154
  icon_button = QPushButton()
3155
+ icon_button.setObjectName("StartingPage_Button")
2970
3156
  pixmap = QPixmap(icons_path+process['icon']).scaled(self.ICON_SIZE, self.ICON_SIZE, mode=Qt.TransformationMode.SmoothTransformation)
2971
3157
  icon_button.setIcon(pixmap)
2972
3158
  icon_button.setIconSize(pixmap.size())
@@ -3007,13 +3193,15 @@ class StartingPage(QFrame):
3007
3193
  caption_text_edit.setFont(caption_font)
3008
3194
  caption_text_edit.setAlignment(Qt.AlignmentFlag.AlignJustify)
3009
3195
  caption_text_edit.setReadOnly(True)
3196
+ caption_text_edit.setTextInteractionFlags(Qt.NoTextInteraction)
3197
+ caption_text_edit.viewport().setCursor(Qt.PointingHandCursor)
3010
3198
  caption_text_edit.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
3011
3199
  caption_text_edit.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
3012
3200
  caption_text_edit.setFrameStyle(QFrame.NoFrame)
3013
3201
  #caption_text_edit.setFixedWidth(self.CAPTION_WIDTH)
3014
3202
  caption_text_edit.setStyleSheet("background: transparent;")
3015
3203
  caption_text_edit.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
3016
-
3204
+
3017
3205
  # Adjust height to content
3018
3206
  caption_text_edit.document().setTextWidth(caption_text_edit.viewport().width())
3019
3207
  caption_text_edit.setFixedHeight(CAPTION_HEIGHT)#caption_text_edit.document().size().height())
@@ -3029,7 +3217,30 @@ class StartingPage(QFrame):
3029
3217
  # Aggiungi il layout orizzontale al layout principale
3030
3218
  self.main_layout.addWidget(widget)
3031
3219
 
3220
+ # --- Make the whole row clickable (icon + labels + background) ---
3221
+ if buttonBar:
3222
+
3223
+ def make_clickable(w, callback):
3224
+ w.setCursor(Qt.PointingHandCursor)
3225
+
3226
+ def mouseReleaseEvent(event, cb=callback, ww=w):
3227
+ if event.button() == Qt.LeftButton:
3228
+ cb()
3229
+ # call base implementation to keep default behaviour
3230
+ QWidget.mouseReleaseEvent(ww, event)
3231
+
3232
+ w.mouseReleaseEvent = mouseReleaseEvent
3233
+
3234
+ # entire row + title + caption all trigger the same action
3235
+ make_clickable(widget, action(n))
3236
+ #make_clickable(name_label, click_callback)
3237
+ #make_clickable(caption_text_edit, click_callback)
3238
+
3032
3239
  self.items[n]=widget
3240
+ self.textItems[n]={
3241
+ "name_label": name_label,
3242
+ "caption": caption_text_edit,
3243
+ }
3033
3244
 
3034
3245
  self.main_layout.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Expanding))
3035
3246
 
@@ -3054,6 +3265,31 @@ class StartingPage(QFrame):
3054
3265
  caption_font.setPixelSize(CAPTION_FONT_SIZE)
3055
3266
  c.setFont(caption_font)
3056
3267
 
3268
+ def setProcessTextActive(self, active: bool, key=None):
3269
+ """
3270
+ If active=True -> black text (active)
3271
+ If active=False -> light bluish text (inactive)
3272
+ If key is None -> apply to all items
3273
+ If key is provided -> apply only to that process key
3274
+ """
3275
+ active_color = "none"
3276
+ inactive_color = "rgb(150, 150, 255)"
3277
+
3278
+ color = active_color if active else inactive_color
3279
+
3280
+ def apply_to(item):
3281
+ item["name_label"].setStyleSheet(f"color: {color};")
3282
+ # QTextEdit draws text in its viewport -> set color on QTextEdit itself is fine
3283
+ item["caption"].setStyleSheet(f"color: {color}; background: transparent;")
3284
+
3285
+ if key is None:
3286
+ for item in self.textItems.values():
3287
+ apply_to(item)
3288
+ else:
3289
+ if key in self.textItems:
3290
+ apply_to(self.textItems[key])
3291
+
3292
+
3057
3293
  if __name__ == "__main__":
3058
3294
  app = QApplication([])
3059
3295
  app.setStyle('Fusion')