diff --git a/CHANGELOG.md b/CHANGELOG.md
index 50ef93c..be219da 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,14 @@
# Changelog
+## 0.10.x
+### 0.10.0: Multithreading for Preview Window
+- The preview window now processes images in a separate thread, and live update preview is enabled by default.
+ - This improves UI responsiveness.
+ - The image now resizes dynamically to fit the window when the window size changes.
+- Minor UI improvements.
+
+---
+
## 0.9.x
### 0.9.1: Patch for Unsuccessful Successful Update
- Addressed a rare issue where the package did not update correctly using the updater.
diff --git a/src/OptimaLab35/gui.py b/src/OptimaLab35/gui.py
index 22fdafb..e69088d 100644
--- a/src/OptimaLab35/gui.py
+++ b/src/OptimaLab35/gui.py
@@ -12,7 +12,7 @@ from OptimaLab35.ui.exif_handler_window import ExifEditor
from OptimaLab35.ui.simple_dialog import SimpleDialog # Import the SimpleDialog class
from OptimaLab35 import __version__
-from PySide6.QtCore import QRunnable, QThreadPool, Signal, QObject, QRegularExpression, Qt, QTimer
+from PySide6.QtCore import QRunnable, QThreadPool, Signal, QObject, QRegularExpression, Qt, QTimer, Slot
from PySide6 import QtWidgets
from PySide6.QtWidgets import (
@@ -52,7 +52,7 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
self.sd = SimpleDialog()
# Change UI elements
- self.change_statusbar(f"Using {self.o.name} v{self.o.version}", 5000)
+ self.change_statusbar(f"{self.name} v{self.version}", 10000)
self.set_title()
self.default_ui_layout()
self.define_gui_interaction()
@@ -501,10 +501,12 @@ class UpdaterWindow(QMainWindow, Ui_Updater_Window):
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):
@@ -520,8 +522,8 @@ class UpdaterWindow(QMainWindow, Ui_Updater_Window):
self.ui.label_optima35_localversion.setText(self.optima35_localversion)
self.ui.label_latest_version.setText("Latest version")
- self.ui.label_optimalab35_latestversion.setText(self.ol35_last_state[1])
- self.ui.label_optima35_latestversion.setText(self.o35_last_state[1])
+ self.ui.label_optimalab35_latestversion.setText("...")
+ self.ui.label_optima35_latestversion.setText("...")
self.ui.update_and_restart_Button.setEnabled(False)
@@ -706,14 +708,18 @@ class UpdaterWindow(QMainWindow, Ui_Updater_Window):
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 interaction
+
+ # 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)
@@ -724,12 +730,8 @@ class PreviewWindow(QMainWindow, Ui_Preview_Window):
# 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.reset_brightness_Button.clicked.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.reset_contrast_Button.clicked.connect(self.on_ui_change)
-
self.ui.grayscale_checkBox.stateChanged.connect(self.on_ui_change)
def on_ui_change(self):
@@ -738,69 +740,48 @@ class PreviewWindow(QMainWindow, Ui_Preview_Window):
self.update_preview()
def browse_file(self):
- file = QFileDialog.getOpenFileName(self, caption = "Select File", filter = ("Images (*.png *.webp *.jpg *.jpeg)"))
+ 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 process_image(self, path):
- """Loads and processes the image with modifications."""
- # Refactored by ChatGPT
- if not os.path.isfile(path):
- return None
+ def update_preview(self):
+ """Handles loading and displaying the image in a separate thread."""
+ path = self.ui.image_path_lineEdit.text()
- try:
- img = self.o.process_image_object(
- image_input_file=path, # Example: resize percentage
- watermark="PREVIEW",
- resize = 100,
- grayscale=self.ui.grayscale_checkBox.isChecked(),
- brightness=int(self.ui.brightness_spinBox.text()),
- contrast=int(self.ui.contrast_spinBox.text())
- )
- return QPixmap.fromImage(img)
- except Exception as e:
- QMessageBox.warning(self, "Warning", "Error loading image...")
- print(f"Error loading image...\n{e}")
- return None
+ 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(),
+ 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."""
- # ChatGPT
if pixmap is None:
+ QMessageBox.warning(self, "Warning", "Error processing image...")
return
- # Get max available size (QLabel size)
max_size = self.ui.QLabel.size()
- max_width = max_size.width()
- max_height = max_size.height()
-
- # Scale image to fit within the available space while maintaining aspect ratio
- scaled_pixmap = pixmap.scaled(
- max_width, max_height,
- Qt.KeepAspectRatio,
- Qt.SmoothTransformation
- )
-
- # Set the scaled image
+ scaled_pixmap = pixmap.scaled(max_size, Qt.KeepAspectRatio, Qt.SmoothTransformation)
self.ui.QLabel.setPixmap(scaled_pixmap)
-
- # Adjust QLabel size to match image
self.ui.QLabel.resize(scaled_pixmap.size())
- def update_preview(self):
- """Handles loading and displaying the image."""
- # ChatGPT
- path = self.ui.image_path_lineEdit.text()
- pixmap = self.process_image(path)
- self.display_image(pixmap)
+ 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):
- # Emit the signal with the values from the spinboxes and checkbox
- # chatgpt
- 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()
+ """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
@@ -851,6 +832,39 @@ class ImageProcessorRunnable(QRunnable):
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, callback):
+ super().__init__()
+ self.path = path
+ self.optima_manager = optima_manager
+ self.brightness = brightness
+ self.contrast = contrast
+ self.grayscale = grayscale
+ 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=100,
+ 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():
app = QtWidgets.QApplication(sys.argv)
diff --git a/src/OptimaLab35/ui/preview_window.py b/src/OptimaLab35/ui/preview_window.py
index 5d2a021..712c5aa 100644
--- a/src/OptimaLab35/ui/preview_window.py
+++ b/src/OptimaLab35/ui/preview_window.py
@@ -131,6 +131,7 @@ class Ui_Preview_Window(object):
self.verticalLayout_3.setObjectName(u"verticalLayout_3")
self.live_update = QCheckBox(self.widget_4)
self.live_update.setObjectName(u"live_update")
+ self.live_update.setChecked(True)
self.verticalLayout_3.addWidget(self.live_update)
@@ -196,7 +197,7 @@ class Ui_Preview_Window(object):
#endif // QT_CONFIG(tooltip)
self.grayscale_checkBox.setText(QCoreApplication.translate("Preview_Window", u"Black n White", None))
#if QT_CONFIG(tooltip)
- self.live_update.setToolTip(QCoreApplication.translate("Preview_Window", u"Enable live updates to immediately preview changes as you adjust brightness, contrast, or grayscale. Be aware that this might impact performance, especially with large images.", None))
+ self.live_update.setToolTip(QCoreApplication.translate("Preview_Window", u"Live update applies changes instantly. If the app becomes unresponsive or lags, disable this option.", None))
#endif // QT_CONFIG(tooltip)
self.live_update.setText(QCoreApplication.translate("Preview_Window", u"Live update", None))
#if QT_CONFIG(tooltip)
diff --git a/src/OptimaLab35/ui/preview_window.ui b/src/OptimaLab35/ui/preview_window.ui
index 33ad094..99fc876 100644
--- a/src/OptimaLab35/ui/preview_window.ui
+++ b/src/OptimaLab35/ui/preview_window.ui
@@ -191,11 +191,14 @@
-
- Enable live updates to immediately preview changes as you adjust brightness, contrast, or grayscale. Be aware that this might impact performance, especially with large images.
+ Live update applies changes instantly. If the app becomes unresponsive or lags, disable this option.
Live update
+
+ true
+
-