/ app / views / window_image.py
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          """)