mirror of
https://github.com/TagStudioDev/TagStudio.git
synced 2024-07-30 21:27:34 +00:00
Reformatted using Ruff
This commit is contained in:
parent
77d7559014
commit
089c8dd50c
46 changed files with 13032 additions and 11320 deletions
|
@ -1,2 +1,2 @@
|
|||
[tool.ruff]
|
||||
exclude = ["main_window.py", "home_ui.py"]
|
||||
exclude = ["main_window.py", "home_ui.py", "resources.py", "resources_rc.py"]
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -2,6 +2,7 @@
|
|||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
|
||||
class FieldTemplate:
|
||||
"""A TagStudio Library Field Template object."""
|
||||
|
||||
|
@ -11,7 +12,7 @@ class FieldTemplate:
|
|||
self.type = type
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'\nID: {self.id}\nName: {self.name}\nType: {self.type}\n'
|
||||
return f"\nID: {self.id}\nName: {self.name}\nType: {self.type}\n"
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return self.__str__()
|
||||
|
@ -20,8 +21,8 @@ class FieldTemplate:
|
|||
"""An alternative to __dict__ that only includes fields containing non-default data."""
|
||||
obj = {}
|
||||
# All Field fields (haha) are mandatory, so no value checks are done.
|
||||
obj['id'] = self.id
|
||||
obj['name'] = self.name
|
||||
obj['type'] = self.type
|
||||
obj["id"] = self.id
|
||||
obj["name"] = self.name
|
||||
obj["type"] = self.type
|
||||
|
||||
return obj
|
||||
|
|
|
@ -1,33 +1,38 @@
|
|||
from typing import TypedDict
|
||||
|
||||
class JsonLibary(TypedDict("",{"ts-version":str})):
|
||||
#"ts-version": str
|
||||
|
||||
class JsonLibary(TypedDict("", {"ts-version": str})):
|
||||
# "ts-version": str
|
||||
tags: "list[JsonTag]"
|
||||
collations: "list[JsonCollation]"
|
||||
fields: list #TODO
|
||||
fields: list # TODO
|
||||
macros: "list[JsonMacro]"
|
||||
entries: "list[JsonEntry]"
|
||||
|
||||
|
||||
class JsonBase(TypedDict):
|
||||
id: int
|
||||
|
||||
class JsonTag(JsonBase,total=False):
|
||||
|
||||
class JsonTag(JsonBase, total=False):
|
||||
name: str
|
||||
aliases: list[str]
|
||||
color: str
|
||||
shorthand: str
|
||||
subtag_ids: list[int]
|
||||
|
||||
class JsonCollation(JsonBase,total=False):
|
||||
|
||||
class JsonCollation(JsonBase, total=False):
|
||||
title: str
|
||||
e_ids_and_pages: list[list[int]]
|
||||
sort_order: str
|
||||
cover_id: int
|
||||
|
||||
class JsonEntry(JsonBase,total=False):
|
||||
|
||||
class JsonEntry(JsonBase, total=False):
|
||||
filename: str
|
||||
path: str
|
||||
fields: list[dict] #TODO
|
||||
fields: list[dict] # TODO
|
||||
|
||||
class JsonMacro(JsonBase,total=False):
|
||||
... #TODO
|
||||
|
||||
class JsonMacro(JsonBase, total=False): ... # TODO
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -14,230 +14,267 @@ class ColorType(Enum):
|
|||
|
||||
|
||||
_TAG_COLORS = {
|
||||
'': {ColorType.PRIMARY: '#1E1A33',
|
||||
ColorType.TEXT: ColorType.LIGHT_ACCENT,
|
||||
ColorType.BORDER: '#2B2547',
|
||||
ColorType.LIGHT_ACCENT: '#CDA7F7',
|
||||
ColorType.DARK_ACCENT: '#1E1A33',
|
||||
},
|
||||
'black': {ColorType.PRIMARY: '#111018',
|
||||
ColorType.TEXT: ColorType.LIGHT_ACCENT,
|
||||
ColorType.BORDER: '#18171e',
|
||||
ColorType.LIGHT_ACCENT: '#b7b6be',
|
||||
ColorType.DARK_ACCENT: '#03020a',
|
||||
},
|
||||
'dark gray': {ColorType.PRIMARY: '#24232a',
|
||||
ColorType.TEXT: ColorType.LIGHT_ACCENT,
|
||||
ColorType.BORDER: '#2a2930',
|
||||
ColorType.LIGHT_ACCENT: '#bdbcc4',
|
||||
ColorType.DARK_ACCENT: '#07060e',
|
||||
},
|
||||
'gray': {ColorType.PRIMARY: '#53525a',
|
||||
ColorType.TEXT: ColorType.LIGHT_ACCENT,
|
||||
ColorType.BORDER: '#5b5a62',
|
||||
ColorType.LIGHT_ACCENT: '#cbcad2',
|
||||
ColorType.DARK_ACCENT: '#191820',
|
||||
},
|
||||
'light gray': {ColorType.PRIMARY: '#aaa9b0',
|
||||
ColorType.TEXT: ColorType.DARK_ACCENT,
|
||||
ColorType.BORDER: '#b6b4bc',
|
||||
ColorType.LIGHT_ACCENT: '#cbcad2',
|
||||
ColorType.DARK_ACCENT: '#191820',
|
||||
},
|
||||
'white': {ColorType.PRIMARY: '#f2f1f8',
|
||||
ColorType.TEXT: ColorType.DARK_ACCENT,
|
||||
ColorType.BORDER: '#fefeff',
|
||||
ColorType.LIGHT_ACCENT: '#ffffff',
|
||||
ColorType.DARK_ACCENT: '#302f36',
|
||||
},
|
||||
'light pink': {ColorType.PRIMARY: '#ff99c4',
|
||||
ColorType.TEXT: ColorType.DARK_ACCENT,
|
||||
ColorType.BORDER: '#ffaad0',
|
||||
ColorType.LIGHT_ACCENT: '#ffcbe7',
|
||||
ColorType.DARK_ACCENT: '#6c2e3b',
|
||||
},
|
||||
'pink': {ColorType.PRIMARY: '#ff99c4',
|
||||
ColorType.TEXT: ColorType.DARK_ACCENT,
|
||||
ColorType.BORDER: '#ffaad0',
|
||||
ColorType.LIGHT_ACCENT: '#ffcbe7',
|
||||
ColorType.DARK_ACCENT: '#6c2e3b',
|
||||
},
|
||||
'magenta': {ColorType.PRIMARY: '#f6466f',
|
||||
ColorType.TEXT: ColorType.DARK_ACCENT,
|
||||
ColorType.BORDER: '#f7587f',
|
||||
ColorType.LIGHT_ACCENT: '#fba4bf',
|
||||
ColorType.DARK_ACCENT: '#61152f',
|
||||
},
|
||||
'red': {ColorType.PRIMARY: '#e22c3c',
|
||||
ColorType.TEXT: ColorType.DARK_ACCENT,
|
||||
ColorType.BORDER: '#b21f2d',
|
||||
# ColorType.BORDER: '#e54252',
|
||||
ColorType.LIGHT_ACCENT: '#f39caa',
|
||||
ColorType.DARK_ACCENT: '#440d12',
|
||||
},
|
||||
'red orange': {ColorType.PRIMARY: '#e83726',
|
||||
ColorType.TEXT: ColorType.DARK_ACCENT,
|
||||
ColorType.BORDER: '#ea4b3b',
|
||||
ColorType.LIGHT_ACCENT: '#f5a59d',
|
||||
ColorType.DARK_ACCENT: '#61120b',
|
||||
},
|
||||
'salmon': {ColorType.PRIMARY: '#f65848',
|
||||
ColorType.TEXT: ColorType.DARK_ACCENT,
|
||||
ColorType.BORDER: '#f76c5f',
|
||||
ColorType.LIGHT_ACCENT: '#fcadaa',
|
||||
ColorType.DARK_ACCENT: '#6f1b16',
|
||||
},
|
||||
'orange': {ColorType.PRIMARY: '#ed6022',
|
||||
ColorType.TEXT: ColorType.DARK_ACCENT,
|
||||
ColorType.BORDER: '#ef7038',
|
||||
ColorType.LIGHT_ACCENT: '#f7b79b',
|
||||
ColorType.DARK_ACCENT: '#551e0a',
|
||||
},
|
||||
'yellow orange': {ColorType.PRIMARY: '#fa9a2c',
|
||||
ColorType.TEXT: ColorType.DARK_ACCENT,
|
||||
ColorType.BORDER: '#fba94b',
|
||||
ColorType.LIGHT_ACCENT: '#fdd7ab',
|
||||
ColorType.DARK_ACCENT: '#66330d',
|
||||
},
|
||||
'yellow': {ColorType.PRIMARY: '#ffd63d',
|
||||
ColorType.TEXT: ColorType.DARK_ACCENT,
|
||||
# ColorType.BORDER: '#ffe071',
|
||||
ColorType.BORDER: '#e8af31',
|
||||
ColorType.LIGHT_ACCENT: '#fff3c4',
|
||||
ColorType.DARK_ACCENT: '#754312',
|
||||
},
|
||||
'mint': {ColorType.PRIMARY: '#4aed90',
|
||||
ColorType.TEXT: ColorType.DARK_ACCENT,
|
||||
ColorType.BORDER: '#79f2b1',
|
||||
ColorType.LIGHT_ACCENT: '#c8fbe9',
|
||||
ColorType.DARK_ACCENT: '#164f3e',
|
||||
},
|
||||
'lime': {ColorType.PRIMARY: '#92e649',
|
||||
ColorType.TEXT: ColorType.DARK_ACCENT,
|
||||
ColorType.BORDER: '#b2ed72',
|
||||
ColorType.LIGHT_ACCENT: '#e9f9b7',
|
||||
ColorType.DARK_ACCENT: '#405516',
|
||||
},
|
||||
'light green': {ColorType.PRIMARY: '#85ec76',
|
||||
ColorType.TEXT: ColorType.DARK_ACCENT,
|
||||
ColorType.BORDER: '#a3f198',
|
||||
ColorType.LIGHT_ACCENT: '#e7fbe4',
|
||||
ColorType.DARK_ACCENT: '#2b5524',
|
||||
},
|
||||
'green': {ColorType.PRIMARY: '#28bb48',
|
||||
ColorType.TEXT: ColorType.DARK_ACCENT,
|
||||
ColorType.BORDER: '#43c568',
|
||||
ColorType.LIGHT_ACCENT: '#93e2c8',
|
||||
ColorType.DARK_ACCENT: '#0d3828',
|
||||
},
|
||||
'teal': {ColorType.PRIMARY: '#1ad9b2',
|
||||
ColorType.TEXT: ColorType.DARK_ACCENT,
|
||||
ColorType.BORDER: '#4de3c7',
|
||||
ColorType.LIGHT_ACCENT: '#a0f3e8',
|
||||
ColorType.DARK_ACCENT: '#08424b',
|
||||
},
|
||||
'cyan': {ColorType.PRIMARY: '#49e4d5',
|
||||
ColorType.TEXT: ColorType.DARK_ACCENT,
|
||||
ColorType.BORDER: '#76ebdf',
|
||||
ColorType.LIGHT_ACCENT: '#bff5f0',
|
||||
ColorType.DARK_ACCENT: '#0f4246',
|
||||
},
|
||||
'light blue': {ColorType.PRIMARY: '#55bbf6',
|
||||
ColorType.TEXT: ColorType.DARK_ACCENT,
|
||||
ColorType.BORDER: '#70c6f7',
|
||||
ColorType.LIGHT_ACCENT: '#bbe4fb',
|
||||
ColorType.DARK_ACCENT: '#122541',
|
||||
},
|
||||
'blue': {ColorType.PRIMARY: '#3b87f0',
|
||||
ColorType.TEXT: ColorType.LIGHT_ACCENT,
|
||||
ColorType.BORDER: '#4e95f2',
|
||||
ColorType.LIGHT_ACCENT: '#aedbfa',
|
||||
ColorType.DARK_ACCENT: '#122948',
|
||||
},
|
||||
'blue violet': {ColorType.PRIMARY: '#5948f2',
|
||||
ColorType.TEXT: ColorType.LIGHT_ACCENT,
|
||||
ColorType.BORDER: '#6258f3',
|
||||
ColorType.LIGHT_ACCENT: '#9cb8fb',
|
||||
ColorType.DARK_ACCENT: '#1b1649',
|
||||
},
|
||||
'violet': {ColorType.PRIMARY: '#874ff5',
|
||||
ColorType.TEXT: ColorType.LIGHT_ACCENT,
|
||||
ColorType.BORDER: '#9360f6',
|
||||
ColorType.LIGHT_ACCENT: '#c9b0fa',
|
||||
ColorType.DARK_ACCENT: '#3a1860',
|
||||
},
|
||||
'purple': {ColorType.PRIMARY: '#bb4ff0',
|
||||
ColorType.TEXT: ColorType.DARK_ACCENT,
|
||||
ColorType.BORDER: '#c364f2',
|
||||
ColorType.LIGHT_ACCENT: '#dda7f7',
|
||||
ColorType.DARK_ACCENT: '#531862',
|
||||
},
|
||||
'peach': {ColorType.PRIMARY: '#f1c69c',
|
||||
ColorType.TEXT: ColorType.DARK_ACCENT,
|
||||
ColorType.BORDER: '#f4d4b4',
|
||||
ColorType.LIGHT_ACCENT: '#fbeee1',
|
||||
ColorType.DARK_ACCENT: '#613f2f',
|
||||
},
|
||||
'brown': {ColorType.PRIMARY: '#823216',
|
||||
ColorType.TEXT: ColorType.LIGHT_ACCENT,
|
||||
ColorType.BORDER: '#8a3e22',
|
||||
ColorType.LIGHT_ACCENT: '#cd9d83',
|
||||
ColorType.DARK_ACCENT: '#3a1804',
|
||||
},
|
||||
'lavender': {ColorType.PRIMARY: '#ad8eef',
|
||||
ColorType.TEXT: ColorType.DARK_ACCENT,
|
||||
ColorType.BORDER: '#b99ef2',
|
||||
ColorType.LIGHT_ACCENT: '#d5c7fa',
|
||||
ColorType.DARK_ACCENT: '#492b65',
|
||||
},
|
||||
'blonde': {ColorType.PRIMARY: '#efc664',
|
||||
ColorType.TEXT: ColorType.DARK_ACCENT,
|
||||
ColorType.BORDER: '#f3d387',
|
||||
ColorType.LIGHT_ACCENT: '#faebc6',
|
||||
ColorType.DARK_ACCENT: '#6d461e',
|
||||
},
|
||||
'auburn': {ColorType.PRIMARY: '#a13220',
|
||||
ColorType.TEXT: ColorType.LIGHT_ACCENT,
|
||||
ColorType.BORDER: '#aa402f',
|
||||
ColorType.LIGHT_ACCENT: '#d98a7f',
|
||||
ColorType.DARK_ACCENT: '#3d100a',
|
||||
},
|
||||
'light brown': {ColorType.PRIMARY: '#be5b2d',
|
||||
ColorType.TEXT: ColorType.DARK_ACCENT,
|
||||
ColorType.BORDER: '#c4693d',
|
||||
ColorType.LIGHT_ACCENT: '#e5b38c',
|
||||
ColorType.DARK_ACCENT: '#4c290e',
|
||||
},
|
||||
'dark brown': {ColorType.PRIMARY: '#4c2315',
|
||||
ColorType.TEXT: ColorType.LIGHT_ACCENT,
|
||||
ColorType.BORDER: '#542a1c',
|
||||
ColorType.LIGHT_ACCENT: '#b78171',
|
||||
ColorType.DARK_ACCENT: '#211006',
|
||||
},
|
||||
'cool gray': {ColorType.PRIMARY: '#515768',
|
||||
ColorType.TEXT: ColorType.LIGHT_ACCENT,
|
||||
ColorType.BORDER: '#5b6174',
|
||||
ColorType.LIGHT_ACCENT: '#9ea1c3',
|
||||
ColorType.DARK_ACCENT: '#181a37',
|
||||
},
|
||||
'warm gray': {ColorType.PRIMARY: '#625550',
|
||||
ColorType.TEXT: ColorType.LIGHT_ACCENT,
|
||||
ColorType.BORDER: '#6c5e57',
|
||||
ColorType.LIGHT_ACCENT: '#c0a392',
|
||||
ColorType.DARK_ACCENT: '#371d18',
|
||||
},
|
||||
'olive': {ColorType.PRIMARY: '#4c652e',
|
||||
ColorType.TEXT: ColorType.LIGHT_ACCENT,
|
||||
ColorType.BORDER: '#586f36',
|
||||
ColorType.LIGHT_ACCENT: '#b4c17a',
|
||||
ColorType.DARK_ACCENT: '#23300e',
|
||||
},
|
||||
'berry': {ColorType.PRIMARY: '#9f2aa7',
|
||||
ColorType.TEXT: ColorType.LIGHT_ACCENT,
|
||||
ColorType.BORDER: '#aa43b4',
|
||||
ColorType.LIGHT_ACCENT: '#cc8fdc',
|
||||
ColorType.DARK_ACCENT: '#41114a',
|
||||
},
|
||||
"": {
|
||||
ColorType.PRIMARY: "#1E1A33",
|
||||
ColorType.TEXT: ColorType.LIGHT_ACCENT,
|
||||
ColorType.BORDER: "#2B2547",
|
||||
ColorType.LIGHT_ACCENT: "#CDA7F7",
|
||||
ColorType.DARK_ACCENT: "#1E1A33",
|
||||
},
|
||||
"black": {
|
||||
ColorType.PRIMARY: "#111018",
|
||||
ColorType.TEXT: ColorType.LIGHT_ACCENT,
|
||||
ColorType.BORDER: "#18171e",
|
||||
ColorType.LIGHT_ACCENT: "#b7b6be",
|
||||
ColorType.DARK_ACCENT: "#03020a",
|
||||
},
|
||||
"dark gray": {
|
||||
ColorType.PRIMARY: "#24232a",
|
||||
ColorType.TEXT: ColorType.LIGHT_ACCENT,
|
||||
ColorType.BORDER: "#2a2930",
|
||||
ColorType.LIGHT_ACCENT: "#bdbcc4",
|
||||
ColorType.DARK_ACCENT: "#07060e",
|
||||
},
|
||||
"gray": {
|
||||
ColorType.PRIMARY: "#53525a",
|
||||
ColorType.TEXT: ColorType.LIGHT_ACCENT,
|
||||
ColorType.BORDER: "#5b5a62",
|
||||
ColorType.LIGHT_ACCENT: "#cbcad2",
|
||||
ColorType.DARK_ACCENT: "#191820",
|
||||
},
|
||||
"light gray": {
|
||||
ColorType.PRIMARY: "#aaa9b0",
|
||||
ColorType.TEXT: ColorType.DARK_ACCENT,
|
||||
ColorType.BORDER: "#b6b4bc",
|
||||
ColorType.LIGHT_ACCENT: "#cbcad2",
|
||||
ColorType.DARK_ACCENT: "#191820",
|
||||
},
|
||||
"white": {
|
||||
ColorType.PRIMARY: "#f2f1f8",
|
||||
ColorType.TEXT: ColorType.DARK_ACCENT,
|
||||
ColorType.BORDER: "#fefeff",
|
||||
ColorType.LIGHT_ACCENT: "#ffffff",
|
||||
ColorType.DARK_ACCENT: "#302f36",
|
||||
},
|
||||
"light pink": {
|
||||
ColorType.PRIMARY: "#ff99c4",
|
||||
ColorType.TEXT: ColorType.DARK_ACCENT,
|
||||
ColorType.BORDER: "#ffaad0",
|
||||
ColorType.LIGHT_ACCENT: "#ffcbe7",
|
||||
ColorType.DARK_ACCENT: "#6c2e3b",
|
||||
},
|
||||
"pink": {
|
||||
ColorType.PRIMARY: "#ff99c4",
|
||||
ColorType.TEXT: ColorType.DARK_ACCENT,
|
||||
ColorType.BORDER: "#ffaad0",
|
||||
ColorType.LIGHT_ACCENT: "#ffcbe7",
|
||||
ColorType.DARK_ACCENT: "#6c2e3b",
|
||||
},
|
||||
"magenta": {
|
||||
ColorType.PRIMARY: "#f6466f",
|
||||
ColorType.TEXT: ColorType.DARK_ACCENT,
|
||||
ColorType.BORDER: "#f7587f",
|
||||
ColorType.LIGHT_ACCENT: "#fba4bf",
|
||||
ColorType.DARK_ACCENT: "#61152f",
|
||||
},
|
||||
"red": {
|
||||
ColorType.PRIMARY: "#e22c3c",
|
||||
ColorType.TEXT: ColorType.DARK_ACCENT,
|
||||
ColorType.BORDER: "#b21f2d",
|
||||
# ColorType.BORDER: '#e54252',
|
||||
ColorType.LIGHT_ACCENT: "#f39caa",
|
||||
ColorType.DARK_ACCENT: "#440d12",
|
||||
},
|
||||
"red orange": {
|
||||
ColorType.PRIMARY: "#e83726",
|
||||
ColorType.TEXT: ColorType.DARK_ACCENT,
|
||||
ColorType.BORDER: "#ea4b3b",
|
||||
ColorType.LIGHT_ACCENT: "#f5a59d",
|
||||
ColorType.DARK_ACCENT: "#61120b",
|
||||
},
|
||||
"salmon": {
|
||||
ColorType.PRIMARY: "#f65848",
|
||||
ColorType.TEXT: ColorType.DARK_ACCENT,
|
||||
ColorType.BORDER: "#f76c5f",
|
||||
ColorType.LIGHT_ACCENT: "#fcadaa",
|
||||
ColorType.DARK_ACCENT: "#6f1b16",
|
||||
},
|
||||
"orange": {
|
||||
ColorType.PRIMARY: "#ed6022",
|
||||
ColorType.TEXT: ColorType.DARK_ACCENT,
|
||||
ColorType.BORDER: "#ef7038",
|
||||
ColorType.LIGHT_ACCENT: "#f7b79b",
|
||||
ColorType.DARK_ACCENT: "#551e0a",
|
||||
},
|
||||
"yellow orange": {
|
||||
ColorType.PRIMARY: "#fa9a2c",
|
||||
ColorType.TEXT: ColorType.DARK_ACCENT,
|
||||
ColorType.BORDER: "#fba94b",
|
||||
ColorType.LIGHT_ACCENT: "#fdd7ab",
|
||||
ColorType.DARK_ACCENT: "#66330d",
|
||||
},
|
||||
"yellow": {
|
||||
ColorType.PRIMARY: "#ffd63d",
|
||||
ColorType.TEXT: ColorType.DARK_ACCENT,
|
||||
# ColorType.BORDER: '#ffe071',
|
||||
ColorType.BORDER: "#e8af31",
|
||||
ColorType.LIGHT_ACCENT: "#fff3c4",
|
||||
ColorType.DARK_ACCENT: "#754312",
|
||||
},
|
||||
"mint": {
|
||||
ColorType.PRIMARY: "#4aed90",
|
||||
ColorType.TEXT: ColorType.DARK_ACCENT,
|
||||
ColorType.BORDER: "#79f2b1",
|
||||
ColorType.LIGHT_ACCENT: "#c8fbe9",
|
||||
ColorType.DARK_ACCENT: "#164f3e",
|
||||
},
|
||||
"lime": {
|
||||
ColorType.PRIMARY: "#92e649",
|
||||
ColorType.TEXT: ColorType.DARK_ACCENT,
|
||||
ColorType.BORDER: "#b2ed72",
|
||||
ColorType.LIGHT_ACCENT: "#e9f9b7",
|
||||
ColorType.DARK_ACCENT: "#405516",
|
||||
},
|
||||
"light green": {
|
||||
ColorType.PRIMARY: "#85ec76",
|
||||
ColorType.TEXT: ColorType.DARK_ACCENT,
|
||||
ColorType.BORDER: "#a3f198",
|
||||
ColorType.LIGHT_ACCENT: "#e7fbe4",
|
||||
ColorType.DARK_ACCENT: "#2b5524",
|
||||
},
|
||||
"green": {
|
||||
ColorType.PRIMARY: "#28bb48",
|
||||
ColorType.TEXT: ColorType.DARK_ACCENT,
|
||||
ColorType.BORDER: "#43c568",
|
||||
ColorType.LIGHT_ACCENT: "#93e2c8",
|
||||
ColorType.DARK_ACCENT: "#0d3828",
|
||||
},
|
||||
"teal": {
|
||||
ColorType.PRIMARY: "#1ad9b2",
|
||||
ColorType.TEXT: ColorType.DARK_ACCENT,
|
||||
ColorType.BORDER: "#4de3c7",
|
||||
ColorType.LIGHT_ACCENT: "#a0f3e8",
|
||||
ColorType.DARK_ACCENT: "#08424b",
|
||||
},
|
||||
"cyan": {
|
||||
ColorType.PRIMARY: "#49e4d5",
|
||||
ColorType.TEXT: ColorType.DARK_ACCENT,
|
||||
ColorType.BORDER: "#76ebdf",
|
||||
ColorType.LIGHT_ACCENT: "#bff5f0",
|
||||
ColorType.DARK_ACCENT: "#0f4246",
|
||||
},
|
||||
"light blue": {
|
||||
ColorType.PRIMARY: "#55bbf6",
|
||||
ColorType.TEXT: ColorType.DARK_ACCENT,
|
||||
ColorType.BORDER: "#70c6f7",
|
||||
ColorType.LIGHT_ACCENT: "#bbe4fb",
|
||||
ColorType.DARK_ACCENT: "#122541",
|
||||
},
|
||||
"blue": {
|
||||
ColorType.PRIMARY: "#3b87f0",
|
||||
ColorType.TEXT: ColorType.LIGHT_ACCENT,
|
||||
ColorType.BORDER: "#4e95f2",
|
||||
ColorType.LIGHT_ACCENT: "#aedbfa",
|
||||
ColorType.DARK_ACCENT: "#122948",
|
||||
},
|
||||
"blue violet": {
|
||||
ColorType.PRIMARY: "#5948f2",
|
||||
ColorType.TEXT: ColorType.LIGHT_ACCENT,
|
||||
ColorType.BORDER: "#6258f3",
|
||||
ColorType.LIGHT_ACCENT: "#9cb8fb",
|
||||
ColorType.DARK_ACCENT: "#1b1649",
|
||||
},
|
||||
"violet": {
|
||||
ColorType.PRIMARY: "#874ff5",
|
||||
ColorType.TEXT: ColorType.LIGHT_ACCENT,
|
||||
ColorType.BORDER: "#9360f6",
|
||||
ColorType.LIGHT_ACCENT: "#c9b0fa",
|
||||
ColorType.DARK_ACCENT: "#3a1860",
|
||||
},
|
||||
"purple": {
|
||||
ColorType.PRIMARY: "#bb4ff0",
|
||||
ColorType.TEXT: ColorType.DARK_ACCENT,
|
||||
ColorType.BORDER: "#c364f2",
|
||||
ColorType.LIGHT_ACCENT: "#dda7f7",
|
||||
ColorType.DARK_ACCENT: "#531862",
|
||||
},
|
||||
"peach": {
|
||||
ColorType.PRIMARY: "#f1c69c",
|
||||
ColorType.TEXT: ColorType.DARK_ACCENT,
|
||||
ColorType.BORDER: "#f4d4b4",
|
||||
ColorType.LIGHT_ACCENT: "#fbeee1",
|
||||
ColorType.DARK_ACCENT: "#613f2f",
|
||||
},
|
||||
"brown": {
|
||||
ColorType.PRIMARY: "#823216",
|
||||
ColorType.TEXT: ColorType.LIGHT_ACCENT,
|
||||
ColorType.BORDER: "#8a3e22",
|
||||
ColorType.LIGHT_ACCENT: "#cd9d83",
|
||||
ColorType.DARK_ACCENT: "#3a1804",
|
||||
},
|
||||
"lavender": {
|
||||
ColorType.PRIMARY: "#ad8eef",
|
||||
ColorType.TEXT: ColorType.DARK_ACCENT,
|
||||
ColorType.BORDER: "#b99ef2",
|
||||
ColorType.LIGHT_ACCENT: "#d5c7fa",
|
||||
ColorType.DARK_ACCENT: "#492b65",
|
||||
},
|
||||
"blonde": {
|
||||
ColorType.PRIMARY: "#efc664",
|
||||
ColorType.TEXT: ColorType.DARK_ACCENT,
|
||||
ColorType.BORDER: "#f3d387",
|
||||
ColorType.LIGHT_ACCENT: "#faebc6",
|
||||
ColorType.DARK_ACCENT: "#6d461e",
|
||||
},
|
||||
"auburn": {
|
||||
ColorType.PRIMARY: "#a13220",
|
||||
ColorType.TEXT: ColorType.LIGHT_ACCENT,
|
||||
ColorType.BORDER: "#aa402f",
|
||||
ColorType.LIGHT_ACCENT: "#d98a7f",
|
||||
ColorType.DARK_ACCENT: "#3d100a",
|
||||
},
|
||||
"light brown": {
|
||||
ColorType.PRIMARY: "#be5b2d",
|
||||
ColorType.TEXT: ColorType.DARK_ACCENT,
|
||||
ColorType.BORDER: "#c4693d",
|
||||
ColorType.LIGHT_ACCENT: "#e5b38c",
|
||||
ColorType.DARK_ACCENT: "#4c290e",
|
||||
},
|
||||
"dark brown": {
|
||||
ColorType.PRIMARY: "#4c2315",
|
||||
ColorType.TEXT: ColorType.LIGHT_ACCENT,
|
||||
ColorType.BORDER: "#542a1c",
|
||||
ColorType.LIGHT_ACCENT: "#b78171",
|
||||
ColorType.DARK_ACCENT: "#211006",
|
||||
},
|
||||
"cool gray": {
|
||||
ColorType.PRIMARY: "#515768",
|
||||
ColorType.TEXT: ColorType.LIGHT_ACCENT,
|
||||
ColorType.BORDER: "#5b6174",
|
||||
ColorType.LIGHT_ACCENT: "#9ea1c3",
|
||||
ColorType.DARK_ACCENT: "#181a37",
|
||||
},
|
||||
"warm gray": {
|
||||
ColorType.PRIMARY: "#625550",
|
||||
ColorType.TEXT: ColorType.LIGHT_ACCENT,
|
||||
ColorType.BORDER: "#6c5e57",
|
||||
ColorType.LIGHT_ACCENT: "#c0a392",
|
||||
ColorType.DARK_ACCENT: "#371d18",
|
||||
},
|
||||
"olive": {
|
||||
ColorType.PRIMARY: "#4c652e",
|
||||
ColorType.TEXT: ColorType.LIGHT_ACCENT,
|
||||
ColorType.BORDER: "#586f36",
|
||||
ColorType.LIGHT_ACCENT: "#b4c17a",
|
||||
ColorType.DARK_ACCENT: "#23300e",
|
||||
},
|
||||
"berry": {
|
||||
ColorType.PRIMARY: "#9f2aa7",
|
||||
ColorType.TEXT: ColorType.LIGHT_ACCENT,
|
||||
ColorType.BORDER: "#aa43b4",
|
||||
ColorType.LIGHT_ACCENT: "#cc8fdc",
|
||||
ColorType.DARK_ACCENT: "#41114a",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
@ -249,4 +286,4 @@ def get_tag_color(type: ColorType, color: str):
|
|||
else:
|
||||
return _TAG_COLORS[color][type]
|
||||
except KeyError:
|
||||
return '#FF00FF'
|
||||
return "#FF00FF"
|
||||
|
|
|
@ -9,223 +9,336 @@ import os
|
|||
|
||||
from src.core.library import Entry, Library
|
||||
|
||||
VERSION: str = '9.2.0' # Major.Minor.Patch
|
||||
VERSION_BRANCH: str = 'Alpha' # 'Alpha', 'Beta', or '' for Full Release
|
||||
VERSION: str = "9.2.0" # Major.Minor.Patch
|
||||
VERSION_BRANCH: str = "Alpha" # 'Alpha', 'Beta', or '' for Full Release
|
||||
|
||||
# The folder & file names where TagStudio keeps its data relative to a library.
|
||||
TS_FOLDER_NAME: str = '.TagStudio'
|
||||
BACKUP_FOLDER_NAME: str = 'backups'
|
||||
COLLAGE_FOLDER_NAME: str = 'collages'
|
||||
LIBRARY_FILENAME: str = 'ts_library.json'
|
||||
TS_FOLDER_NAME: str = ".TagStudio"
|
||||
BACKUP_FOLDER_NAME: str = "backups"
|
||||
COLLAGE_FOLDER_NAME: str = "collages"
|
||||
LIBRARY_FILENAME: str = "ts_library.json"
|
||||
|
||||
# TODO: Turn this whitelist into a user-configurable blacklist.
|
||||
IMAGE_TYPES: list[str] = ['png', 'jpg', 'jpeg', 'jpg_large', 'jpeg_large',
|
||||
'jfif', 'gif', 'tif', 'tiff', 'heic', 'heif', 'webp',
|
||||
'bmp', 'svg', 'avif', 'apng', 'jp2', 'j2k', 'jpg2']
|
||||
VIDEO_TYPES: list[str] = ['mp4', 'webm', 'mov', 'hevc', 'mkv', 'avi', 'wmv',
|
||||
'flv', 'gifv', 'm4p', 'm4v', '3gp']
|
||||
AUDIO_TYPES: list[str] = ['mp3', 'mp4', 'mpeg4', 'm4a', 'aac', 'wav', 'flac',
|
||||
'alac', 'wma', 'ogg', 'aiff']
|
||||
DOC_TYPES: list[str] = ['txt', 'rtf', 'md',
|
||||
'doc', 'docx', 'pdf', 'tex', 'odt', 'pages']
|
||||
PLAINTEXT_TYPES: list[str] = ['txt', 'md', 'css', 'html', 'xml', 'json', 'js',
|
||||
'ts', 'ini', 'htm', 'csv', 'php', 'sh', 'bat']
|
||||
SPREADSHEET_TYPES: list[str] = ['csv', 'xls', 'xlsx', 'numbers', 'ods']
|
||||
PRESENTATION_TYPES: list[str] = ['ppt', 'pptx', 'key', 'odp']
|
||||
ARCHIVE_TYPES: list[str] = ['zip', 'rar', 'tar', 'tar.gz', 'tgz', '7z']
|
||||
PROGRAM_TYPES: list[str] = ['exe', 'app']
|
||||
SHORTCUT_TYPES: list[str] = ['lnk', 'desktop', 'url']
|
||||
IMAGE_TYPES: list[str] = [
|
||||
"png",
|
||||
"jpg",
|
||||
"jpeg",
|
||||
"jpg_large",
|
||||
"jpeg_large",
|
||||
"jfif",
|
||||
"gif",
|
||||
"tif",
|
||||
"tiff",
|
||||
"heic",
|
||||
"heif",
|
||||
"webp",
|
||||
"bmp",
|
||||
"svg",
|
||||
"avif",
|
||||
"apng",
|
||||
"jp2",
|
||||
"j2k",
|
||||
"jpg2",
|
||||
]
|
||||
VIDEO_TYPES: list[str] = [
|
||||
"mp4",
|
||||
"webm",
|
||||
"mov",
|
||||
"hevc",
|
||||
"mkv",
|
||||
"avi",
|
||||
"wmv",
|
||||
"flv",
|
||||
"gifv",
|
||||
"m4p",
|
||||
"m4v",
|
||||
"3gp",
|
||||
]
|
||||
AUDIO_TYPES: list[str] = [
|
||||
"mp3",
|
||||
"mp4",
|
||||
"mpeg4",
|
||||
"m4a",
|
||||
"aac",
|
||||
"wav",
|
||||
"flac",
|
||||
"alac",
|
||||
"wma",
|
||||
"ogg",
|
||||
"aiff",
|
||||
]
|
||||
DOC_TYPES: list[str] = ["txt", "rtf", "md", "doc", "docx", "pdf", "tex", "odt", "pages"]
|
||||
PLAINTEXT_TYPES: list[str] = [
|
||||
"txt",
|
||||
"md",
|
||||
"css",
|
||||
"html",
|
||||
"xml",
|
||||
"json",
|
||||
"js",
|
||||
"ts",
|
||||
"ini",
|
||||
"htm",
|
||||
"csv",
|
||||
"php",
|
||||
"sh",
|
||||
"bat",
|
||||
]
|
||||
SPREADSHEET_TYPES: list[str] = ["csv", "xls", "xlsx", "numbers", "ods"]
|
||||
PRESENTATION_TYPES: list[str] = ["ppt", "pptx", "key", "odp"]
|
||||
ARCHIVE_TYPES: list[str] = ["zip", "rar", "tar", "tar.gz", "tgz", "7z"]
|
||||
PROGRAM_TYPES: list[str] = ["exe", "app"]
|
||||
SHORTCUT_TYPES: list[str] = ["lnk", "desktop", "url"]
|
||||
|
||||
ALL_FILE_TYPES: list[str] = IMAGE_TYPES + VIDEO_TYPES + AUDIO_TYPES + \
|
||||
DOC_TYPES + SPREADSHEET_TYPES + PRESENTATION_TYPES + \
|
||||
ARCHIVE_TYPES + PROGRAM_TYPES + SHORTCUT_TYPES
|
||||
ALL_FILE_TYPES: list[str] = (
|
||||
IMAGE_TYPES
|
||||
+ VIDEO_TYPES
|
||||
+ AUDIO_TYPES
|
||||
+ DOC_TYPES
|
||||
+ SPREADSHEET_TYPES
|
||||
+ PRESENTATION_TYPES
|
||||
+ ARCHIVE_TYPES
|
||||
+ PROGRAM_TYPES
|
||||
+ SHORTCUT_TYPES
|
||||
)
|
||||
|
||||
BOX_FIELDS = ['tag_box', 'text_box']
|
||||
TEXT_FIELDS = ['text_line', 'text_box']
|
||||
DATE_FIELDS = ['datetime']
|
||||
BOX_FIELDS = ["tag_box", "text_box"]
|
||||
TEXT_FIELDS = ["text_line", "text_box"]
|
||||
DATE_FIELDS = ["datetime"]
|
||||
|
||||
TAG_COLORS = ['', 'black', 'dark gray', 'gray', 'light gray', 'white', 'light pink',
|
||||
'pink', 'red', 'red orange', 'orange', 'yellow orange', 'yellow',
|
||||
'lime', 'light green', 'mint', 'green','teal', 'cyan', 'light blue',
|
||||
'blue', 'blue violet', 'violet', 'purple', 'lavender', 'berry',
|
||||
'magenta', 'salmon', 'auburn', 'dark brown', 'brown', 'light brown',
|
||||
'blonde', 'peach', 'warm gray', 'cool gray', 'olive']
|
||||
TAG_COLORS = [
|
||||
"",
|
||||
"black",
|
||||
"dark gray",
|
||||
"gray",
|
||||
"light gray",
|
||||
"white",
|
||||
"light pink",
|
||||
"pink",
|
||||
"red",
|
||||
"red orange",
|
||||
"orange",
|
||||
"yellow orange",
|
||||
"yellow",
|
||||
"lime",
|
||||
"light green",
|
||||
"mint",
|
||||
"green",
|
||||
"teal",
|
||||
"cyan",
|
||||
"light blue",
|
||||
"blue",
|
||||
"blue violet",
|
||||
"violet",
|
||||
"purple",
|
||||
"lavender",
|
||||
"berry",
|
||||
"magenta",
|
||||
"salmon",
|
||||
"auburn",
|
||||
"dark brown",
|
||||
"brown",
|
||||
"light brown",
|
||||
"blonde",
|
||||
"peach",
|
||||
"warm gray",
|
||||
"cool gray",
|
||||
"olive",
|
||||
]
|
||||
|
||||
|
||||
class TagStudioCore:
|
||||
"""
|
||||
Instantiate this to establish a TagStudio session.
|
||||
Holds all TagStudio session data and provides methods to manage it.
|
||||
"""
|
||||
"""
|
||||
Instantiate this to establish a TagStudio session.
|
||||
Holds all TagStudio session data and provides methods to manage it.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.lib: Library = Library()
|
||||
def __init__(self):
|
||||
self.lib: Library = Library()
|
||||
|
||||
def get_gdl_sidecar(self, filepath: str, source: str = "") -> dict:
|
||||
"""
|
||||
Attempts to open and dump a Gallery-DL Sidecar sidecar file for
|
||||
the filepath.\n Returns a formatted object with notable values or an
|
||||
empty object if none is found.
|
||||
"""
|
||||
json_dump = {}
|
||||
info = {}
|
||||
|
||||
def get_gdl_sidecar(self, filepath: str, source: str = '') -> dict:
|
||||
"""
|
||||
Attempts to open and dump a Gallery-DL Sidecar sidecar file for
|
||||
the filepath.\n Returns a formatted object with notable values or an
|
||||
empty object if none is found.
|
||||
"""
|
||||
json_dump = {}
|
||||
info = {}
|
||||
# NOTE: This fixes an unknown (recent?) bug in Gallery-DL where Instagram sidecar
|
||||
# files may be downloaded with indices starting at 1 rather than 0, unlike the posts.
|
||||
# This may only occur with sidecar files that are downloaded separate from posts.
|
||||
if source == "instagram":
|
||||
if not os.path.isfile(os.path.normpath(filepath + ".json")):
|
||||
filepath = filepath[:-16] + "1" + filepath[-15:]
|
||||
|
||||
# NOTE: This fixes an unknown (recent?) bug in Gallery-DL where Instagram sidecar
|
||||
# files may be downloaded with indices starting at 1 rather than 0, unlike the posts.
|
||||
# This may only occur with sidecar files that are downloaded separate from posts.
|
||||
if source == 'instagram':
|
||||
if not os.path.isfile(os.path.normpath(filepath + ".json")):
|
||||
filepath = filepath[:-16] + '1' + filepath[-15:]
|
||||
try:
|
||||
with open(os.path.normpath(filepath + ".json"), "r", encoding="utf8") as f:
|
||||
json_dump = json.load(f)
|
||||
|
||||
try:
|
||||
with open(os.path.normpath(filepath + ".json"), "r", encoding="utf8") as f:
|
||||
json_dump = json.load(f)
|
||||
if json_dump:
|
||||
if source == "twitter":
|
||||
info["content"] = json_dump["content"].strip()
|
||||
info["date_published"] = json_dump["date"]
|
||||
elif source == "instagram":
|
||||
info["description"] = json_dump["description"].strip()
|
||||
info["date_published"] = json_dump["date"]
|
||||
elif source == "artstation":
|
||||
info["title"] = json_dump["title"].strip()
|
||||
info["artist"] = json_dump["user"]["full_name"].strip()
|
||||
info["description"] = json_dump["description"].strip()
|
||||
info["tags"] = json_dump["tags"]
|
||||
# info["tags"] = [x for x in json_dump["mediums"]["name"]]
|
||||
info["date_published"] = json_dump["date"]
|
||||
elif source == "newgrounds":
|
||||
# info["title"] = json_dump["title"]
|
||||
# info["artist"] = json_dump["artist"]
|
||||
# info["description"] = json_dump["description"]
|
||||
info["tags"] = json_dump["tags"]
|
||||
info["date_published"] = json_dump["date"]
|
||||
info["artist"] = json_dump["user"].strip()
|
||||
info["description"] = json_dump["description"].strip()
|
||||
info["source"] = json_dump["post_url"].strip()
|
||||
# else:
|
||||
# print(
|
||||
# f'[INFO]: TagStudio does not currently support sidecar files for "{source}"')
|
||||
|
||||
if json_dump:
|
||||
if source == "twitter":
|
||||
info["content"] = json_dump["content"].strip()
|
||||
info["date_published"] = json_dump["date"]
|
||||
elif source == "instagram":
|
||||
info["description"] = json_dump["description"].strip()
|
||||
info["date_published"] = json_dump["date"]
|
||||
elif source == "artstation":
|
||||
info["title"] = json_dump["title"].strip()
|
||||
info["artist"] = json_dump["user"]["full_name"].strip()
|
||||
info["description"] = json_dump["description"].strip()
|
||||
info["tags"] = json_dump["tags"]
|
||||
# info["tags"] = [x for x in json_dump["mediums"]["name"]]
|
||||
info["date_published"] = json_dump["date"]
|
||||
elif source == "newgrounds":
|
||||
# info["title"] = json_dump["title"]
|
||||
# info["artist"] = json_dump["artist"]
|
||||
# info["description"] = json_dump["description"]
|
||||
info["tags"] = json_dump["tags"]
|
||||
info["date_published"] = json_dump["date"]
|
||||
info["artist"] = json_dump["user"].strip()
|
||||
info["description"] = json_dump["description"].strip()
|
||||
info["source"] = json_dump["post_url"].strip()
|
||||
# else:
|
||||
# print(
|
||||
# f'[INFO]: TagStudio does not currently support sidecar files for "{source}"')
|
||||
# except FileNotFoundError:
|
||||
except:
|
||||
# print(
|
||||
# f'[INFO]: No sidecar file found at "{os.path.normpath(file_path + ".json")}"')
|
||||
pass
|
||||
|
||||
# except FileNotFoundError:
|
||||
except:
|
||||
# print(
|
||||
# f'[INFO]: No sidecar file found at "{os.path.normpath(file_path + ".json")}"')
|
||||
pass
|
||||
return info
|
||||
|
||||
return info
|
||||
# def scrape(self, entry_id):
|
||||
# entry = self.lib.get_entry(entry_id)
|
||||
# if entry.fields:
|
||||
# urls: list[str] = []
|
||||
# if self.lib.get_field_index_in_entry(entry, 21):
|
||||
# urls.extend([self.lib.get_field_attr(entry.fields[x], 'content')
|
||||
# for x in self.lib.get_field_index_in_entry(entry, 21)])
|
||||
# if self.lib.get_field_index_in_entry(entry, 3):
|
||||
# urls.extend([self.lib.get_field_attr(entry.fields[x], 'content')
|
||||
# for x in self.lib.get_field_index_in_entry(entry, 3)])
|
||||
# # try:
|
||||
# if urls:
|
||||
# for url in urls:
|
||||
# url = "https://" + url if 'https://' not in url else url
|
||||
# html_doc = requests.get(url).text
|
||||
# soup = bs(html_doc, "html.parser")
|
||||
# print(soup)
|
||||
# input()
|
||||
|
||||
# def scrape(self, entry_id):
|
||||
# entry = self.lib.get_entry(entry_id)
|
||||
# if entry.fields:
|
||||
# urls: list[str] = []
|
||||
# if self.lib.get_field_index_in_entry(entry, 21):
|
||||
# urls.extend([self.lib.get_field_attr(entry.fields[x], 'content')
|
||||
# for x in self.lib.get_field_index_in_entry(entry, 21)])
|
||||
# if self.lib.get_field_index_in_entry(entry, 3):
|
||||
# urls.extend([self.lib.get_field_attr(entry.fields[x], 'content')
|
||||
# for x in self.lib.get_field_index_in_entry(entry, 3)])
|
||||
# # try:
|
||||
# if urls:
|
||||
# for url in urls:
|
||||
# url = "https://" + url if 'https://' not in url else url
|
||||
# html_doc = requests.get(url).text
|
||||
# soup = bs(html_doc, "html.parser")
|
||||
# print(soup)
|
||||
# input()
|
||||
# # except:
|
||||
# # # print("Could not resolve URL.")
|
||||
# # pass
|
||||
|
||||
# # except:
|
||||
# # # print("Could not resolve URL.")
|
||||
# # pass
|
||||
def match_conditions(self, entry_id: int) -> None:
|
||||
"""Matches defined conditions against a file to add Entry data."""
|
||||
|
||||
def match_conditions(self, entry_id: int) -> None:
|
||||
"""Matches defined conditions against a file to add Entry data."""
|
||||
cond_file = os.path.normpath(
|
||||
f"{self.lib.library_dir}/{TS_FOLDER_NAME}/conditions.json"
|
||||
)
|
||||
# TODO: Make this stored somewhere better instead of temporarily in this JSON file.
|
||||
entry: Entry = self.lib.get_entry(entry_id)
|
||||
try:
|
||||
if os.path.isfile(cond_file):
|
||||
with open(cond_file, "r", encoding="utf8") as f:
|
||||
json_dump = json.load(f)
|
||||
for c in json_dump["conditions"]:
|
||||
match: bool = False
|
||||
for path_c in c["path_conditions"]:
|
||||
if os.path.normpath(path_c) in entry.path:
|
||||
match = True
|
||||
break
|
||||
if match:
|
||||
if fields := c.get("fields"):
|
||||
for field in fields:
|
||||
field_id = self.lib.get_field_attr(field, "id")
|
||||
content = field[field_id]
|
||||
|
||||
cond_file = os.path.normpath(f'{self.lib.library_dir}/{TS_FOLDER_NAME}/conditions.json')
|
||||
# TODO: Make this stored somewhere better instead of temporarily in this JSON file.
|
||||
entry: Entry = self.lib.get_entry(entry_id)
|
||||
try:
|
||||
if os.path.isfile(cond_file):
|
||||
with open(cond_file, "r", encoding="utf8") as f:
|
||||
json_dump = json.load(f)
|
||||
for c in json_dump['conditions']:
|
||||
match: bool = False
|
||||
for path_c in c['path_conditions']:
|
||||
if os.path.normpath(path_c) in entry.path:
|
||||
match = True
|
||||
break
|
||||
if match:
|
||||
if fields := c.get('fields'):
|
||||
for field in fields:
|
||||
if (
|
||||
self.lib.get_field_obj(int(field_id))["type"]
|
||||
== "tag_box"
|
||||
):
|
||||
existing_fields: list[int] = (
|
||||
self.lib.get_field_index_in_entry(
|
||||
entry, field_id
|
||||
)
|
||||
)
|
||||
if existing_fields:
|
||||
self.lib.update_entry_field(
|
||||
entry_id,
|
||||
existing_fields[0],
|
||||
content,
|
||||
"append",
|
||||
)
|
||||
else:
|
||||
self.lib.add_field_to_entry(
|
||||
entry_id, field_id
|
||||
)
|
||||
self.lib.update_entry_field(
|
||||
entry_id, -1, content, "append"
|
||||
)
|
||||
|
||||
field_id = self.lib.get_field_attr(
|
||||
field, 'id')
|
||||
content = field[field_id]
|
||||
if (
|
||||
self.lib.get_field_obj(int(field_id))["type"]
|
||||
in TEXT_FIELDS
|
||||
):
|
||||
if not self.lib.does_field_content_exist(
|
||||
entry_id, field_id, content
|
||||
):
|
||||
self.lib.add_field_to_entry(
|
||||
entry_id, field_id
|
||||
)
|
||||
self.lib.update_entry_field(
|
||||
entry_id, -1, content, "replace"
|
||||
)
|
||||
except:
|
||||
print("Error in match_conditions...")
|
||||
# input()
|
||||
pass
|
||||
|
||||
if self.lib.get_field_obj(int(field_id))['type'] == 'tag_box':
|
||||
existing_fields: list[int] = self.lib.get_field_index_in_entry(
|
||||
entry, field_id)
|
||||
if existing_fields:
|
||||
self.lib.update_entry_field(
|
||||
entry_id, existing_fields[0], content, 'append')
|
||||
else:
|
||||
self.lib.add_field_to_entry(
|
||||
entry_id, field_id)
|
||||
self.lib.update_entry_field(
|
||||
entry_id, -1, content, 'append')
|
||||
def build_url(self, entry_id: int, source: str) -> str:
|
||||
"""Tries to rebuild a source URL given a specific filename structure."""
|
||||
|
||||
if self.lib.get_field_obj(int(field_id))['type'] in TEXT_FIELDS:
|
||||
if not self.lib.does_field_content_exist(entry_id, field_id, content):
|
||||
self.lib.add_field_to_entry(
|
||||
entry_id, field_id)
|
||||
self.lib.update_entry_field(
|
||||
entry_id, -1, content, 'replace')
|
||||
except:
|
||||
print('Error in match_conditions...')
|
||||
# input()
|
||||
pass
|
||||
source = source.lower().replace("-", " ").replace("_", " ")
|
||||
if "twitter" in source:
|
||||
return self._build_twitter_url(entry_id)
|
||||
elif "instagram" in source:
|
||||
return self._build_instagram_url(entry_id)
|
||||
|
||||
def build_url(self, entry_id: int, source: str) -> str:
|
||||
"""Tries to rebuild a source URL given a specific filename structure."""
|
||||
def _build_twitter_url(self, entry_id: int):
|
||||
"""
|
||||
Builds an Twitter URL given a specific filename structure.
|
||||
Method expects filename to be formatted as 'USERNAME_TWEET-ID_INDEX_YEAR-MM-DD'
|
||||
"""
|
||||
try:
|
||||
entry = self.lib.get_entry(entry_id)
|
||||
stubs = entry.filename.rsplit("_", 3)
|
||||
# print(stubs)
|
||||
# source, author = os.path.split(entry.path)
|
||||
url = f"www.twitter.com/{stubs[0]}/status/{stubs[-3]}/photo/{stubs[-2]}"
|
||||
return url
|
||||
except:
|
||||
return ""
|
||||
|
||||
source = source.lower().replace('-', ' ').replace('_', ' ')
|
||||
if 'twitter' in source:
|
||||
return self._build_twitter_url(entry_id)
|
||||
elif 'instagram' in source:
|
||||
return self._build_instagram_url(entry_id)
|
||||
|
||||
def _build_twitter_url(self, entry_id: int):
|
||||
"""
|
||||
Builds an Twitter URL given a specific filename structure.
|
||||
Method expects filename to be formatted as 'USERNAME_TWEET-ID_INDEX_YEAR-MM-DD'
|
||||
"""
|
||||
try:
|
||||
entry = self.lib.get_entry(entry_id)
|
||||
stubs = entry.filename.rsplit('_', 3)
|
||||
# print(stubs)
|
||||
# source, author = os.path.split(entry.path)
|
||||
url = f"www.twitter.com/{stubs[0]}/status/{stubs[-3]}/photo/{stubs[-2]}"
|
||||
return url
|
||||
except:
|
||||
return ''
|
||||
|
||||
def _build_instagram_url(self, entry_id: int):
|
||||
"""
|
||||
Builds an Instagram URL given a specific filename structure.
|
||||
Method expects filename to be formatted as 'USERNAME_POST-ID_INDEX_YEAR-MM-DD'
|
||||
"""
|
||||
try:
|
||||
entry = self.lib.get_entry(entry_id)
|
||||
stubs = entry.filename.rsplit('_', 2)
|
||||
# stubs[0] = stubs[0].replace(f"{author}_", '', 1)
|
||||
# print(stubs)
|
||||
# NOTE: Both Instagram usernames AND their ID can have underscores in them,
|
||||
# so unless you have the exact username (which can change) on hand to remove,
|
||||
# your other best bet is to hope that the ID is only 11 characters long, which
|
||||
# seems to more or less be the case... for now...
|
||||
url = f"www.instagram.com/p/{stubs[-3][-11:]}"
|
||||
return url
|
||||
except:
|
||||
return ''
|
||||
def _build_instagram_url(self, entry_id: int):
|
||||
"""
|
||||
Builds an Instagram URL given a specific filename structure.
|
||||
Method expects filename to be formatted as 'USERNAME_POST-ID_INDEX_YEAR-MM-DD'
|
||||
"""
|
||||
try:
|
||||
entry = self.lib.get_entry(entry_id)
|
||||
stubs = entry.filename.rsplit("_", 2)
|
||||
# stubs[0] = stubs[0].replace(f"{author}_", '', 1)
|
||||
# print(stubs)
|
||||
# NOTE: Both Instagram usernames AND their ID can have underscores in them,
|
||||
# so unless you have the exact username (which can change) on hand to remove,
|
||||
# your other best bet is to hope that the ID is only 11 characters long, which
|
||||
# seems to more or less be the case... for now...
|
||||
url = f"www.instagram.com/p/{stubs[-3][-11:]}"
|
||||
return url
|
||||
except:
|
||||
return ""
|
||||
|
|
|
@ -2,9 +2,10 @@
|
|||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
|
||||
def clean_folder_name(folder_name: str) -> str:
|
||||
cleaned_name = folder_name
|
||||
invalid_chars = "<>:\"/\\|?*."
|
||||
invalid_chars = '<>:"/\\|?*.'
|
||||
for char in invalid_chars:
|
||||
cleaned_name = cleaned_name.replace(char, '_')
|
||||
cleaned_name = cleaned_name.replace(char, "_")
|
||||
return cleaned_name
|
||||
|
|
|
@ -2,10 +2,25 @@
|
|||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
|
||||
def strip_punctuation(string: str) -> str:
|
||||
"""Returns a given string stripped of all punctuation characters."""
|
||||
return string.replace('(', '').replace(')', '').replace('[', '') \
|
||||
.replace(']', '').replace('{', '').replace('}', '').replace("'", '') \
|
||||
.replace('`', '').replace('’', '').replace('‘', '').replace('"', '') \
|
||||
.replace('“', '').replace('”', '').replace('_', '').replace('-', '') \
|
||||
.replace(' ', '').replace(' ', '')
|
||||
return (
|
||||
string.replace("(", "")
|
||||
.replace(")", "")
|
||||
.replace("[", "")
|
||||
.replace("]", "")
|
||||
.replace("{", "")
|
||||
.replace("}", "")
|
||||
.replace("'", "")
|
||||
.replace("`", "")
|
||||
.replace("’", "")
|
||||
.replace("‘", "")
|
||||
.replace('"', "")
|
||||
.replace("“", "")
|
||||
.replace("”", "")
|
||||
.replace("_", "")
|
||||
.replace("-", "")
|
||||
.replace(" ", "")
|
||||
.replace(" ", "")
|
||||
)
|
||||
|
|
|
@ -2,9 +2,10 @@
|
|||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
|
||||
def strip_web_protocol(string: str) -> str:
|
||||
"""Strips a leading web protocol (ex. \"https://\") as well as \"www.\" from a string."""
|
||||
prefixes = ['https://','http://','www.','www2.']
|
||||
prefixes = ["https://", "http://", "www.", "www2."]
|
||||
for prefix in prefixes:
|
||||
string = string.removeprefix(prefix)
|
||||
return string
|
||||
|
|
|
@ -21,143 +21,153 @@ from PySide6.QtWidgets import QLayout, QSizePolicy, QWidget
|
|||
|
||||
# self.setWindowTitle("Flow Layout")
|
||||
|
||||
|
||||
class FlowWidget(QWidget):
|
||||
def __init__(self, parent=None) -> None:
|
||||
super().__init__(parent)
|
||||
self.ignore_size: bool = False
|
||||
def __init__(self, parent=None) -> None:
|
||||
super().__init__(parent)
|
||||
self.ignore_size: bool = False
|
||||
|
||||
|
||||
class FlowLayout(QLayout):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
if parent is not None:
|
||||
self.setContentsMargins(QMargins(0, 0, 0, 0))
|
||||
if parent is not None:
|
||||
self.setContentsMargins(QMargins(0, 0, 0, 0))
|
||||
|
||||
self._item_list = []
|
||||
self.grid_efficiency = False
|
||||
self._item_list = []
|
||||
self.grid_efficiency = False
|
||||
|
||||
def __del__(self):
|
||||
item = self.takeAt(0)
|
||||
while item:
|
||||
item = self.takeAt(0)
|
||||
def __del__(self):
|
||||
item = self.takeAt(0)
|
||||
while item:
|
||||
item = self.takeAt(0)
|
||||
|
||||
def addItem(self, item):
|
||||
self._item_list.append(item)
|
||||
def addItem(self, item):
|
||||
self._item_list.append(item)
|
||||
|
||||
def count(self):
|
||||
return len(self._item_list)
|
||||
def count(self):
|
||||
return len(self._item_list)
|
||||
|
||||
def itemAt(self, index):
|
||||
if 0 <= index < len(self._item_list):
|
||||
return self._item_list[index]
|
||||
def itemAt(self, index):
|
||||
if 0 <= index < len(self._item_list):
|
||||
return self._item_list[index]
|
||||
|
||||
return None
|
||||
return None
|
||||
|
||||
def takeAt(self, index):
|
||||
if 0 <= index < len(self._item_list):
|
||||
return self._item_list.pop(index)
|
||||
def takeAt(self, index):
|
||||
if 0 <= index < len(self._item_list):
|
||||
return self._item_list.pop(index)
|
||||
|
||||
return None
|
||||
return None
|
||||
|
||||
def expandingDirections(self):
|
||||
return Qt.Orientation(0)
|
||||
def expandingDirections(self):
|
||||
return Qt.Orientation(0)
|
||||
|
||||
def hasHeightForWidth(self):
|
||||
return True
|
||||
def hasHeightForWidth(self):
|
||||
return True
|
||||
|
||||
def heightForWidth(self, width):
|
||||
height = self._do_layout(QRect(0, 0, width, 0), True)
|
||||
return height
|
||||
def heightForWidth(self, width):
|
||||
height = self._do_layout(QRect(0, 0, width, 0), True)
|
||||
return height
|
||||
|
||||
def setGeometry(self, rect):
|
||||
super(FlowLayout, self).setGeometry(rect)
|
||||
self._do_layout(rect, False)
|
||||
|
||||
def setGridEfficiency(self, bool):
|
||||
"""
|
||||
Enables or Disables efficiencies when all objects are equally sized.
|
||||
"""
|
||||
self.grid_efficiency = bool
|
||||
def setGeometry(self, rect):
|
||||
super(FlowLayout, self).setGeometry(rect)
|
||||
self._do_layout(rect, False)
|
||||
|
||||
def sizeHint(self):
|
||||
return self.minimumSize()
|
||||
def setGridEfficiency(self, bool):
|
||||
"""
|
||||
Enables or Disables efficiencies when all objects are equally sized.
|
||||
"""
|
||||
self.grid_efficiency = bool
|
||||
|
||||
def minimumSize(self):
|
||||
if self.grid_efficiency:
|
||||
if self._item_list:
|
||||
return self._item_list[0].minimumSize()
|
||||
else:
|
||||
return QSize()
|
||||
else:
|
||||
size = QSize()
|
||||
def sizeHint(self):
|
||||
return self.minimumSize()
|
||||
|
||||
for item in self._item_list:
|
||||
size = size.expandedTo(item.minimumSize())
|
||||
def minimumSize(self):
|
||||
if self.grid_efficiency:
|
||||
if self._item_list:
|
||||
return self._item_list[0].minimumSize()
|
||||
else:
|
||||
return QSize()
|
||||
else:
|
||||
size = QSize()
|
||||
|
||||
size += QSize(2 * self.contentsMargins().top(), 2 * self.contentsMargins().top())
|
||||
return size
|
||||
|
||||
|
||||
for item in self._item_list:
|
||||
size = size.expandedTo(item.minimumSize())
|
||||
|
||||
def _do_layout(self, rect, test_only):
|
||||
x = rect.x()
|
||||
y = rect.y()
|
||||
line_height = 0
|
||||
spacing = self.spacing()
|
||||
item = None
|
||||
style = None
|
||||
layout_spacing_x = None
|
||||
layout_spacing_y = None
|
||||
size += QSize(
|
||||
2 * self.contentsMargins().top(), 2 * self.contentsMargins().top()
|
||||
)
|
||||
return size
|
||||
|
||||
if self.grid_efficiency:
|
||||
if self._item_list:
|
||||
item = self._item_list[0]
|
||||
style = item.widget().style()
|
||||
layout_spacing_x = style.layoutSpacing(
|
||||
QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Horizontal
|
||||
)
|
||||
layout_spacing_y = style.layoutSpacing(
|
||||
QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Vertical
|
||||
)
|
||||
for i, item in enumerate(self._item_list):
|
||||
# print(issubclass(type(item.widget()), FlowWidget))
|
||||
# print(item.widget().ignore_size)
|
||||
skip_count = 0
|
||||
if (issubclass(type(item.widget()), FlowWidget) and item.widget().ignore_size):
|
||||
skip_count += 1
|
||||
def _do_layout(self, rect, test_only):
|
||||
x = rect.x()
|
||||
y = rect.y()
|
||||
line_height = 0
|
||||
spacing = self.spacing()
|
||||
item = None
|
||||
style = None
|
||||
layout_spacing_x = None
|
||||
layout_spacing_y = None
|
||||
|
||||
if (issubclass(type(item.widget()), FlowWidget) and not item.widget().ignore_size) or (not issubclass(type(item.widget()), FlowWidget)):
|
||||
# print(f'Item {i}')
|
||||
if not self.grid_efficiency:
|
||||
style = item.widget().style()
|
||||
layout_spacing_x = style.layoutSpacing(
|
||||
QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Horizontal
|
||||
)
|
||||
layout_spacing_y = style.layoutSpacing(
|
||||
QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Vertical
|
||||
)
|
||||
space_x = spacing + layout_spacing_x
|
||||
space_y = spacing + layout_spacing_y
|
||||
next_x = x + item.sizeHint().width() + space_x
|
||||
if next_x - space_x > rect.right() and line_height > 0:
|
||||
x = rect.x()
|
||||
y = y + line_height + space_y
|
||||
next_x = x + item.sizeHint().width() + space_x
|
||||
line_height = 0
|
||||
if self.grid_efficiency:
|
||||
if self._item_list:
|
||||
item = self._item_list[0]
|
||||
style = item.widget().style()
|
||||
layout_spacing_x = style.layoutSpacing(
|
||||
QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Horizontal
|
||||
)
|
||||
layout_spacing_y = style.layoutSpacing(
|
||||
QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Vertical
|
||||
)
|
||||
for i, item in enumerate(self._item_list):
|
||||
# print(issubclass(type(item.widget()), FlowWidget))
|
||||
# print(item.widget().ignore_size)
|
||||
skip_count = 0
|
||||
if (
|
||||
issubclass(type(item.widget()), FlowWidget)
|
||||
and item.widget().ignore_size
|
||||
):
|
||||
skip_count += 1
|
||||
|
||||
if not test_only:
|
||||
item.setGeometry(QRect(QPoint(x, y), item.sizeHint()))
|
||||
if (
|
||||
issubclass(type(item.widget()), FlowWidget)
|
||||
and not item.widget().ignore_size
|
||||
) or (not issubclass(type(item.widget()), FlowWidget)):
|
||||
# print(f'Item {i}')
|
||||
if not self.grid_efficiency:
|
||||
style = item.widget().style()
|
||||
layout_spacing_x = style.layoutSpacing(
|
||||
QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Horizontal
|
||||
)
|
||||
layout_spacing_y = style.layoutSpacing(
|
||||
QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Vertical
|
||||
)
|
||||
space_x = spacing + layout_spacing_x
|
||||
space_y = spacing + layout_spacing_y
|
||||
next_x = x + item.sizeHint().width() + space_x
|
||||
if next_x - space_x > rect.right() and line_height > 0:
|
||||
x = rect.x()
|
||||
y = y + line_height + space_y
|
||||
next_x = x + item.sizeHint().width() + space_x
|
||||
line_height = 0
|
||||
|
||||
x = next_x
|
||||
line_height = max(line_height, item.sizeHint().height())
|
||||
if not test_only:
|
||||
item.setGeometry(QRect(QPoint(x, y), item.sizeHint()))
|
||||
|
||||
# print(y + line_height - rect.y() * ((len(self._item_list) - skip_count) / len(self._item_list)))
|
||||
# print(y + line_height - rect.y()) * ((len(self._item_list) - skip_count) / len(self._item_list))
|
||||
return y + line_height - rect.y() * ((len(self._item_list)) / len(self._item_list))
|
||||
x = next_x
|
||||
line_height = max(line_height, item.sizeHint().height())
|
||||
|
||||
# print(y + line_height - rect.y() * ((len(self._item_list) - skip_count) / len(self._item_list)))
|
||||
# print(y + line_height - rect.y()) * ((len(self._item_list) - skip_count) / len(self._item_list))
|
||||
return (
|
||||
y + line_height - rect.y() * ((len(self._item_list)) / len(self._item_list))
|
||||
)
|
||||
|
||||
|
||||
# if __name__ == "__main__":
|
||||
# app = QApplication(sys.argv)
|
||||
# main_win = Window()
|
||||
# main_win.show()
|
||||
# sys.exit(app.exec())
|
||||
# sys.exit(app.exec())
|
||||
|
|
|
@ -16,4 +16,4 @@ class CustomRunnable(QRunnable, QObject):
|
|||
|
||||
def run(self):
|
||||
self.function()
|
||||
self.done.emit()
|
||||
self.done.emit()
|
||||
|
|
|
@ -12,121 +12,139 @@ import traceback
|
|||
from PySide6.QtWidgets import QLabel
|
||||
from PySide6.QtCore import Qt
|
||||
|
||||
ERROR = f'[ERROR]'
|
||||
WARNING = f'[WARNING]'
|
||||
INFO = f'[INFO]'
|
||||
ERROR = f"[ERROR]"
|
||||
WARNING = f"[WARNING]"
|
||||
INFO = f"[INFO]"
|
||||
|
||||
logging.basicConfig(format="%(message)s", level=logging.INFO)
|
||||
|
||||
|
||||
def open_file(path: str, file_manager: bool = False):
|
||||
"""Open a file in the default application or file explorer.
|
||||
"""Open a file in the default application or file explorer.
|
||||
|
||||
Args:
|
||||
path (str): The path to the file to open.
|
||||
file_manager (bool, optional): Whether to open the file in the file manager (e.g. Finder on macOS).
|
||||
Defaults to False.
|
||||
"""
|
||||
logging.info(f'Opening file: {path}')
|
||||
if not os.path.exists(path):
|
||||
logging.error(f'File not found: {path}')
|
||||
return
|
||||
try:
|
||||
if sys.platform == "win32":
|
||||
normpath = os.path.normpath(path)
|
||||
if file_manager:
|
||||
command_name = "explorer"
|
||||
command_args = '/select,"' + normpath + '"'
|
||||
# For some reason, if the args are passed in a list, this will error when the path has spaces, even while surrounded in double quotes
|
||||
subprocess.Popen(command_name + command_args, shell=True, close_fds=True, creationflags=subprocess.CREATE_NEW_PROCESS_GROUP | subprocess.CREATE_BREAKAWAY_FROM_JOB)
|
||||
else:
|
||||
command_name = "start"
|
||||
# first parameter is for title, NOT filepath
|
||||
command_args = ["", normpath]
|
||||
subprocess.Popen([command_name] + command_args, shell=True, close_fds=True, creationflags=subprocess.CREATE_NEW_PROCESS_GROUP | subprocess.CREATE_BREAKAWAY_FROM_JOB)
|
||||
else:
|
||||
if sys.platform == "darwin":
|
||||
command_name = "open"
|
||||
command_args = [path]
|
||||
if file_manager:
|
||||
# will reveal in Finder
|
||||
command_args.append("-R")
|
||||
else:
|
||||
if file_manager:
|
||||
command_name = "dbus-send"
|
||||
# might not be guaranteed to launch default?
|
||||
command_args = ["--session", "--dest=org.freedesktop.FileManager1", "--type=method_call",
|
||||
"/org/freedesktop/FileManager1", "org.freedesktop.FileManager1.ShowItems",
|
||||
f"array:string:file://{path}", "string:"]
|
||||
else:
|
||||
command_name = "xdg-open"
|
||||
command_args = [path]
|
||||
command = shutil.which(command_name)
|
||||
if command is not None:
|
||||
subprocess.Popen([command] + command_args, close_fds=True)
|
||||
else:
|
||||
logging.info(f"Could not find {command_name} on system PATH")
|
||||
except:
|
||||
traceback.print_exc()
|
||||
Args:
|
||||
path (str): The path to the file to open.
|
||||
file_manager (bool, optional): Whether to open the file in the file manager (e.g. Finder on macOS).
|
||||
Defaults to False.
|
||||
"""
|
||||
logging.info(f"Opening file: {path}")
|
||||
if not os.path.exists(path):
|
||||
logging.error(f"File not found: {path}")
|
||||
return
|
||||
try:
|
||||
if sys.platform == "win32":
|
||||
normpath = os.path.normpath(path)
|
||||
if file_manager:
|
||||
command_name = "explorer"
|
||||
command_args = '/select,"' + normpath + '"'
|
||||
# For some reason, if the args are passed in a list, this will error when the path has spaces, even while surrounded in double quotes
|
||||
subprocess.Popen(
|
||||
command_name + command_args,
|
||||
shell=True,
|
||||
close_fds=True,
|
||||
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP
|
||||
| subprocess.CREATE_BREAKAWAY_FROM_JOB,
|
||||
)
|
||||
else:
|
||||
command_name = "start"
|
||||
# first parameter is for title, NOT filepath
|
||||
command_args = ["", normpath]
|
||||
subprocess.Popen(
|
||||
[command_name] + command_args,
|
||||
shell=True,
|
||||
close_fds=True,
|
||||
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP
|
||||
| subprocess.CREATE_BREAKAWAY_FROM_JOB,
|
||||
)
|
||||
else:
|
||||
if sys.platform == "darwin":
|
||||
command_name = "open"
|
||||
command_args = [path]
|
||||
if file_manager:
|
||||
# will reveal in Finder
|
||||
command_args.append("-R")
|
||||
else:
|
||||
if file_manager:
|
||||
command_name = "dbus-send"
|
||||
# might not be guaranteed to launch default?
|
||||
command_args = [
|
||||
"--session",
|
||||
"--dest=org.freedesktop.FileManager1",
|
||||
"--type=method_call",
|
||||
"/org/freedesktop/FileManager1",
|
||||
"org.freedesktop.FileManager1.ShowItems",
|
||||
f"array:string:file://{path}",
|
||||
"string:",
|
||||
]
|
||||
else:
|
||||
command_name = "xdg-open"
|
||||
command_args = [path]
|
||||
command = shutil.which(command_name)
|
||||
if command is not None:
|
||||
subprocess.Popen([command] + command_args, close_fds=True)
|
||||
else:
|
||||
logging.info(f"Could not find {command_name} on system PATH")
|
||||
except:
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
class FileOpenerHelper:
|
||||
def __init__(self, filepath: str):
|
||||
"""Initialize the FileOpenerHelper.
|
||||
def __init__(self, filepath: str):
|
||||
"""Initialize the FileOpenerHelper.
|
||||
|
||||
Args:
|
||||
filepath (str): The path to the file to open.
|
||||
"""
|
||||
self.filepath = filepath
|
||||
Args:
|
||||
filepath (str): The path to the file to open.
|
||||
"""
|
||||
self.filepath = filepath
|
||||
|
||||
def set_filepath(self, filepath: str):
|
||||
"""Set the filepath to open.
|
||||
def set_filepath(self, filepath: str):
|
||||
"""Set the filepath to open.
|
||||
|
||||
Args:
|
||||
filepath (str): The path to the file to open.
|
||||
"""
|
||||
self.filepath = filepath
|
||||
Args:
|
||||
filepath (str): The path to the file to open.
|
||||
"""
|
||||
self.filepath = filepath
|
||||
|
||||
def open_file(self):
|
||||
"""Open the file in the default application."""
|
||||
open_file(self.filepath)
|
||||
def open_file(self):
|
||||
"""Open the file in the default application."""
|
||||
open_file(self.filepath)
|
||||
|
||||
def open_explorer(self):
|
||||
"""Open the file in the default file explorer."""
|
||||
open_file(self.filepath, file_manager=True)
|
||||
def open_explorer(self):
|
||||
"""Open the file in the default file explorer."""
|
||||
open_file(self.filepath, file_manager=True)
|
||||
|
||||
|
||||
class FileOpenerLabel(QLabel):
|
||||
def __init__(self, text, parent=None):
|
||||
"""Initialize the FileOpenerLabel.
|
||||
def __init__(self, text, parent=None):
|
||||
"""Initialize the FileOpenerLabel.
|
||||
|
||||
Args:
|
||||
text (str): The text to display.
|
||||
parent (QWidget, optional): The parent widget. Defaults to None.
|
||||
"""
|
||||
super().__init__(text, parent)
|
||||
Args:
|
||||
text (str): The text to display.
|
||||
parent (QWidget, optional): The parent widget. Defaults to None.
|
||||
"""
|
||||
super().__init__(text, parent)
|
||||
|
||||
def setFilePath(self, filepath):
|
||||
"""Set the filepath to open.
|
||||
def setFilePath(self, filepath):
|
||||
"""Set the filepath to open.
|
||||
|
||||
Args:
|
||||
filepath (str): The path to the file to open.
|
||||
"""
|
||||
self.filepath = filepath
|
||||
Args:
|
||||
filepath (str): The path to the file to open.
|
||||
"""
|
||||
self.filepath = filepath
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
"""Handle mouse press events.
|
||||
def mousePressEvent(self, event):
|
||||
"""Handle mouse press events.
|
||||
|
||||
On a left click, open the file in the default file explorer. On a right click, show a context menu.
|
||||
|
||||
Args:
|
||||
event (QMouseEvent): The mouse press event.
|
||||
"""
|
||||
super().mousePressEvent(event)
|
||||
On a left click, open the file in the default file explorer. On a right click, show a context menu.
|
||||
|
||||
if event.button() == Qt.LeftButton:
|
||||
opener = FileOpenerHelper(self.filepath)
|
||||
opener.open_explorer()
|
||||
elif event.button() == Qt.RightButton:
|
||||
# Show context menu
|
||||
pass
|
||||
Args:
|
||||
event (QMouseEvent): The mouse press event.
|
||||
"""
|
||||
super().mousePressEvent(event)
|
||||
|
||||
if event.button() == Qt.LeftButton:
|
||||
opener = FileOpenerHelper(self.filepath)
|
||||
opener.open_explorer()
|
||||
elif event.button() == Qt.RightButton:
|
||||
# Show context menu
|
||||
pass
|
||||
|
|
|
@ -10,6 +10,7 @@ from PySide6.QtCore import Signal, QObject
|
|||
|
||||
class FunctionIterator(QObject):
|
||||
"""Iterates over a yielding function and emits progress as the 'value' signal.\n\nThread-Safe Guarantee™"""
|
||||
|
||||
value = Signal(object)
|
||||
|
||||
def __init__(self, function: FunctionType):
|
||||
|
|
|
@ -8,4 +8,4 @@ from .relink_unlinked import RelinkUnlinkedEntries
|
|||
from .fix_unlinked import FixUnlinkedEntriesModal
|
||||
from .mirror_entities import MirrorEntriesModal
|
||||
from .fix_dupes import FixDupeFilesModal
|
||||
from .folders_to_tags import FoldersToTagsModal
|
||||
from .folders_to_tags import FoldersToTagsModal
|
||||
|
|
|
@ -4,75 +4,87 @@
|
|||
|
||||
|
||||
from PySide6.QtCore import Signal, Qt
|
||||
from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QComboBox
|
||||
from PySide6.QtWidgets import (
|
||||
QWidget,
|
||||
QVBoxLayout,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QPushButton,
|
||||
QComboBox,
|
||||
)
|
||||
|
||||
from src.core.library import Library
|
||||
|
||||
|
||||
class AddFieldModal(QWidget):
|
||||
done = Signal(int)
|
||||
def __init__(self, library:'Library'):
|
||||
# [Done]
|
||||
# - OR -
|
||||
# [Cancel] [Save]
|
||||
super().__init__()
|
||||
self.lib = library
|
||||
self.setWindowTitle(f'Add Field')
|
||||
self.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||
self.setMinimumSize(400, 300)
|
||||
self.root_layout = QVBoxLayout(self)
|
||||
self.root_layout.setContentsMargins(6,6,6,6)
|
||||
done = Signal(int)
|
||||
|
||||
self.title_widget = QLabel()
|
||||
self.title_widget.setObjectName('fieldTitle')
|
||||
self.title_widget.setWordWrap(True)
|
||||
self.title_widget.setStyleSheet(
|
||||
# 'background:blue;'
|
||||
# 'text-align:center;'
|
||||
'font-weight:bold;'
|
||||
'font-size:14px;'
|
||||
'padding-top: 6px'
|
||||
'')
|
||||
self.title_widget.setText('Add Field')
|
||||
self.title_widget.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
self.combo_box = QComboBox()
|
||||
self.combo_box.setEditable(False)
|
||||
# self.combo_box.setMaxVisibleItems(5)
|
||||
self.combo_box.setStyleSheet('combobox-popup:0;')
|
||||
self.combo_box.view().setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
|
||||
for df in self.lib.default_fields:
|
||||
self.combo_box.addItem(f'{df["name"]} ({df["type"].replace("_", " ").title()})')
|
||||
def __init__(self, library: "Library"):
|
||||
# [Done]
|
||||
# - OR -
|
||||
# [Cancel] [Save]
|
||||
super().__init__()
|
||||
self.lib = library
|
||||
self.setWindowTitle(f"Add Field")
|
||||
self.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||
self.setMinimumSize(400, 300)
|
||||
self.root_layout = QVBoxLayout(self)
|
||||
self.root_layout.setContentsMargins(6, 6, 6, 6)
|
||||
|
||||
self.button_container = QWidget()
|
||||
self.button_layout = QHBoxLayout(self.button_container)
|
||||
self.button_layout.setContentsMargins(6,6,6,6)
|
||||
self.button_layout.addStretch(1)
|
||||
self.title_widget = QLabel()
|
||||
self.title_widget.setObjectName("fieldTitle")
|
||||
self.title_widget.setWordWrap(True)
|
||||
self.title_widget.setStyleSheet(
|
||||
# 'background:blue;'
|
||||
# 'text-align:center;'
|
||||
"font-weight:bold;" "font-size:14px;" "padding-top: 6px" ""
|
||||
)
|
||||
self.title_widget.setText("Add Field")
|
||||
self.title_widget.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
# self.cancel_button = QPushButton()
|
||||
# self.cancel_button.setText('Cancel')
|
||||
self.combo_box = QComboBox()
|
||||
self.combo_box.setEditable(False)
|
||||
# self.combo_box.setMaxVisibleItems(5)
|
||||
self.combo_box.setStyleSheet("combobox-popup:0;")
|
||||
self.combo_box.view().setVerticalScrollBarPolicy(
|
||||
Qt.ScrollBarPolicy.ScrollBarAsNeeded
|
||||
)
|
||||
for df in self.lib.default_fields:
|
||||
self.combo_box.addItem(
|
||||
f'{df["name"]} ({df["type"].replace("_", " ").title()})'
|
||||
)
|
||||
|
||||
self.cancel_button = QPushButton()
|
||||
self.cancel_button.setText('Cancel')
|
||||
self.cancel_button.clicked.connect(self.hide)
|
||||
# self.cancel_button.clicked.connect(widget.reset)
|
||||
self.button_layout.addWidget(self.cancel_button)
|
||||
|
||||
self.save_button = QPushButton()
|
||||
self.save_button.setText('Add')
|
||||
# self.save_button.setAutoDefault(True)
|
||||
self.save_button.setDefault(True)
|
||||
self.save_button.clicked.connect(self.hide)
|
||||
self.save_button.clicked.connect(lambda: self.done.emit(self.combo_box.currentIndex()))
|
||||
# self.save_button.clicked.connect(lambda: save_callback(widget.get_content()))
|
||||
self.button_layout.addWidget(self.save_button)
|
||||
self.button_container = QWidget()
|
||||
self.button_layout = QHBoxLayout(self.button_container)
|
||||
self.button_layout.setContentsMargins(6, 6, 6, 6)
|
||||
self.button_layout.addStretch(1)
|
||||
|
||||
# self.returnPressed.connect(lambda: self.done.emit(self.combo_box.currentIndex()))
|
||||
|
||||
# self.done.connect(lambda x: callback(x))
|
||||
|
||||
self.root_layout.addWidget(self.title_widget)
|
||||
self.root_layout.addWidget(self.combo_box)
|
||||
# self.root_layout.setStretch(1,2)
|
||||
self.root_layout.addStretch(1)
|
||||
self.root_layout.addWidget(self.button_container)
|
||||
# self.cancel_button = QPushButton()
|
||||
# self.cancel_button.setText('Cancel')
|
||||
|
||||
self.cancel_button = QPushButton()
|
||||
self.cancel_button.setText("Cancel")
|
||||
self.cancel_button.clicked.connect(self.hide)
|
||||
# self.cancel_button.clicked.connect(widget.reset)
|
||||
self.button_layout.addWidget(self.cancel_button)
|
||||
|
||||
self.save_button = QPushButton()
|
||||
self.save_button.setText("Add")
|
||||
# self.save_button.setAutoDefault(True)
|
||||
self.save_button.setDefault(True)
|
||||
self.save_button.clicked.connect(self.hide)
|
||||
self.save_button.clicked.connect(
|
||||
lambda: self.done.emit(self.combo_box.currentIndex())
|
||||
)
|
||||
# self.save_button.clicked.connect(lambda: save_callback(widget.get_content()))
|
||||
self.button_layout.addWidget(self.save_button)
|
||||
|
||||
# self.returnPressed.connect(lambda: self.done.emit(self.combo_box.currentIndex()))
|
||||
|
||||
# self.done.connect(lambda x: callback(x))
|
||||
|
||||
self.root_layout.addWidget(self.title_widget)
|
||||
self.root_layout.addWidget(self.combo_box)
|
||||
# self.root_layout.setStretch(1,2)
|
||||
self.root_layout.addStretch(1)
|
||||
self.root_layout.addWidget(self.button_container)
|
||||
|
|
|
@ -6,7 +6,17 @@
|
|||
import logging
|
||||
|
||||
from PySide6.QtCore import Signal, Qt
|
||||
from PySide6.QtWidgets import (QWidget, QVBoxLayout, QLabel, QPushButton, QLineEdit, QScrollArea, QFrame, QTextEdit, QComboBox)
|
||||
from PySide6.QtWidgets import (
|
||||
QWidget,
|
||||
QVBoxLayout,
|
||||
QLabel,
|
||||
QPushButton,
|
||||
QLineEdit,
|
||||
QScrollArea,
|
||||
QFrame,
|
||||
QTextEdit,
|
||||
QComboBox,
|
||||
)
|
||||
|
||||
from src.core.library import Library, Tag
|
||||
from src.core.palette import ColorType, get_tag_color
|
||||
|
@ -15,216 +25,220 @@ from src.qt.widgets import PanelWidget, PanelModal, TagWidget
|
|||
from src.qt.modals import TagSearchPanel
|
||||
|
||||
|
||||
ERROR = f'[ERROR]'
|
||||
WARNING = f'[WARNING]'
|
||||
INFO = f'[INFO]'
|
||||
ERROR = f"[ERROR]"
|
||||
WARNING = f"[WARNING]"
|
||||
INFO = f"[INFO]"
|
||||
|
||||
logging.basicConfig(format="%(message)s", level=logging.INFO)
|
||||
|
||||
|
||||
class BuildTagPanel(PanelWidget):
|
||||
on_edit = Signal(Tag)
|
||||
def __init__(self, library, tag_id:int=-1):
|
||||
super().__init__()
|
||||
self.lib: Library = library
|
||||
# self.callback = callback
|
||||
# self.tag_id = tag_id
|
||||
self.tag = None
|
||||
self.setMinimumSize(300, 400)
|
||||
self.root_layout = QVBoxLayout(self)
|
||||
self.root_layout.setContentsMargins(6,0,6,0)
|
||||
self.root_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
||||
on_edit = Signal(Tag)
|
||||
|
||||
# Name -----------------------------------------------------------------
|
||||
self.name_widget = QWidget()
|
||||
self.name_layout = QVBoxLayout(self.name_widget)
|
||||
self.name_layout.setStretch(1, 1)
|
||||
self.name_layout.setContentsMargins(0,0,0,0)
|
||||
self.name_layout.setSpacing(0)
|
||||
self.name_layout.setAlignment(Qt.AlignmentFlag.AlignLeft)
|
||||
self.name_title = QLabel()
|
||||
self.name_title.setText('Name')
|
||||
self.name_layout.addWidget(self.name_title)
|
||||
self.name_field = QLineEdit()
|
||||
self.name_layout.addWidget(self.name_field)
|
||||
def __init__(self, library, tag_id: int = -1):
|
||||
super().__init__()
|
||||
self.lib: Library = library
|
||||
# self.callback = callback
|
||||
# self.tag_id = tag_id
|
||||
self.tag = None
|
||||
self.setMinimumSize(300, 400)
|
||||
self.root_layout = QVBoxLayout(self)
|
||||
self.root_layout.setContentsMargins(6, 0, 6, 0)
|
||||
self.root_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
||||
|
||||
# Shorthand ------------------------------------------------------------
|
||||
self.shorthand_widget = QWidget()
|
||||
self.shorthand_layout = QVBoxLayout(self.shorthand_widget)
|
||||
self.shorthand_layout.setStretch(1, 1)
|
||||
self.shorthand_layout.setContentsMargins(0,0,0,0)
|
||||
self.shorthand_layout.setSpacing(0)
|
||||
self.shorthand_layout.setAlignment(Qt.AlignmentFlag.AlignLeft)
|
||||
self.shorthand_title = QLabel()
|
||||
self.shorthand_title.setText('Shorthand')
|
||||
self.shorthand_layout.addWidget(self.shorthand_title)
|
||||
self.shorthand_field = QLineEdit()
|
||||
self.shorthand_layout.addWidget(self.shorthand_field)
|
||||
# Name -----------------------------------------------------------------
|
||||
self.name_widget = QWidget()
|
||||
self.name_layout = QVBoxLayout(self.name_widget)
|
||||
self.name_layout.setStretch(1, 1)
|
||||
self.name_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.name_layout.setSpacing(0)
|
||||
self.name_layout.setAlignment(Qt.AlignmentFlag.AlignLeft)
|
||||
self.name_title = QLabel()
|
||||
self.name_title.setText("Name")
|
||||
self.name_layout.addWidget(self.name_title)
|
||||
self.name_field = QLineEdit()
|
||||
self.name_layout.addWidget(self.name_field)
|
||||
|
||||
# Aliases --------------------------------------------------------------
|
||||
self.aliases_widget = QWidget()
|
||||
self.aliases_layout = QVBoxLayout(self.aliases_widget)
|
||||
self.aliases_layout.setStretch(1, 1)
|
||||
self.aliases_layout.setContentsMargins(0,0,0,0)
|
||||
self.aliases_layout.setSpacing(0)
|
||||
self.aliases_layout.setAlignment(Qt.AlignmentFlag.AlignLeft)
|
||||
self.aliases_title = QLabel()
|
||||
self.aliases_title.setText('Aliases')
|
||||
self.aliases_layout.addWidget(self.aliases_title)
|
||||
self.aliases_field = QTextEdit()
|
||||
self.aliases_field.setAcceptRichText(False)
|
||||
self.aliases_field.setMinimumHeight(40)
|
||||
self.aliases_layout.addWidget(self.aliases_field)
|
||||
# Shorthand ------------------------------------------------------------
|
||||
self.shorthand_widget = QWidget()
|
||||
self.shorthand_layout = QVBoxLayout(self.shorthand_widget)
|
||||
self.shorthand_layout.setStretch(1, 1)
|
||||
self.shorthand_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.shorthand_layout.setSpacing(0)
|
||||
self.shorthand_layout.setAlignment(Qt.AlignmentFlag.AlignLeft)
|
||||
self.shorthand_title = QLabel()
|
||||
self.shorthand_title.setText("Shorthand")
|
||||
self.shorthand_layout.addWidget(self.shorthand_title)
|
||||
self.shorthand_field = QLineEdit()
|
||||
self.shorthand_layout.addWidget(self.shorthand_field)
|
||||
|
||||
# Subtags ------------------------------------------------------------
|
||||
self.subtags_widget = QWidget()
|
||||
self.subtags_layout = QVBoxLayout(self.subtags_widget)
|
||||
self.subtags_layout.setStretch(1, 1)
|
||||
self.subtags_layout.setContentsMargins(0,0,0,0)
|
||||
self.subtags_layout.setSpacing(0)
|
||||
self.subtags_layout.setAlignment(Qt.AlignmentFlag.AlignLeft)
|
||||
self.subtags_title = QLabel()
|
||||
self.subtags_title.setText('Subtags')
|
||||
self.subtags_layout.addWidget(self.subtags_title)
|
||||
# Aliases --------------------------------------------------------------
|
||||
self.aliases_widget = QWidget()
|
||||
self.aliases_layout = QVBoxLayout(self.aliases_widget)
|
||||
self.aliases_layout.setStretch(1, 1)
|
||||
self.aliases_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.aliases_layout.setSpacing(0)
|
||||
self.aliases_layout.setAlignment(Qt.AlignmentFlag.AlignLeft)
|
||||
self.aliases_title = QLabel()
|
||||
self.aliases_title.setText("Aliases")
|
||||
self.aliases_layout.addWidget(self.aliases_title)
|
||||
self.aliases_field = QTextEdit()
|
||||
self.aliases_field.setAcceptRichText(False)
|
||||
self.aliases_field.setMinimumHeight(40)
|
||||
self.aliases_layout.addWidget(self.aliases_field)
|
||||
|
||||
self.scroll_contents = QWidget()
|
||||
self.scroll_layout = QVBoxLayout(self.scroll_contents)
|
||||
self.scroll_layout.setContentsMargins(6,0,6,0)
|
||||
self.scroll_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
||||
# Subtags ------------------------------------------------------------
|
||||
self.subtags_widget = QWidget()
|
||||
self.subtags_layout = QVBoxLayout(self.subtags_widget)
|
||||
self.subtags_layout.setStretch(1, 1)
|
||||
self.subtags_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.subtags_layout.setSpacing(0)
|
||||
self.subtags_layout.setAlignment(Qt.AlignmentFlag.AlignLeft)
|
||||
self.subtags_title = QLabel()
|
||||
self.subtags_title.setText("Subtags")
|
||||
self.subtags_layout.addWidget(self.subtags_title)
|
||||
|
||||
self.scroll_area = QScrollArea()
|
||||
# self.scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
|
||||
self.scroll_area.setWidgetResizable(True)
|
||||
self.scroll_area.setFrameShadow(QFrame.Shadow.Plain)
|
||||
self.scroll_area.setFrameShape(QFrame.Shape.NoFrame)
|
||||
self.scroll_area.setWidget(self.scroll_contents)
|
||||
# self.scroll_area.setMinimumHeight(60)
|
||||
self.scroll_contents = QWidget()
|
||||
self.scroll_layout = QVBoxLayout(self.scroll_contents)
|
||||
self.scroll_layout.setContentsMargins(6, 0, 6, 0)
|
||||
self.scroll_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
||||
|
||||
self.subtags_layout.addWidget(self.scroll_area)
|
||||
self.scroll_area = QScrollArea()
|
||||
# self.scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
|
||||
self.scroll_area.setWidgetResizable(True)
|
||||
self.scroll_area.setFrameShadow(QFrame.Shadow.Plain)
|
||||
self.scroll_area.setFrameShape(QFrame.Shape.NoFrame)
|
||||
self.scroll_area.setWidget(self.scroll_contents)
|
||||
# self.scroll_area.setMinimumHeight(60)
|
||||
|
||||
self.subtags_add_button = QPushButton()
|
||||
self.subtags_add_button.setText('+')
|
||||
tsp = TagSearchPanel(self.lib)
|
||||
tsp.tag_chosen.connect(lambda x: self.add_subtag_callback(x))
|
||||
self.add_tag_modal = PanelModal(tsp, 'Add Subtags', 'Add Subtags')
|
||||
self.subtags_add_button.clicked.connect(self.add_tag_modal.show)
|
||||
self.subtags_layout.addWidget(self.subtags_add_button)
|
||||
self.subtags_layout.addWidget(self.scroll_area)
|
||||
|
||||
# self.subtags_field = TagBoxWidget()
|
||||
# self.subtags_field.setMinimumHeight(60)
|
||||
# self.subtags_layout.addWidget(self.subtags_field)
|
||||
self.subtags_add_button = QPushButton()
|
||||
self.subtags_add_button.setText("+")
|
||||
tsp = TagSearchPanel(self.lib)
|
||||
tsp.tag_chosen.connect(lambda x: self.add_subtag_callback(x))
|
||||
self.add_tag_modal = PanelModal(tsp, "Add Subtags", "Add Subtags")
|
||||
self.subtags_add_button.clicked.connect(self.add_tag_modal.show)
|
||||
self.subtags_layout.addWidget(self.subtags_add_button)
|
||||
|
||||
# Shorthand ------------------------------------------------------------
|
||||
self.color_widget = QWidget()
|
||||
self.color_layout = QVBoxLayout(self.color_widget)
|
||||
self.color_layout.setStretch(1, 1)
|
||||
self.color_layout.setContentsMargins(0,0,0,0)
|
||||
self.color_layout.setSpacing(0)
|
||||
self.color_layout.setAlignment(Qt.AlignmentFlag.AlignLeft)
|
||||
self.color_title = QLabel()
|
||||
self.color_title.setText('Color')
|
||||
self.color_layout.addWidget(self.color_title)
|
||||
self.color_field = QComboBox()
|
||||
self.color_field.setEditable(False)
|
||||
self.color_field.setMaxVisibleItems(10)
|
||||
self.color_field.setStyleSheet('combobox-popup:0;')
|
||||
for color in TAG_COLORS:
|
||||
self.color_field.addItem(color.title())
|
||||
# self.color_field.setProperty("appearance", "flat")
|
||||
self.color_field.currentTextChanged.connect(lambda c: self.color_field.setStyleSheet(f'''combobox-popup:0;
|
||||
# self.subtags_field = TagBoxWidget()
|
||||
# self.subtags_field.setMinimumHeight(60)
|
||||
# self.subtags_layout.addWidget(self.subtags_field)
|
||||
|
||||
# Shorthand ------------------------------------------------------------
|
||||
self.color_widget = QWidget()
|
||||
self.color_layout = QVBoxLayout(self.color_widget)
|
||||
self.color_layout.setStretch(1, 1)
|
||||
self.color_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.color_layout.setSpacing(0)
|
||||
self.color_layout.setAlignment(Qt.AlignmentFlag.AlignLeft)
|
||||
self.color_title = QLabel()
|
||||
self.color_title.setText("Color")
|
||||
self.color_layout.addWidget(self.color_title)
|
||||
self.color_field = QComboBox()
|
||||
self.color_field.setEditable(False)
|
||||
self.color_field.setMaxVisibleItems(10)
|
||||
self.color_field.setStyleSheet("combobox-popup:0;")
|
||||
for color in TAG_COLORS:
|
||||
self.color_field.addItem(color.title())
|
||||
# self.color_field.setProperty("appearance", "flat")
|
||||
self.color_field.currentTextChanged.connect(
|
||||
lambda c: self.color_field.setStyleSheet(f"""combobox-popup:0;
|
||||
font-weight:600;
|
||||
color:{get_tag_color(ColorType.TEXT, c.lower())};
|
||||
background-color:{get_tag_color(ColorType.PRIMARY, c.lower())};
|
||||
'''))
|
||||
self.color_layout.addWidget(self.color_field)
|
||||
""")
|
||||
)
|
||||
self.color_layout.addWidget(self.color_field)
|
||||
|
||||
# Add Widgets to Layout ================================================
|
||||
self.root_layout.addWidget(self.name_widget)
|
||||
self.root_layout.addWidget(self.shorthand_widget)
|
||||
self.root_layout.addWidget(self.aliases_widget)
|
||||
self.root_layout.addWidget(self.subtags_widget)
|
||||
self.root_layout.addWidget(self.color_widget)
|
||||
# self.parent().done.connect(self.update_tag)
|
||||
|
||||
# Add Widgets to Layout ================================================
|
||||
self.root_layout.addWidget(self.name_widget)
|
||||
self.root_layout.addWidget(self.shorthand_widget)
|
||||
self.root_layout.addWidget(self.aliases_widget)
|
||||
self.root_layout.addWidget(self.subtags_widget)
|
||||
self.root_layout.addWidget(self.color_widget)
|
||||
# self.parent().done.connect(self.update_tag)
|
||||
if tag_id >= 0:
|
||||
self.tag = self.lib.get_tag(tag_id)
|
||||
else:
|
||||
self.tag = Tag(-1, "New Tag", "", [], [], "")
|
||||
self.set_tag(self.tag)
|
||||
|
||||
if tag_id >= 0:
|
||||
self.tag = self.lib.get_tag(tag_id)
|
||||
else:
|
||||
self.tag = Tag(-1, 'New Tag', '', [], [], '')
|
||||
self.set_tag(self.tag)
|
||||
|
||||
|
||||
def add_subtag_callback(self, tag_id:int):
|
||||
logging.info(f'adding {tag_id}')
|
||||
# tag = self.lib.get_tag(self.tag_id)
|
||||
# TODO: Create a single way to update tags and refresh library data
|
||||
# new = self.build_tag()
|
||||
self.tag.add_subtag(tag_id)
|
||||
# self.tag = new
|
||||
# self.lib.update_tag(new)
|
||||
self.set_subtags()
|
||||
# self.on_edit.emit(self.build_tag())
|
||||
|
||||
def remove_subtag_callback(self, tag_id:int):
|
||||
logging.info(f'removing {tag_id}')
|
||||
# tag = self.lib.get_tag(self.tag_id)
|
||||
# TODO: Create a single way to update tags and refresh library data
|
||||
# new = self.build_tag()
|
||||
self.tag.remove_subtag(tag_id)
|
||||
# self.tag = new
|
||||
# self.lib.update_tag(new)
|
||||
self.set_subtags()
|
||||
# self.on_edit.emit(self.build_tag())
|
||||
|
||||
def set_subtags(self):
|
||||
while self.scroll_layout.itemAt(0):
|
||||
self.scroll_layout.takeAt(0).widget().deleteLater()
|
||||
logging.info(f'Setting {self.tag.subtag_ids}')
|
||||
c = QWidget()
|
||||
l = QVBoxLayout(c)
|
||||
l.setContentsMargins(0,0,0,0)
|
||||
l.setSpacing(3)
|
||||
for tag_id in self.tag.subtag_ids:
|
||||
tw = TagWidget(self.lib, self.lib.get_tag(tag_id), False, True)
|
||||
tw.on_remove.connect(lambda checked=False, t=tag_id: self.remove_subtag_callback(t))
|
||||
l.addWidget(tw)
|
||||
self.scroll_layout.addWidget(c)
|
||||
def add_subtag_callback(self, tag_id: int):
|
||||
logging.info(f"adding {tag_id}")
|
||||
# tag = self.lib.get_tag(self.tag_id)
|
||||
# TODO: Create a single way to update tags and refresh library data
|
||||
# new = self.build_tag()
|
||||
self.tag.add_subtag(tag_id)
|
||||
# self.tag = new
|
||||
# self.lib.update_tag(new)
|
||||
self.set_subtags()
|
||||
# self.on_edit.emit(self.build_tag())
|
||||
|
||||
def set_tag(self, tag:Tag):
|
||||
# tag = self.lib.get_tag(tag_id)
|
||||
self.name_field.setText(tag.name)
|
||||
self.shorthand_field.setText(tag.shorthand)
|
||||
self.aliases_field.setText('\n'.join(tag.aliases))
|
||||
self.set_subtags()
|
||||
self.color_field.setCurrentIndex(TAG_COLORS.index(tag.color.lower()))
|
||||
# self.tag_id = tag.id
|
||||
|
||||
def build_tag(self) -> Tag:
|
||||
# tag: Tag = self.tag
|
||||
# if self.tag_id >= 0:
|
||||
# tag = self.lib.get_tag(self.tag_id)
|
||||
# else:
|
||||
# tag = Tag(-1, '', '', [], [], '')
|
||||
new_tag: Tag = Tag(
|
||||
id=self.tag.id,
|
||||
name=self.name_field.text(),
|
||||
shorthand=self.shorthand_field.text(),
|
||||
aliases=self.aliases_field.toPlainText().split('\n'),
|
||||
subtags_ids=self.tag.subtag_ids,
|
||||
color=self.color_field.currentText().lower())
|
||||
logging.info(f'built {new_tag}')
|
||||
return new_tag
|
||||
|
||||
# NOTE: The callback and signal do the same thing, I'm currently
|
||||
# transitioning from using callbacks to the Qt method of using signals.
|
||||
# self.tag_updated.emit(new_tag)
|
||||
# self.callback(new_tag)
|
||||
|
||||
# def on_return(self, callback, text:str):
|
||||
# if text and self.first_tag_id >= 0:
|
||||
# callback(self.first_tag_id)
|
||||
# self.search_field.setText('')
|
||||
# self.update_tags('')
|
||||
# else:
|
||||
# self.search_field.setFocus()
|
||||
# self.parentWidget().hide()
|
||||
def remove_subtag_callback(self, tag_id: int):
|
||||
logging.info(f"removing {tag_id}")
|
||||
# tag = self.lib.get_tag(self.tag_id)
|
||||
# TODO: Create a single way to update tags and refresh library data
|
||||
# new = self.build_tag()
|
||||
self.tag.remove_subtag(tag_id)
|
||||
# self.tag = new
|
||||
# self.lib.update_tag(new)
|
||||
self.set_subtags()
|
||||
# self.on_edit.emit(self.build_tag())
|
||||
|
||||
def set_subtags(self):
|
||||
while self.scroll_layout.itemAt(0):
|
||||
self.scroll_layout.takeAt(0).widget().deleteLater()
|
||||
logging.info(f"Setting {self.tag.subtag_ids}")
|
||||
c = QWidget()
|
||||
l = QVBoxLayout(c)
|
||||
l.setContentsMargins(0, 0, 0, 0)
|
||||
l.setSpacing(3)
|
||||
for tag_id in self.tag.subtag_ids:
|
||||
tw = TagWidget(self.lib, self.lib.get_tag(tag_id), False, True)
|
||||
tw.on_remove.connect(
|
||||
lambda checked=False, t=tag_id: self.remove_subtag_callback(t)
|
||||
)
|
||||
l.addWidget(tw)
|
||||
self.scroll_layout.addWidget(c)
|
||||
|
||||
def set_tag(self, tag: Tag):
|
||||
# tag = self.lib.get_tag(tag_id)
|
||||
self.name_field.setText(tag.name)
|
||||
self.shorthand_field.setText(tag.shorthand)
|
||||
self.aliases_field.setText("\n".join(tag.aliases))
|
||||
self.set_subtags()
|
||||
self.color_field.setCurrentIndex(TAG_COLORS.index(tag.color.lower()))
|
||||
# self.tag_id = tag.id
|
||||
|
||||
def build_tag(self) -> Tag:
|
||||
# tag: Tag = self.tag
|
||||
# if self.tag_id >= 0:
|
||||
# tag = self.lib.get_tag(self.tag_id)
|
||||
# else:
|
||||
# tag = Tag(-1, '', '', [], [], '')
|
||||
new_tag: Tag = Tag(
|
||||
id=self.tag.id,
|
||||
name=self.name_field.text(),
|
||||
shorthand=self.shorthand_field.text(),
|
||||
aliases=self.aliases_field.toPlainText().split("\n"),
|
||||
subtags_ids=self.tag.subtag_ids,
|
||||
color=self.color_field.currentText().lower(),
|
||||
)
|
||||
logging.info(f"built {new_tag}")
|
||||
return new_tag
|
||||
|
||||
# NOTE: The callback and signal do the same thing, I'm currently
|
||||
# transitioning from using callbacks to the Qt method of using signals.
|
||||
# self.tag_updated.emit(new_tag)
|
||||
# self.callback(new_tag)
|
||||
|
||||
# def on_return(self, callback, text:str):
|
||||
# if text and self.first_tag_id >= 0:
|
||||
# callback(self.first_tag_id)
|
||||
# self.search_field.setText('')
|
||||
# self.update_tags('')
|
||||
# else:
|
||||
# self.search_field.setFocus()
|
||||
# self.parentWidget().hide()
|
||||
|
|
|
@ -6,7 +6,14 @@ import typing
|
|||
|
||||
from PySide6.QtCore import Signal, Qt, QThreadPool
|
||||
from PySide6.QtGui import QStandardItemModel, QStandardItem
|
||||
from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QListView
|
||||
from PySide6.QtWidgets import (
|
||||
QWidget,
|
||||
QVBoxLayout,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QPushButton,
|
||||
QListView,
|
||||
)
|
||||
|
||||
from src.core.library import ItemType, Library
|
||||
from src.qt.helpers import CustomRunnable, FunctionIterator
|
||||
|
@ -14,115 +21,120 @@ from src.qt.widgets import ProgressWidget
|
|||
|
||||
# Only import for type checking/autocompletion, will not be imported at runtime.
|
||||
if typing.TYPE_CHECKING:
|
||||
from src.qt.ts_qt import QtDriver
|
||||
from src.qt.ts_qt import QtDriver
|
||||
|
||||
|
||||
class DeleteUnlinkedEntriesModal(QWidget):
|
||||
done = Signal()
|
||||
def __init__(self, library:'Library', driver:'QtDriver'):
|
||||
super().__init__()
|
||||
self.lib = library
|
||||
self.driver = driver
|
||||
self.setWindowTitle(f'Delete Unlinked Entries')
|
||||
self.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||
self.setMinimumSize(500, 400)
|
||||
self.root_layout = QVBoxLayout(self)
|
||||
self.root_layout.setContentsMargins(6,6,6,6)
|
||||
done = Signal()
|
||||
|
||||
self.desc_widget = QLabel()
|
||||
self.desc_widget.setObjectName('descriptionLabel')
|
||||
self.desc_widget.setWordWrap(True)
|
||||
self.desc_widget.setText(f'''
|
||||
def __init__(self, library: "Library", driver: "QtDriver"):
|
||||
super().__init__()
|
||||
self.lib = library
|
||||
self.driver = driver
|
||||
self.setWindowTitle(f"Delete Unlinked Entries")
|
||||
self.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||
self.setMinimumSize(500, 400)
|
||||
self.root_layout = QVBoxLayout(self)
|
||||
self.root_layout.setContentsMargins(6, 6, 6, 6)
|
||||
|
||||
self.desc_widget = QLabel()
|
||||
self.desc_widget.setObjectName("descriptionLabel")
|
||||
self.desc_widget.setWordWrap(True)
|
||||
self.desc_widget.setText(f"""
|
||||
Are you sure you want to delete the following {len(self.lib.missing_files)} entries?
|
||||
''')
|
||||
self.desc_widget.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
""")
|
||||
self.desc_widget.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
self.list_view = QListView()
|
||||
self.model = QStandardItemModel()
|
||||
self.list_view.setModel(self.model)
|
||||
|
||||
self.button_container = QWidget()
|
||||
self.button_layout = QHBoxLayout(self.button_container)
|
||||
self.button_layout.setContentsMargins(6,6,6,6)
|
||||
self.button_layout.addStretch(1)
|
||||
self.list_view = QListView()
|
||||
self.model = QStandardItemModel()
|
||||
self.list_view.setModel(self.model)
|
||||
|
||||
self.cancel_button = QPushButton()
|
||||
self.cancel_button.setText('&Cancel')
|
||||
self.cancel_button.setDefault(True)
|
||||
self.cancel_button.clicked.connect(self.hide)
|
||||
self.button_layout.addWidget(self.cancel_button)
|
||||
|
||||
self.delete_button = QPushButton()
|
||||
self.delete_button.setText('&Delete')
|
||||
self.delete_button.clicked.connect(self.hide)
|
||||
self.delete_button.clicked.connect(lambda: self.delete_entries())
|
||||
self.button_layout.addWidget(self.delete_button)
|
||||
|
||||
self.root_layout.addWidget(self.desc_widget)
|
||||
self.root_layout.addWidget(self.list_view)
|
||||
self.root_layout.addWidget(self.button_container)
|
||||
self.button_container = QWidget()
|
||||
self.button_layout = QHBoxLayout(self.button_container)
|
||||
self.button_layout.setContentsMargins(6, 6, 6, 6)
|
||||
self.button_layout.addStretch(1)
|
||||
|
||||
def refresh_list(self):
|
||||
self.desc_widget.setText(f'''
|
||||
self.cancel_button = QPushButton()
|
||||
self.cancel_button.setText("&Cancel")
|
||||
self.cancel_button.setDefault(True)
|
||||
self.cancel_button.clicked.connect(self.hide)
|
||||
self.button_layout.addWidget(self.cancel_button)
|
||||
|
||||
self.delete_button = QPushButton()
|
||||
self.delete_button.setText("&Delete")
|
||||
self.delete_button.clicked.connect(self.hide)
|
||||
self.delete_button.clicked.connect(lambda: self.delete_entries())
|
||||
self.button_layout.addWidget(self.delete_button)
|
||||
|
||||
self.root_layout.addWidget(self.desc_widget)
|
||||
self.root_layout.addWidget(self.list_view)
|
||||
self.root_layout.addWidget(self.button_container)
|
||||
|
||||
def refresh_list(self):
|
||||
self.desc_widget.setText(f"""
|
||||
Are you sure you want to delete the following {len(self.lib.missing_files)} entries?
|
||||
''')
|
||||
""")
|
||||
|
||||
self.model.clear()
|
||||
for i in self.lib.missing_files:
|
||||
self.model.appendRow(QStandardItem(i))
|
||||
self.model.clear()
|
||||
for i in self.lib.missing_files:
|
||||
self.model.appendRow(QStandardItem(i))
|
||||
|
||||
def delete_entries(self):
|
||||
# pb = QProgressDialog('', None, 0, len(self.lib.missing_files))
|
||||
# # pb.setMaximum(len(self.lib.missing_files))
|
||||
# pb.setFixedSize(432, 112)
|
||||
# pb.setWindowFlags(pb.windowFlags() & ~Qt.WindowType.WindowCloseButtonHint)
|
||||
# pb.setWindowTitle('Deleting Entries')
|
||||
# pb.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||
# pb.show()
|
||||
def delete_entries(self):
|
||||
# pb = QProgressDialog('', None, 0, len(self.lib.missing_files))
|
||||
# # pb.setMaximum(len(self.lib.missing_files))
|
||||
# pb.setFixedSize(432, 112)
|
||||
# pb.setWindowFlags(pb.windowFlags() & ~Qt.WindowType.WindowCloseButtonHint)
|
||||
# pb.setWindowTitle('Deleting Entries')
|
||||
# pb.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||
# pb.show()
|
||||
|
||||
# r = CustomRunnable(lambda: self.lib.ref(pb))
|
||||
# r.done.connect(lambda: self.done.emit())
|
||||
# # r.done.connect(lambda: self.model.clear())
|
||||
# QThreadPool.globalInstance().start(r)
|
||||
# # r.run()
|
||||
# r = CustomRunnable(lambda: self.lib.ref(pb))
|
||||
# r.done.connect(lambda: self.done.emit())
|
||||
# # r.done.connect(lambda: self.model.clear())
|
||||
# QThreadPool.globalInstance().start(r)
|
||||
# # r.run()
|
||||
|
||||
iterator = FunctionIterator(self.lib.remove_missing_files)
|
||||
|
||||
pw = ProgressWidget(
|
||||
window_title="Deleting Entries",
|
||||
label_text="",
|
||||
cancel_button_text=None,
|
||||
minimum=0,
|
||||
maximum=len(self.lib.missing_files),
|
||||
)
|
||||
pw.show()
|
||||
|
||||
iterator = FunctionIterator(self.lib.remove_missing_files)
|
||||
iterator.value.connect(lambda x: pw.update_progress(x[0] + 1))
|
||||
iterator.value.connect(
|
||||
lambda x: pw.update_label(
|
||||
f"Deleting {x[0]+1}/{len(self.lib.missing_files)} Unlinked Entries"
|
||||
)
|
||||
)
|
||||
iterator.value.connect(
|
||||
lambda x: self.driver.purge_item_from_navigation(ItemType.ENTRY, x[1])
|
||||
)
|
||||
|
||||
pw = ProgressWidget(
|
||||
window_title='Deleting Entries',
|
||||
label_text='',
|
||||
cancel_button_text=None,
|
||||
minimum=0,
|
||||
maximum=len(self.lib.missing_files)
|
||||
)
|
||||
pw.show()
|
||||
|
||||
iterator.value.connect(lambda x: pw.update_progress(x[0]+1))
|
||||
iterator.value.connect(lambda x: pw.update_label(f'Deleting {x[0]+1}/{len(self.lib.missing_files)} Unlinked Entries'))
|
||||
iterator.value.connect(lambda x: self.driver.purge_item_from_navigation(ItemType.ENTRY, x[1]))
|
||||
r = CustomRunnable(lambda: iterator.run())
|
||||
QThreadPool.globalInstance().start(r)
|
||||
r.done.connect(lambda: (pw.hide(), pw.deleteLater(), self.done.emit()))
|
||||
|
||||
r = CustomRunnable(lambda:iterator.run())
|
||||
QThreadPool.globalInstance().start(r)
|
||||
r.done.connect(lambda: (pw.hide(), pw.deleteLater(), self.done.emit()))
|
||||
|
||||
# def delete_entries_runnable(self):
|
||||
# deleted = []
|
||||
# for i, missing in enumerate(self.lib.missing_files):
|
||||
# # pb.setValue(i)
|
||||
# # pb.setLabelText(f'Deleting {i}/{len(self.lib.missing_files)} Unlinked Entries')
|
||||
# try:
|
||||
# id = self.lib.get_entry_id_from_filepath(missing)
|
||||
# logging.info(f'Removing Entry ID {id}:\n\t{missing}')
|
||||
# self.lib.remove_entry(id)
|
||||
# self.driver.purge_item_from_navigation(ItemType.ENTRY, id)
|
||||
# deleted.append(missing)
|
||||
# except KeyError:
|
||||
# logging.info(
|
||||
# f'{ERROR} \"{id}\" was reported as missing, but is not in the file_to_entry_id map.')
|
||||
# yield i
|
||||
# for d in deleted:
|
||||
# self.lib.missing_files.remove(d)
|
||||
# # self.driver.filter_items('')
|
||||
# # self.done.emit()
|
||||
# def delete_entries_runnable(self):
|
||||
# deleted = []
|
||||
# for i, missing in enumerate(self.lib.missing_files):
|
||||
# # pb.setValue(i)
|
||||
# # pb.setLabelText(f'Deleting {i}/{len(self.lib.missing_files)} Unlinked Entries')
|
||||
# try:
|
||||
# id = self.lib.get_entry_id_from_filepath(missing)
|
||||
# logging.info(f'Removing Entry ID {id}:\n\t{missing}')
|
||||
# self.lib.remove_entry(id)
|
||||
# self.driver.purge_item_from_navigation(ItemType.ENTRY, id)
|
||||
# deleted.append(missing)
|
||||
# except KeyError:
|
||||
# logging.info(
|
||||
# f'{ERROR} \"{id}\" was reported as missing, but is not in the file_to_entry_id map.')
|
||||
# yield i
|
||||
# for d in deleted:
|
||||
# self.lib.missing_files.remove(d)
|
||||
# # self.driver.filter_items('')
|
||||
# # self.done.emit()
|
||||
|
|
|
@ -11,41 +11,44 @@ from src.qt.widgets import PanelWidget
|
|||
|
||||
|
||||
class FileExtensionModal(PanelWidget):
|
||||
done = Signal()
|
||||
def __init__(self, library:'Library'):
|
||||
super().__init__()
|
||||
self.lib = library
|
||||
self.setWindowTitle(f'File Extensions')
|
||||
self.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||
self.setMinimumSize(200, 400)
|
||||
self.root_layout = QVBoxLayout(self)
|
||||
self.root_layout.setContentsMargins(6,6,6,6)
|
||||
done = Signal()
|
||||
|
||||
self.table = QTableWidget(len(self.lib.ignored_extensions), 1)
|
||||
self.table.horizontalHeader().setVisible(False)
|
||||
self.table.verticalHeader().setVisible(False)
|
||||
self.table.horizontalHeader().setStretchLastSection(True)
|
||||
def __init__(self, library: "Library"):
|
||||
super().__init__()
|
||||
self.lib = library
|
||||
self.setWindowTitle(f"File Extensions")
|
||||
self.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||
self.setMinimumSize(200, 400)
|
||||
self.root_layout = QVBoxLayout(self)
|
||||
self.root_layout.setContentsMargins(6, 6, 6, 6)
|
||||
|
||||
self.add_button = QPushButton()
|
||||
self.add_button.setText('&Add Extension')
|
||||
self.add_button.clicked.connect(self.add_item)
|
||||
self.add_button.setDefault(True)
|
||||
self.add_button.setMinimumWidth(100)
|
||||
self.table = QTableWidget(len(self.lib.ignored_extensions), 1)
|
||||
self.table.horizontalHeader().setVisible(False)
|
||||
self.table.verticalHeader().setVisible(False)
|
||||
self.table.horizontalHeader().setStretchLastSection(True)
|
||||
|
||||
self.root_layout.addWidget(self.table)
|
||||
self.root_layout.addWidget(self.add_button, alignment=Qt.AlignmentFlag.AlignCenter)
|
||||
self.refresh_list()
|
||||
|
||||
def refresh_list(self):
|
||||
for i, ext in enumerate(self.lib.ignored_extensions):
|
||||
self.table.setItem(i, 0, QTableWidgetItem(ext))
|
||||
|
||||
def add_item(self):
|
||||
self.table.insertRow(self.table.rowCount())
|
||||
|
||||
def save(self):
|
||||
self.lib.ignored_extensions.clear()
|
||||
for i in range(self.table.rowCount()):
|
||||
ext = self.table.item(i, 0)
|
||||
if ext and ext.text():
|
||||
self.lib.ignored_extensions.append(ext.text())
|
||||
self.add_button = QPushButton()
|
||||
self.add_button.setText("&Add Extension")
|
||||
self.add_button.clicked.connect(self.add_item)
|
||||
self.add_button.setDefault(True)
|
||||
self.add_button.setMinimumWidth(100)
|
||||
|
||||
self.root_layout.addWidget(self.table)
|
||||
self.root_layout.addWidget(
|
||||
self.add_button, alignment=Qt.AlignmentFlag.AlignCenter
|
||||
)
|
||||
self.refresh_list()
|
||||
|
||||
def refresh_list(self):
|
||||
for i, ext in enumerate(self.lib.ignored_extensions):
|
||||
self.table.setItem(i, 0, QTableWidgetItem(ext))
|
||||
|
||||
def add_item(self):
|
||||
self.table.insertRow(self.table.rowCount())
|
||||
|
||||
def save(self):
|
||||
self.lib.ignored_extensions.clear()
|
||||
for i in range(self.table.rowCount()):
|
||||
ext = self.table.item(i, 0)
|
||||
if ext and ext.text():
|
||||
self.lib.ignored_extensions.append(ext.text())
|
||||
|
|
|
@ -7,153 +7,167 @@ import os
|
|||
import typing
|
||||
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QFileDialog
|
||||
from PySide6.QtWidgets import (
|
||||
QWidget,
|
||||
QVBoxLayout,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QPushButton,
|
||||
QFileDialog,
|
||||
)
|
||||
|
||||
from src.core.library import Library
|
||||
from src.qt.modals import MirrorEntriesModal
|
||||
|
||||
# Only import for type checking/autocompletion, will not be imported at runtime.
|
||||
if typing.TYPE_CHECKING:
|
||||
from src.qt.ts_qt import QtDriver
|
||||
from src.qt.ts_qt import QtDriver
|
||||
|
||||
|
||||
class FixDupeFilesModal(QWidget):
|
||||
# done = Signal(int)
|
||||
def __init__(self, library:'Library', driver:'QtDriver'):
|
||||
super().__init__()
|
||||
self.lib = library
|
||||
self.driver = driver
|
||||
self.count = -1
|
||||
self.filename = ''
|
||||
self.setWindowTitle(f'Fix Duplicate Files')
|
||||
self.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||
self.setMinimumSize(400, 300)
|
||||
self.root_layout = QVBoxLayout(self)
|
||||
self.root_layout.setContentsMargins(6,6,6,6)
|
||||
# done = Signal(int)
|
||||
def __init__(self, library: "Library", driver: "QtDriver"):
|
||||
super().__init__()
|
||||
self.lib = library
|
||||
self.driver = driver
|
||||
self.count = -1
|
||||
self.filename = ""
|
||||
self.setWindowTitle(f"Fix Duplicate Files")
|
||||
self.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||
self.setMinimumSize(400, 300)
|
||||
self.root_layout = QVBoxLayout(self)
|
||||
self.root_layout.setContentsMargins(6, 6, 6, 6)
|
||||
|
||||
self.desc_widget = QLabel()
|
||||
self.desc_widget.setObjectName('descriptionLabel')
|
||||
self.desc_widget.setWordWrap(True)
|
||||
self.desc_widget.setStyleSheet(
|
||||
# 'background:blue;'
|
||||
'text-align:left;'
|
||||
# 'font-weight:bold;'
|
||||
# 'font-size:14px;'
|
||||
# 'padding-top: 6px'
|
||||
'')
|
||||
self.desc_widget.setText('''TagStudio supports importing DupeGuru results to manage duplicate files.''')
|
||||
self.desc_widget.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
self.desc_widget = QLabel()
|
||||
self.desc_widget.setObjectName("descriptionLabel")
|
||||
self.desc_widget.setWordWrap(True)
|
||||
self.desc_widget.setStyleSheet(
|
||||
# 'background:blue;'
|
||||
"text-align:left;"
|
||||
# 'font-weight:bold;'
|
||||
# 'font-size:14px;'
|
||||
# 'padding-top: 6px'
|
||||
""
|
||||
)
|
||||
self.desc_widget.setText(
|
||||
"""TagStudio supports importing DupeGuru results to manage duplicate files."""
|
||||
)
|
||||
self.desc_widget.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
self.dupe_count = QLabel()
|
||||
self.dupe_count.setObjectName('dupeCountLabel')
|
||||
self.dupe_count.setStyleSheet(
|
||||
# 'background:blue;'
|
||||
# 'text-align:center;'
|
||||
'font-weight:bold;'
|
||||
'font-size:14px;'
|
||||
# 'padding-top: 6px'
|
||||
'')
|
||||
self.dupe_count.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
self.dupe_count = QLabel()
|
||||
self.dupe_count.setObjectName("dupeCountLabel")
|
||||
self.dupe_count.setStyleSheet(
|
||||
# 'background:blue;'
|
||||
# 'text-align:center;'
|
||||
"font-weight:bold;"
|
||||
"font-size:14px;"
|
||||
# 'padding-top: 6px'
|
||||
""
|
||||
)
|
||||
self.dupe_count.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
self.file_label = QLabel()
|
||||
self.file_label.setObjectName('fileLabel')
|
||||
# self.file_label.setStyleSheet(
|
||||
# # 'background:blue;'
|
||||
# # 'text-align:center;'
|
||||
# 'font-weight:bold;'
|
||||
# 'font-size:14px;'
|
||||
# # 'padding-top: 6px'
|
||||
# '')
|
||||
# self.file_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
self.file_label.setText('No DupeGuru File Selected')
|
||||
|
||||
self.file_label = QLabel()
|
||||
self.file_label.setObjectName("fileLabel")
|
||||
# self.file_label.setStyleSheet(
|
||||
# # 'background:blue;'
|
||||
# # 'text-align:center;'
|
||||
# 'font-weight:bold;'
|
||||
# 'font-size:14px;'
|
||||
# # 'padding-top: 6px'
|
||||
# '')
|
||||
# self.file_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
self.file_label.setText("No DupeGuru File Selected")
|
||||
|
||||
self.open_button = QPushButton()
|
||||
self.open_button.setText('&Load DupeGuru File')
|
||||
self.open_button.clicked.connect(lambda: self.select_file())
|
||||
self.open_button = QPushButton()
|
||||
self.open_button.setText("&Load DupeGuru File")
|
||||
self.open_button.clicked.connect(lambda: self.select_file())
|
||||
|
||||
self.mirror_button = QPushButton()
|
||||
self.mirror_modal = MirrorEntriesModal(self.lib, self.driver)
|
||||
self.mirror_modal.done.connect(lambda: self.refresh_dupes())
|
||||
self.mirror_button.setText('&Mirror Entries')
|
||||
self.mirror_button.clicked.connect(lambda: self.mirror_modal.show())
|
||||
self.mirror_desc = QLabel()
|
||||
self.mirror_desc.setWordWrap(True)
|
||||
self.mirror_desc.setText("""Mirror the Entry data across each duplicate match set, combining all data while not removing or duplicating fields. This operation will not delete any files or data.""")
|
||||
self.mirror_button = QPushButton()
|
||||
self.mirror_modal = MirrorEntriesModal(self.lib, self.driver)
|
||||
self.mirror_modal.done.connect(lambda: self.refresh_dupes())
|
||||
self.mirror_button.setText("&Mirror Entries")
|
||||
self.mirror_button.clicked.connect(lambda: self.mirror_modal.show())
|
||||
self.mirror_desc = QLabel()
|
||||
self.mirror_desc.setWordWrap(True)
|
||||
self.mirror_desc.setText(
|
||||
"""Mirror the Entry data across each duplicate match set, combining all data while not removing or duplicating fields. This operation will not delete any files or data."""
|
||||
)
|
||||
|
||||
# self.mirror_delete_button = QPushButton()
|
||||
# self.mirror_delete_button.setText('Mirror && Delete')
|
||||
# self.mirror_delete_button = QPushButton()
|
||||
# self.mirror_delete_button.setText('Mirror && Delete')
|
||||
|
||||
self.advice_label = QLabel()
|
||||
self.advice_label.setWordWrap(True)
|
||||
self.advice_label.setText("""After mirroring, you're free to use DupeGuru to delete the unwanted files. Afterwards, use TagStudio's "Fix Unlinked Entries" feature in the Tools menu in order to delete the unlinked Entries.""")
|
||||
|
||||
self.button_container = QWidget()
|
||||
self.button_layout = QHBoxLayout(self.button_container)
|
||||
self.button_layout.setContentsMargins(6,6,6,6)
|
||||
self.button_layout.addStretch(1)
|
||||
|
||||
self.done_button = QPushButton()
|
||||
self.done_button.setText('&Done')
|
||||
# self.save_button.setAutoDefault(True)
|
||||
self.done_button.setDefault(True)
|
||||
self.done_button.clicked.connect(self.hide)
|
||||
# self.done_button.clicked.connect(lambda: self.done.emit(self.combo_box.currentIndex()))
|
||||
# self.save_button.clicked.connect(lambda: save_callback(widget.get_content()))
|
||||
self.button_layout.addWidget(self.done_button)
|
||||
self.advice_label = QLabel()
|
||||
self.advice_label.setWordWrap(True)
|
||||
self.advice_label.setText(
|
||||
"""After mirroring, you're free to use DupeGuru to delete the unwanted files. Afterwards, use TagStudio's "Fix Unlinked Entries" feature in the Tools menu in order to delete the unlinked Entries."""
|
||||
)
|
||||
|
||||
# self.returnPressed.connect(lambda: self.done.emit(self.combo_box.currentIndex()))
|
||||
|
||||
# self.done.connect(lambda x: callback(x))
|
||||
|
||||
self.root_layout.addWidget(self.desc_widget)
|
||||
self.root_layout.addWidget(self.dupe_count)
|
||||
self.root_layout.addWidget(self.file_label)
|
||||
self.root_layout.addWidget(self.open_button)
|
||||
# self.mirror_delete_button.setHidden(True)
|
||||
self.button_container = QWidget()
|
||||
self.button_layout = QHBoxLayout(self.button_container)
|
||||
self.button_layout.setContentsMargins(6, 6, 6, 6)
|
||||
self.button_layout.addStretch(1)
|
||||
|
||||
self.root_layout.addWidget(self.mirror_button)
|
||||
self.root_layout.addWidget(self.mirror_desc)
|
||||
# self.root_layout.addWidget(self.mirror_delete_button)
|
||||
self.root_layout.addWidget(self.advice_label)
|
||||
# self.root_layout.setStretch(1,2)
|
||||
self.root_layout.addStretch(1)
|
||||
self.root_layout.addWidget(self.button_container)
|
||||
self.done_button = QPushButton()
|
||||
self.done_button.setText("&Done")
|
||||
# self.save_button.setAutoDefault(True)
|
||||
self.done_button.setDefault(True)
|
||||
self.done_button.clicked.connect(self.hide)
|
||||
# self.done_button.clicked.connect(lambda: self.done.emit(self.combo_box.currentIndex()))
|
||||
# self.save_button.clicked.connect(lambda: save_callback(widget.get_content()))
|
||||
self.button_layout.addWidget(self.done_button)
|
||||
|
||||
self.set_dupe_count(self.count)
|
||||
|
||||
def select_file(self):
|
||||
qfd = QFileDialog(self,
|
||||
'Open DupeGuru Results File',
|
||||
os.path.normpath(self.lib.library_dir))
|
||||
qfd.setFileMode(QFileDialog.FileMode.ExistingFile)
|
||||
qfd.setNameFilter("DupeGuru Files (*.dupeguru)")
|
||||
if qfd.exec_():
|
||||
filename = qfd.selectedFiles()
|
||||
if filename:
|
||||
self.set_filename(filename[0])
|
||||
|
||||
def set_filename(self, filename:str):
|
||||
if filename:
|
||||
self.file_label.setText(filename)
|
||||
else:
|
||||
self.file_label.setText('No DupeGuru File Selected')
|
||||
self.filename = filename
|
||||
self.refresh_dupes()
|
||||
self.mirror_modal.refresh_list()
|
||||
|
||||
def refresh_dupes(self):
|
||||
self.lib.refresh_dupe_files(self.filename)
|
||||
self.set_dupe_count(len(self.lib.dupe_files))
|
||||
# self.returnPressed.connect(lambda: self.done.emit(self.combo_box.currentIndex()))
|
||||
|
||||
def set_dupe_count(self, count:int):
|
||||
self.count = count
|
||||
if self.count < 0:
|
||||
self.mirror_button.setDisabled(True)
|
||||
self.dupe_count.setText(f'Duplicate File Matches: N/A')
|
||||
elif self.count == 0:
|
||||
self.mirror_button.setDisabled(True)
|
||||
self.dupe_count.setText(f'Duplicate File Matches: {count}')
|
||||
else:
|
||||
self.mirror_button.setDisabled(False)
|
||||
self.dupe_count.setText(f'Duplicate File Matches: {count}')
|
||||
# self.done.connect(lambda x: callback(x))
|
||||
|
||||
self.root_layout.addWidget(self.desc_widget)
|
||||
self.root_layout.addWidget(self.dupe_count)
|
||||
self.root_layout.addWidget(self.file_label)
|
||||
self.root_layout.addWidget(self.open_button)
|
||||
# self.mirror_delete_button.setHidden(True)
|
||||
|
||||
self.root_layout.addWidget(self.mirror_button)
|
||||
self.root_layout.addWidget(self.mirror_desc)
|
||||
# self.root_layout.addWidget(self.mirror_delete_button)
|
||||
self.root_layout.addWidget(self.advice_label)
|
||||
# self.root_layout.setStretch(1,2)
|
||||
self.root_layout.addStretch(1)
|
||||
self.root_layout.addWidget(self.button_container)
|
||||
|
||||
self.set_dupe_count(self.count)
|
||||
|
||||
def select_file(self):
|
||||
qfd = QFileDialog(
|
||||
self, "Open DupeGuru Results File", os.path.normpath(self.lib.library_dir)
|
||||
)
|
||||
qfd.setFileMode(QFileDialog.FileMode.ExistingFile)
|
||||
qfd.setNameFilter("DupeGuru Files (*.dupeguru)")
|
||||
if qfd.exec_():
|
||||
filename = qfd.selectedFiles()
|
||||
if filename:
|
||||
self.set_filename(filename[0])
|
||||
|
||||
def set_filename(self, filename: str):
|
||||
if filename:
|
||||
self.file_label.setText(filename)
|
||||
else:
|
||||
self.file_label.setText("No DupeGuru File Selected")
|
||||
self.filename = filename
|
||||
self.refresh_dupes()
|
||||
self.mirror_modal.refresh_list()
|
||||
|
||||
def refresh_dupes(self):
|
||||
self.lib.refresh_dupe_files(self.filename)
|
||||
self.set_dupe_count(len(self.lib.dupe_files))
|
||||
|
||||
def set_dupe_count(self, count: int):
|
||||
self.count = count
|
||||
if self.count < 0:
|
||||
self.mirror_button.setDisabled(True)
|
||||
self.dupe_count.setText(f"Duplicate File Matches: N/A")
|
||||
elif self.count == 0:
|
||||
self.mirror_button.setDisabled(True)
|
||||
self.dupe_count.setText(f"Duplicate File Matches: {count}")
|
||||
else:
|
||||
self.mirror_button.setDisabled(False)
|
||||
self.dupe_count.setText(f"Duplicate File Matches: {count}")
|
||||
|
|
|
@ -12,168 +12,175 @@ from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushBu
|
|||
from src.core.library import Library
|
||||
from src.qt.helpers import FunctionIterator, CustomRunnable
|
||||
from src.qt.modals import DeleteUnlinkedEntriesModal, RelinkUnlinkedEntries
|
||||
from src.qt.widgets import ProgressWidget
|
||||
from src.qt.widgets import ProgressWidget
|
||||
|
||||
# Only import for type checking/autocompletion, will not be imported at runtime.
|
||||
if typing.TYPE_CHECKING:
|
||||
from src.qt.ts_qt import QtDriver
|
||||
from src.qt.ts_qt import QtDriver
|
||||
|
||||
|
||||
ERROR = f'[ERROR]'
|
||||
WARNING = f'[WARNING]'
|
||||
INFO = f'[INFO]'
|
||||
ERROR = f"[ERROR]"
|
||||
WARNING = f"[WARNING]"
|
||||
INFO = f"[INFO]"
|
||||
|
||||
logging.basicConfig(format="%(message)s", level=logging.INFO)
|
||||
|
||||
|
||||
class FixUnlinkedEntriesModal(QWidget):
|
||||
# done = Signal(int)
|
||||
def __init__(self, library:'Library', driver:'QtDriver'):
|
||||
super().__init__()
|
||||
self.lib = library
|
||||
self.driver = driver
|
||||
self.count = -1
|
||||
self.setWindowTitle(f'Fix Unlinked Entries')
|
||||
self.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||
self.setMinimumSize(400, 300)
|
||||
self.root_layout = QVBoxLayout(self)
|
||||
self.root_layout.setContentsMargins(6,6,6,6)
|
||||
# done = Signal(int)
|
||||
def __init__(self, library: "Library", driver: "QtDriver"):
|
||||
super().__init__()
|
||||
self.lib = library
|
||||
self.driver = driver
|
||||
self.count = -1
|
||||
self.setWindowTitle(f"Fix Unlinked Entries")
|
||||
self.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||
self.setMinimumSize(400, 300)
|
||||
self.root_layout = QVBoxLayout(self)
|
||||
self.root_layout.setContentsMargins(6, 6, 6, 6)
|
||||
|
||||
self.desc_widget = QLabel()
|
||||
self.desc_widget.setObjectName('descriptionLabel')
|
||||
self.desc_widget.setWordWrap(True)
|
||||
self.desc_widget.setStyleSheet(
|
||||
# 'background:blue;'
|
||||
'text-align:left;'
|
||||
# 'font-weight:bold;'
|
||||
# 'font-size:14px;'
|
||||
# 'padding-top: 6px'
|
||||
'')
|
||||
self.desc_widget.setText('''Each library entry is linked to a file in one of your directories. If a file linked to an entry is moved or deleted outside of TagStudio, it is then considered unlinked.
|
||||
Unlinked entries may be automatically relinked via searching your directories, manually relinked by the user, or deleted if desired.''')
|
||||
self.desc_widget.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
self.desc_widget = QLabel()
|
||||
self.desc_widget.setObjectName("descriptionLabel")
|
||||
self.desc_widget.setWordWrap(True)
|
||||
self.desc_widget.setStyleSheet(
|
||||
# 'background:blue;'
|
||||
"text-align:left;"
|
||||
# 'font-weight:bold;'
|
||||
# 'font-size:14px;'
|
||||
# 'padding-top: 6px'
|
||||
""
|
||||
)
|
||||
self.desc_widget.setText("""Each library entry is linked to a file in one of your directories. If a file linked to an entry is moved or deleted outside of TagStudio, it is then considered unlinked.
|
||||
Unlinked entries may be automatically relinked via searching your directories, manually relinked by the user, or deleted if desired.""")
|
||||
self.desc_widget.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
self.missing_count = QLabel()
|
||||
self.missing_count.setObjectName('missingCountLabel')
|
||||
self.missing_count.setStyleSheet(
|
||||
# 'background:blue;'
|
||||
# 'text-align:center;'
|
||||
'font-weight:bold;'
|
||||
'font-size:14px;'
|
||||
# 'padding-top: 6px'
|
||||
'')
|
||||
self.missing_count.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
# self.missing_count.setText('Missing Files: N/A')
|
||||
|
||||
self.missing_count = QLabel()
|
||||
self.missing_count.setObjectName("missingCountLabel")
|
||||
self.missing_count.setStyleSheet(
|
||||
# 'background:blue;'
|
||||
# 'text-align:center;'
|
||||
"font-weight:bold;"
|
||||
"font-size:14px;"
|
||||
# 'padding-top: 6px'
|
||||
""
|
||||
)
|
||||
self.missing_count.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
# self.missing_count.setText('Missing Files: N/A')
|
||||
|
||||
self.refresh_button = QPushButton()
|
||||
self.refresh_button.setText('&Refresh')
|
||||
self.refresh_button.clicked.connect(lambda: self.refresh_missing_files())
|
||||
self.refresh_button = QPushButton()
|
||||
self.refresh_button.setText("&Refresh")
|
||||
self.refresh_button.clicked.connect(lambda: self.refresh_missing_files())
|
||||
|
||||
self.search_button = QPushButton()
|
||||
self.search_button.setText('&Search && Relink')
|
||||
self.relink_class = RelinkUnlinkedEntries(self.lib, self.driver)
|
||||
self.relink_class.done.connect(lambda: self.refresh_missing_files())
|
||||
self.relink_class.done.connect(lambda: self.driver.update_thumbs())
|
||||
self.search_button.clicked.connect(lambda: self.relink_class.repair_entries())
|
||||
self.search_button = QPushButton()
|
||||
self.search_button.setText("&Search && Relink")
|
||||
self.relink_class = RelinkUnlinkedEntries(self.lib, self.driver)
|
||||
self.relink_class.done.connect(lambda: self.refresh_missing_files())
|
||||
self.relink_class.done.connect(lambda: self.driver.update_thumbs())
|
||||
self.search_button.clicked.connect(lambda: self.relink_class.repair_entries())
|
||||
|
||||
self.manual_button = QPushButton()
|
||||
self.manual_button.setText('&Manual Relink')
|
||||
self.manual_button = QPushButton()
|
||||
self.manual_button.setText("&Manual Relink")
|
||||
|
||||
self.delete_button = QPushButton()
|
||||
self.delete_modal = DeleteUnlinkedEntriesModal(self.lib, self.driver)
|
||||
self.delete_modal.done.connect(lambda: self.set_missing_count(len(self.lib.missing_files)))
|
||||
self.delete_modal.done.connect(lambda: self.driver.update_thumbs())
|
||||
self.delete_button.setText('De&lete Unlinked Entries')
|
||||
self.delete_button.clicked.connect(lambda: self.delete_modal.show())
|
||||
|
||||
# self.combo_box = QComboBox()
|
||||
# self.combo_box.setEditable(False)
|
||||
# # self.combo_box.setMaxVisibleItems(5)
|
||||
# self.combo_box.setStyleSheet('combobox-popup:0;')
|
||||
# self.combo_box.view().setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
|
||||
# for df in self.lib.default_fields:
|
||||
# self.combo_box.addItem(f'{df["name"]} ({df["type"].replace("_", " ").title()})')
|
||||
|
||||
self.delete_button = QPushButton()
|
||||
self.delete_modal = DeleteUnlinkedEntriesModal(self.lib, self.driver)
|
||||
self.delete_modal.done.connect(
|
||||
lambda: self.set_missing_count(len(self.lib.missing_files))
|
||||
)
|
||||
self.delete_modal.done.connect(lambda: self.driver.update_thumbs())
|
||||
self.delete_button.setText("De&lete Unlinked Entries")
|
||||
self.delete_button.clicked.connect(lambda: self.delete_modal.show())
|
||||
|
||||
self.button_container = QWidget()
|
||||
self.button_layout = QHBoxLayout(self.button_container)
|
||||
self.button_layout.setContentsMargins(6,6,6,6)
|
||||
self.button_layout.addStretch(1)
|
||||
|
||||
self.done_button = QPushButton()
|
||||
self.done_button.setText('&Done')
|
||||
# self.save_button.setAutoDefault(True)
|
||||
self.done_button.setDefault(True)
|
||||
self.done_button.clicked.connect(self.hide)
|
||||
# self.done_button.clicked.connect(lambda: self.done.emit(self.combo_box.currentIndex()))
|
||||
# self.save_button.clicked.connect(lambda: save_callback(widget.get_content()))
|
||||
self.button_layout.addWidget(self.done_button)
|
||||
# self.combo_box = QComboBox()
|
||||
# self.combo_box.setEditable(False)
|
||||
# # self.combo_box.setMaxVisibleItems(5)
|
||||
# self.combo_box.setStyleSheet('combobox-popup:0;')
|
||||
# self.combo_box.view().setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
|
||||
# for df in self.lib.default_fields:
|
||||
# self.combo_box.addItem(f'{df["name"]} ({df["type"].replace("_", " ").title()})')
|
||||
|
||||
# self.returnPressed.connect(lambda: self.done.emit(self.combo_box.currentIndex()))
|
||||
|
||||
# self.done.connect(lambda x: callback(x))
|
||||
|
||||
self.root_layout.addWidget(self.desc_widget)
|
||||
self.root_layout.addWidget(self.missing_count)
|
||||
self.root_layout.addWidget(self.refresh_button)
|
||||
self.root_layout.addWidget(self.search_button)
|
||||
self.manual_button.setHidden(True)
|
||||
self.root_layout.addWidget(self.manual_button)
|
||||
self.root_layout.addWidget(self.delete_button)
|
||||
# self.root_layout.setStretch(1,2)
|
||||
self.root_layout.addStretch(1)
|
||||
self.root_layout.addWidget(self.button_container)
|
||||
self.button_container = QWidget()
|
||||
self.button_layout = QHBoxLayout(self.button_container)
|
||||
self.button_layout.setContentsMargins(6, 6, 6, 6)
|
||||
self.button_layout.addStretch(1)
|
||||
|
||||
self.set_missing_count(self.count)
|
||||
self.done_button = QPushButton()
|
||||
self.done_button.setText("&Done")
|
||||
# self.save_button.setAutoDefault(True)
|
||||
self.done_button.setDefault(True)
|
||||
self.done_button.clicked.connect(self.hide)
|
||||
# self.done_button.clicked.connect(lambda: self.done.emit(self.combo_box.currentIndex()))
|
||||
# self.save_button.clicked.connect(lambda: save_callback(widget.get_content()))
|
||||
self.button_layout.addWidget(self.done_button)
|
||||
|
||||
def refresh_missing_files(self):
|
||||
logging.info(f'Start RMF: {QThread.currentThread()}')
|
||||
# pb = QProgressDialog(f'Scanning Library for Unlinked Entries...', None, 0,len(self.lib.entries))
|
||||
# pb.setFixedSize(432, 112)
|
||||
# pb.setWindowFlags(pb.windowFlags() & ~Qt.WindowType.WindowCloseButtonHint)
|
||||
# pb.setWindowTitle('Scanning Library')
|
||||
# pb.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||
# pb.show()
|
||||
# self.returnPressed.connect(lambda: self.done.emit(self.combo_box.currentIndex()))
|
||||
|
||||
iterator = FunctionIterator(self.lib.refresh_missing_files)
|
||||
pw = ProgressWidget(
|
||||
window_title='Scanning Library',
|
||||
label_text=f'Scanning Library for Unlinked Entries...',
|
||||
cancel_button_text=None,
|
||||
minimum=0,
|
||||
maximum=len(self.lib.entries)
|
||||
)
|
||||
pw.show()
|
||||
iterator.value.connect(lambda v: pw.update_progress(v+1))
|
||||
# rmf.value.connect(lambda v: pw.update_label(f'Progress: {v}'))
|
||||
r = CustomRunnable(lambda:iterator.run())
|
||||
QThreadPool.globalInstance().start(r)
|
||||
r.done.connect(lambda: (pw.hide(), pw.deleteLater(),
|
||||
self.set_missing_count(len(self.lib.missing_files)),
|
||||
self.delete_modal.refresh_list()))
|
||||
# self.done.connect(lambda x: callback(x))
|
||||
|
||||
# r = CustomRunnable(lambda: self.lib.refresh_missing_files(lambda v: self.update_scan_value(pb, v)))
|
||||
# r.done.connect(lambda: (pb.hide(), pb.deleteLater(), self.set_missing_count(len(self.lib.missing_files)), self.delete_modal.refresh_list()))
|
||||
# QThreadPool.globalInstance().start(r)
|
||||
# # r.run()
|
||||
# pass
|
||||
self.root_layout.addWidget(self.desc_widget)
|
||||
self.root_layout.addWidget(self.missing_count)
|
||||
self.root_layout.addWidget(self.refresh_button)
|
||||
self.root_layout.addWidget(self.search_button)
|
||||
self.manual_button.setHidden(True)
|
||||
self.root_layout.addWidget(self.manual_button)
|
||||
self.root_layout.addWidget(self.delete_button)
|
||||
# self.root_layout.setStretch(1,2)
|
||||
self.root_layout.addStretch(1)
|
||||
self.root_layout.addWidget(self.button_container)
|
||||
|
||||
# def update_scan_value(self, pb:QProgressDialog, value=int):
|
||||
# # pb.setLabelText(f'Scanning Library for Unlinked Entries ({value}/{len(self.lib.entries)})...')
|
||||
# pb.setValue(value)
|
||||
self.set_missing_count(self.count)
|
||||
|
||||
def set_missing_count(self, count:int):
|
||||
self.count = count
|
||||
if self.count < 0:
|
||||
self.search_button.setDisabled(True)
|
||||
self.delete_button.setDisabled(True)
|
||||
self.missing_count.setText(f'Unlinked Entries: N/A')
|
||||
elif self.count == 0:
|
||||
self.search_button.setDisabled(True)
|
||||
self.delete_button.setDisabled(True)
|
||||
self.missing_count.setText(f'Unlinked Entries: {count}')
|
||||
else:
|
||||
self.search_button.setDisabled(False)
|
||||
self.delete_button.setDisabled(False)
|
||||
self.missing_count.setText(f'Unlinked Entries: {count}')
|
||||
def refresh_missing_files(self):
|
||||
logging.info(f"Start RMF: {QThread.currentThread()}")
|
||||
# pb = QProgressDialog(f'Scanning Library for Unlinked Entries...', None, 0,len(self.lib.entries))
|
||||
# pb.setFixedSize(432, 112)
|
||||
# pb.setWindowFlags(pb.windowFlags() & ~Qt.WindowType.WindowCloseButtonHint)
|
||||
# pb.setWindowTitle('Scanning Library')
|
||||
# pb.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||
# pb.show()
|
||||
|
||||
iterator = FunctionIterator(self.lib.refresh_missing_files)
|
||||
pw = ProgressWidget(
|
||||
window_title="Scanning Library",
|
||||
label_text=f"Scanning Library for Unlinked Entries...",
|
||||
cancel_button_text=None,
|
||||
minimum=0,
|
||||
maximum=len(self.lib.entries),
|
||||
)
|
||||
pw.show()
|
||||
iterator.value.connect(lambda v: pw.update_progress(v + 1))
|
||||
# rmf.value.connect(lambda v: pw.update_label(f'Progress: {v}'))
|
||||
r = CustomRunnable(lambda: iterator.run())
|
||||
QThreadPool.globalInstance().start(r)
|
||||
r.done.connect(
|
||||
lambda: (
|
||||
pw.hide(),
|
||||
pw.deleteLater(),
|
||||
self.set_missing_count(len(self.lib.missing_files)),
|
||||
self.delete_modal.refresh_list(),
|
||||
)
|
||||
)
|
||||
|
||||
# r = CustomRunnable(lambda: self.lib.refresh_missing_files(lambda v: self.update_scan_value(pb, v)))
|
||||
# r.done.connect(lambda: (pb.hide(), pb.deleteLater(), self.set_missing_count(len(self.lib.missing_files)), self.delete_modal.refresh_list()))
|
||||
# QThreadPool.globalInstance().start(r)
|
||||
# # r.run()
|
||||
# pass
|
||||
|
||||
# def update_scan_value(self, pb:QProgressDialog, value=int):
|
||||
# # pb.setLabelText(f'Scanning Library for Unlinked Entries ({value}/{len(self.lib.entries)})...')
|
||||
# pb.setValue(value)
|
||||
|
||||
def set_missing_count(self, count: int):
|
||||
self.count = count
|
||||
if self.count < 0:
|
||||
self.search_button.setDisabled(True)
|
||||
self.delete_button.setDisabled(True)
|
||||
self.missing_count.setText(f"Unlinked Entries: N/A")
|
||||
elif self.count == 0:
|
||||
self.search_button.setDisabled(True)
|
||||
self.delete_button.setDisabled(True)
|
||||
self.missing_count.setText(f"Unlinked Entries: {count}")
|
||||
else:
|
||||
self.search_button.setDisabled(False)
|
||||
self.delete_button.setDisabled(False)
|
||||
self.missing_count.setText(f"Unlinked Entries: {count}")
|
||||
|
|
|
@ -8,7 +8,15 @@ import math
|
|||
import typing
|
||||
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QScrollArea, QFrame
|
||||
from PySide6.QtWidgets import (
|
||||
QWidget,
|
||||
QVBoxLayout,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QPushButton,
|
||||
QScrollArea,
|
||||
QFrame,
|
||||
)
|
||||
|
||||
from src.core.library import Library, Tag
|
||||
from src.core.palette import ColorType, get_tag_color
|
||||
|
@ -16,306 +24,329 @@ from src.qt.flowlayout import FlowLayout
|
|||
|
||||
# Only import for type checking/autocompletion, will not be imported at runtime.
|
||||
if typing.TYPE_CHECKING:
|
||||
from src.qt.ts_qt import QtDriver
|
||||
from src.qt.ts_qt import QtDriver
|
||||
|
||||
|
||||
ERROR = f'[ERROR]'
|
||||
WARNING = f'[WARNING]'
|
||||
INFO = f'[INFO]'
|
||||
ERROR = f"[ERROR]"
|
||||
WARNING = f"[WARNING]"
|
||||
INFO = f"[INFO]"
|
||||
|
||||
logging.basicConfig(format="%(message)s", level=logging.INFO)
|
||||
|
||||
|
||||
def folders_to_tags(library:Library):
|
||||
logging.info("Converting folders to Tags")
|
||||
tree = dict(dirs={})
|
||||
def add_tag_to_tree(list:list[Tag]):
|
||||
branch = tree
|
||||
for tag in list:
|
||||
if tag.name not in branch["dirs"]:
|
||||
branch["dirs"][tag.name] = dict(dirs={},tag=tag)
|
||||
branch = branch["dirs"][tag.name]
|
||||
def folders_to_tags(library: Library):
|
||||
logging.info("Converting folders to Tags")
|
||||
tree = dict(dirs={})
|
||||
|
||||
def add_folders_to_tree(list:list[str])->Tag:
|
||||
branch = tree
|
||||
for folder in list:
|
||||
if folder not in branch["dirs"]:
|
||||
new_tag = Tag(-1, folder,"",[],([branch["tag"].id] if "tag" in branch else []),"")
|
||||
library.add_tag_to_library(new_tag)
|
||||
branch["dirs"][folder] = dict(dirs={},tag=new_tag)
|
||||
branch = branch["dirs"][folder]
|
||||
return branch["tag"]
|
||||
def add_tag_to_tree(list: list[Tag]):
|
||||
branch = tree
|
||||
for tag in list:
|
||||
if tag.name not in branch["dirs"]:
|
||||
branch["dirs"][tag.name] = dict(dirs={}, tag=tag)
|
||||
branch = branch["dirs"][tag.name]
|
||||
|
||||
def add_folders_to_tree(list: list[str]) -> Tag:
|
||||
branch = tree
|
||||
for folder in list:
|
||||
if folder not in branch["dirs"]:
|
||||
new_tag = Tag(
|
||||
-1,
|
||||
folder,
|
||||
"",
|
||||
[],
|
||||
([branch["tag"].id] if "tag" in branch else []),
|
||||
"",
|
||||
)
|
||||
library.add_tag_to_library(new_tag)
|
||||
branch["dirs"][folder] = dict(dirs={}, tag=new_tag)
|
||||
branch = branch["dirs"][folder]
|
||||
return branch["tag"]
|
||||
|
||||
for tag in library.tags:
|
||||
reversed_tag = reverse_tag(library, tag, None)
|
||||
add_tag_to_tree(reversed_tag)
|
||||
|
||||
for entry in library.entries:
|
||||
folders = entry.path.split("\\")
|
||||
if len(folders) == 1 and folders[0] == "":
|
||||
continue
|
||||
tag = add_folders_to_tree(folders)
|
||||
if tag:
|
||||
if not entry.has_tag(library, tag.id):
|
||||
entry.add_tag(library, tag.id, 6)
|
||||
|
||||
logging.info("Done")
|
||||
|
||||
|
||||
for tag in library.tags:
|
||||
reversed_tag = reverse_tag(library,tag,None)
|
||||
add_tag_to_tree(reversed_tag)
|
||||
def reverse_tag(library: Library, tag: Tag, list: list[Tag]) -> list[Tag]:
|
||||
if list != None:
|
||||
list.append(tag)
|
||||
else:
|
||||
list = [tag]
|
||||
|
||||
for entry in library.entries:
|
||||
folders = entry.path.split("\\")
|
||||
if len(folders)== 1 and folders[0]=="": continue
|
||||
tag = add_folders_to_tree(folders)
|
||||
if tag:
|
||||
if not entry.has_tag(library,tag.id):
|
||||
entry.add_tag(library,tag.id,6)
|
||||
if len(tag.subtag_ids) == 0:
|
||||
list.reverse()
|
||||
return list
|
||||
else:
|
||||
for subtag_id in tag.subtag_ids:
|
||||
subtag = library.get_tag(subtag_id)
|
||||
return reverse_tag(library, subtag, list)
|
||||
|
||||
logging.info("Done")
|
||||
|
||||
def reverse_tag(library:Library,tag:Tag,list:list[Tag]) -> list[Tag]:
|
||||
if list != None:
|
||||
list.append(tag)
|
||||
else:
|
||||
list = [tag]
|
||||
# =========== UI ===========
|
||||
|
||||
if len(tag.subtag_ids) == 0:
|
||||
list.reverse()
|
||||
return list
|
||||
else:
|
||||
for subtag_id in tag.subtag_ids:
|
||||
subtag = library.get_tag(subtag_id)
|
||||
return reverse_tag(library,subtag,list)
|
||||
|
||||
#=========== UI ===========
|
||||
def generate_preview_data(library: Library):
|
||||
tree = dict(dirs={}, files=[])
|
||||
|
||||
def generate_preview_data(library:Library):
|
||||
tree = dict(dirs={},files=[])
|
||||
def add_tag_to_tree(list: list[Tag]):
|
||||
branch = tree
|
||||
for tag in list:
|
||||
if tag.name not in branch["dirs"]:
|
||||
branch["dirs"][tag.name] = dict(dirs={}, tag=tag, files=[])
|
||||
branch = branch["dirs"][tag.name]
|
||||
|
||||
def add_tag_to_tree(list:list[Tag]):
|
||||
branch = tree
|
||||
for tag in list:
|
||||
if tag.name not in branch["dirs"]:
|
||||
branch["dirs"][tag.name] = dict(dirs={},tag=tag,files=[])
|
||||
branch = branch["dirs"][tag.name]
|
||||
def add_folders_to_tree(list: list[str]) -> Tag:
|
||||
branch = tree
|
||||
for folder in list:
|
||||
if folder not in branch["dirs"]:
|
||||
new_tag = Tag(-1, folder, "", [], [], "green")
|
||||
branch["dirs"][folder] = dict(dirs={}, tag=new_tag, files=[])
|
||||
branch = branch["dirs"][folder]
|
||||
return branch
|
||||
|
||||
def add_folders_to_tree(list:list[str])->Tag:
|
||||
branch = tree
|
||||
for folder in list:
|
||||
if folder not in branch["dirs"]:
|
||||
new_tag = Tag(-1, folder,"",[],[],"green")
|
||||
branch["dirs"][folder] = dict(dirs={},tag=new_tag,files=[])
|
||||
branch = branch["dirs"][folder]
|
||||
return branch
|
||||
|
||||
for tag in library.tags:
|
||||
reversed_tag = reverse_tag(library,tag,None)
|
||||
add_tag_to_tree(reversed_tag)
|
||||
|
||||
for entry in library.entries:
|
||||
folders = entry.path.split("\\")
|
||||
if len(folders) == 1 and folders[0] == "": continue
|
||||
branch = add_folders_to_tree(folders)
|
||||
if branch:
|
||||
field_indexes = library.get_field_index_in_entry(entry,6)
|
||||
has_tag=False
|
||||
for index in field_indexes:
|
||||
content = library.get_field_attr(entry.fields[index],"content")
|
||||
for tag_id in content:
|
||||
tag = library.get_tag(tag_id)
|
||||
if tag.name == branch["tag"].name:
|
||||
has_tag=True
|
||||
break
|
||||
if not has_tag:
|
||||
branch["files"].append(entry.filename)
|
||||
for tag in library.tags:
|
||||
reversed_tag = reverse_tag(library, tag, None)
|
||||
add_tag_to_tree(reversed_tag)
|
||||
|
||||
def cut_branches_adding_nothing(branch:dict):
|
||||
folders = set(branch["dirs"].keys())
|
||||
for folder in folders:
|
||||
cut = cut_branches_adding_nothing(branch["dirs"][folder])
|
||||
if cut:
|
||||
branch['dirs'].pop(folder)
|
||||
for entry in library.entries:
|
||||
folders = entry.path.split("\\")
|
||||
if len(folders) == 1 and folders[0] == "":
|
||||
continue
|
||||
branch = add_folders_to_tree(folders)
|
||||
if branch:
|
||||
field_indexes = library.get_field_index_in_entry(entry, 6)
|
||||
has_tag = False
|
||||
for index in field_indexes:
|
||||
content = library.get_field_attr(entry.fields[index], "content")
|
||||
for tag_id in content:
|
||||
tag = library.get_tag(tag_id)
|
||||
if tag.name == branch["tag"].name:
|
||||
has_tag = True
|
||||
break
|
||||
if not has_tag:
|
||||
branch["files"].append(entry.filename)
|
||||
|
||||
if not "tag" in branch: return
|
||||
if branch["tag"].id == -1 or len(branch["files"])>0:#Needs to be first
|
||||
return False
|
||||
if len(branch["dirs"].keys()) == 0:
|
||||
return True
|
||||
def cut_branches_adding_nothing(branch: dict):
|
||||
folders = set(branch["dirs"].keys())
|
||||
for folder in folders:
|
||||
cut = cut_branches_adding_nothing(branch["dirs"][folder])
|
||||
if cut:
|
||||
branch["dirs"].pop(folder)
|
||||
|
||||
cut_branches_adding_nothing(tree)
|
||||
if not "tag" in branch:
|
||||
return
|
||||
if branch["tag"].id == -1 or len(branch["files"]) > 0: # Needs to be first
|
||||
return False
|
||||
if len(branch["dirs"].keys()) == 0:
|
||||
return True
|
||||
|
||||
return tree
|
||||
cut_branches_adding_nothing(tree)
|
||||
|
||||
return tree
|
||||
|
||||
|
||||
class FoldersToTagsModal(QWidget):
|
||||
# done = Signal(int)
|
||||
def __init__(self, library:'Library', driver:'QtDriver'):
|
||||
super().__init__()
|
||||
self.library = library
|
||||
self.driver = driver
|
||||
self.count = -1
|
||||
self.filename = ''
|
||||
|
||||
self.setWindowTitle(f'Folders To Tags')
|
||||
self.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||
self.setMinimumSize(500, 800)
|
||||
self.root_layout = QVBoxLayout(self)
|
||||
self.root_layout.setContentsMargins(6,6,6,6)
|
||||
|
||||
self.desc_widget = QLabel()
|
||||
self.desc_widget.setObjectName('descriptionLabel')
|
||||
self.desc_widget.setWordWrap(True)
|
||||
self.desc_widget.setStyleSheet(
|
||||
# 'background:blue;'
|
||||
'text-align:left;'
|
||||
# 'font-weight:bold;'
|
||||
'font-size:18px;'
|
||||
# 'padding-top: 6px'
|
||||
'')
|
||||
self.desc_widget.setText('''Creates tags based on the folder structure and applies them to entries.\n The Structure below shows all the tags that would be added and to which files they would be added. It being empty means that there are no Tag to be created or assigned''')
|
||||
self.desc_widget.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
self.open_close_button_w = QWidget()
|
||||
self.open_close_button_layout = QHBoxLayout(self.open_close_button_w)
|
||||
|
||||
self.open_all_button = QPushButton()
|
||||
self.open_all_button.setText("Open All")
|
||||
self.open_all_button.clicked.connect(lambda:self.set_all_branches(False))
|
||||
self.close_all_button = QPushButton()
|
||||
self.close_all_button.setText("Close All")
|
||||
self.close_all_button.clicked.connect(lambda:self.set_all_branches(True))
|
||||
|
||||
self.open_close_button_layout.addWidget(self.open_all_button)
|
||||
self.open_close_button_layout.addWidget(self.close_all_button)
|
||||
|
||||
self.scroll_contents = QWidget()
|
||||
self.scroll_layout = QVBoxLayout(self.scroll_contents)
|
||||
self.scroll_layout.setContentsMargins(6,0,6,0)
|
||||
self.scroll_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
||||
# done = Signal(int)
|
||||
def __init__(self, library: "Library", driver: "QtDriver"):
|
||||
super().__init__()
|
||||
self.library = library
|
||||
self.driver = driver
|
||||
self.count = -1
|
||||
self.filename = ""
|
||||
|
||||
self.scroll_area = QScrollArea()
|
||||
self.scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
|
||||
self.scroll_area.setWidgetResizable(True)
|
||||
self.scroll_area.setFrameShadow(QFrame.Shadow.Plain)
|
||||
self.scroll_area.setFrameShape(QFrame.Shape.NoFrame)
|
||||
self.scroll_area.setWidget(self.scroll_contents)
|
||||
self.setWindowTitle(f"Folders To Tags")
|
||||
self.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||
self.setMinimumSize(500, 800)
|
||||
self.root_layout = QVBoxLayout(self)
|
||||
self.root_layout.setContentsMargins(6, 6, 6, 6)
|
||||
|
||||
self.desc_widget = QLabel()
|
||||
self.desc_widget.setObjectName("descriptionLabel")
|
||||
self.desc_widget.setWordWrap(True)
|
||||
self.desc_widget.setStyleSheet(
|
||||
# 'background:blue;'
|
||||
"text-align:left;"
|
||||
# 'font-weight:bold;'
|
||||
"font-size:18px;"
|
||||
# 'padding-top: 6px'
|
||||
""
|
||||
)
|
||||
self.desc_widget.setText(
|
||||
"""Creates tags based on the folder structure and applies them to entries.\n The Structure below shows all the tags that would be added and to which files they would be added. It being empty means that there are no Tag to be created or assigned"""
|
||||
)
|
||||
self.desc_widget.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
self.open_close_button_w = QWidget()
|
||||
self.open_close_button_layout = QHBoxLayout(self.open_close_button_w)
|
||||
|
||||
self.open_all_button = QPushButton()
|
||||
self.open_all_button.setText("Open All")
|
||||
self.open_all_button.clicked.connect(lambda: self.set_all_branches(False))
|
||||
self.close_all_button = QPushButton()
|
||||
self.close_all_button.setText("Close All")
|
||||
self.close_all_button.clicked.connect(lambda: self.set_all_branches(True))
|
||||
|
||||
self.open_close_button_layout.addWidget(self.open_all_button)
|
||||
self.open_close_button_layout.addWidget(self.close_all_button)
|
||||
|
||||
self.scroll_contents = QWidget()
|
||||
self.scroll_layout = QVBoxLayout(self.scroll_contents)
|
||||
self.scroll_layout.setContentsMargins(6, 0, 6, 0)
|
||||
self.scroll_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
||||
|
||||
self.scroll_area = QScrollArea()
|
||||
self.scroll_area.setVerticalScrollBarPolicy(
|
||||
Qt.ScrollBarPolicy.ScrollBarAlwaysOn
|
||||
)
|
||||
self.scroll_area.setWidgetResizable(True)
|
||||
self.scroll_area.setFrameShadow(QFrame.Shadow.Plain)
|
||||
self.scroll_area.setFrameShape(QFrame.Shape.NoFrame)
|
||||
self.scroll_area.setWidget(self.scroll_contents)
|
||||
|
||||
self.apply_button = QPushButton()
|
||||
self.apply_button.setText("&Apply")
|
||||
self.apply_button.clicked.connect(self.on_apply)
|
||||
|
||||
self.showEvent = self.on_open
|
||||
|
||||
self.root_layout.addWidget(self.desc_widget)
|
||||
self.root_layout.addWidget(self.open_close_button_w)
|
||||
self.root_layout.addWidget(self.scroll_area)
|
||||
self.root_layout.addWidget(self.apply_button)
|
||||
|
||||
def on_apply(self, event):
|
||||
folders_to_tags(self.library)
|
||||
self.close()
|
||||
self.driver.preview_panel.update_widgets()
|
||||
|
||||
def on_open(self, event):
|
||||
for i in reversed(range(self.scroll_layout.count())):
|
||||
self.scroll_layout.itemAt(i).widget().setParent(None)
|
||||
|
||||
data = generate_preview_data(self.library)
|
||||
|
||||
for folder in data["dirs"].values():
|
||||
test = TreeItem(folder, None)
|
||||
self.scroll_layout.addWidget(test)
|
||||
|
||||
def set_all_branches(self, hidden: bool):
|
||||
for i in reversed(range(self.scroll_layout.count())):
|
||||
child = self.scroll_layout.itemAt(i).widget()
|
||||
if type(child) == TreeItem:
|
||||
child.set_all_branches(hidden)
|
||||
|
||||
self.apply_button = QPushButton()
|
||||
self.apply_button.setText('&Apply')
|
||||
self.apply_button.clicked.connect(self.on_apply)
|
||||
|
||||
self.showEvent = self.on_open
|
||||
|
||||
self.root_layout.addWidget(self.desc_widget)
|
||||
self.root_layout.addWidget(self.open_close_button_w)
|
||||
self.root_layout.addWidget(self.scroll_area)
|
||||
self.root_layout.addWidget(self.apply_button)
|
||||
|
||||
def on_apply(self,event):
|
||||
folders_to_tags(self.library)
|
||||
self.close()
|
||||
self.driver.preview_panel.update_widgets()
|
||||
|
||||
def on_open(self,event):
|
||||
for i in reversed(range(self.scroll_layout.count())):
|
||||
self.scroll_layout.itemAt(i).widget().setParent(None)
|
||||
|
||||
data = generate_preview_data(self.library)
|
||||
|
||||
for folder in data["dirs"].values():
|
||||
test = TreeItem(folder,None)
|
||||
self.scroll_layout.addWidget(test)
|
||||
|
||||
def set_all_branches(self,hidden:bool):
|
||||
for i in reversed(range(self.scroll_layout.count())):
|
||||
child = self.scroll_layout.itemAt(i).widget()
|
||||
if type(child) == TreeItem:
|
||||
child.set_all_branches(hidden)
|
||||
|
||||
class TreeItem(QWidget):
|
||||
def __init__(self,data:dict,parentTag:Tag):
|
||||
super().__init__()
|
||||
|
||||
self.setStyleSheet("QLabel{font-size: 13px}")
|
||||
|
||||
self.root_layout = QVBoxLayout(self)
|
||||
self.root_layout.setContentsMargins(20,0,0,0)
|
||||
self.root_layout.setSpacing(1)
|
||||
def __init__(self, data: dict, parentTag: Tag):
|
||||
super().__init__()
|
||||
|
||||
self.test = QWidget()
|
||||
self.root_layout.addWidget(self.test)
|
||||
|
||||
self.tag_layout = FlowLayout(self.test)
|
||||
|
||||
self.label = QLabel()
|
||||
self.tag_layout.addWidget(self.label)
|
||||
self.tag_widget = ModifiedTagWidget(data["tag"],parentTag)
|
||||
self.tag_widget.bg_button.clicked.connect(lambda:self.hide_show())
|
||||
self.tag_layout.addWidget(self.tag_widget)
|
||||
self.setStyleSheet("QLabel{font-size: 13px}")
|
||||
|
||||
self.children_widget = QWidget()
|
||||
self.children_layout = QVBoxLayout(self.children_widget)
|
||||
self.root_layout.addWidget(self.children_widget)
|
||||
self.root_layout = QVBoxLayout(self)
|
||||
self.root_layout.setContentsMargins(20, 0, 0, 0)
|
||||
self.root_layout.setSpacing(1)
|
||||
|
||||
self.populate(data)
|
||||
self.test = QWidget()
|
||||
self.root_layout.addWidget(self.test)
|
||||
|
||||
def hide_show(self):
|
||||
self.children_widget.setHidden(not self.children_widget.isHidden())
|
||||
self.label.setText(">" if self.children_widget.isHidden() else "v")
|
||||
self.tag_layout = FlowLayout(self.test)
|
||||
|
||||
def populate(self,data:dict):
|
||||
for folder in data["dirs"].values():
|
||||
item = TreeItem(folder,data["tag"])
|
||||
self.children_layout.addWidget(item)
|
||||
for file in data["files"]:
|
||||
label = QLabel()
|
||||
label.setText(" -> "+file)
|
||||
self.children_layout.addWidget(label)
|
||||
|
||||
if len(data["files"]) == 0 and len(data["dirs"].values()) == 0:
|
||||
self.hide_show()
|
||||
else:
|
||||
self.label.setText("v")
|
||||
|
||||
def set_all_branches(self,hidden:bool):
|
||||
for i in reversed(range(self.children_layout.count())):
|
||||
child = self.children_layout.itemAt(i).widget()
|
||||
if type(child) == TreeItem:
|
||||
child.set_all_branches(hidden)
|
||||
|
||||
self.children_widget.setHidden(hidden)
|
||||
self.label.setText(">" if self.children_widget.isHidden() else "v")
|
||||
|
||||
class ModifiedTagWidget(QWidget): # Needed to be modified because the original searched the display name in the library where it wasn't added yet
|
||||
def __init__(self, tag:Tag,parentTag:Tag) -> None:
|
||||
super().__init__()
|
||||
self.tag = tag
|
||||
self.label = QLabel()
|
||||
self.tag_layout.addWidget(self.label)
|
||||
self.tag_widget = ModifiedTagWidget(data["tag"], parentTag)
|
||||
self.tag_widget.bg_button.clicked.connect(lambda: self.hide_show())
|
||||
self.tag_layout.addWidget(self.tag_widget)
|
||||
|
||||
self.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
self.base_layout = QVBoxLayout(self)
|
||||
self.base_layout.setObjectName('baseLayout')
|
||||
self.base_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.children_widget = QWidget()
|
||||
self.children_layout = QVBoxLayout(self.children_widget)
|
||||
self.root_layout.addWidget(self.children_widget)
|
||||
|
||||
self.bg_button = QPushButton(self)
|
||||
self.bg_button.setFlat(True)
|
||||
if parentTag != None:
|
||||
text = f"{tag.name} ({parentTag.name})".replace('&', '&&')
|
||||
else:
|
||||
text = tag.name.replace('&', '&&')
|
||||
self.bg_button.setText(text)
|
||||
self.bg_button.setContextMenuPolicy(Qt.ContextMenuPolicy.ActionsContextMenu)
|
||||
self.populate(data)
|
||||
|
||||
self.inner_layout = QHBoxLayout()
|
||||
self.inner_layout.setObjectName('innerLayout')
|
||||
self.inner_layout.setContentsMargins(2, 2, 2, 2)
|
||||
self.bg_button.setLayout(self.inner_layout)
|
||||
self.bg_button.setMinimumSize(math.ceil(22*1.5), 22)
|
||||
def hide_show(self):
|
||||
self.children_widget.setHidden(not self.children_widget.isHidden())
|
||||
self.label.setText(">" if self.children_widget.isHidden() else "v")
|
||||
|
||||
self.bg_button.setStyleSheet(
|
||||
f'QPushButton{{'
|
||||
f'background: {get_tag_color(ColorType.PRIMARY, tag.color)};'
|
||||
f"color: {get_tag_color(ColorType.TEXT, tag.color)};"
|
||||
f'font-weight: 600;'
|
||||
f"border-color:{get_tag_color(ColorType.BORDER, tag.color)};"
|
||||
f'border-radius: 6px;'
|
||||
f'border-style:inset;'
|
||||
f'border-width: {math.ceil(1*self.devicePixelRatio())}px;'
|
||||
f'padding-right: 4px;'
|
||||
f'padding-bottom: 1px;'
|
||||
f'padding-left: 4px;'
|
||||
f'font-size: 13px'
|
||||
f'}}'
|
||||
f'QPushButton::hover{{'
|
||||
f"border-color:{get_tag_color(ColorType.LIGHT_ACCENT, tag.color)};"
|
||||
f'}}')
|
||||
def populate(self, data: dict):
|
||||
for folder in data["dirs"].values():
|
||||
item = TreeItem(folder, data["tag"])
|
||||
self.children_layout.addWidget(item)
|
||||
for file in data["files"]:
|
||||
label = QLabel()
|
||||
label.setText(" -> " + file)
|
||||
self.children_layout.addWidget(label)
|
||||
|
||||
self.base_layout.addWidget(self.bg_button)
|
||||
self.setMinimumSize(50,20)
|
||||
if len(data["files"]) == 0 and len(data["dirs"].values()) == 0:
|
||||
self.hide_show()
|
||||
else:
|
||||
self.label.setText("v")
|
||||
|
||||
def set_all_branches(self, hidden: bool):
|
||||
for i in reversed(range(self.children_layout.count())):
|
||||
child = self.children_layout.itemAt(i).widget()
|
||||
if type(child) == TreeItem:
|
||||
child.set_all_branches(hidden)
|
||||
|
||||
self.children_widget.setHidden(hidden)
|
||||
self.label.setText(">" if self.children_widget.isHidden() else "v")
|
||||
|
||||
|
||||
class ModifiedTagWidget(
|
||||
QWidget
|
||||
): # Needed to be modified because the original searched the display name in the library where it wasn't added yet
|
||||
def __init__(self, tag: Tag, parentTag: Tag) -> None:
|
||||
super().__init__()
|
||||
self.tag = tag
|
||||
|
||||
self.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
self.base_layout = QVBoxLayout(self)
|
||||
self.base_layout.setObjectName("baseLayout")
|
||||
self.base_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
self.bg_button = QPushButton(self)
|
||||
self.bg_button.setFlat(True)
|
||||
if parentTag != None:
|
||||
text = f"{tag.name} ({parentTag.name})".replace("&", "&&")
|
||||
else:
|
||||
text = tag.name.replace("&", "&&")
|
||||
self.bg_button.setText(text)
|
||||
self.bg_button.setContextMenuPolicy(Qt.ContextMenuPolicy.ActionsContextMenu)
|
||||
|
||||
self.inner_layout = QHBoxLayout()
|
||||
self.inner_layout.setObjectName("innerLayout")
|
||||
self.inner_layout.setContentsMargins(2, 2, 2, 2)
|
||||
self.bg_button.setLayout(self.inner_layout)
|
||||
self.bg_button.setMinimumSize(math.ceil(22 * 1.5), 22)
|
||||
|
||||
self.bg_button.setStyleSheet(
|
||||
f"QPushButton{{"
|
||||
f"background: {get_tag_color(ColorType.PRIMARY, tag.color)};"
|
||||
f"color: {get_tag_color(ColorType.TEXT, tag.color)};"
|
||||
f"font-weight: 600;"
|
||||
f"border-color:{get_tag_color(ColorType.BORDER, tag.color)};"
|
||||
f"border-radius: 6px;"
|
||||
f"border-style:inset;"
|
||||
f"border-width: {math.ceil(1*self.devicePixelRatio())}px;"
|
||||
f"padding-right: 4px;"
|
||||
f"padding-bottom: 1px;"
|
||||
f"padding-left: 4px;"
|
||||
f"font-size: 13px"
|
||||
f"}}"
|
||||
f"QPushButton::hover{{"
|
||||
f"border-color:{get_tag_color(ColorType.LIGHT_ACCENT, tag.color)};"
|
||||
f"}}"
|
||||
)
|
||||
|
||||
self.base_layout.addWidget(self.bg_button)
|
||||
self.setMinimumSize(50, 20)
|
||||
|
|
|
@ -8,120 +8,132 @@ import typing
|
|||
|
||||
from PySide6.QtCore import Signal, Qt, QThreadPool
|
||||
from PySide6.QtGui import QStandardItemModel, QStandardItem
|
||||
from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QListView
|
||||
from PySide6.QtWidgets import (
|
||||
QWidget,
|
||||
QVBoxLayout,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QPushButton,
|
||||
QListView,
|
||||
)
|
||||
|
||||
from src.core.library import Library
|
||||
from src.qt.helpers import FunctionIterator, CustomRunnable
|
||||
from src.qt.widgets import ProgressWidget
|
||||
from src.qt.widgets import ProgressWidget
|
||||
|
||||
# Only import for type checking/autocompletion, will not be imported at runtime.
|
||||
if typing.TYPE_CHECKING:
|
||||
from src.qt.ts_qt import QtDriver
|
||||
from src.qt.ts_qt import QtDriver
|
||||
|
||||
|
||||
class MirrorEntriesModal(QWidget):
|
||||
done = Signal()
|
||||
def __init__(self, library:'Library', driver:'QtDriver'):
|
||||
super().__init__()
|
||||
self.lib = library
|
||||
self.driver = driver
|
||||
self.setWindowTitle(f'Mirror Entries')
|
||||
self.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||
self.setMinimumSize(500, 400)
|
||||
self.root_layout = QVBoxLayout(self)
|
||||
self.root_layout.setContentsMargins(6,6,6,6)
|
||||
done = Signal()
|
||||
|
||||
self.desc_widget = QLabel()
|
||||
self.desc_widget.setObjectName('descriptionLabel')
|
||||
self.desc_widget.setWordWrap(True)
|
||||
self.desc_widget.setText(f'''
|
||||
def __init__(self, library: "Library", driver: "QtDriver"):
|
||||
super().__init__()
|
||||
self.lib = library
|
||||
self.driver = driver
|
||||
self.setWindowTitle(f"Mirror Entries")
|
||||
self.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||
self.setMinimumSize(500, 400)
|
||||
self.root_layout = QVBoxLayout(self)
|
||||
self.root_layout.setContentsMargins(6, 6, 6, 6)
|
||||
|
||||
self.desc_widget = QLabel()
|
||||
self.desc_widget.setObjectName("descriptionLabel")
|
||||
self.desc_widget.setWordWrap(True)
|
||||
self.desc_widget.setText(f"""
|
||||
Are you sure you want to mirror the following {len(self.lib.dupe_files)} Entries?
|
||||
''')
|
||||
self.desc_widget.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
""")
|
||||
self.desc_widget.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
self.list_view = QListView()
|
||||
self.model = QStandardItemModel()
|
||||
self.list_view.setModel(self.model)
|
||||
|
||||
self.button_container = QWidget()
|
||||
self.button_layout = QHBoxLayout(self.button_container)
|
||||
self.button_layout.setContentsMargins(6,6,6,6)
|
||||
self.button_layout.addStretch(1)
|
||||
self.list_view = QListView()
|
||||
self.model = QStandardItemModel()
|
||||
self.list_view.setModel(self.model)
|
||||
|
||||
self.cancel_button = QPushButton()
|
||||
self.cancel_button.setText('&Cancel')
|
||||
self.cancel_button.setDefault(True)
|
||||
self.cancel_button.clicked.connect(self.hide)
|
||||
self.button_layout.addWidget(self.cancel_button)
|
||||
|
||||
self.mirror_button = QPushButton()
|
||||
self.mirror_button.setText('&Mirror')
|
||||
self.mirror_button.clicked.connect(self.hide)
|
||||
self.mirror_button.clicked.connect(lambda: self.mirror_entries())
|
||||
self.button_layout.addWidget(self.mirror_button)
|
||||
|
||||
self.root_layout.addWidget(self.desc_widget)
|
||||
self.root_layout.addWidget(self.list_view)
|
||||
self.root_layout.addWidget(self.button_container)
|
||||
|
||||
def refresh_list(self):
|
||||
self.desc_widget.setText(f'''
|
||||
self.button_container = QWidget()
|
||||
self.button_layout = QHBoxLayout(self.button_container)
|
||||
self.button_layout.setContentsMargins(6, 6, 6, 6)
|
||||
self.button_layout.addStretch(1)
|
||||
|
||||
self.cancel_button = QPushButton()
|
||||
self.cancel_button.setText("&Cancel")
|
||||
self.cancel_button.setDefault(True)
|
||||
self.cancel_button.clicked.connect(self.hide)
|
||||
self.button_layout.addWidget(self.cancel_button)
|
||||
|
||||
self.mirror_button = QPushButton()
|
||||
self.mirror_button.setText("&Mirror")
|
||||
self.mirror_button.clicked.connect(self.hide)
|
||||
self.mirror_button.clicked.connect(lambda: self.mirror_entries())
|
||||
self.button_layout.addWidget(self.mirror_button)
|
||||
|
||||
self.root_layout.addWidget(self.desc_widget)
|
||||
self.root_layout.addWidget(self.list_view)
|
||||
self.root_layout.addWidget(self.button_container)
|
||||
|
||||
def refresh_list(self):
|
||||
self.desc_widget.setText(f"""
|
||||
Are you sure you want to mirror the following {len(self.lib.dupe_files)} Entries?
|
||||
''')
|
||||
""")
|
||||
|
||||
self.model.clear()
|
||||
for i in self.lib.dupe_files:
|
||||
self.model.appendRow(QStandardItem(str(i)))
|
||||
self.model.clear()
|
||||
for i in self.lib.dupe_files:
|
||||
self.model.appendRow(QStandardItem(str(i)))
|
||||
|
||||
def mirror_entries(self):
|
||||
# pb = QProgressDialog('', None, 0, len(self.lib.dupe_files))
|
||||
# # pb.setMaximum(len(self.lib.missing_files))
|
||||
# pb.setFixedSize(432, 112)
|
||||
# pb.setWindowFlags(pb.windowFlags() & ~Qt.WindowType.WindowCloseButtonHint)
|
||||
# pb.setWindowTitle('Mirroring Entries')
|
||||
# pb.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||
# pb.show()
|
||||
def mirror_entries(self):
|
||||
# pb = QProgressDialog('', None, 0, len(self.lib.dupe_files))
|
||||
# # pb.setMaximum(len(self.lib.missing_files))
|
||||
# pb.setFixedSize(432, 112)
|
||||
# pb.setWindowFlags(pb.windowFlags() & ~Qt.WindowType.WindowCloseButtonHint)
|
||||
# pb.setWindowTitle('Mirroring Entries')
|
||||
# pb.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||
# pb.show()
|
||||
|
||||
# r = CustomRunnable(lambda: self.mirror_entries_runnable(pb))
|
||||
# r.done.connect(lambda: self.done.emit())
|
||||
# r.done.connect(lambda: self.driver.preview_panel.refresh())
|
||||
# # r.done.connect(lambda: self.model.clear())
|
||||
# # QThreadPool.globalInstance().start(r)
|
||||
# r.run()
|
||||
# r = CustomRunnable(lambda: self.mirror_entries_runnable(pb))
|
||||
# r.done.connect(lambda: self.done.emit())
|
||||
# r.done.connect(lambda: self.driver.preview_panel.refresh())
|
||||
# # r.done.connect(lambda: self.model.clear())
|
||||
# # QThreadPool.globalInstance().start(r)
|
||||
# r.run()
|
||||
|
||||
iterator = FunctionIterator(self.mirror_entries_runnable)
|
||||
pw = ProgressWidget(
|
||||
window_title='Mirroring Entries',
|
||||
label_text=f'Mirroring 1/{len(self.lib.dupe_files)} Entries...',
|
||||
cancel_button_text=None,
|
||||
minimum=0,
|
||||
maximum=len(self.lib.dupe_files)
|
||||
)
|
||||
pw.show()
|
||||
iterator.value.connect(lambda x: pw.update_progress(x+1))
|
||||
iterator.value.connect(lambda x: pw.update_label(f'Mirroring {x+1}/{len(self.lib.dupe_files)} Entries...'))
|
||||
r = CustomRunnable(lambda:iterator.run())
|
||||
QThreadPool.globalInstance().start(r)
|
||||
r.done.connect(lambda: (
|
||||
pw.hide(),
|
||||
pw.deleteLater(),
|
||||
self.driver.preview_panel.update_widgets(),
|
||||
self.done.emit()
|
||||
))
|
||||
|
||||
def mirror_entries_runnable(self):
|
||||
mirrored = []
|
||||
for i, dupe in enumerate(self.lib.dupe_files):
|
||||
# pb.setValue(i)
|
||||
# pb.setLabelText(f'Mirroring {i}/{len(self.lib.dupe_files)} Entries')
|
||||
entry_id_1 = self.lib.get_entry_id_from_filepath(
|
||||
dupe[0])
|
||||
entry_id_2 = self.lib.get_entry_id_from_filepath(
|
||||
dupe[1])
|
||||
self.lib.mirror_entry_fields([entry_id_1, entry_id_2])
|
||||
sleep(0.005)
|
||||
yield i
|
||||
for d in mirrored:
|
||||
self.lib.dupe_files.remove(d)
|
||||
# self.driver.filter_items('')
|
||||
# self.done.emit()
|
||||
iterator = FunctionIterator(self.mirror_entries_runnable)
|
||||
pw = ProgressWidget(
|
||||
window_title="Mirroring Entries",
|
||||
label_text=f"Mirroring 1/{len(self.lib.dupe_files)} Entries...",
|
||||
cancel_button_text=None,
|
||||
minimum=0,
|
||||
maximum=len(self.lib.dupe_files),
|
||||
)
|
||||
pw.show()
|
||||
iterator.value.connect(lambda x: pw.update_progress(x + 1))
|
||||
iterator.value.connect(
|
||||
lambda x: pw.update_label(
|
||||
f"Mirroring {x+1}/{len(self.lib.dupe_files)} Entries..."
|
||||
)
|
||||
)
|
||||
r = CustomRunnable(lambda: iterator.run())
|
||||
QThreadPool.globalInstance().start(r)
|
||||
r.done.connect(
|
||||
lambda: (
|
||||
pw.hide(),
|
||||
pw.deleteLater(),
|
||||
self.driver.preview_panel.update_widgets(),
|
||||
self.done.emit(),
|
||||
)
|
||||
)
|
||||
|
||||
def mirror_entries_runnable(self):
|
||||
mirrored = []
|
||||
for i, dupe in enumerate(self.lib.dupe_files):
|
||||
# pb.setValue(i)
|
||||
# pb.setLabelText(f'Mirroring {i}/{len(self.lib.dupe_files)} Entries')
|
||||
entry_id_1 = self.lib.get_entry_id_from_filepath(dupe[0])
|
||||
entry_id_2 = self.lib.get_entry_id_from_filepath(dupe[1])
|
||||
self.lib.mirror_entry_fields([entry_id_1, entry_id_2])
|
||||
sleep(0.005)
|
||||
yield i
|
||||
for d in mirrored:
|
||||
self.lib.dupe_files.remove(d)
|
||||
# self.driver.filter_items('')
|
||||
# self.done.emit()
|
||||
|
|
|
@ -8,83 +8,91 @@ from PySide6.QtCore import QObject, Signal, QThreadPool
|
|||
|
||||
from src.core.library import Library
|
||||
from src.qt.helpers import FunctionIterator, CustomRunnable
|
||||
from src.qt.widgets import ProgressWidget
|
||||
from src.qt.widgets import ProgressWidget
|
||||
|
||||
# Only import for type checking/autocompletion, will not be imported at runtime.
|
||||
if typing.TYPE_CHECKING:
|
||||
from src.qt.ts_qt import QtDriver
|
||||
from src.qt.ts_qt import QtDriver
|
||||
|
||||
|
||||
class RelinkUnlinkedEntries(QObject):
|
||||
done = Signal()
|
||||
done = Signal()
|
||||
|
||||
def __init__(self, library:'Library', driver:'QtDriver'):
|
||||
super().__init__()
|
||||
self.lib = library
|
||||
self.driver = driver
|
||||
self.fixed = 0
|
||||
def __init__(self, library: "Library", driver: "QtDriver"):
|
||||
super().__init__()
|
||||
self.lib = library
|
||||
self.driver = driver
|
||||
self.fixed = 0
|
||||
|
||||
def repair_entries(self):
|
||||
# pb = QProgressDialog('', None, 0, len(self.lib.missing_files))
|
||||
# # pb.setMaximum(len(self.lib.missing_files))
|
||||
# pb.setFixedSize(432, 112)
|
||||
# pb.setWindowFlags(pb.windowFlags() & ~Qt.WindowType.WindowCloseButtonHint)
|
||||
# pb.setWindowTitle('Relinking Entries')
|
||||
# pb.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||
# pb.show()
|
||||
def repair_entries(self):
|
||||
# pb = QProgressDialog('', None, 0, len(self.lib.missing_files))
|
||||
# # pb.setMaximum(len(self.lib.missing_files))
|
||||
# pb.setFixedSize(432, 112)
|
||||
# pb.setWindowFlags(pb.windowFlags() & ~Qt.WindowType.WindowCloseButtonHint)
|
||||
# pb.setWindowTitle('Relinking Entries')
|
||||
# pb.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||
# pb.show()
|
||||
|
||||
# r = CustomRunnable(lambda: self.repair_entries_runnable(pb))
|
||||
# r.done.connect(lambda: self.done.emit())
|
||||
# # r.done.connect(lambda: self.model.clear())
|
||||
# QThreadPool.globalInstance().start(r)
|
||||
# # r.run()
|
||||
# r = CustomRunnable(lambda: self.repair_entries_runnable(pb))
|
||||
# r.done.connect(lambda: self.done.emit())
|
||||
# # r.done.connect(lambda: self.model.clear())
|
||||
# QThreadPool.globalInstance().start(r)
|
||||
# # r.run()
|
||||
|
||||
iterator = FunctionIterator(self.lib.fix_missing_files)
|
||||
|
||||
iterator = FunctionIterator(self.lib.fix_missing_files)
|
||||
pw = ProgressWidget(
|
||||
window_title="Relinking Entries",
|
||||
label_text="",
|
||||
cancel_button_text=None,
|
||||
minimum=0,
|
||||
maximum=len(self.lib.missing_files),
|
||||
)
|
||||
pw.show()
|
||||
|
||||
pw = ProgressWidget(
|
||||
window_title='Relinking Entries',
|
||||
label_text='',
|
||||
cancel_button_text=None,
|
||||
minimum=0,
|
||||
maximum=len(self.lib.missing_files)
|
||||
)
|
||||
pw.show()
|
||||
iterator.value.connect(lambda x: pw.update_progress(x[0] + 1))
|
||||
iterator.value.connect(
|
||||
lambda x: (
|
||||
self.increment_fixed() if x[1] else (),
|
||||
pw.update_label(
|
||||
f"Attempting to Relink {x[0]+1}/{len(self.lib.missing_files)} Entries, {self.fixed} Successfully Relinked"
|
||||
),
|
||||
)
|
||||
)
|
||||
# iterator.value.connect(lambda x: self.driver.purge_item_from_navigation(ItemType.ENTRY, x[1]))
|
||||
|
||||
iterator.value.connect(lambda x: pw.update_progress(x[0]+1))
|
||||
iterator.value.connect(lambda x: (self.increment_fixed() if x[1] else (), pw.update_label(f'Attempting to Relink {x[0]+1}/{len(self.lib.missing_files)} Entries, {self.fixed} Successfully Relinked')))
|
||||
# iterator.value.connect(lambda x: self.driver.purge_item_from_navigation(ItemType.ENTRY, x[1]))
|
||||
r = CustomRunnable(lambda: iterator.run())
|
||||
r.done.connect(
|
||||
lambda: (pw.hide(), pw.deleteLater(), self.done.emit(), self.reset_fixed())
|
||||
)
|
||||
QThreadPool.globalInstance().start(r)
|
||||
|
||||
r = CustomRunnable(lambda:iterator.run())
|
||||
r.done.connect(lambda: (pw.hide(), pw.deleteLater(), self.done.emit(), self.reset_fixed()))
|
||||
QThreadPool.globalInstance().start(r)
|
||||
|
||||
def increment_fixed(self):
|
||||
self.fixed += 1
|
||||
|
||||
def reset_fixed(self):
|
||||
self.fixed = 0
|
||||
def increment_fixed(self):
|
||||
self.fixed += 1
|
||||
|
||||
# def repair_entries_runnable(self, pb: QProgressDialog):
|
||||
# fixed = 0
|
||||
# for i in self.lib.fix_missing_files():
|
||||
# if i[1]:
|
||||
# fixed += 1
|
||||
# pb.setValue(i[0])
|
||||
# pb.setLabelText(f'Attempting to Relink {i[0]+1}/{len(self.lib.missing_files)} Entries, {fixed} Successfully Relinked')
|
||||
def reset_fixed(self):
|
||||
self.fixed = 0
|
||||
|
||||
# for i, missing in enumerate(self.lib.missing_files):
|
||||
# pb.setValue(i)
|
||||
# pb.setLabelText(f'Relinking {i}/{len(self.lib.missing_files)} Unlinked Entries')
|
||||
# self.lib.fix_missing_files()
|
||||
# try:
|
||||
# id = self.lib.get_entry_id_from_filepath(missing)
|
||||
# logging.info(f'Removing Entry ID {id}:\n\t{missing}')
|
||||
# self.lib.remove_entry(id)
|
||||
# self.driver.purge_item_from_navigation(ItemType.ENTRY, id)
|
||||
# deleted.append(missing)
|
||||
# except KeyError:
|
||||
# logging.info(
|
||||
# f'{ERROR} \"{id}\" was reported as missing, but is not in the file_to_entry_id map.')
|
||||
# for d in deleted:
|
||||
# self.lib.missing_files.remove(d)
|
||||
# def repair_entries_runnable(self, pb: QProgressDialog):
|
||||
# fixed = 0
|
||||
# for i in self.lib.fix_missing_files():
|
||||
# if i[1]:
|
||||
# fixed += 1
|
||||
# pb.setValue(i[0])
|
||||
# pb.setLabelText(f'Attempting to Relink {i[0]+1}/{len(self.lib.missing_files)} Entries, {fixed} Successfully Relinked')
|
||||
|
||||
# for i, missing in enumerate(self.lib.missing_files):
|
||||
# pb.setValue(i)
|
||||
# pb.setLabelText(f'Relinking {i}/{len(self.lib.missing_files)} Unlinked Entries')
|
||||
# self.lib.fix_missing_files()
|
||||
# try:
|
||||
# id = self.lib.get_entry_id_from_filepath(missing)
|
||||
# logging.info(f'Removing Entry ID {id}:\n\t{missing}')
|
||||
# self.lib.remove_entry(id)
|
||||
# self.driver.purge_item_from_navigation(ItemType.ENTRY, id)
|
||||
# deleted.append(missing)
|
||||
# except KeyError:
|
||||
# logging.info(
|
||||
# f'{ERROR} \"{id}\" was reported as missing, but is not in the file_to_entry_id map.')
|
||||
# for d in deleted:
|
||||
# self.lib.missing_files.remove(d)
|
||||
|
|
|
@ -3,7 +3,14 @@
|
|||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
from PySide6.QtCore import Signal, Qt, QSize
|
||||
from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLineEdit, QScrollArea, QFrame
|
||||
from PySide6.QtWidgets import (
|
||||
QWidget,
|
||||
QVBoxLayout,
|
||||
QHBoxLayout,
|
||||
QLineEdit,
|
||||
QScrollArea,
|
||||
QFrame,
|
||||
)
|
||||
|
||||
from src.core.library import Library
|
||||
from src.qt.widgets import PanelWidget, PanelModal, TagWidget
|
||||
|
@ -11,116 +18,128 @@ from src.qt.modals import BuildTagPanel
|
|||
|
||||
|
||||
class TagDatabasePanel(PanelWidget):
|
||||
tag_chosen = Signal(int)
|
||||
tag_chosen = Signal(int)
|
||||
|
||||
def __init__(self, library):
|
||||
super().__init__()
|
||||
self.lib: Library = library
|
||||
# self.callback = callback
|
||||
self.first_tag_id = -1
|
||||
self.tag_limit = 30
|
||||
# self.selected_tag: int = 0
|
||||
|
||||
self.setMinimumSize(300, 400)
|
||||
self.root_layout = QVBoxLayout(self)
|
||||
self.root_layout.setContentsMargins(6,0,6,0)
|
||||
|
||||
self.search_field = QLineEdit()
|
||||
self.search_field.setObjectName('searchField')
|
||||
self.search_field.setMinimumSize(QSize(0, 32))
|
||||
self.search_field.setPlaceholderText('Search Tags')
|
||||
self.search_field.textEdited.connect(lambda x=self.search_field.text(): self.update_tags(x))
|
||||
self.search_field.returnPressed.connect(lambda checked=False: self.on_return(self.search_field.text()))
|
||||
def __init__(self, library):
|
||||
super().__init__()
|
||||
self.lib: Library = library
|
||||
# self.callback = callback
|
||||
self.first_tag_id = -1
|
||||
self.tag_limit = 30
|
||||
# self.selected_tag: int = 0
|
||||
|
||||
# self.content_container = QWidget()
|
||||
# self.content_layout = QHBoxLayout(self.content_container)
|
||||
self.setMinimumSize(300, 400)
|
||||
self.root_layout = QVBoxLayout(self)
|
||||
self.root_layout.setContentsMargins(6, 0, 6, 0)
|
||||
|
||||
self.scroll_contents = QWidget()
|
||||
self.scroll_layout = QVBoxLayout(self.scroll_contents)
|
||||
self.scroll_layout.setContentsMargins(6,0,6,0)
|
||||
self.scroll_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
||||
self.search_field = QLineEdit()
|
||||
self.search_field.setObjectName("searchField")
|
||||
self.search_field.setMinimumSize(QSize(0, 32))
|
||||
self.search_field.setPlaceholderText("Search Tags")
|
||||
self.search_field.textEdited.connect(
|
||||
lambda x=self.search_field.text(): self.update_tags(x)
|
||||
)
|
||||
self.search_field.returnPressed.connect(
|
||||
lambda checked=False: self.on_return(self.search_field.text())
|
||||
)
|
||||
|
||||
self.scroll_area = QScrollArea()
|
||||
# self.scroll_area.setStyleSheet('background: #000000;')
|
||||
self.scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
|
||||
# self.scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
||||
self.scroll_area.setWidgetResizable(True)
|
||||
self.scroll_area.setFrameShadow(QFrame.Shadow.Plain)
|
||||
self.scroll_area.setFrameShape(QFrame.Shape.NoFrame)
|
||||
# sa.setMaximumWidth(self.preview_size[0])
|
||||
self.scroll_area.setWidget(self.scroll_contents)
|
||||
# self.content_container = QWidget()
|
||||
# self.content_layout = QHBoxLayout(self.content_container)
|
||||
|
||||
# self.add_button = QPushButton()
|
||||
# self.root_layout.addWidget(self.add_button)
|
||||
# self.add_button.setText('Add Tag')
|
||||
# # self.done_button.clicked.connect(lambda checked=False, x=1101: (callback(x), self.hide()))
|
||||
# self.add_button.clicked.connect(lambda checked=False, x=1101: callback(x))
|
||||
# # self.setLayout(self.root_layout)
|
||||
self.scroll_contents = QWidget()
|
||||
self.scroll_layout = QVBoxLayout(self.scroll_contents)
|
||||
self.scroll_layout.setContentsMargins(6, 0, 6, 0)
|
||||
self.scroll_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
||||
|
||||
self.root_layout.addWidget(self.search_field)
|
||||
self.root_layout.addWidget(self.scroll_area)
|
||||
self.update_tags('')
|
||||
self.scroll_area = QScrollArea()
|
||||
# self.scroll_area.setStyleSheet('background: #000000;')
|
||||
self.scroll_area.setVerticalScrollBarPolicy(
|
||||
Qt.ScrollBarPolicy.ScrollBarAlwaysOn
|
||||
)
|
||||
# self.scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
||||
self.scroll_area.setWidgetResizable(True)
|
||||
self.scroll_area.setFrameShadow(QFrame.Shadow.Plain)
|
||||
self.scroll_area.setFrameShape(QFrame.Shape.NoFrame)
|
||||
# sa.setMaximumWidth(self.preview_size[0])
|
||||
self.scroll_area.setWidget(self.scroll_contents)
|
||||
|
||||
# def reset(self):
|
||||
# self.search_field.setText('')
|
||||
# self.update_tags('')
|
||||
# self.search_field.setFocus()
|
||||
|
||||
def on_return(self, text:str):
|
||||
if text and self.first_tag_id >= 0:
|
||||
# callback(self.first_tag_id)
|
||||
self.search_field.setText('')
|
||||
self.update_tags('')
|
||||
else:
|
||||
self.search_field.setFocus()
|
||||
self.parentWidget().hide()
|
||||
# self.add_button = QPushButton()
|
||||
# self.root_layout.addWidget(self.add_button)
|
||||
# self.add_button.setText('Add Tag')
|
||||
# # self.done_button.clicked.connect(lambda checked=False, x=1101: (callback(x), self.hide()))
|
||||
# self.add_button.clicked.connect(lambda checked=False, x=1101: callback(x))
|
||||
# # self.setLayout(self.root_layout)
|
||||
|
||||
def update_tags(self, query: str):
|
||||
# TODO: Look at recycling rather than deleting and reinitializing
|
||||
while self.scroll_layout.itemAt(0):
|
||||
self.scroll_layout.takeAt(0).widget().deleteLater()
|
||||
self.root_layout.addWidget(self.search_field)
|
||||
self.root_layout.addWidget(self.scroll_area)
|
||||
self.update_tags("")
|
||||
|
||||
# If there is a query, get a list of tag_ids that match, otherwise return all
|
||||
if query:
|
||||
tags = self.lib.search_tags(query, include_cluster=True)[:self.tag_limit-1]
|
||||
else:
|
||||
# Get tag ids to keep this behaviorally identical
|
||||
tags = [t.id for t in self.lib.tags]
|
||||
# def reset(self):
|
||||
# self.search_field.setText('')
|
||||
# self.update_tags('')
|
||||
# self.search_field.setFocus()
|
||||
|
||||
first_id_set = False
|
||||
for tag_id in tags:
|
||||
if not first_id_set:
|
||||
self.first_tag_id = tag_id
|
||||
first_id_set = True
|
||||
container = QWidget()
|
||||
row = QHBoxLayout(container)
|
||||
row.setContentsMargins(0, 0, 0, 0)
|
||||
row.setSpacing(3)
|
||||
tw = TagWidget(self.lib, self.lib.get_tag(tag_id), True, False)
|
||||
tw.on_edit.connect(lambda checked=False, t=self.lib.get_tag(tag_id): (self.edit_tag(t.id)))
|
||||
row.addWidget(tw)
|
||||
self.scroll_layout.addWidget(container)
|
||||
def on_return(self, text: str):
|
||||
if text and self.first_tag_id >= 0:
|
||||
# callback(self.first_tag_id)
|
||||
self.search_field.setText("")
|
||||
self.update_tags("")
|
||||
else:
|
||||
self.search_field.setFocus()
|
||||
self.parentWidget().hide()
|
||||
|
||||
self.search_field.setFocus()
|
||||
|
||||
def edit_tag(self, tag_id:int):
|
||||
btp = BuildTagPanel(self.lib, tag_id)
|
||||
# btp.on_edit.connect(lambda x: self.edit_tag_callback(x))
|
||||
self.edit_modal = PanelModal(btp,
|
||||
self.lib.get_tag(tag_id).display_name(self.lib),
|
||||
'Edit Tag',
|
||||
done_callback=(self.update_tags(self.search_field.text())),
|
||||
has_save=True)
|
||||
# self.edit_modal.widget.update_display_name.connect(lambda t: self.edit_modal.title_widget.setText(t))
|
||||
#TODO Check Warning: Expected type 'BuildTagPanel', got 'PanelWidget' instead
|
||||
self.edit_modal.saved.connect(lambda: self.edit_tag_callback(btp))
|
||||
self.edit_modal.show()
|
||||
|
||||
def edit_tag_callback(self, btp:BuildTagPanel):
|
||||
self.lib.update_tag(btp.build_tag())
|
||||
self.update_tags(self.search_field.text())
|
||||
def update_tags(self, query: str):
|
||||
# TODO: Look at recycling rather than deleting and reinitializing
|
||||
while self.scroll_layout.itemAt(0):
|
||||
self.scroll_layout.takeAt(0).widget().deleteLater()
|
||||
|
||||
# def enterEvent(self, event: QEnterEvent) -> None:
|
||||
# self.search_field.setFocus()
|
||||
# return super().enterEvent(event)
|
||||
# self.focusOutEvent
|
||||
# If there is a query, get a list of tag_ids that match, otherwise return all
|
||||
if query:
|
||||
tags = self.lib.search_tags(query, include_cluster=True)[
|
||||
: self.tag_limit - 1
|
||||
]
|
||||
else:
|
||||
# Get tag ids to keep this behaviorally identical
|
||||
tags = [t.id for t in self.lib.tags]
|
||||
|
||||
first_id_set = False
|
||||
for tag_id in tags:
|
||||
if not first_id_set:
|
||||
self.first_tag_id = tag_id
|
||||
first_id_set = True
|
||||
container = QWidget()
|
||||
row = QHBoxLayout(container)
|
||||
row.setContentsMargins(0, 0, 0, 0)
|
||||
row.setSpacing(3)
|
||||
tw = TagWidget(self.lib, self.lib.get_tag(tag_id), True, False)
|
||||
tw.on_edit.connect(
|
||||
lambda checked=False, t=self.lib.get_tag(tag_id): (self.edit_tag(t.id))
|
||||
)
|
||||
row.addWidget(tw)
|
||||
self.scroll_layout.addWidget(container)
|
||||
|
||||
self.search_field.setFocus()
|
||||
|
||||
def edit_tag(self, tag_id: int):
|
||||
btp = BuildTagPanel(self.lib, tag_id)
|
||||
# btp.on_edit.connect(lambda x: self.edit_tag_callback(x))
|
||||
self.edit_modal = PanelModal(
|
||||
btp,
|
||||
self.lib.get_tag(tag_id).display_name(self.lib),
|
||||
"Edit Tag",
|
||||
done_callback=(self.update_tags(self.search_field.text())),
|
||||
has_save=True,
|
||||
)
|
||||
# self.edit_modal.widget.update_display_name.connect(lambda t: self.edit_modal.title_widget.setText(t))
|
||||
# TODO Check Warning: Expected type 'BuildTagPanel', got 'PanelWidget' instead
|
||||
self.edit_modal.saved.connect(lambda: self.edit_tag_callback(btp))
|
||||
self.edit_modal.show()
|
||||
|
||||
def edit_tag_callback(self, btp: BuildTagPanel):
|
||||
self.lib.update_tag(btp.build_tag())
|
||||
self.update_tags(self.search_field.text())
|
||||
|
||||
# def enterEvent(self, event: QEnterEvent) -> None:
|
||||
# self.search_field.setFocus()
|
||||
# return super().enterEvent(event)
|
||||
# self.focusOutEvent
|
||||
|
|
|
@ -7,136 +7,154 @@ import logging
|
|||
import math
|
||||
|
||||
from PySide6.QtCore import Signal, Qt, QSize
|
||||
from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLineEdit, QScrollArea, QFrame
|
||||
from PySide6.QtWidgets import (
|
||||
QWidget,
|
||||
QVBoxLayout,
|
||||
QHBoxLayout,
|
||||
QPushButton,
|
||||
QLineEdit,
|
||||
QScrollArea,
|
||||
QFrame,
|
||||
)
|
||||
|
||||
from src.core.library import Library
|
||||
from src.core.palette import ColorType, get_tag_color
|
||||
from src.qt.widgets import PanelWidget, TagWidget
|
||||
|
||||
|
||||
ERROR = f'[ERROR]'
|
||||
WARNING = f'[WARNING]'
|
||||
INFO = f'[INFO]'
|
||||
ERROR = f"[ERROR]"
|
||||
WARNING = f"[WARNING]"
|
||||
INFO = f"[INFO]"
|
||||
|
||||
logging.basicConfig(format="%(message)s", level=logging.INFO)
|
||||
|
||||
|
||||
class TagSearchPanel(PanelWidget):
|
||||
tag_chosen = Signal(int)
|
||||
def __init__(self, library):
|
||||
super().__init__()
|
||||
self.lib: Library = library
|
||||
# self.callback = callback
|
||||
self.first_tag_id = None
|
||||
self.tag_limit = 30
|
||||
# self.selected_tag: int = 0
|
||||
self.setMinimumSize(300, 400)
|
||||
self.root_layout = QVBoxLayout(self)
|
||||
self.root_layout.setContentsMargins(6,0,6,0)
|
||||
tag_chosen = Signal(int)
|
||||
|
||||
self.search_field = QLineEdit()
|
||||
self.search_field.setObjectName('searchField')
|
||||
self.search_field.setMinimumSize(QSize(0, 32))
|
||||
self.search_field.setPlaceholderText('Search Tags')
|
||||
self.search_field.textEdited.connect(lambda x=self.search_field.text(): self.update_tags(x))
|
||||
self.search_field.returnPressed.connect(lambda checked=False: self.on_return(self.search_field.text()))
|
||||
def __init__(self, library):
|
||||
super().__init__()
|
||||
self.lib: Library = library
|
||||
# self.callback = callback
|
||||
self.first_tag_id = None
|
||||
self.tag_limit = 30
|
||||
# self.selected_tag: int = 0
|
||||
self.setMinimumSize(300, 400)
|
||||
self.root_layout = QVBoxLayout(self)
|
||||
self.root_layout.setContentsMargins(6, 0, 6, 0)
|
||||
|
||||
# self.content_container = QWidget()
|
||||
# self.content_layout = QHBoxLayout(self.content_container)
|
||||
self.search_field = QLineEdit()
|
||||
self.search_field.setObjectName("searchField")
|
||||
self.search_field.setMinimumSize(QSize(0, 32))
|
||||
self.search_field.setPlaceholderText("Search Tags")
|
||||
self.search_field.textEdited.connect(
|
||||
lambda x=self.search_field.text(): self.update_tags(x)
|
||||
)
|
||||
self.search_field.returnPressed.connect(
|
||||
lambda checked=False: self.on_return(self.search_field.text())
|
||||
)
|
||||
|
||||
self.scroll_contents = QWidget()
|
||||
self.scroll_layout = QVBoxLayout(self.scroll_contents)
|
||||
self.scroll_layout.setContentsMargins(6,0,6,0)
|
||||
self.scroll_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
||||
# self.content_container = QWidget()
|
||||
# self.content_layout = QHBoxLayout(self.content_container)
|
||||
|
||||
self.scroll_area = QScrollArea()
|
||||
# self.scroll_area.setStyleSheet('background: #000000;')
|
||||
self.scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
|
||||
# self.scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
||||
self.scroll_area.setWidgetResizable(True)
|
||||
self.scroll_area.setFrameShadow(QFrame.Shadow.Plain)
|
||||
self.scroll_area.setFrameShape(QFrame.Shape.NoFrame)
|
||||
# sa.setMaximumWidth(self.preview_size[0])
|
||||
self.scroll_area.setWidget(self.scroll_contents)
|
||||
self.scroll_contents = QWidget()
|
||||
self.scroll_layout = QVBoxLayout(self.scroll_contents)
|
||||
self.scroll_layout.setContentsMargins(6, 0, 6, 0)
|
||||
self.scroll_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
||||
|
||||
# self.add_button = QPushButton()
|
||||
# self.root_layout.addWidget(self.add_button)
|
||||
# self.add_button.setText('Add Tag')
|
||||
# # self.done_button.clicked.connect(lambda checked=False, x=1101: (callback(x), self.hide()))
|
||||
# self.add_button.clicked.connect(lambda checked=False, x=1101: callback(x))
|
||||
# # self.setLayout(self.root_layout)
|
||||
self.scroll_area = QScrollArea()
|
||||
# self.scroll_area.setStyleSheet('background: #000000;')
|
||||
self.scroll_area.setVerticalScrollBarPolicy(
|
||||
Qt.ScrollBarPolicy.ScrollBarAlwaysOn
|
||||
)
|
||||
# self.scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
||||
self.scroll_area.setWidgetResizable(True)
|
||||
self.scroll_area.setFrameShadow(QFrame.Shadow.Plain)
|
||||
self.scroll_area.setFrameShape(QFrame.Shape.NoFrame)
|
||||
# sa.setMaximumWidth(self.preview_size[0])
|
||||
self.scroll_area.setWidget(self.scroll_contents)
|
||||
|
||||
self.root_layout.addWidget(self.search_field)
|
||||
self.root_layout.addWidget(self.scroll_area)
|
||||
# self.add_button = QPushButton()
|
||||
# self.root_layout.addWidget(self.add_button)
|
||||
# self.add_button.setText('Add Tag')
|
||||
# # self.done_button.clicked.connect(lambda checked=False, x=1101: (callback(x), self.hide()))
|
||||
# self.add_button.clicked.connect(lambda checked=False, x=1101: callback(x))
|
||||
# # self.setLayout(self.root_layout)
|
||||
|
||||
# def reset(self):
|
||||
# self.search_field.setText('')
|
||||
# self.update_tags('')
|
||||
# self.search_field.setFocus()
|
||||
|
||||
def on_return(self, text:str):
|
||||
if text and self.first_tag_id is not None:
|
||||
# callback(self.first_tag_id)
|
||||
self.tag_chosen.emit(self.first_tag_id)
|
||||
self.search_field.setText('')
|
||||
self.update_tags()
|
||||
else:
|
||||
self.search_field.setFocus()
|
||||
self.parentWidget().hide()
|
||||
self.root_layout.addWidget(self.search_field)
|
||||
self.root_layout.addWidget(self.scroll_area)
|
||||
|
||||
def update_tags(self, query:str =''):
|
||||
# for c in self.scroll_layout.children():
|
||||
# c.widget().deleteLater()
|
||||
while self.scroll_layout.count():
|
||||
# logging.info(f"I'm deleting { self.scroll_layout.itemAt(0).widget()}")
|
||||
self.scroll_layout.takeAt(0).widget().deleteLater()
|
||||
# def reset(self):
|
||||
# self.search_field.setText('')
|
||||
# self.update_tags('')
|
||||
# self.search_field.setFocus()
|
||||
|
||||
found_tags = self.lib.search_tags(query, include_cluster=True)[:self.tag_limit - 1]
|
||||
self.first_tag_id = found_tags[0] if found_tags else None
|
||||
def on_return(self, text: str):
|
||||
if text and self.first_tag_id is not None:
|
||||
# callback(self.first_tag_id)
|
||||
self.tag_chosen.emit(self.first_tag_id)
|
||||
self.search_field.setText("")
|
||||
self.update_tags()
|
||||
else:
|
||||
self.search_field.setFocus()
|
||||
self.parentWidget().hide()
|
||||
|
||||
for tag_id in found_tags:
|
||||
c = QWidget()
|
||||
l = QHBoxLayout(c)
|
||||
l.setContentsMargins(0, 0, 0, 0)
|
||||
l.setSpacing(3)
|
||||
tw = TagWidget(self.lib, self.lib.get_tag(tag_id), False, False)
|
||||
ab = QPushButton()
|
||||
ab.setMinimumSize(23, 23)
|
||||
ab.setMaximumSize(23, 23)
|
||||
ab.setText('+')
|
||||
ab.setStyleSheet(
|
||||
f'QPushButton{{'
|
||||
f'background: {get_tag_color(ColorType.PRIMARY, self.lib.get_tag(tag_id).color)};'
|
||||
# f'background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 {get_tag_color(ColorType.PRIMARY, tag.color)}, stop:1.0 {get_tag_color(ColorType.BORDER, tag.color)});'
|
||||
# f"border-color:{get_tag_color(ColorType.PRIMARY, tag.color)};"
|
||||
f"color: {get_tag_color(ColorType.TEXT, self.lib.get_tag(tag_id).color)};"
|
||||
f'font-weight: 600;'
|
||||
f"border-color:{get_tag_color(ColorType.BORDER, self.lib.get_tag(tag_id).color)};"
|
||||
f'border-radius: 6px;'
|
||||
f'border-style:solid;'
|
||||
f'border-width: {math.ceil(1*self.devicePixelRatio())}px;'
|
||||
# f'padding-top: 1.5px;'
|
||||
# f'padding-right: 4px;'
|
||||
f'padding-bottom: 5px;'
|
||||
# f'padding-left: 4px;'
|
||||
f'font-size: 20px;'
|
||||
f'}}'
|
||||
f'QPushButton::hover'
|
||||
f'{{'
|
||||
f"border-color:{get_tag_color(ColorType.LIGHT_ACCENT, self.lib.get_tag(tag_id).color)};"
|
||||
f"color: {get_tag_color(ColorType.DARK_ACCENT, self.lib.get_tag(tag_id).color)};"
|
||||
f'background: {get_tag_color(ColorType.LIGHT_ACCENT, self.lib.get_tag(tag_id).color)};'
|
||||
f'}}')
|
||||
def update_tags(self, query: str = ""):
|
||||
# for c in self.scroll_layout.children():
|
||||
# c.widget().deleteLater()
|
||||
while self.scroll_layout.count():
|
||||
# logging.info(f"I'm deleting { self.scroll_layout.itemAt(0).widget()}")
|
||||
self.scroll_layout.takeAt(0).widget().deleteLater()
|
||||
|
||||
ab.clicked.connect(lambda checked=False, x=tag_id: self.tag_chosen.emit(x))
|
||||
found_tags = self.lib.search_tags(query, include_cluster=True)[
|
||||
: self.tag_limit - 1
|
||||
]
|
||||
self.first_tag_id = found_tags[0] if found_tags else None
|
||||
|
||||
l.addWidget(tw)
|
||||
l.addWidget(ab)
|
||||
self.scroll_layout.addWidget(c)
|
||||
for tag_id in found_tags:
|
||||
c = QWidget()
|
||||
l = QHBoxLayout(c)
|
||||
l.setContentsMargins(0, 0, 0, 0)
|
||||
l.setSpacing(3)
|
||||
tw = TagWidget(self.lib, self.lib.get_tag(tag_id), False, False)
|
||||
ab = QPushButton()
|
||||
ab.setMinimumSize(23, 23)
|
||||
ab.setMaximumSize(23, 23)
|
||||
ab.setText("+")
|
||||
ab.setStyleSheet(
|
||||
f"QPushButton{{"
|
||||
f"background: {get_tag_color(ColorType.PRIMARY, self.lib.get_tag(tag_id).color)};"
|
||||
# f'background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 {get_tag_color(ColorType.PRIMARY, tag.color)}, stop:1.0 {get_tag_color(ColorType.BORDER, tag.color)});'
|
||||
# f"border-color:{get_tag_color(ColorType.PRIMARY, tag.color)};"
|
||||
f"color: {get_tag_color(ColorType.TEXT, self.lib.get_tag(tag_id).color)};"
|
||||
f"font-weight: 600;"
|
||||
f"border-color:{get_tag_color(ColorType.BORDER, self.lib.get_tag(tag_id).color)};"
|
||||
f"border-radius: 6px;"
|
||||
f"border-style:solid;"
|
||||
f"border-width: {math.ceil(1*self.devicePixelRatio())}px;"
|
||||
# f'padding-top: 1.5px;'
|
||||
# f'padding-right: 4px;'
|
||||
f"padding-bottom: 5px;"
|
||||
# f'padding-left: 4px;'
|
||||
f"font-size: 20px;"
|
||||
f"}}"
|
||||
f"QPushButton::hover"
|
||||
f"{{"
|
||||
f"border-color:{get_tag_color(ColorType.LIGHT_ACCENT, self.lib.get_tag(tag_id).color)};"
|
||||
f"color: {get_tag_color(ColorType.DARK_ACCENT, self.lib.get_tag(tag_id).color)};"
|
||||
f"background: {get_tag_color(ColorType.LIGHT_ACCENT, self.lib.get_tag(tag_id).color)};"
|
||||
f"}}"
|
||||
)
|
||||
|
||||
self.search_field.setFocus()
|
||||
|
||||
# def enterEvent(self, event: QEnterEvent) -> None:
|
||||
# self.search_field.setFocus()
|
||||
# return super().enterEvent(event)
|
||||
# self.focusOutEvent
|
||||
ab.clicked.connect(lambda checked=False, x=tag_id: self.tag_chosen.emit(x))
|
||||
|
||||
l.addWidget(tw)
|
||||
l.addWidget(ab)
|
||||
self.scroll_layout.addWidget(c)
|
||||
|
||||
self.search_field.setFocus()
|
||||
|
||||
# def enterEvent(self, event: QEnterEvent) -> None:
|
||||
# self.search_field.setFocus()
|
||||
# return super().enterEvent(event)
|
||||
# self.focusOutEvent
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -15419,10 +15419,17 @@ qt_resource_struct = b"\
|
|||
\x00\x00\x01\x8a\xd2\x83?\x9d\
|
||||
"
|
||||
|
||||
|
||||
def qInitResources():
|
||||
QtCore.qRegisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data)
|
||||
QtCore.qRegisterResourceData(
|
||||
0x03, qt_resource_struct, qt_resource_name, qt_resource_data
|
||||
)
|
||||
|
||||
|
||||
def qCleanupResources():
|
||||
QtCore.qUnregisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data)
|
||||
QtCore.qUnregisterResourceData(
|
||||
0x03, qt_resource_struct, qt_resource_name, qt_resource_data
|
||||
)
|
||||
|
||||
|
||||
qInitResources()
|
||||
|
|
|
@ -16901,10 +16901,17 @@ qt_resource_struct = b"\
|
|||
\x00\x00\x01\x8a\xd2\x83?\x9d\
|
||||
"
|
||||
|
||||
|
||||
def qInitResources():
|
||||
QtCore.qRegisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data)
|
||||
QtCore.qRegisterResourceData(
|
||||
0x03, qt_resource_struct, qt_resource_name, qt_resource_data
|
||||
)
|
||||
|
||||
|
||||
def qCleanupResources():
|
||||
QtCore.qUnregisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data)
|
||||
QtCore.qUnregisterResourceData(
|
||||
0x03, qt_resource_struct, qt_resource_name, qt_resource_data
|
||||
)
|
||||
|
||||
|
||||
qInitResources()
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -10,4 +10,4 @@ from .tag import TagWidget
|
|||
from .tag_box import TagBoxWidget
|
||||
from .text import TextWidget
|
||||
from .item_thumb import ItemThumb
|
||||
from .preview_panel import PreviewPanel
|
||||
from .preview_panel import PreviewPanel
|
||||
|
|
|
@ -9,133 +9,171 @@ from pathlib import Path
|
|||
|
||||
import cv2
|
||||
from PIL import Image, ImageChops, UnidentifiedImageError
|
||||
from PySide6.QtCore import QObject, QThread, Signal, QRunnable, Qt, QThreadPool, QSize, QEvent, QTimer, QSettings
|
||||
from PySide6.QtCore import (
|
||||
QObject,
|
||||
QThread,
|
||||
Signal,
|
||||
QRunnable,
|
||||
Qt,
|
||||
QThreadPool,
|
||||
QSize,
|
||||
QEvent,
|
||||
QTimer,
|
||||
QSettings,
|
||||
)
|
||||
|
||||
from src.core.library import Library
|
||||
from src.core.ts_core import DOC_TYPES, VIDEO_TYPES, IMAGE_TYPES
|
||||
|
||||
|
||||
ERROR = f'[ERROR]'
|
||||
WARNING = f'[WARNING]'
|
||||
INFO = f'[INFO]'
|
||||
ERROR = f"[ERROR]"
|
||||
WARNING = f"[WARNING]"
|
||||
INFO = f"[INFO]"
|
||||
|
||||
|
||||
logging.basicConfig(format="%(message)s", level=logging.INFO)
|
||||
|
||||
|
||||
class CollageIconRenderer(QObject):
|
||||
rendered = Signal(Image.Image)
|
||||
done = Signal()
|
||||
rendered = Signal(Image.Image)
|
||||
done = Signal()
|
||||
|
||||
def __init__(self, library:Library):
|
||||
QObject.__init__(self)
|
||||
self.lib = library
|
||||
|
||||
def render(self, entry_id, size:tuple[int,int], data_tint_mode, data_only_mode, keep_aspect):
|
||||
entry = self.lib.get_entry(entry_id)
|
||||
filepath = os.path.normpath(f'{self.lib.library_dir}/{entry.path}/{entry.filename}')
|
||||
file_type = os.path.splitext(filepath)[1].lower()[1:]
|
||||
color: str = ''
|
||||
def __init__(self, library: Library):
|
||||
QObject.__init__(self)
|
||||
self.lib = library
|
||||
|
||||
try:
|
||||
if data_tint_mode or data_only_mode:
|
||||
color = '#000000' # Black (Default)
|
||||
|
||||
if entry.fields:
|
||||
has_any_tags:bool = False
|
||||
has_content_tags:bool = False
|
||||
has_meta_tags:bool = False
|
||||
for field in entry.fields:
|
||||
if self.lib.get_field_attr(field, 'type') == 'tag_box':
|
||||
if self.lib.get_field_attr(field, 'content'):
|
||||
has_any_tags = True
|
||||
if self.lib.get_field_attr(field, 'id') == 7:
|
||||
has_content_tags = True
|
||||
elif self.lib.get_field_attr(field, 'id') == 8:
|
||||
has_meta_tags = True
|
||||
if has_content_tags and has_meta_tags:
|
||||
color = '#28bb48' # Green
|
||||
elif has_any_tags:
|
||||
color = '#ffd63d' # Yellow
|
||||
# color = '#95e345' # Yellow-Green
|
||||
else:
|
||||
# color = '#fa9a2c' # Yellow-Orange
|
||||
color = '#ed8022' # Orange
|
||||
else:
|
||||
color = '#e22c3c' # Red
|
||||
def render(
|
||||
self,
|
||||
entry_id,
|
||||
size: tuple[int, int],
|
||||
data_tint_mode,
|
||||
data_only_mode,
|
||||
keep_aspect,
|
||||
):
|
||||
entry = self.lib.get_entry(entry_id)
|
||||
filepath = os.path.normpath(
|
||||
f"{self.lib.library_dir}/{entry.path}/{entry.filename}"
|
||||
)
|
||||
file_type = os.path.splitext(filepath)[1].lower()[1:]
|
||||
color: str = ""
|
||||
|
||||
if data_only_mode:
|
||||
pic: Image = Image.new('RGB', size, color)
|
||||
# collage.paste(pic, (y*thumb_size, x*thumb_size))
|
||||
self.rendered.emit(pic)
|
||||
if not data_only_mode:
|
||||
logging.info(f'\r{INFO} Combining [ID:{entry_id}/{len(self.lib.entries)}]: {self.get_file_color(file_type)}{entry.path}{os.sep}{entry.filename}\033[0m')
|
||||
# sys.stdout.write(f'\r{INFO} Combining [{i+1}/{len(self.lib.entries)}]: {self.get_file_color(file_type)}{entry.path}{os.sep}{entry.filename}{RESET}')
|
||||
# sys.stdout.flush()
|
||||
if file_type in IMAGE_TYPES:
|
||||
with Image.open(os.path.normpath(f'{self.lib.library_dir}/{entry.path}/{entry.filename}')) as pic:
|
||||
if keep_aspect:
|
||||
pic.thumbnail(size)
|
||||
else:
|
||||
pic = pic.resize(size)
|
||||
if data_tint_mode and color:
|
||||
pic = pic.convert(mode='RGB')
|
||||
pic = ImageChops.hard_light(pic, Image.new('RGB', size, color))
|
||||
# collage.paste(pic, (y*thumb_size, x*thumb_size))
|
||||
self.rendered.emit(pic)
|
||||
elif file_type in VIDEO_TYPES:
|
||||
video = cv2.VideoCapture(filepath)
|
||||
video.set(cv2.CAP_PROP_POS_FRAMES,
|
||||
(video.get(cv2.CAP_PROP_FRAME_COUNT) // 2))
|
||||
success, frame = video.read()
|
||||
if not success:
|
||||
# Depending on the video format, compression, and frame
|
||||
# count, seeking halfway does not work and the thumb
|
||||
# must be pulled from the earliest available frame.
|
||||
video.set(cv2.CAP_PROP_POS_FRAMES, 0)
|
||||
success, frame = video.read()
|
||||
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
||||
with Image.fromarray(frame, mode='RGB') as pic:
|
||||
if keep_aspect:
|
||||
pic.thumbnail(size)
|
||||
else:
|
||||
pic = pic.resize(size)
|
||||
if data_tint_mode and color:
|
||||
pic = ImageChops.hard_light(pic, Image.new('RGB', size, color))
|
||||
# collage.paste(pic, (y*thumb_size, x*thumb_size))
|
||||
self.rendered.emit(pic)
|
||||
except (UnidentifiedImageError, FileNotFoundError):
|
||||
logging.info(f'\n{ERROR} Couldn\'t read {entry.path}{os.sep}{entry.filename}')
|
||||
with Image.open(os.path.normpath(f'{Path(__file__).parent.parent.parent}/resources/qt/images/thumb_broken_512.png')) as pic:
|
||||
pic.thumbnail(size)
|
||||
if data_tint_mode and color:
|
||||
pic = pic.convert(mode='RGB')
|
||||
pic = ImageChops.hard_light(pic, Image.new('RGB', size, color))
|
||||
# collage.paste(pic, (y*thumb_size, x*thumb_size))
|
||||
self.rendered.emit(pic)
|
||||
except KeyboardInterrupt:
|
||||
# self.quit(save=False, backup=True)
|
||||
run = False
|
||||
# clear()
|
||||
logging.info('\n')
|
||||
logging.info(f'{INFO} Collage operation cancelled.')
|
||||
clear_scr=False
|
||||
except:
|
||||
logging.info(f'{ERROR} {entry.path}{os.sep}{entry.filename}')
|
||||
traceback.print_exc()
|
||||
logging.info('Continuing...')
|
||||
|
||||
self.done.emit()
|
||||
# logging.info('Done!')
|
||||
|
||||
def get_file_color(self, ext: str):
|
||||
if ext.lower().replace('.','',1) == 'gif':
|
||||
return '\033[93m'
|
||||
if ext.lower().replace('.','',1) in IMAGE_TYPES:
|
||||
return '\033[37m'
|
||||
elif ext.lower().replace('.','',1) in VIDEO_TYPES:
|
||||
return '\033[96m'
|
||||
elif ext.lower().replace('.','',1) in DOC_TYPES:
|
||||
return '\033[92m'
|
||||
else:
|
||||
return '\033[97m'
|
||||
try:
|
||||
if data_tint_mode or data_only_mode:
|
||||
color = "#000000" # Black (Default)
|
||||
|
||||
if entry.fields:
|
||||
has_any_tags: bool = False
|
||||
has_content_tags: bool = False
|
||||
has_meta_tags: bool = False
|
||||
for field in entry.fields:
|
||||
if self.lib.get_field_attr(field, "type") == "tag_box":
|
||||
if self.lib.get_field_attr(field, "content"):
|
||||
has_any_tags = True
|
||||
if self.lib.get_field_attr(field, "id") == 7:
|
||||
has_content_tags = True
|
||||
elif self.lib.get_field_attr(field, "id") == 8:
|
||||
has_meta_tags = True
|
||||
if has_content_tags and has_meta_tags:
|
||||
color = "#28bb48" # Green
|
||||
elif has_any_tags:
|
||||
color = "#ffd63d" # Yellow
|
||||
# color = '#95e345' # Yellow-Green
|
||||
else:
|
||||
# color = '#fa9a2c' # Yellow-Orange
|
||||
color = "#ed8022" # Orange
|
||||
else:
|
||||
color = "#e22c3c" # Red
|
||||
|
||||
if data_only_mode:
|
||||
pic: Image = Image.new("RGB", size, color)
|
||||
# collage.paste(pic, (y*thumb_size, x*thumb_size))
|
||||
self.rendered.emit(pic)
|
||||
if not data_only_mode:
|
||||
logging.info(
|
||||
f"\r{INFO} Combining [ID:{entry_id}/{len(self.lib.entries)}]: {self.get_file_color(file_type)}{entry.path}{os.sep}{entry.filename}\033[0m"
|
||||
)
|
||||
# sys.stdout.write(f'\r{INFO} Combining [{i+1}/{len(self.lib.entries)}]: {self.get_file_color(file_type)}{entry.path}{os.sep}{entry.filename}{RESET}')
|
||||
# sys.stdout.flush()
|
||||
if file_type in IMAGE_TYPES:
|
||||
with Image.open(
|
||||
os.path.normpath(
|
||||
f"{self.lib.library_dir}/{entry.path}/{entry.filename}"
|
||||
)
|
||||
) as pic:
|
||||
if keep_aspect:
|
||||
pic.thumbnail(size)
|
||||
else:
|
||||
pic = pic.resize(size)
|
||||
if data_tint_mode and color:
|
||||
pic = pic.convert(mode="RGB")
|
||||
pic = ImageChops.hard_light(
|
||||
pic, Image.new("RGB", size, color)
|
||||
)
|
||||
# collage.paste(pic, (y*thumb_size, x*thumb_size))
|
||||
self.rendered.emit(pic)
|
||||
elif file_type in VIDEO_TYPES:
|
||||
video = cv2.VideoCapture(filepath)
|
||||
video.set(
|
||||
cv2.CAP_PROP_POS_FRAMES,
|
||||
(video.get(cv2.CAP_PROP_FRAME_COUNT) // 2),
|
||||
)
|
||||
success, frame = video.read()
|
||||
if not success:
|
||||
# Depending on the video format, compression, and frame
|
||||
# count, seeking halfway does not work and the thumb
|
||||
# must be pulled from the earliest available frame.
|
||||
video.set(cv2.CAP_PROP_POS_FRAMES, 0)
|
||||
success, frame = video.read()
|
||||
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
||||
with Image.fromarray(frame, mode="RGB") as pic:
|
||||
if keep_aspect:
|
||||
pic.thumbnail(size)
|
||||
else:
|
||||
pic = pic.resize(size)
|
||||
if data_tint_mode and color:
|
||||
pic = ImageChops.hard_light(
|
||||
pic, Image.new("RGB", size, color)
|
||||
)
|
||||
# collage.paste(pic, (y*thumb_size, x*thumb_size))
|
||||
self.rendered.emit(pic)
|
||||
except (UnidentifiedImageError, FileNotFoundError):
|
||||
logging.info(
|
||||
f"\n{ERROR} Couldn't read {entry.path}{os.sep}{entry.filename}"
|
||||
)
|
||||
with Image.open(
|
||||
os.path.normpath(
|
||||
f"{Path(__file__).parent.parent.parent}/resources/qt/images/thumb_broken_512.png"
|
||||
)
|
||||
) as pic:
|
||||
pic.thumbnail(size)
|
||||
if data_tint_mode and color:
|
||||
pic = pic.convert(mode="RGB")
|
||||
pic = ImageChops.hard_light(pic, Image.new("RGB", size, color))
|
||||
# collage.paste(pic, (y*thumb_size, x*thumb_size))
|
||||
self.rendered.emit(pic)
|
||||
except KeyboardInterrupt:
|
||||
# self.quit(save=False, backup=True)
|
||||
run = False
|
||||
# clear()
|
||||
logging.info("\n")
|
||||
logging.info(f"{INFO} Collage operation cancelled.")
|
||||
clear_scr = False
|
||||
except:
|
||||
logging.info(f"{ERROR} {entry.path}{os.sep}{entry.filename}")
|
||||
traceback.print_exc()
|
||||
logging.info("Continuing...")
|
||||
|
||||
self.done.emit()
|
||||
# logging.info('Done!')
|
||||
|
||||
def get_file_color(self, ext: str):
|
||||
if ext.lower().replace(".", "", 1) == "gif":
|
||||
return "\033[93m"
|
||||
if ext.lower().replace(".", "", 1) in IMAGE_TYPES:
|
||||
return "\033[37m"
|
||||
elif ext.lower().replace(".", "", 1) in VIDEO_TYPES:
|
||||
return "\033[96m"
|
||||
elif ext.lower().replace(".", "", 1) in DOC_TYPES:
|
||||
return "\033[92m"
|
||||
else:
|
||||
return "\033[97m"
|
||||
|
|
|
@ -10,190 +10,203 @@ from pathlib import Path
|
|||
from typing import Optional
|
||||
|
||||
from PIL import Image, ImageQt
|
||||
from PySide6.QtCore import Qt,QEvent
|
||||
from PySide6.QtCore import Qt, QEvent
|
||||
from PySide6.QtGui import QPixmap, QEnterEvent
|
||||
from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton
|
||||
|
||||
|
||||
class FieldContainer(QWidget):
|
||||
# TODO: reference a resources folder rather than path.parent.parent.parent.parent?
|
||||
clipboard_icon_128: Image.Image = Image.open(os.path.normpath(
|
||||
f'{Path(__file__).parent.parent.parent.parent}/resources/qt/images/clipboard_icon_128.png')).resize((math.floor(24*1.25),math.floor(24*1.25)))
|
||||
clipboard_icon_128.load()
|
||||
# TODO: reference a resources folder rather than path.parent.parent.parent.parent?
|
||||
clipboard_icon_128: Image.Image = Image.open(
|
||||
os.path.normpath(
|
||||
f"{Path(__file__).parent.parent.parent.parent}/resources/qt/images/clipboard_icon_128.png"
|
||||
)
|
||||
).resize((math.floor(24 * 1.25), math.floor(24 * 1.25)))
|
||||
clipboard_icon_128.load()
|
||||
|
||||
edit_icon_128: Image.Image = Image.open(os.path.normpath(
|
||||
f'{Path(__file__).parent.parent.parent.parent}/resources/qt/images/edit_icon_128.png')).resize((math.floor(24*1.25),math.floor(24*1.25)))
|
||||
edit_icon_128.load()
|
||||
edit_icon_128: Image.Image = Image.open(
|
||||
os.path.normpath(
|
||||
f"{Path(__file__).parent.parent.parent.parent}/resources/qt/images/edit_icon_128.png"
|
||||
)
|
||||
).resize((math.floor(24 * 1.25), math.floor(24 * 1.25)))
|
||||
edit_icon_128.load()
|
||||
|
||||
trash_icon_128: Image.Image = Image.open(os.path.normpath(
|
||||
f'{Path(__file__).parent.parent.parent.parent}/resources/qt/images/trash_icon_128.png')).resize((math.floor(24*1.25),math.floor(24*1.25)))
|
||||
trash_icon_128.load()
|
||||
|
||||
def __init__(self, title:str='Field', inline:bool=True) -> None:
|
||||
super().__init__()
|
||||
# self.mode:str = mode
|
||||
self.setObjectName('fieldContainer')
|
||||
# self.item = item
|
||||
self.title:str = title
|
||||
self.inline:bool = inline
|
||||
# self.editable:bool = editable
|
||||
self.copy_callback:FunctionType = None
|
||||
self.edit_callback:FunctionType = None
|
||||
self.remove_callback:FunctionType = None
|
||||
button_size = 24
|
||||
# self.setStyleSheet('border-style:solid;border-color:#1e1a33;border-radius:8px;border-width:2px;')
|
||||
trash_icon_128: Image.Image = Image.open(
|
||||
os.path.normpath(
|
||||
f"{Path(__file__).parent.parent.parent.parent}/resources/qt/images/trash_icon_128.png"
|
||||
)
|
||||
).resize((math.floor(24 * 1.25), math.floor(24 * 1.25)))
|
||||
trash_icon_128.load()
|
||||
|
||||
def __init__(self, title: str = "Field", inline: bool = True) -> None:
|
||||
super().__init__()
|
||||
# self.mode:str = mode
|
||||
self.setObjectName("fieldContainer")
|
||||
# self.item = item
|
||||
self.title: str = title
|
||||
self.inline: bool = inline
|
||||
# self.editable:bool = editable
|
||||
self.copy_callback: FunctionType = None
|
||||
self.edit_callback: FunctionType = None
|
||||
self.remove_callback: FunctionType = None
|
||||
button_size = 24
|
||||
# self.setStyleSheet('border-style:solid;border-color:#1e1a33;border-radius:8px;border-width:2px;')
|
||||
|
||||
self.root_layout = QVBoxLayout(self)
|
||||
self.root_layout.setObjectName('baseLayout')
|
||||
self.root_layout.setContentsMargins(0, 0, 0, 0)
|
||||
# self.setStyleSheet('background-color:red;')
|
||||
self.root_layout = QVBoxLayout(self)
|
||||
self.root_layout.setObjectName("baseLayout")
|
||||
self.root_layout.setContentsMargins(0, 0, 0, 0)
|
||||
# self.setStyleSheet('background-color:red;')
|
||||
|
||||
self.inner_layout = QVBoxLayout()
|
||||
self.inner_layout.setObjectName('innerLayout')
|
||||
self.inner_layout.setContentsMargins(0,0,0,0)
|
||||
self.inner_layout.setSpacing(0)
|
||||
self.inner_container = QWidget()
|
||||
self.inner_container.setObjectName('innerContainer')
|
||||
self.inner_container.setLayout(self.inner_layout)
|
||||
self.root_layout.addWidget(self.inner_container)
|
||||
self.inner_layout = QVBoxLayout()
|
||||
self.inner_layout.setObjectName("innerLayout")
|
||||
self.inner_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.inner_layout.setSpacing(0)
|
||||
self.inner_container = QWidget()
|
||||
self.inner_container.setObjectName("innerContainer")
|
||||
self.inner_container.setLayout(self.inner_layout)
|
||||
self.root_layout.addWidget(self.inner_container)
|
||||
|
||||
self.title_container = QWidget()
|
||||
# self.title_container.setStyleSheet('background:black;')
|
||||
self.title_layout = QHBoxLayout(self.title_container)
|
||||
self.title_layout.setAlignment(Qt.AlignmentFlag.AlignHCenter)
|
||||
self.title_layout.setObjectName('fieldLayout')
|
||||
self.title_layout.setContentsMargins(0,0,0,0)
|
||||
self.title_layout.setSpacing(0)
|
||||
self.inner_layout.addWidget(self.title_container)
|
||||
self.title_container = QWidget()
|
||||
# self.title_container.setStyleSheet('background:black;')
|
||||
self.title_layout = QHBoxLayout(self.title_container)
|
||||
self.title_layout.setAlignment(Qt.AlignmentFlag.AlignHCenter)
|
||||
self.title_layout.setObjectName("fieldLayout")
|
||||
self.title_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.title_layout.setSpacing(0)
|
||||
self.inner_layout.addWidget(self.title_container)
|
||||
|
||||
self.title_widget = QLabel()
|
||||
self.title_widget.setMinimumHeight(button_size)
|
||||
self.title_widget.setObjectName('fieldTitle')
|
||||
self.title_widget.setWordWrap(True)
|
||||
self.title_widget.setStyleSheet('font-weight: bold; font-size: 14px;')
|
||||
# self.title_widget.setStyleSheet('background-color:orange;')
|
||||
self.title_widget.setText(title)
|
||||
# self.inner_layout.addWidget(self.title_widget)
|
||||
self.title_layout.addWidget(self.title_widget)
|
||||
self.title_widget = QLabel()
|
||||
self.title_widget.setMinimumHeight(button_size)
|
||||
self.title_widget.setObjectName("fieldTitle")
|
||||
self.title_widget.setWordWrap(True)
|
||||
self.title_widget.setStyleSheet("font-weight: bold; font-size: 14px;")
|
||||
# self.title_widget.setStyleSheet('background-color:orange;')
|
||||
self.title_widget.setText(title)
|
||||
# self.inner_layout.addWidget(self.title_widget)
|
||||
self.title_layout.addWidget(self.title_widget)
|
||||
|
||||
self.title_layout.addStretch(2)
|
||||
self.title_layout.addStretch(2)
|
||||
|
||||
self.copy_button = QPushButton()
|
||||
self.copy_button.setMinimumSize(button_size, button_size)
|
||||
self.copy_button.setMaximumSize(button_size, button_size)
|
||||
self.copy_button.setFlat(True)
|
||||
self.copy_button.setIcon(
|
||||
QPixmap.fromImage(ImageQt.ImageQt(self.clipboard_icon_128))
|
||||
)
|
||||
self.copy_button.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
self.title_layout.addWidget(self.copy_button)
|
||||
self.copy_button.setHidden(True)
|
||||
|
||||
self.copy_button = QPushButton()
|
||||
self.copy_button.setMinimumSize(button_size,button_size)
|
||||
self.copy_button.setMaximumSize(button_size,button_size)
|
||||
self.copy_button.setFlat(True)
|
||||
self.copy_button.setIcon(QPixmap.fromImage(ImageQt.ImageQt(self.clipboard_icon_128)))
|
||||
self.copy_button.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
self.title_layout.addWidget(self.copy_button)
|
||||
self.copy_button.setHidden(True)
|
||||
self.edit_button = QPushButton()
|
||||
self.edit_button.setMinimumSize(button_size, button_size)
|
||||
self.edit_button.setMaximumSize(button_size, button_size)
|
||||
self.edit_button.setFlat(True)
|
||||
self.edit_button.setIcon(QPixmap.fromImage(ImageQt.ImageQt(self.edit_icon_128)))
|
||||
self.edit_button.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
self.title_layout.addWidget(self.edit_button)
|
||||
self.edit_button.setHidden(True)
|
||||
|
||||
self.edit_button = QPushButton()
|
||||
self.edit_button.setMinimumSize(button_size,button_size)
|
||||
self.edit_button.setMaximumSize(button_size,button_size)
|
||||
self.edit_button.setFlat(True)
|
||||
self.edit_button.setIcon(QPixmap.fromImage(ImageQt.ImageQt(self.edit_icon_128)))
|
||||
self.edit_button.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
self.title_layout.addWidget(self.edit_button)
|
||||
self.edit_button.setHidden(True)
|
||||
self.remove_button = QPushButton()
|
||||
self.remove_button.setMinimumSize(button_size, button_size)
|
||||
self.remove_button.setMaximumSize(button_size, button_size)
|
||||
self.remove_button.setFlat(True)
|
||||
self.remove_button.setIcon(
|
||||
QPixmap.fromImage(ImageQt.ImageQt(self.trash_icon_128))
|
||||
)
|
||||
self.remove_button.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
self.title_layout.addWidget(self.remove_button)
|
||||
self.remove_button.setHidden(True)
|
||||
|
||||
self.remove_button = QPushButton()
|
||||
self.remove_button.setMinimumSize(button_size,button_size)
|
||||
self.remove_button.setMaximumSize(button_size,button_size)
|
||||
self.remove_button.setFlat(True)
|
||||
self.remove_button.setIcon(QPixmap.fromImage(ImageQt.ImageQt(self.trash_icon_128)))
|
||||
self.remove_button.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
self.title_layout.addWidget(self.remove_button)
|
||||
self.remove_button.setHidden(True)
|
||||
self.field_container = QWidget()
|
||||
self.field_container.setObjectName("fieldContainer")
|
||||
self.field_layout = QHBoxLayout()
|
||||
self.field_layout.setObjectName("fieldLayout")
|
||||
self.field_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.field_container.setLayout(self.field_layout)
|
||||
# self.field_container.setStyleSheet('background-color:#666600;')
|
||||
self.inner_layout.addWidget(self.field_container)
|
||||
|
||||
self.field_container = QWidget()
|
||||
self.field_container.setObjectName('fieldContainer')
|
||||
self.field_layout = QHBoxLayout()
|
||||
self.field_layout.setObjectName('fieldLayout')
|
||||
self.field_layout.setContentsMargins(0,0,0,0)
|
||||
self.field_container.setLayout(self.field_layout)
|
||||
# self.field_container.setStyleSheet('background-color:#666600;')
|
||||
self.inner_layout.addWidget(self.field_container)
|
||||
# self.set_inner_widget(mode)
|
||||
|
||||
# self.set_inner_widget(mode)
|
||||
def set_copy_callback(self, callback: Optional[FunctionType]):
|
||||
try:
|
||||
self.copy_button.clicked.disconnect()
|
||||
except RuntimeError:
|
||||
pass
|
||||
|
||||
def set_copy_callback(self, callback:Optional[FunctionType]):
|
||||
try:
|
||||
self.copy_button.clicked.disconnect()
|
||||
except RuntimeError:
|
||||
pass
|
||||
self.copy_callback = callback
|
||||
self.copy_button.clicked.connect(callback)
|
||||
|
||||
self.copy_callback = callback
|
||||
self.copy_button.clicked.connect(callback)
|
||||
def set_edit_callback(self, callback: Optional[FunctionType]):
|
||||
try:
|
||||
self.edit_button.clicked.disconnect()
|
||||
except RuntimeError:
|
||||
pass
|
||||
|
||||
def set_edit_callback(self, callback:Optional[FunctionType]):
|
||||
try:
|
||||
self.edit_button.clicked.disconnect()
|
||||
except RuntimeError:
|
||||
pass
|
||||
self.edit_callback = callback
|
||||
self.edit_button.clicked.connect(callback)
|
||||
|
||||
self.edit_callback = callback
|
||||
self.edit_button.clicked.connect(callback)
|
||||
def set_remove_callback(self, callback: Optional[FunctionType]):
|
||||
try:
|
||||
self.remove_button.clicked.disconnect()
|
||||
except RuntimeError:
|
||||
pass
|
||||
|
||||
def set_remove_callback(self, callback:Optional[FunctionType]):
|
||||
try:
|
||||
self.remove_button.clicked.disconnect()
|
||||
except RuntimeError:
|
||||
pass
|
||||
self.remove_callback = callback
|
||||
self.remove_button.clicked.connect(callback)
|
||||
|
||||
self.remove_callback = callback
|
||||
self.remove_button.clicked.connect(callback)
|
||||
|
||||
def set_inner_widget(self, widget:'FieldWidget'):
|
||||
# widget.setStyleSheet('background-color:green;')
|
||||
# self.inner_container.dumpObjectTree()
|
||||
# logging.info('')
|
||||
if self.field_layout.itemAt(0):
|
||||
# logging.info(f'Removing {self.field_layout.itemAt(0)}')
|
||||
# self.field_layout.removeItem(self.field_layout.itemAt(0))
|
||||
self.field_layout.itemAt(0).widget().deleteLater()
|
||||
self.field_layout.addWidget(widget)
|
||||
|
||||
def get_inner_widget(self) -> Optional['FieldWidget']:
|
||||
if self.field_layout.itemAt(0):
|
||||
return self.field_layout.itemAt(0).widget()
|
||||
return None
|
||||
def set_inner_widget(self, widget: "FieldWidget"):
|
||||
# widget.setStyleSheet('background-color:green;')
|
||||
# self.inner_container.dumpObjectTree()
|
||||
# logging.info('')
|
||||
if self.field_layout.itemAt(0):
|
||||
# logging.info(f'Removing {self.field_layout.itemAt(0)}')
|
||||
# self.field_layout.removeItem(self.field_layout.itemAt(0))
|
||||
self.field_layout.itemAt(0).widget().deleteLater()
|
||||
self.field_layout.addWidget(widget)
|
||||
|
||||
def set_title(self, title:str):
|
||||
self.title = title
|
||||
self.title_widget.setText(title)
|
||||
|
||||
def set_inline(self, inline:bool):
|
||||
self.inline = inline
|
||||
|
||||
# def set_editable(self, editable:bool):
|
||||
# self.editable = editable
|
||||
|
||||
def enterEvent(self, event: QEnterEvent) -> None:
|
||||
# if self.field_layout.itemAt(1):
|
||||
# self.field_layout.itemAt(1).
|
||||
# NOTE: You could pass the hover event to the FieldWidget if needed.
|
||||
if self.copy_callback:
|
||||
self.copy_button.setHidden(False)
|
||||
if self.edit_callback:
|
||||
self.edit_button.setHidden(False)
|
||||
if self.remove_callback:
|
||||
self.remove_button.setHidden(False)
|
||||
return super().enterEvent(event)
|
||||
def get_inner_widget(self) -> Optional["FieldWidget"]:
|
||||
if self.field_layout.itemAt(0):
|
||||
return self.field_layout.itemAt(0).widget()
|
||||
return None
|
||||
|
||||
def leaveEvent(self, event: QEvent) -> None:
|
||||
if self.copy_callback:
|
||||
self.copy_button.setHidden(True)
|
||||
if self.edit_callback:
|
||||
self.edit_button.setHidden(True)
|
||||
if self.remove_callback:
|
||||
self.remove_button.setHidden(True)
|
||||
return super().leaveEvent(event)
|
||||
def set_title(self, title: str):
|
||||
self.title = title
|
||||
self.title_widget.setText(title)
|
||||
|
||||
def set_inline(self, inline: bool):
|
||||
self.inline = inline
|
||||
|
||||
# def set_editable(self, editable:bool):
|
||||
# self.editable = editable
|
||||
|
||||
def enterEvent(self, event: QEnterEvent) -> None:
|
||||
# if self.field_layout.itemAt(1):
|
||||
# self.field_layout.itemAt(1).
|
||||
# NOTE: You could pass the hover event to the FieldWidget if needed.
|
||||
if self.copy_callback:
|
||||
self.copy_button.setHidden(False)
|
||||
if self.edit_callback:
|
||||
self.edit_button.setHidden(False)
|
||||
if self.remove_callback:
|
||||
self.remove_button.setHidden(False)
|
||||
return super().enterEvent(event)
|
||||
|
||||
def leaveEvent(self, event: QEvent) -> None:
|
||||
if self.copy_callback:
|
||||
self.copy_button.setHidden(True)
|
||||
if self.edit_callback:
|
||||
self.edit_button.setHidden(True)
|
||||
if self.remove_callback:
|
||||
self.remove_button.setHidden(True)
|
||||
return super().leaveEvent(event)
|
||||
|
||||
|
||||
class FieldWidget(QWidget):
|
||||
field = dict
|
||||
def __init__(self, title) -> None:
|
||||
super().__init__()
|
||||
# self.item = item
|
||||
self.title = title
|
||||
field = dict
|
||||
|
||||
def __init__(self, title) -> None:
|
||||
super().__init__()
|
||||
# self.item = item
|
||||
self.title = title
|
||||
|
|
|
@ -14,7 +14,14 @@ from typing import Optional
|
|||
from PIL import Image, ImageQt
|
||||
from PySide6.QtCore import Qt, QSize, QEvent
|
||||
from PySide6.QtGui import QPixmap, QEnterEvent, QAction
|
||||
from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, QBoxLayout, QCheckBox
|
||||
from PySide6.QtWidgets import (
|
||||
QWidget,
|
||||
QVBoxLayout,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QBoxLayout,
|
||||
QCheckBox,
|
||||
)
|
||||
|
||||
from src.core.library import ItemType, Library
|
||||
from src.core.ts_core import AUDIO_TYPES, VIDEO_TYPES, IMAGE_TYPES
|
||||
|
@ -23,461 +30,514 @@ from src.qt.helpers import FileOpenerHelper
|
|||
from src.qt.widgets import ThumbRenderer, ThumbButton
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from src.qt.widgets import PreviewPanel
|
||||
from src.qt.widgets import PreviewPanel
|
||||
|
||||
|
||||
ERROR = f'[ERROR]'
|
||||
WARNING = f'[WARNING]'
|
||||
INFO = f'[INFO]'
|
||||
ERROR = f"[ERROR]"
|
||||
WARNING = f"[WARNING]"
|
||||
INFO = f"[INFO]"
|
||||
|
||||
logging.basicConfig(format="%(message)s", level=logging.INFO)
|
||||
|
||||
|
||||
class ItemThumb(FlowWidget):
|
||||
"""
|
||||
The thumbnail widget for a library item (Entry, Collation, Tag Group, etc.).
|
||||
"""
|
||||
update_cutoff: float = time.time()
|
||||
"""
|
||||
The thumbnail widget for a library item (Entry, Collation, Tag Group, etc.).
|
||||
"""
|
||||
|
||||
collation_icon_128: Image.Image = Image.open(os.path.normpath(
|
||||
f'{Path(__file__).parent.parent.parent.parent}/resources/qt/images/collation_icon_128.png'))
|
||||
collation_icon_128.load()
|
||||
update_cutoff: float = time.time()
|
||||
|
||||
tag_group_icon_128: Image.Image = Image.open(os.path.normpath(
|
||||
f'{Path(__file__).parent.parent.parent.parent}/resources/qt/images/tag_group_icon_128.png'))
|
||||
tag_group_icon_128.load()
|
||||
collation_icon_128: Image.Image = Image.open(
|
||||
os.path.normpath(
|
||||
f"{Path(__file__).parent.parent.parent.parent}/resources/qt/images/collation_icon_128.png"
|
||||
)
|
||||
)
|
||||
collation_icon_128.load()
|
||||
|
||||
small_text_style = (
|
||||
f'background-color:rgba(0, 0, 0, 128);'
|
||||
f'font-family:Oxanium;'
|
||||
f'font-weight:bold;'
|
||||
f'font-size:12px;'
|
||||
f'border-radius:3px;'
|
||||
f'padding-top: 4px;'
|
||||
f'padding-right: 1px;'
|
||||
f'padding-bottom: 1px;'
|
||||
f'padding-left: 1px;'
|
||||
)
|
||||
tag_group_icon_128: Image.Image = Image.open(
|
||||
os.path.normpath(
|
||||
f"{Path(__file__).parent.parent.parent.parent}/resources/qt/images/tag_group_icon_128.png"
|
||||
)
|
||||
)
|
||||
tag_group_icon_128.load()
|
||||
|
||||
med_text_style = (
|
||||
f'background-color:rgba(17, 15, 27, 192);'
|
||||
f'font-family:Oxanium;'
|
||||
f'font-weight:bold;'
|
||||
f'font-size:18px;'
|
||||
f'border-radius:3px;'
|
||||
f'padding-top: 4px;'
|
||||
f'padding-right: 1px;'
|
||||
f'padding-bottom: 1px;'
|
||||
f'padding-left: 1px;'
|
||||
)
|
||||
small_text_style = (
|
||||
f"background-color:rgba(0, 0, 0, 128);"
|
||||
f"font-family:Oxanium;"
|
||||
f"font-weight:bold;"
|
||||
f"font-size:12px;"
|
||||
f"border-radius:3px;"
|
||||
f"padding-top: 4px;"
|
||||
f"padding-right: 1px;"
|
||||
f"padding-bottom: 1px;"
|
||||
f"padding-left: 1px;"
|
||||
)
|
||||
|
||||
def __init__(self, mode: Optional[ItemType], library: Library, panel: 'PreviewPanel', thumb_size: tuple[int, int]):
|
||||
"""Modes: entry, collation, tag_group"""
|
||||
super().__init__()
|
||||
self.lib = library
|
||||
self.panel = panel
|
||||
self.mode = mode
|
||||
self.item_id: int = -1
|
||||
self.isFavorite: bool = False
|
||||
self.isArchived: bool = False
|
||||
self.thumb_size:tuple[int,int]= thumb_size
|
||||
self.setMinimumSize(*thumb_size)
|
||||
self.setMaximumSize(*thumb_size)
|
||||
check_size = 24
|
||||
# self.setStyleSheet('background-color:red;')
|
||||
med_text_style = (
|
||||
f"background-color:rgba(17, 15, 27, 192);"
|
||||
f"font-family:Oxanium;"
|
||||
f"font-weight:bold;"
|
||||
f"font-size:18px;"
|
||||
f"border-radius:3px;"
|
||||
f"padding-top: 4px;"
|
||||
f"padding-right: 1px;"
|
||||
f"padding-bottom: 1px;"
|
||||
f"padding-left: 1px;"
|
||||
)
|
||||
|
||||
# +----------+
|
||||
# | ARC FAV| Top Right: Favorite & Archived Badges
|
||||
# | |
|
||||
# | |
|
||||
# |EXT #| Lower Left: File Type, Tag Group Icon, or Collation Icon
|
||||
# +----------+ Lower Right: Collation Count, Video Length, or Word Count
|
||||
def __init__(
|
||||
self,
|
||||
mode: Optional[ItemType],
|
||||
library: Library,
|
||||
panel: "PreviewPanel",
|
||||
thumb_size: tuple[int, int],
|
||||
):
|
||||
"""Modes: entry, collation, tag_group"""
|
||||
super().__init__()
|
||||
self.lib = library
|
||||
self.panel = panel
|
||||
self.mode = mode
|
||||
self.item_id: int = -1
|
||||
self.isFavorite: bool = False
|
||||
self.isArchived: bool = False
|
||||
self.thumb_size: tuple[int, int] = thumb_size
|
||||
self.setMinimumSize(*thumb_size)
|
||||
self.setMaximumSize(*thumb_size)
|
||||
check_size = 24
|
||||
# self.setStyleSheet('background-color:red;')
|
||||
|
||||
# Thumbnail ============================================================
|
||||
# +----------+
|
||||
# | ARC FAV| Top Right: Favorite & Archived Badges
|
||||
# | |
|
||||
# | |
|
||||
# |EXT #| Lower Left: File Type, Tag Group Icon, or Collation Icon
|
||||
# +----------+ Lower Right: Collation Count, Video Length, or Word Count
|
||||
|
||||
# +----------+
|
||||
# |*--------*|
|
||||
# || ||
|
||||
# || ||
|
||||
# |*--------*|
|
||||
# +----------+
|
||||
self.base_layout = QVBoxLayout(self)
|
||||
self.base_layout.setObjectName('baseLayout')
|
||||
# self.base_layout.setRowStretch(1, 2)
|
||||
self.base_layout.setContentsMargins(0, 0, 0, 0)
|
||||
# Thumbnail ============================================================
|
||||
|
||||
# +----------+
|
||||
# |[~~~~~~~~]|
|
||||
# | |
|
||||
# | |
|
||||
# | |
|
||||
# +----------+
|
||||
self.top_layout = QHBoxLayout()
|
||||
self.top_layout.setObjectName('topLayout')
|
||||
# self.top_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
||||
# self.top_layout.setColumnStretch(1, 2)
|
||||
self.top_layout.setContentsMargins(6, 6, 6, 6)
|
||||
self.top_container = QWidget()
|
||||
self.top_container.setLayout(self.top_layout)
|
||||
self.base_layout.addWidget(self.top_container)
|
||||
# +----------+
|
||||
# |*--------*|
|
||||
# || ||
|
||||
# || ||
|
||||
# |*--------*|
|
||||
# +----------+
|
||||
self.base_layout = QVBoxLayout(self)
|
||||
self.base_layout.setObjectName("baseLayout")
|
||||
# self.base_layout.setRowStretch(1, 2)
|
||||
self.base_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
# +----------+
|
||||
# |[~~~~~~~~]|
|
||||
# | ^ |
|
||||
# | | |
|
||||
# | v |
|
||||
# +----------+
|
||||
self.base_layout.addStretch(2)
|
||||
# +----------+
|
||||
# |[~~~~~~~~]|
|
||||
# | |
|
||||
# | |
|
||||
# | |
|
||||
# +----------+
|
||||
self.top_layout = QHBoxLayout()
|
||||
self.top_layout.setObjectName("topLayout")
|
||||
# self.top_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
||||
# self.top_layout.setColumnStretch(1, 2)
|
||||
self.top_layout.setContentsMargins(6, 6, 6, 6)
|
||||
self.top_container = QWidget()
|
||||
self.top_container.setLayout(self.top_layout)
|
||||
self.base_layout.addWidget(self.top_container)
|
||||
|
||||
# +----------+
|
||||
# |[~~~~~~~~]|
|
||||
# | ^ |
|
||||
# | v |
|
||||
# |[~~~~~~~~]|
|
||||
# +----------+
|
||||
self.bottom_layout = QHBoxLayout()
|
||||
self.bottom_layout.setObjectName('bottomLayout')
|
||||
# self.bottom_container.setAlignment(Qt.AlignmentFlag.AlignBottom)
|
||||
# self.bottom_layout.setColumnStretch(1, 2)
|
||||
self.bottom_layout.setContentsMargins(6, 6, 6, 6)
|
||||
self.bottom_container = QWidget()
|
||||
self.bottom_container.setLayout(self.bottom_layout)
|
||||
self.base_layout.addWidget(self.bottom_container)
|
||||
# +----------+
|
||||
# |[~~~~~~~~]|
|
||||
# | ^ |
|
||||
# | | |
|
||||
# | v |
|
||||
# +----------+
|
||||
self.base_layout.addStretch(2)
|
||||
|
||||
# self.root_layout = QGridLayout(self)
|
||||
# self.root_layout.setObjectName('rootLayout')
|
||||
# self.root_layout.setColumnStretch(1, 2)
|
||||
# self.root_layout.setRowStretch(1, 2)
|
||||
# self.root_layout.setContentsMargins(6,6,6,6)
|
||||
# # root_layout.setAlignment(Qt.AlignmentFlag.AlignHCenter)
|
||||
# +----------+
|
||||
# |[~~~~~~~~]|
|
||||
# | ^ |
|
||||
# | v |
|
||||
# |[~~~~~~~~]|
|
||||
# +----------+
|
||||
self.bottom_layout = QHBoxLayout()
|
||||
self.bottom_layout.setObjectName("bottomLayout")
|
||||
# self.bottom_container.setAlignment(Qt.AlignmentFlag.AlignBottom)
|
||||
# self.bottom_layout.setColumnStretch(1, 2)
|
||||
self.bottom_layout.setContentsMargins(6, 6, 6, 6)
|
||||
self.bottom_container = QWidget()
|
||||
self.bottom_container.setLayout(self.bottom_layout)
|
||||
self.base_layout.addWidget(self.bottom_container)
|
||||
|
||||
self.thumb_button = ThumbButton(self, thumb_size)
|
||||
self.renderer = ThumbRenderer()
|
||||
self.renderer.updated.connect(lambda ts, i, s, ext: (self.update_thumb(ts, image=i),
|
||||
self.update_size(
|
||||
ts, size=s),
|
||||
self.set_extension(ext)))
|
||||
self.thumb_button.setFlat(True)
|
||||
# self.root_layout = QGridLayout(self)
|
||||
# self.root_layout.setObjectName('rootLayout')
|
||||
# self.root_layout.setColumnStretch(1, 2)
|
||||
# self.root_layout.setRowStretch(1, 2)
|
||||
# self.root_layout.setContentsMargins(6,6,6,6)
|
||||
# # root_layout.setAlignment(Qt.AlignmentFlag.AlignHCenter)
|
||||
|
||||
# self.bg_button.setStyleSheet('background-color:blue;')
|
||||
# self.bg_button.setLayout(self.root_layout)
|
||||
self.thumb_button.setLayout(self.base_layout)
|
||||
# self.bg_button.setMinimumSize(*thumb_size)
|
||||
# self.bg_button.setMaximumSize(*thumb_size)
|
||||
self.thumb_button = ThumbButton(self, thumb_size)
|
||||
self.renderer = ThumbRenderer()
|
||||
self.renderer.updated.connect(
|
||||
lambda ts, i, s, ext: (
|
||||
self.update_thumb(ts, image=i),
|
||||
self.update_size(ts, size=s),
|
||||
self.set_extension(ext),
|
||||
)
|
||||
)
|
||||
self.thumb_button.setFlat(True)
|
||||
|
||||
self.thumb_button.setContextMenuPolicy(Qt.ContextMenuPolicy.ActionsContextMenu)
|
||||
self.opener = FileOpenerHelper('')
|
||||
open_file_action = QAction('Open file', self)
|
||||
open_file_action.triggered.connect(self.opener.open_file)
|
||||
open_explorer_action = QAction('Open file in explorer', self)
|
||||
open_explorer_action.triggered.connect(self.opener.open_explorer)
|
||||
self.thumb_button.addAction(open_file_action)
|
||||
self.thumb_button.addAction(open_explorer_action)
|
||||
# self.bg_button.setStyleSheet('background-color:blue;')
|
||||
# self.bg_button.setLayout(self.root_layout)
|
||||
self.thumb_button.setLayout(self.base_layout)
|
||||
# self.bg_button.setMinimumSize(*thumb_size)
|
||||
# self.bg_button.setMaximumSize(*thumb_size)
|
||||
|
||||
# Static Badges ========================================================
|
||||
self.thumb_button.setContextMenuPolicy(Qt.ContextMenuPolicy.ActionsContextMenu)
|
||||
self.opener = FileOpenerHelper("")
|
||||
open_file_action = QAction("Open file", self)
|
||||
open_file_action.triggered.connect(self.opener.open_file)
|
||||
open_explorer_action = QAction("Open file in explorer", self)
|
||||
open_explorer_action.triggered.connect(self.opener.open_explorer)
|
||||
self.thumb_button.addAction(open_file_action)
|
||||
self.thumb_button.addAction(open_explorer_action)
|
||||
|
||||
# Item Type Badge ------------------------------------------------------
|
||||
# Used for showing the Tag Group / Collation icons.
|
||||
# Mutually exclusive with the File Extension Badge.
|
||||
self.item_type_badge = QLabel()
|
||||
self.item_type_badge.setObjectName('itemBadge')
|
||||
self.item_type_badge.setPixmap(QPixmap.fromImage(ImageQt.ImageQt(
|
||||
ItemThumb.collation_icon_128.resize((check_size, check_size), Image.Resampling.BILINEAR))))
|
||||
self.item_type_badge.setMinimumSize(check_size, check_size)
|
||||
self.item_type_badge.setMaximumSize(check_size, check_size)
|
||||
# self.root_layout.addWidget(self.item_type_badge, 2, 0)
|
||||
self.bottom_layout.addWidget(self.item_type_badge)
|
||||
# Static Badges ========================================================
|
||||
|
||||
# File Extension Badge -------------------------------------------------
|
||||
# Mutually exclusive with the File Extension Badge.
|
||||
self.ext_badge = QLabel()
|
||||
self.ext_badge.setObjectName('extBadge')
|
||||
# self.ext_badge.setText('MP4')
|
||||
# self.ext_badge.setAlignment(Qt.AlignmentFlag.AlignVCenter)
|
||||
self.ext_badge.setStyleSheet(ItemThumb.small_text_style)
|
||||
# self.type_badge.setAlignment(Qt.AlignmentFlag.AlignRight)
|
||||
# self.root_layout.addWidget(self.ext_badge, 2, 0)
|
||||
self.bottom_layout.addWidget(self.ext_badge)
|
||||
# self.type_badge.setHidden(True)
|
||||
# bl_layout.addWidget(self.type_badge)
|
||||
# Item Type Badge ------------------------------------------------------
|
||||
# Used for showing the Tag Group / Collation icons.
|
||||
# Mutually exclusive with the File Extension Badge.
|
||||
self.item_type_badge = QLabel()
|
||||
self.item_type_badge.setObjectName("itemBadge")
|
||||
self.item_type_badge.setPixmap(
|
||||
QPixmap.fromImage(
|
||||
ImageQt.ImageQt(
|
||||
ItemThumb.collation_icon_128.resize(
|
||||
(check_size, check_size), Image.Resampling.BILINEAR
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
self.item_type_badge.setMinimumSize(check_size, check_size)
|
||||
self.item_type_badge.setMaximumSize(check_size, check_size)
|
||||
# self.root_layout.addWidget(self.item_type_badge, 2, 0)
|
||||
self.bottom_layout.addWidget(self.item_type_badge)
|
||||
|
||||
self.bottom_layout.addStretch(2)
|
||||
# File Extension Badge -------------------------------------------------
|
||||
# Mutually exclusive with the File Extension Badge.
|
||||
self.ext_badge = QLabel()
|
||||
self.ext_badge.setObjectName("extBadge")
|
||||
# self.ext_badge.setText('MP4')
|
||||
# self.ext_badge.setAlignment(Qt.AlignmentFlag.AlignVCenter)
|
||||
self.ext_badge.setStyleSheet(ItemThumb.small_text_style)
|
||||
# self.type_badge.setAlignment(Qt.AlignmentFlag.AlignRight)
|
||||
# self.root_layout.addWidget(self.ext_badge, 2, 0)
|
||||
self.bottom_layout.addWidget(self.ext_badge)
|
||||
# self.type_badge.setHidden(True)
|
||||
# bl_layout.addWidget(self.type_badge)
|
||||
|
||||
# Count Badge ----------------------------------------------------------
|
||||
# Used for Tag Group + Collation counts, video length, word count, etc.
|
||||
self.count_badge = QLabel()
|
||||
self.count_badge.setObjectName('countBadge')
|
||||
# self.count_badge.setMaximumHeight(17)
|
||||
self.count_badge.setText('-:--')
|
||||
# self.count_badge.setAlignment(Qt.AlignmentFlag.AlignVCenter)
|
||||
self.count_badge.setStyleSheet(ItemThumb.small_text_style)
|
||||
# self.count_badge.setAlignment(Qt.AlignmentFlag.AlignBottom)
|
||||
# self.root_layout.addWidget(self.count_badge, 2, 2)
|
||||
self.bottom_layout.addWidget(
|
||||
self.count_badge, alignment=Qt.AlignmentFlag.AlignBottom)
|
||||
self.bottom_layout.addStretch(2)
|
||||
|
||||
self.top_layout.addStretch(2)
|
||||
# Count Badge ----------------------------------------------------------
|
||||
# Used for Tag Group + Collation counts, video length, word count, etc.
|
||||
self.count_badge = QLabel()
|
||||
self.count_badge.setObjectName("countBadge")
|
||||
# self.count_badge.setMaximumHeight(17)
|
||||
self.count_badge.setText("-:--")
|
||||
# self.count_badge.setAlignment(Qt.AlignmentFlag.AlignVCenter)
|
||||
self.count_badge.setStyleSheet(ItemThumb.small_text_style)
|
||||
# self.count_badge.setAlignment(Qt.AlignmentFlag.AlignBottom)
|
||||
# self.root_layout.addWidget(self.count_badge, 2, 2)
|
||||
self.bottom_layout.addWidget(
|
||||
self.count_badge, alignment=Qt.AlignmentFlag.AlignBottom
|
||||
)
|
||||
|
||||
# Intractable Badges ===================================================
|
||||
self.cb_container = QWidget()
|
||||
# check_badges.setStyleSheet('background-color:cyan;')
|
||||
self.cb_layout = QHBoxLayout()
|
||||
self.cb_layout.setDirection(QBoxLayout.Direction.RightToLeft)
|
||||
self.cb_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.cb_layout.setSpacing(6)
|
||||
self.cb_container.setLayout(self.cb_layout)
|
||||
# self.cb_container.setHidden(True)
|
||||
# self.root_layout.addWidget(self.check_badges, 0, 2)
|
||||
self.top_layout.addWidget(self.cb_container)
|
||||
self.top_layout.addStretch(2)
|
||||
|
||||
# Favorite Badge -------------------------------------------------------
|
||||
self.favorite_badge = QCheckBox()
|
||||
self.favorite_badge.setObjectName('favBadge')
|
||||
self.favorite_badge.setToolTip('Favorite')
|
||||
self.favorite_badge.setStyleSheet(f'QCheckBox::indicator{{width: {check_size}px;height: {check_size}px;}}'
|
||||
f'QCheckBox::indicator::unchecked{{image: url(:/images/star_icon_empty_128.png)}}'
|
||||
f'QCheckBox::indicator::checked{{image: url(:/images/star_icon_filled_128.png)}}'
|
||||
# f'QCheckBox{{background-color:yellow;}}'
|
||||
)
|
||||
self.favorite_badge.setMinimumSize(check_size, check_size)
|
||||
self.favorite_badge.setMaximumSize(check_size, check_size)
|
||||
self.favorite_badge.stateChanged.connect(
|
||||
lambda x=self.favorite_badge.isChecked(): self.on_favorite_check(bool(x)))
|
||||
# Intractable Badges ===================================================
|
||||
self.cb_container = QWidget()
|
||||
# check_badges.setStyleSheet('background-color:cyan;')
|
||||
self.cb_layout = QHBoxLayout()
|
||||
self.cb_layout.setDirection(QBoxLayout.Direction.RightToLeft)
|
||||
self.cb_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.cb_layout.setSpacing(6)
|
||||
self.cb_container.setLayout(self.cb_layout)
|
||||
# self.cb_container.setHidden(True)
|
||||
# self.root_layout.addWidget(self.check_badges, 0, 2)
|
||||
self.top_layout.addWidget(self.cb_container)
|
||||
|
||||
# self.fav_badge.setContentsMargins(0,0,0,0)
|
||||
# tr_layout.addWidget(self.fav_badge)
|
||||
# root_layout.addWidget(self.fav_badge, 0, 2)
|
||||
self.cb_layout.addWidget(self.favorite_badge)
|
||||
self.favorite_badge.setHidden(True)
|
||||
# Favorite Badge -------------------------------------------------------
|
||||
self.favorite_badge = QCheckBox()
|
||||
self.favorite_badge.setObjectName("favBadge")
|
||||
self.favorite_badge.setToolTip("Favorite")
|
||||
self.favorite_badge.setStyleSheet(
|
||||
f"QCheckBox::indicator{{width: {check_size}px;height: {check_size}px;}}"
|
||||
f"QCheckBox::indicator::unchecked{{image: url(:/images/star_icon_empty_128.png)}}"
|
||||
f"QCheckBox::indicator::checked{{image: url(:/images/star_icon_filled_128.png)}}"
|
||||
# f'QCheckBox{{background-color:yellow;}}'
|
||||
)
|
||||
self.favorite_badge.setMinimumSize(check_size, check_size)
|
||||
self.favorite_badge.setMaximumSize(check_size, check_size)
|
||||
self.favorite_badge.stateChanged.connect(
|
||||
lambda x=self.favorite_badge.isChecked(): self.on_favorite_check(bool(x))
|
||||
)
|
||||
|
||||
# Archive Badge --------------------------------------------------------
|
||||
self.archived_badge = QCheckBox()
|
||||
self.archived_badge.setObjectName('archiveBadge')
|
||||
self.archived_badge.setToolTip('Archive')
|
||||
self.archived_badge.setStyleSheet(f'QCheckBox::indicator{{width: {check_size}px;height: {check_size}px;}}'
|
||||
f'QCheckBox::indicator::unchecked{{image: url(:/images/box_icon_empty_128.png)}}'
|
||||
f'QCheckBox::indicator::checked{{image: url(:/images/box_icon_filled_128.png)}}'
|
||||
# f'QCheckBox{{background-color:red;}}'
|
||||
)
|
||||
self.archived_badge.setMinimumSize(check_size, check_size)
|
||||
self.archived_badge.setMaximumSize(check_size, check_size)
|
||||
# self.archived_badge.clicked.connect(lambda x: self.assign_archived(x))
|
||||
self.archived_badge.stateChanged.connect(
|
||||
lambda x=self.archived_badge.isChecked(): self.on_archived_check(bool(x)))
|
||||
# self.fav_badge.setContentsMargins(0,0,0,0)
|
||||
# tr_layout.addWidget(self.fav_badge)
|
||||
# root_layout.addWidget(self.fav_badge, 0, 2)
|
||||
self.cb_layout.addWidget(self.favorite_badge)
|
||||
self.favorite_badge.setHidden(True)
|
||||
|
||||
# tr_layout.addWidget(self.archive_badge)
|
||||
self.cb_layout.addWidget(self.archived_badge)
|
||||
self.archived_badge.setHidden(True)
|
||||
# root_layout.addWidget(self.archive_badge, 0, 2)
|
||||
# self.dumpObjectTree()
|
||||
# Archive Badge --------------------------------------------------------
|
||||
self.archived_badge = QCheckBox()
|
||||
self.archived_badge.setObjectName("archiveBadge")
|
||||
self.archived_badge.setToolTip("Archive")
|
||||
self.archived_badge.setStyleSheet(
|
||||
f"QCheckBox::indicator{{width: {check_size}px;height: {check_size}px;}}"
|
||||
f"QCheckBox::indicator::unchecked{{image: url(:/images/box_icon_empty_128.png)}}"
|
||||
f"QCheckBox::indicator::checked{{image: url(:/images/box_icon_filled_128.png)}}"
|
||||
# f'QCheckBox{{background-color:red;}}'
|
||||
)
|
||||
self.archived_badge.setMinimumSize(check_size, check_size)
|
||||
self.archived_badge.setMaximumSize(check_size, check_size)
|
||||
# self.archived_badge.clicked.connect(lambda x: self.assign_archived(x))
|
||||
self.archived_badge.stateChanged.connect(
|
||||
lambda x=self.archived_badge.isChecked(): self.on_archived_check(bool(x))
|
||||
)
|
||||
|
||||
self.set_mode(mode)
|
||||
# tr_layout.addWidget(self.archive_badge)
|
||||
self.cb_layout.addWidget(self.archived_badge)
|
||||
self.archived_badge.setHidden(True)
|
||||
# root_layout.addWidget(self.archive_badge, 0, 2)
|
||||
# self.dumpObjectTree()
|
||||
|
||||
def set_mode(self, mode: Optional[ItemType]) -> None:
|
||||
if mode is None:
|
||||
self.unsetCursor()
|
||||
self.thumb_button.setHidden(True)
|
||||
# self.check_badges.setHidden(True)
|
||||
# self.ext_badge.setHidden(True)
|
||||
# self.item_type_badge.setHidden(True)
|
||||
pass
|
||||
elif mode == ItemType.ENTRY and self.mode != ItemType.ENTRY:
|
||||
self.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
self.thumb_button.setHidden(False)
|
||||
self.cb_container.setHidden(False)
|
||||
# Count Badge depends on file extension (video length, word count)
|
||||
self.item_type_badge.setHidden(True)
|
||||
self.count_badge.setStyleSheet(ItemThumb.small_text_style)
|
||||
self.count_badge.setHidden(True)
|
||||
self.ext_badge.setHidden(True)
|
||||
elif mode == ItemType.COLLATION and self.mode != ItemType.COLLATION:
|
||||
self.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
self.thumb_button.setHidden(False)
|
||||
self.cb_container.setHidden(True)
|
||||
self.ext_badge.setHidden(True)
|
||||
self.count_badge.setStyleSheet(ItemThumb.med_text_style)
|
||||
self.count_badge.setHidden(False)
|
||||
self.item_type_badge.setHidden(False)
|
||||
elif mode == ItemType.TAG_GROUP and self.mode != ItemType.TAG_GROUP:
|
||||
self.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
self.thumb_button.setHidden(False)
|
||||
# self.cb_container.setHidden(True)
|
||||
self.ext_badge.setHidden(True)
|
||||
self.count_badge.setHidden(False)
|
||||
self.item_type_badge.setHidden(False)
|
||||
self.mode = mode
|
||||
# logging.info(f'Set Mode To: {self.mode}')
|
||||
self.set_mode(mode)
|
||||
|
||||
# def update_(self, thumb: QPixmap, size:QSize, ext:str, badges:list[QPixmap]) -> None:
|
||||
# """Updates the ItemThumb's visuals."""
|
||||
# if thumb:
|
||||
# pass
|
||||
def set_mode(self, mode: Optional[ItemType]) -> None:
|
||||
if mode is None:
|
||||
self.unsetCursor()
|
||||
self.thumb_button.setHidden(True)
|
||||
# self.check_badges.setHidden(True)
|
||||
# self.ext_badge.setHidden(True)
|
||||
# self.item_type_badge.setHidden(True)
|
||||
pass
|
||||
elif mode == ItemType.ENTRY and self.mode != ItemType.ENTRY:
|
||||
self.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
self.thumb_button.setHidden(False)
|
||||
self.cb_container.setHidden(False)
|
||||
# Count Badge depends on file extension (video length, word count)
|
||||
self.item_type_badge.setHidden(True)
|
||||
self.count_badge.setStyleSheet(ItemThumb.small_text_style)
|
||||
self.count_badge.setHidden(True)
|
||||
self.ext_badge.setHidden(True)
|
||||
elif mode == ItemType.COLLATION and self.mode != ItemType.COLLATION:
|
||||
self.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
self.thumb_button.setHidden(False)
|
||||
self.cb_container.setHidden(True)
|
||||
self.ext_badge.setHidden(True)
|
||||
self.count_badge.setStyleSheet(ItemThumb.med_text_style)
|
||||
self.count_badge.setHidden(False)
|
||||
self.item_type_badge.setHidden(False)
|
||||
elif mode == ItemType.TAG_GROUP and self.mode != ItemType.TAG_GROUP:
|
||||
self.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
self.thumb_button.setHidden(False)
|
||||
# self.cb_container.setHidden(True)
|
||||
self.ext_badge.setHidden(True)
|
||||
self.count_badge.setHidden(False)
|
||||
self.item_type_badge.setHidden(False)
|
||||
self.mode = mode
|
||||
# logging.info(f'Set Mode To: {self.mode}')
|
||||
|
||||
def set_extension(self, ext: str) -> None:
|
||||
if ext and ext not in IMAGE_TYPES or ext in ['gif', 'apng']:
|
||||
self.ext_badge.setHidden(False)
|
||||
self.ext_badge.setText(ext.upper())
|
||||
if ext in VIDEO_TYPES + AUDIO_TYPES:
|
||||
self.count_badge.setHidden(False)
|
||||
else:
|
||||
if self.mode == ItemType.ENTRY:
|
||||
self.ext_badge.setHidden(True)
|
||||
self.count_badge.setHidden(True)
|
||||
# def update_(self, thumb: QPixmap, size:QSize, ext:str, badges:list[QPixmap]) -> None:
|
||||
# """Updates the ItemThumb's visuals."""
|
||||
# if thumb:
|
||||
# pass
|
||||
|
||||
def set_count(self, count: str) -> None:
|
||||
if count:
|
||||
self.count_badge.setHidden(False)
|
||||
self.count_badge.setText(count)
|
||||
else:
|
||||
if self.mode == ItemType.ENTRY:
|
||||
self.ext_badge.setHidden(True)
|
||||
self.count_badge.setHidden(True)
|
||||
def set_extension(self, ext: str) -> None:
|
||||
if ext and ext not in IMAGE_TYPES or ext in ["gif", "apng"]:
|
||||
self.ext_badge.setHidden(False)
|
||||
self.ext_badge.setText(ext.upper())
|
||||
if ext in VIDEO_TYPES + AUDIO_TYPES:
|
||||
self.count_badge.setHidden(False)
|
||||
else:
|
||||
if self.mode == ItemType.ENTRY:
|
||||
self.ext_badge.setHidden(True)
|
||||
self.count_badge.setHidden(True)
|
||||
|
||||
def update_thumb(self, timestamp: float, image: QPixmap = None):
|
||||
"""Updates attributes of a thumbnail element."""
|
||||
# logging.info(f'[GUI] Updating Thumbnail for element {id(element)}: {id(image) if image else None}')
|
||||
if timestamp > ItemThumb.update_cutoff:
|
||||
self.thumb_button.setIcon(image if image else QPixmap())
|
||||
# element.repaint()
|
||||
def set_count(self, count: str) -> None:
|
||||
if count:
|
||||
self.count_badge.setHidden(False)
|
||||
self.count_badge.setText(count)
|
||||
else:
|
||||
if self.mode == ItemType.ENTRY:
|
||||
self.ext_badge.setHidden(True)
|
||||
self.count_badge.setHidden(True)
|
||||
|
||||
def update_size(self, timestamp: float, size: QSize):
|
||||
"""Updates attributes of a thumbnail element."""
|
||||
# logging.info(f'[GUI] Updating size for element {id(element)}: {size.__str__()}')
|
||||
if timestamp > ItemThumb.update_cutoff:
|
||||
if self.thumb_button.iconSize != size:
|
||||
self.thumb_button.setIconSize(size)
|
||||
self.thumb_button.setMinimumSize(size)
|
||||
self.thumb_button.setMaximumSize(size)
|
||||
def update_thumb(self, timestamp: float, image: QPixmap = None):
|
||||
"""Updates attributes of a thumbnail element."""
|
||||
# logging.info(f'[GUI] Updating Thumbnail for element {id(element)}: {id(image) if image else None}')
|
||||
if timestamp > ItemThumb.update_cutoff:
|
||||
self.thumb_button.setIcon(image if image else QPixmap())
|
||||
# element.repaint()
|
||||
|
||||
def update_clickable(self, clickable: FunctionType = None):
|
||||
"""Updates attributes of a thumbnail element."""
|
||||
# logging.info(f'[GUI] Updating Click Event for element {id(element)}: {id(clickable) if clickable else None}')
|
||||
try:
|
||||
self.thumb_button.clicked.disconnect()
|
||||
except RuntimeError:
|
||||
pass
|
||||
if clickable:
|
||||
self.thumb_button.clicked.connect(clickable)
|
||||
|
||||
def update_badges(self):
|
||||
if self.mode == ItemType.ENTRY:
|
||||
# logging.info(f'[UPDATE BADGES] ENTRY: {self.lib.get_entry(self.item_id)}')
|
||||
# logging.info(f'[UPDATE BADGES] ARCH: {self.lib.get_entry(self.item_id).has_tag(self.lib, 0)}, FAV: {self.lib.get_entry(self.item_id).has_tag(self.lib, 1)}')
|
||||
self.assign_archived(self.lib.get_entry(self.item_id).has_tag(self.lib, 0))
|
||||
self.assign_favorite(self.lib.get_entry(self.item_id).has_tag(self.lib, 1))
|
||||
def update_size(self, timestamp: float, size: QSize):
|
||||
"""Updates attributes of a thumbnail element."""
|
||||
# logging.info(f'[GUI] Updating size for element {id(element)}: {size.__str__()}')
|
||||
if timestamp > ItemThumb.update_cutoff:
|
||||
if self.thumb_button.iconSize != size:
|
||||
self.thumb_button.setIconSize(size)
|
||||
self.thumb_button.setMinimumSize(size)
|
||||
self.thumb_button.setMaximumSize(size)
|
||||
|
||||
def update_clickable(self, clickable: FunctionType = None):
|
||||
"""Updates attributes of a thumbnail element."""
|
||||
# logging.info(f'[GUI] Updating Click Event for element {id(element)}: {id(clickable) if clickable else None}')
|
||||
try:
|
||||
self.thumb_button.clicked.disconnect()
|
||||
except RuntimeError:
|
||||
pass
|
||||
if clickable:
|
||||
self.thumb_button.clicked.connect(clickable)
|
||||
|
||||
def set_item_id(self, id: int):
|
||||
'''
|
||||
also sets the filepath for the file opener
|
||||
'''
|
||||
self.item_id = id
|
||||
if(id == -1):
|
||||
return
|
||||
entry = self.lib.get_entry(self.item_id)
|
||||
filepath = os.path.normpath(f'{self.lib.library_dir}/{entry.path}/{entry.filename}')
|
||||
self.opener.set_filepath(filepath)
|
||||
def update_badges(self):
|
||||
if self.mode == ItemType.ENTRY:
|
||||
# logging.info(f'[UPDATE BADGES] ENTRY: {self.lib.get_entry(self.item_id)}')
|
||||
# logging.info(f'[UPDATE BADGES] ARCH: {self.lib.get_entry(self.item_id).has_tag(self.lib, 0)}, FAV: {self.lib.get_entry(self.item_id).has_tag(self.lib, 1)}')
|
||||
self.assign_archived(self.lib.get_entry(self.item_id).has_tag(self.lib, 0))
|
||||
self.assign_favorite(self.lib.get_entry(self.item_id).has_tag(self.lib, 1))
|
||||
|
||||
def assign_favorite(self, value: bool):
|
||||
# Switching mode to None to bypass mode-specific operations when the
|
||||
# checkbox's state changes.
|
||||
mode = self.mode
|
||||
self.mode = None
|
||||
self.isFavorite = value
|
||||
self.favorite_badge.setChecked(value)
|
||||
if not self.thumb_button.underMouse():
|
||||
self.favorite_badge.setHidden(not self.isFavorite)
|
||||
self.mode = mode
|
||||
def set_item_id(self, id: int):
|
||||
"""
|
||||
also sets the filepath for the file opener
|
||||
"""
|
||||
self.item_id = id
|
||||
if id == -1:
|
||||
return
|
||||
entry = self.lib.get_entry(self.item_id)
|
||||
filepath = os.path.normpath(
|
||||
f"{self.lib.library_dir}/{entry.path}/{entry.filename}"
|
||||
)
|
||||
self.opener.set_filepath(filepath)
|
||||
|
||||
def assign_archived(self, value: bool):
|
||||
# Switching mode to None to bypass mode-specific operations when the
|
||||
# checkbox's state changes.
|
||||
mode = self.mode
|
||||
self.mode = None
|
||||
self.isArchived = value
|
||||
self.archived_badge.setChecked(value)
|
||||
if not self.thumb_button.underMouse():
|
||||
self.archived_badge.setHidden(not self.isArchived)
|
||||
self.mode = mode
|
||||
def assign_favorite(self, value: bool):
|
||||
# Switching mode to None to bypass mode-specific operations when the
|
||||
# checkbox's state changes.
|
||||
mode = self.mode
|
||||
self.mode = None
|
||||
self.isFavorite = value
|
||||
self.favorite_badge.setChecked(value)
|
||||
if not self.thumb_button.underMouse():
|
||||
self.favorite_badge.setHidden(not self.isFavorite)
|
||||
self.mode = mode
|
||||
|
||||
def show_check_badges(self, show: bool):
|
||||
if self.mode != ItemType.TAG_GROUP:
|
||||
self.favorite_badge.setHidden(
|
||||
True if (not show and not self.isFavorite) else False)
|
||||
self.archived_badge.setHidden(
|
||||
True if (not show and not self.isArchived) else False)
|
||||
def assign_archived(self, value: bool):
|
||||
# Switching mode to None to bypass mode-specific operations when the
|
||||
# checkbox's state changes.
|
||||
mode = self.mode
|
||||
self.mode = None
|
||||
self.isArchived = value
|
||||
self.archived_badge.setChecked(value)
|
||||
if not self.thumb_button.underMouse():
|
||||
self.archived_badge.setHidden(not self.isArchived)
|
||||
self.mode = mode
|
||||
|
||||
def enterEvent(self, event: QEnterEvent) -> None:
|
||||
self.show_check_badges(True)
|
||||
return super().enterEvent(event)
|
||||
def show_check_badges(self, show: bool):
|
||||
if self.mode != ItemType.TAG_GROUP:
|
||||
self.favorite_badge.setHidden(
|
||||
True if (not show and not self.isFavorite) else False
|
||||
)
|
||||
self.archived_badge.setHidden(
|
||||
True if (not show and not self.isArchived) else False
|
||||
)
|
||||
|
||||
def leaveEvent(self, event: QEvent) -> None:
|
||||
self.show_check_badges(False)
|
||||
return super().leaveEvent(event)
|
||||
def enterEvent(self, event: QEnterEvent) -> None:
|
||||
self.show_check_badges(True)
|
||||
return super().enterEvent(event)
|
||||
|
||||
def on_archived_check(self, value: bool):
|
||||
# logging.info(f'Archived Check: {value}, Mode: {self.mode}')
|
||||
if self.mode == ItemType.ENTRY:
|
||||
self.isArchived = value
|
||||
DEFAULT_META_TAG_FIELD = 8
|
||||
temp = (ItemType.ENTRY,self.item_id)
|
||||
if list(self.panel.driver.selected).count(temp) > 0: # Is the archived badge apart of the selection?
|
||||
# Yes, then add archived tag to all selected.
|
||||
for x in self.panel.driver.selected:
|
||||
e = self.lib.get_entry(x[1])
|
||||
if value:
|
||||
self.archived_badge.setHidden(False)
|
||||
e.add_tag(self.panel.driver.lib, 0, field_id=DEFAULT_META_TAG_FIELD, field_index=-1)
|
||||
else:
|
||||
e.remove_tag(self.panel.driver.lib, 0)
|
||||
else:
|
||||
# No, then add archived tag to the entry this badge is on.
|
||||
e = self.lib.get_entry(self.item_id)
|
||||
if value:
|
||||
self.favorite_badge.setHidden(False)
|
||||
e.add_tag(self.panel.driver.lib, 0, field_id=DEFAULT_META_TAG_FIELD, field_index=-1)
|
||||
else:
|
||||
e.remove_tag(self.panel.driver.lib, 0)
|
||||
if self.panel.isOpen:
|
||||
self.panel.update_widgets()
|
||||
self.panel.driver.update_badges()
|
||||
def leaveEvent(self, event: QEvent) -> None:
|
||||
self.show_check_badges(False)
|
||||
return super().leaveEvent(event)
|
||||
|
||||
def on_archived_check(self, value: bool):
|
||||
# logging.info(f'Archived Check: {value}, Mode: {self.mode}')
|
||||
if self.mode == ItemType.ENTRY:
|
||||
self.isArchived = value
|
||||
DEFAULT_META_TAG_FIELD = 8
|
||||
temp = (ItemType.ENTRY, self.item_id)
|
||||
if (
|
||||
list(self.panel.driver.selected).count(temp) > 0
|
||||
): # Is the archived badge apart of the selection?
|
||||
# Yes, then add archived tag to all selected.
|
||||
for x in self.panel.driver.selected:
|
||||
e = self.lib.get_entry(x[1])
|
||||
if value:
|
||||
self.archived_badge.setHidden(False)
|
||||
e.add_tag(
|
||||
self.panel.driver.lib,
|
||||
0,
|
||||
field_id=DEFAULT_META_TAG_FIELD,
|
||||
field_index=-1,
|
||||
)
|
||||
else:
|
||||
e.remove_tag(self.panel.driver.lib, 0)
|
||||
else:
|
||||
# No, then add archived tag to the entry this badge is on.
|
||||
e = self.lib.get_entry(self.item_id)
|
||||
if value:
|
||||
self.favorite_badge.setHidden(False)
|
||||
e.add_tag(
|
||||
self.panel.driver.lib,
|
||||
0,
|
||||
field_id=DEFAULT_META_TAG_FIELD,
|
||||
field_index=-1,
|
||||
)
|
||||
else:
|
||||
e.remove_tag(self.panel.driver.lib, 0)
|
||||
if self.panel.isOpen:
|
||||
self.panel.update_widgets()
|
||||
self.panel.driver.update_badges()
|
||||
|
||||
# def on_archived_uncheck(self):
|
||||
# if self.mode == SearchItemType.ENTRY:
|
||||
# self.isArchived = False
|
||||
# e = self.lib.get_entry(self.item_id)
|
||||
# def on_archived_uncheck(self):
|
||||
# if self.mode == SearchItemType.ENTRY:
|
||||
# self.isArchived = False
|
||||
# e = self.lib.get_entry(self.item_id)
|
||||
|
||||
def on_favorite_check(self, value: bool):
|
||||
# logging.info(f'Favorite Check: {value}, Mode: {self.mode}')
|
||||
if self.mode == ItemType.ENTRY:
|
||||
self.isFavorite = value
|
||||
DEFAULT_META_TAG_FIELD = 8
|
||||
temp = (ItemType.ENTRY,self.item_id)
|
||||
if list(self.panel.driver.selected).count(temp) > 0: # Is the favorite badge apart of the selection?
|
||||
# Yes, then add favorite tag to all selected.
|
||||
for x in self.panel.driver.selected:
|
||||
e = self.lib.get_entry(x[1])
|
||||
if value:
|
||||
self.favorite_badge.setHidden(False)
|
||||
e.add_tag(self.panel.driver.lib, 1, field_id=DEFAULT_META_TAG_FIELD, field_index=-1)
|
||||
else:
|
||||
e.remove_tag(self.panel.driver.lib, 1)
|
||||
else:
|
||||
# No, then add favorite tag to the entry this badge is on.
|
||||
e = self.lib.get_entry(self.item_id)
|
||||
if value:
|
||||
self.favorite_badge.setHidden(False)
|
||||
e.add_tag(self.panel.driver.lib, 1, field_id=DEFAULT_META_TAG_FIELD, field_index=-1)
|
||||
else:
|
||||
e.remove_tag(self.panel.driver.lib, 1)
|
||||
if self.panel.isOpen:
|
||||
self.panel.update_widgets()
|
||||
self.panel.driver.update_badges()
|
||||
|
||||
def on_favorite_check(self, value: bool):
|
||||
# logging.info(f'Favorite Check: {value}, Mode: {self.mode}')
|
||||
if self.mode == ItemType.ENTRY:
|
||||
self.isFavorite = value
|
||||
DEFAULT_META_TAG_FIELD = 8
|
||||
temp = (ItemType.ENTRY, self.item_id)
|
||||
if (
|
||||
list(self.panel.driver.selected).count(temp) > 0
|
||||
): # Is the favorite badge apart of the selection?
|
||||
# Yes, then add favorite tag to all selected.
|
||||
for x in self.panel.driver.selected:
|
||||
e = self.lib.get_entry(x[1])
|
||||
if value:
|
||||
self.favorite_badge.setHidden(False)
|
||||
e.add_tag(
|
||||
self.panel.driver.lib,
|
||||
1,
|
||||
field_id=DEFAULT_META_TAG_FIELD,
|
||||
field_index=-1,
|
||||
)
|
||||
else:
|
||||
e.remove_tag(self.panel.driver.lib, 1)
|
||||
else:
|
||||
# No, then add favorite tag to the entry this badge is on.
|
||||
e = self.lib.get_entry(self.item_id)
|
||||
if value:
|
||||
self.favorite_badge.setHidden(False)
|
||||
e.add_tag(
|
||||
self.panel.driver.lib,
|
||||
1,
|
||||
field_id=DEFAULT_META_TAG_FIELD,
|
||||
field_index=-1,
|
||||
)
|
||||
else:
|
||||
e.remove_tag(self.panel.driver.lib, 1)
|
||||
if self.panel.isOpen:
|
||||
self.panel.update_widgets()
|
||||
self.panel.driver.update_badges()
|
||||
|
||||
# def on_favorite_uncheck(self):
|
||||
# if self.mode == SearchItemType.ENTRY:
|
||||
# self.isFavorite = False
|
||||
# e = self.lib.get_entry(self.item_id)
|
||||
# e.remove_tag(1)
|
||||
# def on_favorite_uncheck(self):
|
||||
# if self.mode == SearchItemType.ENTRY:
|
||||
# self.isFavorite = False
|
||||
# e = self.lib.get_entry(self.item_id)
|
||||
# e.remove_tag(1)
|
||||
|
|
|
@ -10,90 +10,100 @@ from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushBu
|
|||
|
||||
|
||||
class PanelModal(QWidget):
|
||||
saved = Signal()
|
||||
# TODO: Separate callbacks from the buttons you want, and just generally
|
||||
# figure out what you want from this.
|
||||
def __init__(self, widget:'PanelWidget', title:str, window_title:str,
|
||||
done_callback:FunctionType=None,
|
||||
# cancel_callback:FunctionType=None,
|
||||
save_callback:FunctionType=None,has_save:bool=False):
|
||||
# [Done]
|
||||
# - OR -
|
||||
# [Cancel] [Save]
|
||||
super().__init__()
|
||||
self.widget = widget
|
||||
self.setWindowTitle(window_title)
|
||||
self.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||
self.root_layout = QVBoxLayout(self)
|
||||
self.root_layout.setContentsMargins(6,0,6,6)
|
||||
saved = Signal()
|
||||
|
||||
self.title_widget = QLabel()
|
||||
self.title_widget.setObjectName('fieldTitle')
|
||||
self.title_widget.setWordWrap(True)
|
||||
self.title_widget.setStyleSheet(
|
||||
# 'background:blue;'
|
||||
# 'text-align:center;'
|
||||
'font-weight:bold;'
|
||||
'font-size:14px;'
|
||||
'padding-top: 6px'
|
||||
'')
|
||||
self.title_widget.setText(title)
|
||||
self.title_widget.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
# TODO: Separate callbacks from the buttons you want, and just generally
|
||||
# figure out what you want from this.
|
||||
def __init__(
|
||||
self,
|
||||
widget: "PanelWidget",
|
||||
title: str,
|
||||
window_title: str,
|
||||
done_callback: FunctionType = None,
|
||||
# cancel_callback:FunctionType=None,
|
||||
save_callback: FunctionType = None,
|
||||
has_save: bool = False,
|
||||
):
|
||||
# [Done]
|
||||
# - OR -
|
||||
# [Cancel] [Save]
|
||||
super().__init__()
|
||||
self.widget = widget
|
||||
self.setWindowTitle(window_title)
|
||||
self.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||
self.root_layout = QVBoxLayout(self)
|
||||
self.root_layout.setContentsMargins(6, 0, 6, 6)
|
||||
|
||||
self.button_container = QWidget()
|
||||
self.button_layout = QHBoxLayout(self.button_container)
|
||||
self.button_layout.setContentsMargins(6,6,6,6)
|
||||
self.button_layout.addStretch(1)
|
||||
self.title_widget = QLabel()
|
||||
self.title_widget.setObjectName("fieldTitle")
|
||||
self.title_widget.setWordWrap(True)
|
||||
self.title_widget.setStyleSheet(
|
||||
# 'background:blue;'
|
||||
# 'text-align:center;'
|
||||
"font-weight:bold;" "font-size:14px;" "padding-top: 6px" ""
|
||||
)
|
||||
self.title_widget.setText(title)
|
||||
self.title_widget.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
# self.cancel_button = QPushButton()
|
||||
# self.cancel_button.setText('Cancel')
|
||||
self.button_container = QWidget()
|
||||
self.button_layout = QHBoxLayout(self.button_container)
|
||||
self.button_layout.setContentsMargins(6, 6, 6, 6)
|
||||
self.button_layout.addStretch(1)
|
||||
|
||||
if not (save_callback or has_save):
|
||||
self.done_button = QPushButton()
|
||||
self.done_button.setText('Done')
|
||||
self.done_button.setAutoDefault(True)
|
||||
self.done_button.clicked.connect(self.hide)
|
||||
if done_callback:
|
||||
self.done_button.clicked.connect(done_callback)
|
||||
self.button_layout.addWidget(self.done_button)
|
||||
# self.cancel_button = QPushButton()
|
||||
# self.cancel_button.setText('Cancel')
|
||||
|
||||
if (save_callback or has_save):
|
||||
self.cancel_button = QPushButton()
|
||||
self.cancel_button.setText('Cancel')
|
||||
self.cancel_button.clicked.connect(self.hide)
|
||||
self.cancel_button.clicked.connect(widget.reset)
|
||||
# self.cancel_button.clicked.connect(cancel_callback)
|
||||
self.button_layout.addWidget(self.cancel_button)
|
||||
|
||||
if (save_callback or has_save):
|
||||
self.save_button = QPushButton()
|
||||
self.save_button.setText('Save')
|
||||
self.save_button.setAutoDefault(True)
|
||||
self.save_button.clicked.connect(self.hide)
|
||||
self.save_button.clicked.connect(self.saved.emit)
|
||||
if done_callback:
|
||||
self.save_button.clicked.connect(done_callback)
|
||||
if save_callback:
|
||||
self.save_button.clicked.connect(lambda: save_callback(widget.get_content()))
|
||||
self.button_layout.addWidget(self.save_button)
|
||||
|
||||
widget.done.connect(lambda: save_callback(widget.get_content()))
|
||||
|
||||
self.root_layout.addWidget(self.title_widget)
|
||||
self.root_layout.addWidget(widget)
|
||||
self.root_layout.setStretch(1,2)
|
||||
self.root_layout.addWidget(self.button_container)
|
||||
if not (save_callback or has_save):
|
||||
self.done_button = QPushButton()
|
||||
self.done_button.setText("Done")
|
||||
self.done_button.setAutoDefault(True)
|
||||
self.done_button.clicked.connect(self.hide)
|
||||
if done_callback:
|
||||
self.done_button.clicked.connect(done_callback)
|
||||
self.button_layout.addWidget(self.done_button)
|
||||
|
||||
if save_callback or has_save:
|
||||
self.cancel_button = QPushButton()
|
||||
self.cancel_button.setText("Cancel")
|
||||
self.cancel_button.clicked.connect(self.hide)
|
||||
self.cancel_button.clicked.connect(widget.reset)
|
||||
# self.cancel_button.clicked.connect(cancel_callback)
|
||||
self.button_layout.addWidget(self.cancel_button)
|
||||
|
||||
if save_callback or has_save:
|
||||
self.save_button = QPushButton()
|
||||
self.save_button.setText("Save")
|
||||
self.save_button.setAutoDefault(True)
|
||||
self.save_button.clicked.connect(self.hide)
|
||||
self.save_button.clicked.connect(self.saved.emit)
|
||||
if done_callback:
|
||||
self.save_button.clicked.connect(done_callback)
|
||||
if save_callback:
|
||||
self.save_button.clicked.connect(
|
||||
lambda: save_callback(widget.get_content())
|
||||
)
|
||||
self.button_layout.addWidget(self.save_button)
|
||||
|
||||
widget.done.connect(lambda: save_callback(widget.get_content()))
|
||||
|
||||
self.root_layout.addWidget(self.title_widget)
|
||||
self.root_layout.addWidget(widget)
|
||||
self.root_layout.setStretch(1, 2)
|
||||
self.root_layout.addWidget(self.button_container)
|
||||
|
||||
|
||||
class PanelWidget(QWidget):
|
||||
"""
|
||||
Used for widgets that go in a modal panel, ex. for editing or searching.
|
||||
"""
|
||||
done = Signal()
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
def get_content(self)-> str:
|
||||
pass
|
||||
def reset(self):
|
||||
pass
|
||||
"""
|
||||
Used for widgets that go in a modal panel, ex. for editing or searching.
|
||||
"""
|
||||
|
||||
done = Signal()
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def get_content(self) -> str:
|
||||
pass
|
||||
|
||||
def reset(self):
|
||||
pass
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -10,24 +10,34 @@ from PySide6.QtWidgets import QWidget, QVBoxLayout, QProgressDialog
|
|||
|
||||
|
||||
class ProgressWidget(QWidget):
|
||||
"""Prebuilt thread-safe progress bar widget."""
|
||||
def __init__(self, window_title:str, label_text:str, cancel_button_text:Optional[str], minimum:int, maximum:int):
|
||||
super().__init__()
|
||||
self.root = QVBoxLayout(self)
|
||||
self.pb = QProgressDialog(
|
||||
labelText=label_text,
|
||||
minimum=minimum,
|
||||
cancelButtonText=cancel_button_text,
|
||||
maximum=maximum
|
||||
)
|
||||
self.root.addWidget(self.pb)
|
||||
self.setFixedSize(432, 112)
|
||||
self.setWindowFlags(self.pb.windowFlags() & ~Qt.WindowType.WindowCloseButtonHint)
|
||||
self.setWindowTitle(window_title)
|
||||
self.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||
|
||||
def update_label(self, text:str):
|
||||
self.pb.setLabelText(text)
|
||||
"""Prebuilt thread-safe progress bar widget."""
|
||||
|
||||
def update_progress(self, value:int):
|
||||
self.pb.setValue(value)
|
||||
def __init__(
|
||||
self,
|
||||
window_title: str,
|
||||
label_text: str,
|
||||
cancel_button_text: Optional[str],
|
||||
minimum: int,
|
||||
maximum: int,
|
||||
):
|
||||
super().__init__()
|
||||
self.root = QVBoxLayout(self)
|
||||
self.pb = QProgressDialog(
|
||||
labelText=label_text,
|
||||
minimum=minimum,
|
||||
cancelButtonText=cancel_button_text,
|
||||
maximum=maximum,
|
||||
)
|
||||
self.root.addWidget(self.pb)
|
||||
self.setFixedSize(432, 112)
|
||||
self.setWindowFlags(
|
||||
self.pb.windowFlags() & ~Qt.WindowType.WindowCloseButtonHint
|
||||
)
|
||||
self.setWindowTitle(window_title)
|
||||
self.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||
|
||||
def update_label(self, text: str):
|
||||
self.pb.setLabelText(text)
|
||||
|
||||
def update_progress(self, value: int):
|
||||
self.pb.setValue(value)
|
||||
|
|
|
@ -17,223 +17,236 @@ from src.core.library import Library, Tag
|
|||
from src.core.palette import ColorType, get_tag_color
|
||||
|
||||
|
||||
ERROR = f'[ERROR]'
|
||||
WARNING = f'[WARNING]'
|
||||
INFO = f'[INFO]'
|
||||
ERROR = f"[ERROR]"
|
||||
WARNING = f"[WARNING]"
|
||||
INFO = f"[INFO]"
|
||||
|
||||
|
||||
class TagWidget(QWidget):
|
||||
edit_icon_128: Image.Image = Image.open(os.path.normpath(
|
||||
f'{Path(__file__).parent.parent.parent.parent}/resources/qt/images/edit_icon_128.png')).resize((math.floor(14*1.25),math.floor(14*1.25)))
|
||||
edit_icon_128.load()
|
||||
on_remove = Signal()
|
||||
on_click = Signal()
|
||||
on_edit = Signal()
|
||||
edit_icon_128: Image.Image = Image.open(
|
||||
os.path.normpath(
|
||||
f"{Path(__file__).parent.parent.parent.parent}/resources/qt/images/edit_icon_128.png"
|
||||
)
|
||||
).resize((math.floor(14 * 1.25), math.floor(14 * 1.25)))
|
||||
edit_icon_128.load()
|
||||
on_remove = Signal()
|
||||
on_click = Signal()
|
||||
on_edit = Signal()
|
||||
|
||||
def __init__(self, library:Library, tag:Tag, has_edit:bool, has_remove:bool, on_remove_callback:FunctionType=None, on_click_callback:FunctionType=None, on_edit_callback:FunctionType=None) -> None:
|
||||
super().__init__()
|
||||
self.lib = library
|
||||
self.tag = tag
|
||||
self.has_edit:bool = has_edit
|
||||
self.has_remove:bool = has_remove
|
||||
# self.bg_label = QLabel()
|
||||
# self.setStyleSheet('background-color:blue;')
|
||||
|
||||
# if on_click_callback:
|
||||
self.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
self.base_layout = QVBoxLayout(self)
|
||||
self.base_layout.setObjectName('baseLayout')
|
||||
self.base_layout.setContentsMargins(0, 0, 0, 0)
|
||||
def __init__(
|
||||
self,
|
||||
library: Library,
|
||||
tag: Tag,
|
||||
has_edit: bool,
|
||||
has_remove: bool,
|
||||
on_remove_callback: FunctionType = None,
|
||||
on_click_callback: FunctionType = None,
|
||||
on_edit_callback: FunctionType = None,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.lib = library
|
||||
self.tag = tag
|
||||
self.has_edit: bool = has_edit
|
||||
self.has_remove: bool = has_remove
|
||||
# self.bg_label = QLabel()
|
||||
# self.setStyleSheet('background-color:blue;')
|
||||
|
||||
self.bg_button = QPushButton(self)
|
||||
self.bg_button.setFlat(True)
|
||||
self.bg_button.setText(tag.display_name(self.lib).replace('&', '&&'))
|
||||
if has_edit:
|
||||
edit_action = QAction('Edit', self)
|
||||
edit_action.triggered.connect(on_edit_callback)
|
||||
edit_action.triggered.connect(self.on_edit.emit)
|
||||
self.bg_button.addAction(edit_action)
|
||||
# if on_click_callback:
|
||||
self.bg_button.setContextMenuPolicy(Qt.ContextMenuPolicy.ActionsContextMenu)
|
||||
# if has_remove:
|
||||
# remove_action = QAction('Remove', self)
|
||||
# # remove_action.triggered.connect(on_remove_callback)
|
||||
# remove_action.triggered.connect(self.on_remove.emit())
|
||||
# self.bg_button.addAction(remove_action)
|
||||
search_for_tag_action = QAction('Search for Tag', self)
|
||||
# search_for_tag_action.triggered.connect(on_click_callback)
|
||||
search_for_tag_action.triggered.connect(self.on_click.emit)
|
||||
self.bg_button.addAction(search_for_tag_action)
|
||||
add_to_search_action = QAction('Add to Search', self)
|
||||
self.bg_button.addAction(add_to_search_action)
|
||||
# if on_click_callback:
|
||||
self.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
self.base_layout = QVBoxLayout(self)
|
||||
self.base_layout.setObjectName("baseLayout")
|
||||
self.base_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
self.inner_layout = QHBoxLayout()
|
||||
self.inner_layout.setObjectName('innerLayout')
|
||||
self.inner_layout.setContentsMargins(2, 2, 2, 2)
|
||||
# self.inner_layout.setAlignment(Qt.AlignmentFlag.AlignRight)
|
||||
|
||||
# self.inner_container = QWidget()
|
||||
# self.inner_container.setLayout(self.inner_layout)
|
||||
# self.base_layout.addWidget(self.inner_container)
|
||||
self.bg_button.setLayout(self.inner_layout)
|
||||
self.bg_button.setMinimumSize(math.ceil(22*1.5), 22)
|
||||
self.bg_button = QPushButton(self)
|
||||
self.bg_button.setFlat(True)
|
||||
self.bg_button.setText(tag.display_name(self.lib).replace("&", "&&"))
|
||||
if has_edit:
|
||||
edit_action = QAction("Edit", self)
|
||||
edit_action.triggered.connect(on_edit_callback)
|
||||
edit_action.triggered.connect(self.on_edit.emit)
|
||||
self.bg_button.addAction(edit_action)
|
||||
# if on_click_callback:
|
||||
self.bg_button.setContextMenuPolicy(Qt.ContextMenuPolicy.ActionsContextMenu)
|
||||
# if has_remove:
|
||||
# remove_action = QAction('Remove', self)
|
||||
# # remove_action.triggered.connect(on_remove_callback)
|
||||
# remove_action.triggered.connect(self.on_remove.emit())
|
||||
# self.bg_button.addAction(remove_action)
|
||||
search_for_tag_action = QAction("Search for Tag", self)
|
||||
# search_for_tag_action.triggered.connect(on_click_callback)
|
||||
search_for_tag_action.triggered.connect(self.on_click.emit)
|
||||
self.bg_button.addAction(search_for_tag_action)
|
||||
add_to_search_action = QAction("Add to Search", self)
|
||||
self.bg_button.addAction(add_to_search_action)
|
||||
|
||||
# self.bg_button.setStyleSheet(
|
||||
# f'QPushButton {{'
|
||||
# f'border: 2px solid #8f8f91;'
|
||||
# f'border-radius: 6px;'
|
||||
# f'background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,stop: 0 {ColorType.PRIMARY}, stop: 1 {ColorType.BORDER});'
|
||||
# f'min-width: 80px;}}')
|
||||
self.inner_layout = QHBoxLayout()
|
||||
self.inner_layout.setObjectName("innerLayout")
|
||||
self.inner_layout.setContentsMargins(2, 2, 2, 2)
|
||||
# self.inner_layout.setAlignment(Qt.AlignmentFlag.AlignRight)
|
||||
|
||||
self.bg_button.setStyleSheet(
|
||||
# f'background: {get_tag_color(ColorType.PRIMARY, tag.color)};'
|
||||
f'QPushButton{{'
|
||||
f'background: {get_tag_color(ColorType.PRIMARY, tag.color)};'
|
||||
# f'background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 {get_tag_color(ColorType.PRIMARY, tag.color)}, stop:1.0 {get_tag_color(ColorType.BORDER, tag.color)});'
|
||||
# f"border-color:{get_tag_color(ColorType.PRIMARY, tag.color)};"
|
||||
f"color: {get_tag_color(ColorType.TEXT, tag.color)};"
|
||||
f'font-weight: 600;'
|
||||
f"border-color:{get_tag_color(ColorType.BORDER, tag.color)};"
|
||||
f'border-radius: 6px;'
|
||||
f'border-style:solid;'
|
||||
f'border-width: {math.ceil(1*self.devicePixelRatio())}px;'
|
||||
# f'border-top:2px solid {get_tag_color(ColorType.LIGHT_ACCENT, tag.color)};'
|
||||
# f'border-bottom:2px solid {get_tag_color(ColorType.BORDER, tag.color)};'
|
||||
# f'border-left:2px solid {get_tag_color(ColorType.BORDER, tag.color)};'
|
||||
# f'border-right:2px solid {get_tag_color(ColorType.BORDER, tag.color)};'
|
||||
# f'padding-top: 0.5px;'
|
||||
f'padding-right: 4px;'
|
||||
f'padding-bottom: 1px;'
|
||||
f'padding-left: 4px;'
|
||||
f'font-size: 13px'
|
||||
f'}}'
|
||||
f'QPushButton::hover{{'
|
||||
# f'background: {get_tag_color(ColorType.LIGHT_ACCENT, tag.color)};'
|
||||
# f'background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 {get_tag_color(ColorType.PRIMARY, tag.color)}, stop:1.0 {get_tag_color(ColorType.BORDER, tag.color)});'
|
||||
# f"border-color:{get_tag_color(ColorType.PRIMARY, tag.color)};"
|
||||
# f"color: {get_tag_color(ColorType.TEXT, tag.color)};"
|
||||
f"border-color:{get_tag_color(ColorType.LIGHT_ACCENT, tag.color)};"
|
||||
f'}}')
|
||||
# self.inner_container = QWidget()
|
||||
# self.inner_container.setLayout(self.inner_layout)
|
||||
# self.base_layout.addWidget(self.inner_container)
|
||||
self.bg_button.setLayout(self.inner_layout)
|
||||
self.bg_button.setMinimumSize(math.ceil(22 * 1.5), 22)
|
||||
|
||||
# self.renderer = ThumbRenderer()
|
||||
# self.renderer.updated.connect(lambda ts, i, s, ext: (self.update_thumb(ts, image=i),
|
||||
# self.update_size(
|
||||
# ts, size=s),
|
||||
# self.set_extension(ext)))
|
||||
|
||||
# self.bg_button.setLayout(self.base_layout)
|
||||
|
||||
self.base_layout.addWidget(self.bg_button)
|
||||
# self.setMinimumSize(self.bg_button.size())
|
||||
# self.bg_button.setStyleSheet(
|
||||
# f'QPushButton {{'
|
||||
# f'border: 2px solid #8f8f91;'
|
||||
# f'border-radius: 6px;'
|
||||
# f'background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,stop: 0 {ColorType.PRIMARY}, stop: 1 {ColorType.BORDER});'
|
||||
# f'min-width: 80px;}}')
|
||||
|
||||
# logging.info(tag.color)
|
||||
if has_remove:
|
||||
self.remove_button = QPushButton(self)
|
||||
self.remove_button.setFlat(True)
|
||||
self.remove_button.setText('–')
|
||||
self.remove_button.setHidden(True)
|
||||
self.remove_button.setStyleSheet(f'color: {get_tag_color(ColorType.PRIMARY, tag.color)};'
|
||||
f"background: {get_tag_color(ColorType.TEXT, tag.color)};"
|
||||
# f"color: {'black' if color not in ['black', 'gray', 'dark gray', 'cool gray', 'warm gray', 'blue', 'purple', 'violet'] else 'white'};"
|
||||
# f"border-color: {get_tag_color(ColorType.BORDER, tag.color)};"
|
||||
f'font-weight: 800;'
|
||||
# f"border-color:{'black' if color not in [
|
||||
# 'black', 'gray', 'dark gray',
|
||||
# 'cool gray', 'warm gray', 'blue',
|
||||
# 'purple', 'violet'] else 'white'};"
|
||||
f'border-radius: 4px;'
|
||||
# f'border-style:solid;'
|
||||
f'border-width:0;'
|
||||
# f'padding-top: 1.5px;'
|
||||
# f'padding-right: 4px;'
|
||||
f'padding-bottom: 4px;'
|
||||
# f'padding-left: 4px;'
|
||||
f'font-size: 14px')
|
||||
self.remove_button.setMinimumSize(19,19)
|
||||
self.remove_button.setMaximumSize(19,19)
|
||||
# self.remove_button.clicked.connect(on_remove_callback)
|
||||
self.remove_button.clicked.connect(self.on_remove.emit)
|
||||
self.bg_button.setStyleSheet(
|
||||
# f'background: {get_tag_color(ColorType.PRIMARY, tag.color)};'
|
||||
f"QPushButton{{"
|
||||
f"background: {get_tag_color(ColorType.PRIMARY, tag.color)};"
|
||||
# f'background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 {get_tag_color(ColorType.PRIMARY, tag.color)}, stop:1.0 {get_tag_color(ColorType.BORDER, tag.color)});'
|
||||
# f"border-color:{get_tag_color(ColorType.PRIMARY, tag.color)};"
|
||||
f"color: {get_tag_color(ColorType.TEXT, tag.color)};"
|
||||
f"font-weight: 600;"
|
||||
f"border-color:{get_tag_color(ColorType.BORDER, tag.color)};"
|
||||
f"border-radius: 6px;"
|
||||
f"border-style:solid;"
|
||||
f"border-width: {math.ceil(1*self.devicePixelRatio())}px;"
|
||||
# f'border-top:2px solid {get_tag_color(ColorType.LIGHT_ACCENT, tag.color)};'
|
||||
# f'border-bottom:2px solid {get_tag_color(ColorType.BORDER, tag.color)};'
|
||||
# f'border-left:2px solid {get_tag_color(ColorType.BORDER, tag.color)};'
|
||||
# f'border-right:2px solid {get_tag_color(ColorType.BORDER, tag.color)};'
|
||||
# f'padding-top: 0.5px;'
|
||||
f"padding-right: 4px;"
|
||||
f"padding-bottom: 1px;"
|
||||
f"padding-left: 4px;"
|
||||
f"font-size: 13px"
|
||||
f"}}"
|
||||
f"QPushButton::hover{{"
|
||||
# f'background: {get_tag_color(ColorType.LIGHT_ACCENT, tag.color)};'
|
||||
# f'background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 {get_tag_color(ColorType.PRIMARY, tag.color)}, stop:1.0 {get_tag_color(ColorType.BORDER, tag.color)});'
|
||||
# f"border-color:{get_tag_color(ColorType.PRIMARY, tag.color)};"
|
||||
# f"color: {get_tag_color(ColorType.TEXT, tag.color)};"
|
||||
f"border-color:{get_tag_color(ColorType.LIGHT_ACCENT, tag.color)};"
|
||||
f"}}"
|
||||
)
|
||||
|
||||
# NOTE: No more edit button! Just make it a right-click option.
|
||||
# self.edit_button = QPushButton(self)
|
||||
# self.edit_button.setFlat(True)
|
||||
# self.edit_button.setText('Edit')
|
||||
# self.edit_button.setIcon(QPixmap.fromImage(ImageQt.ImageQt(self.edit_icon_128)))
|
||||
# self.edit_button.setIconSize(QSize(14,14))
|
||||
# self.edit_button.setHidden(True)
|
||||
# self.edit_button.setStyleSheet(f'color: {color};'
|
||||
# f"background: {'black' if color not in ['black', 'gray', 'dark gray', 'cool gray', 'warm gray', 'blue', 'purple', 'violet'] else 'white'};"
|
||||
# # f"color: {'black' if color not in ['black', 'gray', 'dark gray', 'cool gray', 'warm gray', 'blue', 'purple', 'violet'] else 'white'};"
|
||||
# f"border-color: {'black' if color not in ['black', 'gray', 'dark gray', 'cool gray', 'warm gray', 'blue', 'purple', 'violet'] else 'white'};"
|
||||
# f'font-weight: 600;'
|
||||
# # f"border-color:{'black' if color not in [
|
||||
# # 'black', 'gray', 'dark gray',
|
||||
# # 'cool gray', 'warm gray', 'blue',
|
||||
# # 'purple', 'violet'] else 'white'};"
|
||||
# # f'QPushButton{{border-image: url(:/images/edit_icon_128.png);}}'
|
||||
# # f'QPushButton{{border-image: url(:/images/edit_icon_128.png);}}'
|
||||
# f'border-radius: 4px;'
|
||||
# # f'border-style:solid;'
|
||||
# # f'border-width:1px;'
|
||||
# f'padding-top: 1.5px;'
|
||||
# f'padding-right: 4px;'
|
||||
# f'padding-bottom: 3px;'
|
||||
# f'padding-left: 4px;'
|
||||
# f'font-size: 14px')
|
||||
# self.edit_button.setMinimumSize(18,18)
|
||||
# # self.edit_button.setMaximumSize(18,18)
|
||||
# self.renderer = ThumbRenderer()
|
||||
# self.renderer.updated.connect(lambda ts, i, s, ext: (self.update_thumb(ts, image=i),
|
||||
# self.update_size(
|
||||
# ts, size=s),
|
||||
# self.set_extension(ext)))
|
||||
|
||||
# self.bg_button.setLayout(self.base_layout)
|
||||
|
||||
# self.inner_layout.addWidget(self.edit_button)
|
||||
if has_remove:
|
||||
self.inner_layout.addWidget(self.remove_button)
|
||||
self.inner_layout.addStretch(1)
|
||||
|
||||
self.base_layout.addWidget(self.bg_button)
|
||||
# self.setMinimumSize(self.bg_button.size())
|
||||
|
||||
# NOTE: Do this if you don't want the tag to stretch, like in a search.
|
||||
# self.bg_button.setMaximumWidth(self.bg_button.sizeHint().width())
|
||||
|
||||
# self.set_click(on_click_callback)
|
||||
self.bg_button.clicked.connect(self.on_click.emit)
|
||||
# logging.info(tag.color)
|
||||
if has_remove:
|
||||
self.remove_button = QPushButton(self)
|
||||
self.remove_button.setFlat(True)
|
||||
self.remove_button.setText("–")
|
||||
self.remove_button.setHidden(True)
|
||||
self.remove_button.setStyleSheet(
|
||||
f"color: {get_tag_color(ColorType.PRIMARY, tag.color)};"
|
||||
f"background: {get_tag_color(ColorType.TEXT, tag.color)};"
|
||||
# f"color: {'black' if color not in ['black', 'gray', 'dark gray', 'cool gray', 'warm gray', 'blue', 'purple', 'violet'] else 'white'};"
|
||||
# f"border-color: {get_tag_color(ColorType.BORDER, tag.color)};"
|
||||
f"font-weight: 800;"
|
||||
# f"border-color:{'black' if color not in [
|
||||
# 'black', 'gray', 'dark gray',
|
||||
# 'cool gray', 'warm gray', 'blue',
|
||||
# 'purple', 'violet'] else 'white'};"
|
||||
f"border-radius: 4px;"
|
||||
# f'border-style:solid;'
|
||||
f"border-width:0;"
|
||||
# f'padding-top: 1.5px;'
|
||||
# f'padding-right: 4px;'
|
||||
f"padding-bottom: 4px;"
|
||||
# f'padding-left: 4px;'
|
||||
f"font-size: 14px"
|
||||
)
|
||||
self.remove_button.setMinimumSize(19, 19)
|
||||
self.remove_button.setMaximumSize(19, 19)
|
||||
# self.remove_button.clicked.connect(on_remove_callback)
|
||||
self.remove_button.clicked.connect(self.on_remove.emit)
|
||||
|
||||
# self.setMinimumSize(50,20)
|
||||
# NOTE: No more edit button! Just make it a right-click option.
|
||||
# self.edit_button = QPushButton(self)
|
||||
# self.edit_button.setFlat(True)
|
||||
# self.edit_button.setText('Edit')
|
||||
# self.edit_button.setIcon(QPixmap.fromImage(ImageQt.ImageQt(self.edit_icon_128)))
|
||||
# self.edit_button.setIconSize(QSize(14,14))
|
||||
# self.edit_button.setHidden(True)
|
||||
# self.edit_button.setStyleSheet(f'color: {color};'
|
||||
# f"background: {'black' if color not in ['black', 'gray', 'dark gray', 'cool gray', 'warm gray', 'blue', 'purple', 'violet'] else 'white'};"
|
||||
# # f"color: {'black' if color not in ['black', 'gray', 'dark gray', 'cool gray', 'warm gray', 'blue', 'purple', 'violet'] else 'white'};"
|
||||
# f"border-color: {'black' if color not in ['black', 'gray', 'dark gray', 'cool gray', 'warm gray', 'blue', 'purple', 'violet'] else 'white'};"
|
||||
# f'font-weight: 600;'
|
||||
# # f"border-color:{'black' if color not in [
|
||||
# # 'black', 'gray', 'dark gray',
|
||||
# # 'cool gray', 'warm gray', 'blue',
|
||||
# # 'purple', 'violet'] else 'white'};"
|
||||
# # f'QPushButton{{border-image: url(:/images/edit_icon_128.png);}}'
|
||||
# # f'QPushButton{{border-image: url(:/images/edit_icon_128.png);}}'
|
||||
# f'border-radius: 4px;'
|
||||
# # f'border-style:solid;'
|
||||
# # f'border-width:1px;'
|
||||
# f'padding-top: 1.5px;'
|
||||
# f'padding-right: 4px;'
|
||||
# f'padding-bottom: 3px;'
|
||||
# f'padding-left: 4px;'
|
||||
# f'font-size: 14px')
|
||||
# self.edit_button.setMinimumSize(18,18)
|
||||
# # self.edit_button.setMaximumSize(18,18)
|
||||
|
||||
# def set_name(self, name:str):
|
||||
# self.bg_label.setText(str)
|
||||
# self.inner_layout.addWidget(self.edit_button)
|
||||
if has_remove:
|
||||
self.inner_layout.addWidget(self.remove_button)
|
||||
self.inner_layout.addStretch(1)
|
||||
|
||||
# def on_remove(self):
|
||||
# if self.item and self.item[0] == ItemType.ENTRY:
|
||||
# if self.field_index >= 0:
|
||||
# self.lib.get_entry(self.item[1]).remove_tag(self.tag.id, self.field_index)
|
||||
# else:
|
||||
# self.lib.get_entry(self.item[1]).remove_tag(self.tag.id)
|
||||
# NOTE: Do this if you don't want the tag to stretch, like in a search.
|
||||
# self.bg_button.setMaximumWidth(self.bg_button.sizeHint().width())
|
||||
|
||||
# def set_click(self, callback):
|
||||
# try:
|
||||
# self.bg_button.clicked.disconnect()
|
||||
# except RuntimeError:
|
||||
# pass
|
||||
# if callback:
|
||||
# self.bg_button.clicked.connect(callback)
|
||||
# self.set_click(on_click_callback)
|
||||
self.bg_button.clicked.connect(self.on_click.emit)
|
||||
|
||||
# def set_click(self, function):
|
||||
# try:
|
||||
# self.bg.clicked.disconnect()
|
||||
# except RuntimeError:
|
||||
# pass
|
||||
# # self.bg.clicked.connect(lambda checked=False, filepath=filepath: open_file(filepath))
|
||||
# # self.bg.clicked.connect(function)
|
||||
|
||||
def enterEvent(self, event: QEnterEvent) -> None:
|
||||
if self.has_remove:
|
||||
self.remove_button.setHidden(False)
|
||||
# self.edit_button.setHidden(False)
|
||||
self.update()
|
||||
return super().enterEvent(event)
|
||||
# self.setMinimumSize(50,20)
|
||||
|
||||
def leaveEvent(self, event: QEvent) -> None:
|
||||
if self.has_remove:
|
||||
self.remove_button.setHidden(True)
|
||||
# self.edit_button.setHidden(True)
|
||||
self.update()
|
||||
return super().leaveEvent(event)
|
||||
# def set_name(self, name:str):
|
||||
# self.bg_label.setText(str)
|
||||
|
||||
# def on_remove(self):
|
||||
# if self.item and self.item[0] == ItemType.ENTRY:
|
||||
# if self.field_index >= 0:
|
||||
# self.lib.get_entry(self.item[1]).remove_tag(self.tag.id, self.field_index)
|
||||
# else:
|
||||
# self.lib.get_entry(self.item[1]).remove_tag(self.tag.id)
|
||||
|
||||
# def set_click(self, callback):
|
||||
# try:
|
||||
# self.bg_button.clicked.disconnect()
|
||||
# except RuntimeError:
|
||||
# pass
|
||||
# if callback:
|
||||
# self.bg_button.clicked.connect(callback)
|
||||
|
||||
# def set_click(self, function):
|
||||
# try:
|
||||
# self.bg.clicked.disconnect()
|
||||
# except RuntimeError:
|
||||
# pass
|
||||
# # self.bg.clicked.connect(lambda checked=False, filepath=filepath: open_file(filepath))
|
||||
# # self.bg.clicked.connect(function)
|
||||
|
||||
def enterEvent(self, event: QEnterEvent) -> None:
|
||||
if self.has_remove:
|
||||
self.remove_button.setHidden(False)
|
||||
# self.edit_button.setHidden(False)
|
||||
self.update()
|
||||
return super().enterEvent(event)
|
||||
|
||||
def leaveEvent(self, event: QEvent) -> None:
|
||||
if self.has_remove:
|
||||
self.remove_button.setHidden(True)
|
||||
# self.edit_button.setHidden(True)
|
||||
self.update()
|
||||
return super().leaveEvent(event)
|
||||
|
|
|
@ -17,145 +17,168 @@ from src.qt.modals import BuildTagPanel, TagSearchPanel
|
|||
|
||||
# Only import for type checking/autocompletion, will not be imported at runtime.
|
||||
if typing.TYPE_CHECKING:
|
||||
from src.qt.ts_qt import QtDriver
|
||||
from src.qt.ts_qt import QtDriver
|
||||
|
||||
|
||||
class TagBoxWidget(FieldWidget):
|
||||
updated = Signal()
|
||||
|
||||
def __init__(self, item, title, field_index, library:Library, tags:list[int], driver:'QtDriver') -> None:
|
||||
super().__init__(title)
|
||||
# QObject.__init__(self)
|
||||
self.item = item
|
||||
self.lib = library
|
||||
self.driver = driver # Used for creating tag click callbacks that search entries for that tag.
|
||||
self.field_index = field_index
|
||||
self.tags:list[int] = tags
|
||||
self.setObjectName('tagBox')
|
||||
self.base_layout = FlowLayout()
|
||||
self.base_layout.setGridEfficiency(False)
|
||||
self.base_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.setLayout(self.base_layout)
|
||||
updated = Signal()
|
||||
|
||||
self.add_button = QPushButton()
|
||||
self.add_button.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
self.add_button.setMinimumSize(23, 23)
|
||||
self.add_button.setMaximumSize(23, 23)
|
||||
self.add_button.setText('+')
|
||||
self.add_button.setStyleSheet(
|
||||
f'QPushButton{{'
|
||||
# f'background: #1E1A33;'
|
||||
# f'color: #CDA7F7;'
|
||||
f'font-weight: bold;'
|
||||
# f"border-color: #2B2547;"
|
||||
f'border-radius: 6px;'
|
||||
f'border-style:solid;'
|
||||
f'border-width:{math.ceil(1*self.devicePixelRatio())}px;'
|
||||
# f'padding-top: 1.5px;'
|
||||
# f'padding-right: 4px;'
|
||||
f'padding-bottom: 5px;'
|
||||
# f'padding-left: 4px;'
|
||||
f'font-size: 20px;'
|
||||
f'}}'
|
||||
f'QPushButton::hover'
|
||||
f'{{'
|
||||
# f'background: #2B2547;'
|
||||
f'}}')
|
||||
tsp = TagSearchPanel(self.lib)
|
||||
tsp.tag_chosen.connect(lambda x: self.add_tag_callback(x))
|
||||
self.add_modal = PanelModal(tsp, title, 'Add Tags')
|
||||
self.add_button.clicked.connect(lambda: (tsp.update_tags() ,self.add_modal.show()))
|
||||
def __init__(
|
||||
self,
|
||||
item,
|
||||
title,
|
||||
field_index,
|
||||
library: Library,
|
||||
tags: list[int],
|
||||
driver: "QtDriver",
|
||||
) -> None:
|
||||
super().__init__(title)
|
||||
# QObject.__init__(self)
|
||||
self.item = item
|
||||
self.lib = library
|
||||
self.driver = driver # Used for creating tag click callbacks that search entries for that tag.
|
||||
self.field_index = field_index
|
||||
self.tags: list[int] = tags
|
||||
self.setObjectName("tagBox")
|
||||
self.base_layout = FlowLayout()
|
||||
self.base_layout.setGridEfficiency(False)
|
||||
self.base_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.setLayout(self.base_layout)
|
||||
|
||||
self.set_tags(tags)
|
||||
# self.add_button.setHidden(True)
|
||||
self.add_button = QPushButton()
|
||||
self.add_button.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
self.add_button.setMinimumSize(23, 23)
|
||||
self.add_button.setMaximumSize(23, 23)
|
||||
self.add_button.setText("+")
|
||||
self.add_button.setStyleSheet(
|
||||
f"QPushButton{{"
|
||||
# f'background: #1E1A33;'
|
||||
# f'color: #CDA7F7;'
|
||||
f"font-weight: bold;"
|
||||
# f"border-color: #2B2547;"
|
||||
f"border-radius: 6px;"
|
||||
f"border-style:solid;"
|
||||
f"border-width:{math.ceil(1*self.devicePixelRatio())}px;"
|
||||
# f'padding-top: 1.5px;'
|
||||
# f'padding-right: 4px;'
|
||||
f"padding-bottom: 5px;"
|
||||
# f'padding-left: 4px;'
|
||||
f"font-size: 20px;"
|
||||
f"}}"
|
||||
f"QPushButton::hover"
|
||||
f"{{"
|
||||
# f'background: #2B2547;'
|
||||
f"}}"
|
||||
)
|
||||
tsp = TagSearchPanel(self.lib)
|
||||
tsp.tag_chosen.connect(lambda x: self.add_tag_callback(x))
|
||||
self.add_modal = PanelModal(tsp, title, "Add Tags")
|
||||
self.add_button.clicked.connect(
|
||||
lambda: (tsp.update_tags(), self.add_modal.show())
|
||||
)
|
||||
|
||||
def set_item(self, item):
|
||||
self.item = item
|
||||
self.set_tags(tags)
|
||||
# self.add_button.setHidden(True)
|
||||
|
||||
def set_tags(self, tags:list[int]):
|
||||
logging.info(f'[TAG BOX WIDGET] SET TAGS: T:{tags} for E:{self.item.id}')
|
||||
is_recycled = False
|
||||
if self.base_layout.itemAt(0):
|
||||
# logging.info(type(self.base_layout.itemAt(0).widget()))
|
||||
while self.base_layout.itemAt(0) and self.base_layout.itemAt(1):
|
||||
# logging.info(f"I'm deleting { self.base_layout.itemAt(0).widget()}")
|
||||
self.base_layout.takeAt(0).widget().deleteLater()
|
||||
is_recycled = True
|
||||
for tag in tags:
|
||||
# TODO: Remove space from the special search here (tag_id:x) once that system is finalized.
|
||||
# tw = TagWidget(self.lib, self.lib.get_tag(tag), True, True,
|
||||
# on_remove_callback=lambda checked=False, t=tag: (self.lib.get_entry(self.item.id).remove_tag(self.lib, t, self.field_index), self.updated.emit()),
|
||||
# on_click_callback=lambda checked=False, q=f'tag_id: {tag}': (self.driver.main_window.searchField.setText(q), self.driver.filter_items(q)),
|
||||
# on_edit_callback=lambda checked=False, t=tag: (self.edit_tag(t))
|
||||
# )
|
||||
tw = TagWidget(self.lib, self.lib.get_tag(tag), True, True)
|
||||
tw.on_click.connect(lambda checked=False, q=f'tag_id: {tag}': (self.driver.main_window.searchField.setText(q), self.driver.filter_items(q)))
|
||||
tw.on_remove.connect(lambda checked=False, t=tag: (self.remove_tag(t)))
|
||||
tw.on_edit.connect(lambda checked=False, t=tag: (self.edit_tag(t)))
|
||||
self.base_layout.addWidget(tw)
|
||||
self.tags = tags
|
||||
def set_item(self, item):
|
||||
self.item = item
|
||||
|
||||
# Move or add the '+' button.
|
||||
if is_recycled:
|
||||
self.base_layout.addWidget(self.base_layout.takeAt(0).widget())
|
||||
else:
|
||||
self.base_layout.addWidget(self.add_button)
|
||||
|
||||
# Handles an edge case where there are no more tags and the '+' button
|
||||
# doesn't move all the way to the left.
|
||||
if self.base_layout.itemAt(0) and not self.base_layout.itemAt(1):
|
||||
self.base_layout.update()
|
||||
def set_tags(self, tags: list[int]):
|
||||
logging.info(f"[TAG BOX WIDGET] SET TAGS: T:{tags} for E:{self.item.id}")
|
||||
is_recycled = False
|
||||
if self.base_layout.itemAt(0):
|
||||
# logging.info(type(self.base_layout.itemAt(0).widget()))
|
||||
while self.base_layout.itemAt(0) and self.base_layout.itemAt(1):
|
||||
# logging.info(f"I'm deleting { self.base_layout.itemAt(0).widget()}")
|
||||
self.base_layout.takeAt(0).widget().deleteLater()
|
||||
is_recycled = True
|
||||
for tag in tags:
|
||||
# TODO: Remove space from the special search here (tag_id:x) once that system is finalized.
|
||||
# tw = TagWidget(self.lib, self.lib.get_tag(tag), True, True,
|
||||
# on_remove_callback=lambda checked=False, t=tag: (self.lib.get_entry(self.item.id).remove_tag(self.lib, t, self.field_index), self.updated.emit()),
|
||||
# on_click_callback=lambda checked=False, q=f'tag_id: {tag}': (self.driver.main_window.searchField.setText(q), self.driver.filter_items(q)),
|
||||
# on_edit_callback=lambda checked=False, t=tag: (self.edit_tag(t))
|
||||
# )
|
||||
tw = TagWidget(self.lib, self.lib.get_tag(tag), True, True)
|
||||
tw.on_click.connect(
|
||||
lambda checked=False, q=f"tag_id: {tag}": (
|
||||
self.driver.main_window.searchField.setText(q),
|
||||
self.driver.filter_items(q),
|
||||
)
|
||||
)
|
||||
tw.on_remove.connect(lambda checked=False, t=tag: (self.remove_tag(t)))
|
||||
tw.on_edit.connect(lambda checked=False, t=tag: (self.edit_tag(t)))
|
||||
self.base_layout.addWidget(tw)
|
||||
self.tags = tags
|
||||
|
||||
# Move or add the '+' button.
|
||||
if is_recycled:
|
||||
self.base_layout.addWidget(self.base_layout.takeAt(0).widget())
|
||||
else:
|
||||
self.base_layout.addWidget(self.add_button)
|
||||
|
||||
def edit_tag(self, tag_id:int):
|
||||
btp = BuildTagPanel(self.lib, tag_id)
|
||||
# btp.on_edit.connect(lambda x: self.edit_tag_callback(x))
|
||||
self.edit_modal = PanelModal(btp,
|
||||
self.lib.get_tag(tag_id).display_name(self.lib),
|
||||
'Edit Tag',
|
||||
done_callback=(self.driver.preview_panel.update_widgets),
|
||||
has_save=True)
|
||||
# self.edit_modal.widget.update_display_name.connect(lambda t: self.edit_modal.title_widget.setText(t))
|
||||
panel: BuildTagPanel = self.edit_modal.widget
|
||||
self.edit_modal.saved.connect(lambda: self.lib.update_tag(btp.build_tag()))
|
||||
# panel.tag_updated.connect(lambda tag: self.lib.update_tag(tag))
|
||||
self.edit_modal.show()
|
||||
# Handles an edge case where there are no more tags and the '+' button
|
||||
# doesn't move all the way to the left.
|
||||
if self.base_layout.itemAt(0) and not self.base_layout.itemAt(1):
|
||||
self.base_layout.update()
|
||||
|
||||
def edit_tag(self, tag_id: int):
|
||||
btp = BuildTagPanel(self.lib, tag_id)
|
||||
# btp.on_edit.connect(lambda x: self.edit_tag_callback(x))
|
||||
self.edit_modal = PanelModal(
|
||||
btp,
|
||||
self.lib.get_tag(tag_id).display_name(self.lib),
|
||||
"Edit Tag",
|
||||
done_callback=(self.driver.preview_panel.update_widgets),
|
||||
has_save=True,
|
||||
)
|
||||
# self.edit_modal.widget.update_display_name.connect(lambda t: self.edit_modal.title_widget.setText(t))
|
||||
panel: BuildTagPanel = self.edit_modal.widget
|
||||
self.edit_modal.saved.connect(lambda: self.lib.update_tag(btp.build_tag()))
|
||||
# panel.tag_updated.connect(lambda tag: self.lib.update_tag(tag))
|
||||
self.edit_modal.show()
|
||||
|
||||
def add_tag_callback(self, tag_id):
|
||||
# self.base_layout.addWidget(TagWidget(self.lib, self.lib.get_tag(tag), True))
|
||||
# self.tags.append(tag)
|
||||
logging.info(f'[TAG BOX WIDGET] ADD TAG CALLBACK: T:{tag_id} to E:{self.item.id}')
|
||||
logging.info(f'[TAG BOX WIDGET] SELECTED T:{self.driver.selected}')
|
||||
id = list(self.field.keys())[0]
|
||||
for x in self.driver.selected:
|
||||
self.driver.lib.get_entry(x[1]).add_tag(self.driver.lib, tag_id, field_id=id, field_index=-1)
|
||||
self.updated.emit()
|
||||
if tag_id == 0 or tag_id == 1:
|
||||
self.driver.update_badges()
|
||||
def add_tag_callback(self, tag_id):
|
||||
# self.base_layout.addWidget(TagWidget(self.lib, self.lib.get_tag(tag), True))
|
||||
# self.tags.append(tag)
|
||||
logging.info(
|
||||
f"[TAG BOX WIDGET] ADD TAG CALLBACK: T:{tag_id} to E:{self.item.id}"
|
||||
)
|
||||
logging.info(f"[TAG BOX WIDGET] SELECTED T:{self.driver.selected}")
|
||||
id = list(self.field.keys())[0]
|
||||
for x in self.driver.selected:
|
||||
self.driver.lib.get_entry(x[1]).add_tag(
|
||||
self.driver.lib, tag_id, field_id=id, field_index=-1
|
||||
)
|
||||
self.updated.emit()
|
||||
if tag_id == 0 or tag_id == 1:
|
||||
self.driver.update_badges()
|
||||
|
||||
# if type((x[0]) == ThumbButton):
|
||||
# # TODO: Remove space from the special search here (tag_id:x) once that system is finalized.
|
||||
# logging.info(f'I want to add tag ID {tag_id} to entry {self.item.filename}')
|
||||
# self.updated.emit()
|
||||
# if tag_id not in self.tags:
|
||||
# self.tags.append(tag_id)
|
||||
# self.set_tags(self.tags)
|
||||
# elif type((x[0]) == ThumbButton):
|
||||
# if type((x[0]) == ThumbButton):
|
||||
# # TODO: Remove space from the special search here (tag_id:x) once that system is finalized.
|
||||
# logging.info(f'I want to add tag ID {tag_id} to entry {self.item.filename}')
|
||||
# self.updated.emit()
|
||||
# if tag_id not in self.tags:
|
||||
# self.tags.append(tag_id)
|
||||
# self.set_tags(self.tags)
|
||||
# elif type((x[0]) == ThumbButton):
|
||||
|
||||
|
||||
def edit_tag_callback(self, tag:Tag):
|
||||
self.lib.update_tag(tag)
|
||||
|
||||
def remove_tag(self, tag_id):
|
||||
logging.info(f'[TAG BOX WIDGET] SELECTED T:{self.driver.selected}')
|
||||
id = list(self.field.keys())[0]
|
||||
for x in self.driver.selected:
|
||||
index = self.driver.lib.get_field_index_in_entry(self.driver.lib.get_entry(x[1]),id)
|
||||
self.driver.lib.get_entry(x[1]).remove_tag(self.driver.lib, tag_id,field_index=index[0])
|
||||
self.updated.emit()
|
||||
if tag_id == 0 or tag_id == 1:
|
||||
self.driver.update_badges()
|
||||
def edit_tag_callback(self, tag: Tag):
|
||||
self.lib.update_tag(tag)
|
||||
|
||||
# def show_add_button(self, value:bool):
|
||||
# self.add_button.setHidden(not value)
|
||||
def remove_tag(self, tag_id):
|
||||
logging.info(f"[TAG BOX WIDGET] SELECTED T:{self.driver.selected}")
|
||||
id = list(self.field.keys())[0]
|
||||
for x in self.driver.selected:
|
||||
index = self.driver.lib.get_field_index_in_entry(
|
||||
self.driver.lib.get_entry(x[1]), id
|
||||
)
|
||||
self.driver.lib.get_entry(x[1]).remove_tag(
|
||||
self.driver.lib, tag_id, field_index=index[0]
|
||||
)
|
||||
self.updated.emit()
|
||||
if tag_id == 0 or tag_id == 1:
|
||||
self.driver.update_badges()
|
||||
|
||||
# def show_add_button(self, value:bool):
|
||||
# self.add_button.setHidden(not value)
|
||||
|
|
|
@ -9,23 +9,23 @@ from src.qt.widgets import FieldWidget
|
|||
|
||||
|
||||
class TextWidget(FieldWidget):
|
||||
def __init__(self, title, text: str) -> None:
|
||||
super().__init__(title)
|
||||
# self.item = item
|
||||
self.setObjectName("textBox")
|
||||
# self.setStyleSheet('background-color:purple;')
|
||||
self.base_layout = QHBoxLayout()
|
||||
self.base_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.setLayout(self.base_layout)
|
||||
self.text_label = QLabel()
|
||||
# self.text_label.textFormat(Qt.TextFormat.RichText)
|
||||
self.text_label.setStyleSheet("font-size: 12px")
|
||||
self.text_label.setWordWrap(True)
|
||||
self.text_label.setTextInteractionFlags(
|
||||
Qt.TextInteractionFlag.TextSelectableByMouse
|
||||
)
|
||||
self.base_layout.addWidget(self.text_label)
|
||||
self.set_text(text)
|
||||
|
||||
def __init__(self, title, text:str) -> None:
|
||||
super().__init__(title)
|
||||
# self.item = item
|
||||
self.setObjectName('textBox')
|
||||
# self.setStyleSheet('background-color:purple;')
|
||||
self.base_layout = QHBoxLayout()
|
||||
self.base_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.setLayout(self.base_layout)
|
||||
self.text_label = QLabel()
|
||||
# self.text_label.textFormat(Qt.TextFormat.RichText)
|
||||
self.text_label.setStyleSheet('font-size: 12px')
|
||||
self.text_label.setWordWrap(True)
|
||||
self.text_label.setTextInteractionFlags(
|
||||
Qt.TextInteractionFlag.TextSelectableByMouse)
|
||||
self.base_layout.addWidget(self.text_label)
|
||||
self.set_text(text)
|
||||
|
||||
def set_text(self, text:str):
|
||||
self.text_label.setText(text)
|
||||
def set_text(self, text: str):
|
||||
self.text_label.setText(text)
|
||||
|
|
|
@ -9,19 +9,19 @@ from src.qt.widgets import PanelWidget
|
|||
|
||||
|
||||
class EditTextBox(PanelWidget):
|
||||
def __init__(self, text):
|
||||
super().__init__()
|
||||
# self.setLayout()
|
||||
self.setMinimumSize(480, 480)
|
||||
self.root_layout = QVBoxLayout(self)
|
||||
self.root_layout.setContentsMargins(6,0,6,0)
|
||||
self.text = text
|
||||
self.text_edit = QPlainTextEdit()
|
||||
self.text_edit.setPlainText(text)
|
||||
self.root_layout.addWidget(self.text_edit)
|
||||
|
||||
def get_content(self)-> str:
|
||||
return self.text_edit.toPlainText()
|
||||
|
||||
def reset(self):
|
||||
self.text_edit.setPlainText(self.text)
|
||||
def __init__(self, text):
|
||||
super().__init__()
|
||||
# self.setLayout()
|
||||
self.setMinimumSize(480, 480)
|
||||
self.root_layout = QVBoxLayout(self)
|
||||
self.root_layout.setContentsMargins(6, 0, 6, 0)
|
||||
self.text = text
|
||||
self.text_edit = QPlainTextEdit()
|
||||
self.text_edit.setPlainText(text)
|
||||
self.root_layout.addWidget(self.text_edit)
|
||||
|
||||
def get_content(self) -> str:
|
||||
return self.text_edit.toPlainText()
|
||||
|
||||
def reset(self):
|
||||
self.text_edit.setPlainText(self.text)
|
||||
|
|
|
@ -9,20 +9,20 @@ from src.qt.widgets import PanelWidget
|
|||
|
||||
|
||||
class EditTextLine(PanelWidget):
|
||||
def __init__(self, text):
|
||||
super().__init__()
|
||||
# self.setLayout()
|
||||
self.setMinimumWidth(480)
|
||||
self.root_layout = QVBoxLayout(self)
|
||||
self.root_layout.setContentsMargins(6,0,6,0)
|
||||
self.text = text
|
||||
self.text_edit = QLineEdit()
|
||||
self.text_edit.setText(text)
|
||||
self.text_edit.returnPressed.connect(self.done.emit)
|
||||
self.root_layout.addWidget(self.text_edit)
|
||||
|
||||
def get_content(self)-> str:
|
||||
return self.text_edit.text()
|
||||
|
||||
def reset(self):
|
||||
self.text_edit.setText(self.text)
|
||||
def __init__(self, text):
|
||||
super().__init__()
|
||||
# self.setLayout()
|
||||
self.setMinimumWidth(480)
|
||||
self.root_layout = QVBoxLayout(self)
|
||||
self.root_layout.setContentsMargins(6, 0, 6, 0)
|
||||
self.text = text
|
||||
self.text_edit = QLineEdit()
|
||||
self.text_edit.setText(text)
|
||||
self.text_edit.returnPressed.connect(self.done.emit)
|
||||
self.root_layout.addWidget(self.text_edit)
|
||||
|
||||
def get_content(self) -> str:
|
||||
return self.text_edit.text()
|
||||
|
||||
def reset(self):
|
||||
self.text_edit.setText(self.text)
|
||||
|
|
|
@ -10,64 +10,79 @@ from PySide6.QtWidgets import QWidget, QPushButton
|
|||
|
||||
|
||||
class ThumbButton(QPushButton):
|
||||
def __init__(self, parent:QWidget, thumb_size:tuple[int,int]) -> None:
|
||||
super().__init__(parent)
|
||||
self.thumb_size:tuple[int,int] = thumb_size
|
||||
self.hovered = False
|
||||
self.selected = False
|
||||
def __init__(self, parent: QWidget, thumb_size: tuple[int, int]) -> None:
|
||||
super().__init__(parent)
|
||||
self.thumb_size: tuple[int, int] = thumb_size
|
||||
self.hovered = False
|
||||
self.selected = False
|
||||
|
||||
# self.clicked.connect(lambda checked: self.set_selected(True))
|
||||
|
||||
def paintEvent(self, event:QEvent) -> None:
|
||||
super().paintEvent(event)
|
||||
if self.hovered or self.selected:
|
||||
painter = QPainter()
|
||||
painter.begin(self)
|
||||
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
|
||||
# painter.setCompositionMode(QPainter.CompositionMode.CompositionMode_Source)
|
||||
path = QPainterPath()
|
||||
width = 3
|
||||
radius = 6
|
||||
path.addRoundedRect(QtCore.QRectF(width/2,width/2,self.thumb_size[0]-width, self.thumb_size[1]-width), radius, radius)
|
||||
# self.clicked.connect(lambda checked: self.set_selected(True))
|
||||
|
||||
# color = QColor('#bb4ff0') if self.selected else QColor('#55bbf6')
|
||||
# pen = QPen(color, width)
|
||||
# painter.setPen(pen)
|
||||
# # brush.setColor(fill)
|
||||
# painter.drawPath(path)
|
||||
def paintEvent(self, event: QEvent) -> None:
|
||||
super().paintEvent(event)
|
||||
if self.hovered or self.selected:
|
||||
painter = QPainter()
|
||||
painter.begin(self)
|
||||
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
|
||||
# painter.setCompositionMode(QPainter.CompositionMode.CompositionMode_Source)
|
||||
path = QPainterPath()
|
||||
width = 3
|
||||
radius = 6
|
||||
path.addRoundedRect(
|
||||
QtCore.QRectF(
|
||||
width / 2,
|
||||
width / 2,
|
||||
self.thumb_size[0] - width,
|
||||
self.thumb_size[1] - width,
|
||||
),
|
||||
radius,
|
||||
radius,
|
||||
)
|
||||
|
||||
if self.selected:
|
||||
painter.setCompositionMode(QPainter.CompositionMode.CompositionMode_HardLight)
|
||||
color = QColor('#bb4ff0')
|
||||
color.setAlphaF(0.5)
|
||||
pen = QPen(color, width)
|
||||
painter.setPen(pen)
|
||||
painter.fillPath(path, color)
|
||||
painter.drawPath(path)
|
||||
# color = QColor('#bb4ff0') if self.selected else QColor('#55bbf6')
|
||||
# pen = QPen(color, width)
|
||||
# painter.setPen(pen)
|
||||
# # brush.setColor(fill)
|
||||
# painter.drawPath(path)
|
||||
|
||||
painter.setCompositionMode(QPainter.CompositionMode.CompositionMode_Source)
|
||||
color = QColor('#bb4ff0') if not self.hovered else QColor('#55bbf6')
|
||||
pen = QPen(color, width)
|
||||
painter.setPen(pen)
|
||||
painter.drawPath(path)
|
||||
elif self.hovered:
|
||||
painter.setCompositionMode(QPainter.CompositionMode.CompositionMode_Source)
|
||||
color = QColor('#55bbf6')
|
||||
pen = QPen(color, width)
|
||||
painter.setPen(pen)
|
||||
painter.drawPath(path)
|
||||
painter.end()
|
||||
|
||||
def enterEvent(self, event: QEnterEvent) -> None:
|
||||
self.hovered = True
|
||||
self.repaint()
|
||||
return super().enterEvent(event)
|
||||
if self.selected:
|
||||
painter.setCompositionMode(
|
||||
QPainter.CompositionMode.CompositionMode_HardLight
|
||||
)
|
||||
color = QColor("#bb4ff0")
|
||||
color.setAlphaF(0.5)
|
||||
pen = QPen(color, width)
|
||||
painter.setPen(pen)
|
||||
painter.fillPath(path, color)
|
||||
painter.drawPath(path)
|
||||
|
||||
def leaveEvent(self, event: QEvent) -> None:
|
||||
self.hovered = False
|
||||
self.repaint()
|
||||
return super().leaveEvent(event)
|
||||
painter.setCompositionMode(
|
||||
QPainter.CompositionMode.CompositionMode_Source
|
||||
)
|
||||
color = QColor("#bb4ff0") if not self.hovered else QColor("#55bbf6")
|
||||
pen = QPen(color, width)
|
||||
painter.setPen(pen)
|
||||
painter.drawPath(path)
|
||||
elif self.hovered:
|
||||
painter.setCompositionMode(
|
||||
QPainter.CompositionMode.CompositionMode_Source
|
||||
)
|
||||
color = QColor("#55bbf6")
|
||||
pen = QPen(color, width)
|
||||
painter.setPen(pen)
|
||||
painter.drawPath(path)
|
||||
painter.end()
|
||||
|
||||
def set_selected(self, value:bool) -> None:
|
||||
self.selected = value
|
||||
self.repaint()
|
||||
def enterEvent(self, event: QEnterEvent) -> None:
|
||||
self.hovered = True
|
||||
self.repaint()
|
||||
return super().enterEvent(event)
|
||||
|
||||
def leaveEvent(self, event: QEvent) -> None:
|
||||
self.hovered = False
|
||||
self.repaint()
|
||||
return super().leaveEvent(event)
|
||||
|
||||
def set_selected(self, value: bool) -> None:
|
||||
self.selected = value
|
||||
self.repaint()
|
||||
|
|
|
@ -10,412 +10,493 @@ import os
|
|||
from pathlib import Path
|
||||
|
||||
import cv2
|
||||
from PIL import Image, ImageChops, UnidentifiedImageError, ImageQt, ImageDraw, ImageFont, ImageEnhance, ImageOps
|
||||
from PIL import (
|
||||
Image,
|
||||
ImageChops,
|
||||
UnidentifiedImageError,
|
||||
ImageQt,
|
||||
ImageDraw,
|
||||
ImageFont,
|
||||
ImageEnhance,
|
||||
ImageOps,
|
||||
)
|
||||
from PySide6.QtCore import QObject, Signal, QSize
|
||||
from PySide6.QtGui import QPixmap
|
||||
from src.core.ts_core import PLAINTEXT_TYPES, VIDEO_TYPES, IMAGE_TYPES
|
||||
|
||||
|
||||
ERROR = f'[ERROR]'
|
||||
WARNING = f'[WARNING]'
|
||||
INFO = f'[INFO]'
|
||||
ERROR = f"[ERROR]"
|
||||
WARNING = f"[WARNING]"
|
||||
INFO = f"[INFO]"
|
||||
|
||||
|
||||
logging.basicConfig(format="%(message)s", level=logging.INFO)
|
||||
|
||||
|
||||
class ThumbRenderer(QObject):
|
||||
# finished = Signal()
|
||||
updated = Signal(float, QPixmap, QSize, str)
|
||||
updated_ratio = Signal(float)
|
||||
# updatedImage = Signal(QPixmap)
|
||||
# updatedSize = Signal(QSize)
|
||||
# finished = Signal()
|
||||
updated = Signal(float, QPixmap, QSize, str)
|
||||
updated_ratio = Signal(float)
|
||||
# updatedImage = Signal(QPixmap)
|
||||
# updatedSize = Signal(QSize)
|
||||
|
||||
thumb_mask_512: Image.Image = Image.open(os.path.normpath(
|
||||
f'{Path(__file__).parent.parent.parent.parent}/resources/qt/images/thumb_mask_512.png'))
|
||||
thumb_mask_512.load()
|
||||
thumb_mask_512: Image.Image = Image.open(
|
||||
os.path.normpath(
|
||||
f"{Path(__file__).parent.parent.parent.parent}/resources/qt/images/thumb_mask_512.png"
|
||||
)
|
||||
)
|
||||
thumb_mask_512.load()
|
||||
|
||||
thumb_mask_hl_512: Image.Image = Image.open(os.path.normpath(
|
||||
f'{Path(__file__).parent.parent.parent.parent}/resources/qt/images/thumb_mask_hl_512.png'))
|
||||
thumb_mask_hl_512.load()
|
||||
thumb_mask_hl_512: Image.Image = Image.open(
|
||||
os.path.normpath(
|
||||
f"{Path(__file__).parent.parent.parent.parent}/resources/qt/images/thumb_mask_hl_512.png"
|
||||
)
|
||||
)
|
||||
thumb_mask_hl_512.load()
|
||||
|
||||
thumb_loading_512: Image.Image = Image.open(os.path.normpath(
|
||||
f'{Path(__file__).parent.parent.parent.parent}/resources/qt/images/thumb_loading_512.png'))
|
||||
thumb_loading_512.load()
|
||||
thumb_loading_512: Image.Image = Image.open(
|
||||
os.path.normpath(
|
||||
f"{Path(__file__).parent.parent.parent.parent}/resources/qt/images/thumb_loading_512.png"
|
||||
)
|
||||
)
|
||||
thumb_loading_512.load()
|
||||
|
||||
thumb_broken_512: Image.Image = Image.open(os.path.normpath(
|
||||
f'{Path(__file__).parent.parent.parent.parent}/resources/qt/images/thumb_broken_512.png'))
|
||||
thumb_broken_512.load()
|
||||
thumb_broken_512: Image.Image = Image.open(
|
||||
os.path.normpath(
|
||||
f"{Path(__file__).parent.parent.parent.parent}/resources/qt/images/thumb_broken_512.png"
|
||||
)
|
||||
)
|
||||
thumb_broken_512.load()
|
||||
|
||||
thumb_file_default_512: Image.Image = Image.open(os.path.normpath(
|
||||
f'{Path(__file__).parent.parent.parent.parent}/resources/qt/images/thumb_file_default_512.png'))
|
||||
thumb_file_default_512.load()
|
||||
thumb_file_default_512: Image.Image = Image.open(
|
||||
os.path.normpath(
|
||||
f"{Path(__file__).parent.parent.parent.parent}/resources/qt/images/thumb_file_default_512.png"
|
||||
)
|
||||
)
|
||||
thumb_file_default_512.load()
|
||||
|
||||
# thumb_debug: Image.Image = Image.open(os.path.normpath(
|
||||
# f'{Path(__file__).parent.parent.parent}/resources/qt/images/temp.jpg'))
|
||||
# thumb_debug.load()
|
||||
# thumb_debug: Image.Image = Image.open(os.path.normpath(
|
||||
# f'{Path(__file__).parent.parent.parent}/resources/qt/images/temp.jpg'))
|
||||
# thumb_debug.load()
|
||||
|
||||
# TODO: Make dynamic font sized given different pixel ratios
|
||||
font_pixel_ratio: float = 1
|
||||
ext_font = ImageFont.truetype(os.path.normpath(
|
||||
f'{Path(__file__).parent.parent.parent.parent}/resources/qt/fonts/Oxanium-Bold.ttf'), math.floor(12*font_pixel_ratio))
|
||||
# TODO: Make dynamic font sized given different pixel ratios
|
||||
font_pixel_ratio: float = 1
|
||||
ext_font = ImageFont.truetype(
|
||||
os.path.normpath(
|
||||
f"{Path(__file__).parent.parent.parent.parent}/resources/qt/fonts/Oxanium-Bold.ttf"
|
||||
),
|
||||
math.floor(12 * font_pixel_ratio),
|
||||
)
|
||||
|
||||
def render(self, timestamp: float, filepath, base_size: tuple[int, int], pixelRatio: float, isLoading=False):
|
||||
"""Renders an entry/element thumbnail for the GUI."""
|
||||
adj_size: int = 1
|
||||
image = None
|
||||
pixmap = None
|
||||
final = None
|
||||
extension: str = None
|
||||
broken_thumb = False
|
||||
# adj_font_size = math.floor(12 * pixelRatio)
|
||||
if ThumbRenderer.font_pixel_ratio != pixelRatio:
|
||||
ThumbRenderer.font_pixel_ratio = pixelRatio
|
||||
ThumbRenderer.ext_font = ImageFont.truetype(os.path.normpath(
|
||||
f'{Path(__file__).parent.parent.parent.parent}/resources/qt/fonts/Oxanium-Bold.ttf'), math.floor(12*ThumbRenderer.font_pixel_ratio))
|
||||
def render(
|
||||
self,
|
||||
timestamp: float,
|
||||
filepath,
|
||||
base_size: tuple[int, int],
|
||||
pixelRatio: float,
|
||||
isLoading=False,
|
||||
):
|
||||
"""Renders an entry/element thumbnail for the GUI."""
|
||||
adj_size: int = 1
|
||||
image = None
|
||||
pixmap = None
|
||||
final = None
|
||||
extension: str = None
|
||||
broken_thumb = False
|
||||
# adj_font_size = math.floor(12 * pixelRatio)
|
||||
if ThumbRenderer.font_pixel_ratio != pixelRatio:
|
||||
ThumbRenderer.font_pixel_ratio = pixelRatio
|
||||
ThumbRenderer.ext_font = ImageFont.truetype(
|
||||
os.path.normpath(
|
||||
f"{Path(__file__).parent.parent.parent.parent}/resources/qt/fonts/Oxanium-Bold.ttf"
|
||||
),
|
||||
math.floor(12 * ThumbRenderer.font_pixel_ratio),
|
||||
)
|
||||
|
||||
if isLoading or filepath:
|
||||
adj_size = math.ceil(base_size[0] * pixelRatio)
|
||||
if isLoading or filepath:
|
||||
adj_size = math.ceil(base_size[0] * pixelRatio)
|
||||
|
||||
if isLoading:
|
||||
li: Image.Image = ThumbRenderer.thumb_loading_512.resize(
|
||||
(adj_size, adj_size), resample=Image.Resampling.BILINEAR)
|
||||
qim = ImageQt.ImageQt(li)
|
||||
pixmap = QPixmap.fromImage(qim)
|
||||
pixmap.setDevicePixelRatio(pixelRatio)
|
||||
elif filepath:
|
||||
mask: Image.Image = ThumbRenderer.thumb_mask_512.resize(
|
||||
(adj_size, adj_size), resample=Image.Resampling.BILINEAR).getchannel(3)
|
||||
hl: Image.Image = ThumbRenderer.thumb_mask_hl_512.resize(
|
||||
(adj_size, adj_size), resample=Image.Resampling.BILINEAR)
|
||||
if isLoading:
|
||||
li: Image.Image = ThumbRenderer.thumb_loading_512.resize(
|
||||
(adj_size, adj_size), resample=Image.Resampling.BILINEAR
|
||||
)
|
||||
qim = ImageQt.ImageQt(li)
|
||||
pixmap = QPixmap.fromImage(qim)
|
||||
pixmap.setDevicePixelRatio(pixelRatio)
|
||||
elif filepath:
|
||||
mask: Image.Image = ThumbRenderer.thumb_mask_512.resize(
|
||||
(adj_size, adj_size), resample=Image.Resampling.BILINEAR
|
||||
).getchannel(3)
|
||||
hl: Image.Image = ThumbRenderer.thumb_mask_hl_512.resize(
|
||||
(adj_size, adj_size), resample=Image.Resampling.BILINEAR
|
||||
)
|
||||
|
||||
extension = os.path.splitext(filepath)[1][1:].lower()
|
||||
extension = os.path.splitext(filepath)[1][1:].lower()
|
||||
|
||||
try:
|
||||
# Images =======================================================
|
||||
if extension in IMAGE_TYPES:
|
||||
image = Image.open(filepath)
|
||||
# image = self.thumb_debug
|
||||
if image.mode == 'RGBA':
|
||||
# logging.info(image.getchannel(3).tobytes())
|
||||
new_bg = Image.new('RGB', image.size, color='#222222')
|
||||
new_bg.paste(image, mask=image.getchannel(3))
|
||||
image = new_bg
|
||||
if image.mode != 'RGB':
|
||||
image = image.convert(mode='RGB')
|
||||
try:
|
||||
# Images =======================================================
|
||||
if extension in IMAGE_TYPES:
|
||||
image = Image.open(filepath)
|
||||
# image = self.thumb_debug
|
||||
if image.mode == "RGBA":
|
||||
# logging.info(image.getchannel(3).tobytes())
|
||||
new_bg = Image.new("RGB", image.size, color="#222222")
|
||||
new_bg.paste(image, mask=image.getchannel(3))
|
||||
image = new_bg
|
||||
if image.mode != "RGB":
|
||||
image = image.convert(mode="RGB")
|
||||
|
||||
image = ImageOps.exif_transpose(image)
|
||||
image = ImageOps.exif_transpose(image)
|
||||
|
||||
# Videos =======================================================
|
||||
elif extension in VIDEO_TYPES:
|
||||
video = cv2.VideoCapture(filepath)
|
||||
video.set(cv2.CAP_PROP_POS_FRAMES,
|
||||
(video.get(cv2.CAP_PROP_FRAME_COUNT) // 2))
|
||||
success, frame = video.read()
|
||||
if not success:
|
||||
# Depending on the video format, compression, and frame
|
||||
# count, seeking halfway does not work and the thumb
|
||||
# must be pulled from the earliest available frame.
|
||||
video.set(cv2.CAP_PROP_POS_FRAMES, 0)
|
||||
success, frame = video.read()
|
||||
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
||||
image = Image.fromarray(frame)
|
||||
# Videos =======================================================
|
||||
elif extension in VIDEO_TYPES:
|
||||
video = cv2.VideoCapture(filepath)
|
||||
video.set(
|
||||
cv2.CAP_PROP_POS_FRAMES,
|
||||
(video.get(cv2.CAP_PROP_FRAME_COUNT) // 2),
|
||||
)
|
||||
success, frame = video.read()
|
||||
if not success:
|
||||
# Depending on the video format, compression, and frame
|
||||
# count, seeking halfway does not work and the thumb
|
||||
# must be pulled from the earliest available frame.
|
||||
video.set(cv2.CAP_PROP_POS_FRAMES, 0)
|
||||
success, frame = video.read()
|
||||
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
||||
image = Image.fromarray(frame)
|
||||
|
||||
# Plain Text ===================================================
|
||||
elif extension in PLAINTEXT_TYPES:
|
||||
try:
|
||||
text: str = extension
|
||||
with open(filepath, 'r', encoding='utf-8') as text_file:
|
||||
text = text_file.read(256)
|
||||
bg = Image.new('RGB',(256,256), color='#222222')
|
||||
draw = ImageDraw.Draw(bg)
|
||||
draw.text((16,16), text, file=(255,255,255))
|
||||
image = bg
|
||||
except:
|
||||
logging.info(f'[ThumbRenderer][ERROR]: Coulnd\'t render thumbnail for {filepath}')
|
||||
# No Rendered Thumbnail ========================================
|
||||
else:
|
||||
image = ThumbRenderer.thumb_file_default_512.resize(
|
||||
(adj_size, adj_size), resample=Image.Resampling.BILINEAR)
|
||||
# Plain Text ===================================================
|
||||
elif extension in PLAINTEXT_TYPES:
|
||||
try:
|
||||
text: str = extension
|
||||
with open(filepath, "r", encoding="utf-8") as text_file:
|
||||
text = text_file.read(256)
|
||||
bg = Image.new("RGB", (256, 256), color="#222222")
|
||||
draw = ImageDraw.Draw(bg)
|
||||
draw.text((16, 16), text, file=(255, 255, 255))
|
||||
image = bg
|
||||
except:
|
||||
logging.info(
|
||||
f"[ThumbRenderer][ERROR]: Coulnd't render thumbnail for {filepath}"
|
||||
)
|
||||
# No Rendered Thumbnail ========================================
|
||||
else:
|
||||
image = ThumbRenderer.thumb_file_default_512.resize(
|
||||
(adj_size, adj_size), resample=Image.Resampling.BILINEAR
|
||||
)
|
||||
|
||||
if not image:
|
||||
raise UnidentifiedImageError
|
||||
if not image:
|
||||
raise UnidentifiedImageError
|
||||
|
||||
orig_x, orig_y = image.size
|
||||
new_x, new_y = (adj_size, adj_size)
|
||||
|
||||
if orig_x > orig_y:
|
||||
new_x = adj_size
|
||||
new_y = math.ceil(adj_size * (orig_y / orig_x))
|
||||
elif orig_y > orig_x:
|
||||
new_y = adj_size
|
||||
new_x = math.ceil(adj_size * (orig_x / orig_y))
|
||||
orig_x, orig_y = image.size
|
||||
new_x, new_y = (adj_size, adj_size)
|
||||
|
||||
# img_ratio = new_x / new_y
|
||||
image = image.resize(
|
||||
(new_x, new_y), resample=Image.Resampling.BILINEAR)
|
||||
if orig_x > orig_y:
|
||||
new_x = adj_size
|
||||
new_y = math.ceil(adj_size * (orig_y / orig_x))
|
||||
elif orig_y > orig_x:
|
||||
new_y = adj_size
|
||||
new_x = math.ceil(adj_size * (orig_x / orig_y))
|
||||
|
||||
if image.size != (adj_size, adj_size):
|
||||
# Old 1 color method.
|
||||
# bg_col = image.copy().resize((1, 1)).getpixel((0,0))
|
||||
# bg = Image.new(mode='RGB',size=(adj_size,adj_size),color=bg_col)
|
||||
# bg.thumbnail((1, 1))
|
||||
# bg = bg.resize((adj_size,adj_size), resample=Image.Resampling.NEAREST)
|
||||
# img_ratio = new_x / new_y
|
||||
image = image.resize((new_x, new_y), resample=Image.Resampling.BILINEAR)
|
||||
|
||||
# Small gradient background. Looks decent, and is only a one-liner.
|
||||
# bg = image.copy().resize((2, 2), resample=Image.Resampling.BILINEAR).resize((adj_size,adj_size),resample=Image.Resampling.BILINEAR)
|
||||
if image.size != (adj_size, adj_size):
|
||||
# Old 1 color method.
|
||||
# bg_col = image.copy().resize((1, 1)).getpixel((0,0))
|
||||
# bg = Image.new(mode='RGB',size=(adj_size,adj_size),color=bg_col)
|
||||
# bg.thumbnail((1, 1))
|
||||
# bg = bg.resize((adj_size,adj_size), resample=Image.Resampling.NEAREST)
|
||||
|
||||
# Four-Corner Gradient Background.
|
||||
# Not exactly a one-liner, but it's (subjectively) really cool.
|
||||
tl = image.getpixel((0, 0))
|
||||
tr = image.getpixel(((image.size[0]-1), 0))
|
||||
bl = image.getpixel((0, (image.size[1]-1)))
|
||||
br = image.getpixel(((image.size[0]-1), (image.size[1]-1)))
|
||||
bg = Image.new(mode='RGB', size=(2, 2))
|
||||
bg.paste(tl, (0, 0, 2, 2))
|
||||
bg.paste(tr, (1, 0, 2, 2))
|
||||
bg.paste(bl, (0, 1, 2, 2))
|
||||
bg.paste(br, (1, 1, 2, 2))
|
||||
bg = bg.resize((adj_size, adj_size),
|
||||
resample=Image.Resampling.BICUBIC)
|
||||
# Small gradient background. Looks decent, and is only a one-liner.
|
||||
# bg = image.copy().resize((2, 2), resample=Image.Resampling.BILINEAR).resize((adj_size,adj_size),resample=Image.Resampling.BILINEAR)
|
||||
|
||||
bg.paste(image, box=(
|
||||
(adj_size-image.size[0])//2, (adj_size-image.size[1])//2))
|
||||
# Four-Corner Gradient Background.
|
||||
# Not exactly a one-liner, but it's (subjectively) really cool.
|
||||
tl = image.getpixel((0, 0))
|
||||
tr = image.getpixel(((image.size[0] - 1), 0))
|
||||
bl = image.getpixel((0, (image.size[1] - 1)))
|
||||
br = image.getpixel(((image.size[0] - 1), (image.size[1] - 1)))
|
||||
bg = Image.new(mode="RGB", size=(2, 2))
|
||||
bg.paste(tl, (0, 0, 2, 2))
|
||||
bg.paste(tr, (1, 0, 2, 2))
|
||||
bg.paste(bl, (0, 1, 2, 2))
|
||||
bg.paste(br, (1, 1, 2, 2))
|
||||
bg = bg.resize(
|
||||
(adj_size, adj_size), resample=Image.Resampling.BICUBIC
|
||||
)
|
||||
|
||||
bg.putalpha(mask)
|
||||
final = bg
|
||||
bg.paste(
|
||||
image,
|
||||
box=(
|
||||
(adj_size - image.size[0]) // 2,
|
||||
(adj_size - image.size[1]) // 2,
|
||||
),
|
||||
)
|
||||
|
||||
else:
|
||||
image.putalpha(mask)
|
||||
final = image
|
||||
bg.putalpha(mask)
|
||||
final = bg
|
||||
|
||||
hl_soft = hl.copy()
|
||||
hl_soft.putalpha(ImageEnhance.Brightness(
|
||||
hl.getchannel(3)).enhance(.5))
|
||||
final.paste(ImageChops.soft_light(final, hl_soft),
|
||||
mask=hl_soft.getchannel(3))
|
||||
else:
|
||||
image.putalpha(mask)
|
||||
final = image
|
||||
|
||||
# hl_add = hl.copy()
|
||||
# hl_add.putalpha(ImageEnhance.Brightness(hl.getchannel(3)).enhance(.25))
|
||||
# final.paste(hl_add, mask=hl_add.getchannel(3))
|
||||
hl_soft = hl.copy()
|
||||
hl_soft.putalpha(ImageEnhance.Brightness(hl.getchannel(3)).enhance(0.5))
|
||||
final.paste(
|
||||
ImageChops.soft_light(final, hl_soft), mask=hl_soft.getchannel(3)
|
||||
)
|
||||
|
||||
except (UnidentifiedImageError, FileNotFoundError, cv2.error):
|
||||
broken_thumb = True
|
||||
final = ThumbRenderer.thumb_broken_512.resize(
|
||||
(adj_size, adj_size), resample=Image.Resampling.BILINEAR)
|
||||
# hl_add = hl.copy()
|
||||
# hl_add.putalpha(ImageEnhance.Brightness(hl.getchannel(3)).enhance(.25))
|
||||
# final.paste(hl_add, mask=hl_add.getchannel(3))
|
||||
|
||||
qim = ImageQt.ImageQt(final)
|
||||
if image:
|
||||
image.close()
|
||||
pixmap = QPixmap.fromImage(qim)
|
||||
pixmap.setDevicePixelRatio(pixelRatio)
|
||||
except (UnidentifiedImageError, FileNotFoundError, cv2.error):
|
||||
broken_thumb = True
|
||||
final = ThumbRenderer.thumb_broken_512.resize(
|
||||
(adj_size, adj_size), resample=Image.Resampling.BILINEAR
|
||||
)
|
||||
|
||||
if pixmap:
|
||||
self.updated.emit(timestamp, pixmap, QSize(*base_size), extension)
|
||||
qim = ImageQt.ImageQt(final)
|
||||
if image:
|
||||
image.close()
|
||||
pixmap = QPixmap.fromImage(qim)
|
||||
pixmap.setDevicePixelRatio(pixelRatio)
|
||||
|
||||
else:
|
||||
self.updated.emit(timestamp, QPixmap(),
|
||||
QSize(*base_size), extension)
|
||||
if pixmap:
|
||||
self.updated.emit(timestamp, pixmap, QSize(*base_size), extension)
|
||||
|
||||
def render_big(self, timestamp: float, filepath, base_size: tuple[int, int], pixelRatio: float, isLoading=False):
|
||||
"""Renders a large, non-square entry/element thumbnail for the GUI."""
|
||||
adj_size: int = 1
|
||||
image: Image.Image = None
|
||||
pixmap: QPixmap = None
|
||||
final: Image.Image = None
|
||||
extension: str = None
|
||||
broken_thumb = False
|
||||
img_ratio = 1
|
||||
# adj_font_size = math.floor(12 * pixelRatio)
|
||||
if ThumbRenderer.font_pixel_ratio != pixelRatio:
|
||||
ThumbRenderer.font_pixel_ratio = pixelRatio
|
||||
ThumbRenderer.ext_font = ImageFont.truetype(os.path.normpath(
|
||||
f'{Path(__file__).parent.parent.parent.parent}/resources/qt/fonts/Oxanium-Bold.ttf'), math.floor(12*ThumbRenderer.font_pixel_ratio))
|
||||
else:
|
||||
self.updated.emit(timestamp, QPixmap(), QSize(*base_size), extension)
|
||||
|
||||
if isLoading or filepath:
|
||||
adj_size = math.ceil(max(base_size[0], base_size[1]) * pixelRatio)
|
||||
def render_big(
|
||||
self,
|
||||
timestamp: float,
|
||||
filepath,
|
||||
base_size: tuple[int, int],
|
||||
pixelRatio: float,
|
||||
isLoading=False,
|
||||
):
|
||||
"""Renders a large, non-square entry/element thumbnail for the GUI."""
|
||||
adj_size: int = 1
|
||||
image: Image.Image = None
|
||||
pixmap: QPixmap = None
|
||||
final: Image.Image = None
|
||||
extension: str = None
|
||||
broken_thumb = False
|
||||
img_ratio = 1
|
||||
# adj_font_size = math.floor(12 * pixelRatio)
|
||||
if ThumbRenderer.font_pixel_ratio != pixelRatio:
|
||||
ThumbRenderer.font_pixel_ratio = pixelRatio
|
||||
ThumbRenderer.ext_font = ImageFont.truetype(
|
||||
os.path.normpath(
|
||||
f"{Path(__file__).parent.parent.parent.parent}/resources/qt/fonts/Oxanium-Bold.ttf"
|
||||
),
|
||||
math.floor(12 * ThumbRenderer.font_pixel_ratio),
|
||||
)
|
||||
|
||||
if isLoading:
|
||||
adj_size = math.ceil((512 * pixelRatio))
|
||||
final: Image.Image = ThumbRenderer.thumb_loading_512.resize(
|
||||
(adj_size, adj_size), resample=Image.Resampling.BILINEAR)
|
||||
qim = ImageQt.ImageQt(final)
|
||||
pixmap = QPixmap.fromImage(qim)
|
||||
pixmap.setDevicePixelRatio(pixelRatio)
|
||||
self.updated_ratio.emit(1)
|
||||
|
||||
elif filepath:
|
||||
# mask: Image.Image = ThumbRenderer.thumb_mask_512.resize(
|
||||
# (adj_size, adj_size), resample=Image.Resampling.BILINEAR).getchannel(3)
|
||||
# hl: Image.Image = ThumbRenderer.thumb_mask_hl_512.resize(
|
||||
# (adj_size, adj_size), resample=Image.Resampling.BILINEAR)
|
||||
if isLoading or filepath:
|
||||
adj_size = math.ceil(max(base_size[0], base_size[1]) * pixelRatio)
|
||||
|
||||
extension = os.path.splitext(filepath)[1][1:].lower()
|
||||
if isLoading:
|
||||
adj_size = math.ceil((512 * pixelRatio))
|
||||
final: Image.Image = ThumbRenderer.thumb_loading_512.resize(
|
||||
(adj_size, adj_size), resample=Image.Resampling.BILINEAR
|
||||
)
|
||||
qim = ImageQt.ImageQt(final)
|
||||
pixmap = QPixmap.fromImage(qim)
|
||||
pixmap.setDevicePixelRatio(pixelRatio)
|
||||
self.updated_ratio.emit(1)
|
||||
|
||||
try:
|
||||
# Images =======================================================
|
||||
if extension in IMAGE_TYPES:
|
||||
image = Image.open(filepath)
|
||||
# image = self.thumb_debug
|
||||
if image.mode == 'RGBA':
|
||||
# logging.info(image.getchannel(3).tobytes())
|
||||
new_bg = Image.new('RGB', image.size, color='#222222')
|
||||
new_bg.paste(image, mask=image.getchannel(3))
|
||||
image = new_bg
|
||||
if image.mode != 'RGB':
|
||||
image = image.convert(mode='RGB')
|
||||
|
||||
image = ImageOps.exif_transpose(image)
|
||||
elif filepath:
|
||||
# mask: Image.Image = ThumbRenderer.thumb_mask_512.resize(
|
||||
# (adj_size, adj_size), resample=Image.Resampling.BILINEAR).getchannel(3)
|
||||
# hl: Image.Image = ThumbRenderer.thumb_mask_hl_512.resize(
|
||||
# (adj_size, adj_size), resample=Image.Resampling.BILINEAR)
|
||||
|
||||
# Videos =======================================================
|
||||
elif extension in VIDEO_TYPES:
|
||||
video = cv2.VideoCapture(filepath)
|
||||
video.set(cv2.CAP_PROP_POS_FRAMES,
|
||||
(video.get(cv2.CAP_PROP_FRAME_COUNT) // 2))
|
||||
success, frame = video.read()
|
||||
if not success:
|
||||
# Depending on the video format, compression, and frame
|
||||
# count, seeking halfway does not work and the thumb
|
||||
# must be pulled from the earliest available frame.
|
||||
video.set(cv2.CAP_PROP_POS_FRAMES, 0)
|
||||
success, frame = video.read()
|
||||
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
||||
image = Image.fromarray(frame)
|
||||
# Plain Text ===================================================
|
||||
elif extension in PLAINTEXT_TYPES:
|
||||
try:
|
||||
text: str = extension
|
||||
with open(filepath, 'r', encoding='utf-8') as text_file:
|
||||
text = text_file.read(256)
|
||||
bg = Image.new('RGB',(256,256), color='#222222')
|
||||
draw = ImageDraw.Draw(bg)
|
||||
draw.text((16,16), text, file=(255,255,255))
|
||||
image = bg
|
||||
except:
|
||||
logging.info(f'[ThumbRenderer][ERROR]: Coulnd\'t render thumbnail for {filepath}')
|
||||
# No Rendered Thumbnail ========================================
|
||||
else:
|
||||
image = ThumbRenderer.thumb_file_default_512.resize(
|
||||
(adj_size, adj_size), resample=Image.Resampling.BILINEAR)
|
||||
extension = os.path.splitext(filepath)[1][1:].lower()
|
||||
|
||||
if not image:
|
||||
raise UnidentifiedImageError
|
||||
try:
|
||||
# Images =======================================================
|
||||
if extension in IMAGE_TYPES:
|
||||
image = Image.open(filepath)
|
||||
# image = self.thumb_debug
|
||||
if image.mode == "RGBA":
|
||||
# logging.info(image.getchannel(3).tobytes())
|
||||
new_bg = Image.new("RGB", image.size, color="#222222")
|
||||
new_bg.paste(image, mask=image.getchannel(3))
|
||||
image = new_bg
|
||||
if image.mode != "RGB":
|
||||
image = image.convert(mode="RGB")
|
||||
|
||||
orig_x, orig_y = image.size
|
||||
if orig_x < adj_size and orig_y < adj_size:
|
||||
new_x, new_y = (adj_size, adj_size)
|
||||
if orig_x > orig_y:
|
||||
new_x = adj_size
|
||||
new_y = math.ceil(adj_size * (orig_y / orig_x))
|
||||
elif orig_y > orig_x:
|
||||
new_y = adj_size
|
||||
new_x = math.ceil(adj_size * (orig_x / orig_y))
|
||||
else:
|
||||
new_x, new_y = (adj_size, adj_size)
|
||||
if orig_x > orig_y:
|
||||
new_x = adj_size
|
||||
new_y = math.ceil(adj_size * (orig_y / orig_x))
|
||||
elif orig_y > orig_x:
|
||||
new_y = adj_size
|
||||
new_x = math.ceil(adj_size * (orig_x / orig_y))
|
||||
image = ImageOps.exif_transpose(image)
|
||||
|
||||
self.updated_ratio.emit(new_x / new_y)
|
||||
image = image.resize(
|
||||
(new_x, new_y), resample=Image.Resampling.BILINEAR)
|
||||
# Videos =======================================================
|
||||
elif extension in VIDEO_TYPES:
|
||||
video = cv2.VideoCapture(filepath)
|
||||
video.set(
|
||||
cv2.CAP_PROP_POS_FRAMES,
|
||||
(video.get(cv2.CAP_PROP_FRAME_COUNT) // 2),
|
||||
)
|
||||
success, frame = video.read()
|
||||
if not success:
|
||||
# Depending on the video format, compression, and frame
|
||||
# count, seeking halfway does not work and the thumb
|
||||
# must be pulled from the earliest available frame.
|
||||
video.set(cv2.CAP_PROP_POS_FRAMES, 0)
|
||||
success, frame = video.read()
|
||||
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
||||
image = Image.fromarray(frame)
|
||||
# Plain Text ===================================================
|
||||
elif extension in PLAINTEXT_TYPES:
|
||||
try:
|
||||
text: str = extension
|
||||
with open(filepath, "r", encoding="utf-8") as text_file:
|
||||
text = text_file.read(256)
|
||||
bg = Image.new("RGB", (256, 256), color="#222222")
|
||||
draw = ImageDraw.Draw(bg)
|
||||
draw.text((16, 16), text, file=(255, 255, 255))
|
||||
image = bg
|
||||
except:
|
||||
logging.info(
|
||||
f"[ThumbRenderer][ERROR]: Coulnd't render thumbnail for {filepath}"
|
||||
)
|
||||
# No Rendered Thumbnail ========================================
|
||||
else:
|
||||
image = ThumbRenderer.thumb_file_default_512.resize(
|
||||
(adj_size, adj_size), resample=Image.Resampling.BILINEAR
|
||||
)
|
||||
|
||||
# image = image.resize(
|
||||
# (new_x, new_y), resample=Image.Resampling.BILINEAR)
|
||||
if not image:
|
||||
raise UnidentifiedImageError
|
||||
|
||||
# if image.size != (adj_size, adj_size):
|
||||
# # Old 1 color method.
|
||||
# # bg_col = image.copy().resize((1, 1)).getpixel((0,0))
|
||||
# # bg = Image.new(mode='RGB',size=(adj_size,adj_size),color=bg_col)
|
||||
# # bg.thumbnail((1, 1))
|
||||
# # bg = bg.resize((adj_size,adj_size), resample=Image.Resampling.NEAREST)
|
||||
orig_x, orig_y = image.size
|
||||
if orig_x < adj_size and orig_y < adj_size:
|
||||
new_x, new_y = (adj_size, adj_size)
|
||||
if orig_x > orig_y:
|
||||
new_x = adj_size
|
||||
new_y = math.ceil(adj_size * (orig_y / orig_x))
|
||||
elif orig_y > orig_x:
|
||||
new_y = adj_size
|
||||
new_x = math.ceil(adj_size * (orig_x / orig_y))
|
||||
else:
|
||||
new_x, new_y = (adj_size, adj_size)
|
||||
if orig_x > orig_y:
|
||||
new_x = adj_size
|
||||
new_y = math.ceil(adj_size * (orig_y / orig_x))
|
||||
elif orig_y > orig_x:
|
||||
new_y = adj_size
|
||||
new_x = math.ceil(adj_size * (orig_x / orig_y))
|
||||
|
||||
# # Small gradient background. Looks decent, and is only a one-liner.
|
||||
# # bg = image.copy().resize((2, 2), resample=Image.Resampling.BILINEAR).resize((adj_size,adj_size),resample=Image.Resampling.BILINEAR)
|
||||
self.updated_ratio.emit(new_x / new_y)
|
||||
image = image.resize((new_x, new_y), resample=Image.Resampling.BILINEAR)
|
||||
|
||||
# # Four-Corner Gradient Background.
|
||||
# # Not exactly a one-liner, but it's (subjectively) really cool.
|
||||
# tl = image.getpixel((0, 0))
|
||||
# tr = image.getpixel(((image.size[0]-1), 0))
|
||||
# bl = image.getpixel((0, (image.size[1]-1)))
|
||||
# br = image.getpixel(((image.size[0]-1), (image.size[1]-1)))
|
||||
# bg = Image.new(mode='RGB', size=(2, 2))
|
||||
# bg.paste(tl, (0, 0, 2, 2))
|
||||
# bg.paste(tr, (1, 0, 2, 2))
|
||||
# bg.paste(bl, (0, 1, 2, 2))
|
||||
# bg.paste(br, (1, 1, 2, 2))
|
||||
# bg = bg.resize((adj_size, adj_size),
|
||||
# resample=Image.Resampling.BICUBIC)
|
||||
# image = image.resize(
|
||||
# (new_x, new_y), resample=Image.Resampling.BILINEAR)
|
||||
|
||||
# bg.paste(image, box=(
|
||||
# (adj_size-image.size[0])//2, (adj_size-image.size[1])//2))
|
||||
# if image.size != (adj_size, adj_size):
|
||||
# # Old 1 color method.
|
||||
# # bg_col = image.copy().resize((1, 1)).getpixel((0,0))
|
||||
# # bg = Image.new(mode='RGB',size=(adj_size,adj_size),color=bg_col)
|
||||
# # bg.thumbnail((1, 1))
|
||||
# # bg = bg.resize((adj_size,adj_size), resample=Image.Resampling.NEAREST)
|
||||
|
||||
# bg.putalpha(mask)
|
||||
# final = bg
|
||||
# # Small gradient background. Looks decent, and is only a one-liner.
|
||||
# # bg = image.copy().resize((2, 2), resample=Image.Resampling.BILINEAR).resize((adj_size,adj_size),resample=Image.Resampling.BILINEAR)
|
||||
|
||||
# else:
|
||||
# image.putalpha(mask)
|
||||
# final = image
|
||||
# # Four-Corner Gradient Background.
|
||||
# # Not exactly a one-liner, but it's (subjectively) really cool.
|
||||
# tl = image.getpixel((0, 0))
|
||||
# tr = image.getpixel(((image.size[0]-1), 0))
|
||||
# bl = image.getpixel((0, (image.size[1]-1)))
|
||||
# br = image.getpixel(((image.size[0]-1), (image.size[1]-1)))
|
||||
# bg = Image.new(mode='RGB', size=(2, 2))
|
||||
# bg.paste(tl, (0, 0, 2, 2))
|
||||
# bg.paste(tr, (1, 0, 2, 2))
|
||||
# bg.paste(bl, (0, 1, 2, 2))
|
||||
# bg.paste(br, (1, 1, 2, 2))
|
||||
# bg = bg.resize((adj_size, adj_size),
|
||||
# resample=Image.Resampling.BICUBIC)
|
||||
|
||||
# hl_soft = hl.copy()
|
||||
# hl_soft.putalpha(ImageEnhance.Brightness(
|
||||
# hl.getchannel(3)).enhance(.5))
|
||||
# final.paste(ImageChops.soft_light(final, hl_soft),
|
||||
# mask=hl_soft.getchannel(3))
|
||||
# bg.paste(image, box=(
|
||||
# (adj_size-image.size[0])//2, (adj_size-image.size[1])//2))
|
||||
|
||||
# hl_add = hl.copy()
|
||||
# hl_add.putalpha(ImageEnhance.Brightness(hl.getchannel(3)).enhance(.25))
|
||||
# final.paste(hl_add, mask=hl_add.getchannel(3))
|
||||
scalar = 4
|
||||
rec: Image.Image = Image.new('RGB', tuple(
|
||||
[d * scalar for d in image.size]), 'black')
|
||||
draw = ImageDraw.Draw(rec)
|
||||
draw.rounded_rectangle(
|
||||
(0, 0)+rec.size, (base_size[0]//32) * scalar * pixelRatio, fill='red')
|
||||
rec = rec.resize(
|
||||
tuple([d // scalar for d in rec.size]), resample=Image.Resampling.BILINEAR)
|
||||
# final = image
|
||||
final = Image.new('RGBA', image.size, (0, 0, 0, 0))
|
||||
# logging.info(rec.size)
|
||||
# logging.info(image.size)
|
||||
final.paste(image, mask=rec.getchannel(0))
|
||||
# bg.putalpha(mask)
|
||||
# final = bg
|
||||
|
||||
except (UnidentifiedImageError, FileNotFoundError, cv2.error):
|
||||
broken_thumb = True
|
||||
self.updated_ratio.emit(1)
|
||||
final = ThumbRenderer.thumb_broken_512.resize(
|
||||
(adj_size, adj_size), resample=Image.Resampling.BILINEAR)
|
||||
# else:
|
||||
# image.putalpha(mask)
|
||||
# final = image
|
||||
|
||||
# if extension in VIDEO_TYPES + ['gif', 'apng'] or broken_thumb:
|
||||
# idk = ImageDraw.Draw(final)
|
||||
# # idk.textlength(file_type)
|
||||
# ext_offset_x = idk.textlength(
|
||||
# text=extension.upper(), font=ThumbRenderer.ext_font) / 2
|
||||
# ext_offset_x = math.floor(ext_offset_x * (1/pixelRatio))
|
||||
# x_margin = math.floor(
|
||||
# (adj_size-((base_size[0]//6)+ext_offset_x) * pixelRatio))
|
||||
# y_margin = math.floor(
|
||||
# (adj_size-((base_size[0]//8)) * pixelRatio))
|
||||
# stroke_width = round(2 * pixelRatio)
|
||||
# fill = 'white' if not broken_thumb else '#E32B41'
|
||||
# idk.text((x_margin, y_margin), extension.upper(
|
||||
# ), fill=fill, font=ThumbRenderer.ext_font, stroke_width=stroke_width, stroke_fill=(0, 0, 0))
|
||||
# hl_soft = hl.copy()
|
||||
# hl_soft.putalpha(ImageEnhance.Brightness(
|
||||
# hl.getchannel(3)).enhance(.5))
|
||||
# final.paste(ImageChops.soft_light(final, hl_soft),
|
||||
# mask=hl_soft.getchannel(3))
|
||||
|
||||
qim = ImageQt.ImageQt(final)
|
||||
if image:
|
||||
image.close()
|
||||
pixmap = QPixmap.fromImage(qim)
|
||||
pixmap.setDevicePixelRatio(pixelRatio)
|
||||
# hl_add = hl.copy()
|
||||
# hl_add.putalpha(ImageEnhance.Brightness(hl.getchannel(3)).enhance(.25))
|
||||
# final.paste(hl_add, mask=hl_add.getchannel(3))
|
||||
scalar = 4
|
||||
rec: Image.Image = Image.new(
|
||||
"RGB", tuple([d * scalar for d in image.size]), "black"
|
||||
)
|
||||
draw = ImageDraw.Draw(rec)
|
||||
draw.rounded_rectangle(
|
||||
(0, 0) + rec.size,
|
||||
(base_size[0] // 32) * scalar * pixelRatio,
|
||||
fill="red",
|
||||
)
|
||||
rec = rec.resize(
|
||||
tuple([d // scalar for d in rec.size]),
|
||||
resample=Image.Resampling.BILINEAR,
|
||||
)
|
||||
# final = image
|
||||
final = Image.new("RGBA", image.size, (0, 0, 0, 0))
|
||||
# logging.info(rec.size)
|
||||
# logging.info(image.size)
|
||||
final.paste(image, mask=rec.getchannel(0))
|
||||
|
||||
if pixmap:
|
||||
# logging.info(final.size)
|
||||
# self.updated.emit(pixmap, QSize(*final.size))
|
||||
self.updated.emit(timestamp, pixmap, QSize(math.ceil(
|
||||
adj_size * 1/pixelRatio), math.ceil(final.size[1] * 1/pixelRatio)), extension)
|
||||
except (UnidentifiedImageError, FileNotFoundError, cv2.error):
|
||||
broken_thumb = True
|
||||
self.updated_ratio.emit(1)
|
||||
final = ThumbRenderer.thumb_broken_512.resize(
|
||||
(adj_size, adj_size), resample=Image.Resampling.BILINEAR
|
||||
)
|
||||
|
||||
else:
|
||||
self.updated.emit(timestamp, QPixmap(),
|
||||
QSize(*base_size), extension)
|
||||
# if extension in VIDEO_TYPES + ['gif', 'apng'] or broken_thumb:
|
||||
# idk = ImageDraw.Draw(final)
|
||||
# # idk.textlength(file_type)
|
||||
# ext_offset_x = idk.textlength(
|
||||
# text=extension.upper(), font=ThumbRenderer.ext_font) / 2
|
||||
# ext_offset_x = math.floor(ext_offset_x * (1/pixelRatio))
|
||||
# x_margin = math.floor(
|
||||
# (adj_size-((base_size[0]//6)+ext_offset_x) * pixelRatio))
|
||||
# y_margin = math.floor(
|
||||
# (adj_size-((base_size[0]//8)) * pixelRatio))
|
||||
# stroke_width = round(2 * pixelRatio)
|
||||
# fill = 'white' if not broken_thumb else '#E32B41'
|
||||
# idk.text((x_margin, y_margin), extension.upper(
|
||||
# ), fill=fill, font=ThumbRenderer.ext_font, stroke_width=stroke_width, stroke_fill=(0, 0, 0))
|
||||
|
||||
qim = ImageQt.ImageQt(final)
|
||||
if image:
|
||||
image.close()
|
||||
pixmap = QPixmap.fromImage(qim)
|
||||
pixmap.setDevicePixelRatio(pixelRatio)
|
||||
|
||||
if pixmap:
|
||||
# logging.info(final.size)
|
||||
# self.updated.emit(pixmap, QSize(*final.size))
|
||||
self.updated.emit(
|
||||
timestamp,
|
||||
pixmap,
|
||||
QSize(
|
||||
math.ceil(adj_size * 1 / pixelRatio),
|
||||
math.ceil(final.size[1] * 1 / pixelRatio),
|
||||
),
|
||||
extension,
|
||||
)
|
||||
|
||||
else:
|
||||
self.updated.emit(timestamp, QPixmap(), QSize(*base_size), extension)
|
||||
|
|
|
@ -17,43 +17,59 @@ def main():
|
|||
|
||||
# Parse arguments.
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--open', dest='open', type=str,
|
||||
help='Path to a TagStudio Library folder to open on start.')
|
||||
parser.add_argument('-o', dest='open', type=str,
|
||||
help='Path to a TagStudio Library folder to open on start.')
|
||||
parser.add_argument(
|
||||
"--open",
|
||||
dest="open",
|
||||
type=str,
|
||||
help="Path to a TagStudio Library folder to open on start.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-o",
|
||||
dest="open",
|
||||
type=str,
|
||||
help="Path to a TagStudio Library folder to open on start.",
|
||||
)
|
||||
# parser.add_argument('--browse', dest='browse', action='store_true',
|
||||
# help='Jumps to entry browsing on startup.')
|
||||
# parser.add_argument('--external_preview', dest='external_preview', action='store_true',
|
||||
# help='Outputs current preview thumbnail to a live-updating file.')
|
||||
parser.add_argument('--debug', dest='debug', action='store_true',
|
||||
help='Reveals additional internal data useful for debugging.')
|
||||
parser.add_argument('--ui', dest='ui', type=str,
|
||||
help='User interface option for TagStudio. Options: qt, cli (Default: qt)')
|
||||
parser.add_argument(
|
||||
"--debug",
|
||||
dest="debug",
|
||||
action="store_true",
|
||||
help="Reveals additional internal data useful for debugging.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--ui",
|
||||
dest="ui",
|
||||
type=str,
|
||||
help="User interface option for TagStudio. Options: qt, cli (Default: qt)",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
core = TagStudioCore() # The TagStudio Core instance. UI agnostic.
|
||||
driver = None # The UI driver instance.
|
||||
ui_name: str = 'unknown' # Display name for the UI, used in logs.
|
||||
driver = None # The UI driver instance.
|
||||
ui_name: str = "unknown" # Display name for the UI, used in logs.
|
||||
|
||||
# Driver selection based on parameters.
|
||||
if args.ui and args.ui == 'qt':
|
||||
if args.ui and args.ui == "qt":
|
||||
driver = QtDriver(core, args)
|
||||
ui_name='Qt'
|
||||
elif args.ui and args.ui == 'cli':
|
||||
ui_name = "Qt"
|
||||
elif args.ui and args.ui == "cli":
|
||||
driver = CliDriver(core, args)
|
||||
ui_name='CLI'
|
||||
ui_name = "CLI"
|
||||
else:
|
||||
driver = QtDriver(core, args)
|
||||
ui_name='Qt'
|
||||
ui_name = "Qt"
|
||||
|
||||
# Run the chosen frontend driver.
|
||||
try:
|
||||
driver.start()
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
print(f'\nTagStudio Frontend ({ui_name}) Crashed! Press Enter to Continue...')
|
||||
print(f"\nTagStudio Frontend ({ui_name}) Crashed! Press Enter to Continue...")
|
||||
input()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
|
@ -3,10 +3,16 @@ from src.core.library import Tag
|
|||
|
||||
class TestTags:
|
||||
def test_construction(self):
|
||||
tag = Tag(id=1, name='Tag Name', shorthand='TN', aliases=[
|
||||
'First A', 'Second A'], subtags_ids=[2, 3, 4], color='')
|
||||
assert (tag)
|
||||
tag = Tag(
|
||||
id=1,
|
||||
name="Tag Name",
|
||||
shorthand="TN",
|
||||
aliases=["First A", "Second A"],
|
||||
subtags_ids=[2, 3, 4],
|
||||
color="",
|
||||
)
|
||||
assert tag
|
||||
|
||||
def test_empty_construction(self):
|
||||
tag = Tag(id=1, name='', shorthand='', aliases=[], subtags_ids=[], color='')
|
||||
assert (tag)
|
||||
tag = Tag(id=1, name="", shorthand="", aliases=[], subtags_ids=[], color="")
|
||||
assert tag
|
||||
|
|
Loading…
Reference in a new issue