window_image.py
1 ''' 2 SuperFormat is a program that converts files into various formats 3 Copyright (C) 2025 daroi 4 5 This program is free software: you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as published by 7 the Free Software Foundation, either version 3 of the License, or 8 (at your option) any later version. 9 10 This program is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public License 16 along with this program. If not, see <https://www.gnu.org/licenses/>. 17 ''' 18 19 from PyQt6.QtWidgets import (QScrollArea, 20 QFrame, 21 QMainWindow, 22 QWidget, 23 QVBoxLayout, 24 QHBoxLayout, 25 QGridLayout, 26 QPushButton, 27 QFileDialog, 28 QComboBox, 29 QMessageBox, 30 QProgressBar) 31 32 from functools import partial 33 from app.widgets.card_widget import CardWidget 34 from app.widgets.neon_label import NeonLabel 35 from app.logic.image import Image 36 from app.logic.convert_worker import ConvertWorker 37 from functools import partial 38 from PyQt6.QtCore import Qt, QThread 39 import os 40 41 42 class WindowImage(QWidget): 43 def __init__(self, stack): 44 super().__init__() 45 46 self.buttons = [] # row list 47 self.files_path = {} # files_path diccionary 48 self.folders_path = {} # folders_path diccionary 49 self.format_selections = {} # format selection diccionary 50 51 self.stack = stack 52 53 # main vertical layout 54 main_layout = QVBoxLayout(self) 55 56 # Scroll area for dynamic content 57 self.scroll_area = QScrollArea() 58 self.scroll_area.setWidgetResizable(True) 59 60 # Widget into scroll 61 self.scroll_widget = QFrame() 62 self.scroll_layout = QVBoxLayout(self.scroll_widget) 63 self.scroll_layout.setAlignment(Qt.AlignmentFlag.AlignTop) 64 self.scroll_area.setWidget(self.scroll_widget) 65 main_layout.addWidget(self.scroll_area) 66 67 # lower bar for add and back buttons 68 bottom_bar = QHBoxLayout() 69 70 # Back button 71 self.back_button = QPushButton("Back") 72 # Back button style 73 self.back_button_style() 74 # Connect the back button with the back method 75 self.back_button.clicked.connect(self.back) 76 77 # Add button 78 self.add_button = QPushButton(" + ") 79 # Add button style 80 self.add_button_style() 81 # Connect add button with add method 82 self.add_button.clicked.connect(self.add) 83 84 85 # Covert button 86 self.convert_btn = QPushButton("Convert") 87 # Convert button style 88 self.convert_button_style() 89 90 91 # Create and show progress bar 92 self.progress_bar = QProgressBar() 93 self.progress_bar.setRange(0, 100) 94 main_layout.addWidget(self.progress_bar) 95 96 97 # Add buttons in the bottom bar 98 bottom_bar.addWidget(self.back_button, alignment=Qt.AlignmentFlag.AlignLeft) 99 bottom_bar.addStretch() 100 bottom_bar.addWidget(self.convert_btn, alignment=Qt.AlignmentFlag.AlignCenter) 101 bottom_bar.addStretch() 102 bottom_bar.addWidget(self.add_button, alignment=Qt.AlignmentFlag.AlignRight) 103 104 # Add bottom bar to main layout 105 main_layout.addLayout(bottom_bar) 106 107 # Convert method 108 def convert(self, widget_row, new_row): 109 files = [] 110 folders = [] 111 formats = [] 112 113 # Verify rows 114 incomplete_rows = 0 115 116 # only add if widget exist in the screen 117 for row in self.buttons: 118 widget_row = row[0].parentWidget() 119 120 if widget_row is None or not widget_row.isVisible(): 121 continue 122 123 file = self.files_path.get(widget_row) 124 folder = self.folders_path.get(widget_row) 125 format = self.format_selections.get(widget_row) 126 127 # Verify that all field are selected 128 if file and folder and format and format != "Convert to": 129 files.append(file) 130 folders.append(folder) 131 formats.append(format) 132 else: 133 incomplete_rows += 1 134 135 # Alert empty fields message 136 if incomplete_rows > 0: 137 msg = QMessageBox() 138 msg.setWindowTitle("Incomplete data") 139 msg.setText(f"There are {incomplete_rows} row(s) with incomplete data.\nPlease complete all fields before converting.") 140 msg.setIcon(QMessageBox.Icon.Warning) 141 msg.exec() 142 return 143 144 # test 145 print("files:", files) 146 print("out folders:", folders) 147 print("formats:", formats) 148 149 # Use the Music class for convert the music 150 151 # Create thread and worker 152 hilo = QThread() 153 worker = ConvertWorker(files, folders, formats, Image) 154 155 # connect thread at worker 156 worker.moveToThread(hilo) 157 158 # conexions 159 hilo.started.connect(worker.run) 160 worker.progress.connect(self.progress_bar.setValue) 161 162 def on_finish(): 163 self.progress_bar.setFormat("Conversion Finished!") 164 hilo.quit() 165 hilo.wait() 166 worker.deleteLater() 167 hilo.deleteLater() 168 self.remove_all_rows() 169 170 worker.finish.connect(on_finish) 171 172 # Thread start 173 hilo.start() 174 175 def remove_all_rows(self): 176 for new_row in self.buttons[:]: 177 widget_row = new_row[0].parentWidget() 178 self.scroll_layout.removeWidget(widget_row) 179 widget_row.setParent(None) 180 if new_row in self.buttons: 181 self.buttons.remove(new_row) 182 if widget_row in self.files_path: 183 del self.files_path[widget_row] 184 if widget_row in self.folders_path: 185 del self.folders_path[widget_row] 186 if widget_row in self.format_selections: 187 del self.format_selections[widget_row] 188 189 190 # Back method 191 def back(self): 192 self.stack.setCurrentIndex(0) 193 194 # Add method 195 def add(self): 196 # update progress bar 197 self.progress_bar.setFormat("") 198 self.progress_bar.setValue(0) 199 200 texts = ["File", "Convert to", "Out folder", "Remove"] 201 new_row = [] 202 203 # QWidget row 204 widget_row = QWidget() 205 # layout row 206 layout_row = QHBoxLayout(widget_row) 207 # Center the content 208 layout_row.setAlignment(Qt.AlignmentFlag.AlignCenter) 209 210 # Spacing between cards 211 layout_row.setSpacing(15) 212 213 # Format options list 214 combo = QComboBox() 215 combo.addItems(["Convert to", "jpeg", "png", "bmp", "gif", "tiff", "webp", "ico", "pdf", "eps"]) 216 217 218 # Add buttons in the screen 219 for i in range(4): 220 # Add the format list 221 if i == 1: 222 new_row.append(combo) 223 layout_row.addWidget(combo) 224 else: 225 button = QPushButton(texts[i]) 226 new_row.append(button) 227 layout_row.addWidget(button) 228 229 # Buttons style 230 for i in range(len(new_row)): 231 new_row[i].setStyleSheet(""" 232 QPushButton { 233 border-radius: 10px; 234 padding: 6px 40px; /* top-bottom, left-right */ 235 font-size: 16px; 236 } 237 """) 238 239 # Button remove style 240 new_row[-1].setStyleSheet(""" 241 QPushButton { 242 background-color: #F2476E; 243 color: white; 244 border: 2px solid #FC8691; 245 border-radius: 10px; 246 padding: 6px 40px; 247 } 248 QPushButton:hover { 249 background-color: #C51C42; 250 } 251 """) 252 253 254 # Method to select files, the path is saved in files_path diccionary 255 def select_file(): 256 path, _ = QFileDialog.getOpenFileName(self, "Select file", "", "Image (*.jpeg *.png *.bmp *.gif *.tiff *.webp *.ico *.pdf *.eps)") 257 if path: 258 self.files_path[widget_row] = path 259 new_row[0].setText(path.split("/")[-1]) # Show the file name in the button 260 print("path: ", self.files_path) 261 262 # Method to select folders, the path is saved in folders_path diccionary 263 def select_folder(): 264 folder = QFileDialog.getExistingDirectory(self, "Select out folder", "") 265 if folder: 266 self.folders_path[widget_row] = folder 267 new_row[2].setText(folder.split("/")[-1]) 268 print("path folder: ", self.folders_path) 269 270 # Save the current selection 271 def save_selection(index): 272 self.format_selections[widget_row] = combo.currentText() 273 print("format: ", self.format_selections) 274 275 276 combo.currentIndexChanged.connect(save_selection) 277 new_row[0].clicked.connect(select_file) 278 new_row[2].clicked.connect(select_folder) 279 280 # If remove button is presed, then go to remove_row method 281 new_row[-1].clicked.connect(lambda _, w=widget_row, r=new_row: self.remove_row(w, r)) 282 # Connect convert button with convert method 283 # partial(self.remove_row, widget_row, new_row) 284 self.convert_btn.clicked.connect(partial(self.convert, widget_row, new_row)) 285 286 # Add row to main layout 287 self.scroll_layout.addWidget(widget_row) 288 self.buttons.append(new_row) 289 290 def remove_row(self, widget_row, new_row): 291 self.scroll_layout.removeWidget(widget_row) 292 widget_row.setParent(None) 293 if new_row in self.buttons: 294 self.buttons.remove(new_row) 295 if widget_row in self.files_path: 296 del self.files_path[widget_row] 297 if widget_row in self.folders_path: 298 del self.folders_path[widget_row] 299 if widget_row in self.format_selections: 300 del self.format_selections[widget_row] 301 302 303 def add_button_style(self): 304 self.add_button.setStyleSheet(""" 305 QPushButton { 306 background-color: #6AF247; 307 color: white; 308 border: 2px solid #93FC86; 309 border-radius: 10px; 310 padding: 8px 16px; 311 } 312 QPushButton:hover { 313 background-color: #3AC51C; 314 } 315 """) 316 317 def back_button_style(self): 318 self.back_button.setStyleSheet(""" 319 QPushButton { 320 background-color: #47CDF2; 321 color: white; 322 border: 2px solid #86BFFC; 323 border-radius: 10px; 324 padding: 8px 16px; 325 } 326 QPushButton:hover { 327 background-color: #1C6EC5; 328 } 329 """) 330 331 def convert_button_style(self): 332 self.convert_btn.setStyleSheet(""" 333 QPushButton { 334 background-color: #9D00FF; 335 color: white; 336 border-radius: 10px; 337 padding: 10px 20px; 338 font-weight: bold; 339 } 340 QPushButton:hover { 341 background-color: #B500FF; 342 } 343 """)