Compare commits
13 Commits
12f1d11ee8
...
785c987ba6
Author | SHA1 | Date | |
---|---|---|---|
785c987ba6 | |||
8ecf70bca2 | |||
eda794ac09 | |||
c36e4703d0 | |||
818c3768ad | |||
5100c9640e | |||
0a0c8f32fe | |||
f4e2b5305c | |||
11e2dea0b1 | |||
0c28572fbc | |||
653e792bfd | |||
94f325a088 | |||
ebd7fae059 |
8
.github/workflows/build.yaml
vendored
8
.github/workflows/build.yaml
vendored
@ -46,7 +46,7 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [3.9]
|
||||
python-version: [3.9, 3.13]
|
||||
os: [ubuntu-22.04, macos-13, macos-14, windows-latest]
|
||||
|
||||
steps:
|
||||
@ -70,7 +70,7 @@ jobs:
|
||||
|
||||
- name: Install linux dependencies
|
||||
run: |
|
||||
sudo apt-get update && sudo apt-get upgrade && sudo apt-get install pkg-config libicu-dev libqt5gui5 libfuse2 desktop-file-utils
|
||||
sudo apt-get update && sudo apt-get upgrade && sudo apt-get install pkg-config libicu-dev libqt6gui6 libfuse2 desktop-file-utils
|
||||
if: runner.os == 'Linux'
|
||||
|
||||
- name: Build and install PyPi packages
|
||||
@ -90,7 +90,9 @@ jobs:
|
||||
dist/binary/*.tar.gz
|
||||
dist/binary/*.dmg
|
||||
dist/binary/*.AppImage
|
||||
if: matrix.python == 3.12
|
||||
|
||||
- name: PyTest
|
||||
run: |
|
||||
python -m tox r
|
||||
python -m tox p -e py${{ matrix.python-version }}-none,py${{ matrix.python-version }}-gui,py${{ matrix.python-version }}-7z,py${{ matrix.python-version }}-cbr,py${{ matrix.python-version }}-all
|
||||
shell: bash
|
||||
|
17
.github/workflows/package.yaml
vendored
17
.github/workflows/package.yaml
vendored
@ -15,8 +15,8 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [3.9]
|
||||
os: [ubuntu-22.04, macos-13, macos-14, windows-latest]
|
||||
python-version: [3.13]
|
||||
os: [ubuntu-22.04, ubuntu-22.04-arm, macos-13, macos-14, windows-latest]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@ -39,19 +39,22 @@ jobs:
|
||||
|
||||
- name: Install linux dependencies
|
||||
run: |
|
||||
sudo apt-get update && sudo apt-get upgrade && sudo apt-get install pkg-config libicu-dev libqt5gui5 libfuse2 desktop-file-utils
|
||||
sudo apt-get update && sudo apt-get upgrade && sudo apt-get install pkg-config libicu-dev libqt6gui6 libfuse2 desktop-file-utils
|
||||
if: runner.os == 'Linux'
|
||||
|
||||
- name: Build, Install and Test PyPi packages
|
||||
run: |
|
||||
export PKG_CONFIG_PATH="/usr/local/opt/icu4c/lib/pkgconfig:/opt/homebrew/opt/icu4c/lib/pkgconfig${PKG_CONFIG_PATH+:$PKG_CONFIG_PATH}";
|
||||
export PATH="/usr/local/opt/icu4c/bin:/usr/local/opt/icu4c/sbin${PATH+:$PATH}"
|
||||
python -m tox r
|
||||
python -m tox r -m release
|
||||
python -m tox p
|
||||
|
||||
- name: Release PyPi package
|
||||
run: |
|
||||
python -m tox r -e pypi-upload
|
||||
shell: bash
|
||||
if: matrix.os == 'ubuntu-22.04'
|
||||
|
||||
- name: Get release name
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
shell: bash
|
||||
run: |
|
||||
git fetch --depth=1 origin +refs/tags/*:refs/tags/* # github is dumb
|
||||
@ -70,4 +73,4 @@ jobs:
|
||||
dist/binary/*.tar.gz
|
||||
dist/binary/*.dmg
|
||||
dist/binary/*.AppImage
|
||||
dist/*${{ fromJSON('["never", ""]')[runner.os == 'Linux'] }}.whl
|
||||
dist/*${{ fromJSON('["never", ""]')[matrix.os == 'ubuntu-22.04'] }}.whl
|
||||
|
@ -41,7 +41,7 @@ Please open a [GitHub Pull Request](https://github.com/comictagger/comictagger/p
|
||||
|
||||
Currently only python 3.9 is supported however 3.10 will probably work if you try it
|
||||
|
||||
Those on linux should install `Pillow` from the system package manager if possible and if the GUI `pyqt5` should be installed from the system package manager
|
||||
Those on linux should install `Pillow` from the system package manager if possible and if the GUI `PyQt6` should be installed from the system package manager
|
||||
|
||||
Those on macOS will need to ensure that you are using python3 in x86 mode either by installing an x86 only version of python or using the universal installer and using `python3-intel64` instead of `python3`
|
||||
|
||||
|
@ -3,14 +3,16 @@ from __future__ import annotations
|
||||
import argparse
|
||||
import os
|
||||
import pathlib
|
||||
import platform
|
||||
|
||||
try:
|
||||
import niquests as requests
|
||||
except ImportError:
|
||||
import requests
|
||||
|
||||
arch = platform.machine()
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("APPIMAGETOOL", default="build/appimagetool-x86_64.AppImage", type=pathlib.Path, nargs="?")
|
||||
parser.add_argument("APPIMAGETOOL", default=f"build/appimagetool-{arch}.AppImage", type=pathlib.Path, nargs="?")
|
||||
|
||||
opts = parser.parse_args()
|
||||
opts.APPIMAGETOOL = opts.APPIMAGETOOL.absolute()
|
||||
@ -27,7 +29,8 @@ if opts.APPIMAGETOOL.exists():
|
||||
raise SystemExit(0)
|
||||
|
||||
urlretrieve(
|
||||
"https://github.com/AppImage/appimagetool/releases/latest/download/appimagetool-x86_64.AppImage", opts.APPIMAGETOOL
|
||||
f"https://github.com/AppImage/appimagetool/releases/latest/download/appimagetool-{arch}.AppImage",
|
||||
opts.APPIMAGETOOL,
|
||||
)
|
||||
os.chmod(opts.APPIMAGETOOL, 0o0700)
|
||||
|
||||
|
@ -55,20 +55,15 @@ def Tar(tar_file: pathlib.Path, path: pathlib.Path) -> None:
|
||||
if __name__ == "__main__":
|
||||
app = "ComicTagger"
|
||||
exe = app.casefold()
|
||||
final_name = f"{app}-{__version__}-{platform.system()}-{platform.machine()}"
|
||||
if platform.system() == "Windows":
|
||||
os_version = f"win-{platform.machine()}"
|
||||
app_name = f"{exe}.exe"
|
||||
final_name = f"{app}-{__version__}-{os_version}.exe"
|
||||
exe = f"{exe}.exe"
|
||||
elif platform.system() == "Darwin":
|
||||
exe = f"{app}.app"
|
||||
ver = platform.mac_ver()
|
||||
os_version = f"osx-{ver[0]}-{ver[2]}"
|
||||
app_name = f"{app}.app"
|
||||
final_name = f"{app}-{__version__}-{os_version}"
|
||||
else:
|
||||
app_name = exe
|
||||
final_name = f"ComicTagger-{__version__}-{platform.system()}"
|
||||
final_name = f"{app}-{__version__}-macOS-{ver[0]}-{ver[2]}"
|
||||
|
||||
path = pathlib.Path(f"dist/{app_name}")
|
||||
path = pathlib.Path(f"dist/{exe}")
|
||||
binary_path = pathlib.Path("dist/binary")
|
||||
binary_path.mkdir(parents=True, exist_ok=True)
|
||||
archive_destination = binary_path / final_name
|
||||
|
@ -271,7 +271,7 @@ class RarArchiver(Archiver):
|
||||
@classmethod
|
||||
@functools.cache
|
||||
def _log_not_writeable(cls, exe: str) -> None:
|
||||
logger.warning("Unable to find a useable copy of %r, will not be able to write rar files", str)
|
||||
logger.warning("Unable to find a useable copy of %r, will not be able to write rar files", exe)
|
||||
|
||||
def is_writable(self) -> bool:
|
||||
return bool(self._writeable and bool(self.exe and (os.path.exists(self.exe) or shutil.which(self.exe))))
|
||||
|
@ -9,102 +9,13 @@ import zipfile
|
||||
from typing import cast
|
||||
|
||||
import chardet
|
||||
from zipremove import ZipFile
|
||||
|
||||
from comicapi.archivers import Archiver
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ZipFile(zipfile.ZipFile):
|
||||
|
||||
def remove(self, zinfo_or_arcname): # type: ignore
|
||||
"""Remove a member from the archive."""
|
||||
|
||||
if self.mode not in ("w", "x", "a"):
|
||||
raise ValueError("remove() requires mode 'w', 'x', or 'a'")
|
||||
if not self.fp:
|
||||
raise ValueError("Attempt to write to ZIP archive that was already closed")
|
||||
if self._writing: # type: ignore[attr-defined]
|
||||
raise ValueError("Can't write to ZIP archive while an open writing handle exists")
|
||||
|
||||
# Make sure we have an existing info object
|
||||
if isinstance(zinfo_or_arcname, zipfile.ZipInfo):
|
||||
zinfo = zinfo_or_arcname
|
||||
# make sure zinfo exists
|
||||
if zinfo not in self.filelist:
|
||||
raise KeyError("There is no item %r in the archive" % zinfo_or_arcname)
|
||||
else:
|
||||
# get the info object
|
||||
zinfo = self.getinfo(zinfo_or_arcname)
|
||||
|
||||
return self._remove_members({zinfo})
|
||||
|
||||
def _remove_members(self, members, *, remove_physical=True, chunk_size=2**20): # type: ignore
|
||||
"""Remove members in a zip file.
|
||||
All members (as zinfo) should exist in the zip; otherwise the zip file
|
||||
will erroneously end in an inconsistent state.
|
||||
"""
|
||||
fp = self.fp
|
||||
assert fp
|
||||
entry_offset = 0
|
||||
member_seen = False
|
||||
|
||||
# get a sorted filelist by header offset, in case the dir order
|
||||
# doesn't match the actual entry order
|
||||
filelist = sorted(self.filelist, key=lambda x: x.header_offset)
|
||||
for i in range(len(filelist)):
|
||||
info = filelist[i]
|
||||
is_member = info in members
|
||||
|
||||
if not (member_seen or is_member):
|
||||
continue
|
||||
|
||||
# get the total size of the entry
|
||||
try:
|
||||
offset = filelist[i + 1].header_offset
|
||||
except IndexError:
|
||||
offset = self.start_dir
|
||||
entry_size = offset - info.header_offset
|
||||
|
||||
if is_member:
|
||||
member_seen = True
|
||||
entry_offset += entry_size
|
||||
|
||||
# update caches
|
||||
self.filelist.remove(info)
|
||||
try:
|
||||
del self.NameToInfo[info.filename]
|
||||
except KeyError:
|
||||
pass
|
||||
continue
|
||||
|
||||
# update the header and move entry data to the new position
|
||||
if remove_physical:
|
||||
old_header_offset = info.header_offset
|
||||
info.header_offset -= entry_offset
|
||||
read_size = 0
|
||||
while read_size < entry_size:
|
||||
fp.seek(old_header_offset + read_size)
|
||||
data = fp.read(min(entry_size - read_size, chunk_size))
|
||||
fp.seek(info.header_offset + read_size)
|
||||
fp.write(data)
|
||||
fp.flush()
|
||||
read_size += len(data)
|
||||
|
||||
# Avoid missing entry if entries have a duplicated name.
|
||||
# Reverse the order as NameToInfo normally stores the last added one.
|
||||
for info in reversed(self.filelist):
|
||||
self.NameToInfo.setdefault(info.filename, info)
|
||||
|
||||
# update state
|
||||
if remove_physical:
|
||||
self.start_dir -= entry_offset
|
||||
self._didModify = True
|
||||
|
||||
# seek to the start of the central dir
|
||||
fp.seek(self.start_dir)
|
||||
|
||||
|
||||
class ZipArchiver(Archiver):
|
||||
"""ZIP implementation"""
|
||||
|
||||
@ -149,7 +60,7 @@ class ZipArchiver(Archiver):
|
||||
try:
|
||||
with ZipFile(self.path, mode="a", allowZip64=True, compression=zipfile.ZIP_DEFLATED) as zf:
|
||||
if archive_file in files:
|
||||
zf.remove(archive_file)
|
||||
zf.repack([zf.remove(archive_file)])
|
||||
return True
|
||||
except (zipfile.BadZipfile, OSError) as e:
|
||||
logger.error("Error writing zip archive [%s]: %s :: %s", e, self.path, archive_file)
|
||||
@ -163,7 +74,7 @@ class ZipArchiver(Archiver):
|
||||
# now just add the archive file as a new one
|
||||
with ZipFile(self.path, mode="a", allowZip64=True, compression=zipfile.ZIP_DEFLATED) as zf:
|
||||
if archive_file in files:
|
||||
zf.remove(archive_file)
|
||||
zf.repack([zf.remove(archive_file)])
|
||||
zf.writestr(archive_file, data)
|
||||
return True
|
||||
except (zipfile.BadZipfile, OSError) as e:
|
||||
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
import logging
|
||||
import pathlib
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets, uic
|
||||
from PyQt6 import QtCore, QtGui, QtWidgets, uic
|
||||
|
||||
from comictaggerlib.ui import ui_path
|
||||
|
||||
@ -35,7 +35,7 @@ class ApplicationLogWindow(QtWidgets.QDialog):
|
||||
self.log_handler.qlog.connect(self.textEdit.append)
|
||||
|
||||
f = QtGui.QFont("menlo")
|
||||
f.setStyleHint(QtGui.QFont.Monospace)
|
||||
f.setStyleHint(QtGui.QFont.StyleHint.Monospace)
|
||||
self.setFont(f)
|
||||
self._button = QtWidgets.QPushButton(self)
|
||||
self._button.setText("Test Me")
|
||||
|
@ -20,7 +20,7 @@ import logging
|
||||
import os
|
||||
from typing import Callable
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets, uic
|
||||
from PyQt6 import QtCore, QtGui, QtWidgets, uic
|
||||
|
||||
from comicapi.comicarchive import ComicArchive, tags
|
||||
from comicapi.genericmetadata import GenericMetadata
|
||||
|
@ -18,7 +18,7 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from PyQt5 import QtCore, QtWidgets, uic
|
||||
from PyQt6 import QtCore, QtWidgets, uic
|
||||
|
||||
from comictaggerlib.coverimagewidget import CoverImageWidget
|
||||
from comictaggerlib.ui import ui_path
|
||||
|
@ -18,7 +18,7 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from PyQt5 import QtCore, QtWidgets, uic
|
||||
from PyQt6 import QtCore, QtWidgets, uic
|
||||
|
||||
from comictaggerlib.ctsettings import ct_ns
|
||||
from comictaggerlib.ui import ui_path
|
||||
|
@ -590,9 +590,6 @@ class CLI:
|
||||
self.output(f"Processing {utils.path_to_short_str(ca.path)}...")
|
||||
|
||||
md, tags_read = self.create_local_metadata(ca, self.config.Runtime_Options__tags_read)
|
||||
if md.issue is None or md.issue == "":
|
||||
if self.config.Auto_Tag__assume_issue_one:
|
||||
md.issue = "1"
|
||||
|
||||
matches: list[IssueResult] = []
|
||||
# now, search online
|
||||
@ -629,11 +626,15 @@ class CLI:
|
||||
return res, match_results
|
||||
|
||||
else:
|
||||
qt_md = self.try_quick_tag(ca, md)
|
||||
query_md = md.copy()
|
||||
qt_md = self.try_quick_tag(ca, query_md)
|
||||
if query_md.issue is None or query_md.issue == "":
|
||||
if self.config.Auto_Tag__assume_issue_one:
|
||||
query_md.issue = "1"
|
||||
if qt_md is None or qt_md.is_empty:
|
||||
if qt_md is not None:
|
||||
self.output("Failed to find match via quick tag")
|
||||
ct_md, matches, res, match_results = self.normal_tag(ca, tags_read, md, match_results) # type: ignore[assignment]
|
||||
ct_md, matches, res, match_results = self.normal_tag(ca, tags_read, query_md, match_results) # type: ignore[assignment]
|
||||
if res is not None:
|
||||
return res, match_results
|
||||
else:
|
||||
|
@ -1,4 +1,4 @@
|
||||
"""A PyQt5 widget to display cover images
|
||||
"""A PyQt6 widget to display cover images
|
||||
|
||||
Display cover images from either a local archive, or from comic source metadata.
|
||||
TODO: This should be re-factored using subclasses!
|
||||
@ -23,7 +23,7 @@ from __future__ import annotations
|
||||
import logging
|
||||
import pathlib
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets, uic
|
||||
from PyQt6 import QtCore, QtGui, QtWidgets, uic
|
||||
|
||||
from comicapi.comicarchive import ComicArchive
|
||||
from comictaggerlib.imagefetcher import ImageFetcher
|
||||
|
@ -20,7 +20,7 @@ import logging
|
||||
import operator
|
||||
|
||||
import natsort
|
||||
from PyQt5 import QtCore, QtWidgets, uic
|
||||
from PyQt6 import QtCore, QtWidgets, uic
|
||||
|
||||
from comicapi import utils
|
||||
from comicapi.genericmetadata import Credit
|
||||
|
@ -194,7 +194,7 @@ def parse_metadata_from_string(mdstr: str) -> GenericMetadata:
|
||||
else:
|
||||
value = t(value)
|
||||
except (ValueError, TypeError):
|
||||
raise argparse.ArgumentTypeError(f"Invalid syntax for tag '{key}': {value}")
|
||||
raise argparse.ArgumentTypeError(f"Invalid syntax for tag {key!r}: {value!r}")
|
||||
return value
|
||||
|
||||
md = GenericMetadata()
|
||||
@ -240,6 +240,8 @@ def parse_metadata_from_string(mdstr: str) -> GenericMetadata:
|
||||
else:
|
||||
raise argparse.ArgumentTypeError(f"'{key}' is not a valid tag name")
|
||||
md.is_empty = empty
|
||||
except argparse.ArgumentTypeError as e:
|
||||
raise e
|
||||
except Exception as e:
|
||||
logger.exception("Unable to read metadata from the commandline '%s'", mdstr)
|
||||
raise Exception("Unable to read metadata from the commandline") from e
|
||||
|
@ -18,7 +18,7 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from PyQt5 import QtCore, QtWidgets, uic
|
||||
from PyQt6 import QtCore, QtWidgets, uic
|
||||
|
||||
from comictaggerlib.ui import ui_path
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
"""A PyQt5 widget for managing list of comic archive files"""
|
||||
"""A PyQt6 widget for managing list of comic archive files"""
|
||||
|
||||
#
|
||||
# Copyright 2012-2014 ComicTagger Authors
|
||||
@ -22,7 +22,7 @@ import pathlib
|
||||
import platform
|
||||
from typing import Callable, cast
|
||||
|
||||
from PyQt5 import QtCore, QtWidgets, uic
|
||||
from PyQt6 import QtCore, QtGui, QtWidgets, uic
|
||||
|
||||
from comicapi import utils
|
||||
from comicapi.comicarchive import ComicArchive
|
||||
@ -64,9 +64,9 @@ class FileSelectionList(QtWidgets.QWidget):
|
||||
self.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.ActionsContextMenu)
|
||||
self.dirty_flag = False
|
||||
|
||||
select_all_action = QtWidgets.QAction("Select All", self)
|
||||
remove_action = QtWidgets.QAction("Remove Selected Items", self)
|
||||
self.separator = QtWidgets.QAction("", self)
|
||||
select_all_action = QtGui.QAction("Select All", self)
|
||||
remove_action = QtGui.QAction("Remove Selected Items", self)
|
||||
self.separator = QtGui.QAction("", self)
|
||||
self.separator.setSeparator(True)
|
||||
|
||||
select_all_action.setShortcut("Ctrl+A")
|
||||
@ -86,14 +86,14 @@ class FileSelectionList(QtWidgets.QWidget):
|
||||
|
||||
def get_sorting(self) -> tuple[int, int]:
|
||||
col = self.twList.horizontalHeader().sortIndicatorSection()
|
||||
order = self.twList.horizontalHeader().sortIndicatorOrder()
|
||||
order = self.twList.horizontalHeader().sortIndicatorOrder().value
|
||||
return int(col), int(order)
|
||||
|
||||
def set_sorting(self, col: int, order: QtCore.Qt.SortOrder) -> None:
|
||||
self.twList.horizontalHeader().setSortIndicator(col, order)
|
||||
|
||||
def add_app_action(self, action: QtWidgets.QAction) -> None:
|
||||
self.insertAction(QtWidgets.QAction(), action)
|
||||
def add_app_action(self, action: QtGui.QAction) -> None:
|
||||
self.insertAction(QtGui.QAction(), action)
|
||||
|
||||
def set_modified_flag(self, modified: bool) -> None:
|
||||
self.dirty_flag = modified
|
||||
|
@ -2,11 +2,11 @@
|
||||
|
||||
# Resource object code
|
||||
#
|
||||
# Created by: The Resource Compiler for PyQt5 (Qt v5.15.11)
|
||||
# Created by: The Resource Compiler for PyQt6 (Qt v5.15.11)
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from PyQt5 import QtCore
|
||||
from PyQt6 import QtCore
|
||||
|
||||
qt_resource_data = b"\
|
||||
\x00\x00\x16\x0b\
|
||||
|
@ -16,7 +16,7 @@ from comictalker.comictalker import ComicTalker
|
||||
logger = logging.getLogger("comictagger")
|
||||
try:
|
||||
qt_available = True
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
from PyQt6 import QtCore, QtGui, QtWidgets
|
||||
|
||||
def show_exception_box(log_msg: str) -> None:
|
||||
"""Checks if a QApplication instance is available and shows a messagebox with the exception message.
|
||||
@ -70,7 +70,7 @@ try:
|
||||
|
||||
try:
|
||||
# needed here to initialize QWebEngine
|
||||
from PyQt5.QtWebEngineWidgets import QWebEngineView # noqa: F401
|
||||
from PyQt6.QtWebEngineWidgets import QWebEngineView # noqa: F401
|
||||
|
||||
qt_webengine_available = True
|
||||
except ImportError:
|
||||
@ -81,7 +81,7 @@ try:
|
||||
|
||||
# Handles "Open With" from Finder on macOS
|
||||
def event(self, event: QtCore.QEvent) -> bool:
|
||||
if event.type() == QtCore.QEvent.FileOpen:
|
||||
if event.type() == QtCore.QEvent.Type.FileOpen:
|
||||
logger.info("file open recieved: %s", event.url().toLocalFile())
|
||||
self.openFileRequest.emit(event.url())
|
||||
return True
|
||||
|
@ -33,7 +33,7 @@ except ImportError:
|
||||
from comictaggerlib import ctversion
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from PyQt5 import QtCore, QtNetwork
|
||||
from PyQt6 import QtCore, QtNetwork
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -57,7 +57,7 @@ class ImageFetcher:
|
||||
|
||||
if self.qt_available:
|
||||
try:
|
||||
from PyQt5 import QtNetwork
|
||||
from PyQt6 import QtNetwork
|
||||
|
||||
self.qt_available = True
|
||||
except ImportError:
|
||||
@ -99,7 +99,7 @@ class ImageFetcher:
|
||||
return image_data
|
||||
|
||||
if self.qt_available:
|
||||
from PyQt5 import QtCore, QtNetwork
|
||||
from PyQt6 import QtCore, QtNetwork
|
||||
|
||||
# if we found it, just emit the signal asap
|
||||
if image_data:
|
||||
|
@ -19,7 +19,7 @@ from __future__ import annotations
|
||||
import logging
|
||||
import platform
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets, sip, uic
|
||||
from PyQt6 import QtCore, QtGui, QtWidgets, sip, uic
|
||||
|
||||
from comictaggerlib.ui import ui_path
|
||||
|
||||
@ -82,7 +82,7 @@ class ImagePopup(QtWidgets.QDialog):
|
||||
screen_size.width(),
|
||||
screen_size.height(),
|
||||
QtCore.Qt.AspectRatioMode.IgnoreAspectRatio,
|
||||
QtCore.Qt.SmoothTransformation,
|
||||
QtCore.Qt.TransformationMode.SmoothTransformation,
|
||||
)
|
||||
self.setMask(self.clientBgPixmap.mask())
|
||||
|
||||
@ -104,7 +104,10 @@ class ImagePopup(QtWidgets.QDialog):
|
||||
if self.imagePixmap.width() > win_w or self.imagePixmap.height() > win_h:
|
||||
# scale the pixmap to fit in the frame
|
||||
display_pixmap = self.imagePixmap.scaled(
|
||||
win_w, win_h, QtCore.Qt.AspectRatioMode.KeepAspectRatio, QtCore.Qt.SmoothTransformation
|
||||
win_w,
|
||||
win_h,
|
||||
QtCore.Qt.AspectRatioMode.KeepAspectRatio,
|
||||
QtCore.Qt.TransformationMode.SmoothTransformation,
|
||||
)
|
||||
self.lblImage.setPixmap(display_pixmap)
|
||||
else:
|
||||
|
@ -18,7 +18,7 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets, uic
|
||||
from PyQt6 import QtCore, QtGui, QtWidgets, uic
|
||||
|
||||
from comicapi.genericmetadata import GenericMetadata
|
||||
from comicapi.issuestring import IssueString
|
||||
|
@ -18,7 +18,7 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from PyQt5 import QtCore, QtWidgets, uic
|
||||
from PyQt6 import QtCore, QtWidgets, uic
|
||||
|
||||
from comictaggerlib.ui import qtutils, ui_path
|
||||
|
||||
|
@ -301,7 +301,7 @@ class App:
|
||||
return gui.open_tagger_window(self.talkers, self.config, error)
|
||||
except ImportError:
|
||||
self.config[0].Runtime_Options__no_gui = True
|
||||
logger.warning("PyQt5 is not available. ComicTagger is limited to command-line mode.")
|
||||
logger.warning("PyQt6 is not available. ComicTagger is limited to command-line mode.")
|
||||
|
||||
# GUI mode is not available or CLI mode was requested
|
||||
if error and error[1]:
|
||||
|
@ -19,7 +19,7 @@ from __future__ import annotations
|
||||
import logging
|
||||
import os
|
||||
|
||||
from PyQt5 import QtCore, QtWidgets, uic
|
||||
from PyQt6 import QtCore, QtWidgets, uic
|
||||
|
||||
from comicapi.comicarchive import ComicArchive
|
||||
from comictaggerlib.coverimagewidget import CoverImageWidget
|
||||
|
@ -1,4 +1,4 @@
|
||||
"""A PyQt5 dialog to show a message and let the user check a box
|
||||
"""A PyQt6 dialog to show a message and let the user check a box
|
||||
|
||||
Example usage:
|
||||
|
||||
@ -29,7 +29,7 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from PyQt5 import QtCore, QtWidgets
|
||||
from PyQt6 import QtCore, QtWidgets
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -18,7 +18,7 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets, uic
|
||||
from PyQt6 import QtCore, QtGui, QtWidgets, uic
|
||||
|
||||
from comicapi.comicarchive import ComicArchive
|
||||
from comicapi.genericmetadata import GenericMetadata
|
||||
|
@ -1,4 +1,4 @@
|
||||
"""A PyQt5 widget for editing the page list info"""
|
||||
"""A PyQt6 widget for editing the page list info"""
|
||||
|
||||
#
|
||||
# Copyright 2012-2014 ComicTagger Authors
|
||||
@ -18,7 +18,7 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from PyQt5 import QtCore, QtWidgets, uic
|
||||
from PyQt6 import QtCore, QtGui, QtWidgets, uic
|
||||
|
||||
from comicapi.comicarchive import ComicArchive, tags
|
||||
from comicapi.genericmetadata import GenericMetadata, PageMetadata, PageType
|
||||
@ -145,7 +145,7 @@ class PageListEditor(QtWidgets.QWidget):
|
||||
if show_shortcut:
|
||||
text = text + " (" + shortcut + ")"
|
||||
self.cbPageType.addItem(text, user_data)
|
||||
action_item = QtWidgets.QAction(shortcut, self)
|
||||
action_item = QtGui.QAction(shortcut, self)
|
||||
action_item.triggered.connect(lambda: self.select_page_type_item(self.cbPageType.findData(user_data)))
|
||||
action_item.setShortcut(shortcut)
|
||||
self.addAction(action_item)
|
||||
|
@ -18,7 +18,7 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from PyQt5 import QtCore
|
||||
from PyQt6 import QtCore
|
||||
|
||||
from comicapi.comicarchive import ComicArchive
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
"""A PyQt5 dialog to show ID log and progress"""
|
||||
"""A PyQt6 dialog to show ID log and progress"""
|
||||
|
||||
#
|
||||
# Copyright 2012-2014 ComicTagger Authors
|
||||
@ -18,7 +18,7 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from PyQt5 import QtCore, QtWidgets, uic
|
||||
from PyQt6 import QtCore, QtWidgets, uic
|
||||
|
||||
from comictaggerlib.ui import ui_path
|
||||
|
||||
|
@ -19,7 +19,7 @@ from __future__ import annotations
|
||||
import logging
|
||||
|
||||
import settngs
|
||||
from PyQt5 import QtCore, QtWidgets, uic
|
||||
from PyQt6 import QtCore, QtWidgets, uic
|
||||
|
||||
from comicapi import utils
|
||||
from comicapi.comicarchive import ComicArchive, tags
|
||||
|
@ -21,8 +21,8 @@ import logging
|
||||
from collections import deque
|
||||
|
||||
import natsort
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets, uic
|
||||
from PyQt5.QtCore import QUrl, pyqtSignal
|
||||
from PyQt6 import QtCore, QtGui, QtWidgets, uic
|
||||
from PyQt6.QtCore import QUrl, pyqtSignal
|
||||
|
||||
from comicapi import utils
|
||||
from comicapi.comicarchive import ComicArchive
|
||||
|
@ -26,7 +26,7 @@ import urllib.parse
|
||||
from typing import Any, cast
|
||||
|
||||
import settngs
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets, uic
|
||||
from PyQt6 import QtCore, QtGui, QtWidgets, uic
|
||||
|
||||
import comictaggerlib.ui.talkeruigenerator
|
||||
from comicapi import merge, utils
|
||||
|
@ -31,7 +31,7 @@ from typing import Any, Callable, cast
|
||||
|
||||
import natsort
|
||||
import settngs
|
||||
from PyQt5 import QtCore, QtGui, QtNetwork, QtWidgets, uic
|
||||
from PyQt6 import QtCore, QtGui, QtNetwork, QtWidgets, uic
|
||||
|
||||
import comicapi.merge
|
||||
import comictaggerlib.graphics.resources
|
||||
@ -370,9 +370,9 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
def enabled_tags(self) -> Sequence[str]:
|
||||
return [tag.id for tag in tags.values() if tag.enabled]
|
||||
|
||||
def tag_actions(self) -> tuple[dict[str, QtWidgets.QAction], dict[str, QtWidgets.QAction]]:
|
||||
view_raw_tags: dict[str, QtWidgets.QAction] = {}
|
||||
remove_raw_tags: dict[str, QtWidgets.QAction] = {}
|
||||
def tag_actions(self) -> tuple[dict[str, QtGui.QAction], dict[str, QtGui.QAction]]:
|
||||
view_raw_tags: dict[str, QtGui.QAction] = {}
|
||||
remove_raw_tags: dict[str, QtGui.QAction] = {}
|
||||
for tag in tags.values():
|
||||
view_raw_tags[tag.id] = self.menuViewRawTags.addAction(f"View Raw {tag.name()} Tags")
|
||||
view_raw_tags[tag.id].setEnabled(tag.enabled)
|
||||
@ -449,7 +449,15 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
|
||||
def toggle_enable_embedding_hashes(self) -> None:
|
||||
self.config[0].Runtime_Options__enable_embedding_hashes = self.actionEnableEmbeddingHashes.isChecked()
|
||||
enable_widget(self.md_attributes["original_hash"], self.config[0].Runtime_Options__enable_embedding_hashes)
|
||||
enabled_widgets = set()
|
||||
for tag_id in self.selected_write_tags:
|
||||
if not tags[tag_id].enabled:
|
||||
continue
|
||||
enabled_widgets.update(tags[tag_id].supported_attributes)
|
||||
enable_widget(
|
||||
self.md_attributes["original_hash"],
|
||||
self.config[0].Runtime_Options__enable_embedding_hashes and "original_hash" in enabled_widgets,
|
||||
)
|
||||
if not self.leOriginalHash.text().strip():
|
||||
self.cbHashName.setCurrentText(self.config[0].internal__embedded_hash_type)
|
||||
if self.config[0].Runtime_Options__enable_embedding_hashes:
|
||||
@ -480,7 +488,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
self.actionParse_Filename_split_words.triggered.connect(self.use_filename_split)
|
||||
self.actionReCalcArchiveInfo.triggered.connect(self.recalc_archive_info)
|
||||
self.actionSearchOnline.triggered.connect(self.query_online)
|
||||
self.actionEnableEmbeddingHashes: QtWidgets.QAction
|
||||
self.actionEnableEmbeddingHashes: QtGui.QAction
|
||||
self.actionEnableEmbeddingHashes.triggered.connect(self.toggle_enable_embedding_hashes)
|
||||
self.actionEnableEmbeddingHashes.setChecked(self.config[0].Runtime_Options__enable_embedding_hashes)
|
||||
# Window Menu
|
||||
|
@ -6,8 +6,8 @@ from enum import auto
|
||||
from sys import platform
|
||||
from typing import Any
|
||||
|
||||
from PyQt5 import QtGui, QtWidgets
|
||||
from PyQt5.QtCore import QEvent, QModelIndex, QPoint, QRect, QSize, Qt, pyqtSignal
|
||||
from PyQt6 import QtGui, QtWidgets
|
||||
from PyQt6.QtCore import QEvent, QModelIndex, QPoint, QRect, QSize, Qt, pyqtSignal
|
||||
|
||||
from comicapi.utils import StrEnum
|
||||
|
||||
@ -30,41 +30,41 @@ class ModifyStyleItemDelegate(QtWidgets.QStyledItemDelegate):
|
||||
|
||||
# Draw background with the same color as other widgets
|
||||
palette = self.combobox.palette()
|
||||
background_color = palette.color(QtGui.QPalette.Window)
|
||||
background_color = palette.color(QtGui.QPalette.ColorRole.Window)
|
||||
painter.fillRect(options.rect, background_color)
|
||||
|
||||
style.drawPrimitive(QtWidgets.QStyle.PE_PanelItemViewItem, options, painter, self.combobox)
|
||||
style.drawPrimitive(QtWidgets.QStyle.PrimitiveElement.PE_PanelItemViewItem, options, painter, self.combobox)
|
||||
|
||||
painter.save()
|
||||
|
||||
# Checkbox drawing logic
|
||||
checked = index.data(Qt.CheckStateRole)
|
||||
checked = index.data(Qt.ItemDataRole.CheckStateRole)
|
||||
opts = QtWidgets.QStyleOptionButton()
|
||||
opts.state |= QtWidgets.QStyle.State_Active
|
||||
opts.state |= QtWidgets.QStyle.StateFlag.State_Active
|
||||
opts.rect = self.getCheckBoxRect(options)
|
||||
opts.state |= QtWidgets.QStyle.State_ReadOnly
|
||||
opts.state |= QtWidgets.QStyle.StateFlag.State_ReadOnly
|
||||
if checked:
|
||||
opts.state |= QtWidgets.QStyle.State_On
|
||||
opts.state |= QtWidgets.QStyle.StateFlag.State_On
|
||||
style.drawPrimitive(
|
||||
QtWidgets.QStyle.PrimitiveElement.PE_IndicatorMenuCheckMark, opts, painter, self.combobox
|
||||
)
|
||||
else:
|
||||
opts.state |= QtWidgets.QStyle.State_Off
|
||||
opts.state |= QtWidgets.QStyle.StateFlag.State_Off
|
||||
if platform != "darwin":
|
||||
style.drawControl(QtWidgets.QStyle.CE_CheckBox, opts, painter, self.combobox)
|
||||
style.drawControl(QtWidgets.QStyle.ControlElement.CE_CheckBox, opts, painter, self.combobox)
|
||||
|
||||
label = index.data(Qt.DisplayRole)
|
||||
label = index.data(Qt.ItemDataRole.DisplayRole)
|
||||
rectangle = options.rect
|
||||
rectangle.setX(opts.rect.width() + 10)
|
||||
painter.drawText(rectangle, Qt.AlignVCenter, label)
|
||||
|
||||
# We need the restore here so that text is colored properly
|
||||
painter.restore()
|
||||
painter.drawText(rectangle, Qt.AlignmentFlag.AlignVCenter, label)
|
||||
|
||||
def getCheckBoxRect(self, option: QtWidgets.QStyleOptionViewItem) -> QRect:
|
||||
# Get size of a standard checkbox.
|
||||
opts = QtWidgets.QStyleOptionButton()
|
||||
style = option.widget.style()
|
||||
checkBoxRect = style.subElementRect(QtWidgets.QStyle.SE_CheckBoxIndicator, opts, None)
|
||||
checkBoxRect = style.subElementRect(QtWidgets.QStyle.SubElement.SE_CheckBoxIndicator, opts, None)
|
||||
y = option.rect.y()
|
||||
h = option.rect.height()
|
||||
checkBoxTopLeftCorner = QPoint(5, int(y + h / 2 - checkBoxRect.height() / 2))
|
||||
@ -99,23 +99,23 @@ class CheckableComboBox(QtWidgets.QComboBox):
|
||||
# https://stackoverflow.com/questions/65826378/how-do-i-use-qcombobox-setplaceholdertext/65830989#65830989
|
||||
def paintEvent(self, event: QEvent) -> None:
|
||||
painter = QtWidgets.QStylePainter(self)
|
||||
painter.setPen(self.palette().color(QtGui.QPalette.Text))
|
||||
painter.setPen(self.palette().color(QtGui.QPalette.ColorRole.Text))
|
||||
|
||||
# draw the combobox frame, focusrect and selected etc.
|
||||
opt = QtWidgets.QStyleOptionComboBox()
|
||||
self.initStyleOption(opt)
|
||||
painter.drawComplexControl(QtWidgets.QStyle.CC_ComboBox, opt)
|
||||
painter.drawComplexControl(QtWidgets.QStyle.ComplexControl.CC_ComboBox, opt)
|
||||
|
||||
if self.currentIndex() < 0:
|
||||
opt.palette.setBrush(
|
||||
QtGui.QPalette.ButtonText,
|
||||
opt.palette.brush(QtGui.QPalette.ButtonText).color(),
|
||||
QtGui.QPalette.ColorRole.ButtonText,
|
||||
opt.palette.brush(QtGui.QPalette.ColorRole.ButtonText).color(),
|
||||
)
|
||||
if self.placeholderText():
|
||||
opt.currentText = self.placeholderText()
|
||||
|
||||
# draw the icon and text
|
||||
painter.drawControl(QtWidgets.QStyle.CE_ComboBoxLabel, opt)
|
||||
painter.drawControl(QtWidgets.QStyle.ControlElement.CE_ComboBoxLabel, opt)
|
||||
|
||||
def resizeEvent(self, event: Any) -> None:
|
||||
# Recompute text to elide as needed
|
||||
@ -126,16 +126,16 @@ class CheckableComboBox(QtWidgets.QComboBox):
|
||||
# Allow events before the combobox list is shown
|
||||
if obj == self.view().viewport():
|
||||
# We record that the combobox list has been shown
|
||||
if event.type() == QEvent.Show:
|
||||
if event.type() == QEvent.Type.Show:
|
||||
self.justShown = True
|
||||
# We record that the combobox list has hidden,
|
||||
# this will happen if the user does not make a selection
|
||||
# but clicks outside of the combobox list or presses escape
|
||||
if event.type() == QEvent.Hide:
|
||||
if event.type() == QEvent.Type.Hide:
|
||||
self._updateText()
|
||||
self.justShown = False
|
||||
# QEvent.MouseButtonPress is inconsistent on activation because double clicks are a thing
|
||||
if event.type() == QEvent.MouseButtonRelease:
|
||||
# QEvent.Type.MouseButtonPress is inconsistent on activation because double clicks are a thing
|
||||
if event.type() == QEvent.Type.MouseButtonRelease:
|
||||
# If self.justShown is true it means that they clicked on the combobox to change the checked items
|
||||
# This is standard behavior (on macos) but I think it is surprising when it has a multiple select
|
||||
if self.justShown:
|
||||
@ -153,7 +153,7 @@ class CheckableComboBox(QtWidgets.QComboBox):
|
||||
res = []
|
||||
for i in range(self.count()):
|
||||
item = self.model().item(i)
|
||||
if item.checkState() == Qt.Checked:
|
||||
if item.checkState() == Qt.CheckState.Checked:
|
||||
res.append(self.itemData(i))
|
||||
return res
|
||||
|
||||
@ -168,7 +168,7 @@ class CheckableComboBox(QtWidgets.QComboBox):
|
||||
texts = []
|
||||
for i in range(self.count()):
|
||||
item = self.model().item(i)
|
||||
if item.checkState() == Qt.Checked:
|
||||
if item.checkState() == Qt.CheckState.Checked:
|
||||
texts.append(item.text())
|
||||
text = ", ".join(texts)
|
||||
|
||||
@ -180,22 +180,24 @@ class CheckableComboBox(QtWidgets.QComboBox):
|
||||
so.initFrom(self)
|
||||
|
||||
# Ask the style for the size of the text field
|
||||
rect = self.style().subControlRect(QtWidgets.QStyle.CC_ComboBox, so, QtWidgets.QStyle.SC_ComboBoxEditField)
|
||||
rect = self.style().subControlRect(
|
||||
QtWidgets.QStyle.ComplexControl.CC_ComboBox, so, QtWidgets.QStyle.SubControl.SC_ComboBoxEditField
|
||||
)
|
||||
|
||||
# Compute the elided text
|
||||
elidedText = self.fontMetrics().elidedText(text, Qt.ElideRight, rect.width())
|
||||
elidedText = self.fontMetrics().elidedText(text, Qt.TextElideMode.ElideRight, rect.width())
|
||||
|
||||
# This CheckableComboBox does not use the index, so we clear it and set the placeholder text
|
||||
self.setCurrentIndex(-1)
|
||||
self.setPlaceholderText(elidedText)
|
||||
|
||||
def setItemChecked(self, index: Any, state: bool) -> None:
|
||||
qt_state = Qt.Checked if state else Qt.Unchecked
|
||||
qt_state = Qt.CheckState.Checked if state else Qt.CheckState.Unchecked
|
||||
item = self.model().item(index)
|
||||
current = self.currentData()
|
||||
# If we have at least one item checked emit itemChecked with the current check state and update text
|
||||
# Require at least one item to be checked and provide a tooltip
|
||||
if len(current) == 1 and not state and item.checkState() == Qt.Checked:
|
||||
if len(current) == 1 and not state and item.checkState() == Qt.CheckState.Checked:
|
||||
QtWidgets.QToolTip.showText(QtGui.QCursor.pos(), self.toolTip(), self, QRect(), 3000)
|
||||
return
|
||||
|
||||
@ -205,7 +207,7 @@ class CheckableComboBox(QtWidgets.QComboBox):
|
||||
self._updateText()
|
||||
|
||||
def toggleItem(self, index: int) -> None:
|
||||
if self.model().item(index).checkState() == Qt.Checked:
|
||||
if self.model().item(index).checkState() == Qt.CheckState.Checked:
|
||||
self.setItemChecked(index, False)
|
||||
else:
|
||||
self.setItemChecked(index, True)
|
||||
@ -240,44 +242,44 @@ class ReadStyleItemDelegate(QtWidgets.QStyledItemDelegate):
|
||||
|
||||
# Draw background with the same color as other widgets
|
||||
palette = self.combobox.palette()
|
||||
background_color = palette.color(QtGui.QPalette.Window)
|
||||
background_color = palette.color(QtGui.QPalette.ColorRole.Window)
|
||||
painter.fillRect(options.rect, background_color)
|
||||
|
||||
style.drawPrimitive(QtWidgets.QStyle.PE_PanelItemViewItem, options, painter, self.combobox)
|
||||
style.drawPrimitive(QtWidgets.QStyle.PrimitiveElement.PE_PanelItemViewItem, options, painter, self.combobox)
|
||||
|
||||
painter.save()
|
||||
|
||||
# Checkbox drawing logic
|
||||
checked = index.data(Qt.CheckStateRole)
|
||||
checked = index.data(Qt.ItemDataRole.CheckStateRole)
|
||||
opts = QtWidgets.QStyleOptionButton()
|
||||
opts.state |= QtWidgets.QStyle.State_Active
|
||||
opts.state |= QtWidgets.QStyle.StateFlag.State_Active
|
||||
opts.rect = self.getCheckBoxRect(options)
|
||||
opts.state |= QtWidgets.QStyle.State_ReadOnly
|
||||
opts.state |= QtWidgets.QStyle.StateFlag.State_ReadOnly
|
||||
if checked:
|
||||
opts.state |= QtWidgets.QStyle.State_On
|
||||
opts.state |= QtWidgets.QStyle.StateFlag.State_On
|
||||
style.drawPrimitive(
|
||||
QtWidgets.QStyle.PrimitiveElement.PE_IndicatorMenuCheckMark, opts, painter, self.combobox
|
||||
)
|
||||
else:
|
||||
opts.state |= QtWidgets.QStyle.State_Off
|
||||
opts.state |= QtWidgets.QStyle.StateFlag.State_Off
|
||||
if platform != "darwin":
|
||||
style.drawControl(QtWidgets.QStyle.CE_CheckBox, opts, painter, self.combobox)
|
||||
style.drawControl(QtWidgets.QStyle.ControlElement.CE_CheckBox, opts, painter, self.combobox)
|
||||
|
||||
label = index.data(Qt.DisplayRole)
|
||||
label = index.data(Qt.ItemDataRole.DisplayRole)
|
||||
rectangle = options.rect
|
||||
rectangle.setX(opts.rect.width() + 10)
|
||||
painter.drawText(rectangle, Qt.AlignVCenter, label)
|
||||
# We need the restore here so that text is colored properly
|
||||
painter.restore()
|
||||
painter.drawText(rectangle, Qt.AlignmentFlag.AlignVCenter, label)
|
||||
|
||||
# Draw buttons
|
||||
if checked and (options.state & QtWidgets.QStyle.State_Selected):
|
||||
if checked and (options.state & QtWidgets.QStyle.StateFlag.State_Selected):
|
||||
up_rect = self._button_up_rect(options.rect)
|
||||
down_rect = self._button_down_rect(options.rect)
|
||||
|
||||
painter.drawImage(up_rect, self.up_icon)
|
||||
painter.drawImage(down_rect, self.down_icon)
|
||||
|
||||
painter.restore()
|
||||
|
||||
def _button_up_rect(self, rect: QRect) -> QRect:
|
||||
return QRect(
|
||||
self.combobox.view().width() - (self.button_width * 2) - (self.button_padding * 2),
|
||||
@ -298,7 +300,7 @@ class ReadStyleItemDelegate(QtWidgets.QStyledItemDelegate):
|
||||
# Get size of a standard checkbox.
|
||||
opts = QtWidgets.QStyleOptionButton()
|
||||
style = option.widget.style()
|
||||
checkBoxRect = style.subElementRect(QtWidgets.QStyle.SE_CheckBoxIndicator, opts, None)
|
||||
checkBoxRect = style.subElementRect(QtWidgets.QStyle.SubElement.SE_CheckBoxIndicator, opts, None)
|
||||
y = option.rect.y()
|
||||
h = option.rect.height()
|
||||
checkBoxTopLeftCorner = QPoint(5, int(y + h / 2 - checkBoxRect.height() / 2))
|
||||
@ -307,7 +309,7 @@ class ReadStyleItemDelegate(QtWidgets.QStyledItemDelegate):
|
||||
|
||||
def itemClicked(self, index: QModelIndex, pos: QPoint) -> None:
|
||||
item_rect = self.combobox.view().visualRect(index)
|
||||
checked = index.data(Qt.CheckStateRole)
|
||||
checked = index.data(Qt.ItemDataRole.CheckStateRole)
|
||||
button_up_rect = self._button_up_rect(item_rect)
|
||||
button_down_rect = self._button_down_rect(item_rect)
|
||||
|
||||
@ -336,11 +338,11 @@ class ReadStyleItemDelegate(QtWidgets.QStyledItemDelegate):
|
||||
item_rect = view.visualRect(index)
|
||||
button_up_rect = self._button_up_rect(item_rect)
|
||||
button_down_rect = self._button_down_rect(item_rect)
|
||||
checked = index.data(Qt.CheckStateRole)
|
||||
checked = index.data(Qt.ItemDataRole.CheckStateRole)
|
||||
|
||||
if checked == Qt.Checked and button_up_rect.contains(event.pos()):
|
||||
if checked == Qt.CheckState.Checked and button_up_rect.contains(event.pos()):
|
||||
QtWidgets.QToolTip.showText(event.globalPos(), self.up_help, self.combobox, QRect(), 3000)
|
||||
elif checked == Qt.Checked and button_down_rect.contains(event.pos()):
|
||||
elif checked == Qt.CheckState.Checked and button_down_rect.contains(event.pos()):
|
||||
QtWidgets.QToolTip.showText(event.globalPos(), self.down_help, self.combobox, QRect(), 3000)
|
||||
else:
|
||||
QtWidgets.QToolTip.showText(event.globalPos(), self.item_help, self.combobox, QRect(), 3000)
|
||||
@ -380,23 +382,23 @@ class CheckableOrderComboBox(QtWidgets.QComboBox):
|
||||
# https://stackoverflow.com/questions/65826378/how-do-i-use-qcombobox-setplaceholdertext/65830989#65830989
|
||||
def paintEvent(self, event: QEvent) -> None:
|
||||
painter = QtWidgets.QStylePainter(self)
|
||||
painter.setPen(self.palette().color(QtGui.QPalette.Text))
|
||||
painter.setPen(self.palette().color(QtGui.QPalette.ColorRole.Text))
|
||||
|
||||
# draw the combobox frame, focusrect and selected etc.
|
||||
opt = QtWidgets.QStyleOptionComboBox()
|
||||
self.initStyleOption(opt)
|
||||
painter.drawComplexControl(QtWidgets.QStyle.CC_ComboBox, opt)
|
||||
painter.drawComplexControl(QtWidgets.QStyle.ComplexControl.CC_ComboBox, opt)
|
||||
|
||||
if self.currentIndex() < 0:
|
||||
opt.palette.setBrush(
|
||||
QtGui.QPalette.ButtonText,
|
||||
opt.palette.brush(QtGui.QPalette.ButtonText).color(),
|
||||
QtGui.QPalette.ColorRole.ButtonText,
|
||||
opt.palette.brush(QtGui.QPalette.ColorRole.ButtonText).color(),
|
||||
)
|
||||
if self.placeholderText():
|
||||
opt.currentText = self.placeholderText()
|
||||
|
||||
# draw the icon and text
|
||||
painter.drawControl(QtWidgets.QStyle.CE_ComboBoxLabel, opt)
|
||||
painter.drawControl(QtWidgets.QStyle.ControlElement.CE_ComboBoxLabel, opt)
|
||||
|
||||
def buttonClicked(self, index: QModelIndex, button: ClickedButtonEnum) -> None:
|
||||
if button == ClickedButtonEnum.up:
|
||||
@ -415,18 +417,18 @@ class CheckableOrderComboBox(QtWidgets.QComboBox):
|
||||
# Allow events before the combobox list is shown
|
||||
if obj == self.view().viewport():
|
||||
# We record that the combobox list has been shown
|
||||
if event.type() == QEvent.Show:
|
||||
if event.type() == QEvent.Type.Show:
|
||||
self.justShown = True
|
||||
# We record that the combobox list has hidden,
|
||||
# this will happen if the user does not make a selection
|
||||
# but clicks outside of the combobox list or presses escape
|
||||
if event.type() == QEvent.Hide:
|
||||
if event.type() == QEvent.Type.Hide:
|
||||
self._updateText()
|
||||
self.justShown = False
|
||||
# Reverse as the display order is in "priority" order for the user whereas overlay requires reversed
|
||||
self.dropdownClosed.emit(self.currentData())
|
||||
# QEvent.MouseButtonPress is inconsistent on activation because double clicks are a thing
|
||||
if event.type() == QEvent.MouseButtonRelease:
|
||||
# QEvent.Type.MouseButtonPress is inconsistent on activation because double clicks are a thing
|
||||
if event.type() == QEvent.Type.MouseButtonRelease:
|
||||
# If self.justShown is true it means that they clicked on the combobox to change the checked items
|
||||
# This is standard behavior (on macos) but I think it is surprising when it has a multiple select
|
||||
if self.justShown:
|
||||
@ -446,7 +448,7 @@ class CheckableOrderComboBox(QtWidgets.QComboBox):
|
||||
res = []
|
||||
for i in range(self.count()):
|
||||
item = self.model().item(i)
|
||||
if item.checkState() == Qt.Checked:
|
||||
if item.checkState() == Qt.CheckState.Checked:
|
||||
res.append(self.itemData(i))
|
||||
return res
|
||||
|
||||
@ -458,7 +460,7 @@ class CheckableOrderComboBox(QtWidgets.QComboBox):
|
||||
self.model().item(0).setCheckState(Qt.CheckState.Checked)
|
||||
|
||||
# Add room for "move" arrows
|
||||
text_width = self.fontMetrics().width(text)
|
||||
text_width = self.fontMetrics().boundingRect(text).width()
|
||||
checkbox_width = 40
|
||||
total_width = text_width + checkbox_width + (self.itemDelegate().button_width * 2)
|
||||
if total_width > self.view().minimumWidth():
|
||||
@ -477,19 +479,19 @@ class CheckableOrderComboBox(QtWidgets.QComboBox):
|
||||
return
|
||||
|
||||
# Grab values for the rows to swap
|
||||
cur_data = self.model().item(index).data(Qt.UserRole)
|
||||
cur_title = self.model().item(index).data(Qt.DisplayRole)
|
||||
cur_state = self.model().item(index).data(Qt.CheckStateRole)
|
||||
cur_data = self.model().item(index).data(Qt.ItemDataRole.UserRole)
|
||||
cur_title = self.model().item(index).data(Qt.ItemDataRole.DisplayRole)
|
||||
cur_state = self.model().item(index).checkState()
|
||||
|
||||
swap_data = self.model().item(row).data(Qt.UserRole)
|
||||
swap_title = self.model().item(row).data(Qt.DisplayRole)
|
||||
swap_data = self.model().item(row).data(Qt.ItemDataRole.UserRole)
|
||||
swap_title = self.model().item(row).data(Qt.ItemDataRole.DisplayRole)
|
||||
swap_state = self.model().item(row).checkState()
|
||||
|
||||
self.model().item(row).setData(cur_data, Qt.UserRole)
|
||||
self.model().item(row).setData(cur_data, Qt.ItemDataRole.UserRole)
|
||||
self.model().item(row).setCheckState(cur_state)
|
||||
self.model().item(row).setText(cur_title)
|
||||
|
||||
self.model().item(index).setData(swap_data, Qt.UserRole)
|
||||
self.model().item(index).setData(swap_data, Qt.ItemDataRole.UserRole)
|
||||
self.model().item(index).setCheckState(swap_state)
|
||||
self.model().item(index).setText(swap_title)
|
||||
|
||||
@ -497,7 +499,7 @@ class CheckableOrderComboBox(QtWidgets.QComboBox):
|
||||
texts = []
|
||||
for i in range(self.count()):
|
||||
item = self.model().item(i)
|
||||
if item.checkState() == Qt.Checked:
|
||||
if item.checkState() == Qt.CheckState.Checked:
|
||||
texts.append(item.text())
|
||||
text = ", ".join(texts)
|
||||
|
||||
@ -509,22 +511,24 @@ class CheckableOrderComboBox(QtWidgets.QComboBox):
|
||||
so.initFrom(self)
|
||||
|
||||
# Ask the style for the size of the text field
|
||||
rect = self.style().subControlRect(QtWidgets.QStyle.CC_ComboBox, so, QtWidgets.QStyle.SC_ComboBoxEditField)
|
||||
rect = self.style().subControlRect(
|
||||
QtWidgets.QStyle.ComplexControl.CC_ComboBox, so, QtWidgets.QStyle.SubControl.SC_ComboBoxEditField
|
||||
)
|
||||
|
||||
# Compute the elided text
|
||||
elidedText = self.fontMetrics().elidedText(text, Qt.ElideRight, rect.width())
|
||||
elidedText = self.fontMetrics().elidedText(text, Qt.TextElideMode.ElideRight, rect.width())
|
||||
|
||||
# This CheckableComboBox does not use the index, so we clear it and set the placeholder text
|
||||
self.setCurrentIndex(-1)
|
||||
self.setPlaceholderText(elidedText)
|
||||
|
||||
def setItemChecked(self, index: Any, state: bool) -> None:
|
||||
qt_state = Qt.Checked if state else Qt.Unchecked
|
||||
qt_state = Qt.CheckState.Checked if state else Qt.CheckState.Unchecked
|
||||
item = self.model().item(index)
|
||||
current = self.currentData()
|
||||
# If we have at least one item checked emit itemChecked with the current check state and update text
|
||||
# Require at least one item to be checked and provide a tooltip
|
||||
if len(current) == 1 and not state and item.checkState() == Qt.Checked:
|
||||
if len(current) == 1 and not state and item.checkState() == Qt.CheckState.Checked:
|
||||
QtWidgets.QToolTip.showText(QtGui.QCursor.pos(), self.toolTip(), self, QRect(), 3000)
|
||||
return
|
||||
|
||||
@ -533,7 +537,7 @@ class CheckableOrderComboBox(QtWidgets.QComboBox):
|
||||
self._updateText()
|
||||
|
||||
def toggleItem(self, index: int) -> None:
|
||||
if self.model().item(index).checkState() == Qt.Checked:
|
||||
if self.model().item(index).checkState() == Qt.CheckState.Checked:
|
||||
self.setItemChecked(index, False)
|
||||
else:
|
||||
self.setItemChecked(index, True)
|
||||
|
@ -8,15 +8,15 @@ import traceback
|
||||
import webbrowser
|
||||
from collections.abc import Collection, Sequence
|
||||
|
||||
from PyQt5.QtCore import QUrl
|
||||
from PyQt5.QtGui import QPalette
|
||||
from PyQt5.QtWidgets import QWidget
|
||||
from PyQt6.QtCore import QUrl
|
||||
from PyQt6.QtGui import QPalette
|
||||
from PyQt6.QtWidgets import QWidget
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
from PyQt5 import QtGui, QtWidgets
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt6 import QtGui, QtWidgets
|
||||
from PyQt6.QtCore import Qt
|
||||
|
||||
qt_available = True
|
||||
except ImportError:
|
||||
@ -31,7 +31,8 @@ if qt_available:
|
||||
pil_available = False
|
||||
active_palette: QPalette | None = None
|
||||
try:
|
||||
from PyQt5.QtWebEngineWidgets import QWebEnginePage, QWebEngineView
|
||||
from PyQt6.QtWebEngineCore import QWebEnginePage
|
||||
from PyQt6.QtWebEngineWidgets import QWebEngineView
|
||||
|
||||
class WebPage(QWebEnginePage):
|
||||
def acceptNavigationRequest(
|
||||
@ -54,6 +55,7 @@ if qt_available:
|
||||
webengine.setPage(WebPage(parent))
|
||||
webengine.setContextMenuPolicy(Qt.ContextMenuPolicy.NoContextMenu)
|
||||
settings = webengine.settings()
|
||||
assert settings is not None
|
||||
settings.setAttribute(settings.WebAttribute.AutoLoadImages, True)
|
||||
settings.setAttribute(settings.WebAttribute.JavascriptEnabled, False)
|
||||
settings.setAttribute(settings.WebAttribute.JavascriptCanOpenWindows, False)
|
||||
@ -194,7 +196,7 @@ if qt_available:
|
||||
if isinstance(widget, (QtWidgets.QTextEdit, QtWidgets.QLineEdit, QtWidgets.QAbstractSpinBox)):
|
||||
widget.setReadOnly(False)
|
||||
elif isinstance(widget, QtWidgets.QListWidget):
|
||||
widget.setMovement(QtWidgets.QListWidget.Free)
|
||||
widget.setMovement(QtWidgets.QListWidget.Movement.Free)
|
||||
else:
|
||||
if isinstance(widget, QtWidgets.QTableWidgetItem):
|
||||
widget.setBackground(inactive_brush)
|
||||
@ -211,7 +213,7 @@ if qt_available:
|
||||
elif isinstance(widget, QtWidgets.QListWidget):
|
||||
inactive_palette = palettes()
|
||||
widget.setPalette(inactive_palette[0])
|
||||
widget.setMovement(QtWidgets.QListWidget.Static)
|
||||
widget.setMovement(QtWidgets.QListWidget.Movement.Static)
|
||||
|
||||
def replaceWidget(
|
||||
layout: QtWidgets.QLayout | QtWidgets.QSplitter, old_widget: QtWidgets.QWidget, new_widget: QtWidgets.QWidget
|
||||
|
@ -6,7 +6,7 @@ from pathlib import Path
|
||||
from typing import Any, NamedTuple, cast
|
||||
|
||||
import settngs
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
from PyQt6 import QtCore, QtGui, QtWidgets
|
||||
|
||||
from comictaggerlib.coverimagewidget import CoverImageWidget
|
||||
from comictaggerlib.ctsettings import ct_ns, group_for_plugin
|
||||
@ -39,11 +39,13 @@ class PasswordEdit(QtWidgets.QLineEdit):
|
||||
self.visibleIcon = QtGui.QIcon(":/graphics/eye.svg")
|
||||
self.hiddenIcon = QtGui.QIcon(":/graphics/hidden.svg")
|
||||
|
||||
self.setEchoMode(QtWidgets.QLineEdit.Password)
|
||||
self.setEchoMode(QtWidgets.QLineEdit.EchoMode.Password)
|
||||
|
||||
if show_visibility:
|
||||
# Add the password hide/shown toggle at the end of the edit box.
|
||||
self.togglepasswordAction = self.addAction(self.visibleIcon, QtWidgets.QLineEdit.TrailingPosition)
|
||||
self.togglepasswordAction = self.addAction(
|
||||
self.visibleIcon, QtWidgets.QLineEdit.ActionPosition.TrailingPosition
|
||||
)
|
||||
self.togglepasswordAction.setToolTip("Show password")
|
||||
self.togglepasswordAction.triggered.connect(self.on_toggle_password_action)
|
||||
|
||||
@ -51,12 +53,12 @@ class PasswordEdit(QtWidgets.QLineEdit):
|
||||
|
||||
def on_toggle_password_action(self) -> None:
|
||||
if not self.password_shown:
|
||||
self.setEchoMode(QtWidgets.QLineEdit.Normal)
|
||||
self.setEchoMode(QtWidgets.QLineEdit.EchoMode.Normal)
|
||||
self.password_shown = True
|
||||
self.togglepasswordAction.setIcon(self.hiddenIcon)
|
||||
self.togglepasswordAction.setToolTip("Hide password")
|
||||
else:
|
||||
self.setEchoMode(QtWidgets.QLineEdit.Password)
|
||||
self.setEchoMode(QtWidgets.QLineEdit.EchoMode.Password)
|
||||
self.password_shown = False
|
||||
self.togglepasswordAction.setIcon(self.visibleIcon)
|
||||
self.togglepasswordAction.setToolTip("Show password")
|
||||
@ -125,7 +127,7 @@ def generate_spinbox(option: settngs.Setting, layout: QtWidgets.QGridLayout) ->
|
||||
widget = QtWidgets.QSpinBox()
|
||||
widget.setRange(0, 9999)
|
||||
widget.setToolTip(option.help)
|
||||
layout.addWidget(widget, row, 1, alignment=QtCore.Qt.AlignLeft)
|
||||
layout.addWidget(widget, row, 1, alignment=QtCore.Qt.AlignmentFlag.AlignLeft)
|
||||
|
||||
return widget
|
||||
|
||||
@ -138,7 +140,7 @@ def generate_doublespinbox(option: settngs.Setting, layout: QtWidgets.QGridLayou
|
||||
widget = QtWidgets.QDoubleSpinBox()
|
||||
widget.setRange(0, 9999.99)
|
||||
widget.setToolTip(option.help)
|
||||
layout.addWidget(widget, row, 1, alignment=QtCore.Qt.AlignLeft)
|
||||
layout.addWidget(widget, row, 1, alignment=QtCore.Qt.AlignmentFlag.AlignLeft)
|
||||
|
||||
return widget
|
||||
|
||||
@ -223,8 +225,8 @@ def generate_talker_info(talker: ComicTalker, config: settngs.Config[ct_ns], lay
|
||||
|
||||
# Add horizontal divider
|
||||
line = QtWidgets.QFrame()
|
||||
line.setFrameShape(QtWidgets.QFrame.HLine)
|
||||
line.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||||
line.setFrameShape(QtWidgets.QFrame.Shape.HLine)
|
||||
line.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken)
|
||||
layout.addWidget(line, row + 3, 0, 1, -1)
|
||||
|
||||
|
||||
@ -352,15 +354,15 @@ def generate_source_option_tabs(
|
||||
talker_layout = QtWidgets.QGridLayout()
|
||||
lbl_select_talker = QtWidgets.QLabel("Metadata Source:")
|
||||
line = QtWidgets.QFrame()
|
||||
line.setFrameShape(QtWidgets.QFrame.HLine)
|
||||
line.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||||
line.setFrameShape(QtWidgets.QFrame.Shape.HLine)
|
||||
line.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken)
|
||||
talker_tabs = QtWidgets.QTabWidget()
|
||||
|
||||
# Store all widgets as to allow easier access to their values vs. using findChildren etc. on the tab widget
|
||||
sources: Sources = Sources(QtWidgets.QComboBox(), [])
|
||||
|
||||
talker_layout.addWidget(lbl_select_talker, 0, 0, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Maximum)
|
||||
talker_layout.addWidget(sources[0], 0, 1, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Maximum)
|
||||
talker_layout.addWidget(lbl_select_talker, 0, 0)
|
||||
talker_layout.addWidget(sources[0], 0, 1)
|
||||
talker_layout.addWidget(line, 1, 0, 1, -1)
|
||||
talker_layout.addWidget(talker_tabs, 2, 0, 1, -1)
|
||||
|
||||
@ -440,7 +442,9 @@ def generate_source_option_tabs(
|
||||
generate_api_widgets(talker, tab, key_option, url_option, layout_grid, definitions=config.definitions)
|
||||
|
||||
# Add vertical spacer
|
||||
vspacer = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
|
||||
vspacer = QtWidgets.QSpacerItem(
|
||||
20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding
|
||||
)
|
||||
layout_grid.addItem(vspacer, layout_grid.rowCount() + 1, 0)
|
||||
# Display the new widgets
|
||||
tab.tab.setLayout(layout_grid)
|
||||
|
@ -5,7 +5,7 @@ extend-exclude = "comictaggerlib/graphics/resources.py"
|
||||
|
||||
[tool.isort]
|
||||
line_length = 120
|
||||
extend_skip = ["scripts", "comictaggerlib/graphics/resources.py"]
|
||||
extend_skip_glob = ["scripts", "comictaggerlib/graphics/resources.py"]
|
||||
profile = "black"
|
||||
|
||||
[build-system]
|
||||
@ -23,4 +23,4 @@ disable = "C0330, C0326, C0115, C0116, C0103"
|
||||
max-line-length=120
|
||||
|
||||
[tool.pylint.master]
|
||||
extension-pkg-whitelist="PyQt5"
|
||||
extension-pkg-whitelist="PyQt6"
|
||||
|
65
setup.cfg
65
setup.cfg
@ -50,6 +50,8 @@ install_requires =
|
||||
text2digits
|
||||
typing-extensions>=4.3.0
|
||||
wordninja
|
||||
zipremove
|
||||
pyicu;sys_platform != 'windows'
|
||||
python_requires = >=3.9
|
||||
|
||||
[options.packages.find]
|
||||
@ -73,18 +75,17 @@ pyinstaller40 =
|
||||
|
||||
[options.extras_require]
|
||||
7z =
|
||||
py7zr
|
||||
py7zr<1
|
||||
all =
|
||||
PyQt5
|
||||
PyQtWebEngine
|
||||
comicinfoxml==0.4.*
|
||||
PyQt6
|
||||
PyQt6-WebEngine
|
||||
comicinfoxml==0.5.*
|
||||
gcd-talker>0.1.0
|
||||
metron-talker>0.1.5
|
||||
pillow-avif-plugin>=1.4.1
|
||||
pillow-jxl-plugin>=1.2.5
|
||||
py7zr
|
||||
py7zr<1
|
||||
rarfile>=4.0
|
||||
pyicu;sys_platform == 'linux' or sys_platform == 'darwin'
|
||||
archived_tags =
|
||||
ct-archived-tags
|
||||
avif =
|
||||
@ -92,11 +93,11 @@ avif =
|
||||
cbr =
|
||||
rarfile>=4.0
|
||||
cix =
|
||||
comicinfoxml==0.4.*
|
||||
comicinfoxml==0.5.*
|
||||
gcd =
|
||||
gcd-talker>0.1.0
|
||||
gui =
|
||||
PyQt5
|
||||
PyQt6
|
||||
icu =
|
||||
pyicu;sys_platform == 'linux' or sys_platform == 'darwin'
|
||||
jxl =
|
||||
@ -104,17 +105,16 @@ jxl =
|
||||
metron =
|
||||
metron-talker>0.1.5
|
||||
pyinstaller =
|
||||
PyQt5
|
||||
PyQtWebEngine
|
||||
comicinfoxml==0.4.*
|
||||
PyQt6
|
||||
PyQt6-WebEngine
|
||||
comicinfoxml==0.5.*
|
||||
pillow-avif-plugin>=1.4.1
|
||||
pillow-jxl-plugin>=1.2.5
|
||||
py7zr
|
||||
py7zr<1
|
||||
rarfile>=4.0
|
||||
pyicu;sys_platform == 'linux' or sys_platform == 'darwin'
|
||||
qtw =
|
||||
PyQt5
|
||||
PyQtWebEngine
|
||||
PyQt6
|
||||
PyQt6-WebEngine
|
||||
|
||||
[options.package_data]
|
||||
comicapi =
|
||||
@ -126,7 +126,7 @@ comictaggerlib =
|
||||
[tox:tox]
|
||||
env_list =
|
||||
format
|
||||
py3.9-{none,gui,7z,cbr,icu,all}
|
||||
py3.9-{none,gui,7z,cbr,all}
|
||||
minversion = 4.4.12
|
||||
basepython = {env:tox_python:python3.9}
|
||||
|
||||
@ -139,28 +139,10 @@ extras =
|
||||
7z: 7Z
|
||||
cbr: CBR
|
||||
gui: GUI
|
||||
icu: ICU
|
||||
all: all
|
||||
commands =
|
||||
python -m pytest {tty:--color=yes} {posargs}
|
||||
icu,all: python -c 'import importlib,platform; importlib.import_module("icu") if platform.system() != "Windows" else ...' # Sanity check for icu
|
||||
|
||||
[m1env]
|
||||
description = run the tests with pytest
|
||||
package = wheel
|
||||
deps =
|
||||
pytest>=7
|
||||
icu,all: pyicu-binary
|
||||
extras =
|
||||
7z: 7Z
|
||||
cbr: CBR
|
||||
gui: GUI
|
||||
all: 7Z,CBR,GUI
|
||||
commands =
|
||||
python -m pytest {tty:--color=yes} {posargs}
|
||||
|
||||
[testenv:py3.9-{icu,all}]
|
||||
base = {env:tox_env:testenv}
|
||||
python -c 'import importlib,platform; importlib.import_module("icu") if platform.system() != "Windows" else ...' # Sanity check for icu
|
||||
|
||||
[testenv:format]
|
||||
labels =
|
||||
@ -212,7 +194,6 @@ commands =
|
||||
[testenv:wheel]
|
||||
description = Generate wheel and tar.gz
|
||||
labels =
|
||||
release
|
||||
build
|
||||
depends = clean
|
||||
skip_install = true
|
||||
@ -224,8 +205,6 @@ commands =
|
||||
[testenv:pypi-upload]
|
||||
description = Upload wheel to PyPi
|
||||
platform = linux
|
||||
labels =
|
||||
release
|
||||
skip_install = true
|
||||
depends = wheel
|
||||
deps =
|
||||
@ -246,7 +225,6 @@ commands =
|
||||
description = Generate pyinstaller executable
|
||||
labels =
|
||||
build
|
||||
release
|
||||
base = {env:tox_env:testenv}
|
||||
depends =
|
||||
clean
|
||||
@ -255,7 +233,6 @@ deps =
|
||||
extras =
|
||||
pyinstaller
|
||||
commands =
|
||||
pyrcc5 comictaggerlib/graphics/graphics.qrc -o comictaggerlib/graphics/resources.py
|
||||
pyinstaller -y build-tools/comictagger.spec
|
||||
python -c 'import importlib,platform; importlib.import_module("icu") if platform.system() != "Windows" else ...' # Sanity check for icu
|
||||
|
||||
@ -265,7 +242,6 @@ skip_install = true
|
||||
platform = linux
|
||||
base = {env:tox_env:testenv}
|
||||
labels =
|
||||
release
|
||||
build
|
||||
depends =
|
||||
clean
|
||||
@ -273,11 +249,11 @@ depends =
|
||||
deps =
|
||||
requests
|
||||
allowlist_externals =
|
||||
{tox_root}/build/appimagetool-x86_64.AppImage
|
||||
{tox_root}/build/appimagetool.AppImage
|
||||
change_dir = {tox_root}/dist/binary
|
||||
commands_pre =
|
||||
-python -c 'import shutil; shutil.rmtree("{tox_root}/build/", ignore_errors=True)'
|
||||
python {tox_root}/build-tools/get_appimage.py {tox_root}/build/appimagetool-x86_64.AppImage
|
||||
python {tox_root}/build-tools/get_appimage.py {tox_root}/build/appimagetool.AppImage
|
||||
commands =
|
||||
python -c 'import shutil,pathlib; shutil.copytree("{tox_root}/dist/comictagger/", "{tox_root}/build/appimage", dirs_exist_ok=True); \
|
||||
shutil.copy("{tox_root}/comictaggerlib/graphics/app.png", "{tox_root}/build/appimage/app.png"); \
|
||||
@ -285,12 +261,11 @@ commands =
|
||||
pathlib.Path("{tox_root}/build/appimage/AppRun.desktop").write_text( \
|
||||
pathlib.Path("{tox_root}/build-tools/ComicTagger.desktop").read_text() \
|
||||
.replace("/usr/local/share/comictagger/app.png", "app"))'
|
||||
{tox_root}/build/appimagetool-x86_64.AppImage {tox_root}/build/appimage
|
||||
{tox_root}/build/appimagetool.AppImage {tox_root}/build/appimage
|
||||
|
||||
[testenv:zip_artifacts]
|
||||
description = Zip release artifacts
|
||||
labels =
|
||||
release
|
||||
build
|
||||
depends =
|
||||
wheel
|
||||
|
Reference in New Issue
Block a user