diff --git a/CHANGELOG.md b/CHANGELOG.md
index c60c01e..3ec2081 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/TODO.md b/TODO.md
deleted file mode 100644
index 468cd9b..0000000
--- a/TODO.md
+++ /dev/null
@@ -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
diff --git a/src/OptimaLab35/__main__.py b/src/OptimaLab35/__main__.py
index 14932be..512c7b3 100644
--- a/src/OptimaLab35/__main__.py
+++ b/src/OptimaLab35/__main__.py
@@ -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()
diff --git a/src/OptimaLab35/gui.py b/src/OptimaLab35/mainWindow.py
similarity index 51%
rename from src/OptimaLab35/gui.py
rename to src/OptimaLab35/mainWindow.py
index cf40df3..3006465 100644
--- a/src/OptimaLab35/gui.py
+++ b/src/OptimaLab35/mainWindow.py
@@ -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('
Changelog')
- 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()
diff --git a/src/OptimaLab35/previewWindow.py b/src/OptimaLab35/previewWindow.py
new file mode 100644
index 0000000..72469be
--- /dev/null
+++ b/src/OptimaLab35/previewWindow.py
@@ -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)
diff --git a/src/OptimaLab35/settingsWindow.py b/src/OptimaLab35/settingsWindow.py
new file mode 100644
index 0000000..df5d494
--- /dev/null
+++ b/src/OptimaLab35/settingsWindow.py
@@ -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('Changelog')
+ 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)