refactor: Splitting Classes into Separate Files

This commit is contained in:
Mr Finchum 2025-03-23 21:48:46 +00:00
parent ce2dd90a39
commit 17f08bc74f
6 changed files with 568 additions and 517 deletions

View file

@ -1,11 +1,16 @@
# Changelog
### 1.2.x: Refactor
#### 1.2.0: Splitting Classes into Separate Files
- Refactored `gui.py`, which previously contained almost all the code, into multiple files.
- Each window's logic is now in its own file, improving code organization.
- Window layouts remain in `.ui` folder, while their logic is now properly separated.
## 1.1.x
### 1.1.0: New Function in Preview Window
- Added a new feature to the preview window: **Hold a button to temporarily view the original (unedited) image.** This makes it easier to compare changes.
- Minor UI adjustments.
## 1.0.x
### 1.0.1: Fixed spelling
- Fixes spelling some places

View file

@ -1,6 +0,0 @@
# TODO
## Improvments
### Preview window
Improve performence by changing when the images is updated.
Right now, when moving the slider, the image gets updated for every single step increase resulting in lagging

View file

@ -1,4 +1,30 @@
from .gui import main
import sys
from PySide6 import QtWidgets
from .utils.utility import Utilities
from .mainWindow import OptimaLab35
from .const import (
CONFIG_BASE_PATH
)
def main():
u = Utilities(CONFIG_BASE_PATH)
app_settings = u.load_settings()
app = QtWidgets.QApplication(sys.argv)
try:
import qdarktheme
app_settings["theme"]["theme_pkg"] = True
except ImportError:
app_settings["theme"]["theme_pkg"] = False
if app_settings["theme"]["use_custom_theme"] and app_settings["theme"]["theme_pkg"]:
qdarktheme.setup_theme(app_settings["theme"]["mode"].lower())
u.save_settings(app_settings)
window = OptimaLab35()
window.show()
app.exec()
if __name__ == "__main__":
main()

View file

