Compare commits
54 commits
Author | SHA1 | Date | |
---|---|---|---|
894d444f91 | |||
a89f43ba2e | |||
d93d3de859 | |||
011249e002 | |||
d4e8a25b1e | |||
419dc1eada | |||
63422ccd88 | |||
d356937e41 | |||
ef92ffb5b9 | |||
7adea3084a | |||
35a9f1af55 | |||
17f08bc74f | |||
ce2dd90a39 | |||
3483d67e93 | |||
c0de1a550b | |||
c08bb59dae | |||
2c633d6dc4 | |||
9000702636 | |||
![]() |
fcd7aa97b6 | ||
dc132bffc9 | |||
![]() |
0d0feff502 | ||
938209fdfd | |||
1e91ee8cf6 | |||
963f27109f | |||
0abc4ba96f | |||
7eb8f162bb | |||
f3ba0909f2 | |||
5b2399b99e | |||
b6ec22d61d | |||
0d416aaf8d | |||
cb016ae98f | |||
8bb655eb40 | |||
89c40a8ca1 | |||
630985d70c | |||
b1d9d3fa5e | |||
92a9dccc00 | |||
1bba7f8bbc | |||
0c450328b3 | |||
21f1d9fc01 | |||
2476dd8b1f | |||
fba31cf3e6 | |||
89c3fb3e68 | |||
51108ef86e | |||
d608156206 | |||
09025105ea | |||
2a5efcd88c | |||
3d0830aec5 | |||
bcdad62e8a | |||
a46390ba82 | |||
5752b547fd | |||
db698a3daa | |||
bebdf15ba9 | |||
529bb26d0b | |||
15e04380f4 |
3
.gitignore
vendored
|
@ -2,3 +2,6 @@ test/
|
|||
dist/
|
||||
.ropeproject/
|
||||
__pycache__/
|
||||
.flatpak-builder/
|
||||
flatpak-build-dir/
|
||||
*.jpg
|
||||
|
|
92
.woodpecker/woodpecker_ci.yml
Normal 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/*
|
257
CHANGELOG.md
|
@ -1,4 +1,257 @@
|
|||
# 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.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.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:**
|
||||
1. Option to change the theme (with an optional dependency installation).
|
||||
2. Reset selectable EXIF data to default.
|
||||
- The updater UI has been moved to the second tab.
|
||||
- Added a link to the changelog for easier access to update details.
|
||||
|
||||
- **Patches:**
|
||||
- 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 (25.02.05)
|
||||
### 0.11.1: Fixed pipeline
|
||||
- Fixed pipeline publish error
|
||||
|
||||
### 0.11.0: Refactor and Patches
|
||||
- Fixed an issue with the updater: The updater window wouldn't start if the `updater_log.json` file was missing or lacked a valid last `time.time()` float value.
|
||||
- Corrected layout issues in the preview window, repositioning elements to their proper places.
|
||||
- Added an application icon (may not work on all desktop environments).
|
||||
- Refactored code to reduce the size of the PyPi package by removing unnecessary folders.
|
||||
|
||||
---
|
||||
|
||||
## 0.10.x (25.02.04)
|
||||
### 0.10.1: Fixed Updater
|
||||
- Fixed an issue where the updater was permanently disabled.
|
||||
|
||||
### 0.10.0: Multithreading for Preview Window
|
||||
- The preview window now processes images in a separate thread, and live update preview is enabled by default.
|
||||
- This improves UI responsiveness.
|
||||
- The image now resizes dynamically to fit the window when the window size changes.
|
||||
- Minor UI improvements.
|
||||
|
||||
---
|
||||
|
||||
## 0.9.x
|
||||
### 0.9.2: Enhanced updater
|
||||
- Minor enhancments for the updater
|
||||
|
||||
### 0.9.1: Patch for Unsuccessful Successful Update
|
||||
- Addressed a rare issue where the package did not update correctly using the updater.
|
||||
- Unable to reproduce, but it may have been related to an older version and the restart process.
|
||||
- Added developer functions to test the updater without requiring a published release.
|
||||
|
||||
### 0.9.0: UI Enhancements and Language Refinements
|
||||
- Changed text, labels, buttons, and checkboxes for clearer understanding.
|
||||
- Improved UI language to make the interface easier to navigate.
|
||||
- Added tooltips for more helpful information and guidance.
|
||||
- Updates applied across the main window (both tabs) and the preview window.
|
||||
|
||||
---
|
||||
|
||||
## 0.8.x
|
||||
### 0.8.5: Patch for New PyPiUpdater Version
|
||||
- **PyPiUpdater 0.5** introduced breaking changes; adjusted code to ensure compatibility with the new version.
|
||||
|
||||
### 0.8.4: Minor Enhancements & Cleanup
|
||||
- Updated window titles.
|
||||
- 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
|
||||
- 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
|
||||
- Updated to support **PyPiUpdater 0.4.0**.
|
||||
- Now stores version information locally, preventing an "unknown" state on the first updater launch.
|
||||
- Users still need to press the **Update** button to verify the latest version, ensuring an internet connection is available.
|
||||
|
||||
### 0.8.1: Fix
|
||||
- Fixed a misspelling of `PyPiUpdater` in the build file, which prevented v0.8.0 from being installed.
|
||||
|
||||
### 0.8.0: Updater Feature
|
||||
- Added an updater function utilizing my new package [PyPiUpdater](https://gitlab.com/CodeByMrFinchum/PyPiUpdater).
|
||||
- New updater window displaying the local version and checking for updates online.
|
||||
- Added an option to update and restart the app from the menu.
|
||||
|
||||
---
|
||||
|
||||
## 0.7.0: Enhanced Preview
|
||||
- Images loaded into the preview window are now scaled while maintaining aspect ratio.
|
||||
- Added live updates: changes to brightness, contrast, or grayscale are applied immediately.
|
||||
- This may crush the system depending on image size and system specifications.
|
||||
- Removed Settings from menuBar, and extended the about window.
|
||||
|
||||
---
|
||||
|
||||
## 0.6.0: Initial Flatpak Support
|
||||
- Started Flatpak package building.
|
||||
- Not added to Flathub yet, as only stable software is hosted there.
|
||||
- Not fully completed, icon, name, and description are included, but the version is missing for some reason.
|
||||
- Local build and installation work. The Bash script `build_flatpak.sh` in the `flatpak/` directory generates all pip dependencies, then builds and installs the app locally.
|
||||
- `requirements-parser` has to be installed from pip to finish installing the flatpak (maybe more pypi packages..)
|
||||
|
||||
---
|
||||
|
||||
## 0.5.0
|
||||
- Removed all leftover of tui code that was hiding in some classes.
|
||||
|
||||
---
|
||||
|
||||
## 0.4.0
|
||||
- Fixed a critical issue that prevented the program from functioning.
|
||||
- Updated compatibility to align with the **upcoming** optima35 **release**.
|
||||
|
||||
**Removal of TUI:**
|
||||
- The TUI version is no longer compatible with optima35 v1.0.
|
||||
- Maintaining two UIs has become too time-consuming, as the primary focus is on the GUI, which provides the best user experience. Recently, the TUI version was only receiving patches without any meaningful enhancements.
|
||||
|
||||
---
|
||||
|
||||
## 0.3.x
|
||||
### 0.3.7: prepear for optima35 release
|
||||
- Added a maximum version of dependencies list.
|
||||
|
||||
### 0.3.6: Patch
|
||||
- Added check if any exif options are empty.
|
||||
- Also made the exif editor aviable without checking the exif box.
|
||||
|
||||
### 0.3.5: Fix
|
||||
- Fixed an issue where renaming images, while converting could result in wrong numbering.
|
||||
|
||||
### 0.3.1 - 0.3.4
|
||||
- Repo only: Fix building pipeline
|
||||
|
||||
### 0.3.0
|
||||
- Repo only: adding pipeline
|
||||
|
||||
---
|
||||
|
||||
## 0.2.x
|
||||
### 0.2.3
|
||||
|
@ -20,6 +273,8 @@
|
|||
- Added a new experimental preview window to display an image and show how changing values affects it.
|
||||
- Programm now warns for potential overwrite of existing files.
|
||||
|
||||
---
|
||||
|
||||
## 0.1.x
|
||||
### 0.1.1
|
||||
- Update metadata, preview, readme, bump in version for pip
|
||||
|
@ -27,6 +282,8 @@
|
|||
### 0.1.0
|
||||
- Preserved the current working GUI by pinning `optima35` to a specific version for guaranteed compatibility.
|
||||
|
||||
---
|
||||
|
||||
## 0.0.x
|
||||
### 0.0.4-a2
|
||||
- Adding __version__ to `__init__.py` so version is automaticly updated in program as well as pypi.
|
||||
|
|
5
GitVersion.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
mode: MainLine
|
||||
major-version-bump-message: "^(build|chore|ci|docs|feat|fix|patch|refactor|revert|style|test)(\\([\\w\\s-,/\\\\]*\\))?(!:|:.*\\n\\n((.+\\n)+\\n)?BREAKING CHANGE:\\s.+)" # noqa yaml[line-length]
|
||||
minor-version-bump-message: "^(build|chore|ci|docs|feat|refactor|revert|style|test)(\\([\\w\\s-,/\\\\]*\\))?:"
|
||||
patch-version-bump-message: "^(fix|patch)(\\([\\w\\s-,/\\\\]*\\))?:"
|
135
README.md
|
@ -1,88 +1,93 @@
|
|||
# **OptimaLab35**
|
||||
[OptimaLab35](https://gitlab.com/CodeByMrFinchum/OptimaLab35) is a graphical and terminal user interface for [optima35](https://gitlab.com/CodeByMrFinchum/optima35). It is under **heavy development**, and both UI elements and cross-platform compatibility may change.
|
||||
Developed on my [forgejo instance](https://code.boxyfoxy.net/CodeByMrFinchum), [GitLab](https://gitlab.com/CodeByMrFinchum) is used as backup.
|
||||
|
||||
## **Overview**
|
||||
|
||||
**OptimaLab35** extends **OPTIMA35** (**Organizing, Processing, Tweaking Images, and Modifying scanned Analogs from 35mm Film**) by providing an intuitive interface for image and metadata management. While tailored for analog photography, it supports any type of image.
|
||||
**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://code.boxyfoxy.net/CodeByMrFinchum/optima35), providing an intuitive way to interact with the core functionalities.
|
||||
|
||||
---
|
||||
|
||||
## **Current Status**
|
||||
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.
|
||||
|
||||
### **Versioning and Compatibility**
|
||||
|
||||
The preserved version **v0.1.0** ensures stability with the current GUI design. It depends on **optima35==0.6.4**, a version confirmed to work seamlessly with this release. Future updates may introduce breaking changes, especially as the project evolves across platforms.
|
||||
|
||||
### **Installation**
|
||||
|
||||
Install via pip (dependencies are automatically managed, except for `simple-term-menu` used in TUI mode, which is Linux-only):
|
||||
```bash
|
||||
pip install OptimaLab35
|
||||
```
|
||||
|
||||
## **Development and Notes**
|
||||
|
||||
**Alpha Stage**
|
||||
- UI designs (GUI and TUI) are evolving, and breaking changes may occur.
|
||||
- The [**CHANGELOG**](https://gitlab.com/CodeByMrFinchum/OptimaLab35/-/blob/main/CHANGELOG.md) provides detailed updates.
|
||||
- Some safety checks are still under development.
|
||||
|
||||
**Modes:**
|
||||
- **GUI**: Default if **PySide6** is available.
|
||||
- **TUI**: Fallback if **PySide6** is missing or can be explicitly started using the `--tui` flag.
|
||||
|
||||
### Preview GUI
|
||||
**PREVIEW** might be out of date.
|
||||
|
||||
**Main tab**
|
||||
|
||||
{width=40%}
|
||||
|
||||
**Preview window**
|
||||
|
||||
{width=40%}
|
||||
|
||||
**Exif tab**
|
||||
|
||||
{width=40%}
|
||||
|
||||
**Exif editor**
|
||||
|
||||
{width=40%}
|
||||
|
||||
**Info window**
|
||||
|
||||
{width=40%}
|
||||
---
|
||||
|
||||
## **Features**
|
||||
|
||||
### **Image Processing**
|
||||
- Resizing
|
||||
- Renaming with custom order
|
||||
- Grayscale conversion
|
||||
- Brightness and contrast adjustment
|
||||
- Resize images (upscale or downscale)
|
||||
- Convert images to grayscale
|
||||
- Adjust brightness and contrast
|
||||
- Add customizable text-based watermarks
|
||||
|
||||
### **Image Preview**
|
||||
- Load a single image and see how changes in brightness and contrast affect the image
|
||||
|
||||
### **EXIF Management**
|
||||
- Copy or add custom EXIF data
|
||||
- Add GPS coordinates
|
||||
- Add or modify EXIF dates
|
||||
- Remove EXIF metadata
|
||||
- Add EXIF data using a simple dictionary
|
||||
- Copy EXIF data from the original image
|
||||
- Remove EXIF metadata completely
|
||||
- Add timestamps (e.g., original photo timestamp)
|
||||
- Automatically adjust EXIF timestamps based on image file names
|
||||
- Add GPS coordinates to images
|
||||
|
||||
### **Watermarking**
|
||||
- Add customizable watermarks
|
||||
### **Settings**
|
||||
- Option to use `PyQtDarkTheme` and select Dark, Light, or auto theme
|
||||
- Checks for updates on PyPI, automatically downloads and installs the latest version
|
||||
|
||||
## **Dependencies**
|
||||
---
|
||||
|
||||
**GUI Mode:**
|
||||
- `optima35`
|
||||
- `pyside6`
|
||||
## **Installation**
|
||||
|
||||
**TUI Mode (Linux only):**
|
||||
- `simple-term-menu`
|
||||
Install via **pip** (dependencies are handled automatically):
|
||||
```bash
|
||||
pip install OptimaLab35
|
||||
```
|
||||
|
||||
# Use of LLMs
|
||||
---
|
||||
|
||||
## 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**
|
||||
|
||||
{width=40%}
|
||||
{width=40%}
|
||||
|
||||
**Exif tab**
|
||||
|
||||
{width=40%}
|
||||
{width=40%}
|
||||
|
||||
**Preview window**
|
||||
|
||||
{width=40%}
|
||||
{width=40%}
|
||||
|
||||
**Settings**
|
||||
|
||||
{width=40%}
|
||||
{width=40%}
|
||||
|
||||
**Updater**
|
||||
|
||||
{width=40%}
|
||||
{width=40%}
|
||||
|
||||
---
|
||||
|
||||
# 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 OpenAI’s 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
|
||||
|
@ -94,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)
|
||||
|
|
BIN
flatpak/app-icon.xcf
Normal file
5
flatpak/build_flatpak.sh
Executable file
|
@ -0,0 +1,5 @@
|
|||
#!/bin/bash
|
||||
# runtime, skd, and base has to be installed, see net.boxyfoxy.net.OptimaLab35.json
|
||||
# uses [flatpak-pip-generator](https://github.com/flatpak/flatpak-builder-tools/tree/master/pip) to download and build all dependency from pip
|
||||
python flatpak-pip-generator --runtime='org.kde.Sdk//6.8' piexif pillow optima35 PyYAML hatchling
|
||||
flatpak-builder --user --install flatpak-build-dir net.boxyfoxy.OptimaLab35.json --force-clean
|
3
flatpak/flathub.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"only-arches": ["x86_64"]
|
||||
}
|
533
flatpak/flatpak-pip-generator
Executable file
|
@ -0,0 +1,533 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
__license__ = 'MIT'
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import hashlib
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import urllib.request
|
||||
|
||||
from collections import OrderedDict
|
||||
from typing import Dict
|
||||
|
||||
try:
|
||||
import requirements
|
||||
except ImportError:
|
||||
exit('Requirements modules is not installed. Run "pip install requirements-parser"')
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('packages', nargs='*')
|
||||
parser.add_argument('--python2', action='store_true',
|
||||
help='Look for a Python 2 package')
|
||||
parser.add_argument('--cleanup', choices=['scripts', 'all'],
|
||||
help='Select what to clean up after build')
|
||||
parser.add_argument('--requirements-file', '-r',
|
||||
help='Specify requirements.txt file')
|
||||
parser.add_argument('--build-only', action='store_const',
|
||||
dest='cleanup', const='all',
|
||||
help='Clean up all files after build')
|
||||
parser.add_argument('--build-isolation', action='store_true',
|
||||
default=False,
|
||||
help=(
|
||||
'Do not disable build isolation. '
|
||||
'Mostly useful on pip that does\'t '
|
||||
'support the feature.'
|
||||
))
|
||||
parser.add_argument('--ignore-installed',
|
||||
type=lambda s: s.split(','),
|
||||
default='',
|
||||
help='Comma-separated list of package names for which pip '
|
||||
'should ignore already installed packages. Useful when '
|
||||
'the package is installed in the SDK but not in the '
|
||||
'runtime.')
|
||||
parser.add_argument('--checker-data', action='store_true',
|
||||
help='Include x-checker-data in output for the "Flatpak External Data Checker"')
|
||||
parser.add_argument('--output', '-o',
|
||||
help='Specify output file name')
|
||||
parser.add_argument('--runtime',
|
||||
help='Specify a flatpak to run pip inside of a sandbox, ensures python version compatibility')
|
||||
parser.add_argument('--yaml', action='store_true',
|
||||
help='Use YAML as output format instead of JSON')
|
||||
parser.add_argument('--ignore-errors', action='store_true',
|
||||
help='Ignore errors when downloading packages')
|
||||
parser.add_argument('--ignore-pkg', nargs='*',
|
||||
help='Ignore a package when generating the manifest. Can only be used with a requirements file')
|
||||
opts = parser.parse_args()
|
||||
|
||||
if opts.yaml:
|
||||
try:
|
||||
import yaml
|
||||
except ImportError:
|
||||
exit('PyYAML modules is not installed. Run "pip install PyYAML"')
|
||||
|
||||
|
||||
def get_pypi_url(name: str, filename: str) -> str:
|
||||
url = 'https://pypi.org/pypi/{}/json'.format(name)
|
||||
print('Extracting download url for', name)
|
||||
with urllib.request.urlopen(url) as response:
|
||||
body = json.loads(response.read().decode('utf-8'))
|
||||
for release in body['releases'].values():
|
||||
for source in release:
|
||||
if source['filename'] == filename:
|
||||
return source['url']
|
||||
raise Exception('Failed to extract url from {}'.format(url))
|
||||
|
||||
|
||||
def get_tar_package_url_pypi(name: str, version: str) -> str:
|
||||
url = 'https://pypi.org/pypi/{}/{}/json'.format(name, version)
|
||||
with urllib.request.urlopen(url) as response:
|
||||
body = json.loads(response.read().decode('utf-8'))
|
||||
for ext in ['bz2', 'gz', 'xz', 'zip', 'none-any.whl']:
|
||||
for source in body['urls']:
|
||||
if source['url'].endswith(ext):
|
||||
return source['url']
|
||||
err = 'Failed to get {}-{} source from {}'.format(name, version, url)
|
||||
raise Exception(err)
|
||||
|
||||
|
||||
def get_package_name(filename: str) -> str:
|
||||
if filename.endswith(('bz2', 'gz', 'xz', 'zip')):
|
||||
segments = filename.split('-')
|
||||
if len(segments) == 2:
|
||||
return segments[0]
|
||||
return '-'.join(segments[:len(segments) - 1])
|
||||
elif filename.endswith('whl'):
|
||||
segments = filename.split('-')
|
||||
if len(segments) == 5:
|
||||
return segments[0]
|
||||
candidate = segments[:len(segments) - 4]
|
||||
# Some packages list the version number twice
|
||||
# e.g. PyQt5-5.15.0-5.15.0-cp35.cp36.cp37.cp38-abi3-manylinux2014_x86_64.whl
|
||||
if candidate[-1] == segments[len(segments) - 4]:
|
||||
return '-'.join(candidate[:-1])
|
||||
return '-'.join(candidate)
|
||||
else:
|
||||
raise Exception(
|
||||
'Downloaded filename: {} does not end with bz2, gz, xz, zip, or whl'.format(filename)
|
||||
)
|
||||
|
||||
|
||||
def get_file_version(filename: str) -> str:
|
||||
name = get_package_name(filename)
|
||||
segments = filename.split(name + '-')
|
||||
version = segments[1].split('-')[0]
|
||||
for ext in ['tar.gz', 'whl', 'tar.xz', 'tar.gz', 'tar.bz2', 'zip']:
|
||||
version = version.replace('.' + ext, '')
|
||||
return version
|
||||
|
||||
|
||||
def get_file_hash(filename: str) -> str:
|
||||
sha = hashlib.sha256()
|
||||
print('Generating hash for', filename.split('/')[-1])
|
||||
with open(filename, 'rb') as f:
|
||||
while True:
|
||||
data = f.read(1024 * 1024 * 32)
|
||||
if not data:
|
||||
break
|
||||
sha.update(data)
|
||||
return sha.hexdigest()
|
||||
|
||||
|
||||
def download_tar_pypi(url: str, tempdir: str) -> None:
|
||||
with urllib.request.urlopen(url) as response:
|
||||
file_path = os.path.join(tempdir, url.split('/')[-1])
|
||||
with open(file_path, 'x+b') as tar_file:
|
||||
shutil.copyfileobj(response, tar_file)
|
||||
|
||||
|
||||
def parse_continuation_lines(fin):
|
||||
for line in fin:
|
||||
line = line.rstrip('\n')
|
||||
while line.endswith('\\'):
|
||||
try:
|
||||
line = line[:-1] + next(fin).rstrip('\n')
|
||||
except StopIteration:
|
||||
exit('Requirements have a wrong number of line continuation characters "\\"')
|
||||
yield line
|
||||
|
||||
|
||||
def fprint(string: str) -> None:
|
||||
separator = '=' * 72 # Same as `flatpak-builder`
|
||||
print(separator)
|
||||
print(string)
|
||||
print(separator)
|
||||
|
||||
|
||||
packages = []
|
||||
if opts.requirements_file:
|
||||
requirements_file_input = os.path.expanduser(opts.requirements_file)
|
||||
try:
|
||||
with open(requirements_file_input, 'r') as req_file:
|
||||
reqs = parse_continuation_lines(req_file)
|
||||
reqs_as_str = '\n'.join([r.split('--hash')[0] for r in reqs])
|
||||
reqs_list_raw = reqs_as_str.splitlines()
|
||||
py_version_regex = re.compile(r';.*python_version .+$') # Remove when pip-generator can handle python_version
|
||||
reqs_list = [py_version_regex.sub('', p) for p in reqs_list_raw]
|
||||
if opts.ignore_pkg:
|
||||
reqs_new = '\n'.join(i for i in reqs_list if i not in opts.ignore_pkg)
|
||||
else:
|
||||
reqs_new = reqs_as_str
|
||||
packages = list(requirements.parse(reqs_new))
|
||||
with tempfile.NamedTemporaryFile('w', delete=False, prefix='requirements.') as req_file:
|
||||
req_file.write(reqs_new)
|
||||
requirements_file_output = req_file.name
|
||||
except FileNotFoundError as err:
|
||||
print(err)
|
||||
sys.exit(1)
|
||||
|
||||
elif opts.packages:
|
||||
packages = list(requirements.parse('\n'.join(opts.packages)))
|
||||
with tempfile.NamedTemporaryFile('w', delete=False, prefix='requirements.') as req_file:
|
||||
req_file.write('\n'.join(opts.packages))
|
||||
requirements_file_output = req_file.name
|
||||
else:
|
||||
if not len(sys.argv) > 1:
|
||||
exit('Please specifiy either packages or requirements file argument')
|
||||
else:
|
||||
exit('This option can only be used with requirements file')
|
||||
|
||||
for i in packages:
|
||||
if i["name"].lower().startswith("pyqt"):
|
||||
print("PyQt packages are not supported by flapak-pip-generator")
|
||||
print("However, there is a BaseApp for PyQt available, that you should use")
|
||||
print("Visit https://github.com/flathub/com.riverbankcomputing.PyQt.BaseApp for more information")
|
||||
sys.exit(0)
|
||||
|
||||
with open(requirements_file_output, 'r') as req_file:
|
||||
use_hash = '--hash=' in req_file.read()
|
||||
|
||||
python_version = '2' if opts.python2 else '3'
|
||||
if opts.python2:
|
||||
pip_executable = 'pip2'
|
||||
else:
|
||||
pip_executable = 'pip3'
|
||||
|
||||
if opts.runtime:
|
||||
flatpak_cmd = [
|
||||
'flatpak',
|
||||
'--devel',
|
||||
'--share=network',
|
||||
'--filesystem=/tmp',
|
||||
'--command={}'.format(pip_executable),
|
||||
'run',
|
||||
opts.runtime
|
||||
]
|
||||
if opts.requirements_file:
|
||||
if os.path.exists(requirements_file_output):
|
||||
prefix = os.path.realpath(requirements_file_output)
|
||||
flag = '--filesystem={}'.format(prefix)
|
||||
flatpak_cmd.insert(1,flag)
|
||||
else:
|
||||
flatpak_cmd = [pip_executable]
|
||||
|
||||
output_path = ''
|
||||
|
||||
if opts.output:
|
||||
output_path = os.path.dirname(opts.output)
|
||||
output_package = os.path.basename(opts.output)
|
||||
elif opts.requirements_file:
|
||||
output_package = 'python{}-{}'.format(
|
||||
python_version,
|
||||
os.path.basename(opts.requirements_file).replace('.txt', ''),
|
||||
)
|
||||
elif len(packages) == 1:
|
||||
output_package = 'python{}-{}'.format(
|
||||
python_version, packages[0].name,
|
||||
)
|
||||
else:
|
||||
output_package = 'python{}-modules'.format(python_version)
|
||||
if opts.yaml:
|
||||
output_filename = os.path.join(output_path, output_package) + '.yaml'
|
||||
else:
|
||||
output_filename = os.path.join(output_path, output_package) + '.json'
|
||||
|
||||
modules = []
|
||||
vcs_modules = []
|
||||
sources = {}
|
||||
|
||||
unresolved_dependencies_errors = []
|
||||
|
||||
tempdir_prefix = 'pip-generator-{}'.format(output_package)
|
||||
with tempfile.TemporaryDirectory(prefix=tempdir_prefix) as tempdir:
|
||||
pip_download = flatpak_cmd + [
|
||||
'download',
|
||||
'--exists-action=i',
|
||||
'--dest',
|
||||
tempdir,
|
||||
'-r',
|
||||
requirements_file_output
|
||||
]
|
||||
if use_hash:
|
||||
pip_download.append('--require-hashes')
|
||||
|
||||
fprint('Downloading sources')
|
||||
cmd = ' '.join(pip_download)
|
||||
print('Running: "{}"'.format(cmd))
|
||||
try:
|
||||
subprocess.run(pip_download, check=True)
|
||||
os.remove(requirements_file_output)
|
||||
except subprocess.CalledProcessError:
|
||||
os.remove(requirements_file_output)
|
||||
print('Failed to download')
|
||||
print('Please fix the module manually in the generated file')
|
||||
if not opts.ignore_errors:
|
||||
print('Ignore the error by passing --ignore-errors')
|
||||
raise
|
||||
|
||||
try:
|
||||
os.remove(requirements_file_output)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
fprint('Downloading arch independent packages')
|
||||
for filename in os.listdir(tempdir):
|
||||
if not filename.endswith(('bz2', 'any.whl', 'gz', 'xz', 'zip')):
|
||||
version = get_file_version(filename)
|
||||
name = get_package_name(filename)
|
||||
try:
|
||||
url = get_tar_package_url_pypi(name, version)
|
||||
print('Downloading {}'.format(url))
|
||||
download_tar_pypi(url, tempdir)
|
||||
except Exception as err:
|
||||
# Can happen if only an arch dependent wheel is available like for wasmtime-27.0.2
|
||||
unresolved_dependencies_errors.append(err)
|
||||
print('Deleting', filename)
|
||||
try:
|
||||
os.remove(os.path.join(tempdir, filename))
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
files = {get_package_name(f): [] for f in os.listdir(tempdir)}
|
||||
|
||||
for filename in os.listdir(tempdir):
|
||||
name = get_package_name(filename)
|
||||
files[name].append(filename)
|
||||
|
||||
# Delete redundant sources, for vcs sources
|
||||
for name in files:
|
||||
if len(files[name]) > 1:
|
||||
zip_source = False
|
||||
for f in files[name]:
|
||||
if f.endswith('.zip'):
|
||||
zip_source = True
|
||||
if zip_source:
|
||||
for f in files[name]:
|
||||
if not f.endswith('.zip'):
|
||||
try:
|
||||
os.remove(os.path.join(tempdir, f))
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
vcs_packages = {
|
||||
x.name: {'vcs': x.vcs, 'revision': x.revision, 'uri': x.uri}
|
||||
for x in packages
|
||||
if x.vcs
|
||||
}
|
||||
|
||||
fprint('Obtaining hashes and urls')
|
||||
for filename in os.listdir(tempdir):
|
||||
name = get_package_name(filename)
|
||||
sha256 = get_file_hash(os.path.join(tempdir, filename))
|
||||
is_pypi = False
|
||||
|
||||
if name in vcs_packages:
|
||||
uri = vcs_packages[name]['uri']
|
||||
revision = vcs_packages[name]['revision']
|
||||
vcs = vcs_packages[name]['vcs']
|
||||
url = 'https://' + uri.split('://', 1)[1]
|
||||
s = 'commit'
|
||||
if vcs == 'svn':
|
||||
s = 'revision'
|
||||
source = OrderedDict([
|
||||
('type', vcs),
|
||||
('url', url),
|
||||
(s, revision),
|
||||
])
|
||||
is_vcs = True
|
||||
else:
|
||||
name = name.casefold()
|
||||
is_pypi = True
|
||||
url = get_pypi_url(name, filename)
|
||||
source = OrderedDict([
|
||||
('type', 'file'),
|
||||
('url', url),
|
||||
('sha256', sha256)])
|
||||
if opts.checker_data:
|
||||
source['x-checker-data'] = {
|
||||
'type': 'pypi',
|
||||
'name': name}
|
||||
if url.endswith(".whl"):
|
||||
source['x-checker-data']['packagetype'] = 'bdist_wheel'
|
||||
is_vcs = False
|
||||
sources[name] = {'source': source, 'vcs': is_vcs, 'pypi': is_pypi}
|
||||
|
||||
# Python3 packages that come as part of org.freedesktop.Sdk.
|
||||
system_packages = ['cython', 'easy_install', 'mako', 'markdown', 'meson', 'pip', 'pygments', 'setuptools', 'six', 'wheel']
|
||||
|
||||
fprint('Generating dependencies')
|
||||
for package in packages:
|
||||
|
||||
if package.name is None:
|
||||
print('Warning: skipping invalid requirement specification {} because it is missing a name'.format(package.line), file=sys.stderr)
|
||||
print('Append #egg=<pkgname> to the end of the requirement line to fix', file=sys.stderr)
|
||||
continue
|
||||
elif package.name.casefold() in system_packages:
|
||||
print(f"{package.name} is in system_packages. Skipping.")
|
||||
continue
|
||||
|
||||
if len(package.extras) > 0:
|
||||
extras = '[' + ','.join(extra for extra in package.extras) + ']'
|
||||
else:
|
||||
extras = ''
|
||||
|
||||
version_list = [x[0] + x[1] for x in package.specs]
|
||||
version = ','.join(version_list)
|
||||
|
||||
if package.vcs:
|
||||
revision = ''
|
||||
if package.revision:
|
||||
revision = '@' + package.revision
|
||||
pkg = package.uri + revision + '#egg=' + package.name
|
||||
else:
|
||||
pkg = package.name + extras + version
|
||||
|
||||
dependencies = []
|
||||
# Downloads the package again to list dependencies
|
||||
|
||||
tempdir_prefix = 'pip-generator-{}'.format(package.name)
|
||||
with tempfile.TemporaryDirectory(prefix='{}-{}'.format(tempdir_prefix, package.name)) as tempdir:
|
||||
pip_download = flatpak_cmd + [
|
||||
'download',
|
||||
'--exists-action=i',
|
||||
'--dest',
|
||||
tempdir,
|
||||
]
|
||||
try:
|
||||
print('Generating dependencies for {}'.format(package.name))
|
||||
subprocess.run(pip_download + [pkg], check=True, stdout=subprocess.DEVNULL)
|
||||
for filename in sorted(os.listdir(tempdir)):
|
||||
dep_name = get_package_name(filename)
|
||||
if dep_name.casefold() in system_packages:
|
||||
continue
|
||||
dependencies.append(dep_name)
|
||||
|
||||
except subprocess.CalledProcessError:
|
||||
print('Failed to download {}'.format(package.name))
|
||||
|
||||
is_vcs = True if package.vcs else False
|
||||
package_sources = []
|
||||
for dependency in dependencies:
|
||||
casefolded = dependency.casefold()
|
||||
if casefolded in sources and sources[casefolded].get("pypi") is True:
|
||||
source = sources[casefolded]
|
||||
elif dependency in sources and sources[dependency].get("pypi") is False:
|
||||
source = sources[dependency]
|
||||
elif (
|
||||
casefolded.replace("_", "-") in sources
|
||||
and sources[casefolded.replace("_", "-")].get("pypi") is True
|
||||
):
|
||||
source = sources[casefolded.replace("_", "-")]
|
||||
elif (
|
||||
dependency.replace("_", "-") in sources
|
||||
and sources[dependency.replace("_", "-")].get("pypi") is False
|
||||
):
|
||||
source = sources[dependency.replace("_", "-")]
|
||||
else:
|
||||
continue
|
||||
|
||||
if not (not source['vcs'] or is_vcs):
|
||||
continue
|
||||
|
||||
package_sources.append(source['source'])
|
||||
|
||||
if package.vcs:
|
||||
name_for_pip = '.'
|
||||
else:
|
||||
name_for_pip = pkg
|
||||
|
||||
module_name = 'python{}-{}'.format(python_version, package.name)
|
||||
|
||||
pip_command = [
|
||||
pip_executable,
|
||||
'install',
|
||||
'--verbose',
|
||||
'--exists-action=i',
|
||||
'--no-index',
|
||||
'--find-links="file://${PWD}"',
|
||||
'--prefix=${FLATPAK_DEST}',
|
||||
'"{}"'.format(name_for_pip)
|
||||
]
|
||||
if package.name in opts.ignore_installed:
|
||||
pip_command.append('--ignore-installed')
|
||||
if not opts.build_isolation:
|
||||
pip_command.append('--no-build-isolation')
|
||||
|
||||
module = OrderedDict([
|
||||
('name', module_name),
|
||||
('buildsystem', 'simple'),
|
||||
('build-commands', [' '.join(pip_command)]),
|
||||
('sources', package_sources),
|
||||
])
|
||||
if opts.cleanup == 'all':
|
||||
module['cleanup'] = ['*']
|
||||
elif opts.cleanup == 'scripts':
|
||||
module['cleanup'] = ['/bin', '/share/man/man1']
|
||||
|
||||
if package.vcs:
|
||||
vcs_modules.append(module)
|
||||
else:
|
||||
modules.append(module)
|
||||
|
||||
modules = vcs_modules + modules
|
||||
if len(modules) == 1:
|
||||
pypi_module = modules[0]
|
||||
else:
|
||||
pypi_module = {
|
||||
'name': output_package,
|
||||
'buildsystem': 'simple',
|
||||
'build-commands': [],
|
||||
'modules': modules,
|
||||
}
|
||||
|
||||
print()
|
||||
with open(output_filename, 'w') as output:
|
||||
if opts.yaml:
|
||||
class OrderedDumper(yaml.Dumper):
|
||||
def increase_indent(self, flow=False, indentless=False):
|
||||
return super(OrderedDumper, self).increase_indent(flow, False)
|
||||
|
||||
def dict_representer(dumper, data):
|
||||
return dumper.represent_dict(data.items())
|
||||
|
||||
OrderedDumper.add_representer(OrderedDict, dict_representer)
|
||||
|
||||
output.write("# Generated with flatpak-pip-generator " + " ".join(sys.argv[1:]) + "\n")
|
||||
yaml.dump(pypi_module, output, Dumper=OrderedDumper)
|
||||
else:
|
||||
output.write(json.dumps(pypi_module, indent=4))
|
||||
print('Output saved to {}'.format(output_filename))
|
||||
|
||||
if len(unresolved_dependencies_errors) != 0:
|
||||
print("Unresolved dependencies. Handle them manually")
|
||||
for e in unresolved_dependencies_errors:
|
||||
print(f"- ERROR: {e}")
|
||||
|
||||
workaround = """Example how to handle wheels which only support specific architectures:
|
||||
- type: file
|
||||
url: https://files.pythonhosted.org/packages/79/ae/7e5b85136806f9dadf4878bf73cf223fe5c2636818ba3ab1c585d0403164/numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
|
||||
sha256: 7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e
|
||||
only-arches:
|
||||
- aarch64
|
||||
- type: file
|
||||
url: https://files.pythonhosted.org/packages/3a/d0/edc009c27b406c4f9cbc79274d6e46d634d139075492ad055e3d68445925/numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
|
||||
sha256: 666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5
|
||||
only-arches:
|
||||
- x86_64
|
||||
"""
|
||||
raise Exception(f"Not all dependencies can be determined. Handle them manually.\n{workaround}")
|
32
flatpak/net.boxyfoxy.OptimaLab35.json
Normal file
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"id": "net.boxyfoxy.OptimaLab35",
|
||||
"runtime": "org.kde.Platform",
|
||||
"runtime-version": "6.8",
|
||||
"sdk": "org.kde.Sdk",
|
||||
"sdk-version": "6.8",
|
||||
"base": "io.qt.PySide.BaseApp",
|
||||
"base-version": "6.8",
|
||||
"command": "OptimaLab35",
|
||||
"version": "1.0",
|
||||
"finish-args": ["--socket=wayland", "--socket=x11"],
|
||||
"modules": [
|
||||
"python3-modules.json",
|
||||
{
|
||||
"name": "OptimaLab35",
|
||||
"buildsystem": "simple",
|
||||
"build-commands": [
|
||||
"pip install --no-build-isolation --prefix=/app .",
|
||||
"ls",
|
||||
"install -D flatpak/net.boxyfoxy.OptimaLab35.desktop /app/share/applications/net.boxyfoxy.OptimaLab35.desktop",
|
||||
"install -D flatpak/net.boxyfoxy.OptimaLab35.metainfo.xml /app/share/metainfo/net.boxyfoxy.OptimaLab35.metainfo.xml",
|
||||
"install -D flatpak/app-icon.png /app/share/icons/hicolor/512x512/apps/net.boxyfoxy.OptimaLab35.png"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"type": "dir",
|
||||
"path": "../src"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -5,11 +5,10 @@ build-backend = "hatchling.build"
|
|||
[project]
|
||||
name = "OptimaLab35"
|
||||
dynamic = ["version"]
|
||||
authors = [{ name = "Mr. Finchum" }]
|
||||
authors = [{ name = "Mr Finchum" }]
|
||||
description = "User interface for optima35."
|
||||
readme = "pip_README.md"
|
||||
requires-python = ">=3.8"
|
||||
dependencies = ["optima35>=0.6.7", "pyside6", "PyYAML", "packaging"]
|
||||
dependencies = ["optima35>=1.0.0, <2.0.0", "PyYAML", "PyPyUpdater"]
|
||||
classifiers = [
|
||||
"Programming Language :: Python :: 3",
|
||||
"License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)",
|
||||
|
@ -17,13 +16,13 @@ classifiers = [
|
|||
]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://gitlab.com/CodeByMrFinchum/OptimaLab35"
|
||||
Source = "https://gitlab.com/CodeByMrFinchum/OptimaLab35"
|
||||
|
||||
[project.scripts]
|
||||
OptimaLab35 = "OptimaLab35.main:main"
|
||||
OptimaLab35 = "OptimaLab35.__main__:main"
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["src/OptimaLab35"]
|
||||
packages = ["OptimaLab35"]
|
||||
|
||||
[tool.hatch.version]
|
||||
path = "src/OptimaLab35/__init__.py"
|
||||
path = "OptimaLab35/__init__.py"
|
107
flatpak/python3-modules.json
Normal file
|
@ -0,0 +1,107 @@
|
|||
{
|
||||
"name": "python3-modules",
|
||||
"buildsystem": "simple",
|
||||
"build-commands": [],
|
||||
"modules": [
|
||||
{
|
||||
"name": "python3-piexif",
|
||||
"buildsystem": "simple",
|
||||
"build-commands": [
|
||||
"pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"piexif\" --no-build-isolation"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/2c/d8/6f63147dd73373d051c5eb049ecd841207f898f50a5a1d4378594178f6cf/piexif-1.1.3-py2.py3-none-any.whl",
|
||||
"sha256": "3bc435d171720150b81b15d27e05e54b8abbde7b4242cddd81ef160d283108b6"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "python3-pillow",
|
||||
"buildsystem": "simple",
|
||||
"build-commands": [
|
||||
"pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"pillow\" --no-build-isolation"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/f3/af/c097e544e7bd278333db77933e535098c259609c4eb3b85381109602fb5b/pillow-11.1.0.tar.gz",
|
||||
"sha256": "368da70808b36d73b4b390a8ffac11069f8a5c85f29eff1f1b01bcf3ef5b2a20"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "python3-optima35",
|
||||
"buildsystem": "simple",
|
||||
"build-commands": [
|
||||
"pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"optima35\" --no-build-isolation"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/da/44/f304f2f1f333204dcf57cb883c15acf32aaecdca13730b9822dce79a1b3e/optima35-1.0.1-py3-none-any.whl",
|
||||
"sha256": "76f5623c4d6bfa57230c9d485a16f6336e91fc8a0a6ad88d42618b5193c9f587"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/2c/d8/6f63147dd73373d051c5eb049ecd841207f898f50a5a1d4378594178f6cf/piexif-1.1.3-py2.py3-none-any.whl",
|
||||
"sha256": "3bc435d171720150b81b15d27e05e54b8abbde7b4242cddd81ef160d283108b6"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/f3/af/c097e544e7bd278333db77933e535098c259609c4eb3b85381109602fb5b/pillow-11.1.0.tar.gz",
|
||||
"sha256": "368da70808b36d73b4b390a8ffac11069f8a5c85f29eff1f1b01bcf3ef5b2a20"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "python3-PyYAML",
|
||||
"buildsystem": "simple",
|
||||
"build-commands": [
|
||||
"pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"PyYAML\" --no-build-isolation"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz",
|
||||
"sha256": "d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "python3-hatchling",
|
||||
"buildsystem": "simple",
|
||||
"build-commands": [
|
||||
"pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"hatchling\" --no-build-isolation"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/08/e7/ae38d7a6dfba0533684e0b2136817d667588ae3ec984c1a4e5df5eb88482/hatchling-1.27.0-py3-none-any.whl",
|
||||
"sha256": "d3a2f3567c4f926ea39849cdf924c7e99e6686c9c8e288ae1037c8fa2a5d937b"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl",
|
||||
"sha256": "09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl",
|
||||
"sha256": "a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl",
|
||||
"sha256": "44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/2b/c5/6422dbc59954389b20b2aba85b737ab4a552e357e7ea14b52f40312e7c84/trove_classifiers-2025.1.15.22-py3-none-any.whl",
|
||||
"sha256": "5f19c789d4f17f501d36c94dbbf969fb3e8c2784d008e6f5164dd2c3d6a2b07c"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
Before Width: | Height: | Size: 218 KiB |
Before Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 178 KiB |
Before Width: | Height: | Size: 93 KiB |
Before Width: | Height: | Size: 210 KiB |
BIN
media/mainwindow_dark.png
Normal file
After Width: | Height: | Size: 244 KiB |
BIN
media/mainwindow_exif_dark.png
Normal file
After Width: | Height: | Size: 232 KiB |
BIN
media/mainwindow_exif_light.png
Normal file
After Width: | Height: | Size: 241 KiB |
BIN
media/mainwindow_light.png
Normal file
After Width: | Height: | Size: 226 KiB |
Before Width: | Height: | Size: 1.9 MiB |
BIN
media/previewwindow_dark.png
Normal file
After Width: | Height: | Size: 1.8 MiB |
BIN
media/previewwindow_light.png
Normal file
After Width: | Height: | Size: 1.8 MiB |
BIN
media/settingswindow_dark.png
Normal file
After Width: | Height: | Size: 115 KiB |
BIN
media/settingswindow_light.png
Normal file
After Width: | Height: | Size: 111 KiB |
BIN
media/settingswindow_updater_dark.png
Normal file
After Width: | Height: | Size: 123 KiB |
BIN
media/settingswindow_updater_light.png
Normal file
After Width: | Height: | Size: 119 KiB |
|
@ -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
|
@ -0,0 +1,4 @@
|
|||
optima35
|
||||
PyPiUpdater
|
||||
pyside6
|
||||
PyYAML
|
|
@ -1 +1 @@
|
|||
__version__ = "0.2.3"
|
||||
__version__ = "0.0.1"
|
||||
|
|
30
src/OptimaLab35/__main__.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
import sys
|
||||
from PySide6 import QtWidgets
|
||||
from .utils.utility import Utilities
|
||||
from .mainWindow import OptimaLab35
|
||||
from .const import (
|
||||
CONFIG_BASE_PATH
|
||||
)
|
||||
|
||||
def main():
|
||||
u = Utilities(CONFIG_BASE_PATH)
|
||||
app_settings = u.load_settings()
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
|
||||
try:
|
||||
import qdarktheme
|
||||
app_settings["theme"]["theme_pkg"] = True
|
||||
except ImportError:
|
||||
app_settings["theme"]["theme_pkg"] = False
|
||||
|
||||
if app_settings["theme"]["use_custom_theme"] and app_settings["theme"]["theme_pkg"]:
|
||||
qdarktheme.setup_theme(app_settings["theme"]["mode"].lower())
|
||||
|
||||
u.save_settings(app_settings)
|
||||
|
||||
window = OptimaLab35()
|
||||
window.show()
|
||||
app.exec()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
2
src/OptimaLab35/const.py
Normal file
|
@ -0,0 +1,2 @@
|
|||
APPLICATION_NAME = "OptimaLab35"
|
||||
CONFIG_BASE_PATH = "~/.config/OptimaLab35"
|
|
@ -1,54 +0,0 @@
|
|||
import os
|
||||
from argparse import ArgumentParser
|
||||
from OptimaLab35 import gui, __version__
|
||||
|
||||
# Try importing TUI only if simple-term-menu is installed
|
||||
try:
|
||||
from OptimaLab35 import tui
|
||||
simple_term_menu_installed = True
|
||||
except ImportError:
|
||||
simple_term_menu_installed = False
|
||||
|
||||
# Check if PySide is installed
|
||||
def check_pyside_installed():
|
||||
try:
|
||||
import PySide6 # Replace with PySide2 if using that version
|
||||
return True
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
def start_gui():
|
||||
gui.main()
|
||||
|
||||
def start_tui():
|
||||
if simple_term_menu_installed:
|
||||
tui.main()
|
||||
else:
|
||||
print("Error: simple-term-menu is not installed. Please install it to use the TUI mode.")
|
||||
exit(1)
|
||||
|
||||
def main():
|
||||
parser = ArgumentParser(description="Start the Optima35 application.")
|
||||
parser.add_argument("--tui", action="store_true", help="Start in terminal UI mode.")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.tui:
|
||||
print("Starting TUI...")
|
||||
start_tui()
|
||||
return
|
||||
|
||||
# Check OS and start GUI if on Windows
|
||||
if os.name == "nt":
|
||||
print("Detected Windows. Starting GUI...")
|
||||
start_gui()
|
||||
else:
|
||||
# Non-Windows: Check if PySide is installed
|
||||
if check_pyside_installed():
|
||||
print("PySide detected. Starting GUI...")
|
||||
start_gui()
|
||||
else:
|
||||
print("PySide is not installed. Falling back to TUI...")
|
||||
start_tui()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -1,59 +1,67 @@
|
|||
import sys
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
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.exif_handler_window import ExifEditor
|
||||
from OptimaLab35.ui.simple_dialog import SimpleDialog # Import the SimpleDialog class
|
||||
|
||||
from OptimaLab35 import __version__
|
||||
from .const import (
|
||||
APPLICATION_NAME,
|
||||
CONFIG_BASE_PATH
|
||||
)
|
||||
|
||||
from PySide6.QtCore import QRunnable, QThreadPool, Signal, QObject, QRegularExpression
|
||||
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 import QtWidgets
|
||||
from PySide6.QtWidgets import (
|
||||
QMessageBox,
|
||||
QApplication,
|
||||
QMainWindow,
|
||||
QWidget,
|
||||
QVBoxLayout,
|
||||
QLabel,
|
||||
QLineEdit,
|
||||
QPushButton,
|
||||
QCheckBox,
|
||||
QFileDialog,
|
||||
QHBoxLayout,
|
||||
QSpinBox,
|
||||
QProgressBar,
|
||||
QFileDialog
|
||||
)
|
||||
|
||||
from PySide6.QtGui import QPixmap, QRegularExpressionValidator
|
||||
from PySide6.QtGui import QRegularExpressionValidator, QIcon
|
||||
|
||||
class OptimaLab35(QMainWindow, Ui_MainWindow):
|
||||
def __init__(self):
|
||||
super(OptimaLab35, self).__init__()
|
||||
self.name = "OptimaLab35"
|
||||
self.name = APPLICATION_NAME
|
||||
self.version = __version__
|
||||
self.o = OptimaManager()
|
||||
self.u = Utilities()
|
||||
self.u.program_configs()
|
||||
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("~/.config/OptimaLab35/exif.yaml")
|
||||
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()
|
||||
self.preview_window = PreviewWindow()
|
||||
|
||||
# Change UI elements
|
||||
self.change_statusbar(f"Using {self.o.name} v{self.o.version}", 5000)
|
||||
self.setWindowTitle(f"{self.name} v{self.version}")
|
||||
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):
|
||||
|
@ -61,6 +69,13 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
|
|||
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)
|
||||
|
@ -75,20 +90,23 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
|
|||
self.ui.edit_exif_button.clicked.connect(self.open_exif_editor)
|
||||
|
||||
self.ui.actionAbout.triggered.connect(self.info_window)
|
||||
self.ui.actionPreview.triggered.connect(self.open_preview_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)
|
||||
|
||||
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.show()
|
||||
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
|
||||
|
@ -98,13 +116,22 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
|
|||
self.ui.grayscale_checkBox.setChecked(checkbox_state)
|
||||
|
||||
def info_window(self):
|
||||
# ChatGPT, mainly
|
||||
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 <b>{self.o.name}</b> (v{self.o.version}).</p>
|
||||
<p> Both projects are in active development, for more details, visit:</p>
|
||||
<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>
|
||||
|
@ -170,7 +197,6 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
|
|||
def populate_exif(self):
|
||||
# partly chatGPT
|
||||
# Mapping of EXIF fields to comboboxes in the UI
|
||||
print("populate")
|
||||
combo_mapping = {
|
||||
"make": self.ui.make_comboBox,
|
||||
"model": self.ui.model_comboBox,
|
||||
|
@ -232,6 +258,7 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
|
|||
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):
|
||||
|
@ -295,7 +322,12 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
|
|||
|
||||
def start_process(self):
|
||||
self.toggle_buttons(False)
|
||||
self.update_settings() # Get all user selected data
|
||||
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
|
||||
|
@ -307,6 +339,14 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
|
|||
# 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"]
|
||||
|
||||
|
@ -315,7 +355,7 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
|
|||
|
||||
input_path = os.path.join(input_folder, image_file)
|
||||
|
||||
self.o.insert_dict_to_image(
|
||||
self.o.insert_exif_to_image(
|
||||
exif_dict = self.settings["user_selected_exif"],
|
||||
image_path = input_path,
|
||||
gps = self.settings["gps"])
|
||||
|
@ -327,12 +367,23 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
|
|||
|
||||
def startinsert_exif(self):
|
||||
self.toggle_buttons(False)
|
||||
self.update_settings() # Get all user selected data
|
||||
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)
|
||||
|
@ -366,10 +417,14 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
|
|||
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}) & {self.o.name} (v{self.o.version})"
|
||||
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):
|
||||
|
@ -379,11 +434,20 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
|
|||
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())]
|
||||
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
|
||||
|
@ -413,10 +477,18 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
|
|||
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.
|
||||
|
@ -426,55 +498,9 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
|
|||
elif do == "write":
|
||||
self.u.write_yaml(self.exif_file, self.available_exif_data)
|
||||
|
||||
class PreviewWindow(QMainWindow, Ui_Preview_Window):
|
||||
values_selected = Signal(int, int, bool)
|
||||
|
||||
def __init__(self):
|
||||
super(PreviewWindow, self).__init__()
|
||||
self.ui = Ui_Preview_Window()
|
||||
self.ui.setupUi(self)
|
||||
self.o = OptimaManager()
|
||||
## Ui interaction
|
||||
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))
|
||||
self.preview_image = None
|
||||
|
||||
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):
|
||||
path = self.ui.image_path_lineEdit.text()
|
||||
if not os.path.isfile(path):
|
||||
return
|
||||
try:
|
||||
img = self.o.process_image(
|
||||
save = False,
|
||||
image_input_file = path,
|
||||
image_output_file = "",
|
||||
grayscale = self.ui.grayscale_checkBox.isChecked(),
|
||||
brightness = int(self.ui.brightness_spinBox.text()),
|
||||
contrast = int(self.ui.contrast_spinBox.text())
|
||||
)
|
||||
except Exception as e:
|
||||
QMessageBox.warning(self, "Warning", "Error loading image...")
|
||||
print(f"Error loading image...\n{e}")
|
||||
return
|
||||
|
||||
self.preview_image = QPixmap.fromImage(img)
|
||||
self.ui.QLabel.setPixmap(self.preview_image)
|
||||
|
||||
def close_window(self):
|
||||
# Emit the signal with the values from the spinboxes and checkbox
|
||||
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()
|
||||
def closeEvent(self, event):
|
||||
QApplication.closeAllWindows()
|
||||
event.accept()
|
||||
|
||||
class WorkerSignals(QObject):
|
||||
# ChatGPT
|
||||
|
@ -490,7 +516,7 @@ class ImageProcessorRunnable(QRunnable):
|
|||
self.signals = WorkerSignals()
|
||||
self.signals.progress.connect(progress_callback)
|
||||
self.o = OptimaManager()
|
||||
self.u = Utilities()
|
||||
self.u = Utilities(os.path.expanduser(CONFIG_BASE_PATH))
|
||||
|
||||
def run(self):
|
||||
input_folder = self.settings["input_folder"]
|
||||
|
@ -504,7 +530,7 @@ class ImageProcessorRunnable(QRunnable):
|
|||
image_name = os.path.splitext(image_file)[0]
|
||||
output_path = os.path.join(output_folder, image_name)
|
||||
|
||||
self.o.process_image(
|
||||
self.o.process_and_save_image(
|
||||
image_input_file = input_path,
|
||||
image_output_file = output_path,
|
||||
file_type = self.settings["file_format"],
|
||||
|
@ -524,13 +550,3 @@ class ImageProcessorRunnable(QRunnable):
|
|||
self.signals.progress.emit(int((i / len(self.image_files)) * 100))
|
||||
|
||||
self.signals.finished.emit()
|
||||
|
||||
|
||||
def main():
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
window = OptimaLab35()
|
||||
window.show()
|
||||
app.exec()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
166
src/OptimaLab35/previewWindow.py
Normal 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)
|
357
src/OptimaLab35/settingsWindow.py
Normal 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)
|
|
@ -1,330 +0,0 @@
|
|||
import os
|
||||
from datetime import datetime
|
||||
# my packages
|
||||
from optima35.core import OptimaManager
|
||||
from OptimaLab35.utils.utility import Utilities
|
||||
from OptimaLab35.ui.simple_tui import SimpleTUI
|
||||
from OptimaLab35 import __version__
|
||||
|
||||
class OptimaLab35_lite():
|
||||
def __init__(self):
|
||||
self.name = "OptimaLab35-lite"
|
||||
self.version = __version__
|
||||
self.o = OptimaManager()
|
||||
self.u = Utilities()
|
||||
self.tui = SimpleTUI()
|
||||
self.u.program_configs()
|
||||
self.exif_file = os.path.expanduser("~/.config/OptimaLab35/exif.yaml")
|
||||
self.available_exif_data = self.u.read_yaml(self.exif_file)
|
||||
self.setting_file = os.path.expanduser("~/.config/OptimaLab35/tui_settings.yaml")
|
||||
self.settings = {
|
||||
"input_folder": None,
|
||||
"output_folder": None,
|
||||
"file_format": None,
|
||||
"resize": None,
|
||||
"copy_exif": None,
|
||||
"contrast": None,
|
||||
"brightness": None,
|
||||
"new_file_names": None,
|
||||
"invert_image_order": False,
|
||||
"watermark": None,
|
||||
"gps": None,
|
||||
"modifications": [],
|
||||
}
|
||||
self.settings_to_save = [
|
||||
"resize",
|
||||
"jpg_quality",
|
||||
"png_compression",
|
||||
"optimize",
|
||||
"contrast",
|
||||
"brightness"
|
||||
]
|
||||
|
||||
def _process(self):
|
||||
self._check_options() # Get all user selected data
|
||||
input_folder_valid = os.path.exists(self.settings["input_folder"])
|
||||
output_folder_valid = os.path.exists(self.settings["output_folder"])
|
||||
if not input_folder_valid or not output_folder_valid:
|
||||
print("Warning", f"Input location {input_folder_valid}\nOutput folder {output_folder_valid}...")
|
||||
return
|
||||
|
||||
input_folder = self.settings["input_folder"]
|
||||
output_folder = self.settings["output_folder"]
|
||||
|
||||
image_files = [
|
||||
f for f in os.listdir(input_folder) if f.lower().endswith((".png", ".jpg", ".jpeg", ".webp"))
|
||||
]
|
||||
i = 1
|
||||
for image_file in image_files:
|
||||
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(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_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.selected_exif,
|
||||
gps = self.settings["gps"],
|
||||
copy_exif = self.settings["copy_exif"])
|
||||
self.u.progress_bar(i, len(image_files))
|
||||
i += 1
|
||||
|
||||
def _check_options(self):
|
||||
try:
|
||||
if "Resize image" in self.settings["modifications"]:
|
||||
self.settings["resize"] = self.settings["resize"]
|
||||
else:
|
||||
self.settings["resize"] = None
|
||||
|
||||
if "Convert to grayscale" in self.settings["modifications"]:
|
||||
self.settings["grayscale"] = True
|
||||
else:
|
||||
self.settings["grayscale"] = False
|
||||
|
||||
if "Change contrast" in self.settings["modifications"]:
|
||||
self.settings["contrast"] = self.settings["contrast"]
|
||||
else:
|
||||
self.settings["contrast"] = None
|
||||
|
||||
if "Change brightness" in self.settings["modifications"]:
|
||||
self.settings["brightness"] = self.settings["brightness"]
|
||||
else:
|
||||
self.settings["brightness"] = None
|
||||
|
||||
if "Rename images" in self.settings["modifications"]:
|
||||
self.settings["new_file_names"] = self.settings["new_file_names"]
|
||||
else:
|
||||
self.settings["new_file_names"] = False
|
||||
|
||||
if "Invert image order" in self.settings["modifications"]:
|
||||
self.settings["invert_image_order"] = True
|
||||
else:
|
||||
self.settings["invert_image_order"] = False
|
||||
|
||||
if "Add Watermark" in self.settings["modifications"]:
|
||||
self.settings["watermark"] = self.settings["watermark"]
|
||||
else:
|
||||
self.settings["watermark"] = None
|
||||
|
||||
self.settings["optimize"] = self.settings["optimize"]
|
||||
self.settings["png_compression"] = self.settings["png_compression"]
|
||||
self.settings["jpg_quality"] = self.settings["jpg_quality"]
|
||||
|
||||
self.settings["input_folder"] = self.settings["input_folder"]
|
||||
self.settings["output_folder"] = self.settings["output_folder"]
|
||||
self.settings["file_format"] = self.settings["file_format"]
|
||||
self.settings["font_size"] = 2 # need to add option to select size
|
||||
|
||||
self.settings["copy_exif"] = self.settings["copy_exif"]
|
||||
|
||||
if "Change EXIF" in self.settings["modifications"]: #missing
|
||||
self.selected_exif = self._collect_exif_data() #
|
||||
else:
|
||||
self.selected_exif = None
|
||||
|
||||
except Exception as e:
|
||||
print(f"Whoops: {e}")
|
||||
|
||||
def _load_or_ask_settings(self):
|
||||
"""Load settings from a YAML file or ask the user if not present or incomplete."""
|
||||
try:
|
||||
if self._read_settings(self.settings_to_save):
|
||||
for item in self.settings_to_save:
|
||||
print(f"{item}: {self.settings[item]}")
|
||||
use_saved = self.tui.yes_no_menu("Use these settings?")
|
||||
if use_saved:
|
||||
return
|
||||
else:
|
||||
print("No settings found...")
|
||||
self._ask_for_settings()
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
self._ask_for_settings()
|
||||
|
||||
def _ask_for_settings(self):
|
||||
print("Asking for new settings...\n")
|
||||
print(f"Settings for {self.name} v{self.version} will be saved {self.setting_file}...")
|
||||
self.settings["resize"] = self.take_input_and_validate(question = "Default resize percentage (below 100 downscale, above upscale): ", accepted_type = int, min_value = 10, max_value = 200)
|
||||
self.settings["contrast"] = self.take_input_and_validate(question = "Default contrast percentage (negative = decrease, positive = increase): ", accepted_type = int, min_value = -100, max_value = 100)
|
||||
self.settings["brightness"] = self.take_input_and_validate(question = "Default brighness percentage (negative = decrease, positive = increase): ", accepted_type = int, min_value = -100, max_value = 100)
|
||||
self.settings["jpg_quality"] = self.take_input_and_validate(question = "JPEG quality (1-100, 80 default): ", accepted_type = int, min_value = 1, max_value = 100)
|
||||
self.settings["png_compression"] = self.take_input_and_validate(question = "PNG compression level (0-9, 6 default): ", accepted_type = int, min_value = 0, max_value = 9)
|
||||
self.settings["optimize"] = self.tui.yes_no_menu("Optimize images i.e. compressing?")
|
||||
|
||||
self._write_settings(self.settings_to_save)
|
||||
|
||||
def _write_settings(self, keys_to_save):
|
||||
""""Write self.setting, but only specific values"""
|
||||
keys = keys_to_save
|
||||
filtered_settings = {key: self.settings[key] for key in keys if key in self.settings}
|
||||
self.u.write_yaml(self.setting_file, filtered_settings)
|
||||
print("New settings saved successfully.")
|
||||
|
||||
def _read_settings(self, keys_to_load):
|
||||
"""
|
||||
Read settings from the settings file and update self.settings
|
||||
with the values for specific keys without overwriting existing values.
|
||||
"""
|
||||
# First draft by ChatGPT, adjusted to fit my needs.
|
||||
keys = keys_to_load
|
||||
if os.path.exists(self.setting_file):
|
||||
loaded_settings = self.u.read_yaml(self.setting_file)
|
||||
for key in keys:
|
||||
if key in loaded_settings:
|
||||
self.settings[key] = loaded_settings[key]
|
||||
print("Settings loaded successfully.")
|
||||
return True
|
||||
else:
|
||||
print("Settings file empty.")
|
||||
return False
|
||||
|
||||
def _collect_exif_data(self):
|
||||
"""Collect EXIF data based on user input."""
|
||||
print(f"Exif file can be found {self.exif_file}...")
|
||||
user_data = {}
|
||||
fields = [
|
||||
"make", "model", "lens", "iso", "image_description",
|
||||
"user_comment", "artist", "copyright_info"
|
||||
]
|
||||
for field in fields:
|
||||
|
||||
choise = self.tui.choose_menu(f"Enter {field.replace('_', ' ').title()}", self.available_exif_data[field])
|
||||
user_data[field] = choise
|
||||
|
||||
user_data["software"] = f"{self.o.name} {self.o.version}"
|
||||
new_date = self._get_date_input()
|
||||
|
||||
if new_date:
|
||||
user_data["date_time_original"] = new_date
|
||||
|
||||
self.settings["gps"] = self._get_gps_input(user_data)
|
||||
|
||||
return user_data
|
||||
|
||||
def _get_gps_input(self, test_exif):
|
||||
while True:
|
||||
lat = input("Enter Latitude (xx.xxxxxx): ")
|
||||
if lat == "":
|
||||
return None
|
||||
long = input("Enter Longitude (xx.xxxxxx): ")
|
||||
try:
|
||||
self.o.exif_handler.add_geolocation_to_exif(test_exif, float(lat), float(long))
|
||||
return [float(lat), float(long)]
|
||||
except Exception:
|
||||
print("Invalid GPS formate, try again...")
|
||||
|
||||
def _get_date_input(self):
|
||||
# Partially chatGPT
|
||||
while True:
|
||||
date_input = input("Enter a date (yyyy-mm-dd): ")
|
||||
if date_input == "":
|
||||
return None # Skip if input is empty
|
||||
try:
|
||||
new_date = datetime.strptime(date_input, "%Y-%m-%d")
|
||||
return new_date.strftime("%Y:%m:%d 00:00:00")
|
||||
except ValueError:
|
||||
print("Invalid date format. Please enter the date in yyyy-mm-dd format.")
|
||||
|
||||
def _get_user_settings(self):
|
||||
"""Get initial settings from the user."""
|
||||
menu_options = [
|
||||
"Resize image",
|
||||
"Change EXIF",
|
||||
"Convert to grayscale",
|
||||
"Change contrast",
|
||||
"Change brightness",
|
||||
"Rename images",
|
||||
"Invert image order",
|
||||
"Add Watermark"
|
||||
] # new option can be added here.
|
||||
|
||||
self.settings["input_folder"] = input("Enter path of input folder: ").strip() # Add: check if folder exists.
|
||||
self.settings["output_folder"] = input("Enter path of output folder: ").strip()
|
||||
self.settings["file_format"] = self.take_input_and_validate(question = "Enter export file format (jpg, png, webp): ", accepted_input = ["jpg", "png", "webp"], accepted_type = str)
|
||||
self.settings["modifications"] = self.tui.multi_select_menu(
|
||||
f"\n{self.name} v{self.version} for {self.o.name} v.{self.o.version} \nSelect what you want to do (esc or q to exit)",
|
||||
menu_options
|
||||
)
|
||||
if "Change EXIF" not in self.settings["modifications"]:
|
||||
self.settings["copy_exif"] = self.tui.yes_no_menu("Do you want to copy exif info from original file?")
|
||||
if "Rename images" in self.settings["modifications"]:
|
||||
self.settings["new_file_names"] = input("What should be the name for the new images? ") # Need
|
||||
else:
|
||||
self.settings["new_file_names"] = False
|
||||
if "Invert image order" in self.settings["modifications"]:
|
||||
self.settings["invert_image_order"] = True
|
||||
else:
|
||||
self.settings["invert_image_order"] = False
|
||||
if "Add Watermark" in self.settings["modifications"]:
|
||||
self.settings["watermark"] = input("Enter text for watermark. ")
|
||||
else:
|
||||
self.settings["watermark"] = False
|
||||
|
||||
os.makedirs(self.settings["output_folder"], exist_ok = True)
|
||||
|
||||
def take_input_and_validate(self, question, accepted_input = None, accepted_type = str, min_value = None, max_value = None):
|
||||
"""
|
||||
Asks the user a question, validates the input, and ensures it matches the specified criteria.
|
||||
Args:
|
||||
question (str): The question to ask the user.
|
||||
accepted_input (list): A list of acceptable inputs (optional for non-numeric types).
|
||||
accepted_type (type): The expected type of input (e.g., str, int, float).
|
||||
min_value (int/float): Minimum value for numeric inputs (optional).
|
||||
max_value (int/float): Maximum value for numeric inputs (optional).
|
||||
|
||||
Returns:
|
||||
The validated user input.
|
||||
"""
|
||||
# Main layout by chatGPT, but modified.
|
||||
while True:
|
||||
user_input = input(question).strip()
|
||||
|
||||
try:
|
||||
# Convert input to the desired type
|
||||
if accepted_type in [int, float]:
|
||||
user_input = accepted_type(user_input)
|
||||
# Validate range for numeric types
|
||||
if (min_value is not None and user_input < min_value) or (max_value is not None and user_input > max_value):
|
||||
print(f"Input must be between {min_value} and {max_value}.")
|
||||
continue
|
||||
elif accepted_type == str:
|
||||
# No conversion needed for strings
|
||||
user_input = str(user_input)
|
||||
else:
|
||||
raise ValueError(f"Unsupported type: {accepted_type}")
|
||||
|
||||
# Validate against accepted inputs if provided
|
||||
if accepted_input is not None and user_input not in accepted_input:
|
||||
print(f"Invalid input. Must be one of: {', '.join(map(str, accepted_input))}.")
|
||||
continue
|
||||
|
||||
return user_input # Input is valid
|
||||
|
||||
except ValueError:
|
||||
print(f"Invalid input. Must be of type {accepted_type.__name__}.")
|
||||
|
||||
def run(self):
|
||||
"""Run the main program."""
|
||||
self._load_or_ask_settings()
|
||||
self._get_user_settings()
|
||||
self._process()
|
||||
print("Done")
|
||||
|
||||
def main():
|
||||
app = OptimaLab35_lite()
|
||||
app.run()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -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,38 +17,40 @@ 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)
|
||||
self.actionPreview.setObjectName(u"actionPreview")
|
||||
self.actionAbout = QAction(MainWindow)
|
||||
self.actionAbout.setObjectName(u"actionAbout")
|
||||
self.actionSettings = QAction(MainWindow)
|
||||
self.actionSettings.setObjectName(u"actionSettings")
|
||||
self.centralwidget = QWidget(MainWindow)
|
||||
self.centralwidget.setObjectName(u"centralwidget")
|
||||
self.gridLayout = QGridLayout(self.centralwidget)
|
||||
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)
|
||||
|
@ -113,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)
|
||||
|
||||
|
@ -144,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)
|
||||
|
||||
|
@ -158,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)
|
||||
|
||||
|
@ -190,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)
|
||||
|
||||
|
@ -221,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)
|
||||
|
||||
|
@ -241,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)
|
||||
|
@ -344,7 +352,7 @@ class Ui_MainWindow(object):
|
|||
|
||||
self.edit_exif_button = QPushButton(self.exif_group)
|
||||
self.edit_exif_button.setObjectName(u"edit_exif_button")
|
||||
self.edit_exif_button.setEnabled(False)
|
||||
self.edit_exif_button.setEnabled(True)
|
||||
|
||||
self.horizontalLayout.addWidget(self.edit_exif_button)
|
||||
|
||||
|
@ -535,7 +543,10 @@ 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)
|
||||
|
||||
self.horizontalLayout_2.addWidget(self.dateEdit)
|
||||
|
||||
|
@ -552,17 +563,17 @@ 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.menuSettings = QMenu(self.menuBar)
|
||||
self.menuSettings.setObjectName(u"menuSettings")
|
||||
self.menuBar.setGeometry(QRect(0, 0, 450, 19))
|
||||
self.menuHelp = QMenu(self.menuBar)
|
||||
self.menuHelp.setObjectName(u"menuHelp")
|
||||
self.menuSettings = QMenu(self.menuBar)
|
||||
self.menuSettings.setObjectName(u"menuSettings")
|
||||
MainWindow.setMenuBar(self.menuBar)
|
||||
|
||||
self.menuBar.addAction(self.menuSettings.menuAction())
|
||||
self.menuBar.addAction(self.menuHelp.menuAction())
|
||||
self.menuSettings.addAction(self.actionPreview)
|
||||
self.menuHelp.addAction(self.actionAbout)
|
||||
self.menuSettings.addAction(self.actionSettings)
|
||||
|
||||
self.retranslateUi(MainWindow)
|
||||
self.rename_checkbox.toggled.connect(self.filename.setEnabled)
|
||||
|
@ -584,7 +595,6 @@ class Ui_MainWindow(object):
|
|||
self.resize_Slider.valueChanged.connect(self.resize_spinBox.setValue)
|
||||
self.exif_checkbox.toggled.connect(self.gps_groupBox.setEnabled)
|
||||
self.contrast_spinBox.valueChanged.connect(self.contrast_horizontalSlider.setValue)
|
||||
self.exif_checkbox.toggled.connect(self.edit_exif_button.setEnabled)
|
||||
self.add_date_checkBox.toggled.connect(self.dateEdit.setEnabled)
|
||||
self.jpg_quality_spinBox.valueChanged.connect(self.jpg_quality_Slider.setValue)
|
||||
self.rename_checkbox.toggled.connect(self.revert_checkbox.setEnabled)
|
||||
|
@ -601,27 +611,62 @@ class Ui_MainWindow(object):
|
|||
self.actionInfo.setText(QCoreApplication.translate("MainWindow", u"About", None))
|
||||
self.actionPreview.setText(QCoreApplication.translate("MainWindow", u"Preview image", None))
|
||||
self.actionAbout.setText(QCoreApplication.translate("MainWindow", u"About", None))
|
||||
self.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)
|
||||
self.input_folder_button.setText(QCoreApplication.translate("MainWindow", u"Input", None))
|
||||
self.output_path.setText("")
|
||||
self.output_path.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Output folder", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.input_path.setToolTip("")
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.input_path.setText("")
|
||||
self.input_path.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Input folder", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.output_folder_button.setToolTip(QCoreApplication.translate("MainWindow", u"Open a file browser to select a folder for saving images.", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.output_folder_button.setText(QCoreApplication.translate("MainWindow", u"Output", None))
|
||||
self.groupBox.setTitle(QCoreApplication.translate("MainWindow", u"Essential group", None))
|
||||
self.groupBox.setTitle(QCoreApplication.translate("MainWindow", u"Export Settings", None))
|
||||
self.quality_label_2.setText(QCoreApplication.translate("MainWindow", u"Quality", None))
|
||||
self.label_11.setText(QCoreApplication.translate("MainWindow", u"Export Format", None))
|
||||
self.optimize_checkBox.setText(QCoreApplication.translate("MainWindow", u"optimize", None))
|
||||
self.label_11.setText(QCoreApplication.translate("MainWindow", u"Format ", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.optimize_checkBox.setToolTip(QCoreApplication.translate("MainWindow", u"Recommended for web use. Enables PIL optimization to slightly reduce file size without visible quality loss. ", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.optimize_checkBox.setText(QCoreApplication.translate("MainWindow", u"Optimize", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.png_quality_Slider.setToolTip("")
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.quality_label_1.setText(QCoreApplication.translate("MainWindow", u"Quality", None))
|
||||
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.image_type.setToolTip(QCoreApplication.translate("MainWindow", u"Select export file type.", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.label_13.setToolTip(QCoreApplication.translate("MainWindow", u"Choose image scaling options. \u26a0 Upscaling large images may freeze the system depending on hardware.", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.label_13.setText(QCoreApplication.translate("MainWindow", u"Resize", None))
|
||||
self.groupBox_2.setTitle(QCoreApplication.translate("MainWindow", u"Extra stuff", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.resize_Slider.setToolTip("")
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.resize_spinBox.setToolTip("")
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.groupBox_2.setTitle(QCoreApplication.translate("MainWindow", u"Image Adjustments", None))
|
||||
self.label_9.setText(QCoreApplication.translate("MainWindow", u"Brightness", None))
|
||||
self.label_10.setText(QCoreApplication.translate("MainWindow", u"Contrast", None))
|
||||
self.grayscale_checkBox.setText(QCoreApplication.translate("MainWindow", u"Turn image to Black and White", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.grayscale_checkBox.setToolTip(QCoreApplication.translate("MainWindow", u"Converts image to Grayscale", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.grayscale_checkBox.setText(QCoreApplication.translate("MainWindow", u"Black and White Mode", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.preview_Button.setToolTip(QCoreApplication.translate("MainWindow", u"Open a preview window to see how brightness, contrast, and grayscale adjustments affect your image.", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.preview_Button.setText(QCoreApplication.translate("MainWindow", u"Preview", None))
|
||||
self.groupBox_3.setTitle(QCoreApplication.translate("MainWindow", u"Watermark", None))
|
||||
self.groupBox_3.setTitle(QCoreApplication.translate("MainWindow", u"Watermarking", None))
|
||||
self.watermark_lineEdit.setText("")
|
||||
self.watermark_lineEdit.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Enter Watermark", None))
|
||||
self.watermark_lineEdit.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Enter Watermark Text", None))
|
||||
self.label_12.setText(QCoreApplication.translate("MainWindow", u"Size", None))
|
||||
self.font_size_comboBox.setItemText(0, QCoreApplication.translate("MainWindow", u"Tiny", None))
|
||||
self.font_size_comboBox.setItemText(1, QCoreApplication.translate("MainWindow", u"Small", None))
|
||||
|
@ -629,20 +674,41 @@ class Ui_MainWindow(object):
|
|||
self.font_size_comboBox.setItemText(3, QCoreApplication.translate("MainWindow", u"Large", None))
|
||||
self.font_size_comboBox.setItemText(4, QCoreApplication.translate("MainWindow", u"Huge", None))
|
||||
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.font_size_comboBox.setToolTip(QCoreApplication.translate("MainWindow", u"Adjust the font size for the watermark. Size scales proportionally to image dimensions.", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.font_size_comboBox.setCurrentText(QCoreApplication.translate("MainWindow", u"Normal", None))
|
||||
self.rename_group.setTitle(QCoreApplication.translate("MainWindow", u"files", None))
|
||||
self.rename_group.setTitle(QCoreApplication.translate("MainWindow", u"File Naming", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.rename_checkbox.setToolTip(QCoreApplication.translate("MainWindow", u"Enable to rename all images with a new base name. '_xx' will be added at the end of each filename.", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.rename_checkbox.setText(QCoreApplication.translate("MainWindow", u"Rename", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.revert_checkbox.setToolTip(QCoreApplication.translate("MainWindow", u"Reverses the order of images. Useful for scanned film rolls where the last image (e.g., 36) is scanned first.", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.revert_checkbox.setText(QCoreApplication.translate("MainWindow", u"Revert order", None))
|
||||
self.filename.setText("")
|
||||
self.filename.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Enter file name", None))
|
||||
self.filename.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Enter new File Names", None))
|
||||
self.start_button.setText(QCoreApplication.translate("MainWindow", u"Convert", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.insert_exif_Button.setToolTip(QCoreApplication.translate("MainWindow", u"Insert EXIF without modifying images.", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.insert_exif_Button.setText(QCoreApplication.translate("MainWindow", u"Insert Exif", None))
|
||||
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_1), QCoreApplication.translate("MainWindow", u"Main", None))
|
||||
self.exif_group.setTitle(QCoreApplication.translate("MainWindow", u"EXIF EXPERIMENTAL", None))
|
||||
self.exif_checkbox.setText(QCoreApplication.translate("MainWindow", u"own exif", None))
|
||||
self.exif_copy_checkBox.setText(QCoreApplication.translate("MainWindow", u"copy exif", None))
|
||||
self.edit_exif_button.setText(QCoreApplication.translate("MainWindow", u"Edit Exif", None))
|
||||
self.exif_options_group.setTitle(QCoreApplication.translate("MainWindow", u"Must", None))
|
||||
self.exif_group.setTitle(QCoreApplication.translate("MainWindow", u"EXIF Settings", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.exif_checkbox.setToolTip(QCoreApplication.translate("MainWindow", u"Add your own EXIF data to the images with the selection from below.", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.exif_checkbox.setText(QCoreApplication.translate("MainWindow", u"Custom EXIF", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.exif_copy_checkBox.setToolTip(QCoreApplication.translate("MainWindow", u"Copy EXIF from the input data.", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.exif_copy_checkBox.setText(QCoreApplication.translate("MainWindow", u"Copy EXIF", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.edit_exif_button.setToolTip(QCoreApplication.translate("MainWindow", u"Open the EXIF Editor to add or remove metadata entries, making them available for selection.", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.edit_exif_button.setText(QCoreApplication.translate("MainWindow", u"EXIF editor", None))
|
||||
self.exif_options_group.setTitle(QCoreApplication.translate("MainWindow", u"Essential EXIF Info", None))
|
||||
self.label_7.setText(QCoreApplication.translate("MainWindow", u"Artist", None))
|
||||
self.label_4.setText(QCoreApplication.translate("MainWindow", u"ISO", None))
|
||||
self.label_6.setText(QCoreApplication.translate("MainWindow", u"Scanner", None))
|
||||
|
@ -653,16 +719,28 @@ class Ui_MainWindow(object):
|
|||
self.make_comboBox.setPlaceholderText("")
|
||||
self.label_3.setText(QCoreApplication.translate("MainWindow", u"Model", None))
|
||||
self.label_8.setText(QCoreApplication.translate("MainWindow", u"Copyright", None))
|
||||
self.gps_groupBox.setTitle(QCoreApplication.translate("MainWindow", u"GPS", None))
|
||||
self.gps_checkBox.setText(QCoreApplication.translate("MainWindow", u"add gps", None))
|
||||
self.gps_groupBox.setTitle(QCoreApplication.translate("MainWindow", u"GPS Coordinates", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.gps_checkBox.setToolTip(QCoreApplication.translate("MainWindow", u"From a Homepage like latlong.net", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.gps_checkBox.setText(QCoreApplication.translate("MainWindow", u"Enable GPS Data", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.lat_lineEdit.setToolTip(QCoreApplication.translate("MainWindow", u"Format: xx.xxxxxx (e.g., 57.618520)", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.lat_lineEdit.setText("")
|
||||
self.lat_lineEdit.setPlaceholderText(QCoreApplication.translate("MainWindow", u"xx.xxxxxx latitude", None))
|
||||
self.lat_lineEdit.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Latitude", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.long_lineEdit.setToolTip(QCoreApplication.translate("MainWindow", u"Format: xx.xxxxxx (e.g., -13.779602)", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.long_lineEdit.setText("")
|
||||
self.long_lineEdit.setPlaceholderText(QCoreApplication.translate("MainWindow", u"xx.xxxxxx longitude", None))
|
||||
self.date_groupBox.setTitle(QCoreApplication.translate("MainWindow", u"Optional", None))
|
||||
self.add_date_checkBox.setText(QCoreApplication.translate("MainWindow", u"add date", None))
|
||||
self.long_lineEdit.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Longitude", None))
|
||||
self.date_groupBox.setTitle(QCoreApplication.translate("MainWindow", u"Original Capture Data and Time", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.add_date_checkBox.setToolTip(QCoreApplication.translate("MainWindow", u"Adds \"Date and Time (original)\" to the image, i.e. when the picture was taking.", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.add_date_checkBox.setText(QCoreApplication.translate("MainWindow", u"Enable Timestamp", None))
|
||||
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), QCoreApplication.translate("MainWindow", u"EXIF", None))
|
||||
self.menuSettings.setTitle(QCoreApplication.translate("MainWindow", u"Settings", None))
|
||||
self.menuHelp.setTitle(QCoreApplication.translate("MainWindow", u"Help", None))
|
||||
self.menuSettings.setTitle(QCoreApplication.translate("MainWindow", u"Settings", None))
|
||||
# retranslateUi
|
||||
|
||||
|
|
|
@ -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">
|
||||
|
@ -63,6 +66,9 @@
|
|||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Open a file browser to select a folder for loading images.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string extracomment="Comment?">Input</string>
|
||||
</property>
|
||||
|
@ -83,6 +89,9 @@
|
|||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLineEdit" name="input_path">
|
||||
<property name="toolTip">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
|
@ -99,6 +108,9 @@
|
|||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Open a file browser to select a folder for saving images.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Output</string>
|
||||
</property>
|
||||
|
@ -116,7 +128,7 @@
|
|||
</size>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Essential group</string>
|
||||
<string>Export Settings</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_4">
|
||||
<item row="4" column="0">
|
||||
|
@ -142,19 +154,25 @@
|
|||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_11">
|
||||
<property name="text">
|
||||
<string>Export Format</string>
|
||||
<string>Format </string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="3">
|
||||
<widget class="QCheckBox" name="optimize_checkBox">
|
||||
<property name="toolTip">
|
||||
<string>Recommended for web use. Enables PIL optimization to slightly reduce file size without visible quality loss. </string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>optimize</string>
|
||||
<string>Optimize</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="2">
|
||||
<widget class="QSlider" name="png_quality_Slider">
|
||||
<property name="toolTip">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
|
@ -168,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>
|
||||
|
@ -197,6 +218,9 @@
|
|||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QComboBox" name="image_type">
|
||||
<property name="toolTip">
|
||||
<string>Select export file type.</string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">jpg</string>
|
||||
|
@ -226,12 +250,18 @@
|
|||
<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>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_13">
|
||||
<property name="toolTip">
|
||||
<string>Choose image scaling options. ⚠ Upscaling large images may freeze the system depending on hardware.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Resize</string>
|
||||
</property>
|
||||
|
@ -239,6 +269,9 @@
|
|||
</item>
|
||||
<item row="5" column="2">
|
||||
<widget class="QSlider" name="resize_Slider">
|
||||
<property name="toolTip">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
|
@ -249,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>
|
||||
|
@ -258,6 +294,9 @@
|
|||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
|
@ -287,7 +326,7 @@
|
|||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Extra stuff</string>
|
||||
<string>Image Adjustments</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="1" column="0">
|
||||
|
@ -306,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>
|
||||
|
@ -358,19 +400,28 @@
|
|||
<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>
|
||||
<item row="5" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="grayscale_checkBox">
|
||||
<property name="toolTip">
|
||||
<string>Converts image to Grayscale</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Turn image to Black and White</string>
|
||||
<string>Black and White Mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="2">
|
||||
<widget class="QPushButton" name="preview_Button">
|
||||
<property name="toolTip">
|
||||
<string>Open a preview window to see how brightness, contrast, and grayscale adjustments affect your image.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Preview</string>
|
||||
</property>
|
||||
|
@ -384,8 +435,14 @@
|
|||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>400</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Watermark</string>
|
||||
<string>Watermarking</string>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>false</bool>
|
||||
|
@ -406,7 +463,7 @@
|
|||
<string/>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>Enter Watermark</string>
|
||||
<string>Enter Watermark Text</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -419,6 +476,9 @@
|
|||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="font_size_comboBox">
|
||||
<property name="toolTip">
|
||||
<string>Adjust the font size for the watermark. Size scales proportionally to image dimensions.</string>
|
||||
</property>
|
||||
<property name="currentText">
|
||||
<string>Normal</string>
|
||||
</property>
|
||||
|
@ -464,11 +524,14 @@
|
|||
</size>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>files</string>
|
||||
<string>File Naming</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_6">
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="rename_checkbox">
|
||||
<property name="toolTip">
|
||||
<string>Enable to rename all images with a new base name. '_xx' will be added at the end of each filename.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Rename</string>
|
||||
</property>
|
||||
|
@ -479,6 +542,9 @@
|
|||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Reverses the order of images. Useful for scanned film rolls where the last image (e.g., 36) is scanned first.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Revert order</string>
|
||||
</property>
|
||||
|
@ -493,7 +559,7 @@
|
|||
<string/>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>Enter file name</string>
|
||||
<string>Enter new File Names</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -534,6 +600,9 @@
|
|||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Insert EXIF without modifying images.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Insert Exif</string>
|
||||
</property>
|
||||
|
@ -552,7 +621,7 @@
|
|||
<item>
|
||||
<widget class="QGroupBox" name="exif_group">
|
||||
<property name="title">
|
||||
<string>EXIF EXPERIMENTAL</string>
|
||||
<string>EXIF Settings</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
|
@ -560,25 +629,34 @@
|
|||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Add your own EXIF data to the images with the selection from below.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>own exif</string>
|
||||
<string>Custom EXIF</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="exif_copy_checkBox">
|
||||
<property name="toolTip">
|
||||
<string>Copy EXIF from the input data.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>copy exif</string>
|
||||
<string>Copy EXIF</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="edit_exif_button">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Open the EXIF Editor to add or remove metadata entries, making them available for selection.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Edit Exif</string>
|
||||
<string>EXIF editor</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -591,7 +669,7 @@
|
|||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Must</string>
|
||||
<string>Essential EXIF Info</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_7">
|
||||
<item row="3" column="0">
|
||||
|
@ -738,13 +816,16 @@
|
|||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>GPS</string>
|
||||
<string>GPS Coordinates</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="gps_checkBox">
|
||||
<property name="toolTip">
|
||||
<string>From a Homepage like latlong.net</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>add gps</string>
|
||||
<string>Enable GPS Data</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -753,6 +834,9 @@
|
|||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Format: xx.xxxxxx (e.g., 57.618520)</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
|
@ -760,7 +844,7 @@
|
|||
<number>8</number>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>xx.xxxxxx latitude</string>
|
||||
<string>Latitude</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -769,6 +853,9 @@
|
|||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Format: xx.xxxxxx (e.g., -13.779602)</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
|
@ -776,7 +863,7 @@
|
|||
<number>8</number>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>xx.xxxxxx longitude</string>
|
||||
<string>Longitude</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -789,13 +876,16 @@
|
|||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Optional</string>
|
||||
<string>Original Capture Data and Time</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="add_date_checkBox">
|
||||
<property name="toolTip">
|
||||
<string>Adds "Date and Time (original)" to the image, i.e. when the picture was taking.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>add date</string>
|
||||
<string>Enable Timestamp</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -806,13 +896,30 @@
|
|||
</property>
|
||||
<property name="dateTime">
|
||||
<datetime>
|
||||
<hour>0</hour>
|
||||
<hour>22</hour>
|
||||
<minute>0</minute>
|
||||
<second>0</second>
|
||||
<year>2025</year>
|
||||
<year>2024</year>
|
||||
<month>12</month>
|
||||
<day>31</day>
|
||||
</datetime>
|
||||
</property>
|
||||
<property name="maximumDate">
|
||||
<date>
|
||||
<year>2038</year>
|
||||
<month>12</month>
|
||||
<day>31</day>
|
||||
</date>
|
||||
</property>
|
||||
<property name="minimumDate">
|
||||
<date>
|
||||
<year>1970</year>
|
||||
<month>1</month>
|
||||
<day>1</day>
|
||||
</datetime>
|
||||
</date>
|
||||
</property>
|
||||
<property name="calendarPopup">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -831,22 +938,22 @@
|
|||
<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="menuSettings">
|
||||
<property name="title">
|
||||
<string>Settings</string>
|
||||
</property>
|
||||
<addaction name="actionPreview"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuHelp">
|
||||
<property name="title">
|
||||
<string>Help</string>
|
||||
</property>
|
||||
<addaction name="actionAbout"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuSettings">
|
||||
<property name="title">
|
||||
<string>Settings</string>
|
||||
</property>
|
||||
<addaction name="actionSettings"/>
|
||||
</widget>
|
||||
<addaction name="menuSettings"/>
|
||||
<addaction name="menuHelp"/>
|
||||
</widget>
|
||||
|
@ -865,6 +972,11 @@
|
|||
<string>About</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionSettings">
|
||||
<property name="text">
|
||||
<string>Preferences...</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
|
@ -1172,22 +1284,6 @@
|
|||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>exif_checkbox</sender>
|
||||
<signal>toggled(bool)</signal>
|
||||
<receiver>edit_exif_button</receiver>
|
||||
<slot>setEnabled(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>134</x>
|
||||
<y>107</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>79</x>
|
||||
<y>170</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>add_date_checkBox</sender>
|
||||
<signal>toggled(bool)</signal>
|
||||
|
|
|
@ -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, 700)
|
||||
Preview_Window.resize(875, 775)
|
||||
Preview_Window.setMinimumSize(QSize(800, 700))
|
||||
self.centralwidget = QWidget(Preview_Window)
|
||||
self.centralwidget.setObjectName(u"centralwidget")
|
||||
|
@ -33,127 +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.setScaledContents(True)
|
||||
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(140, 628))
|
||||
self.widget.setMaximumSize(QSize(140, 16777215))
|
||||
self.verticalLayout_3 = QVBoxLayout(self.widget)
|
||||
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.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.widget)
|
||||
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_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_3.addWidget(self.load_Button)
|
||||
|
||||
self.widget_2 = QWidget(self.widget)
|
||||
self.widget_2.setObjectName(u"widget_2")
|
||||
self.widget_2.setMaximumSize(QSize(16777215, 120))
|
||||
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.verticalLayout_3.addWidget(self.widget_2)
|
||||
self.verticalLayout_4.addWidget(self.groupBox_2)
|
||||
|
||||
self.widget_3 = QWidget(self.widget)
|
||||
self.widget_3.setObjectName(u"widget_3")
|
||||
self.widget_3.setMaximumSize(QSize(16777215, 120))
|
||||
self.verticalLayout_2 = QVBoxLayout(self.widget_3)
|
||||
self.groupBox = QGroupBox(self.widget)
|
||||
self.groupBox.setObjectName(u"groupBox")
|
||||
self.verticalLayout_2 = QVBoxLayout(self.groupBox)
|
||||
self.verticalLayout_2.setObjectName(u"verticalLayout_2")
|
||||
self.label_2 = QLabel(self.widget_3)
|
||||
self.label_2.setObjectName(u"label_2")
|
||||
|
||||
self.verticalLayout_2.addWidget(self.label_2)
|
||||
|
||||
self.contrast_spinBox = QSpinBox(self.widget_3)
|
||||
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_2.addWidget(self.contrast_spinBox)
|
||||
|
||||
self.contrast_Slider = QSlider(self.widget_3)
|
||||
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_2.addWidget(self.contrast_Slider)
|
||||
|
||||
self.reset_contrast_Button = QPushButton(self.widget_3)
|
||||
self.reset_contrast_Button = QPushButton(self.groupBox)
|
||||
self.reset_contrast_Button.setObjectName(u"reset_contrast_Button")
|
||||
|
||||
self.verticalLayout_2.addWidget(self.reset_contrast_Button)
|
||||
|
||||
|
||||
self.verticalLayout_3.addWidget(self.widget_3)
|
||||
self.verticalLayout_4.addWidget(self.groupBox)
|
||||
|
||||
self.grayscale_checkBox = QCheckBox(self.widget)
|
||||
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_3.addWidget(self.grayscale_checkBox)
|
||||
self.gridLayout.addWidget(self.grayscale_checkBox, 0, 0, 1, 1)
|
||||
|
||||
self.update_Button = QPushButton(self.widget)
|
||||
self.update_Button.setObjectName(u"update_Button")
|
||||
|
||||
self.verticalLayout_3.addWidget(self.update_Button)
|
||||
self.verticalLayout_4.addWidget(self.groupBox_5)
|
||||
|
||||
self.widget_4 = QWidget(self.widget)
|
||||
self.widget_4.setObjectName(u"widget_4")
|
||||
self.verticalLayout_4 = QVBoxLayout(self.widget_4)
|
||||
self.verticalLayout_4.setObjectName(u"verticalLayout_4")
|
||||
self.label_3 = QLabel(self.widget_4)
|
||||
self.label_3.setObjectName(u"label_3")
|
||||
self.label_3.setScaledContents(False)
|
||||
self.label_3.setWordWrap(True)
|
||||
self.verticalSpacer = QSpacerItem(20, 219, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
|
||||
|
||||
self.verticalLayout_4.addWidget(self.label_3)
|
||||
self.verticalLayout_4.addItem(self.verticalSpacer)
|
||||
|
||||
self.checkBox = QCheckBox(self.widget_4)
|
||||
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_4.addWidget(self.label)
|
||||
|
||||
self.show_OG_Button = QPushButton(self.widget)
|
||||
self.show_OG_Button.setObjectName(u"show_OG_Button")
|
||||
|
||||
self.verticalLayout_4.addWidget(self.show_OG_Button)
|
||||
|
||||
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.LayoutDirection.LeftToRight)
|
||||
self.checkBox.setChecked(True)
|
||||
|
||||
self.verticalLayout_4.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_4.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_3.addWidget(self.widget_4)
|
||||
self.verticalLayout_4.addWidget(self.groupBox_4)
|
||||
|
||||
|
||||
self.horizontalLayout.addWidget(self.widget)
|
||||
|
@ -161,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)
|
||||
|
@ -169,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
|
||||
|
@ -176,16 +227,52 @@ 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.image_path_lineEdit.setPlaceholderText(QCoreApplication.translate("Preview_Window", u"Path to image", None))
|
||||
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.grayscale_checkBox.setText(QCoreApplication.translate("Preview_Window", u"Grayscale", None))
|
||||
self.update_Button.setText(QCoreApplication.translate("Preview_Window", u"Update preview", None))
|
||||
self.label_3.setText(QCoreApplication.translate("Preview_Window", u"Copy values to main window when closing", 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 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)
|
||||
self.checkBox.setText(QCoreApplication.translate("Preview_Window", u"Copy Values", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
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
|
||||
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>803</width>
|
||||
<height>700</height>
|
||||
<width>875</width>
|
||||
<height>775</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
|
@ -30,13 +30,13 @@
|
|||
</size>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::Box</enum>
|
||||
<enum>QFrame::Shape::Box</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="scaledContents">
|
||||
<bool>true</bool>
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -44,47 +44,52 @@
|
|||
<widget class="QWidget" name="widget" native="true">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>140</width>
|
||||
<width>160</width>
|
||||
<height>628</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>140</width>
|
||||
<width>180</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="image_path_lineEdit">
|
||||
<property name="placeholderText">
|
||||
<string>Path to image</string>
|
||||
<widget class="QGroupBox" name="groupBox_3">
|
||||
<property name="title">
|
||||
<string>File</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="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>120</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">
|
||||
|
@ -104,12 +109,18 @@
|
|||
<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>
|
||||
<item>
|
||||
<widget class="QPushButton" name="reset_brightness_Button">
|
||||
<property name="toolTip">
|
||||
<string>Click to reset the brightness to its default value (0).</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Reset</string>
|
||||
</property>
|
||||
|
@ -119,21 +130,11 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="widget_3" native="true">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>120</height>
|
||||
</size>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Contrast</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Contrast</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="contrast_spinBox">
|
||||
<property name="minimum">
|
||||
|
@ -153,12 +154,18 @@
|
|||
<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>
|
||||
<item>
|
||||
<widget class="QPushButton" name="reset_contrast_Button">
|
||||
<property name="toolTip">
|
||||
<string>Click to reset the contrast to its default value (0).</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Reset</string>
|
||||
</property>
|
||||
|
@ -168,37 +175,83 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="grayscale_checkBox">
|
||||
<property name="text">
|
||||
<widget class="QGroupBox" name="groupBox_5">
|
||||
<property name="title">
|
||||
<string>Grayscale</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="update_Button">
|
||||
<property name="text">
|
||||
<string>Update preview</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="widget_4" native="true">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_3">
|
||||
<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>Copy values to main window when closing</string>
|
||||
</property>
|
||||
<property name="scaledContents">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
<string>Black and White</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<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="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::LayoutDirection::LeftToRight</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Copy Values</string>
|
||||
</property>
|
||||
|
@ -207,13 +260,90 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<item row="4" column="0" colspan="2">
|
||||
<widget class="QPushButton" name="close_Button">
|
||||
<property name="toolTip">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Close</string>
|
||||
</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>
|
||||
|
@ -227,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>
|
||||
|
@ -299,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>
|
||||
|
|
3187
src/OptimaLab35/ui/resources_rc.py
Normal file
297
src/OptimaLab35/ui/settings_window.py
Normal file
|
@ -0,0 +1,297 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
################################################################################
|
||||
## Form generated from reading UI file 'settings_window.ui'
|
||||
##
|
||||
## Created by: Qt User Interface Compiler version 6.8.2
|
||||
##
|
||||
## WARNING! All changes made in this file will be lost when recompiling UI file!
|
||||
################################################################################
|
||||
|
||||
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
|
||||
QMetaObject, QObject, QPoint, QRect,
|
||||
QSize, QTime, QUrl, Qt)
|
||||
from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
|
||||
QFont, QFontDatabase, QGradient, QIcon,
|
||||
QImage, QKeySequence, QLinearGradient, QPainter,
|
||||
QPalette, QPixmap, QRadialGradient, QTransform)
|
||||
from PySide6.QtWidgets import (QApplication, QCheckBox, QComboBox, QGridLayout,
|
||||
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, 325)
|
||||
Settings_Window.setMinimumSize(QSize(400, 300))
|
||||
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.tabWidget.setMaximumSize(QSize(500, 500))
|
||||
self.tabWidgetPage2 = QWidget()
|
||||
self.tabWidgetPage2.setObjectName(u"tabWidgetPage2")
|
||||
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.pkg_info_groupBox)
|
||||
self.label_optima35_latestversion.setObjectName(u"label_optima35_latestversion")
|
||||
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.pkg_info_groupBox)
|
||||
self.label_latest_version.setObjectName(u"label_latest_version")
|
||||
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.pkg_info_groupBox)
|
||||
self.label.setObjectName(u"label")
|
||||
|
||||
self.gridLayout.addWidget(self.label, 1, 0, 1, 1)
|
||||
|
||||
self.label_optimalab35_latestversion = QLabel(self.pkg_info_groupBox)
|
||||
self.label_optimalab35_latestversion.setObjectName(u"label_optimalab35_latestversion")
|
||||
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.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.pkg_info_groupBox)
|
||||
self.label_optimalab35_localversion.setObjectName(u"label_optimalab35_localversion")
|
||||
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.pkg_info_groupBox)
|
||||
self.label_6.setObjectName(u"label_6")
|
||||
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.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.pkg_info_groupBox)
|
||||
self.label_optima35_localversion.setObjectName(u"label_optima35_localversion")
|
||||
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.gridLayout_2.addWidget(self.pkg_info_groupBox, 0, 0, 1, 2)
|
||||
|
||||
self.dev_widget = QWidget(self.tabWidgetPage2)
|
||||
self.dev_widget.setObjectName(u"dev_widget")
|
||||
self.horizontalLayout_2 = QHBoxLayout(self.dev_widget)
|
||||
self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
|
||||
self.check_local_Button = QPushButton(self.dev_widget)
|
||||
self.check_local_Button.setObjectName(u"check_local_Button")
|
||||
|
||||
self.horizontalLayout_2.addWidget(self.check_local_Button)
|
||||
|
||||
self.update_local_Button = QPushButton(self.dev_widget)
|
||||
self.update_local_Button.setObjectName(u"update_local_Button")
|
||||
|
||||
self.horizontalLayout_2.addWidget(self.update_local_Button)
|
||||
|
||||
|
||||
self.gridLayout_2.addWidget(self.dev_widget, 1, 0, 1, 2)
|
||||
|
||||
self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
|
||||
|
||||
self.gridLayout_2.addItem(self.verticalSpacer, 2, 0, 1, 1)
|
||||
|
||||
self.label_5 = QLabel(self.tabWidgetPage2)
|
||||
self.label_5.setObjectName(u"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")
|
||||
self.horizontalLayout = QHBoxLayout(self.widget)
|
||||
self.horizontalLayout.setObjectName(u"horizontalLayout")
|
||||
self.check_for_update_Button = QPushButton(self.widget)
|
||||
self.check_for_update_Button.setObjectName(u"check_for_update_Button")
|
||||
|
||||
self.horizontalLayout.addWidget(self.check_for_update_Button)
|
||||
|
||||
self.update_and_restart_Button = QPushButton(self.widget)
|
||||
self.update_and_restart_Button.setObjectName(u"update_and_restart_Button")
|
||||
|
||||
self.horizontalLayout.addWidget(self.update_and_restart_Button)
|
||||
|
||||
self.restart_checkBox = QCheckBox(self.widget)
|
||||
self.restart_checkBox.setObjectName(u"restart_checkBox")
|
||||
self.restart_checkBox.setChecked(True)
|
||||
|
||||
self.horizontalLayout.addWidget(self.restart_checkBox)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
Settings_Window.setCentralWidget(self.centralwidget)
|
||||
|
||||
self.retranslateUi(Settings_Window)
|
||||
self.enable_theme_checkBox.toggled.connect(self.theme_selection_comboBox.setEnabled)
|
||||
|
||||
self.tabWidget.setCurrentIndex(0)
|
||||
|
||||
|
||||
QMetaObject.connectSlotsByName(Settings_Window)
|
||||
# setupUi
|
||||
|
||||
def retranslateUi(self, Settings_Window):
|
||||
Settings_Window.setWindowTitle(QCoreApplication.translate("Settings_Window", u"Settings", 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))
|
||||
self.label_optimalab35_latestversion.setText(QCoreApplication.translate("Settings_Window", u"unknown", None))
|
||||
self.label_9.setText(QCoreApplication.translate("Settings_Window", u"Package", None))
|
||||
self.label_optimalab35_localversion.setText(QCoreApplication.translate("Settings_Window", u"0.0.0", None))
|
||||
self.label_6.setText(QCoreApplication.translate("Settings_Window", u"Local Version", None))
|
||||
self.label_2.setText(QCoreApplication.translate("Settings_Window", u"optima35", None))
|
||||
self.label_optima35_localversion.setText(QCoreApplication.translate("Settings_Window", u"0.0.0", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.check_local_Button.setToolTip(QCoreApplication.translate("Settings_Window", u"FOR DEVELOPER", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.check_local_Button.setText(QCoreApplication.translate("Settings_Window", u"Check local", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.update_local_Button.setToolTip(QCoreApplication.translate("Settings_Window", u"FOR DEVELOPER", None))
|
||||
#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)
|
||||
self.restart_checkBox.setToolTip(QCoreApplication.translate("Settings_Window", u"Restarts the app after update.", None))
|
||||
#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
|
||||
|
408
src/OptimaLab35/ui/settings_window.ui
Normal file
|
@ -0,0 +1,408 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Settings_Window</class>
|
||||
<widget class="QMainWindow" name="Settings_Window">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>325</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>400</width>
|
||||
<height>300</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<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="tabWidgetPage2">
|
||||
<attribute name="title">
|
||||
<string>Updater</string>
|
||||
</attribute>
|
||||
<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">
|
||||
<property name="text">
|
||||
<string>unknown</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QLabel" name="label_latest_version">
|
||||
<property name="text">
|
||||
<string>Latest version</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>OptimaLab35</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QLabel" name="label_optimalab35_latestversion">
|
||||
<property name="text">
|
||||
<string>unknown</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="text">
|
||||
<string>Package</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="label_optimalab35_localversion">
|
||||
<property name="text">
|
||||
<string>0.0.0</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>Local Version</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>optima35</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLabel" name="label_optima35_localversion">
|
||||
<property name="text">
|
||||
<string>0.0.0</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="QWidget" name="dev_widget" native="true">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QPushButton" name="check_local_Button">
|
||||
<property name="toolTip">
|
||||
<string>FOR DEVELOPER</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Check local</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="update_local_Button">
|
||||
<property name="toolTip">
|
||||
<string>FOR DEVELOPER</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Update local</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</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>
|
||||
<widget class="QPushButton" name="check_for_update_Button">
|
||||
<property name="text">
|
||||
<string>Check for update</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="update_and_restart_Button">
|
||||
<property name="text">
|
||||
<string>Update</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="restart_checkBox">
|
||||
<property name="toolTip">
|
||||
<string>Restarts the app after update.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Restart</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</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>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>enable_theme_checkBox</sender>
|
||||
<signal>toggled(bool)</signal>
|
||||
<receiver>theme_selection_comboBox</receiver>
|
||||
<slot>setEnabled(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>96</x>
|
||||
<y>225</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>317</x>
|
||||
<y>225</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
|
@ -1,17 +1,23 @@
|
|||
from PySide6.QtWidgets import QApplication, QDialog, QVBoxLayout, QLineEdit, QPushButton, QLabel
|
||||
# ChatGPT
|
||||
from PySide6.QtWidgets import QApplication, QDialog, QVBoxLayout, QLabel, QPushButton
|
||||
from PySide6.QtCore import Qt
|
||||
|
||||
class SimpleDialog(QDialog):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
# Set default properties
|
||||
self.setGeometry(100, 100, 300, 100)
|
||||
self.setWindowTitle("Information")
|
||||
self.setGeometry(100, 100, 400, 100) # Default size
|
||||
|
||||
# Create the layout
|
||||
layout = QVBoxLayout()
|
||||
|
||||
# Create the label for the message
|
||||
self.message_label = QLabel(self)
|
||||
self.message_label.setWordWrap(True) # Enable word wrapping
|
||||
self.message_label.setAlignment(Qt.AlignLeft | Qt.AlignTop) # Align text
|
||||
self.message_label.setMaximumWidth(400) # Set max width so it wraps text
|
||||
self.message_label.setOpenExternalLinks(True)
|
||||
|
||||
# Create the close button
|
||||
close_button = QPushButton("Close", self)
|
||||
|
@ -27,4 +33,5 @@ class SimpleDialog(QDialog):
|
|||
def show_dialog(self, title: str, message: str):
|
||||
self.setWindowTitle(title) # Set the window title
|
||||
self.message_label.setText(message) # Set the message text
|
||||
self.adjustSize() # Adjust window height dynamically based on text content
|
||||
self.exec() # Open the dialog as a modal window
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
from simple_term_menu import TerminalMenu
|
||||
|
||||
class SimpleTUI:
|
||||
"""TUI parts using library simple_term_menu"""
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def choose_menu(self, menu_title, choices):
|
||||
""" Dynamic function to display content of a list and returnes which was selected."""
|
||||
menu_options = choices
|
||||
menu = TerminalMenu(
|
||||
menu_entries = menu_options,
|
||||
title = menu_title,
|
||||
menu_cursor = "> ",
|
||||
menu_cursor_style = ("fg_gray", "bold"),
|
||||
menu_highlight_style = ("bg_gray", "fg_black"),
|
||||
cycle_cursor = True,
|
||||
clear_screen = False
|
||||
)
|
||||
menu.show()
|
||||
return menu.chosen_menu_entry
|
||||
|
||||
def multi_select_menu(self, menu_title, choices):
|
||||
""" Dynamic function to display content of a list and returnes which was selected."""
|
||||
menu_options = choices
|
||||
menu = TerminalMenu(
|
||||
menu_entries = menu_options,
|
||||
title = menu_title,
|
||||
multi_select=True,
|
||||
show_multi_select_hint=True,
|
||||
menu_cursor_style = ("fg_gray", "bold"),
|
||||
menu_highlight_style = ("bg_gray", "fg_black"),
|
||||
cycle_cursor = True,
|
||||
clear_screen = False
|
||||
)
|
||||
menu.show()
|
||||
choisen_values = menu.chosen_menu_entries
|
||||
|
||||
if choisen_values == None:
|
||||
print("Exiting...")
|
||||
exit()
|
||||
else:
|
||||
return menu.chosen_menu_entries
|
||||
|
||||
def yes_no_menu(self, message): # oh
|
||||
menu_options = ["[y] yes", "[n] no"]
|
||||
menu = TerminalMenu(
|
||||
menu_entries = menu_options,
|
||||
title = f"{message}",
|
||||
menu_cursor = "> ",
|
||||
menu_cursor_style = ("fg_red", "bold"),
|
||||
menu_highlight_style = ("bg_gray", "fg_black"),
|
||||
cycle_cursor = True,
|
||||
clear_screen = False
|
||||
)
|
||||
menu_entry_index = menu.show()
|
||||
if menu_entry_index == 0:
|
||||
return True
|
||||
elif menu_entry_index == 1:
|
||||
return False
|
|
@ -2,8 +2,12 @@ import yaml
|
|||
import os
|
||||
|
||||
class Utilities:
|
||||
def __init__(self):
|
||||
pass
|
||||
def __init__(self, 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")
|
||||
self._prepear_exif_config()
|
||||
|
||||
def read_yaml(self, yaml_file):
|
||||
try:
|
||||
|
@ -12,30 +16,38 @@ class Utilities:
|
|||
return data
|
||||
except (FileNotFoundError, PermissionError) as e:
|
||||
print(f"Error loading settings file: {e}")
|
||||
return
|
||||
return None
|
||||
|
||||
def write_yaml(self, yaml_file, data):
|
||||
try:
|
||||
with open(yaml_file, "w") as file:
|
||||
yaml.dump(data, file)
|
||||
return True
|
||||
except PermissionError as e:
|
||||
print(f"Error saving setings: {e}")
|
||||
return False
|
||||
|
||||
def program_configs(self):
|
||||
def _prepear_exif_config(self):
|
||||
"""Prepear folder for config and generate default exif if non aviable"""
|
||||
program_folder = self._ensure_program_folder_exists()
|
||||
if not os.path.isfile(f"{program_folder}/exif.yaml"):
|
||||
self._default_exif(f"{program_folder}/exif.yaml")
|
||||
if not os.path.isfile(self.exif_path):
|
||||
self.default_exif()
|
||||
|
||||
def _ensure_program_folder_exists(self):
|
||||
program_folder = os.path.expanduser("~/.config/OptimaLab35")
|
||||
print(program_folder)
|
||||
if not os.path.exists(program_folder):
|
||||
print("in not, make folder")
|
||||
os.makedirs(program_folder)
|
||||
return program_folder
|
||||
if not os.path.exists(self.folder_path):
|
||||
os.makedirs(self.folder_path)
|
||||
|
||||
def _default_exif(self, file):
|
||||
def load_settings(self):
|
||||
"""Loads settings from file, or creates default settings if missing."""
|
||||
if os.path.exists(self.settings_path):
|
||||
settings = self.read_yaml(self.settings_path)
|
||||
return settings if settings else self._default_settings()
|
||||
return self._default_settings()
|
||||
|
||||
def save_settings(self, settings):
|
||||
if not self.write_yaml(self.settings_path, settings):
|
||||
print("Error writing file")
|
||||
|
||||
def default_exif(self):
|
||||
"""Makes a default exif file."""
|
||||
print("Making default")
|
||||
def_exif = {
|
||||
|
@ -75,11 +87,23 @@ class Utilities:
|
|||
"AE-1"
|
||||
],
|
||||
"user_comment": [
|
||||
"Scanner.NORITSU-KOKI",
|
||||
"Scanner.NA"
|
||||
"Scanner: NORITSU-KOKI",
|
||||
"Scanner: NA"
|
||||
]
|
||||
}
|
||||
self.write_yaml(file, def_exif)
|
||||
self.write_yaml(self.exif_path, def_exif)
|
||||
|
||||
def _default_settings(self):
|
||||
"""Returns default settings and writes them if the settings file does not exist."""
|
||||
settings = {
|
||||
"theme": {
|
||||
"theme_pkg": False,
|
||||
"use_custom_theme": False,
|
||||
"mode": "Auto"
|
||||
}
|
||||
}
|
||||
self.write_yaml(self.settings_path, settings)
|
||||
return settings
|
||||
|
||||
def append_number_to_name(self, base_name: str, current_image: int, total_images: int, invert: bool):
|
||||
""""Returns name, combination of base_name and ending number."""
|
||||
|
@ -90,28 +114,3 @@ class Utilities:
|
|||
ending_number = current_image
|
||||
ending = f"{ending_number:0{total_digits}}"
|
||||
return f"{base_name}_{ending}"
|
||||
|
||||
def yes_no(self, str):
|
||||
"""Ask user y/n question"""
|
||||
while True:
|
||||
choice = input(f"{str} (y/n): ")
|
||||
if choice == "y":
|
||||
return True
|
||||
elif choice == "n":
|
||||
return False
|
||||
else:
|
||||
print("Not a valid option, try again.")
|
||||
|
||||
def progress_bar(self, current, total, barsize = 50):
|
||||
if current > total:
|
||||
print("\033[91mThis bar has exceeded its limits!\033[0m Maybe the current value needs some restraint?")
|
||||
return
|
||||
progress = int((barsize / total) * current)
|
||||
rest = barsize - progress
|
||||
if rest <= 2: rest = 0
|
||||
# Determine the number of digits in total
|
||||
total_digits = len(str(total))
|
||||
# Format current with leading zeros
|
||||
current_formatted = f"{current:0{total_digits}}"
|
||||
print(f"{current_formatted}|{progress * '-'}>{rest * ' '}|{total}", end="\r")
|
||||
if current == total: print("")
|
||||
|
|
BIN
src/app_resources/app-icon.png
Normal file
After Width: | Height: | Size: 49 KiB |
11
src/app_resources/net.boxyfoxy.OptimaLab35.desktop
Normal file
|
@ -0,0 +1,11 @@
|
|||
[Desktop Entry]
|
||||
Version=1.0
|
||||
Type=Application
|
||||
|
||||
Name=OptimaLab35
|
||||
Comment=A simple tool for editing images and managing metadata, designed for streamlined organization and EXIF adjustments.
|
||||
Categories=Graphics;Photography;
|
||||
|
||||
Icon=net.boxyfoxy.OptimaLab35
|
||||
Exec=OptimaLab35
|
||||
Terminal=false
|
33
src/app_resources/net.boxyfoxy.OptimaLab35.metainfo.xml
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<component type="desktop-application">
|
||||
<id>net.boxyfoxy.OptimaLab35</id>
|
||||
|
||||
<name>OptimaLab35</name>
|
||||
<summary>A simple tool for editing images and managing metadata, designed for streamlined organization and EXIF adjustments.</summary>
|
||||
|
||||
<metadata_license>CC0-1.0</metadata_license>
|
||||
<project_license>AGPL-3.0-only</project_license>
|
||||
<version>1.0.0</version>
|
||||
<release>
|
||||
<version>1.0.0</version>
|
||||
<date>2024-01-30</date>
|
||||
</release>
|
||||
<description>
|
||||
<p>
|
||||
OptimaLab35 is a image editing and metadata management tool, designed with analog photography in mind. It provides an intuitive way to modify and add EXIF data, making scanned film images easier to organize. With features tailored for efficient batch processing, it helps photographers maintain a structured archive while preserving key details about their images.
|
||||
</p>
|
||||
</description>
|
||||
|
||||
<launchable type="desktop-id">net.boxyfoxy.OptimaLab35.desktop</launchable>
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<image>https://gitlab.com/CodeByMrFinchum/OptimaLab35/-/raw/main/media/main_tab.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://gitlab.com/CodeByMrFinchum/OptimaLab35/-/raw/main/media/exif_tab.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://gitlab.com/CodeByMrFinchum/OptimaLab35/-/raw/main/media/preview_window.png</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
</component>
|
6
src/app_resources/resources.qrc
Normal file
|
@ -0,0 +1,6 @@
|
|||
<!DOCTYPE RCC>
|
||||
<RCC version="1.0">
|
||||
<qresource>
|
||||
<file>app-icon.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
3
src/generate_qrc.sh
Normal file
|
@ -0,0 +1,3 @@
|
|||
#!/bin/bash
|
||||
echo "Updating resources.qrc file and placing in ui folder..."
|
||||
pyside6-rcc app_resources/resources.qrc -o OptimaLab35/ui/resources_rc.py
|
39
src/pyproject.toml
Normal file
|
@ -0,0 +1,39 @@
|
|||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "OptimaLab35"
|
||||
dynamic = ["version"]
|
||||
authors = [{ name = "Mr Finchum" }]
|
||||
description = "User interface for optima35."
|
||||
readme = "../pip_README.md"
|
||||
requires-python = ">=3.8, <4.0"
|
||||
dependencies = [
|
||||
"optima35>=1.0.0, <2.0.0",
|
||||
"PyPiUpdater>=0.7.2, <1.0.0",
|
||||
"pyside6",
|
||||
"PyYAML",
|
||||
]
|
||||
classifiers = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Environment :: X11 Applications :: Qt",
|
||||
"Topic :: Multimedia :: Graphics :: Editors",
|
||||
"Programming Language :: Python :: 3",
|
||||
"License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)",
|
||||
"Operating System :: OS Independent",
|
||||
]
|
||||
|
||||
[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"
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["OptimaLab35"]
|
||||
|
||||
[tool.hatch.version]
|
||||
path = "OptimaLab35/__init__.py"
|
|
@ -4,3 +4,5 @@ echo "Update main window."
|
|||
pyside6-uic OptimaLab35/ui/main_window.ui -o OptimaLab35/ui/main_window.py
|
||||
echo "Update preview window."
|
||||
pyside6-uic OptimaLab35/ui/preview_window.ui -o OptimaLab35/ui/preview_window.py
|
||||
echo "Update settings window."
|
||||
pyside6-uic OptimaLab35/ui/settings_window.ui -o OptimaLab35/ui/settings_window.py
|