PaIRS-UniNa 0.1.13__cp310-cp310-win_amd64.whl → 0.2.8__cp310-cp310-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 (292) hide show
  1. PaIRS_UniNa/Calibration_Tab.py +331 -0
  2. PaIRS_UniNa/Changes.txt +134 -2
  3. PaIRS_UniNa/Custom_Top.py +299 -299
  4. PaIRS_UniNa/Explorer.py +3136 -0
  5. PaIRS_UniNa/FolderLoop.py +562 -0
  6. PaIRS_UniNa/Input_Tab.py +831 -0
  7. PaIRS_UniNa/Input_Tab_CalVi.py +786 -0
  8. PaIRS_UniNa/Input_Tab_tools.py +3025 -0
  9. PaIRS_UniNa/Log_Tab.py +57 -14
  10. PaIRS_UniNa/Output_Tab.py +922 -0
  11. PaIRS_UniNa/PaIRS_PIV.py +111 -18
  12. PaIRS_UniNa/PaIRS_pypacks.py +698 -140
  13. PaIRS_UniNa/Process_Tab.py +1221 -1402
  14. PaIRS_UniNa/Process_Tab_CalVi.py +137 -262
  15. PaIRS_UniNa/Process_Tab_Disp.py +163 -0
  16. PaIRS_UniNa/Process_Tab_Min.py +120 -0
  17. PaIRS_UniNa/Saving_tools.py +296 -0
  18. PaIRS_UniNa/TabTools.py +863 -543
  19. PaIRS_UniNa/Vis_Tab.py +1898 -1315
  20. PaIRS_UniNa/Vis_Tab_CalVi.py +484 -356
  21. PaIRS_UniNa/Whatsnew.py +59 -10
  22. PaIRS_UniNa/_PaIRS_PIV.pyd +0 -0
  23. PaIRS_UniNa/__init__.py +4 -3
  24. PaIRS_UniNa/addwidgets_ps.py +326 -56
  25. PaIRS_UniNa/calib.py +19 -12
  26. PaIRS_UniNa/calibView.py +48 -25
  27. PaIRS_UniNa/gPaIRS.py +3902 -3461
  28. PaIRS_UniNa/gPalette.py +189 -170
  29. PaIRS_UniNa/icons/align_all.png +0 -0
  30. PaIRS_UniNa/icons/announcement.png +0 -0
  31. PaIRS_UniNa/icons/automatic_levels_off.png +0 -0
  32. PaIRS_UniNa/icons/automatic_levels_on.png +0 -0
  33. PaIRS_UniNa/icons/automatic_off.png +0 -0
  34. PaIRS_UniNa/icons/automatic_on.png +0 -0
  35. PaIRS_UniNa/icons/automatic_size_off.png +0 -0
  36. PaIRS_UniNa/icons/automatic_size_on.png +0 -0
  37. PaIRS_UniNa/icons/bin_off.png +0 -0
  38. PaIRS_UniNa/icons/bin_on.png +0 -0
  39. PaIRS_UniNa/icons/brush_cursor.png +0 -0
  40. PaIRS_UniNa/icons/bugfix.png +0 -0
  41. PaIRS_UniNa/icons/cal_proc.png +0 -0
  42. PaIRS_UniNa/icons/cal_proc_off.png +0 -0
  43. PaIRS_UniNa/icons/cal_step.png +0 -0
  44. PaIRS_UniNa/icons/cal_step_off.png +0 -0
  45. PaIRS_UniNa/icons/calibration_logo.png +0 -0
  46. PaIRS_UniNa/icons/change_folder.png +0 -0
  47. PaIRS_UniNa/icons/change_folder_off.png +0 -0
  48. PaIRS_UniNa/icons/close_all.png +0 -0
  49. PaIRS_UniNa/icons/close_workspace.png +0 -0
  50. PaIRS_UniNa/icons/colormap.png +0 -0
  51. PaIRS_UniNa/icons/colormaps/Accent.png +0 -0
  52. PaIRS_UniNa/icons/colormaps/BrBG.png +0 -0
  53. PaIRS_UniNa/icons/colormaps/Dark2.png +0 -0
  54. PaIRS_UniNa/icons/colormaps/PRGn.png +0 -0
  55. PaIRS_UniNa/icons/colormaps/Paired.png +0 -0
  56. PaIRS_UniNa/icons/colormaps/Pastel1.png +0 -0
  57. PaIRS_UniNa/icons/colormaps/Pastel2.png +0 -0
  58. PaIRS_UniNa/icons/colormaps/PiYG.png +0 -0
  59. PaIRS_UniNa/icons/colormaps/PuOr.png +0 -0
  60. PaIRS_UniNa/icons/colormaps/RdBu.png +0 -0
  61. PaIRS_UniNa/icons/colormaps/RdGy.png +0 -0
  62. PaIRS_UniNa/icons/colormaps/RdYlBu.png +0 -0
  63. PaIRS_UniNa/icons/colormaps/RdYlGn.png +0 -0
  64. PaIRS_UniNa/icons/colormaps/Set1.png +0 -0
  65. PaIRS_UniNa/icons/colormaps/Set2.png +0 -0
  66. PaIRS_UniNa/icons/colormaps/Set3.png +0 -0
  67. PaIRS_UniNa/icons/colormaps/Spectral.png +0 -0
  68. PaIRS_UniNa/icons/colormaps/Wistia.png +0 -0
  69. PaIRS_UniNa/icons/colormaps/afmhot.png +0 -0
  70. PaIRS_UniNa/icons/colormaps/autumn.png +0 -0
  71. PaIRS_UniNa/icons/colormaps/binary.png +0 -0
  72. PaIRS_UniNa/icons/colormaps/blackVector.png +0 -0
  73. PaIRS_UniNa/icons/colormaps/blueVector.png +0 -0
  74. PaIRS_UniNa/icons/colormaps/bone.png +0 -0
  75. PaIRS_UniNa/icons/colormaps/brg.png +0 -0
  76. PaIRS_UniNa/icons/colormaps/bwr.png +0 -0
  77. PaIRS_UniNa/icons/colormaps/cividis.png +0 -0
  78. PaIRS_UniNa/icons/colormaps/cool.png +0 -0
  79. PaIRS_UniNa/icons/colormaps/coolwarm.png +0 -0
  80. PaIRS_UniNa/icons/colormaps/copper.png +0 -0
  81. PaIRS_UniNa/icons/colormaps/cubehelix.png +0 -0
  82. PaIRS_UniNa/icons/colormaps/cyanVector.png +0 -0
  83. PaIRS_UniNa/icons/colormaps/flag.png +0 -0
  84. PaIRS_UniNa/icons/colormaps/gist_heat.png +0 -0
  85. PaIRS_UniNa/icons/colormaps/gray.png +0 -0
  86. PaIRS_UniNa/icons/colormaps/greenVector.png +0 -0
  87. PaIRS_UniNa/icons/colormaps/hot.png +0 -0
  88. PaIRS_UniNa/icons/colormaps/hsv.png +0 -0
  89. PaIRS_UniNa/icons/colormaps/inferno.png +0 -0
  90. PaIRS_UniNa/icons/colormaps/jet.png +0 -0
  91. PaIRS_UniNa/icons/colormaps/magentaVector.png +0 -0
  92. PaIRS_UniNa/icons/colormaps/magma.png +0 -0
  93. PaIRS_UniNa/icons/colormaps/ocean.png +0 -0
  94. PaIRS_UniNa/icons/colormaps/pink.png +0 -0
  95. PaIRS_UniNa/icons/colormaps/plasma.png +0 -0
  96. PaIRS_UniNa/icons/colormaps/prism.png +0 -0
  97. PaIRS_UniNa/icons/colormaps/rainbow.png +0 -0
  98. PaIRS_UniNa/icons/colormaps/redVector.png +0 -0
  99. PaIRS_UniNa/icons/colormaps/seismic.png +0 -0
  100. PaIRS_UniNa/icons/colormaps/spring.png +0 -0
  101. PaIRS_UniNa/icons/colormaps/summer.png +0 -0
  102. PaIRS_UniNa/icons/colormaps/tab10.png +0 -0
  103. PaIRS_UniNa/icons/colormaps/tab20.png +0 -0
  104. PaIRS_UniNa/icons/colormaps/tab20b.png +0 -0
  105. PaIRS_UniNa/icons/colormaps/tab20c.png +0 -0
  106. PaIRS_UniNa/icons/colormaps/terrain.png +0 -0
  107. PaIRS_UniNa/icons/colormaps/twilight.png +0 -0
  108. PaIRS_UniNa/icons/colormaps/viridis.png +0 -0
  109. PaIRS_UniNa/icons/colormaps/whiteVector.png +0 -0
  110. PaIRS_UniNa/icons/colormaps/winter.png +0 -0
  111. PaIRS_UniNa/icons/colormaps/yellowVector.png +0 -0
  112. PaIRS_UniNa/icons/common_region.png +0 -0
  113. PaIRS_UniNa/icons/common_region_off.png +0 -0
  114. PaIRS_UniNa/icons/completed.png +0 -0
  115. PaIRS_UniNa/icons/contourf_off.png +0 -0
  116. PaIRS_UniNa/icons/contourf_on.png +0 -0
  117. PaIRS_UniNa/icons/copy.png +0 -0
  118. PaIRS_UniNa/icons/copy_process.png +0 -0
  119. PaIRS_UniNa/icons/copy_process_off.png +0 -0
  120. PaIRS_UniNa/icons/cut.png +0 -0
  121. PaIRS_UniNa/icons/cut_warnings.png +0 -0
  122. PaIRS_UniNa/icons/darkmode.png +0 -0
  123. PaIRS_UniNa/icons/disp_step.png +0 -0
  124. PaIRS_UniNa/icons/disp_step_off.png +0 -0
  125. PaIRS_UniNa/icons/edit_list.png +0 -0
  126. PaIRS_UniNa/icons/example_list.png +0 -0
  127. PaIRS_UniNa/icons/flaticon_PaIRS_beta.png +0 -0
  128. PaIRS_UniNa/icons/flaticon_PaIRS_download_warning.png +0 -0
  129. PaIRS_UniNa/icons/flip_y_off.png +0 -0
  130. PaIRS_UniNa/icons/flip_y_on.png +0 -0
  131. PaIRS_UniNa/icons/folder_loop_cleanup.png +0 -0
  132. PaIRS_UniNa/icons/folder_loop_cleanup_off.png +0 -0
  133. PaIRS_UniNa/icons/gear.gif +0 -0
  134. PaIRS_UniNa/icons/gear.png +0 -0
  135. PaIRS_UniNa/icons/import_set.png +0 -0
  136. PaIRS_UniNa/icons/information.png +0 -0
  137. PaIRS_UniNa/icons/input_logo.png +0 -0
  138. PaIRS_UniNa/icons/issue.png +0 -0
  139. PaIRS_UniNa/icons/laser_NTR.png +0 -0
  140. PaIRS_UniNa/icons/laser_TR_double.png +0 -0
  141. PaIRS_UniNa/icons/laser_TR_single.png +0 -0
  142. PaIRS_UniNa/icons/link.png +0 -0
  143. PaIRS_UniNa/icons/linked.png +0 -0
  144. PaIRS_UniNa/icons/log_logo.png +0 -0
  145. PaIRS_UniNa/icons/logo_opaco.png +0 -0
  146. PaIRS_UniNa/icons/measure.png +0 -0
  147. PaIRS_UniNa/icons/measure_off.png +0 -0
  148. PaIRS_UniNa/icons/min_proc.png +0 -0
  149. PaIRS_UniNa/icons/min_proc_off.png +0 -0
  150. PaIRS_UniNa/icons/min_step.png +0 -0
  151. PaIRS_UniNa/icons/min_step_off.png +0 -0
  152. PaIRS_UniNa/icons/new_workspace.png +0 -0
  153. PaIRS_UniNa/icons/open_image.png +0 -0
  154. PaIRS_UniNa/icons/open_new_window.png +0 -0
  155. PaIRS_UniNa/icons/open_result.png +0 -0
  156. PaIRS_UniNa/icons/open_workspace.png +0 -0
  157. PaIRS_UniNa/icons/output_logo.png +0 -0
  158. PaIRS_UniNa/icons/paste_above.png +0 -0
  159. PaIRS_UniNa/icons/paste_below.png +0 -0
  160. PaIRS_UniNa/icons/paused.png +0 -0
  161. PaIRS_UniNa/icons/piv_proc.png +0 -0
  162. PaIRS_UniNa/icons/piv_proc_off.png +0 -0
  163. PaIRS_UniNa/icons/piv_step.png +0 -0
  164. PaIRS_UniNa/icons/piv_step_off.png +0 -0
  165. PaIRS_UniNa/icons/process_logo.png +0 -0
  166. PaIRS_UniNa/icons/process_loop.png +0 -0
  167. PaIRS_UniNa/icons/project.png +0 -0
  168. PaIRS_UniNa/icons/pylog.png +0 -0
  169. PaIRS_UniNa/icons/python_warning.png +0 -0
  170. PaIRS_UniNa/icons/queue.png +0 -0
  171. PaIRS_UniNa/icons/read.png +0 -0
  172. PaIRS_UniNa/icons/read_list.png +0 -0
  173. PaIRS_UniNa/icons/reset.png +0 -0
  174. PaIRS_UniNa/icons/reset_levels.png +0 -0
  175. PaIRS_UniNa/icons/restore_undo.png +0 -0
  176. PaIRS_UniNa/icons/running.gif +0 -0
  177. PaIRS_UniNa/icons/running.png +0 -0
  178. PaIRS_UniNa/icons/running_warn.png +0 -0
  179. PaIRS_UniNa/icons/save_and_stop.png +0 -0
  180. PaIRS_UniNa/icons/save_cfg.png +0 -0
  181. PaIRS_UniNa/icons/saveas_workspace.png +0 -0
  182. PaIRS_UniNa/icons/scale_all.png +0 -0
  183. PaIRS_UniNa/icons/scale_down.png +0 -0
  184. PaIRS_UniNa/icons/scale_up.png +0 -0
  185. PaIRS_UniNa/icons/scan_list.png +0 -0
  186. PaIRS_UniNa/icons/scan_path.png +0 -0
  187. PaIRS_UniNa/icons/scan_path_loop.png +0 -0
  188. PaIRS_UniNa/icons/scan_path_loop_off.png +0 -0
  189. PaIRS_UniNa/icons/search.png +0 -0
  190. PaIRS_UniNa/icons/showIW_off.png +0 -0
  191. PaIRS_UniNa/icons/showIW_on.png +0 -0
  192. PaIRS_UniNa/icons/show_all.png +0 -0
  193. PaIRS_UniNa/icons/sort.png +0 -0
  194. PaIRS_UniNa/icons/sort_reversed.png +0 -0
  195. PaIRS_UniNa/icons/spiv_proc.png +0 -0
  196. PaIRS_UniNa/icons/spiv_proc_off.png +0 -0
  197. PaIRS_UniNa/icons/star.png +0 -0
  198. PaIRS_UniNa/icons/step_inheritance.png +0 -0
  199. PaIRS_UniNa/icons/subMIN_off.png +0 -0
  200. PaIRS_UniNa/icons/subMIN_on.png +0 -0
  201. PaIRS_UniNa/icons/unedited.png +0 -0
  202. PaIRS_UniNa/icons/uninitialized.png +0 -0
  203. PaIRS_UniNa/icons/unlink.png +0 -0
  204. PaIRS_UniNa/icons/unwrap_items.png +0 -0
  205. PaIRS_UniNa/icons/vectorColor.png +0 -0
  206. PaIRS_UniNa/icons/view.png +0 -0
  207. PaIRS_UniNa/icons/view_off.png +0 -0
  208. PaIRS_UniNa/icons/vis_logo.png +0 -0
  209. PaIRS_UniNa/icons/warning_circle.png +0 -0
  210. PaIRS_UniNa/icons/window.png +0 -0
  211. PaIRS_UniNa/icons/workspace.png +0 -0
  212. PaIRS_UniNa/icons/wrap_items.png +0 -0
  213. PaIRS_UniNa/icons/write_list.png +0 -0
  214. PaIRS_UniNa/listLib.py +303 -0
  215. PaIRS_UniNa/mtfPIV.py +8 -8
  216. PaIRS_UniNa/parForMulti.py +7 -5
  217. PaIRS_UniNa/parForWorkers.py +370 -31
  218. PaIRS_UniNa/pivParFor.py +233 -229
  219. PaIRS_UniNa/plt_util.py +141 -141
  220. PaIRS_UniNa/preProcParFor.py +153 -148
  221. PaIRS_UniNa/procTools.py +631 -178
  222. PaIRS_UniNa/readcfg.py +31 -1
  223. PaIRS_UniNa/rqrdpckgs.txt +9 -0
  224. PaIRS_UniNa/stereoPivParFor.py +227 -0
  225. PaIRS_UniNa/tAVarie.py +215 -215
  226. PaIRS_UniNa/tabSplitter.py +612 -0
  227. PaIRS_UniNa/ui_Calibration_Tab.py +545 -0
  228. PaIRS_UniNa/ui_Custom_Top.py +5 -5
  229. PaIRS_UniNa/ui_Input_Tab.py +1101 -0
  230. PaIRS_UniNa/{ui_Import_Tab_CalVi.py → ui_Input_Tab_CalVi.py} +1282 -1275
  231. PaIRS_UniNa/ui_Log_Tab.py +262 -257
  232. PaIRS_UniNa/{ui_Export_Tab.py → ui_Output_Tab.py} +2361 -1778
  233. PaIRS_UniNa/ui_Process_Tab.py +3809 -3758
  234. PaIRS_UniNa/ui_Process_Tab_CalVi.py +1547 -1546
  235. PaIRS_UniNa/ui_Process_Tab_Disp.py +1141 -0
  236. PaIRS_UniNa/ui_Process_Tab_Min.py +437 -0
  237. PaIRS_UniNa/ui_Vis_Tab.py +1626 -1208
  238. PaIRS_UniNa/ui_Vis_Tab_CalVi.py +1249 -1249
  239. PaIRS_UniNa/ui_Whatsnew.py +131 -131
  240. PaIRS_UniNa/ui_gPairs.py +876 -950
  241. PaIRS_UniNa/ui_infoPaIRS.py +550 -425
  242. PaIRS_UniNa/whatsnew.txt +6 -4
  243. {PaIRS_UniNa-0.1.13.dist-info → pairs_unina-0.2.8.dist-info}/METADATA +69 -51
  244. pairs_unina-0.2.8.dist-info/RECORD +329 -0
  245. {PaIRS_UniNa-0.1.13.dist-info → pairs_unina-0.2.8.dist-info}/WHEEL +1 -1
  246. PaIRS_UniNa/CalVi.py +0 -18
  247. PaIRS_UniNa/Export_Tab.py +0 -574
  248. PaIRS_UniNa/Import_Tab.py +0 -657
  249. PaIRS_UniNa/Import_Tab_CalVi.py +0 -861
  250. PaIRS_UniNa/Import_Tab_tools.py +0 -598
  251. PaIRS_UniNa/Tree_Tab.py +0 -543
  252. PaIRS_UniNa/gCalVi.py +0 -2024
  253. PaIRS_UniNa/icons/add.png +0 -0
  254. PaIRS_UniNa/icons/cancelled.png +0 -0
  255. PaIRS_UniNa/icons/chain.png +0 -0
  256. PaIRS_UniNa/icons/chain_broken.png +0 -0
  257. PaIRS_UniNa/icons/default_sizes.png +0 -0
  258. PaIRS_UniNa/icons/dock_tabs.png +0 -0
  259. PaIRS_UniNa/icons/done.png +0 -0
  260. PaIRS_UniNa/icons/down_arrow.png +0 -0
  261. PaIRS_UniNa/icons/export_logo.png +0 -0
  262. PaIRS_UniNa/icons/fast_delete.png +0 -0
  263. PaIRS_UniNa/icons/flip_y.png +0 -0
  264. PaIRS_UniNa/icons/ganci.png +0 -0
  265. PaIRS_UniNa/icons/import_blue.png +0 -0
  266. PaIRS_UniNa/icons/import_logo.png +0 -0
  267. PaIRS_UniNa/icons/missing.png +0 -0
  268. PaIRS_UniNa/icons/process_icon.png +0 -0
  269. PaIRS_UniNa/icons/processing.png +0 -0
  270. PaIRS_UniNa/icons/restart.png +0 -0
  271. PaIRS_UniNa/icons/right_arrow.png +0 -0
  272. PaIRS_UniNa/icons/run_piv.png +0 -0
  273. PaIRS_UniNa/icons/terminal.png +0 -0
  274. PaIRS_UniNa/icons/undock_tabs.png +0 -0
  275. PaIRS_UniNa/icons/vect_field.png +0 -0
  276. PaIRS_UniNa/icons/w0.png +0 -0
  277. PaIRS_UniNa/icons/w1.png +0 -0
  278. PaIRS_UniNa/icons/w2.png +0 -0
  279. PaIRS_UniNa/icons/w3.png +0 -0
  280. PaIRS_UniNa/icons/w4.png +0 -0
  281. PaIRS_UniNa/icons/w5.png +0 -0
  282. PaIRS_UniNa/run_CalVi.py +0 -14
  283. PaIRS_UniNa/run_gcalvi.py +0 -5
  284. PaIRS_UniNa/ui_Import_Tab.py +0 -1077
  285. PaIRS_UniNa/ui_Tree_Tab.py +0 -684
  286. PaIRS_UniNa/ui_gCalVi.py +0 -640
  287. PaIRS_UniNa/ui_infoCalVi.py +0 -428
  288. PaIRS_UniNa-0.1.13.dist-info/LICENSE +0 -19
  289. PaIRS_UniNa-0.1.13.dist-info/RECORD +0 -174
  290. /PaIRS_UniNa/icons/{clean_queue.png → clean.png} +0 -0
  291. /PaIRS_UniNa/icons/{waiting_c.png → sandglass.png} +0 -0
  292. {PaIRS_UniNa-0.1.13.dist-info → pairs_unina-0.2.8.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,3136 @@
1
+ from PySide6.QtGui import QMouseEvent, QResizeEvent
2
+ from .PaIRS_pypacks import *
3
+ from .TabTools import *
4
+ from .procTools import *
5
+ from .listLib import *
6
+ from datetime import datetime
7
+ from .Input_Tab_tools import PaIRSTree
8
+ import json
9
+ from .FolderLoop import *
10
+
11
+ FlagStartingPages=True
12
+
13
+ MONTH_ABBREVIATIONS = {
14
+ 1: "Jan", 2: "Feb", 3: "Mar", 4: "Apr", 5: "May", 6: "Jun",
15
+ 7: "Jul", 8: "Aug", 9: "Sep", StepTypes.cal: "Oct", 11: "Nov", 12: "Dec"
16
+ }
17
+ projectActionButtonSize=[20,24]
18
+ processActionButtonSize=[20,24]
19
+ actionButtonSpacing=3
20
+ processButtonSize=[40,40] #icon, button
21
+ stepButtonSize=[32,32]
22
+ headerHeight=24
23
+ titleHeight=22
24
+ subtitleHeight=14
25
+ firstLevelHeight=titleHeight+subtitleHeight
26
+ titleNameWidth=150
27
+ secondLevelHeight=24
28
+ secondLevelIconSize=16
29
+ secondLevelIndentation=9
30
+ secondLevelSpacing=10
31
+ stepNameWidth=230
32
+ processTreeIndentation=10
33
+ column0Width=30
34
+
35
+ dFontPixelSize_processTitle=6
36
+ dFontPixelSize_stepTitle=3
37
+
38
+ projectActionButtons = {
39
+ '-':[],
40
+ 'info': ['Info','Information',Qt.Key.Key_F1,'information'],
41
+ 'rename': ['Rename','Rename the current project',Qt.Key.Key_F2,'editing'],
42
+ '|rename':[],
43
+ 'new': ['New','Create a new project',Qt.KeyboardModifier.ControlModifier|Qt.Key.Key_N],
44
+ 'open': ['Open','Open a previous project',Qt.KeyboardModifier.ControlModifier|Qt.Key.Key_B],
45
+ '|open':[],
46
+ 'save': ['Save','Save the current project',Qt.KeyboardModifier.ControlModifier|Qt.Key.Key_S],
47
+ 'saveas': ['Save as...','Save the current project to a specific path',Qt.KeyboardModifier.ControlModifier|Qt.KeyboardModifier.ShiftModifier|Qt.Key.Key_S],
48
+ '|save':[],
49
+ 'close': ['Close','Close the current project',Qt.KeyboardModifier.ControlModifier|Qt.Key.Key_X,'close_project'],
50
+ '|close':[],
51
+ 'clean': ['Clean','Clean the whole project list',Qt.KeyboardModifier.ControlModifier|Qt.KeyboardModifier.ShiftModifier|Qt.Key.Key_X],
52
+ }
53
+ projectGlobalActionButtons=['new','open','clean']
54
+ projectPageButtons={}
55
+ for n in ('new','open'):
56
+ d={}
57
+ d['name']=projectActionButtons[n][0]
58
+ d['caption']=projectActionButtons[n][1]
59
+ if len(projectActionButtons[n])>3:
60
+ d['icon']=projectActionButtons[n][0][3]
61
+ else:
62
+ d['icon']=n+'.png'
63
+ projectPageButtons[n]=d
64
+
65
+ processActionButtons = {
66
+ '-': [],
67
+ 'import': ['Import','Import processes from disk',Qt.KeyboardModifier.ControlModifier|Qt.Key.Key_B,'open',],
68
+ '|import':[],
69
+ 'saveas': ['Save as...','Save the current project to a specific path',Qt.KeyboardModifier.ControlModifier|Qt.KeyboardModifier.ShiftModifier|Qt.Key.Key_S],
70
+ '|saveas':[],
71
+ 'info': ['Info','Information',Qt.Key.Key_F1,'information'],
72
+ 'rename': ['Rename','Rename the current process',Qt.Key.Key_F2,'editing',],
73
+ '|rename': [],
74
+ 'copy': ['Copy', 'Copy process',Qt.KeyboardModifier.ControlModifier|Qt.Key.Key_C,'copy',],
75
+ '|paste': [],
76
+ 'paste_below': ['Paste below','Paste below the current process', Qt.KeyboardModifier.ControlModifier|Qt.Key.Key_V,'paste_below',],
77
+ 'paste_above': ['Paste above','Paste above the current process',Qt.KeyboardModifier.ControlModifier|Qt.KeyboardModifier.ShiftModifier|Qt.Key.Key_V,'paste_above',],
78
+ '|process_loop': [],
79
+ 'process_loop': ['Copy for process loop', 'Copy process to loop over folders',Qt.KeyboardModifier.ControlModifier|Qt.Key.Key_L,],
80
+ '|restore': [],
81
+ 'restore': ['Restore','Restore the current process',Qt.KeyboardModifier.ControlModifier|Qt.Key.Key_R,'restore',],
82
+ '|delete': [],
83
+ 'delete': ['Delete','Delete the current process',[Qt.Key.Key_Delete,Qt.Key.Key_Backspace],'delete',],
84
+ '|clean': [],
85
+ 'clean': ['Clean','Clean the whole list',[Qt.KeyboardModifier.ShiftModifier|Qt.Key.Key_Delete,Qt.KeyboardModifier.ShiftModifier|Qt.Key.Key_Backspace],'clean'],
86
+ }
87
+ processGlobalActionButtons=['import','paste_below','paste_above','clean']
88
+
89
+ class TreeIcons():
90
+ icons={
91
+ None: None,
92
+ 'project.png': None,
93
+ 'min_proc.png': None,
94
+ 'piv_proc.png': None,
95
+ 'cal_proc.png': None,
96
+ 'spiv_proc.png': None,
97
+ 'cal_step.png': None,
98
+ 'min_step.png': None,
99
+ 'piv_step.png': None,
100
+ 'disp_step.png': None,
101
+ 'cal_step_off.png': None,
102
+ 'min_step_off.png': None,
103
+ 'piv_step_off.png': None,
104
+ 'disp_step_off.png': None,
105
+ 'redx.png': None,
106
+ 'greenv.png': None,
107
+ 'completed.png': None,
108
+ 'issue.png': None,
109
+ 'running.png': None,
110
+ 'warning_circle.png': None,
111
+ 'running_warn.png': None,
112
+ 'paused.png': None,
113
+ 'queue.png': None,
114
+ 'warning.png': None,
115
+ 'workspace.png': None,
116
+ 'uninitialized.png':None,
117
+ 'linked.png':None
118
+ }
119
+ pixmaps={}
120
+ FlagInit=False
121
+
122
+ def __init__(self):
123
+ if not TreeIcons.FlagInit:
124
+ for t in TreeIcons.icons:
125
+ if t:
126
+ TreeIcons.icons[t]=QIcon(icons_path+t)
127
+ TreeIcons.pixmaps[t]=QPixmap(icons_path+t)
128
+ else:
129
+ TreeIcons.icons[t]=QIcon()
130
+ TreeIcons.pixmaps[t]=QPixmap()
131
+ TreeIcons.FlagInit=True
132
+
133
+ def currentTimeString():
134
+ current_time = datetime.now().strftime("%b %d, %Y, %I:%M:%S %p")
135
+ month_number = datetime.now().month # Get the current month number
136
+ month_abbreviation = MONTH_ABBREVIATIONS.get(month_number, "Mmm") # Get the month abbreviation based on the number
137
+ current_time = current_time.replace(current_time[:3], month_abbreviation) # Replace the month abbreviation
138
+ current_time = current_time.replace("am", "AM").replace("pm", "PM") # Convert AM/PM to uppercase
139
+ return current_time
140
+
141
+ def scrollAreaStyle():
142
+ style="""
143
+ QScrollArea
144
+ {
145
+ border: 1pix solid gray;
146
+ background: transparent;
147
+ }
148
+
149
+ QScrollBar:horizontal
150
+ {
151
+ height: 15px;
152
+ margin: 3px 10px 3px 10px;
153
+ border: 1px transparent #2A2929;
154
+ border-radius: 4px;
155
+ background-color: rgba(200,200,200,50); /* #2A2929; */
156
+ }
157
+
158
+ QScrollBar::handle:horizontal
159
+ {
160
+ background-color: rgba(180,180,180,180); /* #605F5F; */
161
+ min-width: 5px;
162
+ border-radius: 4px;
163
+ }
164
+
165
+ QScrollBar:vertical
166
+ {
167
+ background-color: rgba(200,200,200,50); ;
168
+ width: 15px;
169
+ margin: 10px 3px 10px 3px;
170
+ border: 1px transparent #2A2929;
171
+ border-radius: 4px;
172
+ }
173
+
174
+ QScrollBar::handle:vertical
175
+ {
176
+ background-color: rgba(180,180,180,180); /* #605F5F; */
177
+ min-height: 5px;
178
+ border-radius: 4px;
179
+ }
180
+
181
+ QScrollBar::add-line {
182
+ border: none;
183
+ background: none;
184
+ }
185
+
186
+ QScrollBar::sub-line {
187
+ border: none;
188
+ background: none;
189
+ }
190
+ """
191
+ return style
192
+
193
+ class TREpar(TABpar):
194
+ def __init__(self):
195
+ self.setup()
196
+ super().__init__(self.name,'PaIRS_Explorer')
197
+ self.unchecked_fields+=[]
198
+
199
+ def setup(self):
200
+ self.name_proj, self.username, self.version = identifierName(typeObject='proj')
201
+ self.outName = ''
202
+ self.createdDate = currentTimeString()
203
+ self.modifiedDate = self.createdDate
204
+ self.savedDate = ''
205
+ self.FlagSaved = False
206
+ self.FlagQueue = True
207
+ self.FlagRunnable = True
208
+
209
+ self.project = None
210
+ self.tree = 0
211
+ self.process = None
212
+ self.step = None
213
+ self.basename = 'Project'
214
+ self.name = 'Project 1'
215
+ self.date = f'Created: {self.createdDate}'
216
+ self.icon = 'project.png'
217
+
218
+ def saveBullet(self):
219
+ return '' if self.FlagSaved else '<span style="color: #7A8B8B;"><sup>&#9679;</sup></span>'
220
+
221
+ def InfoMessage(self):
222
+ InfoMessage=f'{self.name}'
223
+ if self.FlagSaved:
224
+ InfoMessage+=f'\nFile location: {self.outName}'
225
+ else:
226
+ if self.savedDate:
227
+ InfoMessage+=' (unsaved)'
228
+ else:
229
+ InfoMessage+=' (never saved)'
230
+ InfoMessage+=f'\n\nCreated : {self.createdDate}'
231
+ InfoMessage+=f'\nModified: {self.modifiedDate}'
232
+ if self.savedDate: InfoMessage+=f'\nSaved : {self.savedDate}'
233
+ InfoMessage+=f'\n\nUser: {self.username}'
234
+ InfoMessage+=f'\nPaIRS version: {self.version}'
235
+ return InfoMessage
236
+
237
+ class ITEpar(TABpar):
238
+ def __init__(self,Process=None,Step=None):
239
+ self.setup(Process,Step)
240
+ super().__init__(self.name,'PaIRS_Explorer')
241
+ self.OptionDone=0
242
+ self.warningMessage='Process step not yet initialized!'
243
+ self.procdata.ind=self.ind
244
+ self.unchecked_fields+=[]
245
+
246
+ def setup(self,Process,Step):
247
+ self.Process = Process
248
+ self.Step = Step
249
+
250
+ self.name_proc, self.username, self.version = identifierName(typeObject='proc')
251
+ self.createdDate = currentTimeString()
252
+ self.modifiedDate = self.createdDate
253
+ self.savedDate = ''
254
+ self.FlagSaved = False
255
+ self.FlagQueue = True
256
+ self.dependencies = []
257
+
258
+ self.name=''
259
+ self.icon=''
260
+ self.date=f'Created: {self.createdDate}'
261
+ self.children={}
262
+ self.parents=[]
263
+ self.mandatory=[]
264
+
265
+ self.tabs=[]
266
+ self.active=False
267
+ self.label='uninitialized'
268
+ self.progress=0
269
+ self.ncam=0
270
+
271
+ self.data_fields=[f for f,_ in self.__dict__.items()]
272
+
273
+ buttons=processData if Step is None else stepData
274
+ type=Process if Step is None else Step
275
+ if type in list(buttons):
276
+ for k,v in buttons[type].items():
277
+ if k in self.data_fields:
278
+ setattr(self,k,v)
279
+ self.basename=self.name
280
+ self.name=self.basename
281
+
282
+ self.procdata=dataTreePar(Process,Step)
283
+
284
+ def copyfrom(self, newist, exceptions=None):
285
+ super().copyfrom(newist, exceptions)
286
+ self.procdata.ind=self.ind
287
+ return
288
+
289
+ def InfoMessage(self):
290
+ InfoMessage=f'{self.name}'
291
+ InfoMessage+=f'\n\nCreated : {self.createdDate}'
292
+ InfoMessage+=f'\nModified: {self.modifiedDate}'
293
+ InfoMessage+=f'\n\nUser: {self.username}'
294
+ InfoMessage+=f'\nPaIRS version: {self.version}'
295
+ return InfoMessage
296
+
297
+ class ModernSwitch(QWidget):
298
+ toggled = Signal(bool)
299
+ handleMargin = 3
300
+ switchHeight=25
301
+ switchWidth=2*(switchHeight+handleMargin)
302
+ timerTime=50 #ms
303
+
304
+ def __init__(self, parent=None, name='', par:TREpar=None):
305
+ super().__init__(parent)
306
+ if parent is None:
307
+ self.gui=self.window()
308
+ else:
309
+ from .gPaIRS import gPaIRS
310
+ if hasattr(parent,'gui'):
311
+ self.gui:gPaIRS=parent.gui
312
+ else:
313
+ self.gui:gPaIRS=parent.window()
314
+ self.name=name
315
+ self.par=par
316
+
317
+
318
+ FlagWindowRun=self.gui.FlagRun if hasattr(self.gui,'FlagRun') else False
319
+ self.setFixedSize(self.switchWidth, self.switchHeight)
320
+ if par:
321
+ self._checked = par.FlagQueue #and not FlagWindowRun
322
+ else:
323
+ self._checked = True
324
+ par.FlagQueue=self._checked
325
+
326
+ # Load image for the handle and scale it to fit the ellipse
327
+ self.handle_image = QPixmap(icons_path+"gear.png") # Replace with your image path
328
+ self.handle_image = self.handle_image.scaledToHeight(self.height() - self.handleMargin*2, Qt.SmoothTransformation)
329
+
330
+ self.setCursor(QCursor(QtCore.Qt.PointingHandCursor))
331
+ self.FlagAnimation=False
332
+ self.setSwitchLayout()
333
+
334
+ self.animation = QPropertyAnimation(self, b"handle_position", self)
335
+ self.animation.setDuration(200)
336
+ self.animation.finished.connect(self.setSwitchLayout)
337
+
338
+ self.text_label = QLabel("run ", self)
339
+ #self.text_label.setStyleSheet("color: white;") # Customize text color if needed
340
+ font=self.text_label.font()
341
+ #font.setItalic(True)
342
+ font.setWeight(QFont.Weight.Light)
343
+ self.text_label.setFont(font)
344
+ self.text_label.adjustSize()
345
+
346
+ self.setFixedSize(self.switchWidth, self.switchHeight)
347
+ self.setContentsMargins(self.handleMargin,0,0,0)
348
+
349
+ # Timer per aggiornare l'animazione
350
+ self.FlagRunning=False
351
+ self.gearMovie:QMovie=None
352
+ self.timer = QTimer(self)
353
+ self.timer.timeout.connect(self.update)
354
+
355
+ self.setEnabled(not FlagWindowRun)
356
+ self.setSwitch(self._checked)
357
+ if par and self.gui.procdata:
358
+ if self.gui.procdata.ind[:-2]==par.ind[:-2]:
359
+ self.startTimer()
360
+ #self.timer.start(50) # Aggiorna ogni 50 ms
361
+
362
+ def startTimer(self):
363
+ if not self.FlagRunning and hasattr(self.gui,'gearMovie'):
364
+ self.FlagRunning=True
365
+ self.gearMovie=self.gui.gearMovie
366
+ self.timer.start(self.timerTime)
367
+ pri.Coding.yellow('Start switch timer')
368
+
369
+ def stopTimer(self):
370
+ if self.FlagRunning:
371
+ self.FlagRunning=False
372
+ self.timer.stop()
373
+
374
+ def set_handle_position(self, pos):
375
+ self._handle_position = pos
376
+ self.update()
377
+
378
+ def get_handle_position(self):
379
+ return self._handle_position
380
+
381
+ handle_position = Property(int, fget=get_handle_position, fset=set_handle_position)
382
+
383
+ def paintEvent(self, event):
384
+ painter = QPainter(self)
385
+ painter.setRenderHint(QPainter.Antialiasing)
386
+
387
+ # Draw background ellipse with border
388
+ bg_rect = QRect(0, 0, self.width(), self.height())
389
+
390
+ # Draw filled background ellipse
391
+ painter.setBrush(self._bg_color)
392
+ painter.drawRoundedRect(bg_rect.adjusted(1, 1, -1, -1), self.height() / 2, self.height() / 2)
393
+
394
+ # Calculate position and scale for image
395
+ handle_x = self._handle_position
396
+ handle_y = self.handleMargin
397
+ handle_width = self.handle_image.width()
398
+ handle_height = self.handle_image.height()
399
+
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())
416
+ else:
417
+ painter.drawImage(handle_rect,self.handle_image.toImage())
418
+
419
+ if self.FlagAnimation:
420
+ self.text_label.setText("")
421
+ else:
422
+ if self._checked:
423
+ self.text_label.setText("run ")
424
+ self.text_label.move(self.handleMargin*2, (self.height() - self.text_label.height()) // 2)
425
+ else:
426
+ self.text_label.setText("skip")
427
+ self.text_label.move(self.width() - self.height() - self.handleMargin*2, (self.height() - self.text_label.height()) // 2)
428
+
429
+ def mousePressEvent(self, event):
430
+ if event.button() == Qt.LeftButton and not self.FlagAnimation:
431
+ self.toggle()
432
+
433
+ def toggle(self):
434
+ self._checked = not self._checked
435
+ if self.par: self.par.FlagQueue = self._checked
436
+ self.toggled.emit(self._checked)
437
+ self.animation.setEndValue(self.switchHandlePosition())
438
+ self.FlagAnimation=True
439
+ self.animation.start()
440
+
441
+ def switchHandlePosition(self):
442
+ if self._checked:
443
+ handle_position = self.width() - self.height() + self.handleMargin
444
+ else:
445
+ handle_position = self.handleMargin
446
+ return handle_position
447
+
448
+ def setSwitchLayout(self):
449
+ self.FlagAnimation=False
450
+ 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.'
453
+ else:
454
+ tip=f'{self.name if self.name else "Item"} excluded from run queue.'
455
+ #text_color = self.palette().color(QPalette.WindowText)
456
+ self._bg_color = QColor(175, 175, 175)
457
+ self._bg_color.setAlpha(0.5)
458
+ self.setToolTip(tip)
459
+ self.setStatusTip(tip)
460
+ self.set_handle_position(self.switchHandlePosition())
461
+
462
+ def setSwitch(self,FlagChecked):
463
+ if FlagChecked!=self._checked:
464
+ self._checked=FlagChecked
465
+ self.setSwitchLayout()
466
+
467
+ class ActionButtonBar(QWidget):
468
+ FlagShortCuts = True
469
+
470
+ def __init__(self, buttonData:dict={}, additionalButtonBars={}, globalButtons=[], buttonSize:list=projectActionButtonSize, FlagInvisible=True, tree:PaIRSTree=None):
471
+ super().__init__()
472
+ self.buttonLayout = QHBoxLayout()
473
+ self.buttonLayout.setContentsMargins(0, 0, 0, 0)
474
+ self.buttonLayout.setSpacing(actionButtonSpacing)
475
+ self.setLayout(self.buttonLayout)
476
+
477
+ self.buttonData=buttonData
478
+ self.additionalButtonBars=additionalButtonBars
479
+ self.globalButtons=globalButtons
480
+ self.buttonSize = bs = buttonSize
481
+ self.FlagInvisible=FlagInvisible
482
+ self.tree:QWidget=None
483
+ self.tree=tree
484
+ if self.tree:
485
+ self.tree.actionBar=self
486
+
487
+ self.bShortCuts={}
488
+ for icon_name, data in buttonData.items():
489
+ if '-' in icon_name:
490
+ self.buttonLayout.addItem(QSpacerItem(bs[1], bs[1], QSizePolicy.Expanding, QSizePolicy.Minimum))
491
+ elif '|' in icon_name:
492
+ separator = QFrame()
493
+ separator.setFrameShape(QFrame.VLine)
494
+ separator.setFrameShadow(QFrame.Sunken)
495
+ setattr(self,'sep_'+icon_name[1:],separator)
496
+ self.buttonLayout.addWidget(separator)
497
+ else:
498
+ b = QToolButton(self)
499
+ b.setObjectName('button_'+icon_name)
500
+ b.setCursor(Qt.CursorShape.PointingHandCursor)
501
+ if FlagInvisible:
502
+ b.setStyleSheet("QToolButton { border: none; background: none;} QToolButton::menu-indicator { image: none; }")
503
+ b.pressed.connect(lambda btn=b: btn.setStyleSheet("QToolButton { border: none; background: #dcdcdc;} QToolButton::menu-indicator { image: none; }"))
504
+ b.released.connect(lambda btn=b: btn.setStyleSheet("QToolButton { border: none; background: none;} QToolButton::menu-indicator { image: none; }"))
505
+ setattr(b,'initialStyle',b.styleSheet())
506
+ b.setToolTip(data[1])
507
+ b.setStatusTip(data[1])
508
+
509
+ if len(data)>3:
510
+ icon_file=icons_path+data[3]+'.png'
511
+ else:
512
+ icon_file=icons_path+icon_name+'.png'
513
+ b.setIcon(QIcon(icon_file))
514
+ b.setIconSize(QSize(bs[0],bs[0]))
515
+ b.setFixedSize(bs[1],bs[1]) # Impostare la dimensione quadrata
516
+ tip=data[1]
517
+ if self.FlagShortCuts:
518
+ if len(data)>2:
519
+ if data[2]:
520
+ data2list=data[2] if isinstance(data[2],list) else [data[2]]
521
+ addtip=[]
522
+ for data2 in data2list:
523
+ addtip.append(QKeySequence(data2).toString(QKeySequence.NativeText) )
524
+ def buttonClick(b:QToolButton):
525
+ if b.isVisible() and b.isEnabled(): b.click()
526
+ self.bShortCuts[data2]=lambda but=b:buttonClick(but)
527
+ tip+=' ('+" or ".join(addtip)+')'
528
+ b.setToolTip(tip)
529
+ b.setStatusTip(tip)
530
+ setattr(self,'button_'+icon_name,b)
531
+ self.buttonLayout.addWidget(b)
532
+ if self.tree: self.setButtonActions()
533
+
534
+ def setButtonActions(self):
535
+ for icon_name, data in self.buttonData.items():
536
+ if '-' not in icon_name and '|' not in icon_name:
537
+ if hasattr(self.tree,'button_'+icon_name+'_action'):
538
+ b:QPushButton=getattr(self,'button_'+icon_name)
539
+ b.clicked.connect(getattr(self.tree,'button_'+icon_name+'_action'))
540
+ self.tree.contextMenuEvent=lambda e: ActionButtonBar.treeContextMenuEvent(self,e)
541
+
542
+ def treeContextMenuEvent(self, event):
543
+ tree:PaIRSTree=self.tree
544
+ item=tree.itemAt(event.pos())
545
+ if 'globals' in self.additionalButtonBars:
546
+ buttonBars=[bar for bar in self.additionalButtonBars['globals']]
547
+ else:
548
+ buttonBars=[]
549
+ if item is None:
550
+ buttons=self.globalButtons
551
+ else:
552
+ if item.parent() is None:
553
+ buttons=list(self.buttonData)
554
+ else:
555
+ buttons=self.globalButtons
556
+ if 'items' in self.additionalButtonBars:
557
+ buttonBars=[bar for bar in self.additionalButtonBars['items']]+buttonBars
558
+ if len(buttons)==0 and len(buttonBars)==0:
559
+ return
560
+ tree.FlagContexMenu=True
561
+
562
+ menu=QMenu(tree)
563
+ name=[]
564
+ act=[]
565
+ fun=[]
566
+
567
+ for bar in buttonBars:
568
+ bar:ProcessButtonBar
569
+ for type, b in bar.buttons.items():
570
+ b:QPushButton
571
+ if b.isVisible() and b.isEnabled():
572
+ FlagNamedBar='name' in bar.buttonData[type]
573
+ if FlagNamedBar:
574
+ nameAction=bar.buttonData[type]['name']
575
+ else:
576
+ nameAction=b.toolTip()
577
+ name.append(nameAction)
578
+ if FlagNamedBar and 'class' in bar.buttonData[type]:
579
+ if 'PIV'!=nameAction[:3]: nameAction=nameAction[:1].lower()+nameAction[1:]
580
+ if b.isCheckable():
581
+ nameAction=f"{'De-activate' if b.isChecked() else 'Activate'} {nameAction}"
582
+ else:
583
+ nameAction='Add '+nameAction
584
+ act.append(QAction(b.icon(),nameAction,self))
585
+ menu.addAction(act[-1])
586
+ if isinstance(b,DraggableButton):
587
+ callback=lambda butt=b: butt.buttonAction()
588
+ else:
589
+ callback=lambda butt=b: butt.click()
590
+ fun.append(callback)
591
+ if len(act): menu.addSeparator()
592
+ for nb in buttons:
593
+ if '-' not in nb and '|' not in nb:
594
+ b:QPushButton=getattr(self,'button_'+nb)
595
+ if b.isVisible() and b.isEnabled():
596
+ if hasattr(tree,'button_'+nb+'_action'):
597
+ name.append(nb)
598
+ act.append(QAction(b.icon(),toPlainText(b.toolTip().split('.')[0]),self))
599
+ menu.addAction(act[-1])
600
+ callback=getattr(tree,'button_'+nb+'_action')
601
+ fun.append(callback)
602
+ elif '|' in nb:
603
+ if len(act): menu.addSeparator()
604
+
605
+ if len(act):
606
+ action = menu.exec(tree.mapToGlobal(event.pos()))
607
+ for nb,a,f in zip(name,act,fun):
608
+ if a==action:
609
+ f()
610
+ return
611
+
612
+ def treeKeyPressEvent(self,event):
613
+ for ksc,f in self.bShortCuts.items():
614
+ if ksc is None: continue
615
+ if type(ksc)!=list: ksc=[ksc]
616
+ for k in ksc:
617
+ if type(k)==QKeyCombination:
618
+ if event.key()==k.key() and event.modifiers()==k.keyboardModifiers():
619
+ f()
620
+ return True # Impedisce al widget di ricevere l'evento
621
+ else:
622
+ if event.key()==k and not event.modifiers():
623
+ f()
624
+ return True # Impedisce al widget di ricevere l'evento
625
+
626
+ class ProjectTree(PaIRSTree):
627
+ def __init__(self, parent: QWidget=None, listDim=2, listDepth=1, projectActionBar:ActionButtonBar=None, widgets:list=[], Explorer=None):
628
+ super().__init__(parent, listDim, listDepth)
629
+ self.setSelectionMode(QTreeWidget.SelectionMode.SingleSelection)
630
+ self.setSelectionBehavior(QTreeWidget.SelectionBehavior.SelectRows)
631
+ self.setIconSize(QSize(firstLevelHeight-4, firstLevelHeight-4))
632
+
633
+ columns=["#","Projects"]
634
+
635
+ self.setColumnCount(len(columns))
636
+ self.setHeaderLabels(columns)
637
+ header=self.header()
638
+ header.setFixedHeight(headerHeight)
639
+ self.headerItem().setTextAlignment(0,Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
640
+ header.setSectionResizeMode(0, QHeaderView.ResizeMode.Fixed)
641
+ self.setColumnWidth(0,column0Width)
642
+ self.setIndentation(0)
643
+ header.setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch)
644
+ font=header.font()
645
+ font.setItalic(True)
646
+ font.setPixelSize(fontPixelSize+dFontPixelSize_stepTitle)
647
+ header.setFont(font)
648
+
649
+ self.initialStyleSheet=self.styleSheet()
650
+ self.FlagExternalDrag=False
651
+
652
+ self.itemDoubleClicked.connect(self.startEditing)
653
+ self.FlagReset=False
654
+ self.editingFinished=lambda: None
655
+
656
+ self.actionBar=projectActionBar
657
+ if self.actionBar:
658
+ self.actionBar.tree=self
659
+ self.actionBar.setButtonActions()
660
+ self.setupWidgets(widgets)
661
+
662
+ self.TREpar=TREpar()
663
+ self.TREpar_None=TREpar()
664
+ self.adjustSelection=lambda: None
665
+ #self.currentItemChanged.connect(self.tree_item_selection)
666
+ self.itemSelectionChanged.connect(self.tree_item_selection)
667
+ self.tree_item_selection()
668
+
669
+ self.Explorer:PaIRS_Explorer=Explorer
670
+ self.adjustSwitches = lambda: None
671
+ self.modifyWorkspace = lambda: None
672
+ self.setVisible(True)
673
+
674
+ def setupWidgets(self,widgets):
675
+ self.widgets=widgets
676
+ expand_level(self.itemList,0,len(widgets)+2)
677
+ for w,itemList in zip(self.widgets,self.itemList[2:]):
678
+ w:gPaIRS_Tab
679
+ w.TABpar_prev=itemList
680
+
681
+ def createProject(self,TRE:TREpar):
682
+ Project=[ [TRE], [ [[],[]] ] ]
683
+ if not self.widgets: return Project
684
+ for w in self.widgets:
685
+ w:gPaIRS_Tab
686
+ Project.append([[]])
687
+ Project[-1][-1].append([]) #processTree
688
+ Project[-1][-1].append([]) #binTree
689
+ return Project
690
+
691
+ def createProjectItem(self,ind=None,FlagNewItem=True):
692
+ self.blockSignals(True)
693
+ if ind==None: ind=self.topLevelItemCount()
694
+ item = QTreeWidgetItem()
695
+
696
+ if FlagNewItem:
697
+ TRE=TREpar()
698
+ TRE.project=ind
699
+ projectNames=[item.name for item in self.itemList[0]]
700
+ nameInd=1
701
+ TRE.name=f'{TRE.basename} {nameInd}'
702
+ while TRE.name in projectNames:
703
+ nameInd+=1
704
+ TRE.name=f'{TRE.basename} {nameInd}'
705
+ Project=self.createProject(TRE)
706
+ insert_at_depth(self.itemList,self.listDepth,ind,Project)
707
+ else:
708
+ TRE=self.itemList[0][ind]
709
+
710
+ item.setText(0,str(ind))
711
+ item.setTextAlignment(0,Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter)
712
+ item.setIcon(1, TreeIcons.icons[TRE.icon]) # Set the icon of the new item
713
+ #item.setFlags(item.flags() | Qt.ItemIsEditable)
714
+
715
+ ind=max([0,ind])
716
+ self.insertTopLevelItem(ind,item)
717
+ self.setProjectItemWidget(item,TRE)
718
+
719
+ item.setData(0,Qt.ItemDataRole.UserRole,[False])
720
+ self.resetImNumber(ind)
721
+ self.blockSignals(False)
722
+ if FlagNewItem: self.signals.updateLists.emit()
723
+ return item
724
+
725
+ def setProjectItemWidget(self,item:QTreeWidgetItem,TRE:TREpar):
726
+ title_label = ResizingLabel(TRE.name+TRE.saveBullet())
727
+ title_label.setObjectName('title_project')
728
+ title_label.setFixedHeight(titleHeight)
729
+ title_label.setMinimumWidth(titleNameWidth)
730
+ title_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
731
+ # Set the font of the title to bold and the subtitle to double font
732
+ self.titleFont(title_label)
733
+
734
+ sub_title=TRE.date
735
+ sub_title_label = ResizingLabel(sub_title) # Create a QLabel for the subtitle
736
+ sub_title_label.setObjectName('subtitle_project')
737
+ sub_title_label.setFixedHeight(subtitleHeight)
738
+ sub_title_label.setMinimumWidth(titleNameWidth)
739
+ sub_title_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
740
+ self.subTitleFont(sub_title_label)
741
+
742
+ project_title_layout = QVBoxLayout() # Create a vertical layout to place the subtitle below the title
743
+ project_title_layout.addWidget(title_label) # Add the title of the element
744
+ project_title_layout.addWidget(sub_title_label) # Add the subtitle
745
+
746
+ project_title_layout.setContentsMargins(10, 2, 0, 2) # Remove margins
747
+ project_title_layout.setSpacing(2) # Remove margins
748
+
749
+ switch = ModernSwitch(parent=self,name='Project',par=TRE)
750
+ switch.setSwitch(TRE.FlagQueue)
751
+ switch.toggled.connect(lambda: self.toogleProcesses(TRE))
752
+ switch.setVisible(len(self.itemList[1][TRE.project][0])>0)
753
+
754
+ #spacer=QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Minimum)
755
+ main_layout=QHBoxLayout()
756
+ main_layout.addLayout(project_title_layout)
757
+ #main_layout.addSpacerItem(spacer)
758
+ main_layout.addWidget(switch)
759
+ main_layout.setContentsMargins(0,0,10,0)
760
+ main_layout.setSpacing(6)
761
+ main_layout.setStretch(0,0)
762
+ main_layout.setStretch(1,1)
763
+
764
+ InfoMessage=TRE.InfoMessage()
765
+ title_label.setToolTip(InfoMessage)
766
+ title_label.setStatusTip(TRE.name)
767
+ sub_title_label.setToolTip(sub_title)
768
+ sub_title_label.setStatusTip(sub_title)
769
+
770
+ widget = QWidget(self) # Create a widget container for the layout
771
+ widget.setLayout(main_layout) # Set the layout for the widget container
772
+ widget.setMinimumWidth(titleNameWidth+switch.switchWidth+main_layout.spacing())
773
+ self.setItemWidget(item, 1 ,widget)
774
+ return widget
775
+
776
+ def toogleProcesses(self,TRE:TREpar):
777
+ ITELists=self.itemList[1][TRE.project][0]
778
+ for k,ITEList in enumerate(ITELists):
779
+ if self.TREpar.project==TRE.project:
780
+ topLevelItem=self.Explorer.processTree.topLevelItem(k)
781
+ itemWidget=self.Explorer.processTree.itemWidget(topLevelItem,1)
782
+ switch:ModernSwitch=itemWidget.findChildren(ModernSwitch)[0]
783
+ if TRE.FlagQueue!=switch._checked:
784
+ switch.blockSignals(True)
785
+ switch.toggle()
786
+ switch.blockSignals(False)
787
+ else:
788
+ ITEList[0].FlagQueue=TRE.FlagQueue
789
+ if self.Explorer.TREpar.process is not None:
790
+ self.Explorer.ITEpar.FlagQueue=self.Explorer.ITEfromInd(self.Explorer.ITEpar.ind).FlagQueue
791
+ self.Explorer.TREpar.FlagQueue=self.Explorer.projectTree.itemList[0][self.Explorer.TREpar.project].FlagQueue
792
+ self.adjustSwitches()
793
+ #self.itemSelectionChanged.emit()
794
+
795
+ def titleFont(self,label:QLabel,fPixelSize=None):
796
+ if fPixelSize is None:
797
+ if hasattr(self.gui,'GPApar'): fPixelSize=self.gui.GPApar.fontPixelSize
798
+ else: fPixelSize=fontPixelSize
799
+ font = label.font()
800
+ font.setFamily(fontName)
801
+ font.setBold(True) # Bold title
802
+ fPS=min([fPixelSize+dFontPixelSize_processTitle,fontPixelSize+dFontPixelSize_processTitle+3])
803
+ font.setPixelSize(fPS) # Double font size for subtitle
804
+ label.setFont(font)
805
+
806
+ def subTitleFont(self,label:QLabel,fPixelSize=None):
807
+ if fPixelSize is None:
808
+ if hasattr(self.gui,'GPApar'): fPixelSize=self.gui.GPApar.fontPixelSize
809
+ else: fPixelSize=fontPixelSize
810
+ font = label.font()
811
+ font.setFamily(fontName)
812
+ font.setItalic(True) # Bold title
813
+ fPS=min([fPixelSize,fontPixelSize+3])
814
+ font.setPixelSize(fPS) # Double font size for subtitle
815
+ label.setFont(font)
816
+
817
+ def dropEvent(self,event):
818
+ self.blockSignals(True)
819
+ items=self.selectedItems()
820
+ indexes=[self.indexOfTopLevelItem(i) for i in items]
821
+ drop_indicator_position = self.dropIndicatorPosition()
822
+ if drop_indicator_position == QTreeWidget.DropIndicatorPosition.OnItem or self.hovered_item is None:
823
+ self.verticalScrollBar().setValue(self.verticalScrollBarVal)
824
+ QCursor.setPos(self.cursor_pos)
825
+ event.ignore() # Ignore the event if it's not a row move or a drop on an item
826
+ return
827
+ else:
828
+ QTreeWidget.dropEvent(self,event)
829
+ self.dropLists(items,indexes)
830
+ for n,item in enumerate(items):
831
+ k=self.indexOfTopLevelItem(item)
832
+ TRE:TREpar=self.itemList[0][k]
833
+ pri.Coding.blue(f'Dropping {TRE.name} {indexes[n]} --> {k}')
834
+ self.setProjectItemWidget(item,TRE)
835
+ self.dragged_items=self.dragged_indexes=None
836
+ self.repaint()
837
+ self.TREpar.copyfrom(TRE)
838
+ self.setCurrentItem(item)
839
+ self.blockSignals(False)
840
+ self.tree_item_selection()
841
+
842
+ def resetImNumber(self,kin=None,kfin=None):
843
+ super().resetImNumber(kin,kfin)
844
+ if not kin: kin=0
845
+ if not kfin: kfin=self.topLevelItemCount()-1
846
+ TREList=self.itemList[0]
847
+ for i in range(kin, kfin + 1):
848
+ TRE:TREpar=TREList[i]
849
+ TRE.project=i
850
+ return
851
+
852
+ def printProjects(self):
853
+ pri.Coding.red(f'{"*"*50}\nProject list (process tree):')
854
+ for k in range(len(self.itemList[0])):
855
+ pri.Coding.green(f'{k+1}) {self.itemList[0][k].name}')
856
+ for i in self.itemList[1][k][0]:
857
+ for n,j in enumerate(i):
858
+ if n==0: pri.Coding.yellow(f' {j.name}')
859
+ else: pri.Coding.cyan (f' {j.name}')
860
+ pri.Coding.red(f'{"*"*50}\n')
861
+
862
+ def startEditing(self, item, column):
863
+ if self.itemWidget(item, column):
864
+ self.editItem(item, column)
865
+
866
+ def editItem(self, item, column):
867
+ if column == 0 or item.parent() is not None: # Se è la prima colonna, non fare nulla
868
+ return
869
+ widget = self.itemWidget(item, column)
870
+ if isinstance(widget, QLineEdit):
871
+ return # If already editing, do nothing
872
+ k=self.indexOfTopLevelItem(item)
873
+ text = self.itemList[0][k].name
874
+ line_edit = CustomLineEdit(text, self)
875
+ self.titleFont(line_edit)
876
+ line_edit.setText(text)
877
+ self.setItemWidget(item, column, line_edit)
878
+ line_edit.selectAll()
879
+ line_edit.setFocus()
880
+ line_edit.editingFinished.connect(lambda: self.finishEditing(item, column))
881
+ line_edit.cancelEditing.connect(lambda: self.finishEditing(item, column))
882
+
883
+ def finishEditing(self, item, column):
884
+ line_edit = self.itemWidget(item, column)
885
+ if not hasattr(line_edit,'text'):
886
+ return
887
+ new_text = line_edit.text()
888
+ k=self.indexOfTopLevelItem(item)
889
+ TRE:TREpar=self.itemList[0][k]
890
+ TRE.name=new_text
891
+ self.TREpar.name=new_text
892
+ self.setProjectItemWidget(item, TRE)
893
+ self.editingFinished()
894
+
895
+ def keyPressEvent(self, event):
896
+ self.actionBar.treeKeyPressEvent(event)
897
+ if event.key() == Qt.Key.Key_Escape:
898
+ self.setCurrentItem(None)
899
+ self.clearSelection()
900
+ else:
901
+ super().keyPressEvent(event)
902
+
903
+ def cutItems(self,items,FlagDelete=False):
904
+ cutted_items=[None]*len(items)
905
+ for k,item in enumerate(items):
906
+ cutted_items[k]=self.duplicateItem(item)
907
+ if FlagDelete: type(self).deleted_items=cutted_items
908
+ else: type(self).cutted_items=cutted_items
909
+ return
910
+
911
+ def copy_cut_delete_action(self,FlagCut=False,FlagDelete=False):
912
+ items,indexes=self.selectTopLevel()
913
+ if not items: return
914
+ if FlagDelete: FlagCut=False
915
+ self.cutItems(items,FlagDelete)
916
+
917
+ FlagSignal=True
918
+ if FlagCut or FlagDelete:
919
+ if len(indexes)<1000:
920
+ for item in items:
921
+ self.takeTopLevelItem(self.indexOfTopLevelItem(item))
922
+ if not FlagDelete: self.cutLists(indexes)
923
+ else: self.deleteLists(indexes)
924
+ else:
925
+ if not FlagDelete: self.cutLists(indexes)
926
+ else: self.deleteLists(indexes)
927
+ FlagSignal=False
928
+ else:
929
+ self.copyLists(indexes)
930
+
931
+ if FlagCut or FlagDelete:
932
+ self.setCurrentItem(None)
933
+ self.clearSelection()
934
+ self.setFocus()
935
+ if FlagSignal and not self.signalsBlocked(): self.signals.updateLists.emit()
936
+
937
+ def clean_action(self):
938
+ self.setSelectionMode(QTreeWidget.SelectionMode.MultiSelection)
939
+ self.selectAll()
940
+ self.copy_cut_delete_action(FlagDelete=True)
941
+ self.setSelectionMode(QTreeWidget.SelectionMode.SingleSelection)
942
+ return
943
+
944
+ def button_rename_action(self):
945
+ selected_items = self.selectedItems()
946
+ if selected_items:
947
+ item = selected_items[0]
948
+ #column = self.currentColumn()
949
+ #self.editItem(item, column)
950
+ self.editItem(item, 1)
951
+
952
+ def button_info_action(self):
953
+ warningDialog(self,self.TREpar.InfoMessage(),pixmap=TreeIcons.pixmaps[self.TREpar.icon],title='Project information')
954
+
955
+ def button_new_action(self):
956
+ item=self.createProjectItem()
957
+ self.setCurrentItem(item)
958
+ self.modifyWorkspace()
959
+ self.tree_item_selection()
960
+ return
961
+
962
+ def button_open_action(self):
963
+ return
964
+
965
+ def button_save_action(self):
966
+ return False
967
+
968
+ def button_saveas_action(self):
969
+ return False
970
+
971
+ def button_close_action(self):
972
+ FlagQuestion=True
973
+ if not self.TREpar.FlagSaved:
974
+ if questionDialog(self,'The current project is unsaved. Do you want to save it before closing?'):
975
+ FlagQuestion=not self.button_save_action()
976
+ if FlagQuestion:
977
+ flagYes=questionDialog(self,f"Are you sure you want to remove the selected project{'' if self.TREpar.FlagSaved else ' without saving'}?")
978
+ if not flagYes: return
979
+ self.blockSignals(True)
980
+ self.copy_cut_delete_action(FlagDelete=True)
981
+ self.blockSignals(False)
982
+ item=self.currentItem()
983
+ if item: self.TREpar.project=self.indexOfTopLevelItem(item)
984
+ else: self.TREpar.project=None
985
+ self.modifyWorkspace()
986
+ self.tree_item_selection()
987
+ return
988
+
989
+ def button_clean_action(self):
990
+ if self.topLevelItemCount()==1:
991
+ self.button_close_action()
992
+ return
993
+ FlagUnsaved=False
994
+ for t in self.itemList[0]:
995
+ t:TREpar
996
+ if not t.FlagSaved:
997
+ FlagUnsaved=True
998
+ break
999
+ additionalSentence='' if not FlagUnsaved else 'There are some unsaved projects in the current workspace. '
1000
+ flagYes=questionDialog(self,f"{additionalSentence}Are you sure you want to remove the selected projects?")
1001
+ if not flagYes: return
1002
+ self.clean_workspace()
1003
+
1004
+ def clean_workspace(self):
1005
+ self.blockSignals(True)
1006
+ self.clean_action()
1007
+ self.blockSignals(False)
1008
+ item=self.currentItem()
1009
+ if item: self.TREpar.project=self.indexOfTopLevelItem(item)
1010
+ else: self.TREpar.project=None
1011
+ self.modifyWorkspace()
1012
+ self.tree_item_selection()
1013
+ return
1014
+
1015
+ def tree_item_selection(self):
1016
+ if self.TREpar.project is not None:
1017
+ TRE:TREpar=self.itemList[0][self.TREpar.project]
1018
+ if TRE.isEqualTo(self.TREpar,fields=['name','project']):
1019
+ TRE.copyfrom(self.TREpar)
1020
+ item=self.currentItem()
1021
+ FlagEnabled=item!=None
1022
+ if FlagEnabled:
1023
+ self.TREpar.copyfrom(self.itemList[0][self.indexOfTopLevelItem(item)])
1024
+ else:
1025
+ self.TREpar.copyfrom(self.TREpar_None)
1026
+ self.setButtonLayout()
1027
+
1028
+ self.adjustSelection()
1029
+ self.printProjects()
1030
+ return
1031
+
1032
+ def setButtonLayout(self):
1033
+ item=self.currentItem()
1034
+ FlagEnabled=item!=None
1035
+ self.actionBar.button_info.setEnabled(FlagEnabled)
1036
+ self.actionBar.button_rename.setEnabled(FlagEnabled)
1037
+ self.actionBar.button_save.setEnabled(not self.TREpar.FlagSaved and FlagEnabled)
1038
+ self.actionBar.button_saveas.setEnabled(FlagEnabled)
1039
+ self.actionBar.button_close.setEnabled(FlagEnabled)
1040
+ self.actionBar.button_clean.setEnabled(self.topLevelItemCount()>0)
1041
+
1042
+ class DraggableButton(QToolButton):
1043
+ def __init__(self, data: dict = {}, buttonSize: list = processButtonSize, FlagInvisible=True, parent=None):
1044
+ super().__init__(parent)
1045
+ self.buttonData = data
1046
+ self.button_text = data['name']
1047
+ if len(buttonSize) == 0: buttonSize = processButtonSize
1048
+ elif len(buttonSize) < 2: buttonSize.append(buttonSize[0])
1049
+ self.buttonSize = buttonSize
1050
+
1051
+ self.buttonIcon = TreeIcons.icons[data['icon']]
1052
+ self.setIconSize(QSize(self.buttonSize[0], self.buttonSize[0]))
1053
+ self.setIcon(self.buttonIcon)
1054
+ self.setFixedSize(self.buttonSize[1], self.buttonSize[1])
1055
+ if FlagInvisible:
1056
+ self.setCursor(Qt.CursorShape.OpenHandCursor)
1057
+ self.setStyleSheet("QToolButton { border: none; background: none;} QToolButton::menu-indicator { image: none; }")
1058
+ self.setToolTip(data['name']+' (drag to process tree or double click to add)')
1059
+ self.setStatusTip(data['name']+' (drag to process tree or double click to add)')
1060
+
1061
+ self.buttonAction=lambda: None
1062
+
1063
+ def mousePressEvent(self, event):
1064
+ if event.button() == Qt.LeftButton:
1065
+ self.drag_start_position = event.position() # Memorizza la posizione del click del mouse
1066
+ self.setCursor(Qt.CursorShape.ClosedHandCursor)
1067
+ super().mousePressEvent(event)
1068
+
1069
+ def mouseReleaseEvent(self, event):
1070
+ self.setCursor(Qt.CursorShape.OpenHandCursor)
1071
+ super().mouseReleaseEvent(event)
1072
+
1073
+ def mouseMoveEvent(self, event):
1074
+ # Calcola la distanza dal punto di inizio del drag
1075
+ distance = (event.position() - self.drag_start_position).manhattanLength()
1076
+ # Se la distanza è superiore a una soglia, avvia il drag
1077
+ if distance > QApplication.startDragDistance():
1078
+ self.dragIcon()
1079
+ super().mouseMoveEvent(event)
1080
+
1081
+ def mouseDoubleClickEvent(self, event):
1082
+ self.buttonAction()
1083
+ pass # Ignora il doppio click del mouse
1084
+
1085
+ def dragIcon(self):
1086
+ mime_data = QMimeData()
1087
+ mime_data.setText(self.button_text) # Store the button text as Mime data
1088
+ mime_data.setData('application/x-button', json.dumps(self.buttonData['type']).encode())
1089
+ drag = QDrag(self)
1090
+ drag.setMimeData(mime_data)
1091
+ drag.setPixmap(self.buttonIcon.pixmap(QSize(self.buttonSize[1], self.buttonSize[1]))) # Show only the image during dragging
1092
+ sh = int(self.buttonSize[1] * 0.5)
1093
+ drag.setHotSpot(QPoint(sh, sh))
1094
+ self.setIconSize(QSize(0, 0))
1095
+ QApplication.setOverrideCursor(Qt.ClosedHandCursor)
1096
+ drag.exec(Qt.MoveAction)
1097
+ QApplication.restoreOverrideCursor()
1098
+ self.setCursor(Qt.CursorShape.OpenHandCursor)
1099
+ self.setIconSize(QSize(self.buttonSize[0], self.buttonSize[0]))
1100
+
1101
+ class ProcessButtonBar(QWidget):
1102
+ def __init__(self, buttonData=processData, buttonSize=processButtonSize, FlagInvisible=True):
1103
+ super().__init__()
1104
+ self.buttonLayout = QHBoxLayout()
1105
+ self.buttonLayout.setContentsMargins(0, 0, 0, 0)
1106
+ self.buttonLayout.setSpacing(int(buttonSize[0] / 2))
1107
+ self.setLayout(self.buttonLayout)
1108
+ self.buttonData=buttonData
1109
+ self.buttonSize = buttonSize
1110
+ self.FlagInvisible=FlagInvisible
1111
+
1112
+ self.buttons={}
1113
+ for type, data in buttonData.items():
1114
+ if all([f in list(data) for f in ('icon', 'name', 'type')]):
1115
+ if type==ProcessTypes.null: continue
1116
+ button = DraggableButton(data, buttonSize, FlagInvisible)
1117
+ self.buttonLayout.addWidget(button)
1118
+ self.buttons[type]=button
1119
+ self.buttonLayout.addSpacerItem(QSpacerItem(0, self.buttonSize[0], QSizePolicy.Expanding, QSizePolicy.Minimum))
1120
+
1121
+ class StepButtonBar(QWidget):
1122
+ def __init__(self, buttonData=stepData, buttonSize=processButtonSize, FlagInvisible=True):
1123
+ super().__init__()
1124
+ self.buttonLayout = QVBoxLayout()
1125
+ self.buttonLayout.setContentsMargins(10, headerHeight+5, 0, 10)
1126
+ self.buttonLayout.setSpacing(int(buttonSize[0]/2))
1127
+ self.setLayout(self.buttonLayout)
1128
+ self.buttonData=buttonData
1129
+ self.buttonSize = buttonSize
1130
+
1131
+ self.buttons={}
1132
+ self.labels={}
1133
+ for type in buttonData:
1134
+ if type==StepTypes.null: continue
1135
+ data=buttonData[type]
1136
+
1137
+ 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]))
1140
+ button.setFixedSize(self.buttonSize[1], self.buttonSize[1])
1141
+
1142
+ if FlagInvisible:
1143
+ button.setCursor(Qt.CursorShape.PointingHandCursor)
1144
+ button.setStyleSheet("QToolButton { border: none; background: none;} QToolButton::menu-indicator { image: none; }")
1145
+ button.pressed.connect(lambda btn=button: btn.setStyleSheet("QToolButton { border: none; background: #dcdcdc;} QToolButton::menu-indicator { image: none; }"))
1146
+ button.released.connect(lambda btn=button: btn.setStyleSheet("QToolButton { border: none; background: none;} QToolButton::menu-indicator { image: none; }"))
1147
+ setattr(button,'initialStyle',button.styleSheet())
1148
+ button.setToolTip(data['name'])
1149
+ button.setStatusTip(button.toolTip())
1150
+
1151
+ button.setCheckable(True)
1152
+ def buttonIcon(b,d):
1153
+ b.iconOn = TreeIcons.icons[d['icon']]
1154
+ b.iconOff = TreeIcons.icons[d['icon'].replace('.png','_off.png')]
1155
+ b.clicked.connect(lambda:self.setButtonIcon(b))
1156
+ setattr(b,'setButtonIcon',lambda:self.setButtonIcon(b))
1157
+
1158
+ """
1159
+ label=QLabel(self)
1160
+ label.setPixmap( iconOn.pixmap(self.buttonSize[0], self.buttonSize[0]))
1161
+ label.setMargin(int(0.5*(self.buttonSize[1]-self.buttonSize[0])))
1162
+ label.setToolTip(data['name'])
1163
+ label.setStatusTip(data['name'])
1164
+ setattr(b,'label',label)
1165
+ """
1166
+ buttonIcon(button,data)
1167
+
1168
+ self.buttonLayout.addWidget(button)
1169
+ #Dself.buttonLayout.addWidget(button.label)
1170
+ button.setButtonIcon()
1171
+ setattr(button,'buttonData',data)
1172
+
1173
+ self.buttons[type]=button
1174
+
1175
+ label = QLabel(self)
1176
+ label.setPixmap(TreeIcons.pixmaps[data['icon']])
1177
+ label.setFixedSize(self.buttonSize[1], self.buttonSize[1])
1178
+ label.setScaledContents(True)
1179
+ label.setToolTip(data['name']+' (mandatory step)')
1180
+ label.setStatusTip(label.toolTip())
1181
+ self.buttonLayout.addWidget(label)
1182
+ self.labels[type]=label
1183
+
1184
+ self.buttonLayout.addSpacerItem(QSpacerItem(self.buttonSize[0], 0, QSizePolicy.Minimum, QSizePolicy.Expanding))
1185
+
1186
+ def setButtonIcon(self,button:QToolButton):
1187
+ if button.isChecked(): #or not button.isVisible():
1188
+ button.setIcon(button.iconOn)
1189
+ else:
1190
+ button.setIcon(button.iconOff)
1191
+
1192
+ class ProcessTree(PaIRSTree):
1193
+ eventSender=None
1194
+
1195
+ class CustomItemDelegate(QStyledItemDelegate):
1196
+ def __init__(self, parent=None):
1197
+ super().__init__(parent)
1198
+ self.h1 = firstLevelHeight
1199
+ self.h2 = secondLevelHeight
1200
+
1201
+ def sizeHint(self, option, index):
1202
+ if index.parent().isValid(): # Verifica se l'elemento ha un genitore
1203
+ return QSize(option.rect.width(), self.h2) # Altezza per il secondo livello
1204
+ else:
1205
+ return QSize(option.rect.width(), self.h1) # Altezza per il primo livello
1206
+
1207
+ def __init__(self, parent: QWidget=None, listDim=2, listDepth=1, restoreTree=None, processActionBar:ActionButtonBar=None,widgets=[], Explorer=None):
1208
+ super().__init__(parent, listDim, listDepth)
1209
+ if type(restoreTree)==ProcessTree:
1210
+ self.FlagBin=True
1211
+ self.restoreTree=restoreTree
1212
+ self.name='Bin'
1213
+ else:
1214
+ self.FlagBin=False
1215
+ self.restoreTree=None
1216
+ self.name='Process'
1217
+ self.setAcceptDrops(True)
1218
+ #self.setSelectionMode(QTreeWidget.SingleSelection)
1219
+ self.setSelectionBehavior(QTreeWidget.SelectionBehavior.SelectRows)
1220
+ self.delegate=self.CustomItemDelegate()
1221
+ self.setItemDelegate(self.delegate) # Set the default height of all rows to 40 pixels
1222
+ self.setIconSize(QSize(firstLevelHeight-4, firstLevelHeight-4))
1223
+
1224
+ if self.FlagBin:
1225
+ columns=["#","Deleted processes"]
1226
+ self.setStyleSheet("""
1227
+ QHeaderView::section {
1228
+ color: red;
1229
+ }
1230
+ """)
1231
+ else:
1232
+ columns=["#","Processes"]
1233
+ self.setColumnCount(len(columns))
1234
+ self.setHeaderLabels(columns)
1235
+ header=self.header()
1236
+ header.setFixedHeight(headerHeight)
1237
+ self.headerItem().setTextAlignment(0,Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
1238
+ header.setSectionResizeMode(0, QHeaderView.ResizeMode.Fixed)
1239
+ self.setColumnWidth(0,column0Width+processTreeIndentation)
1240
+ self.setIndentation(processTreeIndentation)
1241
+ header.setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch)
1242
+ font=header.font()
1243
+ font.setItalic(True)
1244
+ font.setPixelSize(fontPixelSize+dFontPixelSize_stepTitle)
1245
+ header.setFont(font)
1246
+
1247
+ self.linkedIcon=QIcon(QPixmap(icons_path+"linked.png").scaled(QSize(secondLevelIconSize,secondLevelIconSize),mode=Qt.TransformationMode.SmoothTransformation))
1248
+ self.uneditedIcon=QIcon(QPixmap(icons_path+"unedited.png").scaled(QSize(secondLevelIconSize,secondLevelIconSize),mode=Qt.TransformationMode.SmoothTransformation))
1249
+ self.undoneIcon=QIcon(QPixmap(icons_path+"undo.png").scaled(QSize(secondLevelIconSize,secondLevelIconSize),mode=Qt.TransformationMode.SmoothTransformation))
1250
+
1251
+ self.initialStyleSheet=self.styleSheet()
1252
+ self.FlagExternalDrag=False
1253
+
1254
+ self.itemDoubleClicked.connect(self.startEditing)
1255
+ self.FlagReset=False
1256
+ self.FlagContexMenu=False
1257
+
1258
+ self.actionBar=processActionBar
1259
+ if self.actionBar:
1260
+ self.actionBar.tree=self
1261
+ self.actionBar.setButtonActions()
1262
+ self.setupWidgets(widgets)
1263
+
1264
+ self.TREpar=TREpar()
1265
+ self.TREpar.tree=int(self.FlagBin)
1266
+ self.ITEpar=ITEpar()
1267
+
1268
+ self.editingFinished=lambda: None
1269
+
1270
+ self.Explorer:PaIRS_Explorer=Explorer
1271
+ self.adjustSwitches = lambda: None
1272
+ self.setVisible(True)
1273
+
1274
+ def setupWidgets(self,widgets):
1275
+ self.widgets=widgets
1276
+ self.widgetNames=[]
1277
+ for w in self.widgets:
1278
+ w:gPaIRS_Tab
1279
+ self.widgetNames.append(w.TABname)
1280
+
1281
+ def resetImNumber(self,kin=None,kfin=None):
1282
+ super().resetImNumber(kin,kfin)
1283
+ if not kin: kin=0
1284
+ if not kfin: kfin=self.topLevelItemCount()-1
1285
+ ITEList=self.itemList[0]
1286
+ inds_old={}
1287
+ deps_old={}
1288
+ links_old={}
1289
+ inds_new={}
1290
+ for i in range(self.topLevelItemCount()):
1291
+ ITEs=ITEList[i]
1292
+ inds_old_i =[]
1293
+ deps_old_i =[]
1294
+ links_old_i=[]
1295
+ for k,ITE in enumerate(ITEs):
1296
+ if k==0:
1297
+ inds_new[ITE.ind[2]]=i
1298
+ inds_old_i.append(copy.deepcopy(ITE.ind))
1299
+ deps_old_i.append(copy.deepcopy(ITE.dependencies))
1300
+ links_old_i.append(copy.deepcopy(ITE.link))
1301
+ ITE:ITEpar
1302
+ ITE.ind[0]=self.TREpar.project
1303
+ ITE.ind[1]=int(self.FlagBin)
1304
+ ITE.ind[2]=i
1305
+ pass
1306
+ inds_old[i]=inds_old_i
1307
+ deps_old[i]=deps_old_i
1308
+ links_old[i]=links_old_i
1309
+ if i<kin or i>kfin: continue
1310
+ for processPrev in self.itemList[1:]:
1311
+ try:
1312
+ for stepPrev in processPrev[i]:
1313
+ for par in stepPrev:
1314
+ if par and not par.FlagNone:
1315
+ par:TABpar
1316
+ pri.Coding.blue(f'{ITEs[0].name} {par.surname} {par.ind} ---> [{self.TREpar.project}, {int(self.FlagBin)}, {i}, {par.ind[3]}, {par.ind[4]}]')
1317
+ par.ind[0]=self.TREpar.project
1318
+ par.ind[1]=int(self.FlagBin)
1319
+ par.ind[2]=i
1320
+ except Exception as inst:
1321
+ pri.Error.red(f"{inst}\n{traceback.format_exc()}")
1322
+ pass
1323
+ if hasattr(self.gui,'setLinks'):
1324
+ inds_new_list=list(inds_new)
1325
+ FlagUnlink=True
1326
+ for i in range(self.topLevelItemCount()):
1327
+ ITEs=ITEList[i]
1328
+ inds_old_i=inds_old[i]
1329
+ deps_old_i=deps_old[i]
1330
+ links_old_i=links_old[i]
1331
+ for ITE,ind_old,dep_old,link_old in zip(ITEs,inds_old_i,deps_old_i,links_old_i):
1332
+ for k,ind_slave_old in enumerate(dep_old):
1333
+ if ind_slave_old[2] not in inds_new_list: #stiamo rigenerando l'albero
1334
+ ITE.dependencies[k]=copy.deepcopy(dep_old[k])
1335
+ else:
1336
+ ind_slave_new=copy.deepcopy(ind_slave_old)
1337
+ ind_slave_new[2]=inds_new[ind_slave_new[2]]
1338
+ self.gui.setLinks(ind_slave_old,ind_old,FlagUnlink,ind_slave_new,ITE.ind)
1339
+ if ind_slave_new[1]==0 and ITE.ind[1]==0:
1340
+ self.gui.setLinks(ind_slave_new,ITE.ind)
1341
+ pass
1342
+ if link_old in inds_new_list:
1343
+ link_new=copy.deepcopy(link_old)
1344
+ link_new[2]=inds_new[link_new[2]]
1345
+ self.gui.setLinks(ind_old,link_old,FlagUnlink,ITE.ind,link_new)
1346
+ if link_new[1]==0 and ITE.ind[1]==0:
1347
+ self.gui.setLinks(ITE.ind,link_new)
1348
+ pass
1349
+ else: #stiamo rigenerando l'albero
1350
+ ITE.link=copy.deepcopy(link_old)
1351
+
1352
+ def dragEnterEvent(self, event):
1353
+ if type(self).eventSender is None: type(self).eventSender=self
1354
+ if event.mimeData().hasFormat('application/x-qabstractitemmodeldatalist') and type(self).eventSender!=self:
1355
+ self.FlagExternalDrag=True
1356
+ self.setStyleSheet(self.initialStyleSheet+"QTreeWidget { background-color: rgba(0, 116, 255, 0.2); }")
1357
+ event.accept()
1358
+ elif event.mimeData().hasFormat('application/x-button') and not self.FlagBin:
1359
+ self.FlagExternalDrag=True
1360
+ self.dragged_items='externalItem'
1361
+ self.setStyleSheet(self.initialStyleSheet+"QTreeWidget { background-color: rgba(0, 116, 255, 0.2); }")
1362
+ event.accept()
1363
+ else:
1364
+ super().dragEnterEvent(event)
1365
+
1366
+ def dragMoveEvent(self, event):
1367
+ if event.mimeData().hasFormat('application/x-qabstractitemmodeldatalist') and type(self).eventSender!=self:
1368
+ event.setDropAction(Qt.MoveAction)
1369
+ event.accept()
1370
+ elif event.mimeData().hasFormat('application/x-button') and not self.FlagBin:
1371
+ super().dragMoveEvent(event)
1372
+ self.repaint()
1373
+ event.setDropAction(Qt.MoveAction)
1374
+ event.accept()
1375
+ else:
1376
+ super().dragMoveEvent(event)
1377
+
1378
+ def dragLeaveEvent(self,event):
1379
+ self.restoreStyleAfterDrag()
1380
+ super().dragLeaveEvent(event)
1381
+
1382
+ def restoreStyleAfterDrag(self):
1383
+ if self.FlagExternalDrag:
1384
+ self.FlagExternalDrag=False
1385
+ self.dragged_items=None
1386
+ self.setStyleSheet(self.initialStyleSheet)
1387
+ if self.parent(): self.setPalette(self.parent().palette())
1388
+ self.hovered_item=None
1389
+
1390
+ def dropEvent(self, event):
1391
+ if event.mimeData().hasFormat('application/x-qabstractitemmodeldatalist') and type(self).eventSender!=self:
1392
+ if self.FlagBin:
1393
+ self.restoreTree.copy_cut_delete_action(FlagDelete=True)
1394
+ else:
1395
+ self.restoreTree.copy_cut_delete_action(FlagRestore=True)
1396
+ self.restoreStyleAfterDrag()
1397
+ event.setDropAction(Qt.MoveAction)
1398
+ event.accept()
1399
+ elif event.mimeData().hasFormat('application/x-button') and not self.FlagBin:
1400
+ if not self.hovered_item or self.gui.FlagRun:
1401
+ ind=self.topLevelItemCount()
1402
+ else:
1403
+ ind=self.indexOfTopLevelItem(self.hovered_item)+1
1404
+
1405
+ t = json.loads(event.mimeData().data('application/x-button').data().decode())
1406
+ item=self.createProcessItem(t,ind)
1407
+ self.setCurrentItem(item)
1408
+ item.setSelected(True)
1409
+
1410
+ self.restoreStyleAfterDrag()
1411
+ event.setDropAction(Qt.MoveAction)
1412
+ event.accept()
1413
+ else:
1414
+ super().dropEvent(event)
1415
+ for item in self.selectedItems():
1416
+ k=self.indexOfTopLevelItem(item)
1417
+ ITEs=self.itemList[0][k]
1418
+ self.setProcessItemWidget(item,ITEs[0])
1419
+ self.setStepItemWidgets(item,ITEs[0].ind[2])
1420
+ type(self).eventSender=None
1421
+ return
1422
+
1423
+ def createParPrevs(self,ITE:ITEpar):
1424
+ if not self.widgets: return
1425
+ for w in self.widgets:
1426
+ w:gPaIRS_Tab
1427
+ w.gen_TABpar(ITE.ind,FlagEmptyPrev=True,FlagInsert=2,Process=ITE.Process,Step=ITE.Step)
1428
+ pass
1429
+ return
1430
+
1431
+ def createPars(self,ITE:ITEpar):
1432
+ if not self.widgets: return
1433
+ for t in ITE.tabs+['TabArea']:
1434
+ if t in self.widgetNames:
1435
+ k=self.widgetNames.index(t)
1436
+ w:gPaIRS_Tab=self.widgets[k]
1437
+ w.gen_TABpar(ITE.ind,FlagInsert=3,Process=ITE.Process,Step=ITE.Step)
1438
+ pass
1439
+ for w, wn in zip(self.widgets,self.widgetNames):
1440
+ if wn not in ITE.tabs+['TabArea']:
1441
+ w.gen_TABpar(ITE.ind,FlagNone=True,FlagInsert=3,Process=ITE.Process,Step=ITE.Step)
1442
+ pass
1443
+ return
1444
+
1445
+ def createProcessItem(self,type,ind=None,FlagNewItem=True,name=None):
1446
+ self.blockSignals(True)
1447
+ if ind==None: ind=self.topLevelItemCount()
1448
+ item = QTreeWidgetItem()
1449
+
1450
+ if FlagNewItem:
1451
+ ITE=ITEpar(Process=type,Step=None)
1452
+ ITE.FlagQueue=self.TREpar.FlagQueue
1453
+ ITE.ind=[self.TREpar.project,int(self.FlagBin),ind,0,0]
1454
+
1455
+ processNames=[item[0].name for item in self.itemList[0]]+[item[0].name for item in self.restoreTree.itemList[0]]
1456
+ if name is None:
1457
+ nameInd=1
1458
+ ITE.name=f'{ITE.basename} {nameInd}'
1459
+ while ITE.name in processNames:
1460
+ nameInd+=1
1461
+ ITE.name=f'{ITE.basename} {nameInd}'
1462
+ else:
1463
+ ITE.name=name
1464
+ nameInd=1
1465
+ while ITE.name in processNames:
1466
+ nameInd+=1
1467
+ ITE.name=f'{name} ({nameInd})'
1468
+ self.createParPrevs(ITE)
1469
+ self.itemList[0].insert(ind,[ITE])
1470
+ else:
1471
+ ITE=self.itemList[0][ind][0]
1472
+
1473
+ item.setText(0,str(ind))
1474
+ item.setTextAlignment(0,Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter)
1475
+ item.setIcon(1, TreeIcons.icons[ITE.icon]) # Set the icon of the new item
1476
+ #item.setFlags(item.flags() | Qt.ItemIsEditable)
1477
+
1478
+ ind=max([0,ind])
1479
+ self.insertTopLevelItem(ind,item)
1480
+ self.setProcessItemWidget(item,ITE)
1481
+ self.createStepItems(item,ITE)
1482
+ self.setStepItemWidgets(item,ind)
1483
+ item.setExpanded(True)
1484
+
1485
+ item.setData(0,Qt.ItemDataRole.UserRole,[False])
1486
+ self.resetImNumber(ind)
1487
+
1488
+ self.restoreStyleAfterDrag()
1489
+ self.blockSignals(False)
1490
+ if FlagNewItem: self.signals.updateLists.emit()
1491
+ return item
1492
+
1493
+ def setProcessItemWidget(self,item:QTreeWidgetItem,ITE:ITEpar):
1494
+ title=ITE.name
1495
+ title_label = ResizingLabel(title)
1496
+ title_label.setObjectName('title_process')
1497
+ title_label.setFixedHeight(titleHeight)
1498
+ title_label.setMinimumWidth(titleNameWidth)
1499
+ title_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
1500
+ # Set the font of the title to bold and the subtitle to double font
1501
+ self.titleFont(title_label)
1502
+
1503
+ if not ITE.date:
1504
+ ITE.date = f"Created: {currentTimeString()}"
1505
+ sub_title=ITE.date
1506
+ sub_title_label = ResizingLabel(sub_title) # Create a QLabel for the subtitle
1507
+ sub_title_label.setObjectName('subtitle_process')
1508
+ sub_title_label.setFixedHeight(subtitleHeight)
1509
+ sub_title_label.setMinimumWidth(titleNameWidth)
1510
+ sub_title_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
1511
+ self.subTitleFont(sub_title_label)
1512
+
1513
+ process_title_layout = QVBoxLayout() # Create a vertical layout to place the subtitle below the title
1514
+ process_title_layout.addWidget(title_label) # Add the title of the element
1515
+ process_title_layout.addWidget(sub_title_label) # Add the subtitle
1516
+ process_title_layout.setContentsMargins(10, 2, 0, 2) # Remove margins
1517
+ process_title_layout.setSpacing(2) # Remove margins
1518
+
1519
+ switch = ModernSwitch(parent=self,name='Process',par=ITE)
1520
+ switch.setSwitch(ITE.FlagQueue)
1521
+ switch.toggled.connect(lambda: self.toogleProject(ITE))
1522
+ switch.setVisible(ITE.flagRun<=0 and ITE.Process!=ProcessTypes.cal and not self.FlagBin)
1523
+
1524
+ #spacer=QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Minimum)
1525
+ main_layout=QHBoxLayout()
1526
+ main_layout.addLayout(process_title_layout)
1527
+ #main_layout.addSpacerItem(spacer)
1528
+ main_layout.addWidget(switch)
1529
+ main_layout.setContentsMargins(0,0,10,0)
1530
+ main_layout.setSpacing(6)
1531
+ main_layout.setStretch(0,0)
1532
+ main_layout.setStretch(1,1)
1533
+
1534
+ InfoMessage=ITE.InfoMessage()
1535
+ title_label.setToolTip(InfoMessage)
1536
+ title_label.setStatusTip(InfoMessage)
1537
+ sub_title_label.setToolTip(InfoMessage)
1538
+ sub_title_label.setStatusTip(InfoMessage)
1539
+
1540
+ widget = QWidget(self) # Create a widget container for the layout
1541
+ widget.setLayout(main_layout) # Set the layout for the widget container
1542
+ widget.setMinimumWidth(titleNameWidth+switch.switchWidth+main_layout.spacing())
1543
+ self.setItemWidget(item, 1 ,widget)
1544
+ return widget
1545
+
1546
+ def toogleProject(self,ITE:ITEpar):
1547
+ #self.projectTree.itemList[0][ITE.ind[0]].FlagQueue = ITE.FlagQueue
1548
+ topLevelItem=self.Explorer.projectTree.topLevelItem(ITE.ind[0])
1549
+ itemWidget=self.Explorer.projectTree.itemWidget(topLevelItem,1)
1550
+ switch:ModernSwitch=itemWidget.findChildren(ModernSwitch)[0]
1551
+ FlagToogle=False
1552
+ if ITE.FlagQueue==True and switch._checked==False: FlagToogle=True
1553
+ elif ITE.FlagQueue==False:
1554
+ ITELists=self.Explorer.projectTree.itemList[1][ITE.ind[0]][0]
1555
+ if all([i[0].FlagQueue==False for i in ITELists if i[0].flagRun<=0 and i[0].Process!=ProcessTypes.cal]): FlagToogle=True
1556
+ if FlagToogle:
1557
+ switch.blockSignals(True)
1558
+ switch.toggle()
1559
+ switch.blockSignals(False)
1560
+ if self.Explorer.TREpar.process is not None:
1561
+ self.Explorer.ITEpar.FlagQueue=self.Explorer.ITEfromInd(self.Explorer.ITEpar.ind).FlagQueue
1562
+ self.Explorer.TREpar.FlagQueue=self.Explorer.projectTree.itemList[0][self.Explorer.TREpar.project].FlagQueue
1563
+ self.adjustSwitches()
1564
+ # self.itemSelectionChanged.emit()
1565
+
1566
+ def createStepItems(self,parentItem:QTreeWidgetItem,parentITE:ITEpar):
1567
+ indItem=parentITE.ind[2]
1568
+ #ITEs=self.itemList[0][indItem]
1569
+ indChild=-1
1570
+ for stepType,v in parentITE.children.items():
1571
+ QTreeWidgetItem(parentItem)
1572
+ indChild+=1
1573
+ if indChild>=len(self.itemList[0][indItem])-1:
1574
+ ITE=ITEpar(Process=parentITE.Process,Step=stepType)
1575
+ ITE.ind[0]=parentITE.ind[0]
1576
+ ITE.ind[1]=parentITE.ind[1]
1577
+ ITE.ind[2]=indItem
1578
+ ITE.ind[3]=indChild
1579
+ ITE.active=v
1580
+ self.itemList[0][indItem].append(ITE)
1581
+ self.createPars(ITE)
1582
+ return
1583
+
1584
+ def setStepItemWidgets(self,parentItem:QTreeWidgetItem,indItem):
1585
+ for k,data in enumerate(self.itemList[0][indItem][1:]):
1586
+ data:ITEpar
1587
+ item:QTreeWidgetItem=parentItem.child(k)
1588
+ itemWidget=StepItemWidget(data,parent=self)
1589
+ self.setItemWidget(item,1,itemWidget)
1590
+ if len(data.link)==0:
1591
+ if self.gui and data.flagRun==0 and len(self.gui.ui.tabAreaWidget.TABpar_prev_at(data.ind))<=1:
1592
+ item.setIcon(0,self.uneditedIcon)
1593
+ else:
1594
+ item.setIcon(0,QIcon())
1595
+ else:
1596
+ item.setIcon(0,self.linkedIcon)
1597
+ if not data.active:
1598
+ item.setHidden(True)
1599
+
1600
+ def titleFont(self,label:QLabel,fPixelSize=None):
1601
+ if fPixelSize is None:
1602
+ if hasattr(self.gui,'GPApar'): fPixelSize=self.gui.GPApar.fontPixelSize
1603
+ else: fPixelSize=fontPixelSize
1604
+ font = label.font()
1605
+ font.setFamily(fontName)
1606
+ font.setBold(True) # Bold title
1607
+ fPS=min([fPixelSize+dFontPixelSize_processTitle,fontPixelSize+dFontPixelSize_processTitle+3])
1608
+ font.setPixelSize(fPS) # Double font size for subtitle
1609
+ label.setFont(font)
1610
+
1611
+ def subTitleFont(self,label:QLabel,fPixelSize=None):
1612
+ if fPixelSize is None:
1613
+ if hasattr(self.gui,'GPApar'): fPixelSize=self.gui.GPApar.fontPixelSize
1614
+ else: fPixelSize=fontPixelSize
1615
+ font = label.font()
1616
+ font.setFamily(fontName)
1617
+ font.setItalic(True) # Bold title
1618
+ fPS=min([fPixelSize,fontPixelSize+3])
1619
+ font.setPixelSize(fPS) # Double font size for subtitle
1620
+ label.setFont(font)
1621
+
1622
+ def startEditing(self, item, column):
1623
+ if self.itemWidget(item, column):
1624
+ self.editItem(item, column)
1625
+
1626
+ def editItem(self, item, column):
1627
+ if column == 0 or item.parent() is not None: # Se è la prima colonna, non fare nulla
1628
+ return
1629
+ widget = self.itemWidget(item, column)
1630
+ if isinstance(widget, QLineEdit):
1631
+ return # If already editing, do nothing
1632
+ k=self.indexOfTopLevelItem(item)
1633
+ text = self.itemList[0][k][0].name
1634
+ line_edit = CustomLineEdit(text, self)
1635
+ self.titleFont(line_edit)
1636
+ line_edit.setText(text)
1637
+ self.setItemWidget(item, column, line_edit)
1638
+ line_edit.selectAll()
1639
+ line_edit.setFocus()
1640
+ line_edit.editingFinished.connect(lambda: self.finishEditing(item, column))
1641
+ line_edit.cancelEditing.connect(lambda: self.finishEditing(item, column))
1642
+
1643
+ def finishEditing(self, item, column):
1644
+ line_edit = self.itemWidget(item, column)
1645
+ if not hasattr(line_edit,'text'):
1646
+ return
1647
+ new_text = line_edit.text()
1648
+ k=self.indexOfTopLevelItem(item)
1649
+ data=self.itemList[0][k]
1650
+ data[0].name=new_text
1651
+ self.setProcessItemWidget(item, data[0])
1652
+ line_edit.editingFinished.disconnect()
1653
+ line_edit.cancelEditing.disconnect()
1654
+ self.editingFinished()
1655
+
1656
+ def keyPressEvent(self, event):
1657
+ self.actionBar.treeKeyPressEvent(event)
1658
+
1659
+ if event.key() in (Qt.Key.Key_Return,Qt.Key.Key_Enter) and event.modifiers()==Qt.KeyboardModifier.ShiftModifier:
1660
+ self.currentItem().setExpanded(False)
1661
+ return True # Impedisce al widget di ricevere l'evento
1662
+ elif event.key() in (Qt.Key.Key_Return,Qt.Key.Key_Enter):
1663
+ self.currentItem().setExpanded(True)
1664
+ return True # Impedisce al widget di ricevere l'evento
1665
+ elif event.key()==Qt.Key.Key_Escape:
1666
+ self.setCurrentItem(None)
1667
+ self.clearSelection()
1668
+ self.Explorer.arrangeCurrentProcess(self)
1669
+ return True
1670
+ else:
1671
+ super().keyPressEvent(event)
1672
+
1673
+ def cutItems(self,items,FlagDelete=False):
1674
+ cutted_items=[None]*len(items)
1675
+ items_expanded=[None]*len(items)
1676
+ for k,item in enumerate(items):
1677
+ item:QTreeWidgetItem
1678
+ cutted_items[k]=self.duplicateItem(item)
1679
+ items_expanded[k]=item.isExpanded()
1680
+ if FlagDelete: type(self).deleted_items=cutted_items
1681
+ else: type(self).cutted_items=cutted_items
1682
+ type(self).items_expanded=items_expanded
1683
+ return
1684
+
1685
+ def button_import_action(self):
1686
+ exts=[f'*{e}' for e in [outExt.min,outExt.piv,outExt.cal,outExt.spiv]]
1687
+ dirname=os.path.dirname(self.TREpar.outName) if self.TREpar.outName else ''
1688
+ filename, _ = QFileDialog.getOpenFileName(self.gui,\
1689
+ "Select a PaIRS process file", filter=' '.join(exts),\
1690
+ dir=dirname,\
1691
+ options=optionNativeDialog)
1692
+ if not filename: return
1693
+ FlagError=False
1694
+ errorString=''
1695
+ try:
1696
+ from .Saving_tools import loadList
1697
+ data, errorMessage=loadList(filename)
1698
+ errorString+=errorMessage
1699
+ except Exception as inst:
1700
+ errorString+=str(inst)
1701
+ if errorString:
1702
+ if '_data' in filename:
1703
+ try:
1704
+ errorString=''
1705
+ basename,ext=os.path.splitext(filename)
1706
+ filename2=basename[:-5]+ext
1707
+ data, errorMessage=loadList(filename2)
1708
+ #errorString=errorString+errorMessage if errorMessage else ''
1709
+ if errorMessage:
1710
+ WarningMessage="It was not possible to determine which process the selected data file belongs to.\nPlease, try again by loading the process output file directly."
1711
+ warningDialog(self,WarningMessage)
1712
+ except Exception as inst2:
1713
+ errorString+=str(inst2)
1714
+ FlagError=True
1715
+ else:
1716
+ filename=filename2
1717
+ else: FlagError=True
1718
+ if FlagError or errorString:
1719
+ WarningMessage=f'Error with loading the file: {filename}\n'
1720
+ warningDialog(self,WarningMessage)
1721
+ pri.Error.red(f'{WarningMessage}\n{errorString}\n')
1722
+ return
1723
+ try:
1724
+ ind=self.topLevelItemCount()
1725
+ insert_at_depth(self.itemList,self.listDepth,[ind],data)
1726
+ ITEs:TREpar=self.itemList[0][ind]
1727
+ for ITE in ITEs:
1728
+ ITE:ITEpar
1729
+ ITE.ind[0]=self.TREpar.project
1730
+ ITE.ind[1]=int(self.FlagBin)
1731
+ ITE.ind[2]=ind
1732
+ ITE.link=[]
1733
+ ITE.dependencies=[]
1734
+ self.createProcessItem(ind,FlagNewItem=False)
1735
+ for ITE in ITEs: self.Explorer.setITElayout(ITE)
1736
+ item=self.topLevelItem(ind)
1737
+ self.setCurrentItem(item)
1738
+ item.setSelected(True)
1739
+ except Exception as inst:
1740
+ WarningMessage=f'Error while retrieving the process "{data[0][0][0].name}" from the file: {filename}\n'
1741
+ warningDialog(self,WarningMessage)
1742
+ pri.Error.red(f'{WarningMessage}\n{traceback.format_exc()}\n')
1743
+ return
1744
+
1745
+ def save_current_process(self,filename):
1746
+ Process=[]
1747
+ FlagSaved=False
1748
+ for l in self.itemList:
1749
+ Process.append([l[self.TREpar.process]])
1750
+ try:
1751
+ from .Saving_tools import saveList
1752
+ saveList(Process,filename)
1753
+ except Exception as inst:
1754
+ warningDialog(self,f'Error while saving the configuration file {filename}!\nPlease, retry.')
1755
+ pri.Error.red(f'Error while saving the configuration file {filename}!\n{inst}')
1756
+ return FlagSaved
1757
+
1758
+ def button_saveas_action(self):
1759
+ for attr_name, attr_value in vars(ProcessTypes).items():
1760
+ if attr_value == self.ITEpar.Process:
1761
+ procExt=getattr(outExt,attr_name)
1762
+ break
1763
+ Title="Select location and name of the process file to save"
1764
+ dirname=os.path.dirname(self.TREpar.outName) if self.TREpar.outName else ''
1765
+ filename, _ = QFileDialog.getSaveFileName(self,Title,
1766
+ dir=dirname+self.ITEpar.name.replace(' ','_'),
1767
+ filter=f'*{procExt}',\
1768
+ options=optionNativeDialog)
1769
+ if not filename: return
1770
+ if len(filename)>=len(procExt):
1771
+ if filename[-len(procExt):]==procExt: filename=filename[:-len(procExt)] #per adattarlo al mac
1772
+ filename=myStandardRoot('{}'.format(str(filename)))
1773
+ if not outExt.cfg in filename:
1774
+ filename=filename+procExt
1775
+ self.save_current_process(filename)
1776
+
1777
+ def button_info_action(self):
1778
+ warningDialog(self,self.ITEpar.InfoMessage(),pixmap=TreeIcons.pixmaps[self.ITEpar.icon],title='Process information')
1779
+
1780
+ def button_rename_action(self):
1781
+ selected_items = self.selectedItems()
1782
+ if selected_items:
1783
+ item = selected_items[0]
1784
+ #column = self.currentColumn()
1785
+ #self.editItem(item, column)
1786
+ self.editItem(item, 1)
1787
+
1788
+ def deleteDependencies(self,indexes):
1789
+ if hasattr(self.gui,'setLinks'):
1790
+ for i in indexes:
1791
+ for ITE in self.itemList[0][i]:
1792
+ ITE:ITEpar
1793
+ for dep in ITE.dependencies:
1794
+ self.gui.setLinks(dep,ITE.ind,FlagUnlink=True)
1795
+ if ITE.link: self.gui.setLinks(ITE.ind,ITE.link,FlagUnlink=True)
1796
+
1797
+ def copy_cut_delete_action(self,FlagCut=False,FlagDelete=False,FlagRestore=False):
1798
+ items,indexes=self.selectTopLevel()
1799
+ if FlagRestore: FlagDelete=True
1800
+ if FlagDelete and not FlagRestore:
1801
+ FlagCut=False
1802
+ if self.FlagBin:
1803
+ flagYes=questionDialog(self,f"Are you sure you want to remove the selected process{'es' if len(items)>1 else ''}? This operation is irreversible!")
1804
+ else: flagYes=True
1805
+ if not flagYes: return
1806
+ if FlagDelete and not self.FlagBin: self.deleteDependencies(indexes)
1807
+ self.cutItems(items,FlagDelete)
1808
+
1809
+ FlagSignal=True
1810
+ if FlagCut or FlagDelete:
1811
+ if len(indexes)<1000:
1812
+ for item in items:
1813
+ self.takeTopLevelItem(self.indexOfTopLevelItem(item))
1814
+ if not FlagDelete: self.cutLists(indexes)
1815
+ else: self.deleteLists(indexes)
1816
+ else:
1817
+ if not FlagDelete: self.cutLists(indexes)
1818
+ else: self.deleteLists(indexes)
1819
+ FlagSignal=False
1820
+ else:
1821
+ self.copyLists(indexes)
1822
+
1823
+ if FlagCut or FlagDelete:
1824
+ self.setCurrentItem(None)
1825
+ self.clearSelection()
1826
+ self.setFocus()
1827
+ if FlagSignal and not self.signalsBlocked(): self.signals.updateLists.emit()
1828
+ if FlagRestore or (not self.FlagBin and FlagDelete):
1829
+ self.restore_action()
1830
+
1831
+ def restore_action(self):
1832
+ self.restoreTree:ProcessTree
1833
+ self.restoreTree.paste_above_below_action(FlagAbove=not self.FlagBin,row=0 if not self.FlagBin else self.restoreTree.topLevelItemCount()-1,FlagPasteDeleted=True,FlagSelectAfterPasting=True)
1834
+
1835
+ def paste_above_below_action(self,FlagAbove=True,row=None,FlagPasteDeleted=False,FlagSelectAfterPasting=False):
1836
+ if len(type(self).cutted_items)==0 and not FlagPasteDeleted: return
1837
+ elif len(type(self).deleted_items)==0 and FlagPasteDeleted: return
1838
+ self.blockSignals(True)
1839
+ selectedItems,indexes=self.selectTopLevel()
1840
+ self.blockSignals(False)
1841
+ #self.clearSelection()
1842
+ if FlagPasteDeleted: new_items=type(self).deleted_items
1843
+ else: new_items=type(self).cutted_items
1844
+ if FlagAbove:
1845
+ if row is None:
1846
+ if selectedItems: row=indexes[0]
1847
+ else: row=0
1848
+ firstItemToScroll=new_items[0]
1849
+ lastItemToScroll=new_items[-1]
1850
+ else:
1851
+ if row is None:
1852
+ if selectedItems: row=indexes[-1]+1
1853
+ else: row=self.topLevelItemCount()
1854
+ else: row=row+1
1855
+ firstItemToScroll=new_items[-1]
1856
+ lastItemToScroll=new_items[0]
1857
+ self.insertItems2List(row,new_items,True,FlagSignal=False)
1858
+ for k,item in enumerate(new_items):
1859
+ item:QTreeWidgetItem
1860
+ item.setExpanded(type(self).items_expanded[k])
1861
+
1862
+ indexes=[row+k for k in range(len(new_items))]
1863
+ if not self.FlagCutted and not FlagPasteDeleted:
1864
+ self.cutItems(new_items)
1865
+ self.pasteLists(row,FlagPasteDeleted)
1866
+ name_list=[i[0].name for i in self.itemList[0]]
1867
+ for pos,k in enumerate(indexes):
1868
+ item=self.topLevelItem(k)
1869
+ ITE=self.itemList[0][k][0]
1870
+ tail=set(indexes[pos:]);
1871
+ forbidden={name_list[j] for j in range(len(name_list)) if j not in tail}
1872
+ if ITE.name in forbidden:
1873
+ base=ITE.name
1874
+ n=1
1875
+ while f"{base} ({n})" in forbidden: n+=1
1876
+ ITE.name=f"{base} ({n})"
1877
+ name_list[k]=ITE.name
1878
+ self.setProcessItemWidget(item,ITE)
1879
+ self.setStepItemWidgets(item,ITE.ind[2])
1880
+
1881
+ self.scrollToItem(firstItemToScroll)
1882
+ self.scrollToItem(lastItemToScroll)
1883
+ if FlagPasteDeleted and not FlagSelectAfterPasting:
1884
+ type(self).deleted_items=[]
1885
+ self.clearSelection()
1886
+ else:
1887
+ self.setCurrentItem(new_items[-1])
1888
+ new_items[-1].setSelected(True)
1889
+ if FlagPasteDeleted: self.Explorer.processTree_item_selection(self)
1890
+ self.setFocus()
1891
+ self.signals.updateLists.emit()
1892
+
1893
+ def button_copy_action(self):
1894
+ self.copy_cut_delete_action()
1895
+ self.setProcessActionButtonLayout()
1896
+
1897
+ def button_paste_below_action(self):
1898
+ self.blockSignals(True)
1899
+ self.paste_above_below_action(FlagAbove=False)
1900
+ self.blockSignals(False)
1901
+ self.itemSelectionChanged.emit()
1902
+
1903
+ def button_paste_above_action(self):
1904
+ self.blockSignals(True)
1905
+ self.paste_above_below_action()
1906
+ self.blockSignals(False)
1907
+ self.itemSelectionChanged.emit()
1908
+
1909
+ def button_process_loop_action(self):
1910
+ pri.Info.magenta('Process loop over folders')
1911
+ paths=choose_directories()
1912
+ if paths:
1913
+ ITEs=self.Explorer.ITEsfromTRE(self.TREpar)
1914
+ ind=copy.deepcopy(ITEs[0].ind)
1915
+ processType=ITEs[0].Process
1916
+ pixmap_list=[]
1917
+ name_list=[]
1918
+ flag_list=[]
1919
+ for i in range(1,len(ITEs)):
1920
+ ITE:ITEpar=ITEs[i]
1921
+ pixmap_list.append(icons_path+ITE.icon)
1922
+ name_list.append(ITE.name)
1923
+ flag_list.append(ITE.Step!=StepTypes.cal)
1924
+ func=lambda i, opts, cleanup_flag, rescan_flag: self.process_loop(paths,processType,ind,ITEs[0].name,i,opts,cleanup_flag,rescan_flag)
1925
+ dialog = FolderLoopDialog(pixmap_list, name_list, flag_list, parent=self, paths=paths, func=func, process_name=ITEs[0].name)
1926
+ dialog.exec()
1927
+ return
1928
+
1929
+ def process_loop(self,paths,processType,ind,name0,i,opts,cleanup_flag,rescan_flag):
1930
+ nProcess=self.topLevelItemCount()
1931
+ path=paths[i]
1932
+ name=name0+f" (.../{os.path.basename(path)}/)"
1933
+ item=self.createProcessItem(processType,nProcess,name=name)
1934
+ ind_master=copy.deepcopy(ind)
1935
+ ind_master[-1]=-1
1936
+ ind_slave=copy.deepcopy(ind)
1937
+ ind_slave[2]=nProcess
1938
+ ind_slave[-1]=0
1939
+
1940
+ FlagWarning=False
1941
+ for j in range(len(opts)):
1942
+ ind_master[3]=j
1943
+ ind_slave[3]=j
1944
+ if opts[j] in (0,2):
1945
+ ind_new=self.gui.copy_pars(ind_slave,ind_master,FlagNew=True)
1946
+ item_child=item.child(j)
1947
+ ITE:ITEpar=self.Explorer.ITEfromInd(ind_new)
1948
+ item_child.setHidden(not ITE.active)
1949
+ if opts[j]==2:
1950
+ INP: INPpar=self.gui.w_Input.TABpar_at(ind_new)
1951
+ INP.path=myStandardPath(path)
1952
+ if rescan_flag:
1953
+ flagWarning=self.rescanInputPath(ind_master,ind_new,cleanup_flag)
1954
+ else:
1955
+ flagWarning=self.gui.w_Input.scanImList(ind_new)
1956
+ if cleanup_flag: self.gui.w_Input.purgeImList(ind_new)
1957
+ self.gui.w_Input.checkINPpar(ind_new)
1958
+ self.gui.w_Input.setINPwarn(ind_new)
1959
+
1960
+ TABname=self.gui.w_Input.TABname
1961
+ self.gui.bridge(TABname,ind_new)
1962
+
1963
+ FlagSettingPar=TABpar.FlagSettingPar
1964
+ TABpar.FlagSettingPar=True
1965
+ for w in self.gui.tabWidgets:
1966
+ w:gPaIRS_Tab
1967
+ if w!=self.gui.w_Input:
1968
+ currpar=w.TABpar.duplicate()
1969
+ TABind_new=w.TABpar_at(ind_new)
1970
+ if TABind_new and not TABind_new.FlagNone:
1971
+ w.TABpar.copyfrom(TABind_new)
1972
+ w.adjustTABpar()
1973
+ w.adjustTABparInd()
1974
+ w.TABpar.copyfrom(currpar)
1975
+ self.gui.bridge(w.TABname,ind_new)
1976
+ TABpar.FlagSettingPar=FlagSettingPar
1977
+ self.Explorer.setITElayout(ITE)
1978
+ FlagWarning=FlagWarning or flagWarning or ITE.OptionDone==0
1979
+
1980
+ #item_child=item.child(j)
1981
+ #item_child.setSelected(True)
1982
+ #self.setCurrentItem(item_child)
1983
+ else:
1984
+ self.gui.link_pars(ind_slave,ind_master,FlagSet=False)
1985
+ return FlagWarning
1986
+
1987
+ def rescanInputPath(self, ind_master, ind_new, FlagNoWarning):
1988
+ """Rescan input on slave using master patterns, then rebuild imList/imEx."""
1989
+ INP: INPpar = self.gui.w_Input.TABpar_at(ind_new) # slave
1990
+ INPm: INPpar = self.gui.w_Input.TABpar_at(ind_master) # master
1991
+
1992
+ # Build A (for frame_1) and B (for frame_2) from master's pattern at master's frames
1993
+ patm = getattr(INPm.imSet, "pattern", [])
1994
+ ncam = INP.inp_ncam
1995
+ def _pat_list(frames,ind0=0):
1996
+ out=[];
1997
+ for k in range(ncam):
1998
+ f = frames[k]-ind0 if k < len(frames) else -1
1999
+ out.append(patm[f] if isinstance(f,int) and 0 <= f < len(patm) else None)
2000
+ return out
2001
+ A = _pat_list(INPm.frame_1)
2002
+ B = _pat_list(INPm.frame_2,1)
2003
+
2004
+ # Rescan slave input path with patterns=A,B (maps patterns -> frame indices on slave)
2005
+ self.gui.w_Input.scanInputPath(ind_new, patterns=[A, B], FlagNoWarning=FlagNoWarning)
2006
+
2007
+ # Rebuild imList/imEx from frames computed on slave
2008
+ INP.imList, INP.imEx = INP.imSet.genListsFromFrame(
2009
+ INP.frame_1, INP.frame_2, INP.ind_in, INP.npairs, INP.step, INP.FlagTR_Import
2010
+ )
2011
+ # Compare slave vs master lists
2012
+ def _lists_differ(a, b):
2013
+ if len(a) != len(b): return True
2014
+ for x, y in zip(a, b):
2015
+ if x != y: return True
2016
+ return False
2017
+
2018
+ FlagWarning = (
2019
+ _lists_differ(INP.imList, INPm.imList) or
2020
+ _lists_differ(INP.imEx, INPm.imEx)
2021
+ )
2022
+
2023
+ return FlagWarning
2024
+
2025
+
2026
+ def button_delete_action(self):
2027
+ self.blockSignals(True)
2028
+ self.copy_cut_delete_action(FlagDelete=True)
2029
+ self.blockSignals(False)
2030
+ self.itemSelectionChanged.emit()
2031
+
2032
+ def button_restore_action(self):
2033
+ self.copy_cut_delete_action(FlagRestore=True)
2034
+
2035
+ def button_clean_action(self):
2036
+ self.blockSignals(True)
2037
+ self.selectAll()
2038
+ self.copy_cut_delete_action(FlagDelete=True)
2039
+ self.repaint()
2040
+ self.blockSignals(False)
2041
+ #self.currentItemChanged.emit(None,None)
2042
+ self.itemSelectionChanged.emit()
2043
+
2044
+ def setProcessActionButtonLayout(self):
2045
+ item=self.currentItem()
2046
+ FlagEnabled=item is not None and item.parent() is None
2047
+ FlagPaste=len(ProcessTree.cutted_items)>0
2048
+ self.actionBar.setVisible(True) #self.actionBar.setVisible(FlagEnabled)
2049
+ self.actionBar.button_saveas.setEnabled(FlagEnabled)
2050
+ self.actionBar.button_info.setEnabled(FlagEnabled)
2051
+ self.actionBar.button_rename.setEnabled(FlagEnabled)
2052
+ self.actionBar.button_copy.setEnabled(FlagEnabled)
2053
+ self.actionBar.button_paste_below.setEnabled(FlagPaste and not self.gui.FlagRun)
2054
+ self.actionBar.button_paste_above.setEnabled(FlagPaste and not self.gui.FlagRun)
2055
+ self.actionBar.button_process_loop.setEnabled(FlagEnabled and not self.FlagBin)
2056
+ self.actionBar.button_restore.setVisible(self.FlagBin)
2057
+ self.actionBar.sep_restore.setVisible(self.FlagBin)
2058
+ self.actionBar.button_restore.setEnabled(FlagEnabled)
2059
+ self.actionBar.button_delete.setEnabled(FlagEnabled)
2060
+ self.actionBar.button_clean.setEnabled(self.topLevelItemCount()>0)
2061
+
2062
+ class StepItemWidget(QWidget):
2063
+ pixmaps={'error' : 'issue.png',
2064
+ 'done' : 'completed.png',
2065
+ 'running' : 'running.png',
2066
+ 'warning' : 'warning_circle.png',
2067
+ 'running_warn' : 'running_warn.png',
2068
+ 'paused' : 'paused.png',
2069
+ 'queue' : 'queue.png',
2070
+ 'uninitialized' : 'uninitialized.png'
2071
+ }
2072
+
2073
+ colors={'error' : 'rgb(254, 61, 61)', #'#C9302C',
2074
+ 'done' : 'rgb( 46,204,113)', #'#4CAE4C',
2075
+ 'running' : 'rgb( 48,107,255)', #'#3A70C7',
2076
+ 'warning' : 'rgb(255,212, 42)', #'#EC971F',
2077
+ 'running_warn' : 'rgb( 48,107,255)', #'#3A70C7',
2078
+ 'paused' : 'rgb(255,127, 42)', #'#3A70C7',
2079
+ 'queue' : 'rgb(181,170,255)', #'#B5AAFF',
2080
+ 'uninitialized' : 'rgb( 0,0,0)',
2081
+ }
2082
+
2083
+ label_size=secondLevelHeight-4
2084
+ bar_width=label_size*3+4
2085
+
2086
+ def __init__(self, ITE:ITEpar, parent=None):
2087
+ super().__init__(parent)
2088
+ if parent is None:
2089
+ self.gui=self.window()
2090
+ else:
2091
+ from .gPaIRS import gPaIRS
2092
+ if hasattr(parent,'gui'):
2093
+ self.gui:gPaIRS=parent.gui
2094
+ else:
2095
+ self.gui:gPaIRS=parent.window()
2096
+
2097
+ # Create the layout
2098
+ layout = QHBoxLayout(self)
2099
+
2100
+ # Add an icon
2101
+ self.stepIcon = QLabel()
2102
+ self.stepIcon.setFixedHeight(secondLevelHeight-2)
2103
+ self.stepIcon.setFixedWidth(secondLevelHeight-2)
2104
+ self.stepIcon.setScaledContents(True)
2105
+ self.stepIcon.setPixmap(TreeIcons.pixmaps[ITE.icon])
2106
+ layout.addWidget(self.stepIcon)
2107
+
2108
+ # Add a spacer
2109
+ spacer = QSpacerItem(5, secondLevelHeight, QSizePolicy.Minimum, QSizePolicy.Minimum)
2110
+ layout.addItem(spacer)
2111
+
2112
+ # Add a label with text
2113
+ self.stepName = QLabel()
2114
+ self.stepName.setObjectName('title_step')
2115
+ self.stepName.setFixedWidth(stepNameWidth)
2116
+ self.stepName.setFixedHeight(secondLevelHeight)
2117
+ self.stepTitleFont(self.stepName)
2118
+ layout.addWidget(self.stepName)
2119
+
2120
+ self.stepName.setText(ITE.name)
2121
+ self.stepName.setToolTip(ITE.name)
2122
+ self.stepName.setStatusTip(ITE.name)
2123
+
2124
+ # Add another icon
2125
+ self.label = ClickableLabel()
2126
+ self.label.setFixedWidth(self.label_size)
2127
+ self.label.setFixedHeight(self.label_size)
2128
+ self.label.setScaledContents(True)
2129
+ self.label.setToolTip(ITE.warningMessage)
2130
+ self.label.setStatusTip(ITE.warningMessage)
2131
+ layout.addWidget(self.label)
2132
+
2133
+ # Add a progress bar
2134
+ self.progressBar = QProgressBar()
2135
+ self.progressBar.setFixedWidth(self.bar_width)
2136
+ self.progressBar.setFixedHeight(self.label_size)
2137
+ layout.addWidget(self.progressBar)
2138
+
2139
+ # Add a spacer
2140
+ spacer = QSpacerItem(40, secondLevelHeight, QSizePolicy.Expanding, QSizePolicy.Minimum)
2141
+ layout.addItem(spacer)
2142
+
2143
+ # Set layout margin and spacing
2144
+ layout.setContentsMargins(secondLevelIndentation, 0, 0, 0)
2145
+ layout.setSpacing(secondLevelSpacing)
2146
+
2147
+ minimumWidth=self.stepIcon.width()+self.stepName.width()+self.label.width()+self.progressBar.width()+secondLevelSpacing*4+secondLevelIndentation
2148
+ self.setMinimumWidth(minimumWidth)
2149
+ self.setFromITE(ITE)
2150
+
2151
+ def stepTitleFont(self,label:QLabel,fPixelSize=None):
2152
+ if fPixelSize is None:
2153
+ if hasattr(self.gui,'GPApar'): fPixelSize=self.gui.GPApar.fontPixelSize
2154
+ else: fPixelSize=fontPixelSize
2155
+ font = label.font()
2156
+ font.setFamily(fontName)
2157
+ fPS=min([fPixelSize + dFontPixelSize_stepTitle, fontPixelSize + dFontPixelSize_stepTitle +3])
2158
+ font.setPixelSize(fPS) # Double font size for subtitle
2159
+ label.setFont(font)
2160
+
2161
+ def colorProgressBar(self,color='green'):
2162
+ style_sheet = f"""
2163
+ QProgressBar {{
2164
+ background-color: transparent;
2165
+ border: 1px solid gray;
2166
+ border-radius: 5px;
2167
+ text-align: center;
2168
+ }}
2169
+
2170
+ QProgressBar::chunk {{
2171
+ background-color: {color};
2172
+ width: 1px; /* Larghezza del chunk */
2173
+ margin: 0px; /* Spazio tra i chunk */
2174
+ }}
2175
+ """
2176
+ self.progressBar.setStyleSheet(style_sheet)
2177
+
2178
+ def setFromITE(self,ITE:ITEpar):
2179
+ pixmap_path=StepItemWidget.pixmaps[ITE.label]
2180
+ FlagGif=False
2181
+ if ITE.flagRun==-2 and ITE.label=='running' and hasattr(self.gui,'runningMovie'):
2182
+ if hasattr(self.gui,'procdata') and self.gui.procdata is ITE.procdata:
2183
+ FlagGif=True
2184
+ if FlagGif:
2185
+ self.label.moviePixmap=TreeIcons.pixmaps[pixmap_path]
2186
+ self.label.setMovie(self.gui.runningMovie)
2187
+ else:
2188
+ self.label.moviePixmap=None
2189
+ self.label.setPixmap(TreeIcons.pixmaps[pixmap_path])
2190
+ self.label.setToolTip(ITE.warningMessage)
2191
+ self.label.setStatusTip(ITE.warningMessage)
2192
+
2193
+ if ITE.Step==StepTypes.cal:
2194
+ self.progressBar.setVisible(ITE.ncam>0)
2195
+ self.progressBar.setMaximum(ITE.ncam)
2196
+ self.progressBar.setValue(ITE.progress)
2197
+ self.progressBar.setFormat(f'{ITE.progress}/{ITE.ncam}')
2198
+ elif ITE.Step==StepTypes.disp:
2199
+ self.progressBar.setVisible(ITE.procdata.Nit>0)
2200
+ self.progressBar.setMaximum(ITE.procdata.Nit)
2201
+ self.progressBar.setValue(ITE.progress)
2202
+ #self.progressBar.setValue(ITE.procdata.numFinalized)
2203
+ #self.progressBar.setFormat(f'{ITE.progress}/{ITE.procdata.Nit}')
2204
+ else:
2205
+ self.progressBar.setVisible(ITE.procdata.nimg>0)
2206
+ self.progressBar.setMaximum(ITE.procdata.nimg)
2207
+ self.progressBar.setValue(ITE.progress)
2208
+ #self.progressBar.setValue(ITE.procdata.numFinalized) # Set the initial value of the progress bar
2209
+
2210
+
2211
+ bar_color=StepItemWidget.colors[ITE.label]
2212
+ self.colorProgressBar(bar_color)
2213
+
2214
+ class PaIRS_Explorer(gPaIRS_Tab):
2215
+ class ProcessTreeWidget_signals(QObject):
2216
+ pass
2217
+
2218
+ def __init__(self,parent=None,widgets=[]):
2219
+ super().__init__(parent,UiClass=None,ParClass=ITEpar)
2220
+ if __name__ == "__main__":
2221
+ iconW = QIcon()
2222
+ iconW.addFile(u""+ icons_path +"checklist.png", QSize(), QIcon.Normal, QIcon.Off)
2223
+ self.setWindowTitle('Process tree widget')
2224
+ self.setWindowIcon(iconW)
2225
+ self.signals=self.ProcessTreeWidget_signals()
2226
+
2227
+ #------------------------------------- Layout
2228
+ self.treeIcons=TreeIcons()
2229
+
2230
+ self.main_layout = QVBoxLayout()
2231
+ self.setLayout(self.main_layout)
2232
+ self.main_layout.setContentsMargins(5, 5, 5, 5)
2233
+
2234
+ self.projectTree_layout= QVBoxLayout()
2235
+ self.projectTree_layout.setContentsMargins(0, 0, 0, 0)
2236
+ self.projectTree_layout.setSpacing(1)
2237
+
2238
+ self.projectActionBar = ActionButtonBar(buttonData=projectActionButtons,globalButtons=projectGlobalActionButtons,buttonSize=projectActionButtonSize)
2239
+ self.projectTree_layout.addWidget(self.projectActionBar)
2240
+ self.projectTree = ProjectTree(self,projectActionBar=self.projectActionBar,widgets=widgets,Explorer=self)
2241
+
2242
+ self.scroll_area_projectTree = QScrollArea(self)
2243
+ self.scroll_area_projectTree.setObjectName('scroll_area_projectTree')
2244
+ self.scroll_area_projectTree.setWidgetResizable(True)
2245
+ self.scroll_area_projectTree.setWidget(self.projectTree)
2246
+ self.scroll_area_projectTree.setMinimumWidth(0)
2247
+ self.scroll_area_projectTree.setStyleSheet(scrollAreaStyle())
2248
+ self.projectTree_layout.addWidget(self.scroll_area_projectTree)
2249
+
2250
+ self.processTree_layout= QVBoxLayout()
2251
+ spa=int(processButtonSize[0]/4)
2252
+ self.processTree_layout.setContentsMargins(0, spa, 0, 0)
2253
+ self.processTree_layout.setSpacing(1)
2254
+ self.processButtonBar = ProcessButtonBar(processData,buttonSize=processButtonSize)
2255
+ self.processTree_layout.addWidget(self.processButtonBar)
2256
+ self.processTree_layout.addItem(QSpacerItem(0,spa,QSizePolicy.Minimum, QSizePolicy.Minimum))
2257
+ self.processActionBar = ActionButtonBar(buttonData=processActionButtons,globalButtons=processGlobalActionButtons,buttonSize=processActionButtonSize)
2258
+ self.processTree_layout.addWidget(self.processActionBar)
2259
+ self.binActionBar = ActionButtonBar(buttonData=processActionButtons,globalButtons=processGlobalActionButtons,buttonSize=processActionButtonSize)
2260
+ self.processTree_layout.addWidget(self.binActionBar)
2261
+
2262
+ self.stepButton_layout=QHBoxLayout()
2263
+ self.stepButton_layout.setContentsMargins(0, 0, 0, 0)
2264
+ self.stepButton_layout.setSpacing(int(stepButtonSize[0]/4))
2265
+
2266
+ self.stepButtonBar=StepButtonBar(stepData,buttonSize=stepButtonSize)
2267
+ self.stepButton_layout.addWidget(self.stepButtonBar)
2268
+
2269
+ self.processActionBar.additionalButtonBars={'globals': [self.processButtonBar], 'items': [self.stepButtonBar]}
2270
+ self.binActionBar.additionalButtonBars={'items': [self.stepButtonBar]}
2271
+
2272
+ self.processTree = ProcessTree(self,processActionBar=self.processActionBar,widgets=widgets,Explorer=self)
2273
+ self.binTree = ProcessTree(self,restoreTree=self.processTree,processActionBar=self.binActionBar,widgets=widgets,Explorer=self)
2274
+ self.processTree.restoreTree=self.binTree
2275
+ itemWidths=[
2276
+ titleNameWidth+ModernSwitch.switchWidth+6, #first level
2277
+ stepNameWidth+StepItemWidget.label_size+StepItemWidget.bar_width+secondLevelSpacing*3 #second level
2278
+ ]
2279
+ self.projectTree.setMinimumWidth(max(itemWidths)+self.projectTree.columnWidth(0)-30)
2280
+ self.processTree.setMinimumWidth(max(itemWidths)+self.processTree.columnWidth(0)-30)
2281
+ self.binTree.setMinimumWidth(max(itemWidths)+self.binTree.columnWidth(0)+40)
2282
+
2283
+ self.Explorer_tree_splitter=QSplitter(self)
2284
+ self.Explorer_tree_splitter.setObjectName('Explorer_tree_splitter')
2285
+ self.Explorer_tree_splitter.setContentsMargins(0, 0, 0, 0)
2286
+ self.Explorer_tree_splitter.setOrientation(Qt.Horizontal)
2287
+ self.scroll_area_processTree = QScrollArea(self)
2288
+ self.scroll_area_processTree.setObjectName('scroll_area_processTree')
2289
+ self.scroll_area_processTree.setWidgetResizable(True)
2290
+ self.scroll_area_processTree.setWidget(self.processTree)
2291
+ self.scroll_area_processTree.setMinimumWidth(0)
2292
+ self.scroll_area_processTree.setStyleSheet(scrollAreaStyle())
2293
+ self.scroll_area_binTree = QScrollArea(self)
2294
+ self.scroll_area_binTree.setObjectName('scroll_area_binTree')
2295
+ self.scroll_area_binTree.setWidgetResizable(True)
2296
+ self.scroll_area_binTree.setWidget(self.binTree)
2297
+ self.scroll_area_binTree.setMinimumWidth(0)
2298
+ self.scroll_area_binTree.setStyleSheet(scrollAreaStyle())
2299
+ self.Explorer_tree_splitter.addWidget(self.scroll_area_processTree)
2300
+ self.Explorer_tree_splitter.addWidget(self.scroll_area_binTree)
2301
+ self.stepButton_layout.addWidget(self.Explorer_tree_splitter)
2302
+ self.processTree_layout.addLayout(self.stepButton_layout)
2303
+
2304
+ stretches=[0,0,0,0,1] #process bar, spacer, process action bar, bin action bar, trees
2305
+ for k,s in enumerate(stretches):
2306
+ self.processTree_layout.setStretch(k,s)
2307
+
2308
+ self.Explorer_main_splitter=QSplitter(self)
2309
+ self.Explorer_main_splitter.setObjectName('Explorer_main_splitter')
2310
+ self.Explorer_main_splitter.setContentsMargins(0, 0, 0, 0)
2311
+ self.Explorer_main_splitter.setOrientation(Qt.Vertical)
2312
+
2313
+ self.projectWidget=QWidget(self)
2314
+ self.projectWidget.setLayout(self.projectTree_layout)
2315
+ self.projectWidget.setMinimumHeight(150)
2316
+
2317
+ self.processWidget=QWidget(self)
2318
+ self.processWidget.setLayout(self.processTree_layout)
2319
+ self.processWidget.setMinimumHeight(300)
2320
+
2321
+ #self.scroll_area_sup = QScrollArea()
2322
+ #self.scroll_area_sup.setObjectName('scroll_area_sup')
2323
+ #self.scroll_area_sup.setWidgetResizable(True)
2324
+ #self.scroll_area_sup.setWidget(self.projectWidget)
2325
+ #self.scroll_area_sup.setMinimumHeight(150)
2326
+ #self.scroll_area_sup.setStyleSheet(scrollAreaStyle())
2327
+ #self.projectWidget.setMinimumHeight(250)
2328
+
2329
+ #self.scroll_area_inf = QScrollArea()
2330
+ #self.scroll_area_inf.setObjectName('scroll_area_inf')
2331
+ #self.scroll_area_inf.setWidgetResizable(True)
2332
+ #self.scroll_area_inf.setWidget(self.processWidget)
2333
+ #self.scroll_area_inf.setMinimumHeight(300)
2334
+ #self.scroll_area_inf.setStyleSheet(scrollAreaStyle())
2335
+ #self.processWidget.setMinimumHeight(400)
2336
+
2337
+ #self.main_splitter.addWidget(self.scroll_area_sup)
2338
+ #self.main_splitter.addWidget(self.scroll_area_inf)
2339
+
2340
+ self.Explorer_main_splitter.addWidget(self.projectWidget)
2341
+ self.Explorer_main_splitter.addWidget(self.processWidget)
2342
+
2343
+ self.Explorer_main_splitter.setHandleWidth(15)
2344
+ self.Explorer_main_splitter.setCollapsible(0, False)
2345
+ self.Explorer_main_splitter.setCollapsible(1, False)
2346
+ self.main_layout.addWidget(self.Explorer_main_splitter)
2347
+
2348
+ # Creazione del pulsante checkable
2349
+ self.binButton = QToolButton(self.processButtonBar)
2350
+ self.binButton.setIconSize(QSize(self.processButtonBar.buttonSize[0], self.processButtonBar.buttonSize[0]))
2351
+ self.binButton.setFixedSize(self.processButtonBar.buttonSize[1], self.processButtonBar.buttonSize[1])
2352
+ if self.processButtonBar.FlagInvisible:
2353
+ self.binButton.setCursor(Qt.CursorShape.PointingHandCursor)
2354
+ self.binButton.setStyleSheet("QToolButton { border: none; background: none;} QToolButton::menu-indicator { image: none; }")
2355
+ self.binButton.pressed.connect(lambda btn=self.binButton: btn.setStyleSheet("QToolButton { border: none; background: #dcdcdc;} QToolButton::menu-indicator { image: none; }"))
2356
+ self.binButton.released.connect(lambda btn=self.binButton: btn.setStyleSheet("QToolButton { border: none; background: none;} QToolButton::menu-indicator { image: none; }"))
2357
+ self.binButton.setToolTip('Deleted processes')
2358
+ self.binButton.setStatusTip('Deleted processes')
2359
+ self.processButtonBar.buttonLayout.addWidget(self.binButton)
2360
+ self.binIconOff=QIcon(''+ icons_path +'bin_off.png')
2361
+ self.binIconOn=QIcon(''+ icons_path +'bin_on.png')
2362
+ self.binButton.setCheckable(True)
2363
+ self.binButton.setChecked(False) # Mostra l'albero 2 di default
2364
+
2365
+ #------------------------------------- Declaration of parameters
2366
+ self.ITEpar:ITEpar=self.TABpar
2367
+ self.ITEpar.FlagNone=False
2368
+ self.processTree.ITEpar=self.ITEpar
2369
+ self.TREpar=self.projectTree.TREpar
2370
+ self.processTree.TREpar=self.projectTree.TREpar
2371
+ self.binTree.TREpar=self.projectTree.TREpar
2372
+ self.widgets=widgets
2373
+
2374
+ #------------------------------------- Callbacks
2375
+ self.currentTree=None
2376
+ self.binButton.clicked.connect(lambda: self.binButton_action(FlagTreeSelection=True))
2377
+ binShortCut=QShortcut(QKeySequence('Ctrl+B'),self)
2378
+ binShortCut.activated.connect(lambda:self.binButton.click())
2379
+ self.Explorer_tree_splitter.splitterMoved.connect(lambda: self.tree_splitter_action(FlagTreeSelection=True))
2380
+
2381
+ self.projectTree.installEventFilter(self)
2382
+ self.projectTree.actionBar.installEventFilter(self)
2383
+ self.processTree.installEventFilter(self)
2384
+ self.binTree.installEventFilter(self)
2385
+
2386
+ #self.processTree.currentItemChanged.connect(lambda: self.arrangeCurrentProcess(self.processTree))
2387
+ #self.binTree.currentItemChanged.connect(lambda: self.arrangeCurrentProcess(self.binTree))
2388
+ self.processTree.itemSelectionChanged.connect(lambda: self.processTree_item_selection(self.processTree))
2389
+ self.binTree.itemSelectionChanged.connect(lambda: self.processTree_item_selection(self.binTree))
2390
+ self.adjustProcessSelection=lambda: None
2391
+ self.binButton_action()
2392
+ #self.processTree_item_selection(self.processTree)
2393
+
2394
+ self.projectTree.adjustSelection=self.adjustProjectSelection
2395
+ self.adjustProjectSelection()
2396
+
2397
+ self.projectPageActions={}
2398
+ for k in projectPageButtons.keys():
2399
+ b:QPushButton=getattr(self.projectActionBar,'button_'+k)
2400
+ self.projectPageActions[k]=lambda butt=b: butt.click()
2401
+
2402
+ self.processPageActions={}
2403
+ def processButtonAction(tree:ProcessTree,butt:DraggableButton):
2404
+ tree.createProcessItem(butt.buttonData['type'])
2405
+ item=tree.topLevelItem(tree.topLevelItemCount()-1)
2406
+ tree.setCurrentItem(item)
2407
+ self.processTree_item_selection(tree)
2408
+ for t,b in self.processButtonBar.buttons.items():
2409
+ b:DraggableButton
2410
+ b.buttonAction=self.processPageActions[t]=lambda tree=self.processTree, butt=b: processButtonAction(tree,butt)
2411
+
2412
+ self.stepPage:StartingPage=None
2413
+ self.stepPageActions={}
2414
+ def stepButtonAction(butt:QToolButton, type):
2415
+ self.stepButton_action(butt, type)
2416
+ def stepPageAction(type):
2417
+ tree:ProcessTree=self.processTree if self.TREpar.tree==0 else self.binTree
2418
+ ITEs=self.ITEsfromTRE(self.TREpar)
2419
+ c=list(ITEs[0].children).index(type)
2420
+ ITE_chiild=ITEs[c+1]
2421
+ b:QPushButton=self.stepButtonBar.buttons[type]
2422
+ if b.isVisible() and b.isEnabled() and not ITE_chiild.active:
2423
+ b.click()
2424
+ elif ITE_chiild.active:
2425
+ item=tree.topLevelItem(self.TREpar.process)
2426
+ child=item.child(c)
2427
+ tree.setCurrentItem(child)
2428
+ self.processTree_item_selection(tree)
2429
+ else:
2430
+ showTip(self,'Current step is disabled! Please, reset the subsequemt step in the process to access it.')
2431
+ for t,b in self.stepButtonBar.buttons.items():
2432
+ b:QToolButton
2433
+ b.clicked.connect(lambda flag, butt=b, type=t: stepButtonAction(butt, type))
2434
+ self.stepPageActions[t]=lambda type=t: stepPageAction(type)
2435
+
2436
+ self.processTree.editingFinished=lambda: self.arrangeCurrentProcess(self.processTree)
2437
+ self.binTree.editingFinished=lambda: self.arrangeCurrentProcess(self.binTree)
2438
+ self.inheritance=lambda: None
2439
+ self.undoInd=None
2440
+ return
2441
+
2442
+ def binButton_action(self,FlagTreeSelection=True):
2443
+ w=self.Explorer_tree_splitter.width()
2444
+ if self.binButton.isChecked():
2445
+ self.Explorer_tree_splitter.setSizes([0, w])
2446
+ else:
2447
+ self.Explorer_tree_splitter.setSizes([w, 0])
2448
+ self.tree_splitter_action(FlagTreeSelection)
2449
+
2450
+ def tree_splitter_action(self,FlagTreeSelection=True):
2451
+ FlagProcessEnabled=self.Explorer_tree_splitter.sizes()[0]>0
2452
+ self.processTree.setEnabled(FlagProcessEnabled)
2453
+ for b in self.processButtonBar.buttons.values():
2454
+ b.setEnabled(FlagProcessEnabled)
2455
+ if not FlagProcessEnabled and FlagTreeSelection:
2456
+ self.processTree.blockSignals(True)
2457
+ self.processTree.setCurrentItem(None)
2458
+ self.processTree.clearSelection()
2459
+ self.processTree.blockSignals(False)
2460
+ self.processTree_item_selection(self.binTree)
2461
+ FlagBinEnabled=self.Explorer_tree_splitter.sizes()[1]>0
2462
+ self.binTree.setEnabled(FlagBinEnabled)
2463
+ self.binButton.setChecked(FlagBinEnabled)
2464
+ self.binButton.setIcon(self.binIconOn if FlagBinEnabled else self.binIconOff)
2465
+ if not FlagBinEnabled and FlagTreeSelection:
2466
+ self.binTree.blockSignals(True)
2467
+ self.binTree.setCurrentItem(None)
2468
+ self.binTree.clearSelection()
2469
+ self.binTree.blockSignals(False)
2470
+ self.processTree_item_selection(self.processTree)
2471
+
2472
+ def eventFilter(self, obj, event):
2473
+ if event.type() == QEvent.Type.ShortcutOverride:
2474
+ event.accept()
2475
+ return True
2476
+ return super().eventFilter(obj, event)
2477
+
2478
+ def keyPressEvent(self, event: QKeyEvent):
2479
+ if event.key() in (Qt.Key.Key_Return,Qt.Key.Key_Enter):
2480
+ if self.processTree.isEnabled():
2481
+ obj=self.processTree
2482
+ else:
2483
+ obj=self.binTree
2484
+ obj.setFocus()
2485
+ return super().keyPressEvent(event)
2486
+
2487
+ def processTree_item_selection(self, tree:ProcessTree):
2488
+ self.currentTree=tree
2489
+ for indTree,t in enumerate([self.processTree,self.binTree]):
2490
+ if t==tree:
2491
+ index,child=self.arrangeCurrentProcess(tree)
2492
+ item=tree.currentItem()
2493
+ if item: item.setSelected(True)
2494
+ self.TREpar.tree=indTree
2495
+ self.TREpar.process=index
2496
+ self.TREpar.step=child
2497
+ if self.TREpar.project is not None:
2498
+ self.projectTree.itemList[0][self.TREpar.project].copyfromfields(self.TREpar,['tree','process','step'])
2499
+ tree.setProcessActionButtonLayout()
2500
+ self.adjustProcessSelection()
2501
+ if self.TREpar.tree==0 and self.Explorer_tree_splitter.sizes()[0]==0:
2502
+ self.binButton.setChecked(False)
2503
+ self.binButton_action(FlagTreeSelection=False)
2504
+ if index is not None and index>=0:
2505
+ if child:
2506
+ self.selectStep()
2507
+ #ITE:ITEpar=self.ITEfromTRE(self.TREpar)
2508
+ #pri.Coding.green(f'Process tree selection --->\t {self.TREpar.project} {self.TREpar.tree} {self.TREpar.process} {self.TREpar.step-1 if self.TREpar.step else self.TREpar.step} \t {ITE.ind}')
2509
+ else:
2510
+ self.ITEpar.copyfrom(tree.itemList[0][index][child])
2511
+ #pri.Coding.green(f'Process tree selection --->\t {self.TREpar.tree} {self.TREpar.process} {self.TREpar.step-1 if self.TREpar.step else self.TREpar.step}')
2512
+ else:
2513
+ t.actionBar.setVisible(False)
2514
+ t.blockSignals(True)
2515
+ t.clearSelection()
2516
+ t.blockSignals(False)
2517
+
2518
+ def arrangeCurrentProcess(self, tree:ProcessTree):
2519
+ index=child=None
2520
+ item=tree.currentItem()
2521
+ if item:
2522
+ self.stepButtonBar.show()
2523
+ if item.parent():
2524
+ child=item.parent().indexOfChild(item)+1
2525
+ item=item.parent()
2526
+ else:
2527
+ child=0
2528
+ index=tree.indexOfTopLevelItem(item)
2529
+ if index<0 or index>len(tree.itemList[0])-1:
2530
+ index=None
2531
+ return index, child
2532
+ ITEs=tree.itemList[0][index]
2533
+ for c in list(stepData):
2534
+ b:QToolButton=self.stepButtonBar.buttons[c]
2535
+ lab:QLabel=self.stepButtonBar.labels[c]
2536
+ #b.label.setVisible(c in allData[0]['mandatory'])
2537
+ nsteps=len(ITEs[0].children)
2538
+ self.setProcessFlagRun(ITEs[0].ind)
2539
+ if c in ITEs[0].children:
2540
+ ind=list(ITEs[0].children).index(c)
2541
+ if c in ITEs[0].mandatory: ITEs[ind+1].active=True
2542
+ if item.childCount()>ind:
2543
+ item_child=item.child(ind)
2544
+ item_child.setHidden(not ITEs[ind+1].active)
2545
+ b.setVisible(c not in ITEs[0].mandatory) #b.setVisible(True)
2546
+ lab.setVisible(c in ITEs[0].mandatory)
2547
+ flagRunnable=all([ITEs[j].flagRun==0 for j in range(ind+2,nsteps+1)]) if ind<nsteps else True
2548
+ flagRunnable=flagRunnable and ITEs[ind+1].flagRun==0 and ITEs[0].flagRun!=-2 #and len(ITEs[ind+1].link)==0
2549
+ b.setEnabled(flagRunnable)
2550
+ lab.setEnabled(flagRunnable)
2551
+ b.setChecked(ITEs[ind+1].active)
2552
+ else:
2553
+ flagRunnable=False
2554
+ b.setVisible(False)
2555
+ lab.setVisible(False)
2556
+ b.setEnabled(False)
2557
+ lab.setEnabled(False)
2558
+ b.setChecked(False)
2559
+ b.setButtonIcon()
2560
+ if self.stepPage:
2561
+ self.stepPage.items[c].setVisible(c in ITEs[0].children) #and b.isChecked())
2562
+ pageButton:QPushButton=self.stepPage.items[c].findChildren(QPushButton)[0]
2563
+ if flagRunnable:
2564
+ pageButton.setIcon(b.icon())
2565
+ else:
2566
+ pageButton.setIcon(b.iconOn)
2567
+ else:
2568
+ self.ITEpar.FlagNone=True
2569
+ self.hideStepButtons()
2570
+ return index, child
2571
+
2572
+ def hideStepButtons(self):
2573
+ for c in list(stepData):
2574
+ b:QToolButton=self.stepButtonBar.buttons[c]
2575
+ lab:QLabel=self.stepButtonBar.labels[c]
2576
+ b.setVisible(False)
2577
+ lab.setVisible(False)
2578
+
2579
+ def setProcessFlagRun(self,ind):
2580
+ ITEs:ITEpar=self.ITEsfromInd(ind)
2581
+ ITEs[0].flagRun=-2 if any([i.flagRun==-2 for i in ITEs[1:] if i.active]) else -1 if any([i.flagRun==-1 for i in ITEs[1:] if i.active]) else 2 if all([i.flagRun==2 for i in ITEs[1:] if i.active]) else 1 if all([i.flagRun>0 for i in ITEs[1:] if i.active]) else 0
2582
+
2583
+ def selectStep(self):
2584
+ tree=[self.processTree,self.binTree][self.TREpar.tree]
2585
+ index=self.TREpar.process
2586
+ child=self.TREpar.step
2587
+
2588
+ FlagVisible=[]
2589
+ FlagAdjustPar=None
2590
+ #tabAreaWidget=self.widgets[-1]
2591
+ #tabAreaWidget.scrollArea.splitter.hide()
2592
+ #tabAreaWidget.scrollArea.splitter.setFixedWidth(tabAreaWidget.scrollArea.splitter.splitterMaximumSize)
2593
+ self.ITEpar.copyfrom(tree.itemList[0][index][child])
2594
+ """
2595
+ indpar=[j for j in self.ITEpar.ind]
2596
+ for j in range(indpar[-2]):
2597
+ indpar[-2]=j
2598
+ self.inheritance(indpar)
2599
+ """
2600
+ i=None
2601
+ #FlagPrev=CAL.ind[-1]==len(self.w_Calibration.TABpar_prev_at(CAL.ind))-1
2602
+ FlagCalVi=False
2603
+ for k,w in enumerate(self.widgets):
2604
+ w:gPaIRS_Tab
2605
+ try:
2606
+ par:TABpar=tree.itemList[k+1][index][child-1][-1]
2607
+ except Exception as inst:
2608
+ pri.Error.red(f'[selectStep] Error while accessing the prev queues:\n{inst}')
2609
+ pass
2610
+ if w.TABname=='Calibration':
2611
+ flagVisible=par is not None
2612
+ FlagVisible.append(flagVisible)
2613
+ if w.buttonTab: w.buttonTab.setVisible(flagVisible)
2614
+
2615
+ FlagCalVi=w.TABpar.FlagCalVi if flagVisible else False
2616
+ if hasattr(w,'logo_CalVi'): w.logo_CalVi.setVisible(FlagCalVi)
2617
+ if hasattr(w,'button_Run_CalVi'): w.button_Run_CalVi.setVisible(FlagCalVi)
2618
+ elif w.TABname!='TabArea':
2619
+ flagVisible=FlagCalVi if '_CalVi' in w.TABname else par is not None
2620
+ FlagVisible.append(flagVisible)
2621
+ if w.buttonTab: w.buttonTab.setVisible(flagVisible)
2622
+ else:
2623
+ for j,f in enumerate(FlagVisible):
2624
+ if not f: par.FlagVisible[j]=f
2625
+ if par:
2626
+ w.TABpar.copyfrom(par)
2627
+ if FlagAdjustPar is None:
2628
+ FlagAdjustPar=not par.FlagInit
2629
+ i=k
2630
+ else:
2631
+ w.TABpar.FlagNone=True
2632
+
2633
+ FlagAdjustPar=FlagAdjustPar or not self.ITEpar.FlagInit
2634
+ FlagBridge=True
2635
+ if i is not None:
2636
+ #self.gui.setTABpars_at(self.ITEpar.ind,FlagAdjustPar,FlagBridge,widget=self.widgets[i])
2637
+ self.widgets[i].setTABpar(FlagAdjustPar,FlagBridge)
2638
+ #self.widgets[-1].setTABpar(FlagAdjustPar,FlagBridge)
2639
+ #self.widgets[-1].scrollArea.splitter.show()
2640
+
2641
+ def stepButton_action(self,b,t):
2642
+ tree:ProcessTree=self.currentTree
2643
+ item=tree.currentItem()
2644
+ if item.parent():
2645
+ item=item.parent()
2646
+ r=tree.indexOfTopLevelItem(item)
2647
+ ITEs=tree.itemList[0][r]
2648
+ c=list(ITEs[0].children.keys()).index(b.buttonData['type'])
2649
+ child=item.child(c)
2650
+ if b.buttonData['type'] in ITEs[0].mandatory:
2651
+ tree.setCurrentItem(child)
2652
+ self.processTree_item_selection(tree)
2653
+ return
2654
+ ITE:ITEpar=ITEs[c+1]
2655
+ if len(ITE.link)>0:
2656
+ global FlagReturn
2657
+ FlagReturn=False
2658
+ def goToLinkedStep():
2659
+ global FlagReturn
2660
+ self.TREpar.process=ITE.link[2]
2661
+ if ITE.active:
2662
+ self.TREpar.step=ITE.link[3]+1
2663
+ else:
2664
+ self.TREpar.step=0
2665
+ self.gui.adjustProjectSelection()
2666
+ FlagReturn=True
2667
+ return
2668
+ def unLink():
2669
+ global FlagReturn
2670
+ b.setChecked(ITE.active)
2671
+ self.gui.unlink_pars(ITE.ind)
2672
+ b.click()
2673
+ FlagReturn=True
2674
+ return
2675
+ addButton={
2676
+ 'Unlink step': unLink,
2677
+ 'Go to linked step': goToLinkedStep,
2678
+ }
2679
+ ITE0_master=self.ITEsfromInd(ITE.link)[0]
2680
+ linkInfo=f'linked to that of the process:\n\n{ITE0_master.ind[2]+1}: {ITE0_master.name}'
2681
+ warningDialog(self,f'The current {ITE.name} step is {linkInfo}.\n\nYou should either modify the step of the above process or unlink the current step from it.',addButton=addButton)
2682
+ if not FlagReturn: b.setChecked(ITE.active)
2683
+ return
2684
+ child.setHidden(not b.isChecked())
2685
+ ITE.active=b.isChecked()
2686
+ self.inheritance(ITE.ind)
2687
+ for dep in ITE.dependencies:
2688
+ ITE_dep:ITEpar=self.ITEfromInd(dep)
2689
+ ITE_dep.active=b.isChecked()
2690
+ item_dep=tree.topLevelItem(dep[2])
2691
+ child_dep=item_dep.child(c)
2692
+ child_dep.setHidden(not b.isChecked())
2693
+ self.gui.inheritance(dep)
2694
+ if self.stepPage: self.stepPage.items[t].setVisible(b.isChecked())
2695
+ FlagSettingPar=TABpar.FlagSettingPar
2696
+ TABpar.FlagSettingPar=True
2697
+ tree.clearSelection()
2698
+ TABpar.FlagSettingPar=FlagSettingPar
2699
+ if ITEs[c+1].active:
2700
+ tree.setCurrentItem(child)
2701
+ child.setSelected(True)
2702
+ else:
2703
+ tree.setCurrentItem(item)
2704
+ item.setSelected(True)
2705
+ return
2706
+
2707
+ def adjustProjectSelection(self):
2708
+ project=self.TREpar.project
2709
+
2710
+ FlagHideStepButtons=False
2711
+ if project==None:
2712
+ self.processWidget.setEnabled(False)
2713
+ for k,tree in enumerate([self.processTree,self.binTree]):
2714
+ tree.blockSignals(True)
2715
+ tree.clearSelection()
2716
+ clean_tree(tree)
2717
+ tree.blockSignals(False)
2718
+ FlagHideStepButtons=True
2719
+ else:
2720
+ self.processWidget.setEnabled(True)
2721
+
2722
+ #for w in self.widgets: w.TABpar_prev=[[],[]]
2723
+ item=None
2724
+ for k,tree in enumerate([self.processTree,self.binTree]):
2725
+ tree.itemList=[]
2726
+ for itemList in self.projectTree.itemList[1:]:
2727
+ tree.itemList.append(itemList[project][k])
2728
+ """
2729
+ for w,prev in zip(self.widgets,tree.itemList[1:]):
2730
+ w:gPaIRS_Tab
2731
+ w.TABpar_prev[k]=prev
2732
+ pass
2733
+ """
2734
+ tree.blockSignals(True)
2735
+ tree.clearSelection()
2736
+ clean_tree(tree)
2737
+ for i in range(len(tree.itemList[0])):
2738
+ tree.createProcessItem(None,i,FlagNewItem=False)
2739
+
2740
+ if self.TREpar.tree==k:
2741
+ item=self.projectTree.topLevelItem(project)
2742
+ self.projectTree.blockSignals(True)
2743
+ self.projectTree.setCurrentItem(item)
2744
+ item.setSelected(True)
2745
+ self.projectTree.blockSignals(False)
2746
+ self.projectTree.setButtonLayout()
2747
+ self.ui.binButton.setChecked(k==1)
2748
+ self.binButton_action(FlagTreeSelection=False)
2749
+ if self.TREpar.process is not None:
2750
+ item=tree.topLevelItem(self.TREpar.process)
2751
+ if item is None:
2752
+ self.TREpar.process=None
2753
+ self.TREpar.step=None
2754
+ if self.TREpar.step:
2755
+ item=item.child(self.TREpar.step-1)
2756
+ if item:
2757
+ tree.setCurrentItem(item)
2758
+ item.setSelected(True)
2759
+ tree.blockSignals(False)
2760
+ if item:
2761
+ item.setSelected(True)
2762
+ else: FlagHideStepButtons=True
2763
+ if FlagHideStepButtons:
2764
+ for c in list(stepData):
2765
+ b:QToolButton=self.stepButtonBar.buttons[c]
2766
+ b.setVisible(False)
2767
+ b.setChecked(False)
2768
+ return
2769
+
2770
+ def ITEsfromTRE(self,TRE:TREpar):
2771
+ return self.projectTree.itemList[1][TRE.project][TRE.tree][TRE.process]
2772
+
2773
+ def ITEsfromInd(self,ind:list):
2774
+ return self.projectTree.itemList[1][ind[0]][ind[1]][ind[2]]
2775
+
2776
+ def ITEfromTRE(self,TRE:TREpar):
2777
+ return self.projectTree.itemList[1][TRE.project][TRE.tree][TRE.process][TRE.step]
2778
+
2779
+ def ITEfromInd(self,ind:list):
2780
+ return self.projectTree.itemList[1][ind[0]][ind[1]][ind[2]][ind[3]+1]
2781
+
2782
+ def cancelUndo(self,ind:list=None):
2783
+ if ind is None:
2784
+ ind=[self.TREpar.project, self.TREpar.tree, self.TREpar.process, self.TREpar.step-1 if self.TREpar.step else -1, -1]
2785
+ if self.undoInd and self.undoInd[:-1]!=ind[:-1]:
2786
+ self.undoInd[-1]=-1
2787
+ if self.gui.checkProcesses(FlagInit=True,ind=self.undoInd):
2788
+ self.setITElayout(self.ITEfromInd(self.undoInd))
2789
+ self.undoInd=None
2790
+
2791
+ def setITElayout(self,ITE:ITEpar=None):
2792
+ if ITE is None:
2793
+ FlagCurrentITE=True
2794
+ ITE=self.ITEpar
2795
+ else:
2796
+ FlagCurrentITE=False
2797
+ if ITE.FlagNone: return
2798
+ self.cancelUndo(ITE.ind)
2799
+ FlagDone=True
2800
+ warningMessage=''
2801
+ ITEs=self.ITEsfromInd(ITE.ind)
2802
+ #mandatory=ITEs[0].mandatory
2803
+ j=1
2804
+ while ITEs[j].Step!=ITE.Step and j<len(ITEs)-1:
2805
+ if ITEs[j].active and ITEs[j].OptionDone==0 and ITEs[j].Step in ITE.parents:
2806
+ FlagDone=False
2807
+ if warningMessage: warningMessage+='\n\n'
2808
+ warningMessage+='--- '+ITEs[j].name+' ---\n'+f'The completion of the step "{ITEs[j].name}" is needed for "{ITE.name}"'
2809
+ j+=1
2810
+ for w in self.widgets:
2811
+ w:gPaIRS_Tab
2812
+ if FlagCurrentITE:
2813
+ TABpar_ind=w.TABpar
2814
+ else:
2815
+ TABpar_ind:TABpar=w.TABpar_at(ITE.ind)
2816
+ if not TABpar_ind: continue
2817
+ if TABpar_ind.FlagNone: continue
2818
+ if w.TABname=='Calibration':
2819
+ ITE.ncam=TABpar_ind.ncam
2820
+ ITE.progress=len(TABpar_ind.calList)
2821
+ if TABpar_ind.OptionDone==0: pass
2822
+ FlagDone=FlagDone and TABpar_ind.OptionDone!=0
2823
+ if not TABpar_ind.OptionDone==1 and not TABpar_ind.FlagNone:
2824
+ if TABpar_ind.warningMessage=='': continue
2825
+ if warningMessage: warningMessage+='\n\n'
2826
+ warningMessage+='--- '+w.TABname+' ---\n'+TABpar_ind.warningMessage
2827
+ if ITE.flagRun==0:
2828
+ if FlagDone:
2829
+ if warningMessage:
2830
+ ITE.warningMessage=warningMessage
2831
+ ITE.label='running_warn'
2832
+ ITE.OptionDone=-1
2833
+ else:
2834
+ if ITE.Step!=StepTypes.cal:
2835
+ ITE.warningMessage='Process step ready for running!'
2836
+ ITE.OptionDone=1
2837
+ ITE.label='running'
2838
+ else:
2839
+ ITE.warningMessage='Calibration files correctly identified!'
2840
+ ITE.OptionDone=1
2841
+ ITE.label='done'
2842
+ else:
2843
+ if warningMessage:
2844
+ ITE.warningMessage=warningMessage
2845
+ ITE.label='warning'
2846
+ else:
2847
+ ITE.warningMessage='Process step not yet initialized!'
2848
+ ITE.label='uninitialized'
2849
+ ITE.OptionDone=0
2850
+ else:
2851
+ procdata:dataTreePar=self.ITEfromInd(ITE.ind).procdata
2852
+ #if hasattr(self.gui,'reset_step') and not os.path.exists(procdata.procOutName()) and not ITE.FlagInit:
2853
+ # self.gui.reset_step(procdata.ind)
2854
+ # return
2855
+ #ITE.progress=procdata.numFinalized
2856
+ if ITE.flagRun==-2:
2857
+ ITE.warningMessage='Process step currently running.'
2858
+ ITE.label='running'
2859
+ else:
2860
+ if not FlagDone:
2861
+ ITE.warningMessage=f'The following issues are detected with the present step:\n{warningMessage}\n\nPlease, check if it is out-of-date!'
2862
+ ITE.procdata.warnings[0]=ITE.procdata.headerSection('CRITICAL ERROR',ITE.warningMessage,'X')
2863
+ ITE.procdata.warnings[1]=''
2864
+ ITE.procdata.setCompleteLog()
2865
+ ITE.label='error'
2866
+ ITE.OptionDone=0
2867
+ elif procdata.FlagErr:
2868
+ ITE.warningMessage='Some errors occured in this process step! See Log for more information.'
2869
+ ITE.label='error'
2870
+ elif ITE.flagRun==-1:
2871
+ ITE.warningMessage='Process step stopped by user.'
2872
+ ITE.label='paused'
2873
+ elif ITE.flagRun==-10:
2874
+ ITE.warningMessage='Process step in the queue for execution.'
2875
+ ITE.label='queue'
2876
+ else:
2877
+ ITE.warningMessage='Process step correctly completed! 💪🏻'
2878
+ ITE.label='done'
2879
+
2880
+ if FlagCurrentITE:
2881
+ ITE:ITEpar=self.ITEfromInd(ITE.ind)
2882
+ ITE.copyfrom(self.ITEpar,exceptions=['procdata'])
2883
+ self.updateItemWidget(ITE)
2884
+ return
2885
+
2886
+ def updateItemWidget(self,ITE:ITEpar):
2887
+ if self.TREpar.project==ITE.ind[0]:
2888
+ tree=[self.processTree,self.binTree][ITE.ind[1]]
2889
+ item:QTreeWidgetItem=tree.topLevelItem(ITE.ind[2])
2890
+ item=item.child(ITE.ind[3])
2891
+ TREind=[self.TREpar.project, self.TREpar.tree, self.TREpar.process, self.TREpar.step-1 if self.TREpar.step else -1, -1]
2892
+ if len(ITE.link)==0:
2893
+ if self.gui and ITE.flagRun==0 and len(self.gui.ui.tabAreaWidget.TABpar_prev_at(ITE.ind))<=1:
2894
+ item.setIcon(0,self.processTree.uneditedIcon)
2895
+ elif self.gui and TREind[:-1]==ITE.ind[:-1] and self.gui.ui.tabAreaWidget.TABpar.ind[-1]<len(self.gui.w_Input.TABpar_prev_at(ITE.ind))-1:
2896
+ item.setIcon(0,self.processTree.undoneIcon)
2897
+ self.undoInd=self.gui.ui.tabAreaWidget.TABpar.ind
2898
+ else:
2899
+ item.setIcon(0,QIcon())
2900
+ else:
2901
+ item.setIcon(0,self.processTree.linkedIcon)
2902
+
2903
+ itemWidget:StepItemWidget=tree.itemWidget(item,1)
2904
+
2905
+ ITE_ind=self.ITEfromInd(ITE.ind)
2906
+ itemWidget.setFromITE(ITE_ind)
2907
+
2908
+ def updateSwitchMovies(self,ind,FlagStart=False):
2909
+ self.updateProjectSwitchMovie(ind,FlagStart)
2910
+ self.updateProcessSwitchMovie(ind,FlagStart)
2911
+
2912
+ def updateProjectSwitchMovie(self,ind,FlagStart=False):
2913
+ if ind is None: return
2914
+ topLevelItem=self.projectTree.topLevelItem(ind[0])
2915
+ itemWidget=self.projectTree.itemWidget(topLevelItem,1)
2916
+ if itemWidget:
2917
+ switch:ModernSwitch=itemWidget.findChildren(ModernSwitch)[0]
2918
+ if switch:
2919
+ if FlagStart: switch.startTimer()
2920
+ else: switch.stopTimer()
2921
+
2922
+ def updateProcessSwitchMovie(self,ind,FlagStart=False):
2923
+ if ind is None: return
2924
+ if self.TREpar.project==ind[0]:
2925
+ topLevelItem=self.processTree.topLevelItem(ind[2])
2926
+ itemWidget=self.processTree.itemWidget(topLevelItem,1)
2927
+ if itemWidget:
2928
+ switch:ModernSwitch=itemWidget.findChildren(ModernSwitch)[0]
2929
+ if switch:
2930
+ if FlagStart: switch.startTimer()
2931
+ else: switch.stopTimer()
2932
+
2933
+ class StartingPage(QFrame):
2934
+ ICON_SIZE = 65
2935
+ LAYOUT_SPACING = 20
2936
+ LAYOUT_MARGIN = 20
2937
+ ITEM_HEIGHT = 100
2938
+ NAME_LABEL_HEIGHT = 32
2939
+ TEXT_LAYOUT_SPACING = 1
2940
+ CAPTION_WIDTH = 350
2941
+ BUTTON_LAYOUT_TOP_MARGIN = 0
2942
+
2943
+ dFontPixelSize_title=11
2944
+ TITLE_FONT_SIZE = fontPixelSize+dFontPixelSize_title
2945
+ NAME_FONT_SIZE = TITLE_FONT_SIZE-2
2946
+ CAPTION_FONT_SIZE = NAME_FONT_SIZE-2
2947
+
2948
+ def __init__(self, title:str='', processes:dict={}, buttonBar:dict={}):
2949
+ super().__init__()
2950
+ if __name__ == "__main__":
2951
+ iconW = QIcon()
2952
+ iconW.addFile(u""+ icons_path +"logo_PaIRS.png", QSize(), QIcon.Normal, QIcon.Off)
2953
+ self.setWindowTitle(title)
2954
+ self.setWindowIcon(iconW)
2955
+
2956
+ # ScrollArea
2957
+ self.scroll_area = QScrollArea(self)
2958
+ self.scroll_area.setWidgetResizable(True)
2959
+
2960
+ # Widget per contenere il layout principale
2961
+ container_widget = QFrame()
2962
+ container_widget.setObjectName('container')
2963
+ container_widget.setStyleSheet(f"""
2964
+ QFrame#container {{
2965
+ border: 1px solid rgba(128, 128, 128, 0.5);
2966
+ border-radius: 15px;
2967
+ background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 rgba(128, 128, 128, 0), stop:1 rgba(224, 224, 224, 0.25));
2968
+ }}
2969
+ QWidget{{
2970
+ background: transparent;
2971
+ }}
2972
+ """)
2973
+
2974
+ # Layout principale verticale
2975
+ self.main_layout = QVBoxLayout(container_widget)
2976
+ self.main_layout.setSpacing(self.LAYOUT_SPACING)
2977
+ self.main_layout.setContentsMargins(self.LAYOUT_MARGIN, self.LAYOUT_MARGIN, self.LAYOUT_MARGIN, self.LAYOUT_MARGIN)
2978
+
2979
+ # Label iniziale con font più grande
2980
+ self.title = QLabel(title)
2981
+ title_font = self.title.font()
2982
+ title_font.setBold(True)
2983
+ self.title.setFont(title_font)
2984
+ self.title.setAlignment(Qt.AlignLeft)
2985
+ self.title.setFixedHeight(self.NAME_LABEL_HEIGHT)
2986
+ self.main_layout.addWidget(self.title)
2987
+
2988
+ self.scroll_area.setWidget(container_widget)
2989
+ self.scroll_area.setStyleSheet(scrollAreaStyle())
2990
+ layout = QVBoxLayout(self)
2991
+ layout.addWidget(self.scroll_area)
2992
+ self.setLayout(layout)
2993
+
2994
+ self.setupPage(processes,buttonBar)
2995
+
2996
+ def setObjectName(self,name:str):
2997
+ QFrame.setObjectName(self,name)
2998
+ self.scroll_area.setObjectName('scroll_area_'+self.objectName())
2999
+ return
3000
+
3001
+ def setupPage(self, processes:dict={}, buttonBar:dict={}):
3002
+ if not len(processes): return
3003
+ button_margin=self.ITEM_HEIGHT-self.ICON_SIZE-self.BUTTON_LAYOUT_TOP_MARGIN
3004
+ CAPTION_HEIGHT=self.ITEM_HEIGHT-self.NAME_LABEL_HEIGHT-self.TEXT_LAYOUT_SPACING
3005
+ self.items={}
3006
+ # Itera sui dizionari nella lista
3007
+ for n, process in processes.items():
3008
+ # Layout orizzontale per ogni processo
3009
+ widget=QWidget()
3010
+ process_layout = QHBoxLayout(widget)
3011
+ process_layout.setSpacing(self.LAYOUT_SPACING)
3012
+ process_layout.setContentsMargins(0, 0, 0, 0)
3013
+
3014
+ # Pulsante con icona
3015
+ button_layout = QHBoxLayout()
3016
+ button_layout.setSpacing(0)
3017
+ button_layout.setContentsMargins(0, self.BUTTON_LAYOUT_TOP_MARGIN, 0, button_margin)
3018
+
3019
+ icon_button = QPushButton()
3020
+ pixmap = QPixmap(icons_path+process['icon']).scaled(self.ICON_SIZE, self.ICON_SIZE, mode=Qt.TransformationMode.SmoothTransformation)
3021
+ icon_button.setIcon(pixmap)
3022
+ icon_button.setIconSize(pixmap.size())
3023
+ icon_button.setFixedSize(self.ICON_SIZE, self.ICON_SIZE)
3024
+ icon_button.setStyleSheet("border: none; background: none;")
3025
+ icon_button.setCursor(Qt.PointingHandCursor)
3026
+
3027
+ # Effetto dinamico al clic
3028
+ icon_button.pressed.connect(lambda btn=icon_button: btn.setStyleSheet("border: none; background: #dcdcdc;"))
3029
+ icon_button.released.connect(lambda btn=icon_button: btn.setStyleSheet("border: none; background: none;"))
3030
+ if buttonBar:
3031
+ def action(name):
3032
+ return lambda: buttonBar[name]()
3033
+ icon_button.clicked.connect(action(n))
3034
+
3035
+ button_layout.addWidget(icon_button)
3036
+ process_layout.addLayout(button_layout)
3037
+
3038
+ # Layout verticale per nome e descrizione
3039
+ text_layout = QVBoxLayout()
3040
+ text_layout.setSpacing(self.TEXT_LAYOUT_SPACING)
3041
+ text_layout.setContentsMargins(0, 0, 0, 0)
3042
+
3043
+ name_label = QLabel(process['name'])
3044
+ name_label.setObjectName('name_label')
3045
+ name_font = name_label.font()
3046
+ name_font.setPixelSize(self.NAME_FONT_SIZE)
3047
+ name_font.setBold(True)
3048
+ name_label.setFont(name_font)
3049
+ name_label.setAlignment(Qt.AlignLeft)
3050
+ name_label.setFixedHeight(self.NAME_LABEL_HEIGHT)
3051
+ text_layout.addWidget(name_label)
3052
+
3053
+ caption_text_edit = QTextEdit(process['caption'])
3054
+ caption_text_edit.setObjectName('caption_text_edit')
3055
+ caption_font = caption_text_edit.font()
3056
+ caption_font.setPixelSize(self.CAPTION_FONT_SIZE)
3057
+ caption_text_edit.setFont(caption_font)
3058
+ caption_text_edit.setAlignment(Qt.AlignmentFlag.AlignJustify)
3059
+ caption_text_edit.setReadOnly(True)
3060
+ caption_text_edit.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
3061
+ caption_text_edit.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
3062
+ caption_text_edit.setFrameStyle(QFrame.NoFrame)
3063
+ #caption_text_edit.setFixedWidth(self.CAPTION_WIDTH)
3064
+ caption_text_edit.setStyleSheet("background: transparent;")
3065
+ caption_text_edit.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
3066
+
3067
+ # Adjust height to content
3068
+ caption_text_edit.document().setTextWidth(caption_text_edit.viewport().width())
3069
+ caption_text_edit.setFixedHeight(CAPTION_HEIGHT)#caption_text_edit.document().size().height())
3070
+
3071
+ text_layout.addWidget(caption_text_edit)
3072
+
3073
+ process_layout.addLayout(text_layout)
3074
+
3075
+ # Spacer orizzontale che si espande
3076
+ #spacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
3077
+ #process_layout.addSpacerItem(spacer)
3078
+
3079
+ # Aggiungi il layout orizzontale al layout principale
3080
+ self.main_layout.addWidget(widget)
3081
+
3082
+ self.items[n]=widget
3083
+
3084
+ self.main_layout.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Expanding))
3085
+
3086
+ def setFontPixelSize(self,fPixelSize):
3087
+ TITLE_FONT_SIZE = min([fPixelSize+self.dFontPixelSize_title,30])
3088
+ title_font = self.title.font()
3089
+ title_font.setPixelSize(TITLE_FONT_SIZE)
3090
+ title_font.setBold(True)
3091
+ self.title.setFont(title_font)
3092
+
3093
+ NAME_FONT_SIZE = TITLE_FONT_SIZE-2
3094
+ for c in self.findChildren(QLabel,'name_label'):
3095
+ c:QLabel
3096
+ name_font = c.font()
3097
+ name_font.setPixelSize(NAME_FONT_SIZE)
3098
+ name_font.setBold(True)
3099
+ c.setFont(name_font)
3100
+ CAPTION_FONT_SIZE = NAME_FONT_SIZE-4
3101
+ for c in self.findChildren(QTextEdit,'caption_text_edit'):
3102
+ c:QLabel
3103
+ caption_font = c.font()
3104
+ caption_font.setPixelSize(CAPTION_FONT_SIZE)
3105
+ c.setFont(caption_font)
3106
+
3107
+ if __name__ == "__main__":
3108
+ app = QApplication([])
3109
+ app.setStyle('Fusion')
3110
+
3111
+ Explorer = PaIRS_Explorer()
3112
+ if FlagStartingPages:
3113
+ projectStartingPage=StartingPage("Select a project", projectPageButtons,Explorer.projectPageActions)
3114
+ processStartingPage=StartingPage("Select a process", processData,Explorer.processPageActions)
3115
+ stepStartingPage=StartingPage("Set up each step of the process", stepData,Explorer.stepPageActions)
3116
+ Explorer.stepPage=stepStartingPage
3117
+
3118
+ Explorer.resize(500, 750)
3119
+ Explorer.move(0 if FlagStartingPages else 150, 150)
3120
+ Explorer.show()
3121
+ if FlagStartingPages:
3122
+ projectStartingPage.resize(500, 800)
3123
+ projectStartingPage.move(500, 150)
3124
+ projectStartingPage.show()
3125
+ projectStartingPage.setFontPixelSize(fontPixelSize)
3126
+ processStartingPage.resize(500, 800)
3127
+ processStartingPage.move(1000, 150)
3128
+ processStartingPage.show()
3129
+ processStartingPage.setFontPixelSize(fontPixelSize)
3130
+ stepStartingPage.resize(500, 800)
3131
+ stepStartingPage.move(1500, 150)
3132
+ stepStartingPage.show()
3133
+ stepStartingPage.setFontPixelSize(fontPixelSize)
3134
+
3135
+ app.exec()
3136
+