Merge branch 'feature/enhancing' into 'main'

Feature/enhancing

See merge request CodeByMrFinchum/OptimaLab35!5
This commit is contained in:
Mr Finchum 2025-01-13 14:53:43 +00:00
commit 3bfad2035e
15 changed files with 1602 additions and 422 deletions

View file

@ -1,6 +1,27 @@
# Changelog # Changelog
## 0.1.0 ## 0.2.x
### 0.2.2
- Moved processing images into a different thread, making the UI responsiable while processing
### 0.2.1
- Insert exif to image file (i.e. without changing the file).
### 0.2.0
- Now spaces in rename string are replaces with `_`.
- version check of `optima35`, incase pip did not update it.
- Sorting entries from exif file.
### 0.2.0-a1
- Main UI received a facelift.
- Added a new experimental preview window to display an image and show how changing values affects it.
- Programm now warns for potential overwrite of existing files.
## 0.1.x
### 0.1.1
- Update metadata, preview, readme, bump in version for pip
### 0.1.0
- Preserved the current working GUI by pinning `optima35` to a specific version for guaranteed compatibility. - Preserved the current working GUI by pinning `optima35` to a specific version for guaranteed compatibility.
## 0.0.x ## 0.0.x

View file

@ -31,22 +31,27 @@ pip install OptimaLab35
- **TUI**: Fallback if **PySide6** is missing or can be explicitly started using the `--tui` flag. - **TUI**: Fallback if **PySide6** is missing or can be explicitly started using the `--tui` flag.
### Preview GUI ### Preview GUI
**PREVIEW** might be out of date.
**Main tab** **Main tab**
![main](https://gitlab.com/CodeByMrFinchum/OptimaLab35/-/raw/0.1.1/media/main_tab.png){width=40%} ![main](https://gitlab.com/CodeByMrFinchum/OptimaLab35/-/raw/feature/enhancing/media/main_tab.png){width=40%}
**Preview window**
![main](https://gitlab.com/CodeByMrFinchum/OptimaLab35/-/raw/feature/enhancing/media/preview_window.png){width=40%}
**Exif tab** **Exif tab**
![main](https://gitlab.com/CodeByMrFinchum/OptimaLab35/-/raw/0.1.1/media/exif_tab.png){width=40%} ![main](https://gitlab.com/CodeByMrFinchum/OptimaLab35/-/raw/feature/enhancing/media/exif_tab.png){width=40%}
**Exif editor** **Exif editor**
![main](https://gitlab.com/CodeByMrFinchum/OptimaLab35/-/raw/0.1.1/media/exif_editor.png){width=40%} ![main](https://gitlab.com/CodeByMrFinchum/OptimaLab35/-/raw/feature/enhancing/media/exif_editor.png){width=40%}
**Info window** **Info window**
![main](https://gitlab.com/CodeByMrFinchum/OptimaLab35/-/raw/0.1.1/media/info_window.png){width=40%} ![main](https://gitlab.com/CodeByMrFinchum/OptimaLab35/-/raw/feature/enhancing/media/info_window.png){width=40%}
## **Features** ## **Features**

BIN
media/OptimaLab35.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 173 KiB

After

Width:  |  Height:  |  Size: 210 KiB

Before After
Before After

BIN
media/preview_window.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

4
pip_README.md Normal file
View file

@ -0,0 +1,4 @@
OptimaLab35 is in active development, and even *stable* versions may produce errors in unexpected situations. It is a GUI for optima35.
Please visit [OptimaLab35](https://gitlab.com/CodeByMrFinchum/OptimaLab35/-/blob/main/media/exif_editor.png?ref_type=heads) gitlab page for more information.
Uses [optima35](https://gitlab.com/CodeByMrFinchum/optima35) to modify images.

View file

@ -6,10 +6,10 @@ build-backend = "hatchling.build"
name = "OptimaLab35" name = "OptimaLab35"
dynamic = ["version"] dynamic = ["version"]
authors = [{ name = "Mr. Finchum" }] authors = [{ name = "Mr. Finchum" }]
description = "User interface for OPTIMA35." description = "User interface for optima35."
readme = "README.md" readme = "pip_README.md"
requires-python = ">=3.8" requires-python = ">=3.8"
dependencies = ["optima35==0.6.4", "pyside6", "PyYAML"] dependencies = ["optima35>=0.6.6", "pyside6", "PyYAML", "packaging"]
classifiers = [ classifiers = [
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)", "License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)",

View file

@ -1 +1 @@
__version__ = "0.1.1" __version__ = "0.2.2"

View file

@ -2,13 +2,18 @@ import sys
import os import os
from datetime import datetime from datetime import datetime
import time
from optima35.core import OptimaManager from optima35.core import OptimaManager
from OptimaLab35.utils.utility import Utilities from OptimaLab35.utils.utility import Utilities
from OptimaLab35.ui.main_window import Ui_MainWindow from OptimaLab35.ui.main_window import Ui_MainWindow
from OptimaLab35.ui.preview_window import Ui_Preview_Window
from OptimaLab35.ui.exif_handler_window import ExifEditor from OptimaLab35.ui.exif_handler_window import ExifEditor
from OptimaLab35.ui.simple_dialog import SimpleDialog # Import the SimpleDialog class from OptimaLab35.ui.simple_dialog import SimpleDialog # Import the SimpleDialog class
from OptimaLab35 import __version__ from OptimaLab35 import __version__
from PySide6.QtCore import QRunnable, QThreadPool, Signal, QObject
from PySide6 import QtWidgets from PySide6 import QtWidgets
from PySide6.QtWidgets import ( from PySide6.QtWidgets import (
QMessageBox, QMessageBox,
@ -23,8 +28,110 @@ from PySide6.QtWidgets import (
QFileDialog, QFileDialog,
QHBoxLayout, QHBoxLayout,
QSpinBox, QSpinBox,
QProgressBar,
) )
from PySide6.QtGui import QPixmap, QIcon
class PreviewWindow(QMainWindow, Ui_Preview_Window):
values_selected = Signal(int, int, bool)
def __init__(self):
super(PreviewWindow, self).__init__()
self.ui = Ui_Preview_Window()
self.ui.setupUi(self)
self.o = OptimaManager()
## Ui interaction
self.ui.load_Button.clicked.connect(self._browse_file)
self.ui.update_Button.clicked.connect(self._update_preview)
self.ui.close_Button.clicked.connect(self._close_window)
self.ui.reset_brightness_Button.clicked.connect(lambda: self.ui.brightness_spinBox.setValue(0))
self.ui.reset_contrast_Button.clicked.connect(lambda: self.ui.contrast_spinBox.setValue(0))
self.preview_image = None
def _browse_file(self):
file = QFileDialog.getOpenFileName(self, caption = "Select File", filter = ("Images (*.png *.webp *.jpg *.jpeg)"))
if file[0]:
self.ui.image_path_lineEdit.setText(file[0])
self._update_preview()
def _update_preview(self):
path = self.ui.image_path_lineEdit.text()
if not os.path.isfile(path):
return
try:
img = self.o.process_image(
save = False,
image_input_file = path,
image_output_file = "",
grayscale = self.ui.grayscale_checkBox.isChecked(),
brightness = int(self.ui.brightness_spinBox.text()),
contrast = int(self.ui.contrast_spinBox.text())
)
except Exception as e:
QMessageBox.warning(self, "Warning", "Error loading image...")
print(f"Error loading image...\n{e}")
return
self.preview_image = QPixmap.fromImage(img)
self.ui.QLabel.setPixmap(self.preview_image)
def _close_window(self):
# Emit the signal with the values from the spinboxes and checkbox
if self.ui.checkBox.isChecked():
self.values_selected.emit(self.ui.brightness_spinBox.value(), self.ui.contrast_spinBox.value(), self.ui.grayscale_checkBox.isChecked())
self.close()
class WorkerSignals(QObject):
# ChatGPT
progress = Signal(int)
finished = Signal()
class ImageProcessorRunnable(QRunnable):
# ChatGPT gave rough function layout
def __init__(self, image_files, settings, progress_callback):
super().__init__()
self.image_files = image_files
self.settings = settings
self.signals = WorkerSignals()
self.signals.progress.connect(progress_callback)
self.o = OptimaManager()
self.u = Utilities()
def run(self):
input_folder = self.settings["input_folder"]
output_folder = self.settings["output_folder"]
for i, image_file in enumerate(self.image_files, start=1):
input_path = os.path.join(input_folder, image_file)
if self.settings["new_file_names"] != False:
image_name = self.u.append_number_to_name(self.settings["new_file_names"], i, len(self.image_files), self.settings["invert_image_order"])
else:
image_name = os.path.splitext(image_file)[0]
output_path = os.path.join(output_folder, image_name)
self.o.process_image(
image_input_file = input_path,
image_output_file = output_path,
file_type = self.settings["file_format"],
quality = self.settings["jpg_quality"],
compressing = self.settings["png_compression"],
optimize = self.settings["optimize"],
resize = self.settings["resize"],
watermark = self.settings["watermark"],
font_size = self.settings["font_size"],
grayscale = self.settings["grayscale"],
brightness = self.settings["brightness"],
contrast = self.settings["contrast"],
dict_for_exif = self.settings["user_selected_exif"],
gps = self.settings["gps"],
copy_exif = self.settings["copy_exif"]
)
self.signals.progress.emit(int((i / len(self.image_files)) * 100))
self.signals.finished.emit()
class OptimaLab35(QMainWindow, Ui_MainWindow): class OptimaLab35(QMainWindow, Ui_MainWindow):
def __init__(self): def __init__(self):
super(OptimaLab35, self).__init__() super(OptimaLab35, self).__init__()
@ -33,6 +140,7 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
self.ui = Ui_MainWindow() self.ui = Ui_MainWindow()
self.ui.setupUi(self) self.ui.setupUi(self)
self.o = OptimaManager() self.o = OptimaManager()
self.check_version()
self.u = Utilities() self.u = Utilities()
self.u.program_configs() self.u.program_configs()
self.exif_file = os.path.expanduser("~/.config/OptimaLab35/exif.yaml") self.exif_file = os.path.expanduser("~/.config/OptimaLab35/exif.yaml")
@ -44,14 +152,31 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
self.sd = SimpleDialog() self.sd = SimpleDialog()
self._change_statusbar(f"Using {self.o.name} v{self.o.version}", 5000) self._change_statusbar(f"Using {self.o.name} v{self.o.version}", 5000)
# Instantiate the second window
self.preview_window = PreviewWindow()
self.thread_pool = QThreadPool() # multi thread ChatGPT
def open_preview_window(self):
self.preview_window.values_selected.connect(self.update_values)
self.preview_window.show()
def update_values(self, value1, value2, checkbox_state):
# Update main window's widgets with the received values
# ChatGPT
self.ui.brightness_spinBox.setValue(value1)
self.ui.contrast_spinBox.setValue(value2)
self.ui.grayscale_checkBox.setChecked(checkbox_state)
def _default_ui_layout(self): def _default_ui_layout(self):
self.ui.png_quality_spinBox.setVisible(False) self.ui.png_quality_spinBox.setVisible(False)
self.ui.png_quality_Slider.setVisible(False)
self.ui.quality_label_2.setVisible(False)
def _define_gui_interaction(self): def _define_gui_interaction(self):
self.ui.input_folder_button.clicked.connect(self._browse_input_folder) self.ui.input_folder_button.clicked.connect(self._browse_input_folder)
self.ui.output_folder_button.clicked.connect(self._browse_output_folder) self.ui.output_folder_button.clicked.connect(self._browse_output_folder)
self.ui.start_button.clicked.connect(self._process) self.ui.start_button.clicked.connect(self._start_process)
self.ui.insert_exif_Button.clicked.connect(self._start_insert_exif)
self.ui.image_type.currentIndexChanged.connect(self._update_quality_options) self.ui.image_type.currentIndexChanged.connect(self._update_quality_options)
self.ui.exif_checkbox.stateChanged.connect( self.ui.exif_checkbox.stateChanged.connect(
@ -60,68 +185,147 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
self.ui.tabWidget.currentChanged.connect(self._on_tab_changed) self.ui.tabWidget.currentChanged.connect(self._on_tab_changed)
self.ui.edit_exif_button.clicked.connect(self._open_exif_editor) self.ui.edit_exif_button.clicked.connect(self._open_exif_editor)
self.ui.actionInfo.triggered.connect(self._info_window) self.ui.actionAbout.triggered.connect(self._info_window)
self.ui.actionPreview.triggered.connect(self.open_preview_window)
self.ui.preview_Button.clicked.connect(self.open_preview_window)
def _info_window(self): def _info_window(self):
# ChatGPT, mainly
info_text = f""" info_text = f"""
<h3>{self.name} v{self.version}</h3> <h3>{self.name} v{self.version}</h3>
<p>(C) 2024-2025 Mr. Finchum aka CodeByMrFinchum</p>
<p>{self.name} is a GUI for <b>{self.o.name}</b> (v{self.o.version}).</p> <p>{self.name} is a GUI for <b>{self.o.name}</b> (v{self.o.version}).</p>
<p>For more details, visit:</p> <p> Both projects are in active development, for more details, visit:</p>
<ul> <ul>
<li><a href="https://gitlab.com/CodeByMrFinchum/OptimaLab35">OptimaLab35 GitLab</a></li> <li><a href="https://gitlab.com/CodeByMrFinchum/OptimaLab35">OptimaLab35 GitLab</a></li>
<li><a href="https://gitlab.com/CodeByMrFinchum/optima35">Optima35 GitLab</a></li> <li><a href="https://gitlab.com/CodeByMrFinchum/optima35">optima35 GitLab</a></li>
</ul> </ul>
""" """
self.sd.show_dialog(f"{self.name} v{self.version}", info_text) self.sd.show_dialog(f"{self.name} v{self.version}", info_text)
def _process(self): def _prepear_image(self):
self.ui.start_button.setEnabled(False) pass
self._update_settings() # Get all user selected data
input_folder_valid = os.path.exists(self.settings["input_folder"])
output_folder_valid = os.path.exists(self.settings["output_folder"])
if not input_folder_valid or not output_folder_valid:
QMessageBox.warning(self, "Warning", f"Input location {input_folder_valid}\nOutput folder {output_folder_valid}...")
return
def _image_list_from_folder(self, path):
image_files = [
f for f in os.listdir(path) if f.lower().endswith((".png", ".jpg", ".jpeg", ".webp"))
]
return image_files
def _start_process(self):
self._toggle_buttons(False)
self._update_settings() # Get all user selected data
input_folder = self.settings["input_folder"] input_folder = self.settings["input_folder"]
output_folder = self.settings["output_folder"] output_folder = self.settings["output_folder"]
if not input_folder or not output_folder:
QMessageBox.warning(self, "Warning", "Input or output folder not selected")
self._toggle_buttons(True)
return
input_folder_valid = os.path.exists(input_folder)
output_folder_valid = os.path.exists(output_folder)
if not input_folder_valid or not output_folder_valid:
QMessageBox.warning(self, "Warning", f"Input location {input_folder_valid}\nOutput folder {output_folder_valid}...")
self._toggle_buttons(True)
return
image_list = self._image_list_from_folder(input_folder)
if len(image_list) == 0:
QMessageBox.warning(self, "Warning", "Selected folder has no supported files.")
self._toggle_buttons(True)
return
if len(self._image_list_from_folder(output_folder)) != 0:
reply = QMessageBox.question(
self,
"Confirmation",
"Output folder containes images, which might get overritten, continue?",
QMessageBox.Yes | QMessageBox.No,
)
if reply == QMessageBox.No:
self._toggle_buttons(True)
return
# Create a worker ChatGPT
worker = ImageProcessorRunnable(image_list, self.settings, self._handle_qprogressbar)
worker.signals.finished.connect(self._on_processing_finished)
# Start worker in thread pool ChatGPT
self.thread_pool.start(worker)
def _handle_qprogressbar(self, value):
self.ui.progressBar.setValue(value)
def _on_processing_finished(self):
self._toggle_buttons(True)
self._handle_qprogressbar(0)
QMessageBox.information(self, "Information", "Finished!")
def _toggle_buttons(self, state):
self.ui.start_button.setEnabled(state)
if self.ui.exif_checkbox.isChecked():
self.ui.insert_exif_Button.setEnabled(state)
def _insert_exif(self, image_files):
input_folder = self.settings["input_folder"]
image_files = [
f for f in os.listdir(input_folder) if f.lower().endswith((".png", ".jpg", ".jpeg", ".webp"))
]
i = 1 i = 1
for image_file in image_files: for image_file in image_files:
input_path = os.path.join(input_folder, image_file)
if self.settings["new_file_names"] != False:
image_name = self.u.append_number_to_name(self.settings["new_file_names"], i, len(image_files), self.settings["invert_image_order"])
else:
image_name = os.path.splitext(image_file)[0]
output_path = os.path.join(output_folder, image_name)
self.o.process_image( input_path = os.path.join(input_folder, image_file)
image_input_file = input_path,
image_output_file = output_path, self.o.insert_dict_to_image(
file_type = self.settings["file_format"], exif_dict = self.settings["user_selected_exif"],
quality = self.settings["jpg_quality"], image_path = input_path,
compressing = self.settings["png_compression"], gps = self.settings["gps"])
optimize = self.ui.optimize_checkBox.isChecked(), self._change_statusbar(image_file, 100)
resize = self.settings["resize"], self._handle_qprogressbar(int((i / len(image_files)) * 100))
watermark = self.settings["watermark"],
font_size = self.settings["font_size"],
grayscale = self.settings["grayscale"],
brightness = self.settings["brightness"],
contrast = self.settings["contrast"],
dict_for_exif = self.user_selected_exif,
gps = self.settings["gps"],
copy_exif = self.settings["copy_exif"])
self._handle_qprogressbar(i, len(image_files))
i += 1 i += 1
QMessageBox.information(self, "Information", "Finished")
self.ui.start_button.setEnabled(True)
self.ui.progressBar.setValue(0) self.ui.progressBar.setValue(0)
def _start_insert_exif(self):
self._toggle_buttons(False)
self._update_settings() # Get all user selected data
input_folder = self.settings["input_folder"]
output_folder = self.settings["output_folder"]
if not input_folder:
QMessageBox.warning(self, "Warning", "Input not selected")
self._toggle_buttons(True)
return
if output_folder:
reply = QMessageBox.question(
self,
"Confirmation",
"Output folder selected, but insert exif is done to images in input folder, Continue?",
QMessageBox.Yes | QMessageBox.No,
)
if reply == QMessageBox.No:
self._toggle_buttons(True)
return
input_folder_valid = os.path.exists(input_folder)
if not input_folder_valid :
QMessageBox.warning(self, "Warning", f"Input location {input_folder_valid}")
self._toggle_buttons(True)
return
image_list = self._image_list_from_folder(input_folder)
if len(image_list) == 0:
QMessageBox.warning(self, "Warning", "Selected folder has no supported files.")
self._toggle_buttons(True)
return
self._insert_exif(image_list)
self._toggle_buttons(True)
QMessageBox.information(self, "Information", "Finished")
def _open_exif_editor(self): def _open_exif_editor(self):
"""Open the EXIF Editor.""" """Open the EXIF Editor."""
self.exif_editor = ExifEditor(self.available_exif_data) self.exif_editor = ExifEditor(self.available_exif_data)
@ -146,9 +350,27 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
elif index == 0: # Main Tab elif index == 0: # Main Tab
self._handle_exif_file("write") self._handle_exif_file("write")
def _sort_dict_of_lists(self, input_dict):
# Partily ChatGPT
sorted_dict = {}
for key, lst in input_dict.items():
# Sort alphabetically for strings, numerically for numbers
if key == "iso":
lst = [int(x) for x in lst]
lst = sorted(lst)
lst = [str(x) for x in lst]
sorted_dict["iso"] = lst
elif all(isinstance(x, str) for x in lst):
sorted_dict[key] = sorted(lst, key=str.lower) # Case-insensitive sort for strings
return sorted_dict
def _handle_exif_file(self, do): def _handle_exif_file(self, do):
if do == "read": if do == "read":
self.available_exif_data = self.u.read_yaml(self.exif_file) file_dict = self.u.read_yaml(self.exif_file)
self.available_exif_data = self._sort_dict_of_lists(file_dict)
elif do == "write": elif do == "write":
self.u.write_yaml(self.exif_file, self.available_exif_data) self.u.write_yaml(self.exif_file, self.available_exif_data)
@ -165,6 +387,7 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
"artist": self.ui.artist_comboBox, "artist": self.ui.artist_comboBox,
"copyright_info": self.ui.copyright_info_comboBox, "copyright_info": self.ui.copyright_info_comboBox,
} }
self._populate_comboboxes(combo_mapping) self._populate_comboboxes(combo_mapping)
def _populate_comboboxes(self, combo_mapping): def _populate_comboboxes(self, combo_mapping):
@ -181,13 +404,23 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
# Hide all quality settings # Hide all quality settings
self.ui.png_quality_spinBox.setVisible(False) self.ui.png_quality_spinBox.setVisible(False)
self.ui.jpg_quality_spinBox.setVisible(False) self.ui.jpg_quality_spinBox.setVisible(False)
self.ui.jpg_quality_Slider.setVisible(False)
self.ui.png_quality_Slider.setVisible(False)
self.ui.quality_label_1.setVisible(False)
self.ui.quality_label_2.setVisible(False)
# Show relevant settings # Show relevant settings
if selected_format == "jpg": if selected_format == "jpg":
self.ui.jpg_quality_spinBox.setVisible(True) self.ui.jpg_quality_spinBox.setVisible(True)
self.ui.jpg_quality_Slider.setVisible(True)
self.ui.quality_label_1.setVisible(True)
elif selected_format == "webp": elif selected_format == "webp":
self.ui.jpg_quality_spinBox.setVisible(True) self.ui.jpg_quality_spinBox.setVisible(True)
self.ui.jpg_quality_Slider.setVisible(True)
self.ui.quality_label_1.setVisible(True)
elif selected_format == "png": elif selected_format == "png":
self.ui.png_quality_spinBox.setVisible(True) self.ui.png_quality_spinBox.setVisible(True)
self.ui.png_quality_Slider.setVisible(True)
self.ui.quality_label_2.setVisible(True)
def _browse_input_folder(self): def _browse_input_folder(self):
folder = QFileDialog.getExistingDirectory(self, "Select Input Folder") folder = QFileDialog.getExistingDirectory(self, "Select Input Folder")
@ -202,10 +435,6 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
def _change_statusbar(self, msg, timeout = 500): def _change_statusbar(self, msg, timeout = 500):
self.ui.statusBar.showMessage(msg, timeout) self.ui.statusBar.showMessage(msg, timeout)
def _handle_qprogressbar(self, current, total):
progress = int((100 / total) * current)
self.ui.progressBar.setValue(progress)
def _get_checkbox_value(self, checkbox, default=None): def _get_checkbox_value(self, checkbox, default=None):
"""Helper function to get the value of a checkbox or a default value.""" """Helper function to get the value of a checkbox or a default value."""
return checkbox.isChecked() if checkbox else default return checkbox.isChecked() if checkbox else default
@ -251,18 +480,20 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
self.settings["own_date"] = self._get_checkbox_value(self.ui.add_date_checkBox) self.settings["own_date"] = self._get_checkbox_value(self.ui.add_date_checkBox)
# Conditional settings with logic # Conditional settings with logic
self.settings["resize"] = self._get_spinbox_value(self.ui.resize_spinBox) if self.ui.resize_checkbox.isChecked() else None self.settings["resize"] = int(self.ui.resize_spinBox.text()) if self.ui.resize_spinBox.text() != "100" else None
self.settings["brightness"] = self._get_spinbox_value(self.ui.brightness_spinBox) if self.ui.brightness_checkbox.isChecked() else None self.settings["brightness"] = int(self.ui.brightness_spinBox.text()) if self.ui.brightness_spinBox.text() != "0" else None
self.settings["contrast"] = self._get_spinbox_value(self.ui.contrast_spinBox) if self.ui.contrast_checkbox.isChecked() else None self.settings["contrast"] = int(self.ui.contrast_spinBox.text()) if self.ui.contrast_spinBox.text() != "0" else None
self.settings["new_file_names"] = self._get_text_value(self.ui.filename, False) if self.ui.rename_checkbox.isChecked() else False new_name = self._get_text_value(self.ui.filename, False) if self.ui.rename_checkbox.isChecked() else False
self.settings["watermark"] = self._get_text_value(self.ui.watermark_lineEdit) if self.ui.watermark_checkbox.isChecked() else None if isinstance(new_name, str): new_name = new_name.replace(" ", "_")
self.settings["new_file_names"] = new_name
self.settings["watermark"] = self.ui.watermark_lineEdit.text() if len(self.ui.watermark_lineEdit.text()) != 0 else None
# Handle EXIF data selection # Handle EXIF data selection
if self.settings["own_exif"]: if self.settings["own_exif"]:
self.user_selected_exif = self._get_selected_exif() self.settings["user_selected_exif"] = self._get_selected_exif()
else: else:
self.user_selected_exif = None self.settings["user_selected_exif"] = None
self.settings["gps"] = None self.settings["gps"] = None
def _get_date(self): def _get_date(self):
@ -280,9 +511,27 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
user_data["user_comment"] = self.ui.user_comment_comboBox.currentText() user_data["user_comment"] = self.ui.user_comment_comboBox.currentText()
user_data["artist"] = self.ui.artist_comboBox.currentText() user_data["artist"] = self.ui.artist_comboBox.currentText()
user_data["copyright_info"] = self.ui.copyright_info_comboBox.currentText() user_data["copyright_info"] = self.ui.copyright_info_comboBox.currentText()
user_data["software"] = f"{self.o.name} {self.o.version}" user_data["software"] = f"{self.name} {self.version} with {self.o.name} {self.o.version}"
return user_data return user_data
def closeEvent(self, event):
self.preview_window.close()
def check_version(self, min_version="0.6.6"):
# Mainly ChatGPT
from packaging import version # Use `packaging` for robust version comparison
current_version = self.o.version
if version.parse(current_version) < version.parse(min_version):
msg = (
f"optima35 version {current_version} detected.\n"
f"Minimum required version is {min_version}.\n"
"Please update the core package to continue.\n"
"https://pypi.org/project/optima35/"
)
QMessageBox.critical(None, "Version Error", msg)
sys.exit(1)
def main(): def main():
app = QtWidgets.QApplication(sys.argv) app = QtWidgets.QApplication(sys.argv)
window = OptimaLab35() window = OptimaLab35()

View file

@ -69,7 +69,7 @@ class ExifEditor(QMainWindow):
self.list_widget.addItem(new_item) self.list_widget.addItem(new_item)
self.line_edit.clear() self.line_edit.clear()
else: else:
QMessageBox.warning(self, "Warning", "Cannot add an empty item.") QMessageBox.warning(self, "Warning", f"Cannot add an empty item.\nDelete {self.exif_file}...")
def delete_item(self): def delete_item(self):
"""Delete the selected item from the list.""" """Delete the selected item from the list."""

View file

@ -20,18 +20,23 @@ from PySide6.QtWidgets import (QApplication, QCheckBox, QComboBox, QDateEdit,
QFrame, QGridLayout, QGroupBox, QHBoxLayout, QFrame, QGridLayout, QGroupBox, QHBoxLayout,
QLabel, QLineEdit, QMainWindow, QMenu, QLabel, QLineEdit, QMainWindow, QMenu,
QMenuBar, QProgressBar, QPushButton, QSizePolicy, QMenuBar, QProgressBar, QPushButton, QSizePolicy,
QSpinBox, QStatusBar, QTabWidget, QVBoxLayout, QSlider, QSpinBox, QStatusBar, QTabWidget,
QWidget) QVBoxLayout, QWidget)
class Ui_MainWindow(object): class Ui_MainWindow(object):
def setupUi(self, MainWindow): def setupUi(self, MainWindow):
if not MainWindow.objectName(): if not MainWindow.objectName():
MainWindow.setObjectName(u"MainWindow") MainWindow.setObjectName(u"MainWindow")
MainWindow.resize(450, 708) MainWindow.setWindowModality(Qt.NonModal)
MainWindow.resize(440, 756)
MainWindow.setMinimumSize(QSize(350, 677)) MainWindow.setMinimumSize(QSize(350, 677))
MainWindow.setMaximumSize(QSize(500, 1000)) MainWindow.setMaximumSize(QSize(1000, 1000))
self.actionInfo = QAction(MainWindow) self.actionInfo = QAction(MainWindow)
self.actionInfo.setObjectName(u"actionInfo") self.actionInfo.setObjectName(u"actionInfo")
self.actionPreview = QAction(MainWindow)
self.actionPreview.setObjectName(u"actionPreview")
self.actionAbout = QAction(MainWindow)
self.actionAbout.setObjectName(u"actionAbout")
self.centralwidget = QWidget(MainWindow) self.centralwidget = QWidget(MainWindow)
self.centralwidget.setObjectName(u"centralwidget") self.centralwidget.setObjectName(u"centralwidget")
self.gridLayout = QGridLayout(self.centralwidget) self.gridLayout = QGridLayout(self.centralwidget)
@ -48,25 +53,28 @@ class Ui_MainWindow(object):
self.folder_group.setMaximumSize(QSize(400, 16777215)) self.folder_group.setMaximumSize(QSize(400, 16777215))
self.gridLayout_5 = QGridLayout(self.folder_group) self.gridLayout_5 = QGridLayout(self.folder_group)
self.gridLayout_5.setObjectName(u"gridLayout_5") self.gridLayout_5.setObjectName(u"gridLayout_5")
self.input_folder_button = QPushButton(self.folder_group)
self.input_folder_button.setObjectName(u"input_folder_button")
self.input_folder_button.setMinimumSize(QSize(70, 0))
self.input_folder_button.setFlat(False)
self.gridLayout_5.addWidget(self.input_folder_button, 0, 1, 1, 1)
self.output_path = QLineEdit(self.folder_group)
self.output_path.setObjectName(u"output_path")
self.gridLayout_5.addWidget(self.output_path, 0, 2, 1, 1)
self.input_path = QLineEdit(self.folder_group) self.input_path = QLineEdit(self.folder_group)
self.input_path.setObjectName(u"input_path") self.input_path.setObjectName(u"input_path")
self.gridLayout_5.addWidget(self.input_path, 0, 0, 1, 1) self.gridLayout_5.addWidget(self.input_path, 0, 0, 1, 1)
self.output_path = QLineEdit(self.folder_group)
self.output_path.setObjectName(u"output_path")
self.gridLayout_5.addWidget(self.output_path, 0, 1, 1, 1)
self.input_folder_button = QPushButton(self.folder_group)
self.input_folder_button.setObjectName(u"input_folder_button")
self.gridLayout_5.addWidget(self.input_folder_button, 1, 0, 1, 1)
self.output_folder_button = QPushButton(self.folder_group) self.output_folder_button = QPushButton(self.folder_group)
self.output_folder_button.setObjectName(u"output_folder_button") self.output_folder_button.setObjectName(u"output_folder_button")
self.output_folder_button.setMinimumSize(QSize(70, 0))
self.gridLayout_5.addWidget(self.output_folder_button, 1, 1, 1, 1) self.gridLayout_5.addWidget(self.output_folder_button, 0, 3, 1, 1)
self.verticalLayout_10.addWidget(self.folder_group) self.verticalLayout_10.addWidget(self.folder_group)
@ -76,36 +84,38 @@ class Ui_MainWindow(object):
self.groupBox.setMaximumSize(QSize(400, 16777215)) self.groupBox.setMaximumSize(QSize(400, 16777215))
self.gridLayout_4 = QGridLayout(self.groupBox) self.gridLayout_4 = QGridLayout(self.groupBox)
self.gridLayout_4.setObjectName(u"gridLayout_4") self.gridLayout_4.setObjectName(u"gridLayout_4")
self.resize_checkbox = QCheckBox(self.groupBox) self.quality_label_2 = QLabel(self.groupBox)
self.resize_checkbox.setObjectName(u"resize_checkbox") self.quality_label_2.setObjectName(u"quality_label_2")
self.gridLayout_4.addWidget(self.resize_checkbox, 0, 0, 1, 1) self.gridLayout_4.addWidget(self.quality_label_2, 4, 0, 1, 1)
self.resize_spinBox = QSpinBox(self.groupBox)
self.resize_spinBox.setObjectName(u"resize_spinBox")
self.resize_spinBox.setEnabled(False)
self.resize_spinBox.setMinimum(1)
self.resize_spinBox.setMaximum(200)
self.resize_spinBox.setSingleStep(1)
self.resize_spinBox.setValue(80)
self.gridLayout_4.addWidget(self.resize_spinBox, 0, 1, 1, 1)
self.image_type = QComboBox(self.groupBox)
self.image_type.addItem(u"jpg")
self.image_type.addItem(u"png")
self.image_type.addItem(u"webp")
self.image_type.setObjectName(u"image_type")
self.gridLayout_4.addWidget(self.image_type, 1, 0, 1, 1)
self.jpg_quality_spinBox = QSpinBox(self.groupBox) self.jpg_quality_spinBox = QSpinBox(self.groupBox)
self.jpg_quality_spinBox.setObjectName(u"jpg_quality_spinBox") self.jpg_quality_spinBox.setObjectName(u"jpg_quality_spinBox")
self.jpg_quality_spinBox.setMinimum(1) self.jpg_quality_spinBox.setMinimum(1)
self.jpg_quality_spinBox.setMaximum(100) self.jpg_quality_spinBox.setMaximum(100)
self.jpg_quality_spinBox.setValue(80) self.jpg_quality_spinBox.setValue(90)
self.gridLayout_4.addWidget(self.jpg_quality_spinBox, 1, 1, 1, 1) self.gridLayout_4.addWidget(self.jpg_quality_spinBox, 3, 3, 1, 1)
self.label_11 = QLabel(self.groupBox)
self.label_11.setObjectName(u"label_11")
self.gridLayout_4.addWidget(self.label_11, 0, 0, 1, 1)
self.optimize_checkBox = QCheckBox(self.groupBox)
self.optimize_checkBox.setObjectName(u"optimize_checkBox")
self.gridLayout_4.addWidget(self.optimize_checkBox, 0, 3, 1, 1)
self.png_quality_Slider = QSlider(self.groupBox)
self.png_quality_Slider.setObjectName(u"png_quality_Slider")
self.png_quality_Slider.setMinimum(1)
self.png_quality_Slider.setMaximum(9)
self.png_quality_Slider.setPageStep(1)
self.png_quality_Slider.setSliderPosition(6)
self.png_quality_Slider.setOrientation(Qt.Horizontal)
self.gridLayout_4.addWidget(self.png_quality_Slider, 4, 2, 1, 1)
self.png_quality_spinBox = QSpinBox(self.groupBox) self.png_quality_spinBox = QSpinBox(self.groupBox)
self.png_quality_spinBox.setObjectName(u"png_quality_spinBox") self.png_quality_spinBox.setObjectName(u"png_quality_spinBox")
@ -114,72 +124,140 @@ class Ui_MainWindow(object):
self.png_quality_spinBox.setMaximum(9) self.png_quality_spinBox.setMaximum(9)
self.png_quality_spinBox.setValue(6) self.png_quality_spinBox.setValue(6)
self.gridLayout_4.addWidget(self.png_quality_spinBox, 1, 2, 1, 1) self.gridLayout_4.addWidget(self.png_quality_spinBox, 4, 3, 1, 1)
self.optimize_checkBox = QCheckBox(self.groupBox) self.quality_label_1 = QLabel(self.groupBox)
self.optimize_checkBox.setObjectName(u"optimize_checkBox") self.quality_label_1.setObjectName(u"quality_label_1")
self.gridLayout_4.addWidget(self.optimize_checkBox, 0, 2, 1, 1) self.gridLayout_4.addWidget(self.quality_label_1, 3, 0, 1, 1)
self.image_type = QComboBox(self.groupBox)
self.image_type.addItem(u"jpg")
self.image_type.addItem(u"png")
self.image_type.addItem(u"webp")
self.image_type.setObjectName(u"image_type")
self.gridLayout_4.addWidget(self.image_type, 0, 2, 1, 1)
self.jpg_quality_Slider = QSlider(self.groupBox)
self.jpg_quality_Slider.setObjectName(u"jpg_quality_Slider")
self.jpg_quality_Slider.setMinimum(1)
self.jpg_quality_Slider.setMaximum(100)
self.jpg_quality_Slider.setSliderPosition(90)
self.jpg_quality_Slider.setOrientation(Qt.Horizontal)
self.gridLayout_4.addWidget(self.jpg_quality_Slider, 3, 2, 1, 1)
self.label_13 = QLabel(self.groupBox)
self.label_13.setObjectName(u"label_13")
self.gridLayout_4.addWidget(self.label_13, 5, 0, 1, 1)
self.resize_Slider = QSlider(self.groupBox)
self.resize_Slider.setObjectName(u"resize_Slider")
self.resize_Slider.setMinimum(1)
self.resize_Slider.setMaximum(200)
self.resize_Slider.setValue(100)
self.resize_Slider.setOrientation(Qt.Horizontal)
self.gridLayout_4.addWidget(self.resize_Slider, 5, 2, 1, 1)
self.resize_spinBox = QSpinBox(self.groupBox)
self.resize_spinBox.setObjectName(u"resize_spinBox")
self.resize_spinBox.setEnabled(True)
self.resize_spinBox.setMinimum(1)
self.resize_spinBox.setMaximum(200)
self.resize_spinBox.setSingleStep(1)
self.resize_spinBox.setValue(100)
self.gridLayout_4.addWidget(self.resize_spinBox, 5, 3, 1, 1)
self.png_quality_spinBox.raise_()
self.resize_checkbox.raise_()
self.resize_spinBox.raise_()
self.image_type.raise_()
self.jpg_quality_spinBox.raise_()
self.optimize_checkBox.raise_()
self.verticalLayout_10.addWidget(self.groupBox) self.verticalLayout_10.addWidget(self.groupBox)
self.groupBox_2 = QGroupBox(self.tab_1) self.groupBox_2 = QGroupBox(self.tab_1)
self.groupBox_2.setObjectName(u"groupBox_2") self.groupBox_2.setObjectName(u"groupBox_2")
self.groupBox_2.setMaximumSize(QSize(400, 16777215)) self.groupBox_2.setMaximumSize(QSize(400, 16777215))
self.groupBox_2.setMouseTracking(False)
self.gridLayout_3 = QGridLayout(self.groupBox_2) self.gridLayout_3 = QGridLayout(self.groupBox_2)
self.gridLayout_3.setObjectName(u"gridLayout_3") self.gridLayout_3.setObjectName(u"gridLayout_3")
self.watermark_lineEdit = QLineEdit(self.groupBox_2) self.label_9 = QLabel(self.groupBox_2)
self.watermark_lineEdit.setObjectName(u"watermark_lineEdit") self.label_9.setObjectName(u"label_9")
self.watermark_lineEdit.setEnabled(False)
self.gridLayout_3.addWidget(self.watermark_lineEdit, 3, 0, 1, 3) self.gridLayout_3.addWidget(self.label_9, 1, 0, 1, 1)
self.brightness_checkbox = QCheckBox(self.groupBox_2) self.brightness_horizontalSlider = QSlider(self.groupBox_2)
self.brightness_checkbox.setObjectName(u"brightness_checkbox") self.brightness_horizontalSlider.setObjectName(u"brightness_horizontalSlider")
self.brightness_horizontalSlider.setMinimum(-100)
self.brightness_horizontalSlider.setMaximum(100)
self.brightness_horizontalSlider.setOrientation(Qt.Horizontal)
self.gridLayout_3.addWidget(self.brightness_checkbox, 0, 0, 1, 1) self.gridLayout_3.addWidget(self.brightness_horizontalSlider, 1, 1, 1, 1)
self.brightness_spinBox = QSpinBox(self.groupBox_2)
self.brightness_spinBox.setObjectName(u"brightness_spinBox")
self.brightness_spinBox.setEnabled(True)
self.brightness_spinBox.setMinimum(-100)
self.brightness_spinBox.setMaximum(100)
self.brightness_spinBox.setValue(0)
self.gridLayout_3.addWidget(self.brightness_spinBox, 1, 2, 1, 1)
self.contrast_spinBox = QSpinBox(self.groupBox_2)
self.contrast_spinBox.setObjectName(u"contrast_spinBox")
self.contrast_spinBox.setEnabled(True)
self.contrast_spinBox.setMinimum(-100)
self.contrast_spinBox.setMaximum(100)
self.contrast_spinBox.setValue(0)
self.gridLayout_3.addWidget(self.contrast_spinBox, 4, 2, 1, 1)
self.label_10 = QLabel(self.groupBox_2)
self.label_10.setObjectName(u"label_10")
self.gridLayout_3.addWidget(self.label_10, 4, 0, 1, 1)
self.contrast_horizontalSlider = QSlider(self.groupBox_2)
self.contrast_horizontalSlider.setObjectName(u"contrast_horizontalSlider")
self.contrast_horizontalSlider.setMinimum(-100)
self.contrast_horizontalSlider.setMaximum(100)
self.contrast_horizontalSlider.setOrientation(Qt.Horizontal)
self.gridLayout_3.addWidget(self.contrast_horizontalSlider, 4, 1, 1, 1)
self.grayscale_checkBox = QCheckBox(self.groupBox_2) self.grayscale_checkBox = QCheckBox(self.groupBox_2)
self.grayscale_checkBox.setObjectName(u"grayscale_checkBox") self.grayscale_checkBox.setObjectName(u"grayscale_checkBox")
self.gridLayout_3.addWidget(self.grayscale_checkBox, 0, 2, 1, 1) self.gridLayout_3.addWidget(self.grayscale_checkBox, 5, 0, 1, 2)
self.contrast_spinBox = QSpinBox(self.groupBox_2) self.preview_Button = QPushButton(self.groupBox_2)
self.contrast_spinBox.setObjectName(u"contrast_spinBox") self.preview_Button.setObjectName(u"preview_Button")
self.contrast_spinBox.setEnabled(False)
self.contrast_spinBox.setMinimum(-100)
self.contrast_spinBox.setMaximum(100)
self.contrast_spinBox.setValue(10)
self.gridLayout_3.addWidget(self.contrast_spinBox, 1, 1, 1, 1) self.gridLayout_3.addWidget(self.preview_Button, 5, 2, 1, 1)
self.watermark_checkbox = QCheckBox(self.groupBox_2)
self.watermark_checkbox.setObjectName(u"watermark_checkbox")
self.gridLayout_3.addWidget(self.watermark_checkbox, 2, 0, 1, 1) self.verticalLayout_10.addWidget(self.groupBox_2)
self.brightness_spinBox = QSpinBox(self.groupBox_2) self.groupBox_3 = QGroupBox(self.tab_1)
self.brightness_spinBox.setObjectName(u"brightness_spinBox") self.groupBox_3.setObjectName(u"groupBox_3")
self.brightness_spinBox.setEnabled(False) self.groupBox_3.setEnabled(True)
self.brightness_spinBox.setMinimum(-100) self.groupBox_3.setFlat(False)
self.brightness_spinBox.setMaximum(100) self.groupBox_3.setCheckable(False)
self.brightness_spinBox.setValue(-10) self.groupBox_3.setChecked(False)
self.horizontalLayout_5 = QHBoxLayout(self.groupBox_3)
self.horizontalLayout_5.setObjectName(u"horizontalLayout_5")
self.watermark_lineEdit = QLineEdit(self.groupBox_3)
self.watermark_lineEdit.setObjectName(u"watermark_lineEdit")
self.watermark_lineEdit.setEnabled(True)
self.gridLayout_3.addWidget(self.brightness_spinBox, 0, 1, 1, 1) self.horizontalLayout_5.addWidget(self.watermark_lineEdit)
self.contrast_checkbox = QCheckBox(self.groupBox_2) self.label_12 = QLabel(self.groupBox_3)
self.contrast_checkbox.setObjectName(u"contrast_checkbox") self.label_12.setObjectName(u"label_12")
self.gridLayout_3.addWidget(self.contrast_checkbox, 1, 0, 1, 1) self.horizontalLayout_5.addWidget(self.label_12)
self.font_size_comboBox = QComboBox(self.groupBox_2) self.font_size_comboBox = QComboBox(self.groupBox_3)
self.font_size_comboBox.addItem("") self.font_size_comboBox.addItem("")
self.font_size_comboBox.addItem("") self.font_size_comboBox.addItem("")
self.font_size_comboBox.addItem("") self.font_size_comboBox.addItem("")
@ -187,10 +265,10 @@ class Ui_MainWindow(object):
self.font_size_comboBox.addItem("") self.font_size_comboBox.addItem("")
self.font_size_comboBox.setObjectName(u"font_size_comboBox") self.font_size_comboBox.setObjectName(u"font_size_comboBox")
self.gridLayout_3.addWidget(self.font_size_comboBox, 2, 1, 1, 1) self.horizontalLayout_5.addWidget(self.font_size_comboBox)
self.verticalLayout_10.addWidget(self.groupBox_2) self.verticalLayout_10.addWidget(self.groupBox_3)
self.rename_group = QGroupBox(self.tab_1) self.rename_group = QGroupBox(self.tab_1)
self.rename_group.setObjectName(u"rename_group") self.rename_group.setObjectName(u"rename_group")
@ -234,6 +312,12 @@ class Ui_MainWindow(object):
self.horizontalLayout_3.addWidget(self.start_button) self.horizontalLayout_3.addWidget(self.start_button)
self.insert_exif_Button = QPushButton(self.widget_9)
self.insert_exif_Button.setObjectName(u"insert_exif_Button")
self.insert_exif_Button.setEnabled(False)
self.horizontalLayout_3.addWidget(self.insert_exif_Button)
self.verticalLayout_10.addWidget(self.widget_9) self.verticalLayout_10.addWidget(self.widget_9)
@ -465,29 +549,41 @@ class Ui_MainWindow(object):
MainWindow.setStatusBar(self.statusBar) MainWindow.setStatusBar(self.statusBar)
self.menuBar = QMenuBar(MainWindow) self.menuBar = QMenuBar(MainWindow)
self.menuBar.setObjectName(u"menuBar") self.menuBar.setObjectName(u"menuBar")
self.menuBar.setGeometry(QRect(0, 0, 450, 27)) self.menuBar.setGeometry(QRect(0, 0, 440, 27))
self.menuInfo = QMenu(self.menuBar) self.menuSettings = QMenu(self.menuBar)
self.menuInfo.setObjectName(u"menuInfo") self.menuSettings.setObjectName(u"menuSettings")
self.menuHelp = QMenu(self.menuBar)
self.menuHelp.setObjectName(u"menuHelp")
MainWindow.setMenuBar(self.menuBar) MainWindow.setMenuBar(self.menuBar)
self.menuBar.addAction(self.menuInfo.menuAction()) self.menuBar.addAction(self.menuSettings.menuAction())
self.menuInfo.addAction(self.actionInfo) self.menuBar.addAction(self.menuHelp.menuAction())
self.menuSettings.addAction(self.actionPreview)
self.menuHelp.addAction(self.actionAbout)
self.retranslateUi(MainWindow) self.retranslateUi(MainWindow)
self.resize_checkbox.toggled.connect(self.resize_spinBox.setEnabled)
self.brightness_checkbox.toggled.connect(self.brightness_spinBox.setEnabled)
self.contrast_checkbox.toggled.connect(self.contrast_spinBox.setEnabled)
self.watermark_checkbox.toggled.connect(self.watermark_lineEdit.setEnabled)
self.rename_checkbox.toggled.connect(self.filename.setEnabled) self.rename_checkbox.toggled.connect(self.filename.setEnabled)
self.exif_checkbox.toggled.connect(self.exif_options_group.setEnabled) self.exif_checkbox.toggled.connect(self.exif_options_group.setEnabled)
self.exif_checkbox.toggled.connect(self.exif_copy_checkBox.setDisabled) self.jpg_quality_Slider.valueChanged.connect(self.jpg_quality_spinBox.setValue)
self.exif_copy_checkBox.toggled.connect(self.exif_checkbox.setDisabled) self.exif_copy_checkBox.toggled.connect(self.exif_checkbox.setDisabled)
self.exif_checkbox.toggled.connect(self.exif_copy_checkBox.setDisabled)
self.resize_spinBox.valueChanged.connect(self.resize_Slider.setValue)
self.png_quality_spinBox.valueChanged.connect(self.png_quality_Slider.setValue)
self.contrast_horizontalSlider.valueChanged.connect(self.contrast_spinBox.setValue)
self.resize_Slider.valueChanged.connect(self.resize_spinBox.setValue)
self.brightness_horizontalSlider.valueChanged.connect(self.brightness_spinBox.setValue)
self.exif_checkbox.toggled.connect(self.date_groupBox.setEnabled)
self.png_quality_Slider.valueChanged.connect(self.png_quality_spinBox.setValue)
self.gps_checkBox.toggled.connect(self.lat_lineEdit.setEnabled)
self.exif_checkbox.toggled.connect(self.insert_exif_Button.setEnabled)
self.gps_checkBox.toggled.connect(self.long_lineEdit.setEnabled)
self.brightness_spinBox.valueChanged.connect(self.brightness_horizontalSlider.setValue)
self.resize_Slider.valueChanged.connect(self.resize_spinBox.setValue)
self.exif_checkbox.toggled.connect(self.gps_groupBox.setEnabled)
self.contrast_spinBox.valueChanged.connect(self.contrast_horizontalSlider.setValue)
self.exif_checkbox.toggled.connect(self.edit_exif_button.setEnabled) self.exif_checkbox.toggled.connect(self.edit_exif_button.setEnabled)
self.add_date_checkBox.toggled.connect(self.dateEdit.setEnabled) self.add_date_checkBox.toggled.connect(self.dateEdit.setEnabled)
self.exif_checkbox.toggled.connect(self.date_groupBox.setEnabled) self.jpg_quality_spinBox.valueChanged.connect(self.jpg_quality_Slider.setValue)
self.exif_checkbox.toggled.connect(self.gps_groupBox.setEnabled)
self.gps_checkBox.toggled.connect(self.lat_lineEdit.setEnabled)
self.gps_checkBox.toggled.connect(self.long_lineEdit.setEnabled)
self.tabWidget.setCurrentIndex(0) self.tabWidget.setCurrentIndex(0)
self.font_size_comboBox.setCurrentIndex(2) self.font_size_comboBox.setCurrentIndex(2)
@ -497,25 +593,32 @@ class Ui_MainWindow(object):
# setupUi # setupUi
def retranslateUi(self, MainWindow): def retranslateUi(self, MainWindow):
MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"OPTIMA-35", None)) MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"OptimaLab35", None))
self.actionInfo.setText(QCoreApplication.translate("MainWindow", u"Info", None)) self.actionInfo.setText(QCoreApplication.translate("MainWindow", u"About", None))
self.input_path.setText("") self.actionPreview.setText(QCoreApplication.translate("MainWindow", u"Preview image", None))
self.input_path.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Enter input folder", None)) self.actionAbout.setText(QCoreApplication.translate("MainWindow", u"About", None))
self.input_folder_button.setText(QCoreApplication.translate("MainWindow", u"Input", None))
self.output_path.setText("") self.output_path.setText("")
self.output_path.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Enter output folder", None)) self.output_path.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Output folder", None))
self.input_folder_button.setText(QCoreApplication.translate("MainWindow", u"input", None)) self.input_path.setText("")
self.output_folder_button.setText(QCoreApplication.translate("MainWindow", u"output", None)) self.input_path.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Input folder", None))
self.output_folder_button.setText(QCoreApplication.translate("MainWindow", u"Output", None))
self.groupBox.setTitle(QCoreApplication.translate("MainWindow", u"Essential group", None)) self.groupBox.setTitle(QCoreApplication.translate("MainWindow", u"Essential group", None))
self.resize_checkbox.setText(QCoreApplication.translate("MainWindow", u"Resize", None)) self.quality_label_2.setText(QCoreApplication.translate("MainWindow", u"Quality", None))
self.label_11.setText(QCoreApplication.translate("MainWindow", u"Export Format", None))
self.optimize_checkBox.setText(QCoreApplication.translate("MainWindow", u"optimize", None)) self.optimize_checkBox.setText(QCoreApplication.translate("MainWindow", u"optimize", None))
self.quality_label_1.setText(QCoreApplication.translate("MainWindow", u"Quality", None))
self.label_13.setText(QCoreApplication.translate("MainWindow", u"Resize", None))
self.groupBox_2.setTitle(QCoreApplication.translate("MainWindow", u"Extra stuff", None)) self.groupBox_2.setTitle(QCoreApplication.translate("MainWindow", u"Extra stuff", None))
self.label_9.setText(QCoreApplication.translate("MainWindow", u"Brightness", None))
self.label_10.setText(QCoreApplication.translate("MainWindow", u"Contrast", None))
self.grayscale_checkBox.setText(QCoreApplication.translate("MainWindow", u"Turn image to Black and White", None))
self.preview_Button.setText(QCoreApplication.translate("MainWindow", u"Preview", None))
self.groupBox_3.setTitle(QCoreApplication.translate("MainWindow", u"Watermark", None))
self.watermark_lineEdit.setText("") self.watermark_lineEdit.setText("")
self.watermark_lineEdit.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Enter Watermark", None)) self.watermark_lineEdit.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Enter Watermark", None))
self.brightness_checkbox.setText(QCoreApplication.translate("MainWindow", u"Brightness", None)) self.label_12.setText(QCoreApplication.translate("MainWindow", u"Size", None))
self.grayscale_checkBox.setText(QCoreApplication.translate("MainWindow", u"Grayscale", None))
self.watermark_checkbox.setText(QCoreApplication.translate("MainWindow", u"Watermark", None))
self.contrast_checkbox.setText(QCoreApplication.translate("MainWindow", u"Contrast", None))
self.font_size_comboBox.setItemText(0, QCoreApplication.translate("MainWindow", u"Tiny", None)) self.font_size_comboBox.setItemText(0, QCoreApplication.translate("MainWindow", u"Tiny", None))
self.font_size_comboBox.setItemText(1, QCoreApplication.translate("MainWindow", u"Small", None)) self.font_size_comboBox.setItemText(1, QCoreApplication.translate("MainWindow", u"Small", None))
self.font_size_comboBox.setItemText(2, QCoreApplication.translate("MainWindow", u"Normal", None)) self.font_size_comboBox.setItemText(2, QCoreApplication.translate("MainWindow", u"Normal", None))
@ -529,11 +632,12 @@ class Ui_MainWindow(object):
self.filename.setText("") self.filename.setText("")
self.filename.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Enter file name", None)) self.filename.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Enter file name", None))
self.start_button.setText(QCoreApplication.translate("MainWindow", u"Convert", None)) self.start_button.setText(QCoreApplication.translate("MainWindow", u"Convert", None))
self.insert_exif_Button.setText(QCoreApplication.translate("MainWindow", u"Insert Exif", None))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_1), QCoreApplication.translate("MainWindow", u"Main", None)) self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_1), QCoreApplication.translate("MainWindow", u"Main", None))
self.exif_group.setTitle(QCoreApplication.translate("MainWindow", u"EXIF EXPERIMENTAL", None)) self.exif_group.setTitle(QCoreApplication.translate("MainWindow", u"EXIF EXPERIMENTAL", None))
self.exif_checkbox.setText(QCoreApplication.translate("MainWindow", u"own exif", None)) self.exif_checkbox.setText(QCoreApplication.translate("MainWindow", u"own exif", None))
self.exif_copy_checkBox.setText(QCoreApplication.translate("MainWindow", u"copy exif", None)) self.exif_copy_checkBox.setText(QCoreApplication.translate("MainWindow", u"copy exif", None))
self.edit_exif_button.setText(QCoreApplication.translate("MainWindow", u"edit exif", None)) self.edit_exif_button.setText(QCoreApplication.translate("MainWindow", u"Edit Exif", None))
self.exif_options_group.setTitle(QCoreApplication.translate("MainWindow", u"Must", None)) self.exif_options_group.setTitle(QCoreApplication.translate("MainWindow", u"Must", None))
self.label_7.setText(QCoreApplication.translate("MainWindow", u"Artist", None)) self.label_7.setText(QCoreApplication.translate("MainWindow", u"Artist", None))
self.label_4.setText(QCoreApplication.translate("MainWindow", u"ISO", None)) self.label_4.setText(QCoreApplication.translate("MainWindow", u"ISO", None))
@ -554,6 +658,7 @@ class Ui_MainWindow(object):
self.date_groupBox.setTitle(QCoreApplication.translate("MainWindow", u"Optional", None)) self.date_groupBox.setTitle(QCoreApplication.translate("MainWindow", u"Optional", None))
self.add_date_checkBox.setText(QCoreApplication.translate("MainWindow", u"add date", None)) self.add_date_checkBox.setText(QCoreApplication.translate("MainWindow", u"add date", None))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), QCoreApplication.translate("MainWindow", u"EXIF", None)) self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), QCoreApplication.translate("MainWindow", u"EXIF", None))
self.menuInfo.setTitle(QCoreApplication.translate("MainWindow", u"Info", None)) self.menuSettings.setTitle(QCoreApplication.translate("MainWindow", u"Settings", None))
self.menuHelp.setTitle(QCoreApplication.translate("MainWindow", u"Help", None))
# retranslateUi # retranslateUi

