PaIRS-UniNa 0.2.8__cp312-cp312-win_amd64.whl → 0.2.9__cp312-cp312-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- PaIRS_UniNa/Calibration_Tab.py +15 -0
- PaIRS_UniNa/Changes.txt +9 -0
- PaIRS_UniNa/Explorer.py +3158 -3126
- PaIRS_UniNa/FolderLoop.py +561 -561
- PaIRS_UniNa/Input_Tab.py +826 -826
- PaIRS_UniNa/Input_Tab_tools.py +3016 -3019
- PaIRS_UniNa/PaIRS.py +17 -17
- PaIRS_UniNa/SPIVCalHelp.py +155 -0
- PaIRS_UniNa/Saving_tools.py +277 -277
- PaIRS_UniNa/_PaIRS_PIV.pyd +0 -0
- PaIRS_UniNa/__init__.py +2 -2
- PaIRS_UniNa/gPaIRS.py +3890 -3889
- PaIRS_UniNa/icons/information.png +0 -0
- PaIRS_UniNa/icons/information2.png +0 -0
- PaIRS_UniNa/icons/spiv_setup_no.png +0 -0
- PaIRS_UniNa/icons/spiv_setup_ok.png +0 -0
- PaIRS_UniNa/listLib.py +301 -301
- PaIRS_UniNa/parForMulti.py +433 -433
- PaIRS_UniNa/tabSplitter.py +606 -606
- PaIRS_UniNa/ui_Calibration_Tab.py +575 -542
- PaIRS_UniNa/ui_Custom_Top.py +294 -294
- PaIRS_UniNa/ui_Input_Tab.py +1098 -1098
- PaIRS_UniNa/ui_Input_Tab_CalVi.py +1280 -1280
- PaIRS_UniNa/ui_Log_Tab.py +261 -261
- PaIRS_UniNa/ui_Output_Tab.py +2360 -2360
- PaIRS_UniNa/ui_Process_Tab.py +3808 -3808
- PaIRS_UniNa/ui_Process_Tab_CalVi.py +1547 -1547
- PaIRS_UniNa/ui_Process_Tab_Disp.py +1139 -1139
- PaIRS_UniNa/ui_Process_Tab_Min.py +435 -435
- PaIRS_UniNa/ui_ResizePopup.py +203 -203
- PaIRS_UniNa/ui_Vis_Tab.py +1626 -1626
- PaIRS_UniNa/ui_Vis_Tab_CalVi.py +1249 -1249
- PaIRS_UniNa/ui_Whatsnew.py +131 -131
- PaIRS_UniNa/ui_gPairs.py +873 -873
- PaIRS_UniNa/ui_infoPaIRS.py +550 -550
- PaIRS_UniNa/whatsnew.txt +1 -1
- {pairs_unina-0.2.8.dist-info → pairs_unina-0.2.9.dist-info}/METADATA +4 -3
- {pairs_unina-0.2.8.dist-info → pairs_unina-0.2.9.dist-info}/RECORD +40 -36
- {pairs_unina-0.2.8.dist-info → pairs_unina-0.2.9.dist-info}/WHEEL +0 -0
- {pairs_unina-0.2.8.dist-info → pairs_unina-0.2.9.dist-info}/top_level.txt +0 -0
PaIRS_UniNa/FolderLoop.py
CHANGED
|
@@ -1,562 +1,562 @@
|
|
|
1
|
-
import sys
|
|
2
|
-
from PySide6.QtWidgets import (
|
|
3
|
-
QApplication, QDialog, QVBoxLayout, QLabel, QHBoxLayout, QWidget, QSpacerItem, QTreeWidget, QTreeWidgetItem,
|
|
4
|
-
QPushButton, QProgressBar, QSizePolicy,
|
|
5
|
-
QFileDialog, QListView, QAbstractItemView, QTreeView, QFileSystemModel
|
|
6
|
-
)
|
|
7
|
-
from PySide6.QtGui import QPixmap, QFont, QIcon, QCursor
|
|
8
|
-
from PySide6.QtCore import Qt, QTimer, QSize, QVariantAnimation
|
|
9
|
-
from time import sleep as timesleep
|
|
10
|
-
from pathlib import Path
|
|
11
|
-
from typing import Optional, List
|
|
1
|
+
import sys
|
|
2
|
+
from PySide6.QtWidgets import (
|
|
3
|
+
QApplication, QDialog, QVBoxLayout, QLabel, QHBoxLayout, QWidget, QSpacerItem, QTreeWidget, QTreeWidgetItem,
|
|
4
|
+
QPushButton, QProgressBar, QSizePolicy,
|
|
5
|
+
QFileDialog, QListView, QAbstractItemView, QTreeView, QFileSystemModel
|
|
6
|
+
)
|
|
7
|
+
from PySide6.QtGui import QPixmap, QFont, QIcon, QCursor
|
|
8
|
+
from PySide6.QtCore import Qt, QTimer, QSize, QVariantAnimation
|
|
9
|
+
from time import sleep as timesleep
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Optional, List
|
|
12
12
|
from .PaIRS_pypacks import fontPixelSize, fontName, icons_path
|
|
13
|
-
|
|
14
|
-
time_sleep_loop=0
|
|
15
|
-
|
|
16
|
-
def choose_directories(base:Path = Path('.'),parent=None) -> Optional[List[str]]:
|
|
17
|
-
"""
|
|
18
|
-
Open a dialogue to select multiple directories
|
|
19
|
-
Args:
|
|
20
|
-
base (Path): Starting directory to show when opening dialogue
|
|
21
|
-
Returns:
|
|
22
|
-
List[str]: List of paths that were selected, ``None`` if "cancel" selected"
|
|
23
|
-
References:
|
|
24
|
-
Mildly adapted from https://stackoverflow.com/a/28548773
|
|
25
|
-
to use outside an exising Qt Application
|
|
26
|
-
"""
|
|
27
|
-
|
|
28
|
-
file_dialog = QFileDialog(parent)
|
|
29
|
-
file_dialog.setWindowTitle("Select folders for process loop")
|
|
30
|
-
file_dialog.setWindowIcon(QIcon(icons_path + "process_loop.png"))
|
|
31
|
-
file_dialog.setWindowIconText("Select folders for process loop")
|
|
32
|
-
file_dialog.setOption(QFileDialog.Option.DontUseNativeDialog, True)
|
|
33
|
-
file_dialog.setFileMode(QFileDialog.Directory)
|
|
34
|
-
for widget_type in (QListView, QTreeView):
|
|
35
|
-
for view in file_dialog.findChildren(widget_type):
|
|
36
|
-
if isinstance(view.model(), QFileSystemModel):
|
|
37
|
-
view.setSelectionMode(
|
|
38
|
-
QAbstractItemView.ExtendedSelection)
|
|
39
|
-
|
|
40
|
-
paths=[]
|
|
41
|
-
if file_dialog.exec():
|
|
42
|
-
paths = file_dialog.selectedFiles()
|
|
43
|
-
return paths
|
|
44
|
-
|
|
45
|
-
class FolderLoopDialog(QDialog):
|
|
46
|
-
def __init__(self, pixmap_list, name_list, flag_list, parent=None, func=lambda it, opt: print(f"Iteration {it}"), paths=[], process_name = 'Process', *args, **kwargs):
|
|
47
|
-
super().__init__(parent=parent, *args, **kwargs)
|
|
48
|
-
|
|
49
|
-
self.func=func
|
|
50
|
-
self.nfolders=len(paths)
|
|
51
|
-
self.paths=paths
|
|
52
|
-
|
|
53
|
-
# Variables for easy customization
|
|
54
|
-
self.setWindowTitle("Process loop over folders")
|
|
55
|
-
self.setWindowIcon(QIcon(icons_path + "process_loop.png"))
|
|
56
|
-
self.setWindowIconText("Process loop over folders")
|
|
57
|
-
self.title_text = "Configure each step of the process"
|
|
58
|
-
if parent is None:
|
|
59
|
-
self.title_font = QFont(fontName, fontPixelSize+12, QFont.Bold)
|
|
60
|
-
self.item_font = QFont(fontName, fontPixelSize+8)
|
|
61
|
-
self.button_font = QFont(fontName, fontPixelSize+2)
|
|
62
|
-
else:
|
|
63
|
-
self.title_font:QFont = parent.font()
|
|
64
|
-
self.title_font.setBold(True)
|
|
65
|
-
self.title_font.setPixelSize(fontPixelSize+12)
|
|
66
|
-
self.item_font:QFont = parent.font()
|
|
67
|
-
self.item_font = parent.font()
|
|
68
|
-
self.item_font.setPixelSize(fontPixelSize+8)
|
|
69
|
-
self.button_font: QFont = parent.font()
|
|
70
|
-
self.button_font.setPixelSize(fontPixelSize+2)
|
|
71
|
-
self.title_height = 48
|
|
72
|
-
self.icon_size = QSize(42, 42) # Icon size inside buttons
|
|
73
|
-
self.icon_size_off = QSize(24, 24) # Icon size inside buttons when unchecked
|
|
74
|
-
self.button_size = QSize(56, 56) # Button size
|
|
75
|
-
self.row_spacing = 18 # Spacing between rows
|
|
76
|
-
self.margin_size = 5 # Window margin size
|
|
77
|
-
self.progress_bar_height = 36
|
|
78
|
-
|
|
79
|
-
self.min_height = self.row_spacing + self.margin_size*2 + self.title_height *2 + self.progress_bar_height # min window height
|
|
80
|
-
self.max_height = len(name_list) * (self.button_size.height() + self.row_spacing) + self.min_height # Max window height
|
|
81
|
-
|
|
82
|
-
# Tooltip texts
|
|
83
|
-
self.tooltips = {
|
|
84
|
-
"copy": "Copy the item",
|
|
85
|
-
"link": "Link the item",
|
|
86
|
-
"change_folder": "Change the folder",
|
|
87
|
-
"auto_purge": "Auto-remove missing-image pairs",
|
|
88
|
-
"rescan": "Re-scan destination paths"
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
# Icons for the buttons
|
|
92
|
-
self.icons = {
|
|
93
|
-
"copy": [QIcon(icons_path + "copy_process.png"), QIcon(icons_path + "copy_process_off.png")],
|
|
94
|
-
"link": [QIcon(icons_path + "link.png"), QIcon(icons_path + "unlink.png")],
|
|
95
|
-
"change_folder": [QIcon(icons_path + "change_folder.png"), QIcon(icons_path + "change_folder_off.png")],
|
|
96
|
-
"auto_purge": [QIcon(icons_path + "folder_loop_cleanup.png"), QIcon(icons_path + "folder_loop_cleanup_off.png")],
|
|
97
|
-
"rescan": [QIcon(icons_path + "scan_path_loop.png"), QIcon(icons_path + "scan_path_loop_off.png")],
|
|
98
|
-
}
|
|
99
|
-
self.options_list=list(self.icons)
|
|
100
|
-
|
|
101
|
-
# Main layout
|
|
102
|
-
layout = QVBoxLayout()
|
|
103
|
-
layout.setSpacing(self.row_spacing)
|
|
104
|
-
|
|
105
|
-
# Left-aligned title
|
|
106
|
-
header_layout = QHBoxLayout()
|
|
107
|
-
header_layout.setContentsMargins(0,0,0,0)
|
|
108
|
-
header_layout.setSpacing(10)
|
|
109
|
-
|
|
110
|
-
self.header_icon = QLabel('')
|
|
111
|
-
self.header_icon.setPixmap(QPixmap(icons_path + "process_loop.png"))
|
|
112
|
-
self.header_icon.setScaledContents(True)
|
|
113
|
-
self.header_icon.setFixedSize(self.icon_size)
|
|
114
|
-
header_layout.addWidget(self.header_icon)
|
|
115
|
-
|
|
116
|
-
header_text=process_name[:48]+f"{'...' if len(process_name)>50 else ''}"
|
|
117
|
-
self.header_label = QLabel(header_text)
|
|
118
|
-
self.header_label.setFont(self.title_font)
|
|
119
|
-
self.header_label.setAlignment(Qt.AlignmentFlag.AlignLeft|Qt.AlignmentFlag.AlignVCenter)
|
|
120
|
-
self.header_label.setFixedHeight(self.title_height)
|
|
121
|
-
header_layout.addWidget(self.header_label)
|
|
122
|
-
|
|
123
|
-
layout.addLayout(header_layout)
|
|
124
|
-
|
|
125
|
-
# Left-aligned title
|
|
126
|
-
title_layout = QHBoxLayout()
|
|
127
|
-
title_layout.setContentsMargins(0,0,0,0)
|
|
128
|
-
title_layout.setSpacing(10)
|
|
129
|
-
|
|
130
|
-
"""
|
|
131
|
-
self.title_icon = QLabel('')
|
|
132
|
-
self.title_icon.setPixmap(QPixmap(icons_path + "process_loop.png"))
|
|
133
|
-
self.title_icon.setScaledContents(True)
|
|
134
|
-
self.title_icon.setFixedSize(self.icon_size)
|
|
135
|
-
title_layout.addWidget(self.title_icon)
|
|
136
|
-
"""
|
|
137
|
-
|
|
138
|
-
self.title_label = QLabel(self.title_text)
|
|
139
|
-
self.title_label.setFont(self.title_font)
|
|
140
|
-
self.title_label.setAlignment(Qt.AlignmentFlag.AlignLeft|Qt.AlignmentFlag.AlignVCenter)
|
|
141
|
-
self.title_label.setFixedHeight(self.title_height)
|
|
142
|
-
title_layout.addWidget(self.title_label)
|
|
143
|
-
|
|
144
|
-
layout.addLayout(title_layout)
|
|
145
|
-
|
|
146
|
-
# List to store button states (0, 1, 2)
|
|
147
|
-
self.button_states = [0] * len(name_list)
|
|
148
|
-
|
|
149
|
-
# Add the additional n elements
|
|
150
|
-
self.buttons_group = []
|
|
151
|
-
self.step_options = []
|
|
152
|
-
self.widgets = []
|
|
153
|
-
self.nstep=len(pixmap_list)
|
|
154
|
-
for i in range(self.nstep):
|
|
155
|
-
widget = QWidget()
|
|
156
|
-
item_layout = QHBoxLayout()
|
|
157
|
-
item_layout.setContentsMargins(0,0,0,0)
|
|
158
|
-
widget.setLayout(item_layout)
|
|
159
|
-
widget.setFixedHeight(self.button_size.height())
|
|
160
|
-
self.widgets.append(widget)
|
|
161
|
-
|
|
162
|
-
# Label with pixmap (fitted to 32x32 pixels)
|
|
163
|
-
pixmap_label = QLabel(self)
|
|
164
|
-
pixmap = QPixmap(pixmap_list[i])#.scaled(self.button_size, Qt.KeepAspectRatio, Qt.SmoothTransformation)
|
|
165
|
-
pixmap_label.setPixmap(pixmap)
|
|
166
|
-
pixmap_label.setScaledContents(True)
|
|
167
|
-
pixmap_label.setFixedSize(self.button_size)
|
|
168
|
-
item_layout.addWidget(pixmap_label)
|
|
169
|
-
|
|
170
|
-
spacer=QSpacerItem(10, 0, QSizePolicy.Minimum, QSizePolicy.Minimum)
|
|
171
|
-
item_layout.addItem(spacer)
|
|
172
|
-
|
|
173
|
-
# Label with text from the name list (larger font)
|
|
174
|
-
name_label = QLabel(name_list[i],self)
|
|
175
|
-
name_label.setFont(self.item_font)
|
|
176
|
-
name_label.setAlignment(Qt.AlignmentFlag.AlignLeft|Qt.AlignmentFlag.AlignVCenter)
|
|
177
|
-
item_layout.addWidget(name_label)
|
|
178
|
-
|
|
179
|
-
# Stretch the name label to fill the row
|
|
180
|
-
item_layout.addStretch()
|
|
181
|
-
|
|
182
|
-
# Three checkable buttons with icons
|
|
183
|
-
self.step_options.append(-1)
|
|
184
|
-
copy_button = self.create_icon_button(i, "copy")
|
|
185
|
-
link_button = self.create_icon_button(i, "link")
|
|
186
|
-
if flag_list[i]:
|
|
187
|
-
folder_button = self.create_icon_button(i, "change_folder")
|
|
188
|
-
else:
|
|
189
|
-
folder_button = QLabel(self,text='')
|
|
190
|
-
folder_button.setFixedWidth(self.button_size.width())
|
|
191
|
-
self.buttons_group.append([copy_button, link_button, folder_button])
|
|
192
|
-
|
|
193
|
-
item_layout.addWidget(copy_button)
|
|
194
|
-
item_layout.addWidget(link_button)
|
|
195
|
-
item_layout.addWidget(folder_button)
|
|
196
|
-
|
|
197
|
-
layout.addWidget(widget)
|
|
198
|
-
|
|
199
|
-
# --- Cleanup row ---
|
|
200
|
-
cleanup_spacing = 5
|
|
201
|
-
self.cleanup_widget = QWidget()
|
|
202
|
-
cw_layout = QHBoxLayout(self.cleanup_widget)
|
|
203
|
-
cw_layout.setContentsMargins(0, cleanup_spacing, 0, cleanup_spacing)
|
|
204
|
-
cw_layout.setSpacing(5)
|
|
205
|
-
|
|
206
|
-
self.cleanup_button = QPushButton(self)
|
|
207
|
-
self.cleanup_button.setCheckable(True)
|
|
208
|
-
self.cleanup_button.setChecked(False)
|
|
209
|
-
self.cleanup_button.setIcon(self.icons["auto_purge"][1])
|
|
210
|
-
self.cleanup_button.setFixedSize(self.button_size)
|
|
211
|
-
self.cleanup_button.setStyleSheet("QPushButton{border: none;}")
|
|
212
|
-
self.cleanup_button.setCursor(QCursor(Qt.PointingHandCursor))
|
|
213
|
-
self.cleanup_button.setToolTip(self.tooltips["auto_purge"])
|
|
214
|
-
self.cleanup_button.setIconSize(self.icon_size_off)
|
|
215
|
-
|
|
216
|
-
self.cleanup_label = QLabel("", self)
|
|
217
|
-
self.cleanup_label_font = parent.font()
|
|
218
|
-
self.cleanup_label_font.setPixelSize(fontPixelSize)
|
|
219
|
-
self.cleanup_label_font.setItalic(True)
|
|
220
|
-
self.cleanup_label.setFont(self.cleanup_label_font)
|
|
221
|
-
self.cleanup_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
|
|
222
|
-
|
|
223
|
-
self.cleanup_button.toggled.connect(self.on_cleanup_toggled)
|
|
224
|
-
cw_layout.addItem(QSpacerItem(10, 0, QSizePolicy.Expanding, QSizePolicy.Minimum))
|
|
225
|
-
cw_layout.addWidget(self.cleanup_label)
|
|
226
|
-
cw_layout.addWidget(self.cleanup_button)
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
# --- Rescan row ---
|
|
230
|
-
rescan_spacing = 5
|
|
231
|
-
self.rescan_widget = QWidget()
|
|
232
|
-
rw_layout = QHBoxLayout(self.rescan_widget)
|
|
233
|
-
rw_layout.setContentsMargins(0, rescan_spacing, 0, rescan_spacing)
|
|
234
|
-
rw_layout.setSpacing(5)
|
|
235
|
-
|
|
236
|
-
self.rescan_button = QPushButton(self)
|
|
237
|
-
self.rescan_button.setCheckable(True)
|
|
238
|
-
self.rescan_button.setChecked(False)
|
|
239
|
-
self.rescan_button.setIcon(self.icons["rescan"][1])
|
|
240
|
-
self.rescan_button.setFixedSize(self.button_size)
|
|
241
|
-
self.rescan_button.setStyleSheet("QPushButton{border: none;}")
|
|
242
|
-
self.rescan_button.setCursor(QCursor(Qt.PointingHandCursor))
|
|
243
|
-
self.rescan_button.setToolTip(self.tooltips["rescan"])
|
|
244
|
-
self.rescan_button.setIconSize(self.icon_size_off)
|
|
245
|
-
|
|
246
|
-
self.rescan_label = QLabel("", self)
|
|
247
|
-
self.rescan_label_font = parent.font()
|
|
248
|
-
self.rescan_label_font.setPixelSize(fontPixelSize)
|
|
249
|
-
self.rescan_label_font.setItalic(True)
|
|
250
|
-
self.rescan_label.setFont(self.rescan_label_font)
|
|
251
|
-
self.rescan_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
|
|
252
|
-
|
|
253
|
-
self.rescan_button.toggled.connect(self.on_rescan_toggled)
|
|
254
|
-
rw_layout.addItem(QSpacerItem(10, 0, QSizePolicy.Expanding, QSizePolicy.Minimum))
|
|
255
|
-
rw_layout.addWidget(self.rescan_label)
|
|
256
|
-
rw_layout.addWidget(self.rescan_button)
|
|
257
|
-
|
|
258
|
-
# --- Container for both rows ---
|
|
259
|
-
self.extra_opts_widget = QWidget()
|
|
260
|
-
eo_layout = QVBoxLayout(self.extra_opts_widget)
|
|
261
|
-
eo_layout.setContentsMargins(0, 5, 0, 50) # top=10, bottom=10
|
|
262
|
-
eo_layout.setSpacing(5)
|
|
263
|
-
eo_layout.addWidget(self.rescan_widget)
|
|
264
|
-
eo_layout.addWidget(self.cleanup_widget)
|
|
265
|
-
self.extra_opts_widget.setFixedHeight(self.button_size.height()*2+60)
|
|
266
|
-
|
|
267
|
-
self.extra_opts_widget.setVisible(False)
|
|
268
|
-
self.widgets.append(self.extra_opts_widget)
|
|
269
|
-
|
|
270
|
-
# Add container to parent layout
|
|
271
|
-
layout.addWidget(self.extra_opts_widget)
|
|
272
|
-
|
|
273
|
-
self.warnings=[False]*len(self.paths)
|
|
274
|
-
|
|
275
|
-
# Progress bar and final buttons (Cancel, Proceed)
|
|
276
|
-
progress_widget = QWidget()
|
|
277
|
-
progress_layout = QHBoxLayout()
|
|
278
|
-
progress_layout.setContentsMargins(0,0,0,0)
|
|
279
|
-
progress_layout.setSpacing(10)
|
|
280
|
-
progress_widget.setLayout(progress_layout)
|
|
281
|
-
progress_widget.setFixedHeight(self.progress_bar_height)
|
|
282
|
-
self.progress_bar = QProgressBar(self)
|
|
283
|
-
self.progress_bar.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
|
|
284
|
-
self.progress_bar.setVisible(False) # Hidden initially
|
|
285
|
-
self.title_label.adjustSize()
|
|
286
|
-
self.progress_bar.setFixedWidth(self.title_label.width())
|
|
287
|
-
progress_layout.addWidget(self.progress_bar)
|
|
288
|
-
|
|
289
|
-
# Spacer to extend before the buttons
|
|
290
|
-
progress_layout.addStretch()
|
|
291
|
-
|
|
292
|
-
cancel_button_text = "Cancel"
|
|
293
|
-
self.proceed_button_text = "Proceed"
|
|
294
|
-
self.stop_button_text = "Stop"
|
|
295
|
-
|
|
296
|
-
cancel_button = QPushButton(cancel_button_text,self)
|
|
297
|
-
cancel_button.setFixedHeight(self.progress_bar_height)
|
|
298
|
-
cancel_button.setFont(self.button_font)
|
|
299
|
-
cancel_button.clicked.connect(self.cancel) # Closes the dialog without doing anything
|
|
300
|
-
progress_layout.addWidget(cancel_button)
|
|
301
|
-
|
|
302
|
-
self.proceed_button = QPushButton(self.proceed_button_text,self)
|
|
303
|
-
self.proceed_button.setFixedHeight(self.progress_bar_height)
|
|
304
|
-
self.proceed_button.setFont(self.button_font)
|
|
305
|
-
self.proceed_button.clicked.connect(self.on_proceed)
|
|
306
|
-
progress_layout.addWidget(self.proceed_button)
|
|
307
|
-
|
|
308
|
-
layout.addWidget(progress_widget)
|
|
309
|
-
self.setLayout(layout)
|
|
310
|
-
|
|
311
|
-
# Set window maximum height and margins
|
|
312
|
-
self.on_cleanup_toggled(self.cleanup_button.isChecked())
|
|
313
|
-
self.on_rescan_toggled(self.rescan_button.isChecked())
|
|
314
|
-
self.update_extraopts_visibility(FlagAnimation=False)
|
|
315
|
-
self.setFixedHeight(self.max_height)
|
|
316
|
-
self.setContentsMargins(self.margin_size*2, self.margin_size, self.margin_size*2, self.margin_size)
|
|
317
|
-
self.setMinimumWidth(640)
|
|
318
|
-
self.setMaximumWidth(800)
|
|
319
|
-
|
|
320
|
-
# Timer for progress
|
|
321
|
-
self.iteration = 0
|
|
322
|
-
self.timer = QTimer(self)
|
|
323
|
-
self.timer.timeout.connect(self.update_progress)
|
|
324
|
-
self.timer.setSingleShot(True)
|
|
325
|
-
self.loop_running = False
|
|
326
|
-
|
|
327
|
-
self.animation=None
|
|
328
|
-
|
|
329
|
-
def create_icon_button(self, index, button_type):
|
|
330
|
-
"""Create a checkable button with icon based on its type (copy, link, change_folder)."""
|
|
331
|
-
button = QPushButton(self)
|
|
332
|
-
button.setCheckable(True)
|
|
333
|
-
button.setChecked((button_type=='change_folder' and index==self.nstep-1) or (button_type=='copy' and index<self.nstep-1))
|
|
334
|
-
button.setIcon(self.icons[button_type][int(not button.isChecked())]) # Set initial 'off' icon
|
|
335
|
-
button.setFixedSize(self.button_size) # Set button size to 32x32 pixels
|
|
336
|
-
button.setStyleSheet("QPushButton{border: none;}") # Remove button borders
|
|
337
|
-
button.setCursor(QCursor(Qt.PointingHandCursor)) # Set cursor to pointing hand on hover
|
|
338
|
-
button.setToolTip(self.tooltips[button_type]) # Set tooltip
|
|
339
|
-
button.clicked.connect(lambda: self.update_buttons(index, button_type))
|
|
340
|
-
if button.isChecked():
|
|
341
|
-
self.step_options[index]=self.options_list.index(button_type)
|
|
342
|
-
button.setIconSize(self.icon_size)
|
|
343
|
-
else:
|
|
344
|
-
button.setIconSize(self.icon_size_off) # Set icon size to 28x28 pixels
|
|
345
|
-
return button
|
|
346
|
-
|
|
347
|
-
def update_buttons(self, index, button_type):
|
|
348
|
-
"""Update the icons and states of the buttons, ensuring only one is checked at a time."""
|
|
349
|
-
for k, button in enumerate(self.buttons_group[index]):
|
|
350
|
-
if not isinstance(button,QPushButton): continue
|
|
351
|
-
if button_type!=self.options_list[k]:
|
|
352
|
-
button.setChecked(False) # Uncheck all buttons
|
|
353
|
-
button.setIconSize(self.icon_size_off)
|
|
354
|
-
button.setIcon(self.icons[self.options_list[k]][1])
|
|
355
|
-
else:
|
|
356
|
-
button.setChecked(True)
|
|
357
|
-
button.setIconSize(self.icon_size)
|
|
358
|
-
button.setIcon(self.icons[self.options_list[k]][0]) # Checked state icon
|
|
359
|
-
self.step_options[index]=k
|
|
360
|
-
self.update_extraopts_visibility()
|
|
361
|
-
|
|
362
|
-
def update_extraopts_visibility(self,FlagAnimation=True):
|
|
363
|
-
"""Show extra row if any row has 'change_folder' checked; hide otherwise."""
|
|
364
|
-
try:
|
|
365
|
-
idx_change = self.options_list.index("change_folder")
|
|
366
|
-
except ValueError:
|
|
367
|
-
idx_change = -1
|
|
368
|
-
|
|
369
|
-
show = (idx_change >= 0) and any(
|
|
370
|
-
(opt == idx_change) for opt in self.step_options if isinstance(opt, int)
|
|
371
|
-
)
|
|
372
|
-
|
|
373
|
-
if self.extra_opts_widget.isVisible() != show:
|
|
374
|
-
self.extra_opts_widget.setVisible(show)
|
|
375
|
-
extra_opts_widget = self.extra_opts_widget.height()
|
|
376
|
-
maxHeight = self.nstep * (self.button_size.height() + self.row_spacing) + int(show)*extra_opts_widget + self.min_height
|
|
377
|
-
if FlagAnimation:
|
|
378
|
-
self.window_resize(maxHeight)
|
|
379
|
-
self.max_height = maxHeight
|
|
380
|
-
|
|
381
|
-
def on_cleanup_toggled(self, checked: bool):
|
|
382
|
-
"""Sync icon/size and label text when the extra toggle is changed."""
|
|
383
|
-
self.cleanup_button.setIcon(self.icons["auto_purge"][0 if checked else 1])
|
|
384
|
-
self.cleanup_button.setIconSize(self.icon_size if checked else self.icon_size_off)
|
|
385
|
-
self.cleanup_label.setText(
|
|
386
|
-
"Image-missing pairs will be removed automatically."
|
|
387
|
-
if checked else
|
|
388
|
-
"Image-missing pairs are kept; batch copy may raise warnings."
|
|
389
|
-
)
|
|
390
|
-
self.cleanup_label.setStyleSheet("color: none;" if checked else "color: rgb(125,125,125);")
|
|
391
|
-
self.cleanup_enabled = bool(checked)
|
|
392
|
-
|
|
393
|
-
def on_rescan_toggled(self, checked: bool):
|
|
394
|
-
"""Sync icon/size and label text when the rescan toggle is changed."""
|
|
395
|
-
self.rescan_button.setIcon(self.icons["rescan"][0 if checked else 1])
|
|
396
|
-
self.rescan_button.setIconSize(self.icon_size if checked else self.icon_size_off)
|
|
397
|
-
|
|
398
|
-
self.rescan_label.setText(
|
|
399
|
-
"The input folder will be re-scanned; input image list may differ or trigger warnings."
|
|
400
|
-
if checked else
|
|
401
|
-
"No folder scan; images identified in the master process will be used."
|
|
402
|
-
)
|
|
403
|
-
self.rescan_label.setStyleSheet("color: none;" if checked else "color: rgb(125,125,125);")
|
|
404
|
-
self.rescan_enabled = bool(checked)
|
|
405
|
-
|
|
406
|
-
def on_proceed(self):
|
|
407
|
-
"""Start or stop the progress loop depending on the button state."""
|
|
408
|
-
if not self.loop_running:
|
|
409
|
-
self.start_progress()
|
|
410
|
-
else:
|
|
411
|
-
self.stop_progress()
|
|
412
|
-
|
|
413
|
-
def start_progress(self):
|
|
414
|
-
"""Start the progress and update button text to 'Stop'."""
|
|
415
|
-
self.loop_running = True
|
|
416
|
-
self.title_label.setText('Preparing copy...')
|
|
417
|
-
self.title_label.setFont(self.item_font)
|
|
418
|
-
self.proceed_button.setText(self.stop_button_text)
|
|
419
|
-
self.proceed_button.setVisible(False)
|
|
420
|
-
self.animate_window_resize()
|
|
421
|
-
self.progress_bar.setVisible(True) # Show the progress bar when Proceed is clicked
|
|
422
|
-
self.progress_bar.setMaximum(self.nfolders)
|
|
423
|
-
|
|
424
|
-
def stop_progress(self):
|
|
425
|
-
"""Stop the progress and update button text to 'Proceed'."""
|
|
426
|
-
self.loop_running = False
|
|
427
|
-
self.proceed_button.setText(self.proceed_button_text)
|
|
428
|
-
self.timer.stop()
|
|
429
|
-
|
|
430
|
-
def update_progress(self):
|
|
431
|
-
"""Update the progress bar on each timer tick."""
|
|
432
|
-
if self.iteration < self.progress_bar.maximum():
|
|
433
|
-
timesleep(time_sleep_loop)
|
|
434
|
-
title_text=self.paths[self.iteration][:48]+f"{'...' if len(self.paths[self.iteration])>50 else ''}"
|
|
435
|
-
self.title_label.setText(title_text)
|
|
436
|
-
self.warnings[self.iteration]=self.func(self.iteration,self.step_options,self.cleanup_enabled,self.rescan_enabled)
|
|
437
|
-
self.progress_bar.setValue(self.iteration)
|
|
438
|
-
self.iteration += 1
|
|
439
|
-
if self.loop_running: self.timer.start(0)
|
|
440
|
-
else:
|
|
441
|
-
self.progress_bar.setValue(self.progress_bar.maximum())
|
|
442
|
-
self.stop_progress()
|
|
443
|
-
self.hide()
|
|
444
|
-
self.show_batch_issues_dialog()
|
|
445
|
-
if not self.animation: self.done(0) # Closes the dialog when complete
|
|
446
|
-
|
|
447
|
-
def cancel(self):
|
|
448
|
-
if self.loop_running: self.stop_progress()
|
|
449
|
-
if not self.animation: self.done(0) # Closes the dialog when complete
|
|
450
|
-
|
|
451
|
-
def animate_window_resize(self):
|
|
452
|
-
for w in self.widgets:
|
|
453
|
-
w:QWidget
|
|
454
|
-
w.setVisible(False)
|
|
455
|
-
self.animation = QVariantAnimation(self)
|
|
456
|
-
self.animation.valueChanged.connect(self.window_resize)
|
|
457
|
-
self.animation.setDuration(300)
|
|
458
|
-
self.animation.setStartValue(self.max_height)
|
|
459
|
-
self.animation.setEndValue(self.min_height)
|
|
460
|
-
self.animation.finished.connect(self.finishedAnimation)
|
|
461
|
-
self.animation.start()
|
|
462
|
-
|
|
463
|
-
def finishedAnimation(self):
|
|
464
|
-
self.animation=None
|
|
465
|
-
self.timer.start(0) # Start or resume the timer
|
|
466
|
-
|
|
467
|
-
def window_resize(self, h):
|
|
468
|
-
self.setFixedHeight(h)
|
|
469
|
-
|
|
470
|
-
def show_batch_issues_dialog(self):
|
|
471
|
-
"""Show a summary dialog listing all destination folders with warnings=True."""
|
|
472
|
-
issues=[(i,p) for i,p in enumerate(self.paths) if i<len(self.warnings) and self.warnings[i]]
|
|
473
|
-
if not issues: return
|
|
474
|
-
|
|
475
|
-
dlg=QDialog(self); dlg.setWindowTitle("Batch copy issues")
|
|
476
|
-
font=dlg.font()
|
|
477
|
-
font.setPixelSize(fontPixelSize)
|
|
478
|
-
dlg.setFont(font)
|
|
479
|
-
lay=QVBoxLayout(dlg)
|
|
480
|
-
|
|
481
|
-
# --- Warning header with icon ---
|
|
482
|
-
icon_width=96
|
|
483
|
-
warn_layout = QHBoxLayout()
|
|
484
|
-
warn_icon = QLabel()
|
|
485
|
-
warn_pix = QPixmap(icons_path + "warning.png").scaled(
|
|
486
|
-
icon_width, icon_width, Qt.KeepAspectRatio, Qt.SmoothTransformation
|
|
487
|
-
)
|
|
488
|
-
warn_icon.setPixmap(warn_pix)
|
|
489
|
-
warn_icon.setScaledContents(False)
|
|
490
|
-
#warn_icon.setAlignment(Qt.AlignTop)
|
|
491
|
-
warn_icon.setFixedWidth(icon_width)
|
|
492
|
-
|
|
493
|
-
head = QLabel(
|
|
494
|
-
f"{len(issues)} destination folder{'s' if len(issues)>1 else ''} could require attention: "
|
|
495
|
-
"potential warnings during batch copy or image-set mismatch!\n\n"
|
|
496
|
-
"Please note that the interface may not explicitly flag these cases. "
|
|
497
|
-
"Copy the paths below if you wish to inspect the corresponding processes.\n",
|
|
498
|
-
dlg
|
|
499
|
-
)
|
|
500
|
-
head.setWordWrap(True)
|
|
501
|
-
|
|
502
|
-
warn_layout.addWidget(warn_icon)
|
|
503
|
-
warn_layout.addWidget(head)
|
|
504
|
-
lay.addLayout(warn_layout)
|
|
505
|
-
|
|
506
|
-
tree=QTreeWidget(dlg); tree.setHeaderHidden(True)
|
|
507
|
-
tree.header().setStretchLastSection(True)
|
|
508
|
-
for _,p in issues:
|
|
509
|
-
item=QTreeWidgetItem([p])
|
|
510
|
-
item.setToolTip(0,p)
|
|
511
|
-
tree.addTopLevelItem(item)
|
|
512
|
-
lay.addWidget(tree)
|
|
513
|
-
|
|
514
|
-
# actions
|
|
515
|
-
btns=QHBoxLayout()
|
|
516
|
-
def _copy():
|
|
517
|
-
txt="\n".join(p for _,p in issues)
|
|
518
|
-
QApplication.clipboard().setText(txt)
|
|
519
|
-
copy_btn=QPushButton("Copy paths"); copy_btn.clicked.connect(_copy)
|
|
520
|
-
close_btn=QPushButton("Close"); close_btn.clicked.connect(dlg.accept)
|
|
521
|
-
btns.addWidget(copy_btn); btns.addStretch(1); btns.addWidget(close_btn)
|
|
522
|
-
lay.addLayout(btns)
|
|
523
|
-
|
|
524
|
-
dlg.resize(720, 360); dlg.exec()
|
|
525
|
-
|
|
526
|
-
if __name__ == "__main__":
|
|
527
|
-
import random
|
|
528
|
-
import string
|
|
529
|
-
|
|
530
|
-
# Function to generate a random string
|
|
531
|
-
def generate_random_string(length):
|
|
532
|
-
letters = string.ascii_letters # Includes uppercase and lowercase letters
|
|
533
|
-
return ''.join(random.choice(letters) for i in range(length))
|
|
534
|
-
|
|
535
|
-
# Number of strings and length of each string
|
|
536
|
-
num_strings = 10 # Number of strings in the list
|
|
537
|
-
string_length = 25 # Length of each string
|
|
538
|
-
|
|
539
|
-
# Generate the list of random strings
|
|
540
|
-
random_strings_list = [generate_random_string(string_length) for _ in range(num_strings)]
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
app = QApplication(sys.argv)
|
|
544
|
-
app.setStyle('Fusion')
|
|
545
|
-
|
|
546
|
-
time_sleep_loop=0.01
|
|
547
|
-
|
|
548
|
-
print(choose_directories())
|
|
549
|
-
|
|
550
|
-
# Example lists
|
|
551
|
-
pixmap_list = [icons_path + "cal_step.png",
|
|
552
|
-
icons_path + "min_step.png",
|
|
553
|
-
icons_path + "disp_step.png",
|
|
554
|
-
icons_path + "piv_step.png"]
|
|
555
|
-
flag_list = [False,True,True,True]
|
|
556
|
-
name_list = ["Camera calibration",
|
|
557
|
-
"Image pre-processing",
|
|
558
|
-
"Disparity correction",
|
|
559
|
-
"Stereoscopic PIV analysis"]
|
|
560
|
-
|
|
561
|
-
dialog = FolderLoopDialog(pixmap_list, name_list, flag_list, paths=random_strings_list, process_name='Stereoscopic PIV process 1')
|
|
562
|
-
sys.exit(dialog.exec())
|
|
13
|
+
|
|
14
|
+
time_sleep_loop=0
|
|
15
|
+
|
|
16
|
+
def choose_directories(base:Path = Path('.'),parent=None) -> Optional[List[str]]:
|
|
17
|
+
"""
|
|
18
|
+
Open a dialogue to select multiple directories
|
|
19
|
+
Args:
|
|
20
|
+
base (Path): Starting directory to show when opening dialogue
|
|
21
|
+
Returns:
|
|
22
|
+
List[str]: List of paths that were selected, ``None`` if "cancel" selected"
|
|
23
|
+
References:
|
|
24
|
+
Mildly adapted from https://stackoverflow.com/a/28548773
|
|
25
|
+
to use outside an exising Qt Application
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
file_dialog = QFileDialog(parent)
|
|
29
|
+
file_dialog.setWindowTitle("Select folders for process loop")
|
|
30
|
+
file_dialog.setWindowIcon(QIcon(icons_path + "process_loop.png"))
|
|
31
|
+
file_dialog.setWindowIconText("Select folders for process loop")
|
|
32
|
+
file_dialog.setOption(QFileDialog.Option.DontUseNativeDialog, True)
|
|
33
|
+
file_dialog.setFileMode(QFileDialog.Directory)
|
|
34
|
+
for widget_type in (QListView, QTreeView):
|
|
35
|
+
for view in file_dialog.findChildren(widget_type):
|
|
36
|
+
if isinstance(view.model(), QFileSystemModel):
|
|
37
|
+
view.setSelectionMode(
|
|
38
|
+
QAbstractItemView.ExtendedSelection)
|
|
39
|
+
|
|
40
|
+
paths=[]
|
|
41
|
+
if file_dialog.exec():
|
|
42
|
+
paths = file_dialog.selectedFiles()
|
|
43
|
+
return paths
|
|
44
|
+
|
|
45
|
+
class FolderLoopDialog(QDialog):
|
|
46
|
+
def __init__(self, pixmap_list, name_list, flag_list, parent=None, func=lambda it, opt: print(f"Iteration {it}"), paths=[], process_name = 'Process', *args, **kwargs):
|
|
47
|
+
super().__init__(parent=parent, *args, **kwargs)
|
|
48
|
+
|
|
49
|
+
self.func=func
|
|
50
|
+
self.nfolders=len(paths)
|
|
51
|
+
self.paths=paths
|
|
52
|
+
|
|
53
|
+
# Variables for easy customization
|
|
54
|
+
self.setWindowTitle("Process loop over folders")
|
|
55
|
+
self.setWindowIcon(QIcon(icons_path + "process_loop.png"))
|
|
56
|
+
self.setWindowIconText("Process loop over folders")
|
|
57
|
+
self.title_text = "Configure each step of the process"
|
|
58
|
+
if parent is None:
|
|
59
|
+
self.title_font = QFont(fontName, fontPixelSize+12, QFont.Bold)
|
|
60
|
+
self.item_font = QFont(fontName, fontPixelSize+8)
|
|
61
|
+
self.button_font = QFont(fontName, fontPixelSize+2)
|
|
62
|
+
else:
|
|
63
|
+
self.title_font:QFont = parent.font()
|
|
64
|
+
self.title_font.setBold(True)
|
|
65
|
+
self.title_font.setPixelSize(fontPixelSize+12)
|
|
66
|
+
self.item_font:QFont = parent.font()
|
|
67
|
+
self.item_font = parent.font()
|
|
68
|
+
self.item_font.setPixelSize(fontPixelSize+8)
|
|
69
|
+
self.button_font: QFont = parent.font()
|
|
70
|
+
self.button_font.setPixelSize(fontPixelSize+2)
|
|
71
|
+
self.title_height = 48
|
|
72
|
+
self.icon_size = QSize(42, 42) # Icon size inside buttons
|
|
73
|
+
self.icon_size_off = QSize(24, 24) # Icon size inside buttons when unchecked
|
|
74
|
+
self.button_size = QSize(56, 56) # Button size
|
|
75
|
+
self.row_spacing = 18 # Spacing between rows
|
|
76
|
+
self.margin_size = 5 # Window margin size
|
|
77
|
+
self.progress_bar_height = 36
|
|
78
|
+
|
|
79
|
+
self.min_height = self.row_spacing + self.margin_size*2 + self.title_height *2 + self.progress_bar_height # min window height
|
|
80
|
+
self.max_height = len(name_list) * (self.button_size.height() + self.row_spacing) + self.min_height # Max window height
|
|
81
|
+
|
|
82
|
+
# Tooltip texts
|
|
83
|
+
self.tooltips = {
|
|
84
|
+
"copy": "Copy the item",
|
|
85
|
+
"link": "Link the item",
|
|
86
|
+
"change_folder": "Change the folder",
|
|
87
|
+
"auto_purge": "Auto-remove missing-image pairs",
|
|
88
|
+
"rescan": "Re-scan destination paths"
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
# Icons for the buttons
|
|
92
|
+
self.icons = {
|
|
93
|
+
"copy": [QIcon(icons_path + "copy_process.png"), QIcon(icons_path + "copy_process_off.png")],
|
|
94
|
+
"link": [QIcon(icons_path + "link.png"), QIcon(icons_path + "unlink.png")],
|
|
95
|
+
"change_folder": [QIcon(icons_path + "change_folder.png"), QIcon(icons_path + "change_folder_off.png")],
|
|
96
|
+
"auto_purge": [QIcon(icons_path + "folder_loop_cleanup.png"), QIcon(icons_path + "folder_loop_cleanup_off.png")],
|
|
97
|
+
"rescan": [QIcon(icons_path + "scan_path_loop.png"), QIcon(icons_path + "scan_path_loop_off.png")],
|
|
98
|
+
}
|
|
99
|
+
self.options_list=list(self.icons)
|
|
100
|
+
|
|
101
|
+
# Main layout
|
|
102
|
+
layout = QVBoxLayout()
|
|
103
|
+
layout.setSpacing(self.row_spacing)
|
|
104
|
+
|
|
105
|
+
# Left-aligned title
|
|
106
|
+
header_layout = QHBoxLayout()
|
|
107
|
+
header_layout.setContentsMargins(0,0,0,0)
|
|
108
|
+
header_layout.setSpacing(10)
|
|
109
|
+
|
|
110
|
+
self.header_icon = QLabel('')
|
|
111
|
+
self.header_icon.setPixmap(QPixmap(icons_path + "process_loop.png"))
|
|
112
|
+
self.header_icon.setScaledContents(True)
|
|
113
|
+
self.header_icon.setFixedSize(self.icon_size)
|
|
114
|
+
header_layout.addWidget(self.header_icon)
|
|
115
|
+
|
|
116
|
+
header_text=process_name[:48]+f"{'...' if len(process_name)>50 else ''}"
|
|
117
|
+
self.header_label = QLabel(header_text)
|
|
118
|
+
self.header_label.setFont(self.title_font)
|
|
119
|
+
self.header_label.setAlignment(Qt.AlignmentFlag.AlignLeft|Qt.AlignmentFlag.AlignVCenter)
|
|
120
|
+
self.header_label.setFixedHeight(self.title_height)
|
|
121
|
+
header_layout.addWidget(self.header_label)
|
|
122
|
+
|
|
123
|
+
layout.addLayout(header_layout)
|
|
124
|
+
|
|
125
|
+
# Left-aligned title
|
|
126
|
+
title_layout = QHBoxLayout()
|
|
127
|
+
title_layout.setContentsMargins(0,0,0,0)
|
|
128
|
+
title_layout.setSpacing(10)
|
|
129
|
+
|
|
130
|
+
"""
|
|
131
|
+
self.title_icon = QLabel('')
|
|
132
|
+
self.title_icon.setPixmap(QPixmap(icons_path + "process_loop.png"))
|
|
133
|
+
self.title_icon.setScaledContents(True)
|
|
134
|
+
self.title_icon.setFixedSize(self.icon_size)
|
|
135
|
+
title_layout.addWidget(self.title_icon)
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
self.title_label = QLabel(self.title_text)
|
|
139
|
+
self.title_label.setFont(self.title_font)
|
|
140
|
+
self.title_label.setAlignment(Qt.AlignmentFlag.AlignLeft|Qt.AlignmentFlag.AlignVCenter)
|
|
141
|
+
self.title_label.setFixedHeight(self.title_height)
|
|
142
|
+
title_layout.addWidget(self.title_label)
|
|
143
|
+
|
|
144
|
+
layout.addLayout(title_layout)
|
|
145
|
+
|
|
146
|
+
# List to store button states (0, 1, 2)
|
|
147
|
+
self.button_states = [0] * len(name_list)
|
|
148
|
+
|
|
149
|
+
# Add the additional n elements
|
|
150
|
+
self.buttons_group = []
|
|
151
|
+
self.step_options = []
|
|
152
|
+
self.widgets = []
|
|
153
|
+
self.nstep=len(pixmap_list)
|
|
154
|
+
for i in range(self.nstep):
|
|
155
|
+
widget = QWidget()
|
|
156
|
+
item_layout = QHBoxLayout()
|
|
157
|
+
item_layout.setContentsMargins(0,0,0,0)
|
|
158
|
+
widget.setLayout(item_layout)
|
|
159
|
+
widget.setFixedHeight(self.button_size.height())
|
|
160
|
+
self.widgets.append(widget)
|
|
161
|
+
|
|
162
|
+
# Label with pixmap (fitted to 32x32 pixels)
|
|
163
|
+
pixmap_label = QLabel(self)
|
|
164
|
+
pixmap = QPixmap(pixmap_list[i])#.scaled(self.button_size, Qt.KeepAspectRatio, Qt.SmoothTransformation)
|
|
165
|
+
pixmap_label.setPixmap(pixmap)
|
|
166
|
+
pixmap_label.setScaledContents(True)
|
|
167
|
+
pixmap_label.setFixedSize(self.button_size)
|
|
168
|
+
item_layout.addWidget(pixmap_label)
|
|
169
|
+
|
|
170
|
+
spacer=QSpacerItem(10, 0, QSizePolicy.Minimum, QSizePolicy.Minimum)
|
|
171
|
+
item_layout.addItem(spacer)
|
|
172
|
+
|
|
173
|
+
# Label with text from the name list (larger font)
|
|
174
|
+
name_label = QLabel(name_list[i],self)
|
|
175
|
+
name_label.setFont(self.item_font)
|
|
176
|
+
name_label.setAlignment(Qt.AlignmentFlag.AlignLeft|Qt.AlignmentFlag.AlignVCenter)
|
|
177
|
+
item_layout.addWidget(name_label)
|
|
178
|
+
|
|
179
|
+
# Stretch the name label to fill the row
|
|
180
|
+
item_layout.addStretch()
|
|
181
|
+
|
|
182
|
+
# Three checkable buttons with icons
|
|
183
|
+
self.step_options.append(-1)
|
|
184
|
+
copy_button = self.create_icon_button(i, "copy")
|
|
185
|
+
link_button = self.create_icon_button(i, "link")
|
|
186
|
+
if flag_list[i]:
|
|
187
|
+
folder_button = self.create_icon_button(i, "change_folder")
|
|
188
|
+
else:
|
|
189
|
+
folder_button = QLabel(self,text='')
|
|
190
|
+
folder_button.setFixedWidth(self.button_size.width())
|
|
191
|
+
self.buttons_group.append([copy_button, link_button, folder_button])
|
|
192
|
+
|
|
193
|
+
item_layout.addWidget(copy_button)
|
|
194
|
+
item_layout.addWidget(link_button)
|
|
195
|
+
item_layout.addWidget(folder_button)
|
|
196
|
+
|
|
197
|
+
layout.addWidget(widget)
|
|
198
|
+
|
|
199
|
+
# --- Cleanup row ---
|
|
200
|
+
cleanup_spacing = 5
|
|
201
|
+
self.cleanup_widget = QWidget()
|
|
202
|
+
cw_layout = QHBoxLayout(self.cleanup_widget)
|
|
203
|
+
cw_layout.setContentsMargins(0, cleanup_spacing, 0, cleanup_spacing)
|
|
204
|
+
cw_layout.setSpacing(5)
|
|
205
|
+
|
|
206
|
+
self.cleanup_button = QPushButton(self)
|
|
207
|
+
self.cleanup_button.setCheckable(True)
|
|
208
|
+
self.cleanup_button.setChecked(False)
|
|
209
|
+
self.cleanup_button.setIcon(self.icons["auto_purge"][1])
|
|
210
|
+
self.cleanup_button.setFixedSize(self.button_size)
|
|
211
|
+
self.cleanup_button.setStyleSheet("QPushButton{border: none;}")
|
|
212
|
+
self.cleanup_button.setCursor(QCursor(Qt.PointingHandCursor))
|
|
213
|
+
self.cleanup_button.setToolTip(self.tooltips["auto_purge"])
|
|
214
|
+
self.cleanup_button.setIconSize(self.icon_size_off)
|
|
215
|
+
|
|
216
|
+
self.cleanup_label = QLabel("", self)
|
|
217
|
+
self.cleanup_label_font = parent.font()
|
|
218
|
+
self.cleanup_label_font.setPixelSize(fontPixelSize)
|
|
219
|
+
self.cleanup_label_font.setItalic(True)
|
|
220
|
+
self.cleanup_label.setFont(self.cleanup_label_font)
|
|
221
|
+
self.cleanup_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
|
|
222
|
+
|
|
223
|
+
self.cleanup_button.toggled.connect(self.on_cleanup_toggled)
|
|
224
|
+
cw_layout.addItem(QSpacerItem(10, 0, QSizePolicy.Expanding, QSizePolicy.Minimum))
|
|
225
|
+
cw_layout.addWidget(self.cleanup_label)
|
|
226
|
+
cw_layout.addWidget(self.cleanup_button)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
# --- Rescan row ---
|
|
230
|
+
rescan_spacing = 5
|
|
231
|
+
self.rescan_widget = QWidget()
|
|
232
|
+
rw_layout = QHBoxLayout(self.rescan_widget)
|
|
233
|
+
rw_layout.setContentsMargins(0, rescan_spacing, 0, rescan_spacing)
|
|
234
|
+
rw_layout.setSpacing(5)
|
|
235
|
+
|
|
236
|
+
self.rescan_button = QPushButton(self)
|
|
237
|
+
self.rescan_button.setCheckable(True)
|
|
238
|
+
self.rescan_button.setChecked(False)
|
|
239
|
+
self.rescan_button.setIcon(self.icons["rescan"][1])
|
|
240
|
+
self.rescan_button.setFixedSize(self.button_size)
|
|
241
|
+
self.rescan_button.setStyleSheet("QPushButton{border: none;}")
|
|
242
|
+
self.rescan_button.setCursor(QCursor(Qt.PointingHandCursor))
|
|
243
|
+
self.rescan_button.setToolTip(self.tooltips["rescan"])
|
|
244
|
+
self.rescan_button.setIconSize(self.icon_size_off)
|
|
245
|
+
|
|
246
|
+
self.rescan_label = QLabel("", self)
|
|
247
|
+
self.rescan_label_font = parent.font()
|
|
248
|
+
self.rescan_label_font.setPixelSize(fontPixelSize)
|
|
249
|
+
self.rescan_label_font.setItalic(True)
|
|
250
|
+
self.rescan_label.setFont(self.rescan_label_font)
|
|
251
|
+
self.rescan_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
|
|
252
|
+
|
|
253
|
+
self.rescan_button.toggled.connect(self.on_rescan_toggled)
|
|
254
|
+
rw_layout.addItem(QSpacerItem(10, 0, QSizePolicy.Expanding, QSizePolicy.Minimum))
|
|
255
|
+
rw_layout.addWidget(self.rescan_label)
|
|
256
|
+
rw_layout.addWidget(self.rescan_button)
|
|
257
|
+
|
|
258
|
+
# --- Container for both rows ---
|
|
259
|
+
self.extra_opts_widget = QWidget()
|
|
260
|
+
eo_layout = QVBoxLayout(self.extra_opts_widget)
|
|
261
|
+
eo_layout.setContentsMargins(0, 5, 0, 50) # top=10, bottom=10
|
|
262
|
+
eo_layout.setSpacing(5)
|
|
263
|
+
eo_layout.addWidget(self.rescan_widget)
|
|
264
|
+
eo_layout.addWidget(self.cleanup_widget)
|
|
265
|
+
self.extra_opts_widget.setFixedHeight(self.button_size.height()*2+60)
|
|
266
|
+
|
|
267
|
+
self.extra_opts_widget.setVisible(False)
|
|
268
|
+
self.widgets.append(self.extra_opts_widget)
|
|
269
|
+
|
|
270
|
+
# Add container to parent layout
|
|
271
|
+
layout.addWidget(self.extra_opts_widget)
|
|
272
|
+
|
|
273
|
+
self.warnings=[False]*len(self.paths)
|
|
274
|
+
|
|
275
|
+
# Progress bar and final buttons (Cancel, Proceed)
|
|
276
|
+
progress_widget = QWidget()
|
|
277
|
+
progress_layout = QHBoxLayout()
|
|
278
|
+
progress_layout.setContentsMargins(0,0,0,0)
|
|
279
|
+
progress_layout.setSpacing(10)
|
|
280
|
+
progress_widget.setLayout(progress_layout)
|
|
281
|
+
progress_widget.setFixedHeight(self.progress_bar_height)
|
|
282
|
+
self.progress_bar = QProgressBar(self)
|
|
283
|
+
self.progress_bar.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
|
|
284
|
+
self.progress_bar.setVisible(False) # Hidden initially
|
|
285
|
+
self.title_label.adjustSize()
|
|
286
|
+
self.progress_bar.setFixedWidth(self.title_label.width())
|
|
287
|
+
progress_layout.addWidget(self.progress_bar)
|
|
288
|
+
|
|
289
|
+
# Spacer to extend before the buttons
|
|
290
|
+
progress_layout.addStretch()
|
|
291
|
+
|
|
292
|
+
cancel_button_text = "Cancel"
|
|
293
|
+
self.proceed_button_text = "Proceed"
|
|
294
|
+
self.stop_button_text = "Stop"
|
|
295
|
+
|
|
296
|
+
cancel_button = QPushButton(cancel_button_text,self)
|
|
297
|
+
cancel_button.setFixedHeight(self.progress_bar_height)
|
|
298
|
+
cancel_button.setFont(self.button_font)
|
|
299
|
+
cancel_button.clicked.connect(self.cancel) # Closes the dialog without doing anything
|
|
300
|
+
progress_layout.addWidget(cancel_button)
|
|
301
|
+
|
|
302
|
+
self.proceed_button = QPushButton(self.proceed_button_text,self)
|
|
303
|
+
self.proceed_button.setFixedHeight(self.progress_bar_height)
|
|
304
|
+
self.proceed_button.setFont(self.button_font)
|
|
305
|
+
self.proceed_button.clicked.connect(self.on_proceed)
|
|
306
|
+
progress_layout.addWidget(self.proceed_button)
|
|
307
|
+
|
|
308
|
+
layout.addWidget(progress_widget)
|
|
309
|
+
self.setLayout(layout)
|
|
310
|
+
|
|
311
|
+
# Set window maximum height and margins
|
|
312
|
+
self.on_cleanup_toggled(self.cleanup_button.isChecked())
|
|
313
|
+
self.on_rescan_toggled(self.rescan_button.isChecked())
|
|
314
|
+
self.update_extraopts_visibility(FlagAnimation=False)
|
|
315
|
+
self.setFixedHeight(self.max_height)
|
|
316
|
+
self.setContentsMargins(self.margin_size*2, self.margin_size, self.margin_size*2, self.margin_size)
|
|
317
|
+
self.setMinimumWidth(640)
|
|
318
|
+
self.setMaximumWidth(800)
|
|
319
|
+
|
|
320
|
+
# Timer for progress
|
|
321
|
+
self.iteration = 0
|
|
322
|
+
self.timer = QTimer(self)
|
|
323
|
+
self.timer.timeout.connect(self.update_progress)
|
|
324
|
+
self.timer.setSingleShot(True)
|
|
325
|
+
self.loop_running = False
|
|
326
|
+
|
|
327
|
+
self.animation=None
|
|
328
|
+
|
|
329
|
+
def create_icon_button(self, index, button_type):
|
|
330
|
+
"""Create a checkable button with icon based on its type (copy, link, change_folder)."""
|
|
331
|
+
button = QPushButton(self)
|
|
332
|
+
button.setCheckable(True)
|
|
333
|
+
button.setChecked((button_type=='change_folder' and index==self.nstep-1) or (button_type=='copy' and index<self.nstep-1))
|
|
334
|
+
button.setIcon(self.icons[button_type][int(not button.isChecked())]) # Set initial 'off' icon
|
|
335
|
+
button.setFixedSize(self.button_size) # Set button size to 32x32 pixels
|
|
336
|
+
button.setStyleSheet("QPushButton{border: none;}") # Remove button borders
|
|
337
|
+
button.setCursor(QCursor(Qt.PointingHandCursor)) # Set cursor to pointing hand on hover
|
|
338
|
+
button.setToolTip(self.tooltips[button_type]) # Set tooltip
|
|
339
|
+
button.clicked.connect(lambda: self.update_buttons(index, button_type))
|
|
340
|
+
if button.isChecked():
|
|
341
|
+
self.step_options[index]=self.options_list.index(button_type)
|
|
342
|
+
button.setIconSize(self.icon_size)
|
|
343
|
+
else:
|
|
344
|
+
button.setIconSize(self.icon_size_off) # Set icon size to 28x28 pixels
|
|
345
|
+
return button
|
|
346
|
+
|
|
347
|
+
def update_buttons(self, index, button_type):
|
|
348
|
+
"""Update the icons and states of the buttons, ensuring only one is checked at a time."""
|
|
349
|
+
for k, button in enumerate(self.buttons_group[index]):
|
|
350
|
+
if not isinstance(button,QPushButton): continue
|
|
351
|
+
if button_type!=self.options_list[k]:
|
|
352
|
+
button.setChecked(False) # Uncheck all buttons
|
|
353
|
+
button.setIconSize(self.icon_size_off)
|
|
354
|
+
button.setIcon(self.icons[self.options_list[k]][1])
|
|
355
|
+
else:
|
|
356
|
+
button.setChecked(True)
|
|
357
|
+
button.setIconSize(self.icon_size)
|
|
358
|
+
button.setIcon(self.icons[self.options_list[k]][0]) # Checked state icon
|
|
359
|
+
self.step_options[index]=k
|
|
360
|
+
self.update_extraopts_visibility()
|
|
361
|
+
|
|
362
|
+
def update_extraopts_visibility(self,FlagAnimation=True):
|
|
363
|
+
"""Show extra row if any row has 'change_folder' checked; hide otherwise."""
|
|
364
|
+
try:
|
|
365
|
+
idx_change = self.options_list.index("change_folder")
|
|
366
|
+
except ValueError:
|
|
367
|
+
idx_change = -1
|
|
368
|
+
|
|
369
|
+
show = (idx_change >= 0) and any(
|
|
370
|
+
(opt == idx_change) for opt in self.step_options if isinstance(opt, int)
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
if self.extra_opts_widget.isVisible() != show:
|
|
374
|
+
self.extra_opts_widget.setVisible(show)
|
|
375
|
+
extra_opts_widget = self.extra_opts_widget.height()
|
|
376
|
+
maxHeight = self.nstep * (self.button_size.height() + self.row_spacing) + int(show)*extra_opts_widget + self.min_height
|
|
377
|
+
if FlagAnimation:
|
|
378
|
+
self.window_resize(maxHeight)
|
|
379
|
+
self.max_height = maxHeight
|
|
380
|
+
|
|
381
|
+
def on_cleanup_toggled(self, checked: bool):
|
|
382
|
+
"""Sync icon/size and label text when the extra toggle is changed."""
|
|
383
|
+
self.cleanup_button.setIcon(self.icons["auto_purge"][0 if checked else 1])
|
|
384
|
+
self.cleanup_button.setIconSize(self.icon_size if checked else self.icon_size_off)
|
|
385
|
+
self.cleanup_label.setText(
|
|
386
|
+
"Image-missing pairs will be removed automatically."
|
|
387
|
+
if checked else
|
|
388
|
+
"Image-missing pairs are kept; batch copy may raise warnings."
|
|
389
|
+
)
|
|
390
|
+
self.cleanup_label.setStyleSheet("color: none;" if checked else "color: rgb(125,125,125);")
|
|
391
|
+
self.cleanup_enabled = bool(checked)
|
|
392
|
+
|
|
393
|
+
def on_rescan_toggled(self, checked: bool):
|
|
394
|
+
"""Sync icon/size and label text when the rescan toggle is changed."""
|
|
395
|
+
self.rescan_button.setIcon(self.icons["rescan"][0 if checked else 1])
|
|
396
|
+
self.rescan_button.setIconSize(self.icon_size if checked else self.icon_size_off)
|
|
397
|
+
|
|
398
|
+
self.rescan_label.setText(
|
|
399
|
+
"The input folder will be re-scanned; input image list may differ or trigger warnings."
|
|
400
|
+
if checked else
|
|
401
|
+
"No folder scan; images identified in the master process will be used."
|
|
402
|
+
)
|
|
403
|
+
self.rescan_label.setStyleSheet("color: none;" if checked else "color: rgb(125,125,125);")
|
|
404
|
+
self.rescan_enabled = bool(checked)
|
|
405
|
+
|
|
406
|
+
def on_proceed(self):
|
|
407
|
+
"""Start or stop the progress loop depending on the button state."""
|
|
408
|
+
if not self.loop_running:
|
|
409
|
+
self.start_progress()
|
|
410
|
+
else:
|
|
411
|
+
self.stop_progress()
|
|
412
|
+
|
|
413
|
+
def start_progress(self):
|
|
414
|
+
"""Start the progress and update button text to 'Stop'."""
|
|
415
|
+
self.loop_running = True
|
|
416
|
+
self.title_label.setText('Preparing copy...')
|
|
417
|
+
self.title_label.setFont(self.item_font)
|
|
418
|
+
self.proceed_button.setText(self.stop_button_text)
|
|
419
|
+
self.proceed_button.setVisible(False)
|
|
420
|
+
self.animate_window_resize()
|
|
421
|
+
self.progress_bar.setVisible(True) # Show the progress bar when Proceed is clicked
|
|
422
|
+
self.progress_bar.setMaximum(self.nfolders)
|
|
423
|
+
|
|
424
|
+
def stop_progress(self):
|
|
425
|
+
"""Stop the progress and update button text to 'Proceed'."""
|
|
426
|
+
self.loop_running = False
|
|
427
|
+
self.proceed_button.setText(self.proceed_button_text)
|
|
428
|
+
self.timer.stop()
|
|
429
|
+
|
|
430
|
+
def update_progress(self):
|
|
431
|
+
"""Update the progress bar on each timer tick."""
|
|
432
|
+
if self.iteration < self.progress_bar.maximum():
|
|
433
|
+
timesleep(time_sleep_loop)
|
|
434
|
+
title_text=self.paths[self.iteration][:48]+f"{'...' if len(self.paths[self.iteration])>50 else ''}"
|
|
435
|
+
self.title_label.setText(title_text)
|
|
436
|
+
self.warnings[self.iteration]=self.func(self.iteration,self.step_options,self.cleanup_enabled,self.rescan_enabled)
|
|
437
|
+
self.progress_bar.setValue(self.iteration)
|
|
438
|
+
self.iteration += 1
|
|
439
|
+
if self.loop_running: self.timer.start(0)
|
|
440
|
+
else:
|
|
441
|
+
self.progress_bar.setValue(self.progress_bar.maximum())
|
|
442
|
+
self.stop_progress()
|
|
443
|
+
self.hide()
|
|
444
|
+
self.show_batch_issues_dialog()
|
|
445
|
+
if not self.animation: self.done(0) # Closes the dialog when complete
|
|
446
|
+
|
|
447
|
+
def cancel(self):
|
|
448
|
+
if self.loop_running: self.stop_progress()
|
|
449
|
+
if not self.animation: self.done(0) # Closes the dialog when complete
|
|
450
|
+
|
|
451
|
+
def animate_window_resize(self):
|
|
452
|
+
for w in self.widgets:
|
|
453
|
+
w:QWidget
|
|
454
|
+
w.setVisible(False)
|
|
455
|
+
self.animation = QVariantAnimation(self)
|
|
456
|
+
self.animation.valueChanged.connect(self.window_resize)
|
|
457
|
+
self.animation.setDuration(300)
|
|
458
|
+
self.animation.setStartValue(self.max_height)
|
|
459
|
+
self.animation.setEndValue(self.min_height)
|
|
460
|
+
self.animation.finished.connect(self.finishedAnimation)
|
|
461
|
+
self.animation.start()
|
|
462
|
+
|
|
463
|
+
def finishedAnimation(self):
|
|
464
|
+
self.animation=None
|
|
465
|
+
self.timer.start(0) # Start or resume the timer
|
|
466
|
+
|
|
467
|
+
def window_resize(self, h):
|
|
468
|
+
self.setFixedHeight(h)
|
|
469
|
+
|
|
470
|
+
def show_batch_issues_dialog(self):
|
|
471
|
+
"""Show a summary dialog listing all destination folders with warnings=True."""
|
|
472
|
+
issues=[(i,p) for i,p in enumerate(self.paths) if i<len(self.warnings) and self.warnings[i]]
|
|
473
|
+
if not issues: return
|
|
474
|
+
|
|
475
|
+
dlg=QDialog(self); dlg.setWindowTitle("Batch copy issues")
|
|
476
|
+
font=dlg.font()
|
|
477
|
+
font.setPixelSize(fontPixelSize)
|
|
478
|
+
dlg.setFont(font)
|
|
479
|
+
lay=QVBoxLayout(dlg)
|
|
480
|
+
|
|
481
|
+
# --- Warning header with icon ---
|
|
482
|
+
icon_width=96
|
|
483
|
+
warn_layout = QHBoxLayout()
|
|
484
|
+
warn_icon = QLabel()
|
|
485
|
+
warn_pix = QPixmap(icons_path + "warning.png").scaled(
|
|
486
|
+
icon_width, icon_width, Qt.KeepAspectRatio, Qt.SmoothTransformation
|
|
487
|
+
)
|
|
488
|
+
warn_icon.setPixmap(warn_pix)
|
|
489
|
+
warn_icon.setScaledContents(False)
|
|
490
|
+
#warn_icon.setAlignment(Qt.AlignTop)
|
|
491
|
+
warn_icon.setFixedWidth(icon_width)
|
|
492
|
+
|
|
493
|
+
head = QLabel(
|
|
494
|
+
f"{len(issues)} destination folder{'s' if len(issues)>1 else ''} could require attention: "
|
|
495
|
+
"potential warnings during batch copy or image-set mismatch!\n\n"
|
|
496
|
+
"Please note that the interface may not explicitly flag these cases. "
|
|
497
|
+
"Copy the paths below if you wish to inspect the corresponding processes.\n",
|
|
498
|
+
dlg
|
|
499
|
+
)
|
|
500
|
+
head.setWordWrap(True)
|
|
501
|
+
|
|
502
|
+
warn_layout.addWidget(warn_icon)
|
|
503
|
+
warn_layout.addWidget(head)
|
|
504
|
+
lay.addLayout(warn_layout)
|
|
505
|
+
|
|
506
|
+
tree=QTreeWidget(dlg); tree.setHeaderHidden(True)
|
|
507
|
+
tree.header().setStretchLastSection(True)
|
|
508
|
+
for _,p in issues:
|
|
509
|
+
item=QTreeWidgetItem([p])
|
|
510
|
+
item.setToolTip(0,p)
|
|
511
|
+
tree.addTopLevelItem(item)
|
|
512
|
+
lay.addWidget(tree)
|
|
513
|
+
|
|
514
|
+
# actions
|
|
515
|
+
btns=QHBoxLayout()
|
|
516
|
+
def _copy():
|
|
517
|
+
txt="\n".join(p for _,p in issues)
|
|
518
|
+
QApplication.clipboard().setText(txt)
|
|
519
|
+
copy_btn=QPushButton("Copy paths"); copy_btn.clicked.connect(_copy)
|
|
520
|
+
close_btn=QPushButton("Close"); close_btn.clicked.connect(dlg.accept)
|
|
521
|
+
btns.addWidget(copy_btn); btns.addStretch(1); btns.addWidget(close_btn)
|
|
522
|
+
lay.addLayout(btns)
|
|
523
|
+
|
|
524
|
+
dlg.resize(720, 360); dlg.exec()
|
|
525
|
+
|
|
526
|
+
if __name__ == "__main__":
|
|
527
|
+
import random
|
|
528
|
+
import string
|
|
529
|
+
|
|
530
|
+
# Function to generate a random string
|
|
531
|
+
def generate_random_string(length):
|
|
532
|
+
letters = string.ascii_letters # Includes uppercase and lowercase letters
|
|
533
|
+
return ''.join(random.choice(letters) for i in range(length))
|
|
534
|
+
|
|
535
|
+
# Number of strings and length of each string
|
|
536
|
+
num_strings = 10 # Number of strings in the list
|
|
537
|
+
string_length = 25 # Length of each string
|
|
538
|
+
|
|
539
|
+
# Generate the list of random strings
|
|
540
|
+
random_strings_list = [generate_random_string(string_length) for _ in range(num_strings)]
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
app = QApplication(sys.argv)
|
|
544
|
+
app.setStyle('Fusion')
|
|
545
|
+
|
|
546
|
+
time_sleep_loop=0.01
|
|
547
|
+
|
|
548
|
+
print(choose_directories())
|
|
549
|
+
|
|
550
|
+
# Example lists
|
|
551
|
+
pixmap_list = [icons_path + "cal_step.png",
|
|
552
|
+
icons_path + "min_step.png",
|
|
553
|
+
icons_path + "disp_step.png",
|
|
554
|
+
icons_path + "piv_step.png"]
|
|
555
|
+
flag_list = [False,True,True,True]
|
|
556
|
+
name_list = ["Camera calibration",
|
|
557
|
+
"Image pre-processing",
|
|
558
|
+
"Disparity correction",
|
|
559
|
+
"Stereoscopic PIV analysis"]
|
|
560
|
+
|
|
561
|
+
dialog = FolderLoopDialog(pixmap_list, name_list, flag_list, paths=random_strings_list, process_name='Stereoscopic PIV process 1')
|
|
562
|
+
sys.exit(dialog.exec())
|