diff --git a/pyproject.toml b/pyproject.toml
index b9e2577..a9e3c18 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -9,7 +9,12 @@ authors = [{ name = "Mr Finchum" }]
description = "User interface for optima35."
readme = "pip_README.md"
requires-python = ">=3.8"
-dependencies = ["optima35>=1.0.0, <2.0.0", "pyside6", "PyYAML"]
+dependencies = [
+ "optima35>=1.0.0, <2.0.0",
+ "PyPyUpdater>=0.3.0, <1.0.0",
+ "pyside6",
+ "PyYAML",
+]
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)",
diff --git a/src/OptimaLab35/gui.py b/src/OptimaLab35/gui.py
index 1194845..e2d9384 100644
--- a/src/OptimaLab35/gui.py
+++ b/src/OptimaLab35/gui.py
@@ -2,10 +2,12 @@ import sys
import os
from datetime import datetime
+from PyPiUpdater import PyPiUpdater
from optima35.core import OptimaManager
from OptimaLab35.utils.utility import Utilities
from OptimaLab35.ui.main_window import Ui_MainWindow
from OptimaLab35.ui.preview_window import Ui_Preview_Window
+from OptimaLab35.ui.updater_window import Ui_Updater_Window
from OptimaLab35.ui.exif_handler_window import ExifEditor
from OptimaLab35.ui.simple_dialog import SimpleDialog # Import the SimpleDialog class
from OptimaLab35 import __version__
@@ -48,7 +50,7 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.sd = SimpleDialog()
- self.preview_window = PreviewWindow()
+
# Change UI elements
self.change_statusbar(f"Using {self.o.name} v{self.o.version}", 5000)
self.setWindowTitle(f"{self.name} v{self.version}")
@@ -76,6 +78,7 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
self.ui.actionAbout.triggered.connect(self.info_window)
self.ui.preview_Button.clicked.connect(self.open_preview_window)
+ self.ui.actionUpdate.triggered.connect(self.open_updater_window)
regex = QRegularExpression(r"^\d{1,2}\.\d{1,6}$")
validator = QRegularExpressionValidator(regex)
@@ -86,9 +89,14 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
# UI related function, changing parts, open, etc.
def open_preview_window(self):
+ self.preview_window = PreviewWindow()
self.preview_window.values_selected.connect(self.update_values)
self.preview_window.showMaximized()
+ def open_updater_window(self):
+ self.updater_window = UpdaterWindow(self.version, self.o.version)
+ self.updater_window.show()
+
def update_values(self, value1, value2, checkbox_state):
# Update main window's widgets with the received values
# ChatGPT
@@ -454,6 +462,132 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
elif do == "write":
self.u.write_yaml(self.exif_file, self.available_exif_data)
+class UpdaterWindow(QMainWindow, Ui_Updater_Window):
+ # Mixture of code by me, code/functions refactored by ChatGPT and code directly from ChatGPT
+ def __init__(self, optimalab35_localversion, optima35_localversion):
+ super(UpdaterWindow, self).__init__()
+ self.ui = Ui_Updater_Window()
+ self.ui.setupUi(self)
+ from PyPiUpdater import PyPiUpdater
+ # Update log file location
+ self.update_log_file = os.path.expanduser("~/.config/OptimaLab35/update.log")
+ # 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.last_update = self.ppu_o35.last_update_date_string()
+
+ # 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."""
+ if self.optimalab35_localversion == "0.0.1":
+ self.dev_mode()
+ return
+ else:
+ self.ui.label_dev.setVisible(False)
+ self.ui.label_optimalab35_localversion.setText(self.optimalab35_localversion)
+ self.ui.label_optima35_localversion.setText(self.optima35_localversion)
+ 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_2.setText(self.last_update)
+
+ def dev_mode(self):
+ self.ui.update_and_restart_Button.setEnabled(False)
+ self.ui.check_for_update_Button.setEnabled(False)
+ self.ui.label_dev.setStyleSheet("QLabel { background-color : red; color : black; }")
+
+ 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
+ is_newer_ol35, latest_version_ol35 = self.ppu_ol35.check_for_update(True)
+ if is_newer_ol35 is None:
+ self.ui.label_optimalab35_latestversion.setText("Error")
+ else:
+ self.ui.label_optimalab35_latestversion.setText(latest_version_ol35)
+ self.updates_available["OptimaLab35"] = is_newer_ol35
+
+ # Check optima35 update
+ is_newer_o35, latest_version_o35 = self.ppu_o35.check_for_update(True)
+ if is_newer_o35 is None:
+ self.ui.label_optima35_latestversion.setText("Error")
+ else:
+ self.ui.label_optima35_latestversion.setText(latest_version_o35)
+ self.updates_available["optima35"] = is_newer_o35
+
+ # Enable update button if any update is available
+ if any(self.updates_available.values()):
+ self.ui.update_and_restart_Button.setEnabled(True)
+
+ self.ppu_o35.record_update_check()
+ self.ui.label_last_check_2.setText(self.ppu_o35.last_update_date_string())
+ 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")
+ msg.setText(f"Updating: {', '.join(packages_to_update)}\nRestart after update?")
+ 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":
+ success, message = self.ppu_ol35.update_package()
+ elif package == "optima35":
+ success, message = self.ppu_o35.update_package()
+
+ update_results.append(f"{package}: {'✔ Success' if success else '❌ Failed'}\n{message}")
+
+ # 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"
+ self.ppu_ol35.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)
diff --git a/src/OptimaLab35/ui/main_window.py b/src/OptimaLab35/ui/main_window.py
index 5af0bc9..bca1e18 100644
--- a/src/OptimaLab35/ui/main_window.py
+++ b/src/OptimaLab35/ui/main_window.py
@@ -37,6 +37,8 @@ class Ui_MainWindow(object):
self.actionPreview.setObjectName(u"actionPreview")
self.actionAbout = QAction(MainWindow)
self.actionAbout.setObjectName(u"actionAbout")
+ self.actionUpdate = QAction(MainWindow)
+ self.actionUpdate.setObjectName(u"actionUpdate")
self.centralwidget = QWidget(MainWindow)
self.centralwidget.setObjectName(u"centralwidget")
self.gridLayout = QGridLayout(self.centralwidget)
@@ -555,10 +557,14 @@ class Ui_MainWindow(object):
self.menuBar.setGeometry(QRect(0, 0, 440, 27))
self.menuHelp = QMenu(self.menuBar)
self.menuHelp.setObjectName(u"menuHelp")
+ self.menuSettings = QMenu(self.menuBar)
+ self.menuSettings.setObjectName(u"menuSettings")
MainWindow.setMenuBar(self.menuBar)
+ self.menuBar.addAction(self.menuSettings.menuAction())
self.menuBar.addAction(self.menuHelp.menuAction())
self.menuHelp.addAction(self.actionAbout)
+ self.menuSettings.addAction(self.actionUpdate)
self.retranslateUi(MainWindow)
self.rename_checkbox.toggled.connect(self.filename.setEnabled)
@@ -596,6 +602,7 @@ class Ui_MainWindow(object):
self.actionInfo.setText(QCoreApplication.translate("MainWindow", u"About", None))
self.actionPreview.setText(QCoreApplication.translate("MainWindow", u"Preview image", None))
self.actionAbout.setText(QCoreApplication.translate("MainWindow", u"About", None))
+ self.actionUpdate.setText(QCoreApplication.translate("MainWindow", u"Update", None))
self.input_folder_button.setText(QCoreApplication.translate("MainWindow", u"Input", None))
self.output_path.setText("")
self.output_path.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Output folder", None))
@@ -604,7 +611,7 @@ class Ui_MainWindow(object):
self.output_folder_button.setText(QCoreApplication.translate("MainWindow", u"Output", None))
self.groupBox.setTitle(QCoreApplication.translate("MainWindow", u"Essential group", None))
self.quality_label_2.setText(QCoreApplication.translate("MainWindow", u"Quality", None))
- self.label_11.setText(QCoreApplication.translate("MainWindow", u"Export Format", None))
+ self.label_11.setText(QCoreApplication.translate("MainWindow", u"Format ", None))
self.optimize_checkBox.setText(QCoreApplication.translate("MainWindow", u"optimize", None))
self.quality_label_1.setText(QCoreApplication.translate("MainWindow", u"Quality", None))
@@ -658,5 +665,6 @@ class Ui_MainWindow(object):
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.menuHelp.setTitle(QCoreApplication.translate("MainWindow", u"Help", None))
+ self.menuSettings.setTitle(QCoreApplication.translate("MainWindow", u"Settings", None))
# retranslateUi
diff --git a/src/OptimaLab35/ui/main_window.ui b/src/OptimaLab35/ui/main_window.ui
index 3779add..62acda5 100644
--- a/src/OptimaLab35/ui/main_window.ui
+++ b/src/OptimaLab35/ui/main_window.ui
@@ -142,7 +142,7 @@
-
- Export Format
+ Format
@@ -841,6 +841,13 @@
+
+
@@ -858,6 +865,11 @@
About
+
+
+ Update
+
+
diff --git a/src/OptimaLab35/ui/updater_window.py b/src/OptimaLab35/ui/updater_window.py
new file mode 100644
index 0000000..13a2eb4
--- /dev/null
+++ b/src/OptimaLab35/ui/updater_window.py
@@ -0,0 +1,147 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'updater_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, QGridLayout, QHBoxLayout, QLabel,
+ QMainWindow, QPushButton, QSizePolicy, QWidget)
+
+class Ui_Updater_Window(object):
+ def setupUi(self, Updater_Window):
+ if not Updater_Window.objectName():
+ Updater_Window.setObjectName(u"Updater_Window")
+ Updater_Window.setEnabled(True)
+ Updater_Window.resize(372, 224)
+ self.centralwidget = QWidget(Updater_Window)
+ self.centralwidget.setObjectName(u"centralwidget")
+ self.gridLayout_2 = QGridLayout(self.centralwidget)
+ self.gridLayout_2.setObjectName(u"gridLayout_2")
+ self.label_last_check = QLabel(self.centralwidget)
+ self.label_last_check.setObjectName(u"label_last_check")
+
+ self.gridLayout_2.addWidget(self.label_last_check, 0, 0, 1, 1)
+
+ self.label_last_check_2 = QLabel(self.centralwidget)
+ self.label_last_check_2.setObjectName(u"label_last_check_2")
+ self.label_last_check_2.setEnabled(True)
+
+ self.gridLayout_2.addWidget(self.label_last_check_2, 0, 1, 1, 1)
+
+ self.widget_2 = QWidget(self.centralwidget)
+ self.widget_2.setObjectName(u"widget_2")
+ self.gridLayout = QGridLayout(self.widget_2)
+ self.gridLayout.setObjectName(u"gridLayout")
+ self.label_6 = QLabel(self.widget_2)
+ self.label_6.setObjectName(u"label_6")
+ self.label_6.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter)
+
+ self.gridLayout.addWidget(self.label_6, 0, 1, 1, 1)
+
+ self.label_optimalab35_latestversion = QLabel(self.widget_2)
+ self.label_optimalab35_latestversion.setObjectName(u"label_optimalab35_latestversion")
+ self.label_optimalab35_latestversion.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter)
+
+ self.gridLayout.addWidget(self.label_optimalab35_latestversion, 1, 2, 1, 1)
+
+ self.label_2 = QLabel(self.widget_2)
+ self.label_2.setObjectName(u"label_2")
+
+ self.gridLayout.addWidget(self.label_2, 3, 0, 1, 1)
+
+ self.label_optima35_latestversion = QLabel(self.widget_2)
+ self.label_optima35_latestversion.setObjectName(u"label_optima35_latestversion")
+ self.label_optima35_latestversion.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter)
+
+ self.gridLayout.addWidget(self.label_optima35_latestversion, 3, 2, 1, 1)
+
+ self.label_3 = QLabel(self.widget_2)
+ self.label_3.setObjectName(u"label_3")
+ self.label_3.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter)
+
+ self.gridLayout.addWidget(self.label_3, 0, 2, 1, 1)
+
+ self.label = QLabel(self.widget_2)
+ self.label.setObjectName(u"label")
+
+ self.gridLayout.addWidget(self.label, 1, 0, 1, 1)
+
+ self.label_9 = QLabel(self.widget_2)
+ self.label_9.setObjectName(u"label_9")
+
+ self.gridLayout.addWidget(self.label_9, 0, 0, 1, 1)
+
+ self.label_optimalab35_localversion = QLabel(self.widget_2)
+ self.label_optimalab35_localversion.setObjectName(u"label_optimalab35_localversion")
+ self.label_optimalab35_localversion.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter)
+
+ self.gridLayout.addWidget(self.label_optimalab35_localversion, 1, 1, 1, 1)
+
+ self.label_optima35_localversion = QLabel(self.widget_2)
+ self.label_optima35_localversion.setObjectName(u"label_optima35_localversion")
+ self.label_optima35_localversion.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter)
+
+ self.gridLayout.addWidget(self.label_optima35_localversion, 3, 1, 1, 1)
+
+ self.label_dev = QLabel(self.widget_2)
+ self.label_dev.setObjectName(u"label_dev")
+ self.label_dev.setAlignment(Qt.AlignCenter)
+
+ self.gridLayout.addWidget(self.label_dev, 2, 0, 1, 3)
+
+
+ self.gridLayout_2.addWidget(self.widget_2, 1, 0, 1, 2)
+
+ self.widget = QWidget(self.centralwidget)
+ self.widget.setObjectName(u"widget")
+ self.horizontalLayout = QHBoxLayout(self.widget)
+ self.horizontalLayout.setObjectName(u"horizontalLayout")
+ self.check_for_update_Button = QPushButton(self.widget)
+ self.check_for_update_Button.setObjectName(u"check_for_update_Button")
+
+ self.horizontalLayout.addWidget(self.check_for_update_Button)
+
+ self.update_and_restart_Button = QPushButton(self.widget)
+ self.update_and_restart_Button.setObjectName(u"update_and_restart_Button")
+
+ self.horizontalLayout.addWidget(self.update_and_restart_Button)
+
+
+ self.gridLayout_2.addWidget(self.widget, 2, 0, 1, 2)
+
+ Updater_Window.setCentralWidget(self.centralwidget)
+
+ self.retranslateUi(Updater_Window)
+
+ QMetaObject.connectSlotsByName(Updater_Window)
+ # setupUi
+
+ def retranslateUi(self, Updater_Window):
+ Updater_Window.setWindowTitle(QCoreApplication.translate("Updater_Window", u"OptimaLab35 - Updater", None))
+ self.label_last_check.setText(QCoreApplication.translate("Updater_Window", u"Last update check ago:", None))
+ self.label_last_check_2.setText(QCoreApplication.translate("Updater_Window", u"TextLabel", None))
+ self.label_6.setText(QCoreApplication.translate("Updater_Window", u"Local Version", None))
+ self.label_optimalab35_latestversion.setText(QCoreApplication.translate("Updater_Window", u"unknown", None))
+ self.label_2.setText(QCoreApplication.translate("Updater_Window", u"optima35", None))
+ self.label_optima35_latestversion.setText(QCoreApplication.translate("Updater_Window", u"unknown", None))
+ self.label_3.setText(QCoreApplication.translate("Updater_Window", u"Latest version", None))
+ self.label.setText(QCoreApplication.translate("Updater_Window", u"OptimaLab35", None))
+ self.label_9.setText(QCoreApplication.translate("Updater_Window", u"Package", None))
+ self.label_optimalab35_localversion.setText(QCoreApplication.translate("Updater_Window", u"0.0.0", None))
+ self.label_optima35_localversion.setText(QCoreApplication.translate("Updater_Window", u"0.0.0", None))
+ self.label_dev.setText(QCoreApplication.translate("Updater_Window", u"Detected dev env. updater disabled", None))
+ self.check_for_update_Button.setText(QCoreApplication.translate("Updater_Window", u"Check for update", None))
+ self.update_and_restart_Button.setText(QCoreApplication.translate("Updater_Window", u"Update and restart", None))
+ # retranslateUi
+
diff --git a/src/OptimaLab35/ui/updater_window.ui b/src/OptimaLab35/ui/updater_window.ui
new file mode 100644
index 0000000..0e74e74
--- /dev/null
+++ b/src/OptimaLab35/ui/updater_window.ui
@@ -0,0 +1,160 @@
+
+
+ Updater_Window
+
+
+ true
+
+
+
+ 0
+ 0
+ 372
+ 224
+
+
+
+ OptimaLab35 - Updater
+
+
+
+ -
+
+
+ Last update check ago:
+
+
+
+ -
+
+
+ true
+
+
+ TextLabel
+
+
+
+ -
+
+
+
-
+
+
+ Local Version
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ unknown
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ optima35
+
+
+
+ -
+
+
+ unknown
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ Latest version
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ OptimaLab35
+
+
+
+ -
+
+
+ Package
+
+
+
+ -
+
+
+ 0.0.0
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ 0.0.0
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ Detected dev env. updater disabled
+
+
+ Qt::AlignCenter
+
+
+
+
+
+
+ -
+
+
+
-
+
+
+ Check for update
+
+
+
+ -
+
+
+ Update and restart
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/OptimaLab35/update_ui.sh b/src/OptimaLab35/update_ui.sh
index d533d7a..95cda5f 100755
--- a/src/OptimaLab35/update_ui.sh
+++ b/src/OptimaLab35/update_ui.sh
@@ -4,3 +4,5 @@ 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
+echo "Update updater window."
+pyside6-uic OptimaLab35/ui/updater_window.ui -o OptimaLab35/ui/updater_window.py
diff --git a/src/pyproject.toml b/src/pyproject.toml
index 0b86791..4e3097a 100644
--- a/src/pyproject.toml
+++ b/src/pyproject.toml
@@ -8,7 +8,7 @@ dynamic = ["version"]
authors = [{ name = "Mr Finchum" }]
description = "User interface for optima35."
requires-python = ">=3.8"
-dependencies = ["optima35>=1.0.0, <2.0.0", "PyYAML"]
+dependencies = ["optima35>=1.0.0, <2.0.0", "PyYAML", "PyPyUpdater"]
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)",