PaIRS-UniNa 0.2.10__cp313-cp313-macosx_11_0_universal2.whl

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