@ -1,23 +1,22 @@
import sys
import os
from datetime import datetime
from .ui import resources_rc
from PyPiUpdater import PyPiUpdater
from optima35.core import OptimaManager
from .utils.utility import Utilities
from .ui.main_window import Ui_MainWindow
from .ui.preview_window import Ui_Preview_Window
from .ui.settings_window import Ui_Settings_Window
from .ui.exif_handler_window import ExifEditor
from .ui.simple_dialog import SimpleDialog # Import the SimpleDialog class
from OptimaLab35 import __version__
from .const import (
APPLICATION_NAME,
CONFIG_BASE_PATH
)
from .ui import resources_rc
from .previewWindow import PreviewWindow
from .settingsWindow import SettingsWindow
from .utils.utility import Utilities
from .ui.main_window import Ui_MainWindow
from .ui.exif_handler_window import ExifEditor
from .ui.simple_dialog import SimpleDialog # Import the SimpleDialog class
from PySide6 import QtWidgets, QtCore
from PySide6.QtCore import (
@ -26,9 +25,7 @@ from PySide6.QtCore import (
Signal,
QObject,
QRegularExpression,
Qt,
QTimer,
Slot
Qt
)
from PySide6.QtWidgets import (
@ -38,7 +35,7 @@ from PySide6.QtWidgets import (
QFileDialog
)
from PySide6.QtGui import QPixmap, QRegularExpressionValidator, QIcon
from PySide6.QtGui import QRegularExpressionValidator, QIcon
class OptimaLab35(QMainWindow, Ui_MainWindow):
def __init__(self):
@ -99,8 +96,6 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
validator = QRegularExpressionValidator(regex)
self.ui.lat_lineEdit.setValidator(validator)
self.ui.long_lineEdit.setValidator(validator)
#layout.addWidget(self.ui.lat_lineEdit)
#layout.addWidget(self.ui.long_lineEdit)
# UI related function, changing parts, open, etc.
def open_preview_window(self):
@ -501,441 +496,6 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
QApplication.closeAllWindows()
event.accept()
class SettingsWindow(QMainWindow, Ui_Settings_Window):
# Mixture of code by me, code/functions refactored by ChatGPT and code directly from ChatGPT
def __init__(self, optimalab35_localversion, optima35_localversion):
super(SettingsWindow, self).__init__()
self.ui = Ui_Settings_Window()
self.ui.setupUi(self)
self.u = Utilities(os.path.expanduser("~/.config/OptimaLab35"))
self.app_settings = self.u.load_settings()
self.dev_mode = True if optimalab35_localversion == "0.0.1" else False
from PyPiUpdater import PyPiUpdater
# Update log file location
self.update_log_file = os.path.expanduser("~/.config/OptimaLab35/update_log.json")
# Store local versions
self.optimalab35_localversion = optimalab35_localversion
self.optima35_localversion = optima35_localversion
# Create PyPiUpdater instances
self.ppu_ol35 = PyPiUpdater("OptimaLab35", self.optimalab35_localversion, self.update_log_file)
self.ppu_o35 = PyPiUpdater("optima35", self.optima35_localversion, self.update_log_file)
self.ol35_last_state = self.ppu_ol35.get_last_state()
self.o35_last_state = self.ppu_o35.get_last_state()
# Track which packages need an update
self.updates_available = {"OptimaLab35": False, "optima35": False}
self.define_gui_interaction()
def define_gui_interaction(self):
"""Setup UI interactions."""
# Updater related
self.ui.label_optimalab35_localversion.setText(self.optimalab35_localversion)
self.ui.label_optima35_localversion.setText(self.optima35_localversion)
self.ui.label_latest_version.setText("Latest version")
self.ui.label_optimalab35_latestversion.setText("...")
self.ui.label_optima35_latestversion.setText("...")
self.ui.update_and_restart_Button.setEnabled(False)
# Connect buttons to functions
self.ui.check_for_update_Button.clicked.connect(self.check_for_updates)
self.ui.update_and_restart_Button.clicked.connect(self.update_and_restart)
self.ui.label_last_check.setText(f"Last check: {self.time_to_string(self.ol35_last_state[0])}")
self.ui.dev_widget.setVisible(False)
# Timer for long press detection
self.timer = QTimer()
self.timer.setSingleShot(True)
self.timer.timeout.connect(self.toggle_dev_ui)
# Connect button press/release
self.ui.check_for_update_Button.pressed.connect(self.start_long_press)
self.ui.check_for_update_Button.released.connect(self.cancel_long_press)
self.ui.label_5.setText('<li><a href="https://gitlab.com/CodeByMrFinchum/OptimaLab35/-/blob/main/CHANGELOG.md?ref_type=heads">Changelog</a></li>')
self.ui.label_5.setOpenExternalLinks(True)
#settings related
self.load_settings_into_ui()
self.ui.reset_exif_Button.clicked.connect(self.ask_reset_exif)
self.ui.save_and_close_Button.clicked.connect(self.save_and_close)
self.ui.save_and_restart_Button.clicked.connect(self.save_and_restart)
if os.name == "nt": # Disable restart app when windows.
self.ui.save_and_restart_Button.setVisible(False)
self.ui.restart_checkBox.setChecked(False)
self.ui.restart_checkBox.setVisible(False)
# setting related
def load_settings_into_ui(self):
"""Loads the settings into the UI elements."""
settings = self.app_settings
theme_mode = settings["theme"]["mode"]
use_custom_theme = settings["theme"]["use_custom_theme"]
pkg_available = settings["theme"]["theme_pkg"]
if pkg_available:
index = self.ui.theme_selection_comboBox.findText(theme_mode, QtCore.Qt.MatchFlag.MatchExactly)
if index != -1:
self.ui.theme_selection_comboBox.setCurrentIndex(index)
self.ui.enable_theme_checkBox.setChecked(use_custom_theme)
self.ui.install_pkg_Button.setVisible(False)
self.ui.enable_theme_checkBox.setEnabled(True)
else:
self.ui.enable_theme_checkBox.setEnabled(False)
self.ui.install_pkg_Button.clicked.connect(self.install_theme_pkg)
def install_theme_pkg(self):
a = self.ppu_ol35.install_package("PyQtDarkTheme-fork")
self.ui.install_pkg_Button.setEnabled(False)
self.ui.install_pkg_Button.setText("Please wait...")
msg_box = QMessageBox()
msg_box.setIcon(QMessageBox.Information)
msg_box.setWindowTitle("Message")
msg_box.setText(a[1])
msg_box.setStandardButtons(QMessageBox.Ok)
msg_box.exec()
if a[0]:
self.app_settings["theme"]["theme_pkg"] = True
self.load_settings_into_ui()
else:
self.ui.install_pkg_Button.setEnabled(True)
self.ui.install_pkg_Button.setText("Try again?")
def save_settings(self):
self.app_settings["theme"]["mode"] = self.ui.theme_selection_comboBox.currentText()
self.app_settings["theme"]["use_custom_theme"] = self.ui.enable_theme_checkBox.isChecked()
self.u.save_settings(self.app_settings)
def save_and_close(self):
self.save_settings()
self.close()
def save_and_restart(self):
msg = QMessageBox()
msg.setIcon(QMessageBox.Icon.Question)
msg.setWindowTitle("Confirm Reset")
msg.setText("Are you sure you want to restart the app?")
msg.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
# Show the message box and wait for the user's response
response = msg.exec()
# Check response and perform action
if response == QMessageBox.StandardButton.Yes:
self.save_settings()
self.restart_program()
else:
pass # Do nothing if "No" is selected
def ask_reset_exif(self):
"""Shows a dialog to ask the user if they are sure about resetting EXIF options to default."""
# Create a QMessageBox with a Yes/No question
msg = QMessageBox()
msg.setIcon(QMessageBox.Icon.Question)
msg.setWindowTitle("Confirm Reset")
msg.setText("Are you sure you want to reset the EXIF options to default?")
msg.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
# Show the message box and wait for the user's response
response = msg.exec()
# Check response and perform action
if response == QMessageBox.StandardButton.Yes:
self.u.default_exif() # Reset EXIF options to default
else:
pass # Do nothing if "No" is selected
# update related parts
def start_long_press(self):
"""Start the timer when button is pressed."""
# brave AI
self.timer.start(1000) # 1-second long press
def cancel_long_press(self):
"""Cancel long press if released early."""
# brave AI
self.timer.stop()
def toggle_dev_ui(self):
"""Show or hide the hidden UI when long press is detected."""
self.ui.dev_widget.setVisible(True)
self.ui.check_local_Button.clicked.connect(self.local_check_for_updates)
self.ui.update_local_Button.clicked.connect(self.local_update)
def local_check_for_updates(self):
dist_folder = os.path.expanduser("~/.config/OptimaLab35/dist/")
self.ui.label_optimalab35_latestversion.setText("Checking...")
self.ui.label_optima35_latestversion.setText("Checking...")
# Check OptimaLab35 update
ol35_pkg_info = self.ppu_ol35.check_update_local(dist_folder)
if ol35_pkg_info[0] is None:
self.ui.label_optimalab35_latestversion.setText(ol35_pkg_info[1][0:13])
else:
self.ui.label_optimalab35_latestversion.setText(ol35_pkg_info[1])
self.updates_available["OptimaLab35"] = ol35_pkg_info[0]
# Check optima35 update
o35_pkg_info = self.ppu_o35.check_update_local(dist_folder)
if o35_pkg_info[0] is None:
self.ui.label_optima35_latestversion.setText(o35_pkg_info[1][0:13])
else:
self.ui.label_optima35_latestversion.setText(o35_pkg_info[1])
self.updates_available["optima35"] = o35_pkg_info[0]
def local_update(self):
dist_folder = os.path.expanduser("~/.config/OptimaLab35/dist/")
packages_to_update = [pkg for pkg, update in self.updates_available.items() if update]
if not packages_to_update:
QMessageBox.information(self, "Update", "No updates available.")
return
# Confirm update
msg = QMessageBox()
msg.setWindowTitle("Update Available")
message = f"Updating: {', '.join(packages_to_update)}\nUpdate "
if self.ui.restart_checkBox.isChecked():
message = message + "and restart app?"
else:
message = message + "app?"
msg.setText(message)
msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
result = msg.exec()
if result == QMessageBox.Yes:
update_results = [] # Store results
for package in packages_to_update:
if package == "OptimaLab35":
pkg_info = self.ppu_ol35.update_from_local(dist_folder)
elif package == "optima35":
pkg_info = self.ppu_o35.update_from_local(dist_folder)
update_results.append(f"{package}: {'Success' if pkg_info[0] else 'Failed'}\n{pkg_info[1]}")
# Show summary of updates
# Show update completion message
msg = QMessageBox()
msg.setWindowTitle("Update Complete")
msg.setText("\n\n".join(update_results))
msg.setStandardButtons(QMessageBox.Ok)
msg.exec()
# Restart the application after user clicks "OK"
if self.ui.restart_checkBox.isChecked():
self.restart_program()
def time_to_string(self, time_time):
try:
dt_obj = datetime.fromtimestamp(time_time)
date_string = dt_obj.strftime("%d %h %H:%M")
return date_string
except TypeError:
return "Missing information"
def check_for_updates(self):
"""Check for updates and update the UI."""
self.ui.check_for_update_Button.setEnabled(False)
self.ui.label_optimalab35_latestversion.setText("Checking...")
self.ui.label_optima35_latestversion.setText("Checking...")
# Check OptimaLab35 update
ol35_pkg_info = self.ppu_ol35.check_for_update()
if ol35_pkg_info[0] is None:
self.ui.label_optimalab35_latestversion.setText(ol35_pkg_info[1][0:13])
else:
self.ui.label_optimalab35_latestversion.setText(ol35_pkg_info[1])
self.updates_available["OptimaLab35"] = ol35_pkg_info[0]
# Check optima35 update
o35_pkg_info = self.ppu_o35.check_for_update()
if o35_pkg_info[0] is None:
self.ui.label_optima35_latestversion.setText(o35_pkg_info[1][0:13])
else:
self.ui.label_optima35_latestversion.setText(o35_pkg_info[1])
self.updates_available["optima35"] = o35_pkg_info[0]
# Enable update button if any update is available
if any(self.updates_available.values()):
if self.dev_mode:
self.ui.update_and_restart_Button.setEnabled(False)
self.ui.update_and_restart_Button.setText("Update disabled")
else:
self.ui.update_and_restart_Button.setEnabled(True)
last_date = self.time_to_string(self.ppu_ol35.get_last_state()[0])
self.ui.label_last_check.setText(f"Last check: {last_date}")
self.ui.label_latest_version.setText("Online version")
self.ui.check_for_update_Button.setEnabled(True)
def update_and_restart(self):
"""Update selected packages and restart the application."""
packages_to_update = [pkg for pkg, update in self.updates_available.items() if update]
if not packages_to_update:
QMessageBox.information(self, "Update", "No updates available.")
return
# Confirm update
msg = QMessageBox()
msg.setWindowTitle("Update Available")
message = f"Updating: {', '.join(packages_to_update)}\nUpdate "
if self.ui.restart_checkBox.isChecked():
message = message + "and restart app?"
else:
message = message + "app?"
msg.setText(message)
msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
result = msg.exec()
if result == QMessageBox.Yes:
update_results = [] # Store results
for package in packages_to_update:
if package == "OptimaLab35":
pkg_info = self.ppu_ol35.update_package()
elif package == "optima35":
pkg_info = self.ppu_o35.update_package()
update_results.append(f"{package}: {'Success' if pkg_info[0] else 'Failed'}\n{pkg_info[1]}")
# Show summary of updates
# Show update completion message
msg = QMessageBox()
msg.setWindowTitle("Update Complete")
msg.setText("\n\n".join(update_results))
msg.setStandardButtons(QMessageBox.Ok)
msg.exec()
# Restart the application after user clicks "OK"
if self.ui.restart_checkBox.isChecked():
self.restart_program()
def restart_program(self):
"""Restart the Python program after an update."""
print("Restarting the application...")
# Close all running Qt windows before restarting
app = QApplication.instance()
if app:
app.quit()
python = sys.executable
os.execl(python, python, *sys.argv)
class PreviewWindow(QMainWindow, Ui_Preview_Window):
values_selected = Signal(int, int, bool)
# Large ChatGPT with rewrite and bug fixes from me.
def __init__(self):
super(PreviewWindow, self).__init__()
self.ui = Ui_Preview_Window()
self.ui.setupUi(self)
self.o = OptimaManager()
self.threadpool = QThreadPool() # Thread pool for managing worker threads
self.ui.QLabel.setAlignment(Qt.AlignCenter)
# UI interactions
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))
# Connect UI elements to `on_ui_change`
self.ui.brightness_spinBox.valueChanged.connect(self.on_ui_change)
self.ui.brightness_Slider.valueChanged.connect(self.on_ui_change)
self.ui.contrast_spinBox.valueChanged.connect(self.on_ui_change)
self.ui.contrast_Slider.valueChanged.connect(self.on_ui_change)
self.ui.grayscale_checkBox.stateChanged.connect(self.on_ui_change)
self.ui_elements(False)
self.ui.show_OG_Button.pressed.connect(self.show_OG_image)
self.ui.show_OG_Button.released.connect(self.update_preview)
def on_ui_change(self):
"""Triggers update only if live update is enabled."""
if self.ui.live_update.isChecked():
self.update_preview()
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()
self.ui_elements(True)
def show_OG_image(self):
"""Handles loading and displaying the image in a separate thread."""
path = self.ui.image_path_lineEdit.text()
worker = ImageProcessorWorker(
path = path,
optima_manager = self.o,
brightness = 0,
contrast = 0,
grayscale = False,
resize = self.ui.scale_Slider.value(),
callback = self.display_image # Callback to update UI
)
self.threadpool.start(worker)
def ui_elements(self, state):
self.ui.groupBox_2.setEnabled(state)
self.ui.groupBox.setEnabled(state)
self.ui.groupBox_5.setEnabled(state)
self.ui.show_OG_Button.setEnabled(state)
def update_preview(self):
"""Handles loading and displaying the image in a separate thread."""
path = self.ui.image_path_lineEdit.text()
worker = ImageProcessorWorker(
path = path,
optima_manager = self.o,
brightness = int(self.ui.brightness_spinBox.text()),
contrast = int(self.ui.contrast_spinBox.text()),
grayscale = self.ui.grayscale_checkBox.isChecked(),
resize = self.ui.scale_Slider.value(),
callback = self.display_image # Callback to update UI
)
self.threadpool.start(worker) # Run worker in a thread
def display_image(self, pixmap):
"""Adjusts the image to fit within the QLabel."""
if pixmap is None:
QMessageBox.warning(self, "Warning", "Error processing image...")
return
max_size = self.ui.QLabel.size()
scaled_pixmap = pixmap.scaled(max_size, Qt.KeepAspectRatio, Qt.SmoothTransformation)
self.ui.QLabel.setPixmap(scaled_pixmap)
self.ui.QLabel.resize(scaled_pixmap.size())
def resizeEvent(self, event):
"""Triggered when the preview window is resized."""
file_path = self.ui.image_path_lineEdit.text()
if os.path.exists(file_path):
self.update_preview() # Re-process and display the image
super().resizeEvent(event) # Keep the default behavior
def close_window(self):
"""Emits signal and closes the window."""
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)
@ -984,61 +544,3 @@ class ImageProcessorRunnable(QRunnable):
self.signals.progress.emit(int((i / len(self.image_files)) * 100))
self.signals.finished.emit()
class ImageProcessorWorker(QRunnable):
"""Worker class to load and process the image in a separate thread."""
# ChatGPT
def __init__(self, path, optima_manager, brightness, contrast, grayscale, resize, callback):
super().__init__()
self.path = path
self.optima_manager = optima_manager
self.brightness = brightness
self.contrast = contrast
self.grayscale = grayscale
self.resize = resize
self.callback = callback # Function to call when processing is done
@Slot()
def run(self):
"""Runs the image processing in a separate thread."""
if not os.path.isfile(self.path):
self.callback(None)
return
try:
img = self.optima_manager.process_image_object(
image_input_file = self.path,
watermark = "PREVIEW",
resize = self.resize,
grayscale = self.grayscale,
brightness = self.brightness,
contrast = self.contrast
)
pixmap = QPixmap.fromImage(img)
self.callback(pixmap)
except Exception as e:
print(f"Error processing image: {e}")
self.callback(None)
def main():
u = Utilities(os.path.expanduser(CONFIG_BASE_PATH))
app_settings = u.load_settings()
app = QtWidgets.QApplication(sys.argv)
try:
import qdarktheme
app_settings["theme"]["theme_pkg"] = True
except Exception:
app_settings["theme"]["theme_pkg"] = False
if app_settings["theme"]["use_custom_theme"] and app_settings["theme"]["theme_pkg"]:
qdarktheme.setup_theme(app_settings["theme"]["mode"].lower())
u.save_settings(app_settings)
del u
window = OptimaLab35()
window.show()
app.exec()
if __name__ == "__main__":
main()