View file

@ -2,12 +2,15 @@
<ui version="4.0"> <ui version="4.0">
<class>MainWindow</class> <class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow"> <widget class="QMainWindow" name="MainWindow">
<property name="windowModality">
<enum>Qt::NonModal</enum>
</property>
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>450</width> <width>440</width>
<height>708</height> <height>756</height>
</rect> </rect>
</property> </property>
<property name="minimumSize"> <property name="minimumSize">
@ -18,12 +21,12 @@
</property> </property>
<property name="maximumSize"> <property name="maximumSize">
<size> <size>
<width>500</width> <width>1000</width>
<height>1000</height> <height>1000</height>
</size> </size>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>OPTIMA-35</string> <string>OptimaLab35</string>
</property> </property>
<widget class="QWidget" name="centralwidget"> <widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
@ -52,37 +55,52 @@
</size> </size>
</property> </property>
<layout class="QGridLayout" name="gridLayout_5"> <layout class="QGridLayout" name="gridLayout_5">
<item row="0" column="1">
<widget class="QPushButton" name="input_folder_button">
<property name="minimumSize">
<size>
<width>70</width>
<height>0</height>
</size>
</property>
<property name="text">
<string extracomment="Comment?">Input</string>
</property>
<property name="flat">
<bool>false</bool>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLineEdit" name="output_path">
<property name="text">
<string/>
</property>
<property name="placeholderText">
<string>Output folder</string>
</property>
</widget>
</item>
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLineEdit" name="input_path"> <widget class="QLineEdit" name="input_path">
<property name="text"> <property name="text">
<string/> <string/>
</property> </property>
<property name="placeholderText"> <property name="placeholderText">
<string>Enter input folder</string> <string>Input folder</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="1"> <item row="0" column="3">
<widget class="QLineEdit" name="output_path">
<property name="text">
<string/>
</property>
<property name="placeholderText">
<string>Enter output folder</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QPushButton" name="input_folder_button">
<property name="text">
<string>input</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="output_folder_button"> <widget class="QPushButton" name="output_folder_button">
<property name="minimumSize">
<size>
<width>70</width>
<height>0</height>
</size>
</property>
<property name="text"> <property name="text">
<string>output</string> <string>Output</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -101,33 +119,83 @@
<string>Essential group</string> <string>Essential group</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout_4"> <layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="0"> <item row="4" column="0">
<widget class="QCheckBox" name="resize_checkbox"> <widget class="QLabel" name="quality_label_2">
<property name="text"> <property name="text">
<string>Resize</string> <string>Quality</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="1"> <item row="3" column="3">
<widget class="QSpinBox" name="resize_spinBox"> <widget class="QSpinBox" name="jpg_quality_spinBox">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>90</number>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>Export Format</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QCheckBox" name="optimize_checkBox">
<property name="text">
<string>optimize</string>
</property>
</widget>
</item>
<item row="4" column="2">
<widget class="QSlider" name="png_quality_Slider">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>9</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
<property name="sliderPosition">
<number>6</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="4" column="3">
<widget class="QSpinBox" name="png_quality_spinBox">
<property name="enabled"> <property name="enabled">
<bool>false</bool> <bool>true</bool>
</property> </property>
<property name="minimum"> <property name="minimum">
<number>1</number> <number>1</number>
</property> </property>
<property name="maximum"> <property name="maximum">
<number>200</number> <number>9</number>
</property>
<property name="singleStep">
<number>1</number>
</property> </property>
<property name="value"> <property name="value">
<number>80</number> <number>6</number>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="0"> <item row="3" column="0">
<widget class="QLabel" name="quality_label_1">
<property name="text">
<string>Quality</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QComboBox" name="image_type"> <widget class="QComboBox" name="image_type">
<item> <item>
<property name="text"> <property name="text">
@ -146,21 +214,47 @@
</item> </item>
</widget> </widget>
</item> </item>
<item row="1" column="1"> <item row="3" column="2">
<widget class="QSpinBox" name="jpg_quality_spinBox"> <widget class="QSlider" name="jpg_quality_Slider">
<property name="minimum"> <property name="minimum">
<number>1</number> <number>1</number>
</property> </property>
<property name="maximum"> <property name="maximum">
<number>100</number> <number>100</number>
</property> </property>
<property name="value"> <property name="sliderPosition">
<number>80</number> <number>90</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="2"> <item row="5" column="0">
<widget class="QSpinBox" name="png_quality_spinBox"> <widget class="QLabel" name="label_13">
<property name="text">
<string>Resize</string>
</property>
</widget>
</item>
<item row="5" column="2">
<widget class="QSlider" name="resize_Slider">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>200</number>
</property>
<property name="value">
<number>100</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="5" column="3">
<widget class="QSpinBox" name="resize_spinBox">
<property name="enabled"> <property name="enabled">
<bool>true</bool> <bool>true</bool>
</property> </property>
@ -168,27 +262,17 @@
<number>1</number> <number>1</number>
</property> </property>
<property name="maximum"> <property name="maximum">
<number>9</number> <number>200</number>
</property>
<property name="singleStep">
<number>1</number>
</property> </property>
<property name="value"> <property name="value">
<number>6</number> <number>100</number>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QCheckBox" name="optimize_checkBox">
<property name="text">
<string>optimize</string>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
<zorder>png_quality_spinBox</zorder>
<zorder>resize_checkbox</zorder>
<zorder>resize_spinBox</zorder>
<zorder>image_type</zorder>
<zorder>jpg_quality_spinBox</zorder>
<zorder>optimize_checkBox</zorder>
</widget> </widget>
</item> </item>
<item> <item>
@ -199,14 +283,124 @@
<height>16777215</height> <height>16777215</height>
</size> </size>
</property> </property>
<property name="mouseTracking">
<bool>false</bool>
</property>
<property name="title"> <property name="title">
<string>Extra stuff</string> <string>Extra stuff</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout_3"> <layout class="QGridLayout" name="gridLayout_3">
<item row="3" column="0" colspan="3"> <item row="1" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Brightness</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSlider" name="brightness_horizontalSlider">
<property name="minimum">
<number>-100</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QSpinBox" name="brightness_spinBox">
<property name="enabled">
<bool>true</bool>
</property>
<property name="minimum">
<number>-100</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item row="4" column="2">
<widget class="QSpinBox" name="contrast_spinBox">
<property name="enabled">
<bool>true</bool>
</property>
<property name="minimum">
<number>-100</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>Contrast</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QSlider" name="contrast_horizontalSlider">
<property name="minimum">
<number>-100</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="5" column="0" colspan="2">
<widget class="QCheckBox" name="grayscale_checkBox">
<property name="text">
<string>Turn image to Black and White</string>
</property>
</widget>
</item>
<item row="5" column="2">
<widget class="QPushButton" name="preview_Button">
<property name="text">
<string>Preview</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_3">
<property name="enabled">
<bool>true</bool>
</property>
<property name="title">
<string>Watermark</string>
</property>
<property name="flat">
<bool>false</bool>
</property>
<property name="checkable">
<bool>false</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QLineEdit" name="watermark_lineEdit"> <widget class="QLineEdit" name="watermark_lineEdit">
<property name="enabled"> <property name="enabled">
<bool>false</bool> <bool>true</bool>
</property> </property>
<property name="text"> <property name="text">
<string/> <string/>
@ -216,67 +410,14 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="0"> <item>
<widget class="QCheckBox" name="brightness_checkbox"> <widget class="QLabel" name="label_12">
<property name="text"> <property name="text">
<string>Brightness</string> <string>Size</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="2"> <item>
<widget class="QCheckBox" name="grayscale_checkBox">
<property name="text">
<string>Grayscale</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="contrast_spinBox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimum">
<number>-100</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>10</number>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="watermark_checkbox">
<property name="text">
<string>Watermark</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="brightness_spinBox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimum">
<number>-100</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>-10</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="contrast_checkbox">
<property name="text">
<string>Contrast</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="font_size_comboBox"> <widget class="QComboBox" name="font_size_comboBox">
<property name="currentText"> <property name="currentText">
<string>Normal</string> <string>Normal</string>
@ -385,6 +526,16 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QPushButton" name="insert_exif_Button">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Insert Exif</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>
@ -424,7 +575,7 @@
<bool>false</bool> <bool>false</bool>
</property> </property>
<property name="text"> <property name="text">
<string>edit exif</string> <string>Edit Exif</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -671,90 +822,43 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>450</width> <width>440</width>
<height>27</height> <height>27</height>
</rect> </rect>
</property> </property>
<widget class="QMenu" name="menuInfo"> <widget class="QMenu" name="menuSettings">
<property name="title"> <property name="title">
<string>Info</string> <string>Settings</string>
</property> </property>
<addaction name="actionInfo"/> <addaction name="actionPreview"/>
</widget> </widget>
<addaction name="menuInfo"/> <widget class="QMenu" name="menuHelp">
<property name="title">
<string>Help</string>
</property>
<addaction name="actionAbout"/>
</widget>
<addaction name="menuSettings"/>
<addaction name="menuHelp"/>
</widget> </widget>
<action name="actionInfo"> <action name="actionInfo">
<property name="text"> <property name="text">
<string>Info</string> <string>About</string>
</property>
</action>
<action name="actionPreview">
<property name="text">
<string>Preview image</string>
</property>
</action>
<action name="actionAbout">
<property name="text">
<string>About</string>
</property> </property>
</action> </action>
</widget> </widget>
<resources/> <resources/>
<connections> <connections>
<connection>
<sender>resize_checkbox</sender>
<signal>toggled(bool)</signal>
<receiver>resize_spinBox</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>75</x>
<y>96</y>
</hint>
<hint type="destinationlabel">
<x>196</x>
<y>118</y>
</hint>
</hints>
</connection>
<connection>
<sender>brightness_checkbox</sender>
<signal>toggled(bool)</signal>
<receiver>brightness_spinBox</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>83</x>
<y>363</y>
</hint>
<hint type="destinationlabel">
<x>83</x>
<y>399</y>
</hint>
</hints>
</connection>
<connection>
<sender>contrast_checkbox</sender>
<signal>toggled(bool)</signal>
<receiver>contrast_spinBox</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>185</x>
<y>363</y>
</hint>
<hint type="destinationlabel">
<x>185</x>
<y>399</y>
</hint>
</hints>
</connection>
<connection>
<sender>watermark_checkbox</sender>
<signal>toggled(bool)</signal>
<receiver>watermark_lineEdit</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>83</x>
<y>435</y>
</hint>
<hint type="destinationlabel">
<x>237</x>
<y>435</y>
</hint>
</hints>
</connection>
<connection> <connection>
<sender>rename_checkbox</sender> <sender>rename_checkbox</sender>
<signal>toggled(bool)</signal> <signal>toggled(bool)</signal>
@ -788,18 +892,18 @@
</hints> </hints>
</connection> </connection>
<connection> <connection>
<sender>exif_checkbox</sender> <sender>jpg_quality_Slider</sender>
<signal>toggled(bool)</signal> <signal>valueChanged(int)</signal>
<receiver>exif_copy_checkBox</receiver> <receiver>jpg_quality_spinBox</receiver>
<slot>setDisabled(bool)</slot> <slot>setValue(int)</slot>
<hints> <hints>
<hint type="sourcelabel"> <hint type="sourcelabel">
<x>130</x> <x>218</x>
<y>105</y> <y>289</y>
</hint> </hint>
<hint type="destinationlabel"> <hint type="destinationlabel">
<x>332</x> <x>380</x>
<y>105</y> <y>289</y>
</hint> </hint>
</hints> </hints>
</connection> </connection>
@ -819,6 +923,246 @@
</hint> </hint>
</hints> </hints>
</connection> </connection>
<connection>
<sender>exif_checkbox</sender>
<signal>toggled(bool)</signal>
<receiver>exif_copy_checkBox</receiver>
<slot>setDisabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>130</x>
<y>105</y>
</hint>
<hint type="destinationlabel">
<x>332</x>
<y>105</y>
</hint>
</hints>
</connection>
<connection>
<sender>resize_spinBox</sender>
<signal>valueChanged(int)</signal>
<receiver>resize_Slider</receiver>
<slot>setValue(int)</slot>
<hints>
<hint type="sourcelabel">
<x>380</x>
<y>252</y>
</hint>
<hint type="destinationlabel">
<x>218</x>
<y>252</y>
</hint>
</hints>
</connection>
<connection>
<sender>png_quality_spinBox</sender>
<signal>valueChanged(int)</signal>
<receiver>png_quality_Slider</receiver>
<slot>setValue(int)</slot>
<hints>
<hint type="sourcelabel">
<x>380</x>
<y>326</y>
</hint>
<hint type="destinationlabel">
<x>218</x>
<y>326</y>
</hint>
</hints>
</connection>
<connection>
<sender>contrast_horizontalSlider</sender>
<signal>valueChanged(int)</signal>
<receiver>contrast_spinBox</receiver>
<slot>setValue(int)</slot>
<hints>
<hint type="sourcelabel">
<x>187</x>
<y>458</y>
</hint>
<hint type="destinationlabel">
<x>378</x>
<y>458</y>
</hint>
</hints>
</connection>
<connection>
<sender>resize_Slider</sender>
<signal>valueChanged(int)</signal>
<receiver>resize_spinBox</receiver>
<slot>setValue(int)</slot>
<hints>
<hint type="sourcelabel">
<x>218</x>
<y>252</y>
</hint>
<hint type="destinationlabel">
<x>380</x>
<y>252</y>
</hint>
</hints>
</connection>
<connection>
<sender>brightness_horizontalSlider</sender>
<signal>valueChanged(int)</signal>
<receiver>brightness_spinBox</receiver>
<slot>setValue(int)</slot>
<hints>
<hint type="sourcelabel">
<x>187</x>
<y>393</y>
</hint>
<hint type="destinationlabel">
<x>378</x>
<y>393</y>
</hint>
</hints>
</connection>
<connection>
<sender>exif_checkbox</sender>
<signal>toggled(bool)</signal>
<receiver>date_groupBox</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>126</x>
<y>103</y>
</hint>
<hint type="destinationlabel">
<x>224</x>
<y>589</y>
</hint>
</hints>
</connection>
<connection>
<sender>png_quality_Slider</sender>
<signal>valueChanged(int)</signal>
<receiver>png_quality_spinBox</receiver>
<slot>setValue(int)</slot>
<hints>
<hint type="sourcelabel">
<x>218</x>
<y>326</y>
</hint>
<hint type="destinationlabel">
<x>380</x>
<y>326</y>
</hint>
</hints>
</connection>
<connection>
<sender>gps_checkBox</sender>
<signal>toggled(bool)</signal>
<receiver>lat_lineEdit</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>72</x>
<y>547</y>
</hint>
<hint type="destinationlabel">
<x>192</x>
<y>547</y>
</hint>
</hints>
</connection>
<connection>
<sender>exif_checkbox</sender>
<signal>toggled(bool)</signal>
<receiver>insert_exif_Button</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>92</x>
<y>130</y>
</hint>
<hint type="destinationlabel">
<x>369</x>
<y>684</y>
</hint>
</hints>
</connection>
<connection>
<sender>gps_checkBox</sender>
<signal>toggled(bool)</signal>
<receiver>long_lineEdit</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>72</x>
<y>547</y>
</hint>
<hint type="destinationlabel">
<x>344</x>
<y>547</y>
</hint>
</hints>
</connection>
<connection>
<sender>brightness_spinBox</sender>
<signal>valueChanged(int)</signal>
<receiver>brightness_horizontalSlider</receiver>
<slot>setValue(int)</slot>
<hints>
<hint type="sourcelabel">
<x>378</x>
<y>393</y>
</hint>
<hint type="destinationlabel">
<x>187</x>
<y>393</y>
</hint>
</hints>
</connection>
<connection>
<sender>resize_Slider</sender>
<signal>valueChanged(int)</signal>
<receiver>resize_spinBox</receiver>
<slot>setValue(int)</slot>
<hints>
<hint type="sourcelabel">
<x>218</x>
<y>252</y>
</hint>
<hint type="destinationlabel">
<x>380</x>
<y>252</y>
</hint>
</hints>
</connection>
<connection>
<sender>exif_checkbox</sender>
<signal>toggled(bool)</signal>
<receiver>gps_groupBox</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>94</x>
<y>103</y>
</hint>
<hint type="destinationlabel">
<x>224</x>
<y>535</y>
</hint>
</hints>
</connection>
<connection>
<sender>contrast_spinBox</sender>
<signal>valueChanged(int)</signal>
<receiver>contrast_horizontalSlider</receiver>
<slot>setValue(int)</slot>
<hints>
<hint type="sourcelabel">
<x>378</x>
<y>458</y>
</hint>
<hint type="destinationlabel">
<x>187</x>
<y>458</y>
</hint>
</hints>
</connection>
<connection> <connection>
<sender>exif_checkbox</sender> <sender>exif_checkbox</sender>
<signal>toggled(bool)</signal> <signal>toggled(bool)</signal>
@ -852,66 +1196,18 @@
</hints> </hints>
</connection> </connection>
<connection> <connection>
<sender>exif_checkbox</sender> <sender>jpg_quality_spinBox</sender>
<signal>toggled(bool)</signal> <signal>valueChanged(int)</signal>
<receiver>date_groupBox</receiver> <receiver>jpg_quality_Slider</receiver>
<slot>setEnabled(bool)</slot> <slot>setValue(int)</slot>
<hints> <hints>
<hint type="sourcelabel"> <hint type="sourcelabel">
<x>126</x> <x>380</x>
<y>103</y> <y>289</y>
</hint> </hint>
<hint type="destinationlabel"> <hint type="destinationlabel">
<x>224</x> <x>218</x>
<y>589</y> <y>289</y>
</hint>
</hints>
</connection>
<connection>
<sender>exif_checkbox</sender>
<signal>toggled(bool)</signal>
<receiver>gps_groupBox</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>94</x>
<y>103</y>
</hint>
<hint type="destinationlabel">
<x>224</x>
<y>535</y>
</hint>
</hints>
</connection>
<connection>
<sender>gps_checkBox</sender>
<signal>toggled(bool)</signal>
<receiver>lat_lineEdit</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>72</x>
<y>547</y>
</hint>
<hint type="destinationlabel">
<x>192</x>
<y>547</y>
</hint>
</hints>
</connection>
<connection>
<sender>gps_checkBox</sender>
<signal>toggled(bool)</signal>
<receiver>long_lineEdit</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>72</x>
<y>547</y>
</hint>
<hint type="destinationlabel">
<x>344</x>
<y>547</y>
</hint> </hint>
</hints> </hints>
</connection> </connection>

