Compare commits

..

26 commits
0.12.1 ... main

Author SHA1 Message Date
894d444f91
feat: dateEdit is now, instead of static day.
All checks were successful
ci/woodpecker/push/woodpecker_ci Pipeline was successful
ci/woodpecker/tag/woodpecker_ci Pipeline was successful
2025-05-30 12:17:12 +02:00
a89f43ba2e
fix: fixed linkage and changelog
All checks were successful
ci/woodpecker/pr/woodpecker_ci Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker_ci Pipeline was successful
ci/woodpecker/push/woodpecker_ci Pipeline was successful
ci/woodpecker/tag/woodpecker_ci Pipeline was successful
2025-04-11 15:57:58 +02:00
d93d3de859 fix: fixing woodpecker ci (#2)
All checks were successful
ci/woodpecker/push/woodpecker_ci Pipeline was successful
ci/woodpecker/tag/woodpecker_ci Pipeline was successful
Reviewed-on: #2
Co-authored-by: Mr Finchum <mr.finchum@pm.me>
Co-committed-by: Mr Finchum <mr.finchum@pm.me>
2025-04-11 13:41:24 +02:00
011249e002
ci: switch to woodpecker ci.
Some checks failed
ci/woodpecker/pr/woodpecker_ci Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker_ci Pipeline was successful
ci/woodpecker/push/woodpecker_ci Pipeline failed
ci/woodpecker/tag/woodpecker_ci Pipeline was successful
2025-04-11 13:34:29 +02:00
d4e8a25b1e fix: fixed misspelling 2025-04-01 18:37:11 +02:00
419dc1eada patch: better performance for preview window 2025-04-01 16:30:21 +00:00
63422ccd88 fix: fixed app path 2025-03-30 19:29:54 +02:00
d356937e41 fix: fixed insert exif feature 2025-03-27 11:07:58 +00:00
ef92ffb5b9 feat: image adjustments written to exif. 2025-03-24 15:34:29 +01:00
7adea3084a patch: Patched pyproject file. 2025-03-23 23:09:05 +01:00
35a9f1af55 patch: patched formation of file. 2025-03-23 23:02:39 +01:00
17f08bc74f refactor: Splitting Classes into Separate Files 2025-03-23 21:48:46 +00:00
ce2dd90a39 feat: added feature to show original image in preview window. 2025-03-23 12:37:56 +00:00
3483d67e93 fix: Fixed spelling a few places 2025-03-06 21:33:26 +00:00
c0de1a550b fix!: Fixed wrong version bump
Prior version should have been 1.0 but seems it should be !: not just !
2025-03-06 17:09:50 +00:00
c08bb59dae chore! finishing touches
Minor changes to the UI, few fixes and updating documentation for stable release.
2025-03-06 16:20:13 +00:00
2c633d6dc4 feat: Added resizing option to preview window to reduce lag. 2025-02-12 15:43:53 +01:00
9000702636 patch: Updated missing entries. 2025-02-12 15:10:29 +01:00
Mr. Finch
fcd7aa97b6 refactor: introduces consts and optimizes the code 2025-02-11 18:51:23 +00:00
dc132bffc9 fix: Fixed error regarding image processing. 2025-02-11 18:58:19 +01:00
Mr. Finch
0d0feff502 chore: adds requirements file 2025-02-11 18:45:24 +01:00
938209fdfd patch: disabled restart option for windows, as it does not work. 2025-02-11 13:00:24 +01:00
1e91ee8cf6 fix: Fixed bug with default exif file. 2025-02-10 21:33:20 +01:00
963f27109f patch: Updated readme and preview of the program. 2025-02-10 17:04:14 +01:00
0abc4ba96f patch: Adjusted some GUI elements 2025-02-10 15:41:50 +00:00
7eb8f162bb fix: Fixed ui clipping 2025-02-10 11:01:04 +00:00
39 changed files with 2122 additions and 1707 deletions

1
.gitignore vendored
View file

@ -4,3 +4,4 @@ dist/
__pycache__/
.flatpak-builder/
flatpak-build-dir/
*.jpg

View file

@ -1,69 +0,0 @@
---
include:
- local: .gitlab-ci/versioning/gitversion.yml
- local: .gitlab-ci/git/create_tag.yml
stages:
- build
- release
gitversion:
extends: .versioning:gitversion
stage: .pre
tags:
- gitlab-org-docker
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH # Run this job when commits are pushed or merged to the default branch
build:
stage: build
image: python:3.9.21
tags:
- gitlab-org-docker
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH # Run this job when commits are pushed or merged to the default branch
needs:
- job: gitversion
artifacts: true
script:
- sed -i "s/0.0.1/${GitVersion_MajorMinorPatch}/" src/OptimaLab35/__init__.py
- cat src/OptimaLab35/__init__.py
- python3 -m pip install build
- python3 -m build --wheel --sdist -s src
artifacts:
paths:
- src/dist/*
expire_in: 1 day
publish:
stage: release
image: python:3.9.21
tags:
- gitlab-org-docker
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH # Run this job when commits are pushed or merged to the default branch
variables:
TWINE_USERNAME: "__token__"
TWINE_PASSWORD: $TWINE_API
needs:
- job: build
artifacts: true
script:
- python3 -m pip install twine
- python3 -m twine upload src/dist/*
create_tag:
extends: .git:create_tag
stage: release
tags:
- gitlab-org-docker
variables:
VERSION: $GitVersion_SemVer
TOKEN: $GITLAB_TOKEN
needs:
- job: gitversion
artifacts: true
rules:
- if: $CI_COMMIT_TAG
when: never # Do not run this job when a tag is created manually
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH # Run this job when commits are pushed or merged to the default branch

View file

@ -1,15 +0,0 @@
---
.git:create_tag:
image: alpine:3.21
variables:
GIT_STRATEGY: clone
GIT_DEPTH: 0
GIT_LFS_SKIP_SMUDGE: 1
VERSION: ''
TOKEN: '' # Token with push privileges
script:
- apk add git
- git remote set-url origin https://oauth2:$TOKEN@$CI_SERVER_HOST/$CI_PROJECT_PATH
- git tag $VERSION
- git push origin tag $VERSION

View file

@ -1,31 +0,0 @@
---
.versioning:gitversion:
image:
name: mcr.microsoft.com/dotnet/sdk:9.0
variables:
GIT_STRATEGY: clone
GIT_DEPTH: 0 # force a deep/non-shallow fetch need by gitversion
GIT_LFS_SKIP_SMUDGE: 1
cache: [] # caches and before / after scripts can mess things up
script:
- |
dotnet tool install --global GitVersion.Tool --version 5.*
export PATH="$PATH:/root/.dotnet/tools"
dotnet-gitversion -output buildserver
# We could just collect the output file gitversion.properties (with artifacts:report:dotenv: gitversion.properties as it is already in DOTENV format,
# however it contains ~33 variables which unnecessarily consumes many of the 50 max DOTENV variables of the free GitLab version.
# Limits are higher for licensed editions, see https://docs.gitlab.com/ee/ci/yaml/artifacts_reports.html#artifactsreportsdotenv
grep 'GitVersion_LegacySemVer=' gitversion.properties >> gitversion.env
grep 'GitVersion_SemVer=' gitversion.properties >> gitversion.env
grep 'GitVersion_FullSemVer=' gitversion.properties >> gitversion.env
grep 'GitVersion_Major=' gitversion.properties >> gitversion.env
grep 'GitVersion_Minor=' gitversion.properties >> gitversion.env
grep 'GitVersion_Patch=' gitversion.properties >> gitversion.env
grep 'GitVersion_MajorMinorPatch=' gitversion.properties >> gitversion.env
grep 'GitVersion_BuildMetaData=' gitversion.properties >> gitversion.env
artifacts:
reports:
# propagates variables into the pipeline level
dotenv: gitversion.env

View file

@ -0,0 +1,92 @@
steps:
- name: gitversion
depends_on: [] # nothing start emititly
when:
event: push
branch: main
image: mcr.microsoft.com/dotnet/sdk:9.0
environment:
CI_TOKEN:
from_secret: CI_TOKEN
commands:
- git remote set-url origin https://CodeByMrFinchum:$CI_TOKEN@code.boxyfoxy.net/$CI_REPO.git
- git fetch --unshallow --tags
- apt-get update && apt-get install -y jq
- dotnet tool install --global GitVersion.Tool --version 5.*
- export PATH="$PATH:/root/.dotnet/tools"
- dotnet-gitversion -output json > version.json
- ls
- cat version.json
- |
echo "GitVersion_SemVer=$(jq -r '.SemVer' version.json)" >> gitversion.env
echo "GitVersion_LegacySemVer=$(jq -r '.LegacySemVer' version.json)" >> gitversion.env
echo "GitVersion_FullSemVer=$(jq -r '.FullSemVer' version.json)" >> gitversion.env
echo "GitVersion_Major=$(jq -r '.Major' version.json)" >> gitversion.env
echo "GitVersion_Minor=$(jq -r '.Minor' version.json)" >> gitversion.env
echo "GitVersion_Patch=$(jq -r '.Patch' version.json)" >> gitversion.env
echo "GitVersion_MajorMinorPatch=$(jq -r '.MajorMinorPatch' version.json)" >> gitversion.env
echo "GitVersion_BuildMetaData=$(jq -r '.BuildMetaData' version.json)" >> gitversion.env
- name: tagging
depends_on: [gitversion]
when:
event: push
branch: main
image: alpine/git
environment:
CI_TOKEN:
from_secret: CI_TOKEN
commands:
- ls
- cat gitversion.env
- git config --global user.email "ci@noreply.boxyfoxy.net"
- git config --global user.name "CI Bot"
- git remote set-url origin https://CodeByMrFinchum:$${CI_TOKEN}@code.boxyfoxy.net/$${CI_REPO}.git
- . gitversion.env
- git tag $GitVersion_SemVer
- git push origin tag $GitVersion_SemVer
- name: build
depends_on: [gitversion, tagging]
when:
event: push
branch: main
image: python:3.9.21
commands:
- ls
- cat gitversion.env
- export $(cat gitversion.env | xargs)
- sed -i "s/0.0.1/$GitVersion_SemVer/" src/OptimaLab35/__init__.py
- cat src/OptimaLab35/__init__.py
- python3 -m pip install build
- python3 -m build --wheel --sdist -s src
- name: publish_pypi
depends_on: [gitversion, tagging, build]
when:
event: push
branch: main
image: python:3.9.21
environment:
TWINE_PASSWORD:
from_secret: TWINE_API
TWINE_USERNAME: "__token__"
commands:
- ls
- python3 -m pip install twine
- python3 -m twine upload src/dist/*
- name: publish_forgejo
depends_on: [gitversion, tagging, build]
when:
event: push
branch: main
image: python:3.9.21
environment:
TWINE_PASSWORD:
from_secret: PKG_TOKEN
TWINE_USERNAME: "CodeByMrFinchum"
commands:
- ls
- python3 -m pip install twine
- python3 -m twine upload --repository-url https://code.boxyfoxy.net/api/packages/CodeByMrFinchum/pypi src/dist/*

View file

@ -1,10 +1,124 @@
# Changelog
## 1.5.x
### 1.5.0: Feature - Time of dateEdit now today
- Changes that instead of the dateEdit elements being always set to a last day of 2024 it is the current day.
## 1.4.x
### 1.4.2: Fix links
- Fixed that changelog was linked to GitLab, not it is to code.boxyfoxy.net
- Fixed Changelog
### 1.4.1: Fix CI
- Fixed pipline
### 1.4.0: New CI
- Migrated repo from GitLab to my forgejo instance, therefore switching to woodpecker CI
---
## 1.3.x
### 1.3.4: Fix - Spelling (25.04.01)
- Fixed misspelling in preview window.
### 1.3.3: Patch - Increased Preview Performance x2 (25.04.01)
- Reduced preview stutter: Previously, the image was updated twice when changing brightness or contrast. Now, it updates only once, improving performance by 100%.
- There is still room for improvement. Analysis shows that image processing takes the most time, while displaying in Qt is relatively fast. Reducing the image size impacts performance, so resizing to 50% is a good idea.
- There is an issue where `QRunner` does not always finish in the correct order when brightness or contrast values are changed rapidly. ATM I do not know how to fix this easly.
- The "Preview" watermark now displays the brightness and contrast levels of the shown image.
### 1.3.2: Fix - Fixed problem with app folders (25.03.30)
- Fixed a problem that the app folder path was not generated correctly.
### 1.3.1: Fix - Fixed insert exif not working (25.03.27)
- Fixed the feature that inserted exif into images without modifying them.
### 1.3.0: Feature - Write Adjustments into EXIF (25.03.24)
- Changes to contrast and brightness are now recorded in the EXIF comment section (labeled *Scanner* in the program).
- This allows users to track what adjustments were made to an image.
---
## 1.2.x
### 1.2.2: Patch - Pyproject file (25.03.23)
- Fixed `Development Status` Classifier
- Added <4.0 python version
### 1.2.1: Patch - Changelog file (25.03.23)
- Patches formation in changelog file.
### 1.2.0: Refactor - Splitting Classes into Separate Files (25.03.23)
- 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: Feature - New Function in Preview Window (25.03.23)
- 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 (25.03.06)
### 1.0.1: Fixed spelling
- Fixes spelling some places
### 1.0.0: Fix version bump
- Version was not bumped correctly
---
## 0.15.1: Final Polish (25.03.06)
- Fixed a bug where the GPS field being empty but selected caused issues.
- EXIF insertion is now canceled if any image in the folder does not end with a number.
- Minor GUI adjustments for a more polished experience.
- Disabled preview adjustment controls until an image is loaded to prevent errors.
## 0.15.0: Preview Image Resizing Update (25.02.12)
- Added the ability to change the preview image size dynamically.
- Previously, the image was processed and displayed at its original size, causing lag or unresponsiveness when adjusting the slider.
- Reducing the processed image size should help improve performance.
- Default preview image size is now **25%**, but users can adjust it between **10% and 100%**.
---
## 0.14.x
### 0.14.1: Patch changelog (25.02.12)
- Updated the changelog to include missing entries.
### 0.14.0: Code refactor (by Mr. Finch) (25.02.11)
- Introduced constants and optimized the code.
## 0.13.x (25.02.11)
### 0.13.1: Fixed image processing
- Fixed a bug that made it impossible to process images.
### 0.13.0: Requirements file (by Mr. Finch)
- Added a requirements file.
---
## 0.12.x
### 0.12.1: Removed Unnecessary Debug Prints
- Removed leftover debug print statements.
### 0.12.6: Disabled app restart on Windows (25.02.11)
- The app can not restart properly on Windows, so the restart button has been hidden when the OS is `nt`.
- Also updated tool tip to indicate that changing theme requeres are restart.
### 0.12.0: New Settings Menu & Patches
### 0.12.5: Fixed EXIF File Generation Bug (25.02.10)
- Fixed a bug where the application failed to generate a new default EXIF file if none was available. Now, the file is correctly created when missing.
### 0.12.4: Updated README (25.02.10)
- The README file (project description) now includes updates description and screenshots.
### 0.12.3: UI Adjustments (25.02.10)
- Minor changes to maintain a unified layout across all windows.
- Added option to sync app theme with OS (if supported).
- Set auto theme as the default value.
### 0.12.2: Minor UI Improvements for Theme Compatibility (25.02.10)
- Fixed text clipping issues when using the new theme options.
### 0.12.1: Removed Unnecessary Debug Prints (25.02.09)
- Removed leftover debug statements.
### 0.12.0: New Settings Menu & Patches (25.02.09)
- **New Settings Window:**
- The updater window has been reworked into a settings window.
- **Initial settings (first tab) include:**
@ -17,9 +131,10 @@
- Fixed an issue where links in labels (About window) did not open a browser.
- Added a changelog link in the About window.
- Minor changes to `utility.py` to handle settings.
---
## 0.11.x
## 0.11.x (25.02.05)
### 0.11.1: Fixed pipeline
- Fixed pipeline publish error
@ -31,7 +146,7 @@
---
## 0.10.x
## 0.10.x (25.02.04)
### 0.10.1: Fixed Updater
- Fixed an issue where the updater was permanently disabled.
@ -69,7 +184,7 @@
- Improved error handling for updater: now displays the specific error message instead of just **"error"** when an issue occurs during update checks.
- Ensured all child windows close when the main window is closed.
### 0.8.3: Fix OptimaLab35 Not Closing After Update
### 0.8.3: Fix - OptimaLab35 Not Closing After Update
- Fixed an issue where **OptimaLab35** would not close properly when updating, resulting in an unresponsive instance and multiple running processes.
### 0.8.2: Patch for New PyPiUpdater Version

View file

@ -1,21 +1,17 @@
# **OptimaLab35**
_Last updated: 02 Feb 2025 (v.0.9.0)_
Developed on my [forgejo instance](https://code.boxyfoxy.net/CodeByMrFinchum), [GitLab](https://gitlab.com/CodeByMrFinchum) is used as backup.
## **Overview**
**OptimaLab35** enhances **OPTIMA35** (**Organizing, Processing, Tweaking Images, and Modifying scanned Analogs from 35mm Film**) by offering a user-friendly graphical interface for efficient image and metadata management.
It serves as a GUI for the [OPTIMA35 library](https://gitlab.com/CodeByMrFinchum/optima35), providing an intuitive way to interact with the core functionalities.
It serves as a GUI for the [optima35 library](https://code.boxyfoxy.net/CodeByMrFinchum/optima35), providing an intuitive way to interact with the core functionalities.
---
## **Current Status**
### **Early Development**
OptimaLab35 is actively developed using **PySide6** and **Qt**, providing a modern interface for **OPTIMA35**.
The program is still in its early stages, and features may change drastically over time. Some features might be added but later removed if they don't prove useful. Expect significant changes to the UI and functionality between updates.
For the most accurate and detailed update information, please refer to the [**CHANGELOG**](https://gitlab.com/CodeByMrFinchum/OptimaLab35/-/blob/main/CHANGELOG.md) as the readme might lack behind.
The program has reached a stable release. All functions have been tested, and there should be *no* bugs.
While there is always room for additional features and optimizations, the core functionality is complete and reliable.
---
@ -38,9 +34,9 @@ For the most accurate and detailed update information, please refer to the [**CH
- Automatically adjust EXIF timestamps based on image file names
- Add GPS coordinates to images
### **Updater**
### **Settings**
- Option to use `PyQtDarkTheme` and select Dark, Light, or auto theme
- Checks for updates on PyPI, automatically downloads and installs the latest version
- Restarts the program after update
---
@ -53,39 +49,45 @@ pip install OptimaLab35
---
## Preview GUI **0.9.0**
**PREVIEW** might be out of date.
## GUI Preview
The layout remains consistent with v1.0.0.
The UI is OS-dependent, but a custom theme can be enabled in the settings.
**Main tab**
![main](https://gitlab.com/CodeByMrFinchum/OptimaLab35/-/raw/main/media/main_tab.png){width=40%}
![main](https://code.boxyfoxy.net/CodeByMrFinchum/OptimaLab35/raw/branch/main/media/mainwindow_light.png){width=40%}
![main](https://code.boxyfoxy.net/CodeByMrFinchum/OptimaLab35/raw/branch/main/media/mainwindow_dark.png){width=40%}
**Exif tab**
![main](https://gitlab.com/CodeByMrFinchum/OptimaLab35/-/raw/main/media/exif_tab.png){width=40%}
**Exif editor**
![main](https://gitlab.com/CodeByMrFinchum/OptimaLab35/-/raw/main/media/exif_editor.png){width=40%}
![main](https://code.boxyfoxy.net/CodeByMrFinchum/OptimaLab35/raw/branch/main/media/mainwindow_exif_light.png){width=40%}
![main](https://code.boxyfoxy.net/CodeByMrFinchum/OptimaLab35/raw/branch/main/media/mainwindow_exif_dark.png){width=40%}
**Preview window**
![main](https://gitlab.com/CodeByMrFinchum/OptimaLab35/-/raw/main/media/preview_window.png){width=40%}
![main](https://code.boxyfoxy.net/CodeByMrFinchum/OptimaLab35/raw/branch/main/media/previewwindow_light.png){width=40%}
![main](https://code.boxyfoxy.net/CodeByMrFinchum/OptimaLab35/raw/branch/main/media/previewwindow_dark.png){width=40%}
**About**
**Settings**
![main](https://gitlab.com/CodeByMrFinchum/OptimaLab35/-/raw/main/media/about_window.png){width=40%}
![main](https://code.boxyfoxy.net/CodeByMrFinchum/OptimaLab35/raw/branch/main/media/settingswindow_light.png){width=40%}
![main](https://code.boxyfoxy.net/CodeByMrFinchum/OptimaLab35/raw/branch/main/media/settingswindow_dark.png){width=40%}
**Updater**
![main](https://gitlab.com/CodeByMrFinchum/OptimaLab35/-/raw/main/media/updater_window.png){width=40%}
![main](https://code.boxyfoxy.net/CodeByMrFinchum/OptimaLab35/raw/branch/main/media/settingswindow_updater_light.png){width=40%}
![main](https://code.boxyfoxy.net/CodeByMrFinchum/OptimaLab35/raw/branch/main/media/settingswindow_updater_dark.png){width=40%}
---
# Use of LLMs
# Contribution
Thanks to developer [Mr Finch](https://gitlab.com/MrFinchMkV) for contributing to this project.
## Use of LLMs
In the interest of transparency, I disclose that Generative AI (GAI) large language models (LLMs), including OpenAIs ChatGPT and Ollama models (e.g., OpenCoder and Qwen2.5-coder), have been used to assist in this project.
## Areas of Assistance:
### Areas of Assistance:
- Project discussions and planning
- Spelling and grammar corrections
- Suggestions for suitable packages and libraries
@ -97,7 +99,7 @@ In cases where LLMs contribute directly to code or provide substantial optimizat
- mradermacher gguf Q4K-M Instruct version of infly/OpenCoder-1.5B
- unsloth gguf Q4K_M Instruct version of both Qwen/QWEN2 1.5B and 3B
### References
#### References
1. **Huang, Siming, et al.**
*OpenCoder: The Open Cookbook for Top-Tier Code Large Language Models.*
2024. [PDF](https://arxiv.org/pdf/2411.04905)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 293 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 222 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 220 KiB

BIN
media/mainwindow_dark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 241 KiB

BIN
media/mainwindow_light.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

View file

@ -1,4 +1,11 @@
OptimaLab35 is in active development, and even *stable* versions may produce errors in unexpected situations. It is a GUI for optima35.
Please visit [OptimaLab35](https://gitlab.com/CodeByMrFinchum/OptimaLab35/-/blob/main/media/exif_editor.png?ref_type=heads) gitlab page for more information.
**[OptimaLab35](https://code.boxyfoxy.net/CodeByMrFinchum/OptimaLab35)** is a graphical interface for the [optima35 library](https://code.boxyfoxy.net/CodeByMrFinchum/optima35), designed for efficient image and metadata management.
Uses [optima35](https://gitlab.com/CodeByMrFinchum/optima35) to modify images.
Developed on my [forgejo instance](https://code.boxyfoxy.net/CodeByMrFinchum), [GitLab](https://gitlab.com/CodeByMrFinchum) is used as backup.
### **Features**
- Resize, adjust brightness/contrast, and convert images to grayscale
- Add customizable text-based watermarks
- Manage EXIF data: add, copy, remove, and adjust timestamps
- Preview image adjustments in real time
- Theme selection (light, dark, auto)
- Automatic updates via PyPI

4
requirements.txt Normal file
View file

@ -0,0 +1,4 @@
optima35
PyPiUpdater
pyside6
PyYAML

View file

@ -1,7 +1,30 @@
from OptimaLab35 import gui, __version__
import sys
from PySide6 import QtWidgets
from .utils.utility import Utilities
from .mainWindow import OptimaLab35
from .const import (
CONFIG_BASE_PATH
)
def main():
gui.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()

2
src/OptimaLab35/const.py Normal file
View file

@ -0,0 +1,2 @@
APPLICATION_NAME = "OptimaLab35"
CONFIG_BASE_PATH = "~/.config/OptimaLab35"

View file

@ -1,997 +0,0 @@
import sys
import os
from datetime import datetime
import importlib.resources as pkg_resources
try:
from OptimaLab35.ui import resources_rc
# keep the try for now
except Exception as e:
print(e)
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.settings_window import Ui_Settings_Window
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, Slot, QDir
from PySide6 import QtWidgets, QtCore
from PySide6.QtWidgets import (
QMessageBox,
QApplication,
QMainWindow,
QWidget,
QVBoxLayout,
QLabel,
QLineEdit,
QPushButton,
QCheckBox,
QFileDialog,
QHBoxLayout,
QSpinBox,
QProgressBar,
)
from PySide6.QtGui import QPixmap, QRegularExpressionValidator, QIcon
class OptimaLab35(QMainWindow, Ui_MainWindow):
def __init__(self):
super(OptimaLab35, self).__init__()
self.name = "OptimaLab35"
self.version = __version__
self.o = OptimaManager()
self.u = Utilities(os.path.expanduser("~/.config/OptimaLab35"))
self.app_settings = self.u.load_settings()
self.thread_pool = QThreadPool() # multi thread ChatGPT
# Initiate internal object
self.exif_file = os.path.expanduser("~/.config/OptimaLab35/exif.yaml")
self.available_exif_data = None
self.settings = {}
# UI elements
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.sd = SimpleDialog()
# Change UI elements
self.change_statusbar(f"{self.name} v{self.version}", 10000)
self.set_title()
self.default_ui_layout()
self.define_gui_interaction()
try:
self.setWindowIcon(QIcon(":app-icon.png"))
# keep the try for now
except Exception as e:
print(e)
# Init function
def default_ui_layout(self):
self.ui.png_quality_spinBox.setVisible(False)
self.ui.png_quality_Slider.setVisible(False)
self.ui.quality_label_2.setVisible(False)
def set_title(self):
if self.version == "0.0.1":
title = f"{self.name} DEV MODE"
else:
title = self.name
self.setWindowTitle(title)
def define_gui_interaction(self):
self.ui.input_folder_button.clicked.connect(self.browse_input_folder)
self.ui.output_folder_button.clicked.connect(self.browse_output_folder)
self.ui.start_button.clicked.connect(self.start_process)
self.ui.insert_exif_Button.clicked.connect(self.startinsert_exif)
self.ui.image_type.currentIndexChanged.connect(self.update_quality_options)
self.ui.exif_checkbox.stateChanged.connect(
lambda state: self.handle_checkbox_state(state, 2, self.populate_exif)
)
self.ui.tabWidget.currentChanged.connect(self.on_tab_changed)
self.ui.edit_exif_button.clicked.connect(self.open_exif_editor)
self.ui.actionAbout.triggered.connect(self.info_window)
self.ui.preview_Button.clicked.connect(self.open_preview_window)
self.ui.actionSettings.triggered.connect(self.open_updater_window)
regex = QRegularExpression(r"^\d{1,2}\.\d{1,6}$")
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):
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 = SettingsWindow(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
self.ui.brightness_spinBox.setValue(value1)
self.ui.contrast_spinBox.setValue(value2)
self.ui.grayscale_checkBox.setChecked(checkbox_state)
def info_window(self):
info_text = f"""
<h3>{self.name} v{self.version}</h3>
<p>(C) 2024-2025 Mr Finchum aka CodeByMrFinchum</p>
<p>{self.name} is a GUI for {self.o.name} (v{self.o.version}), enhancing its functionality with a user-friendly interface for efficient image and metadata management.</p>
<h4>Features:</h4>
<ul>
<li>Image processing: resize, grayscale, brightness/contrast adjustments</li>
<li>Live image preview: see changes before applying</li>
<li>EXIF management: add, copy, remove metadata, GPS support</li>
<li>Watermarking: add custom text-based watermarks</li>
</ul>
<p>For more details, visit:</p>
<ul>
<li><a href="https://gitlab.com/CodeByMrFinchum/OptimaLab35/-/blob/main/CHANGELOG.md?ref_type=heads">Changelog</a></li>
<li><a href="https://gitlab.com/CodeByMrFinchum/OptimaLab35">OptimaLab35 GitLab</a></li>
<li><a href="https://gitlab.com/CodeByMrFinchum/optima35">optima35 GitLab</a></li>
</ul>
"""
self.sd.show_dialog(f"{self.name} v{self.version}", info_text)
def handle_qprogressbar(self, value):
self.ui.progressBar.setValue(value)
def toggle_buttons(self, state):
self.ui.start_button.setEnabled(state)
if self.ui.exif_checkbox.isChecked():
self.ui.insert_exif_Button.setEnabled(state)
def handle_checkbox_state(self, state, desired_state, action):
"""Perform an action based on the checkbox state and a desired state. Have to use lambda when calling."""
if state == desired_state:
action()
def on_tab_changed(self, index):
"""Handle tab changes."""
# chatgpt
if index == 1: # EXIF Tab
self.handle_exif_file("read")
elif index == 0: # Main Tab
self.handle_exif_file("write")
def sort_dict_of_lists(self, input_dict):
# Partily ChatGPT
sorted_dict = {}
for key, lst in input_dict.items():
# Sort alphabetically for strings, numerically for numbers
if key == "iso":
lst = [int(x) for x in lst]
lst = sorted(lst)
lst = [str(x) for x in lst]
sorted_dict["iso"] = lst
elif all(isinstance(x, str) for x in lst):
sorted_dict[key] = sorted(lst, key=str.lower) # Case-insensitive sort for strings
return sorted_dict
def populate_comboboxes(self, combo_mapping):
"""Populate comboboxes with EXIF data."""
# ChatGPT
for field, comboBox in combo_mapping.items():
comboBox.clear() # Clear existing items
comboBox.addItems(map(str, self.available_exif_data.get(field, [])))
def open_exif_editor(self):
"""Open the EXIF Editor."""
self.exif_editor = ExifEditor(self.available_exif_data)
self.exif_editor.exif_data_updated.connect(self.update_exif_data)
self.exif_editor.show()
def update_exif_data(self, updated_exif_data):
"""Update the EXIF data."""
self.exif_data = updated_exif_data
self.populate_exif()
def populate_exif(self):
# partly chatGPT
# Mapping of EXIF fields to comboboxes in the UI
combo_mapping = {
"make": self.ui.make_comboBox,
"model": self.ui.model_comboBox,
"lens": self.ui.lens_comboBox,
"iso": self.ui.iso_comboBox,
"image_description": self.ui.image_description_comboBox,
"user_comment": self.ui.user_comment_comboBox,
"artist": self.ui.artist_comboBox,
"copyright_info": self.ui.copyright_info_comboBox,
}
self.populate_comboboxes(combo_mapping)
def update_quality_options(self):
"""Update visibility of quality settings based on selected format."""
# Partly ChatGPT
selected_format = self.ui.image_type.currentText()
# Hide all quality settings
self.ui.png_quality_spinBox.setVisible(False)
self.ui.jpg_quality_spinBox.setVisible(False)
self.ui.jpg_quality_Slider.setVisible(False)
self.ui.png_quality_Slider.setVisible(False)
self.ui.quality_label_1.setVisible(False)
self.ui.quality_label_2.setVisible(False)
# Show relevant settings
if selected_format == "jpg":
self.ui.jpg_quality_spinBox.setVisible(True)
self.ui.jpg_quality_Slider.setVisible(True)
self.ui.quality_label_1.setVisible(True)
elif selected_format == "webp":
self.ui.jpg_quality_spinBox.setVisible(True)
self.ui.jpg_quality_Slider.setVisible(True)
self.ui.quality_label_1.setVisible(True)
elif selected_format == "png":
self.ui.png_quality_spinBox.setVisible(True)
self.ui.png_quality_Slider.setVisible(True)
self.ui.quality_label_2.setVisible(True)
def browse_input_folder(self):
folder = QFileDialog.getExistingDirectory(self, "Select Input Folder")
if folder:
self.ui.input_path.setText(folder)
def browse_output_folder(self):
folder = QFileDialog.getExistingDirectory(self, "Select Output Folder")
if folder:
self.ui.output_path.setText(folder)
def change_statusbar(self, msg, timeout = 500):
self.ui.statusBar.showMessage(msg, timeout)
# Core functions
def on_processing_finished(self):
self.toggle_buttons(True)
self.handle_qprogressbar(0)
QMessageBox.information(self, "Information", "Finished!")
def image_list_from_folder(self, path):
image_files = [
f for f in os.listdir(path) if f.lower().endswith((".png", ".jpg", ".jpeg", ".webp"))
]
image_files.sort()
return image_files
def control_before_start(self, process):
input_folder = self.settings["input_folder"]
output_folder = self.settings["output_folder"]
image_list = self.image_list_from_folder(input_folder)
input_folder_valid = os.path.exists(input_folder)
if isinstance(output_folder, str):
output_folder_valid = os.path.exists(output_folder)
if process == "image":
if not input_folder or not output_folder:
QMessageBox.warning(self, "Warning", "Input or output folder not selected")
return False
if not input_folder_valid or not output_folder_valid:
QMessageBox.warning(self, "Warning", f"Input location {input_folder_valid}\nOutput folder {output_folder_valid}...")
return False
if len(self.image_list_from_folder(output_folder)) != 0:
reply = QMessageBox.question(
self,
"Confirmation",
"Output folder containes images, which might get overritten, continue?",
QMessageBox.Yes | QMessageBox.No,
)
if reply == QMessageBox.No:
return False
elif process == "exif":
if not input_folder:
QMessageBox.warning(self, "Warning", "Input not selected")
return False
if output_folder:
reply = QMessageBox.question(
self,
"Confirmation",
"Output folder selected, but insert exif is done to images in input folder, Continue?",
QMessageBox.Yes | QMessageBox.No,
)
if reply == QMessageBox.No:
return False
if not input_folder_valid :
QMessageBox.warning(self, "Warning", f"Input location {input_folder_valid}")
return False
else:
print("Something went wrong")
if len(image_list) == 0:
QMessageBox.warning(self, "Warning", "Selected folder has no supported files.")
return False
return True
def start_process(self):
self.toggle_buttons(False)
u = self.update_settings()
if u != None: # Get all user selected data
QMessageBox.warning(self, "Warning", f"Error: {u}")
self.toggle_buttons(True)
return
if self.control_before_start("image") == False:
self.toggle_buttons(True)
return
image_list = self.image_list_from_folder(self.settings["input_folder"])
# Create a worker ChatGPT
worker = ImageProcessorRunnable(image_list, self.settings, self.handle_qprogressbar)
worker.signals.finished.connect(self.on_processing_finished)
# Start worker in thread pool ChatGPT
self.thread_pool.start(worker)
def insert_exif(self, image_files):
input_folder = self.settings["input_folder"]
i = 1
for image_file in image_files:
input_path = os.path.join(input_folder, image_file)
self.o.insert_exif_to_image(
exif_dict = self.settings["user_selected_exif"],
image_path = input_path,
gps = self.settings["gps"])
self.change_statusbar(image_file, 100)
self.handle_qprogressbar(int((i / len(image_files)) * 100))
i += 1
self.ui.progressBar.setValue(0)
def startinsert_exif(self):
self.toggle_buttons(False)
u = self.update_settings()
if u != None: # Get all user selected data
QMessageBox.warning(self, "Warning", f"Error: {u}")
self.toggle_buttons(True)
return
if self.control_before_start("exif") == False:
self.toggle_buttons(True)
return
image_list = self.image_list_from_folder(self.settings["input_folder"])
self.insert_exif(image_list)
self.toggle_buttons(True)
QMessageBox.information(self, "Information", "Finished")
def get_checkbox_value(self, checkbox, default = None):
"""Helper function to get the value of a checkbox or a default value."""
return checkbox.isChecked() if checkbox else default
def get_spinbox_value(self, spinbox, default = None):
"""Helper function to get the value of a spinbox and handle empty input."""
return int(spinbox.text()) if spinbox.text() else default
def get_combobox_value(self, combobox, default = None):
"""Helper function to get the value of a combobox."""
return combobox.currentIndex() + 1 if combobox.currentIndex() != -1 else default
def get_text_value(self, lineedit, default = None):
"""Helper function to get the value of a text input field."""
return lineedit.text() if lineedit.text() else default
def get_date(self):
date_input = self.ui.dateEdit.date().toString("yyyy-MM-dd")
new_date = datetime.strptime(date_input, "%Y-%m-%d")
return new_date.strftime("%Y:%m:%d 00:00:00")
def collect_selected_exif(self):
user_data = {}
user_data["make"] = self.ui.make_comboBox.currentText()
user_data["model"] = self.ui.model_comboBox.currentText()
user_data["lens"] = self.ui.lens_comboBox.currentText()
user_data["iso"] = self.ui.iso_comboBox.currentText()
user_data["image_description"] = self.ui.image_description_comboBox.currentText()
user_data["user_comment"] = self.ui.user_comment_comboBox.currentText()
user_data["artist"] = self.ui.artist_comboBox.currentText()
user_data["copyright_info"] = self.ui.copyright_info_comboBox.currentText()
user_data["software"] = f"{self.name} (v{self.version}) with {self.o.name} (v{self.o.version})"
return user_data
def get_selected_exif(self):
"""Collect selected EXIF data and handle date and GPS if necessary."""
selected_exif = self.collect_selected_exif() if self.ui.exif_checkbox.isChecked() else None
if selected_exif:
if self.ui.add_date_checkBox.isChecked():
selected_exif["date_time_original"] = self.get_date()
if self.ui.gps_checkBox.isChecked():
self.settings["gps"] = [float(self.ui.lat_lineEdit.text()), float(self.ui.long_lineEdit.text())]
else:
self.settings["gps"] = None
return selected_exif
def check_selected_exif(self, exif):
for key in exif:
if len(exif[key]) == 0:
return f"{key} is empty"
return True
def update_settings(self):
"""Update .settings from all GUI elements."""
# Basic
self.settings["input_folder"] = self.get_text_value(self.ui.input_path)
self.settings["output_folder"] = self.get_text_value(self.ui.output_path)
self.settings["file_format"] = self.ui.image_type.currentText()
# Quality
self.settings["jpg_quality"] = self.get_spinbox_value(self.ui.jpg_quality_spinBox)
self.settings["png_compression"] = self.get_spinbox_value(self.ui.png_quality_spinBox)
self.settings["resize"] = int(self.ui.resize_spinBox.text()) if self.ui.resize_spinBox.text() != "100" else None
self.settings["optimize"] = self.get_checkbox_value(self.ui.optimize_checkBox)
# Changes for image
self.settings["brightness"] = int(self.ui.brightness_spinBox.text()) if self.ui.brightness_spinBox.text() != "0" else None
self.settings["contrast"] = int(self.ui.contrast_spinBox.text()) if self.ui.contrast_spinBox.text() != "0" else None
self.settings["grayscale"] = self.get_checkbox_value(self.ui.grayscale_checkBox)
# Watermark
self.settings["font_size"] = self.get_combobox_value(self.ui.font_size_comboBox)
self.settings["watermark"] = self.get_text_value(self.ui.watermark_lineEdit)
# Naming
new_name = self.get_text_value(self.ui.filename, False) if self.ui.rename_checkbox.isChecked() else False
if isinstance(new_name, str): new_name = new_name.replace(" ", "_")
self.settings["new_file_names"] = new_name
self.settings["invert_image_order"] = self.get_checkbox_value(self.ui.revert_checkbox) if new_name is not False else None
# Handle EXIF data selection
self.settings["copy_exif"] = self.get_checkbox_value(self.ui.exif_copy_checkBox)
self.settings["own_exif"] = self.get_checkbox_value(self.ui.exif_checkbox)
self.settings["own_date"] = self.get_checkbox_value(self.ui.add_date_checkBox)
if self.settings["own_exif"]:
self.settings["user_selected_exif"] = self.get_selected_exif()
else:
self.settings["user_selected_exif"] = None
self.settings["gps"] = None
if self.settings["user_selected_exif"] is not None:
u = self.check_selected_exif(self.settings["user_selected_exif"])
if u != True:
return u
# Helper functions, low level
def handle_exif_file(self, do):
# TODO: add check if data is missing.
if do == "read":
file_dict = self.u.read_yaml(self.exif_file)
self.available_exif_data = self.sort_dict_of_lists(file_dict)
elif do == "write":
self.u.write_yaml(self.exif_file, self.available_exif_data)
def closeEvent(self, event):
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_2.setText(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)
# 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_2.setText(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)
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()
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(),
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)
finished = Signal()
class ImageProcessorRunnable(QRunnable):
# ChatGPT gave rough function layout
def __init__(self, image_files, settings, progress_callback):
super().__init__()
self.image_files = image_files
self.settings = settings
self.signals = WorkerSignals()
self.signals.progress.connect(progress_callback)
self.o = OptimaManager()
self.u = Utilities()
def run(self):
input_folder = self.settings["input_folder"]
output_folder = self.settings["output_folder"]
for i, image_file in enumerate(self.image_files, start=1):
input_path = os.path.join(input_folder, image_file)
if self.settings["new_file_names"] != False:
image_name = self.u.append_number_to_name(self.settings["new_file_names"], i, len(self.image_files), self.settings["invert_image_order"])
else:
image_name = os.path.splitext(image_file)[0]
output_path = os.path.join(output_folder, image_name)
self.o.process_and_save_image(
image_input_file = input_path,
image_output_file = output_path,
file_type = self.settings["file_format"],
quality = self.settings["jpg_quality"],
compressing = self.settings["png_compression"],
optimize = self.settings["optimize"],
resize = self.settings["resize"],
watermark = self.settings["watermark"],
font_size = self.settings["font_size"],
grayscale = self.settings["grayscale"],
brightness = self.settings["brightness"],
contrast = self.settings["contrast"],
dict_for_exif = self.settings["user_selected_exif"],
gps = self.settings["gps"],
copy_exif = self.settings["copy_exif"]
)
self.signals.progress.emit(int((i / len(self.image_files)) * 100))
self.signals.finished.emit()
class 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():
u = Utilities(os.path.expanduser("~/.config/OptimaLab35"))
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"]:
app.setStyleSheet(qdarktheme.load_stylesheet(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,552 @@
import os
from datetime import datetime
from optima35.core import OptimaManager
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 (
QRunnable,
QThreadPool,
Signal,
QObject,
QRegularExpression,
Qt,
QDate
)
from PySide6.QtWidgets import (
QMessageBox,
QApplication,
QMainWindow,
QFileDialog
)
from PySide6.QtGui import QRegularExpressionValidator, QIcon
class OptimaLab35(QMainWindow, Ui_MainWindow):
def __init__(self):
super(OptimaLab35, self).__init__()
self.name = APPLICATION_NAME
self.version = __version__
self.o = OptimaManager()
self.u = Utilities(os.path.expanduser(CONFIG_BASE_PATH))
self.app_settings = self.u.load_settings()
self.thread_pool = QThreadPool() # multi thread ChatGPT
# Initiate internal object
self.exif_file = os.path.expanduser(f"{CONFIG_BASE_PATH}/exif.yaml")
self.available_exif_data = None
self.settings = {}
# UI elements
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.sd = SimpleDialog()
# Change UI elements
self.change_statusbar(f"{self.name} v{self.version}", 10000)
self.set_title()
self.default_ui_layout()
self.define_gui_interaction()
self.setWindowIcon(QIcon(":app-icon.png"))
# Init function
def default_ui_layout(self):
self.ui.png_quality_spinBox.setVisible(False)
self.ui.png_quality_Slider.setVisible(False)
self.ui.quality_label_2.setVisible(False)
def set_title(self):
if self.version == "0.0.1":
title = f"{self.name} DEV MODE"
else:
title = self.name
self.setWindowTitle(title)
def define_gui_interaction(self):
self.ui.input_folder_button.clicked.connect(self.browse_input_folder)
self.ui.output_folder_button.clicked.connect(self.browse_output_folder)
self.ui.start_button.clicked.connect(self.start_process)
self.ui.insert_exif_Button.clicked.connect(self.startinsert_exif)
self.ui.image_type.currentIndexChanged.connect(self.update_quality_options)
self.ui.exif_checkbox.stateChanged.connect(
lambda state: self.handle_checkbox_state(state, 2, self.populate_exif)
)
self.ui.tabWidget.currentChanged.connect(self.on_tab_changed)
self.ui.edit_exif_button.clicked.connect(self.open_exif_editor)
self.ui.actionAbout.triggered.connect(self.info_window)
self.ui.preview_Button.clicked.connect(self.open_preview_window)
self.ui.actionSettings.triggered.connect(self.open_updater_window)
regex = QRegularExpression(r"^\d{1,2}\.\d{1,6}$")
validator = QRegularExpressionValidator(regex)
self.ui.lat_lineEdit.setValidator(validator)
self.ui.long_lineEdit.setValidator(validator)
self.ui.dateEdit.setDate(QDate.currentDate())
# 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 = SettingsWindow(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
self.ui.brightness_spinBox.setValue(value1)
self.ui.contrast_spinBox.setValue(value2)
self.ui.grayscale_checkBox.setChecked(checkbox_state)
def info_window(self):
info_text = f"""
<h3>{self.name} v{self.version}</h3>
<p>(C) 2024-2025 Mr Finchum aka CodeByMrFinchum</p>
<p>{self.name} is a GUI for {self.o.name} (v{self.o.version}), enhancing its functionality with a user-friendly interface for efficient image and metadata management.</p>
<h4>Features:</h4>
<ul>
<li>Image processing: resize, grayscale, brightness/contrast adjustments</li>
<li>Live image preview: see changes before applying</li>
<li>EXIF management: add, copy, remove metadata, GPS support</li>
<li>Watermarking: add custom text-based watermarks</li>
</ul>
<p>For more details, visit:</p>
<ul>
<li><a href="https://code.boxyfoxy.net/CodeByMrFinchum/OptimaLab35/src/branch/main/CHANGELOG.md">Changelog</a></li>
<li><a href="https://gitlab.com/CodeByMrFinchum/OptimaLab35">OptimaLab35 GitLab</a></li>
<li><a href="https://gitlab.com/CodeByMrFinchum/optima35">optima35 GitLab</a></li>
</ul>
"""
self.sd.show_dialog(f"{self.name} v{self.version}", info_text)
def handle_qprogressbar(self, value):
self.ui.progressBar.setValue(value)
def toggle_buttons(self, state):
self.ui.start_button.setEnabled(state)
if self.ui.exif_checkbox.isChecked():
self.ui.insert_exif_Button.setEnabled(state)
def handle_checkbox_state(self, state, desired_state, action):
"""Perform an action based on the checkbox state and a desired state. Have to use lambda when calling."""
if state == desired_state:
action()
def on_tab_changed(self, index):
"""Handle tab changes."""
# chatgpt
if index == 1: # EXIF Tab
self.handle_exif_file("read")
elif index == 0: # Main Tab
self.handle_exif_file("write")
def sort_dict_of_lists(self, input_dict):
# Partily ChatGPT
sorted_dict = {}
for key, lst in input_dict.items():
# Sort alphabetically for strings, numerically for numbers
if key == "iso":
lst = [int(x) for x in lst]
lst = sorted(lst)
lst = [str(x) for x in lst]
sorted_dict["iso"] = lst
elif all(isinstance(x, str) for x in lst):
sorted_dict[key] = sorted(lst, key=str.lower) # Case-insensitive sort for strings
return sorted_dict
def populate_comboboxes(self, combo_mapping):
"""Populate comboboxes with EXIF data."""
# ChatGPT
for field, comboBox in combo_mapping.items():
comboBox.clear() # Clear existing items
comboBox.addItems(map(str, self.available_exif_data.get(field, [])))
def open_exif_editor(self):
"""Open the EXIF Editor."""
self.exif_editor = ExifEditor(self.available_exif_data)
self.exif_editor.exif_data_updated.connect(self.update_exif_data)
self.exif_editor.show()
def update_exif_data(self, updated_exif_data):
"""Update the EXIF data."""
self.exif_data = updated_exif_data
self.populate_exif()
def populate_exif(self):
# partly chatGPT
# Mapping of EXIF fields to comboboxes in the UI
combo_mapping = {
"make": self.ui.make_comboBox,
"model": self.ui.model_comboBox,
"lens": self.ui.lens_comboBox,
"iso": self.ui.iso_comboBox,
"image_description": self.ui.image_description_comboBox,
"user_comment": self.ui.user_comment_comboBox,
"artist": self.ui.artist_comboBox,
"copyright_info": self.ui.copyright_info_comboBox,
}
self.populate_comboboxes(combo_mapping)
def update_quality_options(self):
"""Update visibility of quality settings based on selected format."""
# Partly ChatGPT
selected_format = self.ui.image_type.currentText()
# Hide all quality settings
self.ui.png_quality_spinBox.setVisible(False)
self.ui.jpg_quality_spinBox.setVisible(False)
self.ui.jpg_quality_Slider.setVisible(False)
self.ui.png_quality_Slider.setVisible(False)
self.ui.quality_label_1.setVisible(False)
self.ui.quality_label_2.setVisible(False)
# Show relevant settings
if selected_format == "jpg":
self.ui.jpg_quality_spinBox.setVisible(True)
self.ui.jpg_quality_Slider.setVisible(True)
self.ui.quality_label_1.setVisible(True)
elif selected_format == "webp":
self.ui.jpg_quality_spinBox.setVisible(True)
self.ui.jpg_quality_Slider.setVisible(True)
self.ui.quality_label_1.setVisible(True)
elif selected_format == "png":
self.ui.png_quality_spinBox.setVisible(True)
self.ui.png_quality_Slider.setVisible(True)
self.ui.quality_label_2.setVisible(True)
def browse_input_folder(self):
folder = QFileDialog.getExistingDirectory(self, "Select Input Folder")
if folder:
self.ui.input_path.setText(folder)
def browse_output_folder(self):
folder = QFileDialog.getExistingDirectory(self, "Select Output Folder")
if folder:
self.ui.output_path.setText(folder)
def change_statusbar(self, msg, timeout = 500):
self.ui.statusBar.showMessage(msg, timeout)
# Core functions
def on_processing_finished(self):
self.toggle_buttons(True)
self.handle_qprogressbar(0)
QMessageBox.information(self, "Information", "Finished!")
def image_list_from_folder(self, path):
image_files = [
f for f in os.listdir(path) if f.lower().endswith((".png", ".jpg", ".jpeg", ".webp"))
]
image_files.sort()
return image_files
def control_before_start(self, process):
input_folder = self.settings["input_folder"]
output_folder = self.settings["output_folder"]
image_list = self.image_list_from_folder(input_folder)
input_folder_valid = os.path.exists(input_folder)
if isinstance(output_folder, str):
output_folder_valid = os.path.exists(output_folder)
if process == "image":
if not input_folder or not output_folder:
QMessageBox.warning(self, "Warning", "Input or output folder not selected")
return False
if not input_folder_valid or not output_folder_valid:
QMessageBox.warning(self, "Warning", f"Input location {input_folder_valid}\nOutput folder {output_folder_valid}...")
return False
if len(self.image_list_from_folder(output_folder)) != 0:
reply = QMessageBox.question(
self,
"Confirmation",
"Output folder containes images, which might get overritten, continue?",
QMessageBox.Yes | QMessageBox.No,
)
if reply == QMessageBox.No:
return False
elif process == "exif":
if not input_folder:
QMessageBox.warning(self, "Warning", "Input not selected")
return False
if output_folder:
reply = QMessageBox.question(
self,
"Confirmation",
"Output folder selected, but insert exif is done to images in input folder, Continue?",
QMessageBox.Yes | QMessageBox.No,
)
if reply == QMessageBox.No:
return False
if not input_folder_valid :
QMessageBox.warning(self, "Warning", f"Input location {input_folder_valid}")
return False
else:
print("Something went wrong")
if len(image_list) == 0:
QMessageBox.warning(self, "Warning", "Selected folder has no supported files.")
return False
return True
def start_process(self):
self.toggle_buttons(False)
u = self.update_settings()
if u != None: # Get all user selected data
QMessageBox.warning(self, "Warning", f"Error: {u}")
self.toggle_buttons(True)
return
if self.control_before_start("image") == False:
self.toggle_buttons(True)
return
image_list = self.image_list_from_folder(self.settings["input_folder"])
# Create a worker ChatGPT
worker = ImageProcessorRunnable(image_list, self.settings, self.handle_qprogressbar)
worker.signals.finished.connect(self.on_processing_finished)
# Start worker in thread pool ChatGPT
self.thread_pool.start(worker)
def control_ending(self, name_lst):
for item in name_lst:
try:
int(item[-5])
except ValueError:
return False
return True
def insert_exif(self, image_files):
input_folder = self.settings["input_folder"]
i = 1
for image_file in image_files:
input_path = os.path.join(input_folder, image_file)
self.o.insert_exif_to_image(
exif_dict = self.settings["user_selected_exif"],
image_path = input_path,
gps = self.settings["gps"])
self.change_statusbar(image_file, 100)
self.handle_qprogressbar(int((i / len(image_files)) * 100))
i += 1
self.ui.progressBar.setValue(0)
def startinsert_exif(self):
self.toggle_buttons(False)
u = self.update_settings()
if u != None: # Get all user selected data
QMessageBox.warning(self, "Warning", f"Error: {u}")
self.toggle_buttons(True)
return
if self.control_before_start("exif") == False:
self.toggle_buttons(True)
return
image_list = self.image_list_from_folder(self.settings["input_folder"])
print(image_list)
if not self.control_ending(image_list):
QMessageBox.warning(self, "Warning", f"Error: one or more filenames do not end on a number.\nCan not adjust time")
self.toggle_buttons(True)
return
self.insert_exif(image_list)
self.toggle_buttons(True)
QMessageBox.information(self, "Information", "Finished")
def get_checkbox_value(self, checkbox, default = None):
"""Helper function to get the value of a checkbox or a default value."""
return checkbox.isChecked() if checkbox else default
def get_spinbox_value(self, spinbox, default = None):
"""Helper function to get the value of a spinbox and handle empty input."""
return int(spinbox.text()) if spinbox.text() else default
def get_combobox_value(self, combobox, default = None):
"""Helper function to get the value of a combobox."""
return combobox.currentIndex() + 1 if combobox.currentIndex() != -1 else default
def get_text_value(self, lineedit, default = None):
"""Helper function to get the value of a text input field."""
return lineedit.text() if lineedit.text() else default
def get_date(self):
date_input = self.ui.dateEdit.date().toString("yyyy-MM-dd")
new_date = datetime.strptime(date_input, "%Y-%m-%d")
return new_date.strftime("%Y:%m:%d 00:00:00")
def collect_selected_exif(self):
user_data = {}
user_data["make"] = self.ui.make_comboBox.currentText()
user_data["model"] = self.ui.model_comboBox.currentText()
user_data["lens"] = self.ui.lens_comboBox.currentText()
user_data["iso"] = self.ui.iso_comboBox.currentText()
user_data["image_description"] = self.ui.image_description_comboBox.currentText()
user_data["artist"] = self.ui.artist_comboBox.currentText()
user_data["copyright_info"] = self.ui.copyright_info_comboBox.currentText()
user_data["software"] = f"{self.name} {self.version} with {self.o.name} {self.o.version}"
if int(self.ui.contrast_spinBox.text()) != 0 or int(self.ui.brightness_spinBox.text()) != 0:
user_data["user_comment"] = f"{self.ui.user_comment_comboBox.currentText()}, contrast: {self.ui.contrast_spinBox.text()}, brightness: {self.ui.brightness_spinBox.text()}"
else:
user_data["user_comment"] = self.ui.user_comment_comboBox.currentText()
return user_data
def get_selected_exif(self):
"""Collect selected EXIF data and handle date and GPS if necessary."""
selected_exif = self.collect_selected_exif() if self.ui.exif_checkbox.isChecked() else None
if selected_exif:
if self.ui.add_date_checkBox.isChecked():
selected_exif["date_time_original"] = self.get_date()
if self.ui.gps_checkBox.isChecked():
try:
self.settings["gps"] = [float(self.ui.lat_lineEdit.text()), float(self.ui.long_lineEdit.text())]
except ValueError as e:
self.settings["gps"] = "Wrong gps data"
else:
self.settings["gps"] = None
return selected_exif
def check_selected_exif(self, exif):
for key in exif:
if len(exif[key]) == 0:
return f"{key} is empty"
return True
def update_settings(self):
"""Update .settings from all GUI elements."""
# Basic
self.settings["input_folder"] = self.get_text_value(self.ui.input_path)
self.settings["output_folder"] = self.get_text_value(self.ui.output_path)
self.settings["file_format"] = self.ui.image_type.currentText()
# Quality
self.settings["jpg_quality"] = self.get_spinbox_value(self.ui.jpg_quality_spinBox)
self.settings["png_compression"] = self.get_spinbox_value(self.ui.png_quality_spinBox)
self.settings["resize"] = int(self.ui.resize_spinBox.text()) if self.ui.resize_spinBox.text() != "100" else None
self.settings["optimize"] = self.get_checkbox_value(self.ui.optimize_checkBox)
# Changes for image
self.settings["brightness"] = int(self.ui.brightness_spinBox.text()) if self.ui.brightness_spinBox.text() != "0" else None
self.settings["contrast"] = int(self.ui.contrast_spinBox.text()) if self.ui.contrast_spinBox.text() != "0" else None
self.settings["grayscale"] = self.get_checkbox_value(self.ui.grayscale_checkBox)
# Watermark
self.settings["font_size"] = self.get_combobox_value(self.ui.font_size_comboBox)
self.settings["watermark"] = self.get_text_value(self.ui.watermark_lineEdit)
# Naming
new_name = self.get_text_value(self.ui.filename, False) if self.ui.rename_checkbox.isChecked() else False
if isinstance(new_name, str): new_name = new_name.replace(" ", "_")
self.settings["new_file_names"] = new_name
self.settings["invert_image_order"] = self.get_checkbox_value(self.ui.revert_checkbox) if new_name is not False else None
# Handle EXIF data selection
self.settings["copy_exif"] = self.get_checkbox_value(self.ui.exif_copy_checkBox)
self.settings["own_exif"] = self.get_checkbox_value(self.ui.exif_checkbox)
self.settings["own_date"] = self.get_checkbox_value(self.ui.add_date_checkBox)
if self.settings["own_exif"]:
self.settings["user_selected_exif"] = self.get_selected_exif()
if self.settings["gps"] is not None:
if len(self.settings["gps"]) != 2:
return self.settings["gps"]
else:
self.settings["user_selected_exif"] = None
self.settings["gps"] = None
if self.settings["user_selected_exif"] is not None:
u = self.check_selected_exif(self.settings["user_selected_exif"])
if u != True:
return u
# Helper functions, low level
def handle_exif_file(self, do):
# TODO: add check if data is missing.
if do == "read":
file_dict = self.u.read_yaml(self.exif_file)
self.available_exif_data = self.sort_dict_of_lists(file_dict)
elif do == "write":
self.u.write_yaml(self.exif_file, self.available_exif_data)
def closeEvent(self, event):
QApplication.closeAllWindows()
event.accept()
class WorkerSignals(QObject):
# ChatGPT
progress = Signal(int)
finished = Signal()
class ImageProcessorRunnable(QRunnable):
# ChatGPT gave rough function layout
def __init__(self, image_files, settings, progress_callback):
super().__init__()
self.image_files = image_files
self.settings = settings
self.signals = WorkerSignals()
self.signals.progress.connect(progress_callback)
self.o = OptimaManager()
self.u = Utilities(os.path.expanduser(CONFIG_BASE_PATH))
def run(self):
input_folder = self.settings["input_folder"]
output_folder = self.settings["output_folder"]
for i, image_file in enumerate(self.image_files, start=1):
input_path = os.path.join(input_folder, image_file)
if self.settings["new_file_names"] != False:
image_name = self.u.append_number_to_name(self.settings["new_file_names"], i, len(self.image_files), self.settings["invert_image_order"])
else:
image_name = os.path.splitext(image_file)[0]
output_path = os.path.join(output_folder, image_name)
self.o.process_and_save_image(
image_input_file = input_path,
image_output_file = output_path,
file_type = self.settings["file_format"],
quality = self.settings["jpg_quality"],
compressing = self.settings["png_compression"],
optimize = self.settings["optimize"],
resize = self.settings["resize"],
watermark = self.settings["watermark"],
font_size = self.settings["font_size"],
grayscale = self.settings["grayscale"],
brightness = self.settings["brightness"],
contrast = self.settings["contrast"],
dict_for_exif = self.settings["user_selected_exif"],
gps = self.settings["gps"],
copy_exif = self.settings["copy_exif"]
)
self.signals.progress.emit(int((i / len(self.image_files)) * 100))
self.signals.finished.emit()

View file

@ -0,0 +1,166 @@
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) # brightness slider changes spinbox value, do not need an event for the slider
self.ui.contrast_spinBox.valueChanged.connect(self.on_ui_change) # contrast slider changes spinbox value, do not need an event for the slider
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 = f"PREVIEW B:{self.brightness} C:{self.contrast}",
font_size = 1,
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://code.boxyfoxy.net/CodeByMrFinchum/OptimaLab35/src/branch/main/CHANGELOG.md">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)

View file

@ -3,7 +3,7 @@
################################################################################
## Form generated from reading UI file 'main_window.ui'
##
## Created by: Qt User Interface Compiler version 6.8.1
## Created by: Qt User Interface Compiler version 6.8.2
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
@ -17,20 +17,20 @@ from PySide6.QtGui import (QAction, QBrush, QColor, QConicalGradient,
QPainter, QPalette, QPixmap, QRadialGradient,
QTransform)
from PySide6.QtWidgets import (QApplication, QCheckBox, QComboBox, QDateEdit,
QFrame, QGridLayout, QGroupBox, QHBoxLayout,
QLabel, QLineEdit, QMainWindow, QMenu,
QMenuBar, QProgressBar, QPushButton, QSizePolicy,
QSlider, QSpinBox, QStatusBar, QTabWidget,
QVBoxLayout, QWidget)
QGridLayout, QGroupBox, QHBoxLayout, QLabel,
QLineEdit, QMainWindow, QMenu, QMenuBar,
QProgressBar, QPushButton, QSizePolicy, QSlider,
QSpinBox, QStatusBar, QTabWidget, QVBoxLayout,
QWidget)
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
if not MainWindow.objectName():
MainWindow.setObjectName(u"MainWindow")
MainWindow.setWindowModality(Qt.NonModal)
MainWindow.resize(440, 756)
MainWindow.setWindowModality(Qt.WindowModality.NonModal)
MainWindow.resize(450, 720)
MainWindow.setMinimumSize(QSize(350, 677))
MainWindow.setMaximumSize(QSize(1000, 1000))
MainWindow.setMaximumSize(QSize(450, 1000))
self.actionInfo = QAction(MainWindow)
self.actionInfo.setObjectName(u"actionInfo")
self.actionPreview = QAction(MainWindow)
@ -45,12 +45,12 @@ class Ui_MainWindow(object):
self.gridLayout.setObjectName(u"gridLayout")
self.tabWidget = QTabWidget(self.centralwidget)
self.tabWidget.setObjectName(u"tabWidget")
self.tabWidget.setMaximumSize(QSize(500, 16777215))
self.tabWidget.setMaximumSize(QSize(420, 16777215))
self.tab_1 = QWidget()
self.tab_1.setObjectName(u"tab_1")
self.verticalLayout_10 = QVBoxLayout(self.tab_1)
self.verticalLayout_10.setObjectName(u"verticalLayout_10")
self.folder_group = QFrame(self.tab_1)
self.folder_group = QGroupBox(self.tab_1)
self.folder_group.setObjectName(u"folder_group")
self.folder_group.setMaximumSize(QSize(400, 16777215))
self.gridLayout_5 = QGridLayout(self.folder_group)
@ -115,7 +115,8 @@ class Ui_MainWindow(object):
self.png_quality_Slider.setMaximum(9)
self.png_quality_Slider.setPageStep(1)
self.png_quality_Slider.setSliderPosition(6)
self.png_quality_Slider.setOrientation(Qt.Horizontal)
self.png_quality_Slider.setOrientation(Qt.Orientation.Horizontal)
self.png_quality_Slider.setTickPosition(QSlider.TickPosition.TicksBothSides)
self.gridLayout_4.addWidget(self.png_quality_Slider, 4, 2, 1, 1)
@ -146,7 +147,8 @@ class Ui_MainWindow(object):
self.jpg_quality_Slider.setMinimum(1)
self.jpg_quality_Slider.setMaximum(100)
self.jpg_quality_Slider.setSliderPosition(90)
self.jpg_quality_Slider.setOrientation(Qt.Horizontal)
self.jpg_quality_Slider.setOrientation(Qt.Orientation.Horizontal)
self.jpg_quality_Slider.setTickPosition(QSlider.TickPosition.TicksBothSides)
self.gridLayout_4.addWidget(self.jpg_quality_Slider, 3, 2, 1, 1)
@ -160,7 +162,8 @@ class Ui_MainWindow(object):
self.resize_Slider.setMinimum(1)
self.resize_Slider.setMaximum(200)
self.resize_Slider.setValue(100)
self.resize_Slider.setOrientation(Qt.Horizontal)
self.resize_Slider.setOrientation(Qt.Orientation.Horizontal)
self.resize_Slider.setTickPosition(QSlider.TickPosition.TicksBothSides)
self.gridLayout_4.addWidget(self.resize_Slider, 5, 2, 1, 1)
@ -192,7 +195,8 @@ class Ui_MainWindow(object):
self.brightness_horizontalSlider.setObjectName(u"brightness_horizontalSlider")
self.brightness_horizontalSlider.setMinimum(-100)
self.brightness_horizontalSlider.setMaximum(100)
self.brightness_horizontalSlider.setOrientation(Qt.Horizontal)
self.brightness_horizontalSlider.setOrientation(Qt.Orientation.Horizontal)
self.brightness_horizontalSlider.setTickPosition(QSlider.TickPosition.TicksBothSides)
self.gridLayout_3.addWidget(self.brightness_horizontalSlider, 1, 1, 1, 1)
@ -223,7 +227,8 @@ class Ui_MainWindow(object):
self.contrast_horizontalSlider.setObjectName(u"contrast_horizontalSlider")
self.contrast_horizontalSlider.setMinimum(-100)
self.contrast_horizontalSlider.setMaximum(100)
self.contrast_horizontalSlider.setOrientation(Qt.Horizontal)
self.contrast_horizontalSlider.setOrientation(Qt.Orientation.Horizontal)
self.contrast_horizontalSlider.setTickPosition(QSlider.TickPosition.TicksBelow)
self.gridLayout_3.addWidget(self.contrast_horizontalSlider, 4, 1, 1, 1)
@ -243,6 +248,7 @@ class Ui_MainWindow(object):
self.groupBox_3 = QGroupBox(self.tab_1)
self.groupBox_3.setObjectName(u"groupBox_3")
self.groupBox_3.setEnabled(True)
self.groupBox_3.setMaximumSize(QSize(400, 16777215))
self.groupBox_3.setFlat(False)
self.groupBox_3.setCheckable(False)
self.groupBox_3.setChecked(False)
@ -537,7 +543,7 @@ class Ui_MainWindow(object):
self.dateEdit = QDateEdit(self.date_groupBox)
self.dateEdit.setObjectName(u"dateEdit")
self.dateEdit.setEnabled(False)
self.dateEdit.setDateTime(QDateTime(QDate(2025, 1, 1), QTime(0, 0, 0)))
self.dateEdit.setDateTime(QDateTime(QDate(2024, 12, 31), QTime(22, 0, 0)))
self.dateEdit.setMaximumDate(QDate(2038, 12, 31))
self.dateEdit.setMinimumDate(QDate(1970, 1, 1))
self.dateEdit.setCalendarPopup(True)
@ -557,7 +563,7 @@ class Ui_MainWindow(object):
MainWindow.setStatusBar(self.statusBar)
self.menuBar = QMenuBar(MainWindow)
self.menuBar.setObjectName(u"menuBar")
self.menuBar.setGeometry(QRect(0, 0, 440, 27))
self.menuBar.setGeometry(QRect(0, 0, 450, 19))
self.menuHelp = QMenu(self.menuBar)
self.menuHelp.setObjectName(u"menuHelp")
self.menuSettings = QMenu(self.menuBar)
@ -606,6 +612,7 @@ class Ui_MainWindow(object):
self.actionPreview.setText(QCoreApplication.translate("MainWindow", u"Preview image", None))
self.actionAbout.setText(QCoreApplication.translate("MainWindow", u"About", None))
self.actionSettings.setText(QCoreApplication.translate("MainWindow", u"Preferences...", None))
self.folder_group.setTitle(QCoreApplication.translate("MainWindow", u"File selection", None))
#if QT_CONFIG(tooltip)
self.input_folder_button.setToolTip(QCoreApplication.translate("MainWindow", u"Open a file browser to select a folder for loading images.", None))
#endif // QT_CONFIG(tooltip)

View file

@ -3,14 +3,14 @@
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="windowModality">
<enum>Qt::NonModal</enum>
<enum>Qt::WindowModality::NonModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>440</width>
<height>756</height>
<width>450</width>
<height>720</height>
</rect>
</property>
<property name="minimumSize">
@ -21,7 +21,7 @@
</property>
<property name="maximumSize">
<size>
<width>1000</width>
<width>450</width>
<height>1000</height>
</size>
</property>
@ -34,7 +34,7 @@
<widget class="QTabWidget" name="tabWidget">
<property name="maximumSize">
<size>
<width>500</width>
<width>420</width>
<height>16777215</height>
</size>
</property>
@ -47,13 +47,16 @@
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_10">
<item>
<widget class="QFrame" name="folder_group">
<widget class="QGroupBox" name="folder_group">
<property name="maximumSize">
<size>
<width>400</width>
<height>16777215</height>
</size>
</property>
<property name="title">
<string>File selection</string>
</property>
<layout class="QGridLayout" name="gridLayout_5">
<item row="0" column="1">
<widget class="QPushButton" name="input_folder_button">
@ -183,7 +186,10 @@
<number>6</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="tickPosition">
<enum>QSlider::TickPosition::TicksBothSides</enum>
</property>
</widget>
</item>
@ -244,7 +250,10 @@
<number>90</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="tickPosition">
<enum>QSlider::TickPosition::TicksBothSides</enum>
</property>
</widget>
</item>
@ -273,7 +282,10 @@
<number>100</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="tickPosition">
<enum>QSlider::TickPosition::TicksBothSides</enum>
</property>
</widget>
</item>
@ -333,7 +345,10 @@
<number>100</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="tickPosition">
<enum>QSlider::TickPosition::TicksBothSides</enum>
</property>
</widget>
</item>
@ -385,7 +400,10 @@
<number>100</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="tickPosition">
<enum>QSlider::TickPosition::TicksBelow</enum>
</property>
</widget>
</item>
@ -417,6 +435,12 @@
<property name="enabled">
<bool>true</bool>
</property>
<property name="maximumSize">
<size>
<width>400</width>
<height>16777215</height>
</size>
</property>
<property name="title">
<string>Watermarking</string>
</property>
@ -872,12 +896,12 @@
</property>
<property name="dateTime">
<datetime>
<hour>0</hour>
<hour>22</hour>
<minute>0</minute>
<second>0</second>
<year>2025</year>
<month>1</month>
<day>1</day>
<year>2024</year>
<month>12</month>
<day>31</day>
</datetime>
</property>
<property name="maximumDate">
@ -914,8 +938,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>440</width>
<height>27</height>
<width>450</width>
<height>19</height>
</rect>
</property>
<widget class="QMenu" name="menuHelp">

View file

@ -3,7 +3,7 @@
################################################################################
## Form generated from reading UI file 'preview_window.ui'
##
## Created by: Qt User Interface Compiler version 6.8.1
## Created by: Qt User Interface Compiler version 6.8.2
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
@ -15,16 +15,17 @@ from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
QFont, QFontDatabase, QGradient, QIcon,
QImage, QKeySequence, QLinearGradient, QPainter,
QPalette, QPixmap, QRadialGradient, QTransform)
from PySide6.QtWidgets import (QApplication, QCheckBox, QFrame, QHBoxLayout,
QLabel, QLineEdit, QMainWindow, QMenuBar,
QPushButton, QSizePolicy, QSlider, QSpinBox,
QVBoxLayout, QWidget)
from PySide6.QtWidgets import (QApplication, QCheckBox, QFrame, QGridLayout,
QGroupBox, QHBoxLayout, QLabel, QLineEdit,
QMainWindow, QMenuBar, QPushButton, QSizePolicy,
QSlider, QSpacerItem, QSpinBox, QVBoxLayout,
QWidget)
class Ui_Preview_Window(object):
def setupUi(self, Preview_Window):
if not Preview_Window.objectName():
Preview_Window.setObjectName(u"Preview_Window")
Preview_Window.resize(803, 775)
Preview_Window.resize(875, 775)
Preview_Window.setMinimumSize(QSize(800, 700))
self.centralwidget = QWidget(Preview_Window)
self.centralwidget.setObjectName(u"centralwidget")
@ -33,128 +34,175 @@ class Ui_Preview_Window(object):
self.QLabel = QLabel(self.centralwidget)
self.QLabel.setObjectName(u"QLabel")
self.QLabel.setMinimumSize(QSize(628, 628))
self.QLabel.setFrameShape(QFrame.Box)
self.QLabel.setFrameShape(QFrame.Shape.Box)
self.QLabel.setScaledContents(False)
self.horizontalLayout.addWidget(self.QLabel)
self.widget = QWidget(self.centralwidget)
self.widget.setObjectName(u"widget")
self.widget.setMinimumSize(QSize(150, 628))
self.widget.setMaximumSize(QSize(150, 16777215))
self.widget.setMinimumSize(QSize(160, 628))
self.widget.setMaximumSize(QSize(180, 16777215))
self.verticalLayout_4 = QVBoxLayout(self.widget)
self.verticalLayout_4.setObjectName(u"verticalLayout_4")
self.image_path_lineEdit = QLineEdit(self.widget)
self.groupBox_3 = QGroupBox(self.widget)
self.groupBox_3.setObjectName(u"groupBox_3")
self.verticalLayout_3 = QVBoxLayout(self.groupBox_3)
self.verticalLayout_3.setObjectName(u"verticalLayout_3")
self.image_path_lineEdit = QLineEdit(self.groupBox_3)
self.image_path_lineEdit.setObjectName(u"image_path_lineEdit")
self.image_path_lineEdit.setEnabled(False)
self.verticalLayout_4.addWidget(self.image_path_lineEdit)
self.verticalLayout_3.addWidget(self.image_path_lineEdit)
self.load_Button = QPushButton(self.widget)
self.load_Button = QPushButton(self.groupBox_3)
self.load_Button.setObjectName(u"load_Button")
self.verticalLayout_4.addWidget(self.load_Button)
self.verticalLayout_3.addWidget(self.load_Button)
self.widget_2 = QWidget(self.widget)
self.widget_2.setObjectName(u"widget_2")
self.widget_2.setMinimumSize(QSize(0, 250))
self.widget_2.setMaximumSize(QSize(16777215, 250))
self.verticalLayout = QVBoxLayout(self.widget_2)
self.verticalLayout_4.addWidget(self.groupBox_3)
self.groupBox_2 = QGroupBox(self.widget)
self.groupBox_2.setObjectName(u"groupBox_2")
self.verticalLayout = QVBoxLayout(self.groupBox_2)
self.verticalLayout.setObjectName(u"verticalLayout")
self.label = QLabel(self.widget_2)
self.label.setObjectName(u"label")
self.verticalLayout.addWidget(self.label)
self.brightness_spinBox = QSpinBox(self.widget_2)
self.brightness_spinBox = QSpinBox(self.groupBox_2)
self.brightness_spinBox.setObjectName(u"brightness_spinBox")
self.brightness_spinBox.setMinimum(-100)
self.brightness_spinBox.setMaximum(100)
self.verticalLayout.addWidget(self.brightness_spinBox)
self.brightness_Slider = QSlider(self.widget_2)
self.brightness_Slider = QSlider(self.groupBox_2)
self.brightness_Slider.setObjectName(u"brightness_Slider")
self.brightness_Slider.setMinimum(-100)
self.brightness_Slider.setMaximum(100)
self.brightness_Slider.setOrientation(Qt.Horizontal)
self.brightness_Slider.setOrientation(Qt.Orientation.Horizontal)
self.brightness_Slider.setTickPosition(QSlider.TickPosition.TicksBothSides)
self.verticalLayout.addWidget(self.brightness_Slider)
self.reset_brightness_Button = QPushButton(self.widget_2)
self.reset_brightness_Button = QPushButton(self.groupBox_2)
self.reset_brightness_Button.setObjectName(u"reset_brightness_Button")
self.verticalLayout.addWidget(self.reset_brightness_Button)
self.label_2 = QLabel(self.widget_2)
self.label_2.setObjectName(u"label_2")
self.verticalLayout.addWidget(self.label_2)
self.verticalLayout_4.addWidget(self.groupBox_2)
self.contrast_spinBox = QSpinBox(self.widget_2)
self.groupBox = QGroupBox(self.widget)
self.groupBox.setObjectName(u"groupBox")
self.verticalLayout_2 = QVBoxLayout(self.groupBox)
self.verticalLayout_2.setObjectName(u"verticalLayout_2")
self.contrast_spinBox = QSpinBox(self.groupBox)
self.contrast_spinBox.setObjectName(u"contrast_spinBox")
self.contrast_spinBox.setMinimum(-100)
self.contrast_spinBox.setMaximum(100)
self.verticalLayout.addWidget(self.contrast_spinBox)
self.verticalLayout_2.addWidget(self.contrast_spinBox)
self.contrast_Slider = QSlider(self.widget_2)
self.contrast_Slider = QSlider(self.groupBox)
self.contrast_Slider.setObjectName(u"contrast_Slider")
self.contrast_Slider.setMinimum(-100)
self.contrast_Slider.setMaximum(100)
self.contrast_Slider.setOrientation(Qt.Horizontal)
self.contrast_Slider.setOrientation(Qt.Orientation.Horizontal)
self.contrast_Slider.setTickPosition(QSlider.TickPosition.TicksBothSides)
self.verticalLayout.addWidget(self.contrast_Slider)
self.verticalLayout_2.addWidget(self.contrast_Slider)
self.reset_contrast_Button = QPushButton(self.widget_2)
self.reset_contrast_Button = QPushButton(self.groupBox)
self.reset_contrast_Button.setObjectName(u"reset_contrast_Button")
self.verticalLayout.addWidget(self.reset_contrast_Button)
self.verticalLayout_2.addWidget(self.reset_contrast_Button)
self.grayscale_checkBox = QCheckBox(self.widget_2)
self.verticalLayout_4.addWidget(self.groupBox)
self.groupBox_5 = QGroupBox(self.widget)
self.groupBox_5.setObjectName(u"groupBox_5")
self.gridLayout = QGridLayout(self.groupBox_5)
self.gridLayout.setObjectName(u"gridLayout")
self.grayscale_checkBox = QCheckBox(self.groupBox_5)
self.grayscale_checkBox.setObjectName(u"grayscale_checkBox")
self.verticalLayout.addWidget(self.grayscale_checkBox)
self.gridLayout.addWidget(self.grayscale_checkBox, 0, 0, 1, 1)
self.verticalLayout_4.addWidget(self.widget_2)
self.verticalLayout_4.addWidget(self.groupBox_5)
self.widget_5 = QWidget(self.widget)
self.widget_5.setObjectName(u"widget_5")
self.verticalLayout_2 = QVBoxLayout(self.widget_5)
self.verticalLayout_2.setObjectName(u"verticalLayout_2")
self.verticalSpacer = QSpacerItem(20, 219, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
self.verticalLayout_4.addWidget(self.widget_5)
self.verticalLayout_4.addItem(self.verticalSpacer)
self.widget_4 = QWidget(self.widget)
self.widget_4.setObjectName(u"widget_4")
self.verticalLayout_3 = QVBoxLayout(self.widget_4)
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.label = QLabel(self.widget)
self.label.setObjectName(u"label")
self.label.setLayoutDirection(Qt.LayoutDirection.LeftToRight)
self.label.setAutoFillBackground(False)
self.label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.label.setWordWrap(True)
self.verticalLayout_3.addWidget(self.live_update)
self.verticalLayout_4.addWidget(self.label)
self.update_Button = QPushButton(self.widget_4)
self.update_Button.setObjectName(u"update_Button")
self.update_Button.setAutoFillBackground(False)
self.show_OG_Button = QPushButton(self.widget)
self.show_OG_Button.setObjectName(u"show_OG_Button")
self.verticalLayout_3.addWidget(self.update_Button)
self.verticalLayout_4.addWidget(self.show_OG_Button)
self.checkBox = QCheckBox(self.widget_4)
self.groupBox_4 = QGroupBox(self.widget)
self.groupBox_4.setObjectName(u"groupBox_4")
self.groupBox_4.setMaximumSize(QSize(170, 16777215))
self.gridLayout_2 = QGridLayout(self.groupBox_4)
self.gridLayout_2.setObjectName(u"gridLayout_2")
self.checkBox = QCheckBox(self.groupBox_4)
self.checkBox.setObjectName(u"checkBox")
self.checkBox.setLayoutDirection(Qt.LeftToRight)
self.checkBox.setLayoutDirection(Qt.LayoutDirection.LeftToRight)
self.checkBox.setChecked(True)
self.verticalLayout_3.addWidget(self.checkBox)
self.gridLayout_2.addWidget(self.checkBox, 3, 0, 1, 2)
self.close_Button = QPushButton(self.widget_4)
self.close_Button = QPushButton(self.groupBox_4)
self.close_Button.setObjectName(u"close_Button")
self.verticalLayout_3.addWidget(self.close_Button)
self.gridLayout_2.addWidget(self.close_Button, 4, 0, 1, 2)
self.live_update = QCheckBox(self.groupBox_4)
self.live_update.setObjectName(u"live_update")
font = QFont()
font.setPointSize(11)
self.live_update.setFont(font)
self.live_update.setChecked(True)
self.gridLayout_2.addWidget(self.live_update, 0, 0, 1, 1)
self.scale_Slider = QSlider(self.groupBox_4)
self.scale_Slider.setObjectName(u"scale_Slider")
self.scale_Slider.setMinimum(10)
self.scale_Slider.setMaximum(100)
self.scale_Slider.setPageStep(10)
self.scale_Slider.setValue(50)
self.scale_Slider.setOrientation(Qt.Orientation.Horizontal)
self.scale_Slider.setTickPosition(QSlider.TickPosition.TicksBothSides)
self.gridLayout_2.addWidget(self.scale_Slider, 1, 0, 1, 2)
self.update_Button = QPushButton(self.groupBox_4)
self.update_Button.setObjectName(u"update_Button")
self.update_Button.setEnabled(False)
self.update_Button.setAutoFillBackground(False)
self.gridLayout_2.addWidget(self.update_Button, 2, 0, 1, 2)
self.scale_label = QLabel(self.groupBox_4)
self.scale_label.setObjectName(u"scale_label")
font1 = QFont()
font1.setPointSize(9)
self.scale_label.setFont(font1)
self.gridLayout_2.addWidget(self.scale_label, 0, 1, 1, 1)
self.verticalLayout_4.addWidget(self.widget_4)
self.verticalLayout_4.addWidget(self.groupBox_4)
self.horizontalLayout.addWidget(self.widget)
@ -162,7 +210,7 @@ class Ui_Preview_Window(object):
Preview_Window.setCentralWidget(self.centralwidget)
self.menubar = QMenuBar(Preview_Window)
self.menubar.setObjectName(u"menubar")
self.menubar.setGeometry(QRect(0, 0, 803, 27))
self.menubar.setGeometry(QRect(0, 0, 875, 19))
Preview_Window.setMenuBar(self.menubar)
self.retranslateUi(Preview_Window)
@ -170,6 +218,8 @@ class Ui_Preview_Window(object):
self.brightness_spinBox.valueChanged.connect(self.brightness_Slider.setValue)
self.contrast_Slider.valueChanged.connect(self.contrast_spinBox.setValue)
self.contrast_spinBox.valueChanged.connect(self.contrast_Slider.setValue)
self.live_update.toggled.connect(self.update_Button.setDisabled)
self.scale_Slider.valueChanged.connect(self.scale_label.setNum)
QMetaObject.connectSlotsByName(Preview_Window)
# setupUi
@ -177,33 +227,30 @@ class Ui_Preview_Window(object):
def retranslateUi(self, Preview_Window):
Preview_Window.setWindowTitle(QCoreApplication.translate("Preview_Window", u"OptimaLab35 - Preview", None))
self.QLabel.setText("")
self.groupBox_3.setTitle(QCoreApplication.translate("Preview_Window", u"File", None))
#if QT_CONFIG(tooltip)
self.image_path_lineEdit.setToolTip(QCoreApplication.translate("Preview_Window", u"Enter the path to the image file or use the 'Select Image' button to browse.", None))
#endif // QT_CONFIG(tooltip)
self.image_path_lineEdit.setPlaceholderText(QCoreApplication.translate("Preview_Window", u"Image Path", None))
self.load_Button.setText(QCoreApplication.translate("Preview_Window", u"Select image", None))
self.label.setText(QCoreApplication.translate("Preview_Window", u"Brightness", None))
self.groupBox_2.setTitle(QCoreApplication.translate("Preview_Window", u"Brightness", None))
#if QT_CONFIG(tooltip)
self.reset_brightness_Button.setToolTip(QCoreApplication.translate("Preview_Window", u"Click to reset the brightness to its default value (0).", None))
#endif // QT_CONFIG(tooltip)
self.reset_brightness_Button.setText(QCoreApplication.translate("Preview_Window", u"Reset", None))
self.label_2.setText(QCoreApplication.translate("Preview_Window", u"Contrast", None))
self.groupBox.setTitle(QCoreApplication.translate("Preview_Window", u"Contrast", None))
#if QT_CONFIG(tooltip)
self.reset_contrast_Button.setToolTip(QCoreApplication.translate("Preview_Window", u"Click to reset the contrast to its default value (0).", None))
#endif // QT_CONFIG(tooltip)
self.reset_contrast_Button.setText(QCoreApplication.translate("Preview_Window", u"Reset", None))
self.groupBox_5.setTitle(QCoreApplication.translate("Preview_Window", u"Grayscale", None))
#if QT_CONFIG(tooltip)
self.grayscale_checkBox.setToolTip(QCoreApplication.translate("Preview_Window", u"Convert the image to grayscale (black and white).", None))
#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"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)
self.update_Button.setToolTip(QCoreApplication.translate("Preview_Window", u"Apply Changes to Preview", None))
#endif // QT_CONFIG(tooltip)
self.update_Button.setText(QCoreApplication.translate("Preview_Window", u"Update Preview", None))
self.grayscale_checkBox.setText(QCoreApplication.translate("Preview_Window", u"Black and White", None))
self.label.setText(QCoreApplication.translate("Preview_Window", u"Hold button to show original image", None))
self.show_OG_Button.setText(QCoreApplication.translate("Preview_Window", u"Show", None))
self.groupBox_4.setTitle(QCoreApplication.translate("Preview_Window", u"Behavior", None))
#if QT_CONFIG(tooltip)
self.checkBox.setToolTip(QCoreApplication.translate("Preview_Window", u"Enable to copy adjustments to the main window upon closing", None))
#endif // QT_CONFIG(tooltip)
@ -212,5 +259,20 @@ class Ui_Preview_Window(object):
self.close_Button.setToolTip("")
#endif // QT_CONFIG(tooltip)
self.close_Button.setText(QCoreApplication.translate("Preview_Window", u"Close", None))
#if QT_CONFIG(tooltip)
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 refresh", None))
#if QT_CONFIG(tooltip)
self.scale_Slider.setToolTip(QCoreApplication.translate("Preview_Window", u"Sets the resize value for the preview image. A high value may cause the application to freeze.", None))
#endif // QT_CONFIG(tooltip)
#if QT_CONFIG(tooltip)
self.update_Button.setToolTip(QCoreApplication.translate("Preview_Window", u"Apply Changes to Preview", None))
#endif // QT_CONFIG(tooltip)
self.update_Button.setText(QCoreApplication.translate("Preview_Window", u"Refresh image", None))
#if QT_CONFIG(tooltip)
self.scale_label.setToolTip(QCoreApplication.translate("Preview_Window", u"Sets the resize value for the preview image. A high value may cause the application to freeze.", None))
#endif // QT_CONFIG(tooltip)
self.scale_label.setText(QCoreApplication.translate("Preview_Window", u"50", None))
# retranslateUi

View file

@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>803</width>
<width>875</width>
<height>775</height>
</rect>
</property>
@ -30,7 +30,7 @@
</size>
</property>
<property name="frameShape">
<enum>QFrame::Box</enum>
<enum>QFrame::Shape::Box</enum>
</property>
<property name="text">
<string/>
@ -44,56 +44,52 @@
<widget class="QWidget" name="widget" native="true">
<property name="minimumSize">
<size>
<width>150</width>
<width>160</width>
<height>628</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>150</width>
<width>180</width>
<height>16777215</height>
</size>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QLineEdit" name="image_path_lineEdit">
<property name="toolTip">
<string>Enter the path to the image file or use the 'Select Image' button to browse.</string>
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
<string>File</string>
</property>
<property name="placeholderText">
<string>Image Path</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="load_Button">
<property name="text">
<string>Select image</string>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="widget_2" native="true">
<property name="minimumSize">
<size>
<width>0</width>
<height>250</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>250</height>
</size>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Brightness</string>
<widget class="QLineEdit" name="image_path_lineEdit">
<property name="enabled">
<bool>false</bool>
</property>
<property name="toolTip">
<string>Enter the path to the image file or use the 'Select Image' button to browse.</string>
</property>
<property name="placeholderText">
<string>Image Path</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="load_Button">
<property name="text">
<string>Select image</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Brightness</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QSpinBox" name="brightness_spinBox">
<property name="minimum">
@ -113,7 +109,10 @@
<number>100</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="tickPosition">
<enum>QSlider::TickPosition::TicksBothSides</enum>
</property>
</widget>
</item>
@ -127,13 +126,15 @@
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Contrast</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Contrast</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QSpinBox" name="contrast_spinBox">
<property name="minimum">
@ -153,7 +154,10 @@
<number>100</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="tickPosition">
<enum>QSlider::TickPosition::TicksBothSides</enum>
</property>
</widget>
</item>
@ -167,13 +171,22 @@
</property>
</widget>
</item>
<item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_5">
<property name="title">
<string>Grayscale</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QCheckBox" name="grayscale_checkBox">
<property name="toolTip">
<string>Convert the image to grayscale (black and white).</string>
</property>
<property name="text">
<string>Black n White</string>
<string>Black and White</string>
</property>
</widget>
</item>
@ -181,46 +194,63 @@
</widget>
</item>
<item>
<widget class="QWidget" name="widget_5" native="true">
<layout class="QVBoxLayout" name="verticalLayout_2"/>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>219</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label">
<property name="layoutDirection">
<enum>Qt::LayoutDirection::LeftToRight</enum>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="text">
<string>Hold button to show original image</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="widget_4" native="true">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QCheckBox" name="live_update">
<property name="toolTip">
<string>Live update applies changes instantly. If the app becomes unresponsive or lags, disable this option.</string>
</property>
<property name="text">
<string>Live update</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="update_Button">
<property name="toolTip">
<string>Apply Changes to Preview</string>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="text">
<string>Update Preview</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="show_OG_Button">
<property name="text">
<string>Show</string>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_4">
<property name="maximumSize">
<size>
<width>170</width>
<height>16777215</height>
</size>
</property>
<property name="title">
<string>Behavior</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="3" column="0" colspan="2">
<widget class="QCheckBox" name="checkBox">
<property name="toolTip">
<string>Enable to copy adjustments to the main window upon closing</string>
</property>
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
<enum>Qt::LayoutDirection::LeftToRight</enum>
</property>
<property name="text">
<string>Copy Values</string>
@ -230,7 +260,7 @@
</property>
</widget>
</item>
<item>
<item row="4" column="0" colspan="2">
<widget class="QPushButton" name="close_Button">
<property name="toolTip">
<string/>
@ -240,6 +270,80 @@
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="live_update">
<property name="font">
<font>
<pointsize>11</pointsize>
</font>
</property>
<property name="toolTip">
<string>Live update applies changes instantly. If the app becomes unresponsive or lags, disable this option.</string>
</property>
<property name="text">
<string>Live refresh</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QSlider" name="scale_Slider">
<property name="toolTip">
<string>Sets the resize value for the preview image. A high value may cause the application to freeze.</string>
</property>
<property name="minimum">
<number>10</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="pageStep">
<number>10</number>
</property>
<property name="value">
<number>50</number>
</property>
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="tickPosition">
<enum>QSlider::TickPosition::TicksBothSides</enum>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QPushButton" name="update_Button">
<property name="enabled">
<bool>false</bool>
</property>
<property name="toolTip">
<string>Apply Changes to Preview</string>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="text">
<string>Refresh image</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="scale_label">
<property name="font">
<font>
<pointsize>9</pointsize>
</font>
</property>
<property name="toolTip">
<string>Sets the resize value for the preview image. A high value may cause the application to freeze.</string>
</property>
<property name="text">
<string>50</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
@ -253,8 +357,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>803</width>
<height>27</height>
<width>875</width>
<height>19</height>
</rect>
</property>
</widget>
@ -325,5 +429,37 @@
</hint>
</hints>
</connection>
<connection>
<sender>live_update</sender>
<signal>toggled(bool)</signal>
<receiver>update_Button</receiver>
<slot>setDisabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>785</x>
<y>624</y>
</hint>
<hint type="destinationlabel">
<x>785</x>
<y>659</y>
</hint>
</hints>
</connection>
<connection>
<sender>scale_Slider</sender>
<signal>valueChanged(int)</signal>
<receiver>scale_label</receiver>
<slot>setNum(int)</slot>
<hints>
<hint type="sourcelabel">
<x>783</x>
<y>644</y>
</hint>
<hint type="destinationlabel">
<x>831</x>
<y>619</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View file

@ -3,7 +3,7 @@
################################################################################
## Form generated from reading UI file 'settings_window.ui'
##
## Created by: Qt User Interface Compiler version 6.8.1
## Created by: Qt User Interface Compiler version 6.8.2
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
@ -16,181 +16,87 @@ from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
QImage, QKeySequence, QLinearGradient, QPainter,
QPalette, QPixmap, QRadialGradient, QTransform)
from PySide6.QtWidgets import (QApplication, QCheckBox, QComboBox, QGridLayout,
QHBoxLayout, QLabel, QMainWindow, QPushButton,
QSizePolicy, QSpacerItem, QTabWidget, QVBoxLayout,
QWidget)
QGroupBox, QHBoxLayout, QLabel, QMainWindow,
QPushButton, QSizePolicy, QSpacerItem, QTabWidget,
QVBoxLayout, QWidget)
class Ui_Settings_Window(object):
def setupUi(self, Settings_Window):
if not Settings_Window.objectName():
Settings_Window.setObjectName(u"Settings_Window")
Settings_Window.setEnabled(True)
Settings_Window.resize(400, 300)
Settings_Window.resize(400, 325)
Settings_Window.setMinimumSize(QSize(400, 300))
Settings_Window.setMaximumSize(QSize(500, 500))
Settings_Window.setMaximumSize(QSize(450, 350))
self.centralwidget = QWidget(Settings_Window)
self.centralwidget.setObjectName(u"centralwidget")
self.centralwidget.setMaximumSize(QSize(500, 500))
self.verticalLayout = QVBoxLayout(self.centralwidget)
self.verticalLayout.setObjectName(u"verticalLayout")
self.tabWidget = QTabWidget(self.centralwidget)
self.tabWidget.setObjectName(u"tabWidget")
self.tabWidgetPage1 = QWidget()
self.tabWidgetPage1.setObjectName(u"tabWidgetPage1")
self.verticalLayout_2 = QVBoxLayout(self.tabWidgetPage1)
self.verticalLayout_2.setObjectName(u"verticalLayout_2")
self.widget_3 = QWidget(self.tabWidgetPage1)
self.widget_3.setObjectName(u"widget_3")
self.gridLayout_3 = QGridLayout(self.widget_3)
self.gridLayout_3.setObjectName(u"gridLayout_3")
self.label_3 = QLabel(self.widget_3)
self.label_3.setObjectName(u"label_3")
self.gridLayout_3.addWidget(self.label_3, 0, 0, 1, 3)
self.theme_selection_comboBox = QComboBox(self.widget_3)
self.theme_selection_comboBox.addItem("")
self.theme_selection_comboBox.addItem("")
self.theme_selection_comboBox.setObjectName(u"theme_selection_comboBox")
self.theme_selection_comboBox.setEnabled(False)
self.theme_selection_comboBox.setMinimumSize(QSize(100, 0))
self.theme_selection_comboBox.setMaximumSize(QSize(100, 16777215))
self.gridLayout_3.addWidget(self.theme_selection_comboBox, 1, 2, 1, 1)
self.enable_theme_checkBox = QCheckBox(self.widget_3)
self.enable_theme_checkBox.setObjectName(u"enable_theme_checkBox")
self.enable_theme_checkBox.setChecked(False)
self.gridLayout_3.addWidget(self.enable_theme_checkBox, 1, 0, 1, 1)
self.horizontalSpacer_2 = QSpacerItem(98, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
self.gridLayout_3.addItem(self.horizontalSpacer_2, 1, 1, 1, 1)
self.install_pkg_Button = QPushButton(self.widget_3)
self.install_pkg_Button.setObjectName(u"install_pkg_Button")
self.gridLayout_3.addWidget(self.install_pkg_Button, 2, 0, 1, 3)
self.verticalLayout_2.addWidget(self.widget_3)
self.widget_4 = QWidget(self.tabWidgetPage1)
self.widget_4.setObjectName(u"widget_4")
self.horizontalLayout_3 = QHBoxLayout(self.widget_4)
self.horizontalLayout_3.setObjectName(u"horizontalLayout_3")
self.label_4 = QLabel(self.widget_4)
self.label_4.setObjectName(u"label_4")
self.label_4.setWordWrap(True)
self.horizontalLayout_3.addWidget(self.label_4)
self.reset_exif_Button = QPushButton(self.widget_4)
self.reset_exif_Button.setObjectName(u"reset_exif_Button")
self.reset_exif_Button.setMinimumSize(QSize(100, 0))
self.reset_exif_Button.setMaximumSize(QSize(100, 16777215))
self.horizontalLayout_3.addWidget(self.reset_exif_Button)
self.verticalLayout_2.addWidget(self.widget_4)
self.verticalSpacer = QSpacerItem(20, 237, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
self.verticalLayout_2.addItem(self.verticalSpacer)
self.widget_5 = QWidget(self.tabWidgetPage1)
self.widget_5.setObjectName(u"widget_5")
self.horizontalLayout_4 = QHBoxLayout(self.widget_5)
self.horizontalLayout_4.setObjectName(u"horizontalLayout_4")
self.save_and_close_Button = QPushButton(self.widget_5)
self.save_and_close_Button.setObjectName(u"save_and_close_Button")
self.horizontalLayout_4.addWidget(self.save_and_close_Button)
self.save_and_restart_Button = QPushButton(self.widget_5)
self.save_and_restart_Button.setObjectName(u"save_and_restart_Button")
self.horizontalLayout_4.addWidget(self.save_and_restart_Button)
self.verticalLayout_2.addWidget(self.widget_5)
self.tabWidget.addTab(self.tabWidgetPage1, "")
self.tabWidget.setMaximumSize(QSize(500, 500))
self.tabWidgetPage2 = QWidget()
self.tabWidgetPage2.setObjectName(u"tabWidgetPage2")
self.verticalLayout_3 = QVBoxLayout(self.tabWidgetPage2)
self.verticalLayout_3.setObjectName(u"verticalLayout_3")
self.widget_6 = QWidget(self.tabWidgetPage2)
self.widget_6.setObjectName(u"widget_6")
self.label_last_check = QLabel(self.widget_6)
self.label_last_check.setObjectName(u"label_last_check")
self.label_last_check.setGeometry(QRect(0, 10, 131, 22))
self.label_last_check_2 = QLabel(self.widget_6)
self.label_last_check_2.setObjectName(u"label_last_check_2")
self.label_last_check_2.setEnabled(True)
self.label_last_check_2.setGeometry(QRect(190, 10, 67, 22))
self.label_last_check_2.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter)
self.verticalLayout_3.addWidget(self.widget_6)
self.widget_2 = QWidget(self.tabWidgetPage2)
self.widget_2.setObjectName(u"widget_2")
self.gridLayout = QGridLayout(self.widget_2)
self.gridLayout_2 = QGridLayout(self.tabWidgetPage2)
self.gridLayout_2.setObjectName(u"gridLayout_2")
self.pkg_info_groupBox = QGroupBox(self.tabWidgetPage2)
self.pkg_info_groupBox.setObjectName(u"pkg_info_groupBox")
self.gridLayout = QGridLayout(self.pkg_info_groupBox)
self.gridLayout.setObjectName(u"gridLayout")
self.label_optima35_latestversion = QLabel(self.widget_2)
self.label_optima35_latestversion = QLabel(self.pkg_info_groupBox)
self.label_optima35_latestversion.setObjectName(u"label_optima35_latestversion")
self.label_optima35_latestversion.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter)
self.label_optima35_latestversion.setAlignment(Qt.AlignmentFlag.AlignRight|Qt.AlignmentFlag.AlignTrailing|Qt.AlignmentFlag.AlignVCenter)
self.gridLayout.addWidget(self.label_optima35_latestversion, 2, 2, 1, 1)
self.label_latest_version = QLabel(self.widget_2)
self.label_latest_version = QLabel(self.pkg_info_groupBox)
self.label_latest_version.setObjectName(u"label_latest_version")
self.label_latest_version.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter)
self.label_latest_version.setAlignment(Qt.AlignmentFlag.AlignRight|Qt.AlignmentFlag.AlignTrailing|Qt.AlignmentFlag.AlignVCenter)
self.gridLayout.addWidget(self.label_latest_version, 0, 2, 1, 1)
self.label = QLabel(self.widget_2)
self.label = QLabel(self.pkg_info_groupBox)
self.label.setObjectName(u"label")
self.gridLayout.addWidget(self.label, 1, 0, 1, 1)
self.label_optimalab35_latestversion = QLabel(self.widget_2)
self.label_optimalab35_latestversion = QLabel(self.pkg_info_groupBox)
self.label_optimalab35_latestversion.setObjectName(u"label_optimalab35_latestversion")
self.label_optimalab35_latestversion.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter)
self.label_optimalab35_latestversion.setAlignment(Qt.AlignmentFlag.AlignRight|Qt.AlignmentFlag.AlignTrailing|Qt.AlignmentFlag.AlignVCenter)
self.gridLayout.addWidget(self.label_optimalab35_latestversion, 1, 2, 1, 1)
self.label_9 = QLabel(self.widget_2)
self.label_9 = QLabel(self.pkg_info_groupBox)
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 = QLabel(self.pkg_info_groupBox)
self.label_optimalab35_localversion.setObjectName(u"label_optimalab35_localversion")
self.label_optimalab35_localversion.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter)
self.label_optimalab35_localversion.setAlignment(Qt.AlignmentFlag.AlignRight|Qt.AlignmentFlag.AlignTrailing|Qt.AlignmentFlag.AlignVCenter)
self.gridLayout.addWidget(self.label_optimalab35_localversion, 1, 1, 1, 1)
self.label_6 = QLabel(self.widget_2)
self.label_6 = QLabel(self.pkg_info_groupBox)
self.label_6.setObjectName(u"label_6")
self.label_6.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter)
self.label_6.setAlignment(Qt.AlignmentFlag.AlignRight|Qt.AlignmentFlag.AlignTrailing|Qt.AlignmentFlag.AlignVCenter)
self.gridLayout.addWidget(self.label_6, 0, 1, 1, 1)
self.label_2 = QLabel(self.widget_2)
self.label_2 = QLabel(self.pkg_info_groupBox)
self.label_2.setObjectName(u"label_2")
self.gridLayout.addWidget(self.label_2, 2, 0, 1, 1)
self.label_optima35_localversion = QLabel(self.widget_2)
self.label_optima35_localversion = QLabel(self.pkg_info_groupBox)
self.label_optima35_localversion.setObjectName(u"label_optima35_localversion")
self.label_optima35_localversion.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter)
self.label_optima35_localversion.setAlignment(Qt.AlignmentFlag.AlignRight|Qt.AlignmentFlag.AlignTrailing|Qt.AlignmentFlag.AlignVCenter)
self.gridLayout.addWidget(self.label_optima35_localversion, 2, 1, 1, 1)
self.verticalLayout_3.addWidget(self.widget_2)
self.gridLayout_2.addWidget(self.pkg_info_groupBox, 0, 0, 1, 2)
self.dev_widget = QWidget(self.tabWidgetPage2)
self.dev_widget.setObjectName(u"dev_widget")
@ -207,16 +113,22 @@ class Ui_Settings_Window(object):
self.horizontalLayout_2.addWidget(self.update_local_Button)
self.verticalLayout_3.addWidget(self.dev_widget)
self.gridLayout_2.addWidget(self.dev_widget, 1, 0, 1, 2)
self.verticalSpacer_2 = QSpacerItem(20, 9, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
self.verticalLayout_3.addItem(self.verticalSpacer_2)
self.gridLayout_2.addItem(self.verticalSpacer, 2, 0, 1, 1)
self.label_5 = QLabel(self.tabWidgetPage2)
self.label_5.setObjectName(u"label_5")
self.verticalLayout_3.addWidget(self.label_5)
self.gridLayout_2.addWidget(self.label_5, 3, 0, 1, 1)
self.label_last_check = QLabel(self.tabWidgetPage2)
self.label_last_check.setObjectName(u"label_last_check")
self.label_last_check.setAlignment(Qt.AlignmentFlag.AlignRight|Qt.AlignmentFlag.AlignTrailing|Qt.AlignmentFlag.AlignVCenter)
self.gridLayout_2.addWidget(self.label_last_check, 3, 1, 2, 1)
self.widget = QWidget(self.tabWidgetPage2)
self.widget.setObjectName(u"widget")
@ -239,9 +151,82 @@ class Ui_Settings_Window(object):
self.horizontalLayout.addWidget(self.restart_checkBox)
self.verticalLayout_3.addWidget(self.widget)
self.gridLayout_2.addWidget(self.widget, 5, 0, 1, 2)
self.tabWidget.addTab(self.tabWidgetPage2, "")
self.tabWidgetPage1 = QWidget()
self.tabWidgetPage1.setObjectName(u"tabWidgetPage1")
self.verticalLayout_2 = QVBoxLayout(self.tabWidgetPage1)
self.verticalLayout_2.setObjectName(u"verticalLayout_2")
self.groupBox_4 = QGroupBox(self.tabWidgetPage1)
self.groupBox_4.setObjectName(u"groupBox_4")
self.horizontalLayout_3 = QHBoxLayout(self.groupBox_4)
self.horizontalLayout_3.setObjectName(u"horizontalLayout_3")
self.label_4 = QLabel(self.groupBox_4)
self.label_4.setObjectName(u"label_4")
self.label_4.setWordWrap(True)
self.horizontalLayout_3.addWidget(self.label_4)
self.reset_exif_Button = QPushButton(self.groupBox_4)
self.reset_exif_Button.setObjectName(u"reset_exif_Button")
self.reset_exif_Button.setMinimumSize(QSize(100, 0))
self.reset_exif_Button.setMaximumSize(QSize(100, 16777215))
self.horizontalLayout_3.addWidget(self.reset_exif_Button)
self.verticalLayout_2.addWidget(self.groupBox_4)
self.groupBox_3 = QGroupBox(self.tabWidgetPage1)
self.groupBox_3.setObjectName(u"groupBox_3")
self.gridLayout_3 = QGridLayout(self.groupBox_3)
self.gridLayout_3.setObjectName(u"gridLayout_3")
self.label_3 = QLabel(self.groupBox_3)
self.label_3.setObjectName(u"label_3")
self.gridLayout_3.addWidget(self.label_3, 0, 0, 1, 3)
self.enable_theme_checkBox = QCheckBox(self.groupBox_3)
self.enable_theme_checkBox.setObjectName(u"enable_theme_checkBox")
self.enable_theme_checkBox.setChecked(False)
self.gridLayout_3.addWidget(self.enable_theme_checkBox, 1, 0, 1, 1)
self.theme_selection_comboBox = QComboBox(self.groupBox_3)
self.theme_selection_comboBox.addItem("")
self.theme_selection_comboBox.addItem("")
self.theme_selection_comboBox.addItem("")
self.theme_selection_comboBox.setObjectName(u"theme_selection_comboBox")
self.theme_selection_comboBox.setEnabled(False)
self.theme_selection_comboBox.setMinimumSize(QSize(100, 0))
self.theme_selection_comboBox.setMaximumSize(QSize(100, 16777215))
self.gridLayout_3.addWidget(self.theme_selection_comboBox, 1, 2, 1, 1)
self.save_and_close_Button = QPushButton(self.groupBox_3)
self.save_and_close_Button.setObjectName(u"save_and_close_Button")
self.gridLayout_3.addWidget(self.save_and_close_Button, 3, 0, 1, 1)
self.horizontalSpacer_2 = QSpacerItem(98, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
self.gridLayout_3.addItem(self.horizontalSpacer_2, 1, 1, 1, 1)
self.install_pkg_Button = QPushButton(self.groupBox_3)
self.install_pkg_Button.setObjectName(u"install_pkg_Button")
self.gridLayout_3.addWidget(self.install_pkg_Button, 2, 0, 1, 3)
self.save_and_restart_Button = QPushButton(self.groupBox_3)
self.save_and_restart_Button.setObjectName(u"save_and_restart_Button")
self.gridLayout_3.addWidget(self.save_and_restart_Button, 3, 1, 1, 2)
self.verticalLayout_2.addWidget(self.groupBox_3)
self.tabWidget.addTab(self.tabWidgetPage1, "")
self.verticalLayout.addWidget(self.tabWidget)
@ -258,25 +243,7 @@ class Ui_Settings_Window(object):
def retranslateUi(self, Settings_Window):
Settings_Window.setWindowTitle(QCoreApplication.translate("Settings_Window", u"Settings", None))
self.label_3.setText(QCoreApplication.translate("Settings_Window", u"Change theme from OS to PyQT Dark or Light", None))
self.theme_selection_comboBox.setItemText(0, QCoreApplication.translate("Settings_Window", u"Dark", None))
self.theme_selection_comboBox.setItemText(1, QCoreApplication.translate("Settings_Window", u"Light", None))
#if QT_CONFIG(tooltip)
self.theme_selection_comboBox.setToolTip(QCoreApplication.translate("Settings_Window", u"App needs to restart before changes take effect.", None))
#endif // QT_CONFIG(tooltip)
#if QT_CONFIG(tooltip)
self.enable_theme_checkBox.setToolTip(QCoreApplication.translate("Settings_Window", u"App needs to restart before changes take effect.", None))
#endif // QT_CONFIG(tooltip)
self.enable_theme_checkBox.setText(QCoreApplication.translate("Settings_Window", u"Custom theme", None))
self.install_pkg_Button.setText(QCoreApplication.translate("Settings_Window", u"Install package for custom theme", None))
self.label_4.setText(QCoreApplication.translate("Settings_Window", u"Reset selectable EXIF data to default", None))
self.reset_exif_Button.setText(QCoreApplication.translate("Settings_Window", u"Reset", None))
self.save_and_close_Button.setText(QCoreApplication.translate("Settings_Window", u"Save and close", None))
self.save_and_restart_Button.setText(QCoreApplication.translate("Settings_Window", u"Save and restart", None))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tabWidgetPage1), QCoreApplication.translate("Settings_Window", u"Preferences", None))
self.label_last_check.setText(QCoreApplication.translate("Settings_Window", u"Last update check:", None))
self.label_last_check_2.setText(QCoreApplication.translate("Settings_Window", u"TextLabel", None))
self.pkg_info_groupBox.setTitle(QCoreApplication.translate("Settings_Window", u"Package information", None))
self.label_optima35_latestversion.setText(QCoreApplication.translate("Settings_Window", u"unknown", None))
self.label_latest_version.setText(QCoreApplication.translate("Settings_Window", u"Latest version", None))
self.label.setText(QCoreApplication.translate("Settings_Window", u"OptimaLab35", None))
@ -295,6 +262,7 @@ class Ui_Settings_Window(object):
#endif // QT_CONFIG(tooltip)
self.update_local_Button.setText(QCoreApplication.translate("Settings_Window", u"Update local", None))
self.label_5.setText(QCoreApplication.translate("Settings_Window", u"TextLabel", None))
self.label_last_check.setText(QCoreApplication.translate("Settings_Window", u"TextLabel", None))
self.check_for_update_Button.setText(QCoreApplication.translate("Settings_Window", u"Check for update", None))
self.update_and_restart_Button.setText(QCoreApplication.translate("Settings_Window", u"Update", None))
#if QT_CONFIG(tooltip)
@ -302,5 +270,28 @@ class Ui_Settings_Window(object):
#endif // QT_CONFIG(tooltip)
self.restart_checkBox.setText(QCoreApplication.translate("Settings_Window", u"Restart", None))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tabWidgetPage2), QCoreApplication.translate("Settings_Window", u"Updater", None))
self.groupBox_4.setTitle(QCoreApplication.translate("Settings_Window", u"EXIF", None))
self.label_4.setText(QCoreApplication.translate("Settings_Window", u"Reset selectable EXIF data to default", None))
self.reset_exif_Button.setText(QCoreApplication.translate("Settings_Window", u"Reset", None))
self.groupBox_3.setTitle(QCoreApplication.translate("Settings_Window", u"Theme", None))
self.label_3.setText(QCoreApplication.translate("Settings_Window", u"Change theme from OS to PyQT Dark or Light", None))
#if QT_CONFIG(tooltip)
self.enable_theme_checkBox.setToolTip(QCoreApplication.translate("Settings_Window", u"Changes will take effect after restarting the application.", None))
#endif // QT_CONFIG(tooltip)
self.enable_theme_checkBox.setText(QCoreApplication.translate("Settings_Window", u"Custom theme", None))
self.theme_selection_comboBox.setItemText(0, QCoreApplication.translate("Settings_Window", u"Auto", None))
self.theme_selection_comboBox.setItemText(1, QCoreApplication.translate("Settings_Window", u"Dark", None))
self.theme_selection_comboBox.setItemText(2, QCoreApplication.translate("Settings_Window", u"Light", None))
#if QT_CONFIG(tooltip)
self.theme_selection_comboBox.setToolTip(QCoreApplication.translate("Settings_Window", u"Changes will take effect after restarting the application.", None))
#endif // QT_CONFIG(tooltip)
#if QT_CONFIG(tooltip)
self.save_and_close_Button.setToolTip(QCoreApplication.translate("Settings_Window", u"Changes will take effect after restarting the application.", None))
#endif // QT_CONFIG(tooltip)
self.save_and_close_Button.setText(QCoreApplication.translate("Settings_Window", u"Apply theme", None))
self.install_pkg_Button.setText(QCoreApplication.translate("Settings_Window", u"Install package for custom theme", None))
self.save_and_restart_Button.setText(QCoreApplication.translate("Settings_Window", u"Apply and restart", None))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tabWidgetPage1), QCoreApplication.translate("Settings_Window", u"Preferences", None))
# retranslateUi

View file

@ -10,7 +10,7 @@
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
<height>325</height>
</rect>
</property>
<property name="minimumSize">
@ -21,216 +21,42 @@
</property>
<property name="maximumSize">
<size>
<width>500</width>
<height>500</height>
<width>450</width>
<height>350</height>
</size>
</property>
<property name="windowTitle">
<string>Settings</string>
</property>
<widget class="QWidget" name="centralwidget">
<property name="maximumSize">
<size>
<width>500</width>
<height>500</height>
</size>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="maximumSize">
<size>
<width>500</width>
<height>500</height>
</size>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tabWidgetPage1" native="true">
<attribute name="title">
<string>Preferences</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QWidget" name="widget_3" native="true">
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0" colspan="3">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Change theme from OS to PyQT Dark or Light</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QComboBox" name="theme_selection_comboBox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>App needs to restart before changes take effect.</string>
</property>
<item>
<property name="text">
<string>Dark</string>
</property>
</item>
<item>
<property name="text">
<string>Light</string>
</property>
</item>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="enable_theme_checkBox">
<property name="toolTip">
<string>App needs to restart before changes take effect.</string>
</property>
<property name="text">
<string>Custom theme</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item row="1" column="1">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>98</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="0" colspan="3">
<widget class="QPushButton" name="install_pkg_Button">
<property name="text">
<string>Install package for custom theme</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="widget_4" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>Reset selectable EXIF data to default</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="reset_exif_Button">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Reset</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>237</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QWidget" name="widget_5" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QPushButton" name="save_and_close_Button">
<property name="text">
<string>Save and close</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="save_and_restart_Button">
<property name="text">
<string>Save and restart</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tabWidgetPage2" native="true">
<widget class="QWidget" name="tabWidgetPage2">
<attribute name="title">
<string>Updater</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QWidget" name="widget_6" native="true">
<widget class="QLabel" name="label_last_check">
<property name="geometry">
<rect>
<x>0</x>
<y>10</y>
<width>131</width>
<height>22</height>
</rect>
</property>
<property name="text">
<string>Last update check:</string>
</property>
</widget>
<widget class="QLabel" name="label_last_check_2">
<property name="enabled">
<bool>true</bool>
</property>
<property name="geometry">
<rect>
<x>190</x>
<y>10</y>
<width>67</width>
<height>22</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</widget>
</item>
<item>
<widget class="QWidget" name="widget_2" native="true">
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0" colspan="2">
<widget class="QGroupBox" name="pkg_info_groupBox">
<property name="title">
<string>Package information</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="2" column="2">
<widget class="QLabel" name="label_optima35_latestversion">
@ -238,7 +64,7 @@
<string>unknown</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
</property>
</widget>
</item>
@ -248,7 +74,7 @@
<string>Latest version</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
</property>
</widget>
</item>
@ -265,7 +91,7 @@
<string>unknown</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
</property>
</widget>
</item>
@ -282,7 +108,7 @@
<string>0.0.0</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
</property>
</widget>
</item>
@ -292,7 +118,7 @@
<string>Local Version</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
</property>
</widget>
</item>
@ -309,14 +135,14 @@
<string>0.0.0</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<item row="1" column="0" colspan="2">
<widget class="QWidget" name="dev_widget" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
@ -342,27 +168,37 @@
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<item row="2" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>9</height>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<item row="3" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item>
<item row="3" column="1" rowspan="2">
<widget class="QLabel" name="label_last_check">
<property name="text">
<string>TextLabel</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
</property>
</widget>
</item>
<item row="5" column="0" colspan="2">
<widget class="QWidget" name="widget" native="true">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
@ -397,6 +233,154 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="tabWidgetPage1">
<attribute name="title">
<string>Preferences</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QGroupBox" name="groupBox_4">
<property name="title">
<string>EXIF</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>Reset selectable EXIF data to default</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="reset_exif_Button">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Reset</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
<string>Theme</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0" colspan="3">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Change theme from OS to PyQT Dark or Light</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="enable_theme_checkBox">
<property name="toolTip">
<string>Changes will take effect after restarting the application.</string>
</property>
<property name="text">
<string>Custom theme</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QComboBox" name="theme_selection_comboBox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Changes will take effect after restarting the application.</string>
</property>
<item>
<property name="text">
<string>Auto</string>
</property>
</item>
<item>
<property name="text">
<string>Dark</string>
</property>
</item>
<item>
<property name="text">
<string>Light</string>
</property>
</item>
</widget>
</item>
<item row="3" column="0">
<widget class="QPushButton" name="save_and_close_Button">
<property name="toolTip">
<string>Changes will take effect after restarting the application.</string>
</property>
<property name="text">
<string>Apply theme</string>
</property>
</widget>
</item>
<item row="1" column="1">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>98</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="0" colspan="3">
<widget class="QPushButton" name="install_pkg_Button">
<property name="text">
<string>Install package for custom theme</string>
</property>
</widget>
</item>
<item row="3" column="1" colspan="2">
<widget class="QPushButton" name="save_and_restart_Button">
<property name="text">
<string>Apply and restart</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
@ -411,12 +395,12 @@
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>112</x>
<y>103</y>
<x>96</x>
<y>225</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>103</y>
<x>317</x>
<y>225</y>
</hint>
</hints>
</connection>

View file

@ -3,7 +3,7 @@ import os
class Utilities:
def __init__(self, app_folder_path):
self.folder_path = app_folder_path
self.folder_path = os.path.expanduser(app_folder_path)
self._ensure_program_folder_exists()
self.exif_path = os.path.expanduser(f"{app_folder_path}/exif.yaml")
self.settings_path = os.path.expanduser(f"{app_folder_path}/settings.yaml")
@ -30,7 +30,7 @@ class Utilities:
def _prepear_exif_config(self):
"""Prepear folder for config and generate default exif if non aviable"""
if not os.path.isfile(self.exif_path):
self.default_exif(self.exif_path)
self.default_exif()
def _ensure_program_folder_exists(self):
if not os.path.exists(self.folder_path):
@ -99,7 +99,7 @@ class Utilities:
"theme": {
"theme_pkg": False,
"use_custom_theme": False,
"mode": "Dark"
"mode": "Auto"
}
}
self.write_yaml(self.settings_path, settings)

View file

@ -8,15 +8,15 @@ dynamic = ["version"]
authors = [{ name = "Mr Finchum" }]
description = "User interface for optima35."
readme = "../pip_README.md"
requires-python = ">=3.8"
requires-python = ">=3.8, <4.0"
dependencies = [
"optima35>=1.0.0, <2.0.0",
"PyPiUpdater>=0.7.0, <1.0.0",
"PyPiUpdater>=0.7.2, <1.0.0",
"pyside6",
"PyYAML",
]
classifiers = [
"Development Status :: 4 - Beta",
"Development Status :: 5 - Production/Stable",
"Environment :: X11 Applications :: Qt",
"Topic :: Multimedia :: Graphics :: Editors",
"Programming Language :: Python :: 3",
@ -26,6 +26,8 @@ classifiers = [
[project.urls]
Source = "https://gitlab.com/CodeByMrFinchum/OptimaLab35"
Documentation = "https://gitlab.com/CodeByMrFinchum/OptimaLab35/-/blob/main/README.md"
Changelog = "https://gitlab.com/CodeByMrFinchum/OptimaLab35/-/blob/main/CHANGELOG.md"
[project.scripts]
OptimaLab35 = "OptimaLab35.__main__:main"