View file

@ -0,0 +1,167 @@
import os
from optima35.core import OptimaManager
from OptimaLab35 import __version__
from .ui import resources_rc
from .ui.preview_window import Ui_Preview_Window
from PySide6 import QtWidgets, QtCore
from PySide6.QtCore import (
QRunnable,
QThreadPool,
Signal,
QObject,
QRegularExpression,
Qt,
QTimer,
Slot
)
from PySide6.QtWidgets import (
QMessageBox,
QApplication,
QMainWindow,
QFileDialog
)
from PySide6.QtGui import QPixmap, QRegularExpressionValidator, QIcon
class PreviewWindow(QMainWindow, Ui_Preview_Window):
values_selected = Signal(int, int, bool)
# Large ChatGPT with rewrite and bug fixes from me.
def __init__(self):
super(PreviewWindow, self).__init__()
self.ui = Ui_Preview_Window()
self.ui.setupUi(self)
self.o = OptimaManager()
self.threadpool = QThreadPool() # Thread pool for managing worker threads
self.setWindowIcon(QIcon(":app-icon.png"))
self.ui.QLabel.setAlignment(Qt.AlignCenter)
# UI interactions
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))
# Connect UI elements to `on_ui_change`
self.ui.brightness_spinBox.valueChanged.connect(self.on_ui_change)
self.ui.brightness_Slider.valueChanged.connect(self.on_ui_change)
self.ui.contrast_spinBox.valueChanged.connect(self.on_ui_change)
self.ui.contrast_Slider.valueChanged.connect(self.on_ui_change)
self.ui.grayscale_checkBox.stateChanged.connect(self.on_ui_change)
self.ui_elements(False)
self.ui.show_OG_Button.pressed.connect(self.show_OG_image)
self.ui.show_OG_Button.released.connect(self.update_preview)
def on_ui_change(self):
"""Triggers update only if live update is enabled."""
if self.ui.live_update.isChecked():
self.update_preview()
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()
self.ui_elements(True)
def show_OG_image(self):
"""Handles loading and displaying the image in a separate thread."""
path = self.ui.image_path_lineEdit.text()
worker = ImageProcessorWorker(
path = path,
optima_manager = self.o,
brightness = 0,
contrast = 0,
grayscale = False,
resize = self.ui.scale_Slider.value(),
callback = self.display_image # Callback to update UI
)
self.threadpool.start(worker)
def ui_elements(self, state):
self.ui.groupBox_2.setEnabled(state)
self.ui.groupBox.setEnabled(state)
self.ui.groupBox_5.setEnabled(state)
self.ui.show_OG_Button.setEnabled(state)
def update_preview(self):
"""Handles loading and displaying the image in a separate thread."""
path = self.ui.image_path_lineEdit.text()
worker = ImageProcessorWorker(
path = path,
optima_manager = self.o,
brightness = int(self.ui.brightness_spinBox.text()),
contrast = int(self.ui.contrast_spinBox.text()),
grayscale = self.ui.grayscale_checkBox.isChecked(),
resize = self.ui.scale_Slider.value(),
callback = self.display_image # Callback to update UI
)
self.threadpool.start(worker) # Run worker in a thread
def display_image(self, pixmap):
"""Adjusts the image to fit within the QLabel."""
if pixmap is None:
QMessageBox.warning(self, "Warning", "Error processing image...")
return
max_size = self.ui.QLabel.size()
scaled_pixmap = pixmap.scaled(max_size, Qt.KeepAspectRatio, Qt.SmoothTransformation)
self.ui.QLabel.setPixmap(scaled_pixmap)
self.ui.QLabel.resize(scaled_pixmap.size())
def resizeEvent(self, event):
"""Triggered when the preview window is resized."""
file_path = self.ui.image_path_lineEdit.text()
if os.path.exists(file_path):
self.update_preview() # Re-process and display the image
super().resizeEvent(event) # Keep the default behavior
def close_window(self):
"""Emits signal and closes the window."""
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 ImageProcessorWorker(QRunnable):
"""Worker class to load and process the image in a separate thread."""
# ChatGPT
def __init__(self, path, optima_manager, brightness, contrast, grayscale, resize, callback):
super().__init__()
self.path = path
self.optima_manager = optima_manager
self.brightness = brightness
self.contrast = contrast
self.grayscale = grayscale
self.resize = resize
self.callback = callback # Function to call when processing is done
@Slot()
def run(self):
"""Runs the image processing in a separate thread."""
if not os.path.isfile(self.path):
self.callback(None)
return
try:
img = self.optima_manager.process_image_object(
image_input_file = self.path,
watermark = "PREVIEW",
resize = self.resize,
grayscale = self.grayscale,
brightness = self.brightness,
contrast = self.contrast
)
pixmap = QPixmap.fromImage(img)
self.callback(pixmap)
except Exception as e:
print(f"Error processing image: {e}")
self.callback(None)