View file

@ -0,0 +1,191 @@
# -*- coding: utf-8 -*-
################################################################################
## Form generated from reading UI file 'preview_window.ui'
##
## Created by: Qt User Interface Compiler version 6.8.1
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
QMetaObject, QObject, QPoint, QRect,
QSize, QTime, QUrl, Qt)
from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
QFont, QFontDatabase, QGradient, QIcon,
QImage, QKeySequence, QLinearGradient, QPainter,
QPalette, QPixmap, QRadialGradient, QTransform)
from PySide6.QtWidgets import (QApplication, QCheckBox, QFrame, QHBoxLayout,
QLabel, QLineEdit, QMainWindow, QMenuBar,
QPushButton, QSizePolicy, QSlider, QSpinBox,
QVBoxLayout, QWidget)
class Ui_Preview_Window(object):
def setupUi(self, Preview_Window):
if not Preview_Window.objectName():
Preview_Window.setObjectName(u"Preview_Window")
Preview_Window.resize(803, 700)
Preview_Window.setMinimumSize(QSize(800, 700))
self.centralwidget = QWidget(Preview_Window)
self.centralwidget.setObjectName(u"centralwidget")
self.horizontalLayout = QHBoxLayout(self.centralwidget)
self.horizontalLayout.setObjectName(u"horizontalLayout")
self.QLabel = QLabel(self.centralwidget)
self.QLabel.setObjectName(u"QLabel")
self.QLabel.setMinimumSize(QSize(628, 628))
self.QLabel.setFrameShape(QFrame.Box)
self.QLabel.setScaledContents(True)
self.horizontalLayout.addWidget(self.QLabel)
self.widget = QWidget(self.centralwidget)
self.widget.setObjectName(u"widget")
self.widget.setMinimumSize(QSize(140, 628))
self.widget.setMaximumSize(QSize(140, 16777215))
self.verticalLayout_3 = QVBoxLayout(self.widget)
self.verticalLayout_3.setObjectName(u"verticalLayout_3")
self.image_path_lineEdit = QLineEdit(self.widget)
self.image_path_lineEdit.setObjectName(u"image_path_lineEdit")
self.verticalLayout_3.addWidget(self.image_path_lineEdit)
self.load_Button = QPushButton(self.widget)
self.load_Button.setObjectName(u"load_Button")
self.verticalLayout_3.addWidget(self.load_Button)
self.widget_2 = QWidget(self.widget)
self.widget_2.setObjectName(u"widget_2")
self.widget_2.setMaximumSize(QSize(16777215, 120))
self.verticalLayout = QVBoxLayout(self.widget_2)
self.verticalLayout.setObjectName(u"verticalLayout")
self.label = QLabel(self.widget_2)
self.label.setObjectName(u"label")
self.verticalLayout.addWidget(self.label)
self.brightness_spinBox = QSpinBox(self.widget_2)
self.brightness_spinBox.setObjectName(u"brightness_spinBox")
self.brightness_spinBox.setMinimum(-100)
self.brightness_spinBox.setMaximum(100)
self.verticalLayout.addWidget(self.brightness_spinBox)
self.brightness_Slider = QSlider(self.widget_2)
self.brightness_Slider.setObjectName(u"brightness_Slider")
self.brightness_Slider.setMinimum(-100)
self.brightness_Slider.setMaximum(100)
self.brightness_Slider.setOrientation(Qt.Horizontal)
self.verticalLayout.addWidget(self.brightness_Slider)
self.reset_brightness_Button = QPushButton(self.widget_2)
self.reset_brightness_Button.setObjectName(u"reset_brightness_Button")
self.verticalLayout.addWidget(self.reset_brightness_Button)
self.verticalLayout_3.addWidget(self.widget_2)
self.widget_3 = QWidget(self.widget)
self.widget_3.setObjectName(u"widget_3")
self.widget_3.setMaximumSize(QSize(16777215, 120))
self.verticalLayout_2 = QVBoxLayout(self.widget_3)
self.verticalLayout_2.setObjectName(u"verticalLayout_2")
self.label_2 = QLabel(self.widget_3)
self.label_2.setObjectName(u"label_2")
self.verticalLayout_2.addWidget(self.label_2)
self.contrast_spinBox = QSpinBox(self.widget_3)
self.contrast_spinBox.setObjectName(u"contrast_spinBox")
self.contrast_spinBox.setMinimum(-100)
self.contrast_spinBox.setMaximum(100)
self.verticalLayout_2.addWidget(self.contrast_spinBox)
self.contrast_Slider = QSlider(self.widget_3)
self.contrast_Slider.setObjectName(u"contrast_Slider")
self.contrast_Slider.setMinimum(-100)
self.contrast_Slider.setMaximum(100)
self.contrast_Slider.setOrientation(Qt.Horizontal)
self.verticalLayout_2.addWidget(self.contrast_Slider)
self.reset_contrast_Button = QPushButton(self.widget_3)
self.reset_contrast_Button.setObjectName(u"reset_contrast_Button")
self.verticalLayout_2.addWidget(self.reset_contrast_Button)
self.verticalLayout_3.addWidget(self.widget_3)
self.grayscale_checkBox = QCheckBox(self.widget)
self.grayscale_checkBox.setObjectName(u"grayscale_checkBox")
self.verticalLayout_3.addWidget(self.grayscale_checkBox)
self.update_Button = QPushButton(self.widget)
self.update_Button.setObjectName(u"update_Button")
self.verticalLayout_3.addWidget(self.update_Button)
self.widget_4 = QWidget(self.widget)
self.widget_4.setObjectName(u"widget_4")
self.verticalLayout_4 = QVBoxLayout(self.widget_4)
self.verticalLayout_4.setObjectName(u"verticalLayout_4")
self.label_3 = QLabel(self.widget_4)
self.label_3.setObjectName(u"label_3")
self.label_3.setScaledContents(False)
self.label_3.setWordWrap(True)
self.verticalLayout_4.addWidget(self.label_3)
self.checkBox = QCheckBox(self.widget_4)
self.checkBox.setObjectName(u"checkBox")
self.checkBox.setChecked(True)
self.verticalLayout_4.addWidget(self.checkBox)
self.close_Button = QPushButton(self.widget_4)
self.close_Button.setObjectName(u"close_Button")
self.verticalLayout_4.addWidget(self.close_Button)
self.verticalLayout_3.addWidget(self.widget_4)
self.horizontalLayout.addWidget(self.widget)
Preview_Window.setCentralWidget(self.centralwidget)
self.menubar = QMenuBar(Preview_Window)
self.menubar.setObjectName(u"menubar")
self.menubar.setGeometry(QRect(0, 0, 803, 27))
Preview_Window.setMenuBar(self.menubar)
self.retranslateUi(Preview_Window)
self.brightness_Slider.valueChanged.connect(self.brightness_spinBox.setValue)
self.brightness_spinBox.valueChanged.connect(self.brightness_Slider.setValue)
self.contrast_Slider.valueChanged.connect(self.contrast_spinBox.setValue)
self.contrast_spinBox.valueChanged.connect(self.contrast_Slider.setValue)
QMetaObject.connectSlotsByName(Preview_Window)
# setupUi
def retranslateUi(self, Preview_Window):
Preview_Window.setWindowTitle(QCoreApplication.translate("Preview_Window", u"OptimaLab35 - Preview", None))
self.QLabel.setText("")
self.image_path_lineEdit.setPlaceholderText(QCoreApplication.translate("Preview_Window", u"Path to image", None))
self.load_Button.setText(QCoreApplication.translate("Preview_Window", u"Select image", None))
self.label.setText(QCoreApplication.translate("Preview_Window", u"Brightness", None))
self.reset_brightness_Button.setText(QCoreApplication.translate("Preview_Window", u"Reset", None))
self.label_2.setText(QCoreApplication.translate("Preview_Window", u"Contrast", None))
self.reset_contrast_Button.setText(QCoreApplication.translate("Preview_Window", u"Reset", None))
self.grayscale_checkBox.setText(QCoreApplication.translate("Preview_Window", u"Grayscale", None))
self.update_Button.setText(QCoreApplication.translate("Preview_Window", u"Update preview", None))
self.label_3.setText(QCoreApplication.translate("Preview_Window", u"Copy values to main window when closing", None))
self.checkBox.setText(QCoreApplication.translate("Preview_Window", u"Copy Values", None))
self.close_Button.setText(QCoreApplication.translate("Preview_Window", u"Close", None))
# retranslateUi