View file

@ -0,0 +1,357 @@
import sys
import os
from datetime import datetime
from PyPiUpdater import PyPiUpdater
from OptimaLab35 import __version__
from .const import (
CONFIG_BASE_PATH
)
from .ui import resources_rc
from .utils.utility import Utilities
from .ui.settings_window import Ui_Settings_Window
from PySide6 import QtWidgets, QtCore
from PySide6.QtCore import (
QRegularExpression,
Qt,
QTimer
)
from PySide6.QtWidgets import (
QMessageBox,
QApplication,
QMainWindow
)
from PySide6.QtGui import QIcon
class SettingsWindow(QMainWindow, Ui_Settings_Window):
# Mixture of code by me, code/functions refactored by ChatGPT and code directly from ChatGPT
def __init__(self, optimalab35_localversion, optima35_localversion):
super(SettingsWindow, self).__init__()
self.ui = Ui_Settings_Window()
self.ui.setupUi(self)
self.u = Utilities(os.path.expanduser(CONFIG_BASE_PATH))
self.app_settings = self.u.load_settings()
self.dev_mode = True if optimalab35_localversion == "0.0.1" else False
self.setWindowIcon(QIcon(":app-icon.png"))
# Update log file location
self.update_log_file = os.path.expanduser(f"{CONFIG_BASE_PATH}/update_log.json")
# Store local versions
self.optimalab35_localversion = optimalab35_localversion
self.optima35_localversion = optima35_localversion
# Create PyPiUpdater instances
self.ppu_ol35 = PyPiUpdater("OptimaLab35", self.optimalab35_localversion, self.update_log_file)
self.ppu_o35 = PyPiUpdater("optima35", self.optima35_localversion, self.update_log_file)
self.ol35_last_state = self.ppu_ol35.get_last_state()
self.o35_last_state = self.ppu_o35.get_last_state()
# Track which packages need an update
self.updates_available = {"OptimaLab35": False, "optima35": False}
self.define_gui_interaction()
def define_gui_interaction(self):
"""Setup UI interactions."""
# Updater related
self.ui.label_optimalab35_localversion.setText(self.optimalab35_localversion)
self.ui.label_optima35_localversion.setText(self.optima35_localversion)
self.ui.label_latest_version.setText("Latest version")
self.ui.label_optimalab35_latestversion.setText("...")
self.ui.label_optima35_latestversion.setText("...")
self.ui.update_and_restart_Button.setEnabled(False)
# Connect buttons to functions
self.ui.check_for_update_Button.clicked.connect(self.check_for_updates)
self.ui.update_and_restart_Button.clicked.connect(self.update_and_restart)
self.ui.label_last_check.setText(f"Last check: {self.time_to_string(self.ol35_last_state[0])}")
self.ui.dev_widget.setVisible(False)
# Timer for long press detection
self.timer = QTimer()
self.timer.setSingleShot(True)
self.timer.timeout.connect(self.toggle_dev_ui)
# Connect button press/release
self.ui.check_for_update_Button.pressed.connect(self.start_long_press)
self.ui.check_for_update_Button.released.connect(self.cancel_long_press)
self.ui.label_5.setText('<li><a href="https://gitlab.com/CodeByMrFinchum/OptimaLab35/-/blob/main/CHANGELOG.md?ref_type=heads">Changelog</a></li>')
self.ui.label_5.setOpenExternalLinks(True)
#settings related
self.load_settings_into_ui()
self.ui.reset_exif_Button.clicked.connect(self.ask_reset_exif)
self.ui.save_and_close_Button.clicked.connect(self.save_and_close)
self.ui.save_and_restart_Button.clicked.connect(self.save_and_restart)
if os.name == "nt": # Disable restart app when windows.
self.ui.save_and_restart_Button.setVisible(False)
self.ui.restart_checkBox.setChecked(False)
self.ui.restart_checkBox.setVisible(False)
# setting related
def load_settings_into_ui(self):
"""Loads the settings into the UI elements."""
settings = self.app_settings
theme_mode = settings["theme"]["mode"]
use_custom_theme = settings["theme"]["use_custom_theme"]
pkg_available = settings["theme"]["theme_pkg"]
if pkg_available:
index = self.ui.theme_selection_comboBox.findText(theme_mode, QtCore.Qt.MatchFlag.MatchExactly)
if index != -1:
self.ui.theme_selection_comboBox.setCurrentIndex(index)
self.ui.enable_theme_checkBox.setChecked(use_custom_theme)
self.ui.install_pkg_Button.setVisible(False)
self.ui.enable_theme_checkBox.setEnabled(True)
else:
self.ui.enable_theme_checkBox.setEnabled(False)
self.ui.install_pkg_Button.clicked.connect(self.install_theme_pkg)
def install_theme_pkg(self):
a = self.ppu_ol35.install_package("PyQtDarkTheme-fork")
self.ui.install_pkg_Button.setEnabled(False)
self.ui.install_pkg_Button.setText("Please wait...")
msg_box = QMessageBox()
msg_box.setIcon(QMessageBox.Information)
msg_box.setWindowTitle("Message")
msg_box.setText(a[1])
msg_box.setStandardButtons(QMessageBox.Ok)
msg_box.exec()
if a[0]:
self.app_settings["theme"]["theme_pkg"] = True
self.load_settings_into_ui()
else:
self.ui.install_pkg_Button.setEnabled(True)
self.ui.install_pkg_Button.setText("Try again?")
def save_settings(self):
self.app_settings["theme"]["mode"] = self.ui.theme_selection_comboBox.currentText()
self.app_settings["theme"]["use_custom_theme"] = self.ui.enable_theme_checkBox.isChecked()
self.u.save_settings(self.app_settings)
def save_and_close(self):
self.save_settings()
self.close()
def save_and_restart(self):
msg = QMessageBox()
msg.setIcon(QMessageBox.Icon.Question)
msg.setWindowTitle("Confirm Reset")
msg.setText("Are you sure you want to restart the app?")
msg.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
# Show the message box and wait for the user's response
response = msg.exec()
# Check response and perform action
if response == QMessageBox.StandardButton.Yes:
self.save_settings()
self.restart_program()
else:
pass # Do nothing if "No" is selected
def ask_reset_exif(self):
"""Shows a dialog to ask the user if they are sure about resetting EXIF options to default."""
# Create a QMessageBox with a Yes/No question
msg = QMessageBox()
msg.setIcon(QMessageBox.Icon.Question)
msg.setWindowTitle("Confirm Reset")
msg.setText("Are you sure you want to reset the EXIF options to default?")
msg.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
# Show the message box and wait for the user's response
response = msg.exec()
# Check response and perform action
if response == QMessageBox.StandardButton.Yes:
self.u.default_exif() # Reset EXIF options to default
else:
pass # Do nothing if "No" is selected
# update related parts
def start_long_press(self):
"""Start the timer when button is pressed."""
# brave AI
self.timer.start(1000) # 1-second long press
def cancel_long_press(self):
"""Cancel long press if released early."""
# brave AI
self.timer.stop()
def toggle_dev_ui(self):
"""Show or hide the hidden UI when long press is detected."""
self.ui.dev_widget.setVisible(True)
self.ui.check_local_Button.clicked.connect(self.local_check_for_updates)
self.ui.update_local_Button.clicked.connect(self.local_update)
def local_check_for_updates(self):
dist_folder = os.path.expanduser("~/.config/OptimaLab35/dist/")
self.ui.label_optimalab35_latestversion.setText("Checking...")
self.ui.label_optima35_latestversion.setText("Checking...")
# Check OptimaLab35 update
ol35_pkg_info = self.ppu_ol35.check_update_local(dist_folder)
if ol35_pkg_info[0] is None:
self.ui.label_optimalab35_latestversion.setText(ol35_pkg_info[1][0:13])
else:
self.ui.label_optimalab35_latestversion.setText(ol35_pkg_info[1])
self.updates_available["OptimaLab35"] = ol35_pkg_info[0]
# Check optima35 update
o35_pkg_info = self.ppu_o35.check_update_local(dist_folder)
if o35_pkg_info[0] is None:
self.ui.label_optima35_latestversion.setText(o35_pkg_info[1][0:13])
else:
self.ui.label_optima35_latestversion.setText(o35_pkg_info[1])
self.updates_available["optima35"] = o35_pkg_info[0]
def local_update(self):
dist_folder = os.path.expanduser("~/.config/OptimaLab35/dist/")
packages_to_update = [pkg for pkg, update in self.updates_available.items() if update]
if not packages_to_update:
QMessageBox.information(self, "Update", "No updates available.")
return
# Confirm update
msg = QMessageBox()
msg.setWindowTitle("Update Available")
message = f"Updating: {', '.join(packages_to_update)}\nUpdate "
if self.ui.restart_checkBox.isChecked():
message = message + "and restart app?"
else:
message = message + "app?"
msg.setText(message)
msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
result = msg.exec()
if result == QMessageBox.Yes:
update_results = [] # Store results
for package in packages_to_update:
if package == "OptimaLab35":
pkg_info = self.ppu_ol35.update_from_local(dist_folder)
elif package == "optima35":
pkg_info = self.ppu_o35.update_from_local(dist_folder)
update_results.append(f"{package}: {'Success' if pkg_info[0] else 'Failed'}\n{pkg_info[1]}")
# Show summary of updates
# Show update completion message
msg = QMessageBox()
msg.setWindowTitle("Update Complete")
msg.setText("\n\n".join(update_results))
msg.setStandardButtons(QMessageBox.Ok)
msg.exec()
# Restart the application after user clicks "OK"
if self.ui.restart_checkBox.isChecked():
self.restart_program()
def time_to_string(self, time_time):
try:
dt_obj = datetime.fromtimestamp(time_time)
date_string = dt_obj.strftime("%d %h %H:%M")
return date_string
except TypeError:
return "Missing information"
def check_for_updates(self):
"""Check for updates and update the UI."""
self.ui.check_for_update_Button.setEnabled(False)
self.ui.label_optimalab35_latestversion.setText("Checking...")
self.ui.label_optima35_latestversion.setText("Checking...")
# Check OptimaLab35 update
ol35_pkg_info = self.ppu_ol35.check_for_update()
if ol35_pkg_info[0] is None:
self.ui.label_optimalab35_latestversion.setText(ol35_pkg_info[1][0:13])
else:
self.ui.label_optimalab35_latestversion.setText(ol35_pkg_info[1])
self.updates_available["OptimaLab35"] = ol35_pkg_info[0]
# Check optima35 update
o35_pkg_info = self.ppu_o35.check_for_update()
if o35_pkg_info[0] is None:
self.ui.label_optima35_latestversion.setText(o35_pkg_info[1][0:13])
else:
self.ui.label_optima35_latestversion.setText(o35_pkg_info[1])
self.updates_available["optima35"] = o35_pkg_info[0]
# Enable update button if any update is available
if any(self.updates_available.values()):
if self.dev_mode:
self.ui.update_and_restart_Button.setEnabled(False)
self.ui.update_and_restart_Button.setText("Update disabled")
else:
self.ui.update_and_restart_Button.setEnabled(True)
last_date = self.time_to_string(self.ppu_ol35.get_last_state()[0])
self.ui.label_last_check.setText(f"Last check: {last_date}")
self.ui.label_latest_version.setText("Online version")
self.ui.check_for_update_Button.setEnabled(True)
def update_and_restart(self):
"""Update selected packages and restart the application."""
packages_to_update = [pkg for pkg, update in self.updates_available.items() if update]
if not packages_to_update:
QMessageBox.information(self, "Update", "No updates available.")
return
# Confirm update
msg = QMessageBox()
msg.setWindowTitle("Update Available")
message = f"Updating: {', '.join(packages_to_update)}\nUpdate "
if self.ui.restart_checkBox.isChecked():
message = message + "and restart app?"
else:
message = message + "app?"
msg.setText(message)
msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
result = msg.exec()
if result == QMessageBox.Yes:
update_results = [] # Store results
for package in packages_to_update:
if package == "OptimaLab35":
pkg_info = self.ppu_ol35.update_package()
elif package == "optima35":
pkg_info = self.ppu_o35.update_package()
update_results.append(f"{package}: {'Success' if pkg_info[0] else 'Failed'}\n{pkg_info[1]}")
# Show summary of updates
# Show update completion message
msg = QMessageBox()
msg.setWindowTitle("Update Complete")
msg.setText("\n\n".join(update_results))
msg.setStandardButtons(QMessageBox.Ok)
msg.exec()
# Restart the application after user clicks "OK"
if self.ui.restart_checkBox.isChecked():
self.restart_program()
def restart_program(self):
"""Restart the Python program after an update."""
print("Restarting the application...")
# Close all running Qt windows before restarting
app = QApplication.instance()
if app:
app.quit()
python = sys.executable
os.execl(python, python, *sys.argv)