View file

@ -0,0 +1,303 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Preview_Window</class>
<widget class="QMainWindow" name="Preview_Window">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>803</width>
<height>700</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>800</width>
<height>700</height>
</size>
</property>
<property name="windowTitle">
<string>OptimaLab35 - Preview</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="QLabel">
<property name="minimumSize">
<size>
<width>628</width>
<height>628</height>
</size>
</property>
<property name="frameShape">
<enum>QFrame::Box</enum>
</property>
<property name="text">
<string/>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="widget" native="true">
<property name="minimumSize">
<size>
<width>140</width>
<height>628</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>140</width>
<height>16777215</height>
</size>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLineEdit" name="image_path_lineEdit">
<property name="placeholderText">
<string>Path to image</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="load_Button">
<property name="text">
<string>Select image</string>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="widget_2" native="true">
<property name="maximumSize">
<size>
<width>16777215</width>
<height>120</height>
</size>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Brightness</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="brightness_spinBox">
<property name="minimum">
<number>-100</number>
</property>
<property name="maximum">
<number>100</number>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="brightness_Slider">
<property name="minimum">
<number>-100</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="reset_brightness_Button">
<property name="text">
<string>Reset</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="widget_3" native="true">
<property name="maximumSize">
<size>
<width>16777215</width>
<height>120</height>
</size>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Contrast</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="contrast_spinBox">
<property name="minimum">
<number>-100</number>
</property>
<property name="maximum">
<number>100</number>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="contrast_Slider">
<property name="minimum">
<number>-100</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="reset_contrast_Button">
<property name="text">
<string>Reset</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QCheckBox" name="grayscale_checkBox">
<property name="text">
<string>Grayscale</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="update_Button">
<property name="text">
<string>Update preview</string>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="widget_4" native="true">
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Copy values to main window when closing</string>
</property>
<property name="scaledContents">
<bool>false</bool>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkBox">
<property name="text">
<string>Copy Values</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="close_Button">
<property name="text">
<string>Close</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>803</width>
<height>27</height>
</rect>
</property>
</widget>
</widget>
<resources/>
<connections>
<connection>
<sender>brightness_Slider</sender>
<signal>valueChanged(int)</signal>
<receiver>brightness_spinBox</receiver>
<slot>setValue(int)</slot>
<hints>
<hint type="sourcelabel">
<x>720</x>
<y>311</y>
</hint>
<hint type="destinationlabel">
<x>706</x>
<y>282</y>
</hint>
</hints>
</connection>
<connection>
<sender>brightness_spinBox</sender>
<signal>valueChanged(int)</signal>
<receiver>brightness_Slider</receiver>
<slot>setValue(int)</slot>
<hints>
<hint type="sourcelabel">
<x>706</x>
<y>282</y>
</hint>
<hint type="destinationlabel">
<x>720</x>
<y>311</y>
</hint>
</hints>
</connection>
<connection>
<sender>contrast_Slider</sender>
<signal>valueChanged(int)</signal>
<receiver>contrast_spinBox</receiver>
<slot>setValue(int)</slot>
<hints>
<hint type="sourcelabel">
<x>720</x>
<y>454</y>
</hint>
<hint type="destinationlabel">
<x>699</x>
<y>425</y>
</hint>
</hints>
</connection>
<connection>
<sender>contrast_spinBox</sender>
<signal>valueChanged(int)</signal>
<receiver>contrast_Slider</receiver>
<slot>setValue(int)</slot>
<hints>
<hint type="sourcelabel">
<x>699</x>
<y>425</y>
</hint>
<hint type="destinationlabel">
<x>720</x>
<y>454</y>
</hint>
</hints>
</connection>
</connections>
</ui>

6
src/OptimaLab35/update_ui.sh Executable file
View file

@ -0,0 +1,6 @@
#!/bin/bash
echo "Update .ui files with pyside"
echo "Update main window."
pyside6-uic OptimaLab35/ui/main_window.ui -o OptimaLab35/ui/main_window.py
echo "Update preview window."
pyside6-uic OptimaLab35/ui/preview_window.ui -o OptimaLab35/ui/preview_window.py