Alpha v9.1.0
Initial public release
0
tagstudio/__init__.py
Normal file
BIN
tagstudio/resources/icon.ico
Normal file
|
After Width: | Height: | Size: 628 KiB |
BIN
tagstudio/resources/icon.png
Normal file
|
After Width: | Height: | Size: 992 KiB |
BIN
tagstudio/resources/qt/fonts/Oxanium-Bold.ttf
Normal file
BIN
tagstudio/resources/qt/images/box_icon_empty_128 - Copy.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
tagstudio/resources/qt/images/box_icon_empty_128.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
tagstudio/resources/qt/images/box_icon_filled_128 - Copy.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
tagstudio/resources/qt/images/box_icon_filled_128.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
tagstudio/resources/qt/images/clipboard_icon_128.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
tagstudio/resources/qt/images/collation_icon_128.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
tagstudio/resources/qt/images/edit_icon_128.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
tagstudio/resources/qt/images/splash.png
Normal file
|
After Width: | Height: | Size: 229 KiB |
BIN
tagstudio/resources/qt/images/splitter_handle_128.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
tagstudio/resources/qt/images/star_icon_empty_128 - Copy.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
tagstudio/resources/qt/images/star_icon_empty_128.png
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
BIN
tagstudio/resources/qt/images/star_icon_filled_128 - Copy.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
tagstudio/resources/qt/images/star_icon_filled_128.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
tagstudio/resources/qt/images/tag_group_icon_128.png
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
BIN
tagstudio/resources/qt/images/thumb_border_512.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
tagstudio/resources/qt/images/thumb_broken_512.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
tagstudio/resources/qt/images/thumb_loading_512.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
tagstudio/resources/qt/images/thumb_mask_128.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
tagstudio/resources/qt/images/thumb_mask_512.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
tagstudio/resources/qt/images/thumb_mask_hl_512.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
tagstudio/resources/qt/images/trash_icon_128.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
0
tagstudio/src/cli/__init__.py
Normal file
3300
tagstudio/src/cli/ts_cli.py
Normal file
0
tagstudio/src/core/__init__.py
Normal file
27
tagstudio/src/core/field_template.py
Normal file
@@ -0,0 +1,27 @@
|
||||
# Copyright (C) 2024 Travis Abendshien (CyanVoxel).
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
class FieldTemplate:
|
||||
"""A TagStudio Library Field Template object."""
|
||||
|
||||
def __init__(self, id: int, name: str, type: str) -> None:
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.type = type
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'\nID: {self.id}\nName: {self.name}\nType: {self.type}\n'
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return self.__str__()
|
||||
|
||||
def to_compressed_obj(self) -> dict:
|
||||
"""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
|
||||
|
||||
return obj
|
||||
2252
tagstudio/src/core/library.py
Normal file
252
tagstudio/src/core/palette.py
Normal file
@@ -0,0 +1,252 @@
|
||||
# Copyright (C) 2024 Travis Abendshien (CyanVoxel).
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class ColorType(Enum):
|
||||
PRIMARY = 0
|
||||
TEXT = 1
|
||||
BORDER = 2
|
||||
LIGHT_ACCENT = 3
|
||||
DARK_ACCENT = 4
|
||||
|
||||
|
||||
_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',
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def get_tag_color(type: ColorType, color: str):
|
||||
color = color.lower()
|
||||
try:
|
||||
if type == ColorType.TEXT:
|
||||
return get_tag_color(_TAG_COLORS[color][type], color)
|
||||
else:
|
||||
return _TAG_COLORS[color][type]
|
||||
except KeyError:
|
||||
return '#FF00FF'
|
||||
233
tagstudio/src/core/ts_core.py
Normal file
@@ -0,0 +1,233 @@
|
||||
# Copyright (C) 2024 Travis Abendshien (CyanVoxel).
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
"""The core classes and methods of TagStudio."""
|
||||
|
||||
import os
|
||||
from types import FunctionType
|
||||
# from typing import Dict, Optional, TypedDict, List
|
||||
import json
|
||||
from pathlib import Path
|
||||
import traceback
|
||||
import requests
|
||||
# from bs4 import BeautifulSoup as bs
|
||||
from src.core.library import *
|
||||
from src.core.field_template import FieldTemplate
|
||||
|
||||
VERSION: str = '9.1.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'
|
||||
|
||||
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']
|
||||
TEXT_TYPES: list[str] = ['txt', 'rtf', 'md',
|
||||
'doc', 'docx', 'pdf', 'tex', 'odt', 'pages']
|
||||
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']
|
||||
|
||||
ALL_FILE_TYPES: list[str] = IMAGE_TYPES + VIDEO_TYPES
|
||||
|
||||
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']
|
||||
|
||||
|
||||
class TagStudioCore:
|
||||
"""
|
||||
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 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:]
|
||||
|
||||
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}"')
|
||||
|
||||
# except FileNotFoundError:
|
||||
except:
|
||||
# print(
|
||||
# f'[INFO]: No sidecar file found at "{os.path.normpath(file_path + ".json")}"')
|
||||
pass
|
||||
|
||||
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()
|
||||
|
||||
# # except:
|
||||
# # # print("Could not resolve URL.")
|
||||
# # pass
|
||||
|
||||
def match_conditions(self, entry_id: int) -> str:
|
||||
"""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.
|
||||
json_dump = {}
|
||||
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' in c.keys() and c['fields']:
|
||||
for field in c['fields']:
|
||||
|
||||
field_id = self.lib.get_field_attr(
|
||||
field, 'id')
|
||||
content = field[field_id]
|
||||
|
||||
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')
|
||||
|
||||
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
|
||||
|
||||
def build_url(self, entry_id: int, source: str) -> str:
|
||||
"""Tries to rebuild a source URL given a specific filename structure."""
|
||||
|
||||
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 ''
|
||||
0
tagstudio/src/core/utils/__init__.py
Normal file
13
tagstudio/src/core/utils/fs.py
Normal file
@@ -0,0 +1,13 @@
|
||||
# Copyright (C) 2024 Travis Abendshien (CyanVoxel).
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
import os
|
||||
|
||||
|
||||
def clean_folder_name(folder_name: str) -> str:
|
||||
cleaned_name = folder_name
|
||||
invalid_chars = "<>:\"/\\|?*."
|
||||
for char in invalid_chars:
|
||||
cleaned_name = cleaned_name.replace(char, '_')
|
||||
return cleaned_name
|
||||
11
tagstudio/src/core/utils/str.py
Normal file
@@ -0,0 +1,11 @@
|
||||
# Copyright (C) 2024 Travis Abendshien (CyanVoxel).
|
||||
# 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(' ', '')
|
||||
12
tagstudio/src/core/utils/web.py
Normal file
@@ -0,0 +1,12 @@
|
||||
# Copyright (C) 2024 Travis Abendshien (CyanVoxel).
|
||||
# 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."""
|
||||
new_str = string
|
||||
new_str = new_str.removeprefix('https://')
|
||||
new_str = new_str.removeprefix('http://')
|
||||
new_str = new_str.removeprefix('www.')
|
||||
new_str = new_str.removeprefix('www2.')
|
||||
return new_str
|
||||
0
tagstudio/src/qt/__init__.py
Normal file
164
tagstudio/src/qt/flowlayout.py
Normal file
@@ -0,0 +1,164 @@
|
||||
# Copyright (C) 2013 Riverbank Computing Limited.
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
"""PySide6 port of the widgets/layouts/flowlayout example from Qt v6.x"""
|
||||
|
||||
import sys
|
||||
from PySide6.QtCore import Qt, QMargins, QPoint, QRect, QSize
|
||||
from PySide6.QtWidgets import QApplication, QLayout, QPushButton, QSizePolicy, QWidget
|
||||
|
||||
|
||||
# class Window(QWidget):
|
||||
# def __init__(self):
|
||||
# super().__init__()
|
||||
|
||||
# flow_layout = FlowLayout(self)
|
||||
# flow_layout.addWidget(QPushButton("Short"))
|
||||
# flow_layout.addWidget(QPushButton("Longer"))
|
||||
# flow_layout.addWidget(QPushButton("Different text"))
|
||||
# flow_layout.addWidget(QPushButton("More text"))
|
||||
# flow_layout.addWidget(QPushButton("Even longer button text"))
|
||||
|
||||
# self.setWindowTitle("Flow Layout")
|
||||
|
||||
class FlowWidget(QWidget):
|
||||
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)
|
||||
|
||||
if parent is not None:
|
||||
self.setContentsMargins(QMargins(0, 0, 0, 0))
|
||||
|
||||
self._item_list = []
|
||||
self.grid_efficiency = False
|
||||
|
||||
def __del__(self):
|
||||
item = self.takeAt(0)
|
||||
while item:
|
||||
item = self.takeAt(0)
|
||||
|
||||
def addItem(self, item):
|
||||
self._item_list.append(item)
|
||||
|
||||
def count(self):
|
||||
return len(self._item_list)
|
||||
|
||||
def itemAt(self, index):
|
||||
if 0 <= index < len(self._item_list):
|
||||
return self._item_list[index]
|
||||
|
||||
return None
|
||||
|
||||
def takeAt(self, index):
|
||||
if 0 <= index < len(self._item_list):
|
||||
return self._item_list.pop(index)
|
||||
|
||||
return None
|
||||
|
||||
def expandingDirections(self):
|
||||
return Qt.Orientation(0)
|
||||
|
||||
def hasHeightForWidth(self):
|
||||
return True
|
||||
|
||||
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 sizeHint(self):
|
||||
return self.minimumSize()
|
||||
|
||||
def minimumSize(self):
|
||||
if self.grid_efficiency:
|
||||
if self._item_list:
|
||||
return self._item_list[0].minimumSize()
|
||||
else:
|
||||
return QSize()
|
||||
else:
|
||||
size = QSize()
|
||||
|
||||
for item in self._item_list:
|
||||
size = size.expandedTo(item.minimumSize())
|
||||
|
||||
size += QSize(2 * self.contentsMargins().top(), 2 * self.contentsMargins().top())
|
||||
return size
|
||||
|
||||
|
||||
|
||||
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 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 (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 not test_only:
|
||||
item.setGeometry(QRect(QPoint(x, y), item.sizeHint()))
|
||||
|
||||
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())
|
||||
276
tagstudio/src/qt/main_window.py
Normal file
@@ -0,0 +1,276 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
################################################################################
|
||||
# Form generated from reading UI file 'home.ui'
|
||||
##
|
||||
# Created by: Qt User Interface Compiler version 6.5.1
|
||||
##
|
||||
# WARNING! All changes made in this file will be lost when recompiling UI file!
|
||||
################################################################################
|
||||
|
||||
# Copyright (C) 2024 Travis Abendshien (CyanVoxel).
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
from re import S
|
||||
import time
|
||||
from typing import Optional
|
||||
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
|
||||
QMetaObject, QObject, QPoint, QRect,
|
||||
QSize, QTime, QUrl, Qt)
|
||||
from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
|
||||
QFont, QFontDatabase, QGradient, QIcon,
|
||||
QImage, QKeySequence, QLinearGradient, QPainter,
|
||||
QPalette, QPixmap, QRadialGradient, QTransform, QAction)
|
||||
from PySide6.QtWidgets import (QApplication, QComboBox, QFrame, QGridLayout,
|
||||
QHBoxLayout, QVBoxLayout, QLayout, QLineEdit, QMainWindow,
|
||||
QMenuBar, QPushButton, QScrollArea, QSizePolicy,
|
||||
QStatusBar, QWidget, QSplitter, QMenu)
|
||||
from src.qt.pagination import Pagination
|
||||
# from src.qt.qtacrylic.qtacrylic import WindowEffect
|
||||
# from qframelesswindow import FramelessMainWindow, StandardTitleBar
|
||||
|
||||
|
||||
class Ui_MainWindow(QMainWindow):
|
||||
def __init__(self, parent=None) -> None:
|
||||
super().__init__(parent)
|
||||
self.setupUi(self)
|
||||
|
||||
# self.setWindowFlag(Qt.WindowType.NoDropShadowWindowHint, True)
|
||||
# self.setWindowFlag(Qt.WindowType.WindowTransparentForInput, False)
|
||||
# # self.setWindowFlag(Qt.WindowType.FramelessWindowHint, True)
|
||||
# self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
|
||||
|
||||
# self.windowFX = WindowEffect()
|
||||
# self.windowFX.setAcrylicEffect(self.winId(), isEnableShadow=False)
|
||||
|
||||
# # self.setStyleSheet(
|
||||
# # 'background:#EE000000;'
|
||||
# # )
|
||||
|
||||
|
||||
|
||||
def setupUi(self, MainWindow):
|
||||
if not MainWindow.objectName():
|
||||
MainWindow.setObjectName(u"MainWindow")
|
||||
MainWindow.resize(1300, 720)
|
||||
|
||||
# self._createMenuBar(MainWindow)
|
||||
|
||||
print(type(MainWindow))
|
||||
self.centralwidget = QWidget(MainWindow)
|
||||
self.centralwidget.setObjectName(u"centralwidget")
|
||||
self.gridLayout = QGridLayout(self.centralwidget)
|
||||
self.gridLayout.setObjectName(u"gridLayout")
|
||||
# self.gridLayout.setContentsMargins(0, 0, 0, 0)
|
||||
self.horizontalLayout = QHBoxLayout()
|
||||
self.horizontalLayout.setObjectName(u"horizontalLayout")
|
||||
|
||||
|
||||
|
||||
# tb = StandardTitleBar(MainWindow)
|
||||
# tb.setObjectName('TitleBar')
|
||||
# # # self.setTitleBar(tb)
|
||||
# hor = QVBoxLayout()
|
||||
# self.gridLayout.setContentsMargins(0,0,0,0)
|
||||
# self.gridLayout.addLayout(hor, 0, 0, 1, 1)
|
||||
|
||||
|
||||
|
||||
# hor.addWidget(tb)
|
||||
|
||||
self.splitter = QSplitter()
|
||||
self.splitter.setObjectName(u"splitter")
|
||||
self.splitter.setHandleWidth(12)
|
||||
|
||||
self.frame_container = QWidget()
|
||||
# self.frame_container.setStyleSheet('background:red;')
|
||||
self.frame_layout = QVBoxLayout(self.frame_container)
|
||||
# self.frame_container.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Minimum)
|
||||
|
||||
self.frame_layout.setSpacing(0)
|
||||
|
||||
|
||||
|
||||
self.scrollArea = QScrollArea()
|
||||
# self.scrollArea.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
||||
# self.scrollArea.setStyleSheet('background:green;')
|
||||
self.scrollArea.setObjectName(u"scrollArea")
|
||||
self.scrollArea.setFocusPolicy(Qt.WheelFocus)
|
||||
self.scrollArea.setFrameShape(QFrame.NoFrame)
|
||||
self.scrollArea.setFrameShadow(QFrame.Plain)
|
||||
self.scrollArea.setWidgetResizable(True)
|
||||
self.scrollAreaWidgetContents = QWidget()
|
||||
self.scrollAreaWidgetContents.setObjectName(
|
||||
u"scrollAreaWidgetContents")
|
||||
self.scrollAreaWidgetContents.setGeometry(QRect(0, 0, 1260, 590))
|
||||
self.gridLayout_2 = QGridLayout(self.scrollAreaWidgetContents)
|
||||
self.gridLayout_2.setSpacing(8)
|
||||
self.gridLayout_2.setObjectName(u"gridLayout_2")
|
||||
self.gridLayout_2.setContentsMargins(0, 0, 0, 8)
|
||||
self.scrollArea.setWidget(self.scrollAreaWidgetContents)
|
||||
self.frame_layout.addWidget(self.scrollArea)
|
||||
|
||||
self.scrollArea.setAttribute(Qt.WidgetAttribute.WA_NoSystemBackground)
|
||||
# self.scrollArea.setWindowFlag(Qt.WindowType.FramelessWindowHint)
|
||||
self.scrollArea.setAttribute(
|
||||
Qt.WidgetAttribute.WA_TranslucentBackground)
|
||||
self.scrollArea.setStyleSheet('background:#00000000;')
|
||||
|
||||
# self.page_bar_controls = QWidget()
|
||||
# self.page_bar_controls.setStyleSheet('background:blue;')
|
||||
# self.page_bar_controls.setMinimumHeight(32)
|
||||
|
||||
self.pagination = Pagination()
|
||||
self.frame_layout.addWidget(self.pagination)
|
||||
|
||||
# self.frame_layout.addWidget(self.page_bar_controls)
|
||||
# self.frame_layout.addWidget(self.page_bar_controls)
|
||||
|
||||
# self.horizontalLayout.addWidget(self.scrollArea)
|
||||
self.horizontalLayout.addWidget(self.splitter)
|
||||
self.splitter.addWidget(self.frame_container)
|
||||
self.splitter.setStretchFactor(0, 1)
|
||||
|
||||
self.gridLayout.addLayout(self.horizontalLayout, 10, 0, 1, 1)
|
||||
|
||||
self.horizontalLayout_2 = QHBoxLayout()
|
||||
self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
|
||||
self.horizontalLayout_2.setSizeConstraint(QLayout.SetMinimumSize)
|
||||
self.backButton = QPushButton(self.centralwidget)
|
||||
self.backButton.setObjectName(u"backButton")
|
||||
self.backButton.setMinimumSize(QSize(0, 32))
|
||||
self.backButton.setMaximumSize(QSize(32, 16777215))
|
||||
font = QFont()
|
||||
font.setPointSize(14)
|
||||
font.setBold(True)
|
||||
self.backButton.setFont(font)
|
||||
|
||||
self.horizontalLayout_2.addWidget(self.backButton)
|
||||
|
||||
self.forwardButton = QPushButton(self.centralwidget)
|
||||
self.forwardButton.setObjectName(u"forwardButton")
|
||||
self.forwardButton.setMinimumSize(QSize(0, 32))
|
||||
self.forwardButton.setMaximumSize(QSize(32, 16777215))
|
||||
font1 = QFont()
|
||||
font1.setPointSize(14)
|
||||
font1.setBold(True)
|
||||
font1.setKerning(True)
|
||||
self.forwardButton.setFont(font1)
|
||||
|
||||
self.horizontalLayout_2.addWidget(self.forwardButton)
|
||||
|
||||
self.searchField = QLineEdit(self.centralwidget)
|
||||
self.searchField.setObjectName(u"searchField")
|
||||
self.searchField.setMinimumSize(QSize(0, 32))
|
||||
self.searchField.setStyleSheet(
|
||||
'background:#55000000;'
|
||||
'border-radius:6px;'
|
||||
'border-style:solid;'
|
||||
'border-width:1px;'
|
||||
'border-color:#11FFFFFF;'
|
||||
)
|
||||
font2 = QFont()
|
||||
font2.setPointSize(11)
|
||||
font2.setBold(False)
|
||||
self.searchField.setFont(font2)
|
||||
|
||||
self.horizontalLayout_2.addWidget(self.searchField)
|
||||
|
||||
self.searchButton = QPushButton(self.centralwidget)
|
||||
self.searchButton.setObjectName(u"searchButton")
|
||||
self.searchButton.setMinimumSize(QSize(0, 32))
|
||||
self.searchButton.setFont(font2)
|
||||
|
||||
self.horizontalLayout_2.addWidget(self.searchButton)
|
||||
|
||||
self.gridLayout.addLayout(self.horizontalLayout_2, 3, 0, 1, 1)
|
||||
|
||||
self.comboBox = QComboBox(self.centralwidget)
|
||||
self.comboBox.setObjectName(u"comboBox")
|
||||
sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(
|
||||
self.comboBox.sizePolicy().hasHeightForWidth())
|
||||
self.comboBox.setSizePolicy(sizePolicy)
|
||||
self.comboBox.setMinimumWidth(128)
|
||||
self.comboBox.setMaximumWidth(128)
|
||||
|
||||
self.gridLayout.addWidget(self.comboBox, 4, 0, 1, 1, Qt.AlignRight)
|
||||
|
||||
self.gridLayout_2.setContentsMargins(6, 6, 6, 6)
|
||||
|
||||
MainWindow.setCentralWidget(self.centralwidget)
|
||||
# self.menubar = QMenuBar(MainWindow)
|
||||
# self.menubar.setObjectName(u"menubar")
|
||||
# self.menubar.setGeometry(QRect(0, 0, 1280, 22))
|
||||
# MainWindow.setMenuBar(self.menubar)
|
||||
self.statusbar = QStatusBar(MainWindow)
|
||||
self.statusbar.setObjectName(u"statusbar")
|
||||
sizePolicy1 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Maximum)
|
||||
sizePolicy1.setHorizontalStretch(0)
|
||||
sizePolicy1.setVerticalStretch(0)
|
||||
sizePolicy1.setHeightForWidth(
|
||||
self.statusbar.sizePolicy().hasHeightForWidth())
|
||||
self.statusbar.setSizePolicy(sizePolicy1)
|
||||
MainWindow.setStatusBar(self.statusbar)
|
||||
|
||||
menu_bar = self.menuBar()
|
||||
self.setMenuBar(menu_bar)
|
||||
# self.gridLayout.addWidget(menu_bar, 4, 0, 1, 1, Qt.AlignRight)
|
||||
self.frame_layout.addWidget(menu_bar)
|
||||
|
||||
self.retranslateUi(MainWindow)
|
||||
# self.dumpObjectTree()
|
||||
|
||||
QMetaObject.connectSlotsByName(MainWindow)
|
||||
# setupUi
|
||||
|
||||
def retranslateUi(self, MainWindow):
|
||||
MainWindow.setWindowTitle(QCoreApplication.translate(
|
||||
"MainWindow", u"MainWindow", None))
|
||||
self.backButton.setText(
|
||||
QCoreApplication.translate("MainWindow", u"<", None))
|
||||
self.forwardButton.setText(
|
||||
QCoreApplication.translate("MainWindow", u">", None))
|
||||
self.searchField.setPlaceholderText(
|
||||
QCoreApplication.translate("MainWindow", u"Search Entries", None))
|
||||
self.searchButton.setText(
|
||||
QCoreApplication.translate("MainWindow", u"Search", None))
|
||||
self.comboBox.setCurrentText("")
|
||||
self.comboBox.setPlaceholderText(
|
||||
QCoreApplication.translate("MainWindow", u"Thumbnail Size", None))
|
||||
# retranslateUi
|
||||
|
||||
def moveEvent(self, event) -> None:
|
||||
# time.sleep(0.02) # sleep for 20ms
|
||||
pass
|
||||
|
||||
def resizeEvent(self, event) -> None:
|
||||
# time.sleep(0.02) # sleep for 20ms
|
||||
pass
|
||||
|
||||
def _createMenuBar(self, main_window):
|
||||
menu_bar = QMenuBar(main_window)
|
||||
file_menu = QMenu('&File', main_window)
|
||||
edit_menu = QMenu('&Edit', main_window)
|
||||
tools_menu = QMenu('&Tools', main_window)
|
||||
macros_menu = QMenu('&Macros', main_window)
|
||||
help_menu = QMenu('&Help', main_window)
|
||||
|
||||
file_menu.addAction(QAction('&New Library', main_window))
|
||||
file_menu.addAction(QAction('&Open Library', main_window))
|
||||
file_menu.addAction(QAction('&Save Library', main_window))
|
||||
file_menu.addAction(QAction('&Close Library', main_window))
|
||||
|
||||
file_menu.addAction(QAction('&Refresh Directories', main_window))
|
||||
file_menu.addAction(QAction('&Add New Files to Library', main_window))
|
||||
|
||||
menu_bar.addMenu(file_menu)
|
||||
menu_bar.addMenu(edit_menu)
|
||||
menu_bar.addMenu(tools_menu)
|
||||
menu_bar.addMenu(macros_menu)
|
||||
menu_bar.addMenu(help_menu)
|
||||
|
||||
main_window.setMenuBar(menu_bar)
|
||||
551
tagstudio/src/qt/pagination.py
Normal file
@@ -0,0 +1,551 @@
|
||||
# Copyright (C) 2024 Travis Abendshien (CyanVoxel).
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
"""A pagination widget created for TagStudio."""
|
||||
# I never want to see this code again.
|
||||
|
||||
from PySide6 import QtCore
|
||||
from PySide6.QtGui import *
|
||||
from PySide6.QtWidgets import *
|
||||
from PySide6.QtCore import QFile, QObject, QThread, Signal, QRunnable, Qt, QThreadPool, QSize, QEvent, QMimeData
|
||||
|
||||
# class NumberEdit(QLineEdit):
|
||||
# def __init__(self, parent=None) -> None:
|
||||
# super().__init__(parent)
|
||||
# self.textChanged
|
||||
|
||||
class Pagination(QWidget, QObject):
|
||||
"""Widget containing controls for navigating between pages of items."""
|
||||
index = Signal(int)
|
||||
def __init__(self, parent=None) -> None:
|
||||
super().__init__(parent)
|
||||
self.page_count: int = 0
|
||||
self.current_page_index: int = 0
|
||||
self.buffer_page_count: int = 4
|
||||
self.button_size = QSize(32, 24)
|
||||
|
||||
# ------------ UI EXAMPLE --------------
|
||||
# [<] [1]...[3][4] [5] [6][7]...[42] [>]
|
||||
# ^^^^ <-- 2 Buffer Pages
|
||||
# Center Page Number is Editable Text
|
||||
# --------------------------------------
|
||||
|
||||
# [----------- ROOT LAYOUT ------------]
|
||||
self.setHidden(True)
|
||||
self.root_layout = QHBoxLayout(self)
|
||||
self.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Preferred)
|
||||
self.root_layout.setContentsMargins(0,6,0,0)
|
||||
self.root_layout.setSpacing(3)
|
||||
# self.setMinimumHeight(32)
|
||||
|
||||
# [<] ----------------------------------
|
||||
self.prev_button = QPushButton()
|
||||
self.prev_button.setText('<')
|
||||
self.prev_button.setMinimumSize(self.button_size)
|
||||
self.prev_button.setMaximumSize(self.button_size)
|
||||
|
||||
# --- [1] ------------------------------
|
||||
self.start_button = QPushButton()
|
||||
self.start_button.setMinimumSize(self.button_size)
|
||||
self.start_button.setMaximumSize(self.button_size)
|
||||
# self.start_button.setStyleSheet('background:cyan;')
|
||||
# self.start_button.setMaximumHeight(self.button_size.height())
|
||||
|
||||
# ------ ... ---------------------------
|
||||
self.start_ellipses = QLabel()
|
||||
self.start_ellipses.setMinimumSize(self.button_size)
|
||||
self.start_ellipses.setMaximumSize(self.button_size)
|
||||
# self.start_ellipses.setMaximumHeight(self.button_size.height())
|
||||
self.start_ellipses.setText('. . .')
|
||||
|
||||
# --------- [3][4] ---------------------
|
||||
self.start_buffer_container = QWidget()
|
||||
self.start_buffer_layout = QHBoxLayout(self.start_buffer_container)
|
||||
self.start_buffer_layout.setContentsMargins(0,0,0,0)
|
||||
self.start_buffer_layout.setSpacing(3)
|
||||
# self.start_buffer_container.setStyleSheet('background:blue;')
|
||||
|
||||
# ---------------- [5] -----------------
|
||||
self.current_page_field = QLineEdit()
|
||||
self.current_page_field.setMinimumSize(self.button_size)
|
||||
self.current_page_field.setMaximumSize(self.button_size)
|
||||
self.validator = Validator(1, self.page_count)
|
||||
self.current_page_field.setValidator(self.validator)
|
||||
self.current_page_field.returnPressed.connect(lambda: self._goto_page(int(self.current_page_field.text())-1))
|
||||
# self.current_page_field.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Preferred)
|
||||
# self.current_page_field.setMaximumHeight(self.button_size.height())
|
||||
# self.current_page_field.setMaximumWidth(self.button_size.width())
|
||||
|
||||
# -------------------- [6][7] ----------
|
||||
self.end_buffer_container = QWidget()
|
||||
self.end_buffer_layout = QHBoxLayout(self.end_buffer_container)
|
||||
self.end_buffer_layout.setContentsMargins(0,0,0,0)
|
||||
self.end_buffer_layout.setSpacing(3)
|
||||
# self.end_buffer_container.setStyleSheet('background:orange;')
|
||||
|
||||
# -------------------------- ... -------
|
||||
self.end_ellipses = QLabel()
|
||||
self.end_ellipses.setMinimumSize(self.button_size)
|
||||
self.end_ellipses.setMaximumSize(self.button_size)
|
||||
# self.end_ellipses.setMaximumHeight(self.button_size.height())
|
||||
self.end_ellipses.setText('. . .')
|
||||
|
||||
# ----------------------------- [42] ---
|
||||
self.end_button = QPushButton()
|
||||
self.end_button.setMinimumSize(self.button_size)
|
||||
self.end_button.setMaximumSize(self.button_size)
|
||||
# self.end_button.setMaximumHeight(self.button_size.height())
|
||||
# self.end_button.setStyleSheet('background:red;')
|
||||
|
||||
# ---------------------------------- [>]
|
||||
self.next_button = QPushButton()
|
||||
self.next_button.setText('>')
|
||||
self.next_button.setMinimumSize(self.button_size)
|
||||
self.next_button.setMaximumSize(self.button_size)
|
||||
|
||||
# Add Widgets to Root Layout
|
||||
self.root_layout.addStretch(1)
|
||||
self.root_layout.addWidget(self.prev_button)
|
||||
self.root_layout.addWidget(self.start_button)
|
||||
self.root_layout.addWidget(self.start_ellipses)
|
||||
self.root_layout.addWidget(self.start_buffer_container)
|
||||
self.root_layout.addWidget(self.current_page_field)
|
||||
self.root_layout.addWidget(self.end_buffer_container)
|
||||
self.root_layout.addWidget(self.end_ellipses)
|
||||
self.root_layout.addWidget(self.end_button)
|
||||
self.root_layout.addWidget(self.next_button)
|
||||
self.root_layout.addStretch(1)
|
||||
|
||||
self._populate_buffer_buttons()
|
||||
# self.update_buttons(page_count=9, index=0)
|
||||
|
||||
|
||||
def update_buttons(self, page_count:int, index:int, emit:bool=True):
|
||||
|
||||
# Screw it
|
||||
for i in range(0, 10):
|
||||
if self.start_buffer_layout.itemAt(i):
|
||||
self.start_buffer_layout.itemAt(i).widget().setHidden(True)
|
||||
if self.end_buffer_layout.itemAt(i):
|
||||
self.end_buffer_layout.itemAt(i).widget().setHidden(True)
|
||||
|
||||
if page_count <= 1:
|
||||
# Hide everything if there are only one or less pages.
|
||||
# [-------------- HIDDEN --------------]
|
||||
self.setHidden(True)
|
||||
# elif page_count > 1 and page_count < 7:
|
||||
# # Only show Next/Prev, current index field, and both start and end
|
||||
# # buffers (the end may be odd).
|
||||
# # [<] [1][2][3][4][5][6] [>]
|
||||
# self.start_button.setHidden(True)
|
||||
# self.start_ellipses.setHidden(True)
|
||||
# self.end_ellipses.setHidden(True)
|
||||
# self.end_button.setHidden(True)
|
||||
# elif page_count > 1:
|
||||
# self.start_button.setHidden(False)
|
||||
# self.start_ellipses.setHidden(False)
|
||||
# self.end_ellipses.setHidden(False)
|
||||
# self.end_button.setHidden(False)
|
||||
|
||||
# self.start_button.setText('1')
|
||||
# self.assign_click(self.start_button, 0)
|
||||
# self.end_button.setText(str(page_count))
|
||||
# self.assign_click(self.end_button, page_count-1)
|
||||
|
||||
elif page_count > 1:
|
||||
|
||||
# Enable/Disable Next+Prev Buttons
|
||||
if index == 0:
|
||||
self.prev_button.setDisabled(True)
|
||||
# self.start_buffer_layout.setContentsMargins(0,0,0,0)
|
||||
else:
|
||||
# self.start_buffer_layout.setContentsMargins(3,0,3,0)
|
||||
self._assign_click(self.prev_button, index-1)
|
||||
self.prev_button.setDisabled(False)
|
||||
if index == page_count-1:
|
||||
self.next_button.setDisabled(True)
|
||||
# self.end_buffer_layout.setContentsMargins(0,0,0,0)
|
||||
else:
|
||||
# self.end_buffer_layout.setContentsMargins(3,0,3,0)
|
||||
self._assign_click(self.next_button, index+1)
|
||||
self.next_button.setDisabled(False)
|
||||
|
||||
# Set Ellipses Sizes
|
||||
if page_count == 8:
|
||||
if index == 0:
|
||||
self.end_ellipses.setMinimumWidth(self.button_size.width()*2 + 3)
|
||||
self.end_ellipses.setMaximumWidth(self.button_size.width()*2 + 3)
|
||||
else:
|
||||
self.end_ellipses.setMinimumWidth(self.button_size.width())
|
||||
self.end_ellipses.setMaximumWidth(self.button_size.width())
|
||||
if index == page_count-1:
|
||||
self.start_ellipses.setMinimumWidth(self.button_size.width()*2 + 3)
|
||||
self.start_ellipses.setMaximumWidth(self.button_size.width()*2 + 3)
|
||||
else:
|
||||
self.start_ellipses.setMinimumWidth(self.button_size.width())
|
||||
self.start_ellipses.setMaximumWidth(self.button_size.width())
|
||||
elif page_count == 9:
|
||||
if index == 0:
|
||||
self.end_ellipses.setMinimumWidth(self.button_size.width()*3 + 6)
|
||||
self.end_ellipses.setMaximumWidth(self.button_size.width()*3 + 6)
|
||||
elif index == 1:
|
||||
self.end_ellipses.setMinimumWidth(self.button_size.width()*2 + 3)
|
||||
self.end_ellipses.setMaximumWidth(self.button_size.width()*2 + 3)
|
||||
else:
|
||||
self.end_ellipses.setMinimumWidth(self.button_size.width())
|
||||
self.end_ellipses.setMaximumWidth(self.button_size.width())
|
||||
if index == page_count-1:
|
||||
self.start_ellipses.setMinimumWidth(self.button_size.width()*3 + 6)
|
||||
self.start_ellipses.setMaximumWidth(self.button_size.width()*3 + 6)
|
||||
elif index == page_count-2:
|
||||
self.start_ellipses.setMinimumWidth(self.button_size.width()*2 + 3)
|
||||
self.start_ellipses.setMaximumWidth(self.button_size.width()*2 + 3)
|
||||
else:
|
||||
self.start_ellipses.setMinimumWidth(self.button_size.width())
|
||||
self.start_ellipses.setMaximumWidth(self.button_size.width())
|
||||
elif page_count == 10:
|
||||
if index == 0:
|
||||
self.end_ellipses.setMinimumWidth(self.button_size.width()*4 + 9)
|
||||
self.end_ellipses.setMaximumWidth(self.button_size.width()*4 + 9)
|
||||
elif index == 1:
|
||||
self.end_ellipses.setMinimumWidth(self.button_size.width()*3 + 6)
|
||||
self.end_ellipses.setMaximumWidth(self.button_size.width()*3 + 6)
|
||||
elif index == 2:
|
||||
self.end_ellipses.setMinimumWidth(self.button_size.width()*2 + 3)
|
||||
self.end_ellipses.setMaximumWidth(self.button_size.width()*2 + 3)
|
||||
else:
|
||||
self.end_ellipses.setMinimumWidth(self.button_size.width())
|
||||
self.end_ellipses.setMaximumWidth(self.button_size.width())
|
||||
if index == page_count-1:
|
||||
self.start_ellipses.setMinimumWidth(self.button_size.width()*4 + 9)
|
||||
self.start_ellipses.setMaximumWidth(self.button_size.width()*4 + 9)
|
||||
elif index == page_count-2:
|
||||
self.start_ellipses.setMinimumWidth(self.button_size.width()*3 + 6)
|
||||
self.start_ellipses.setMaximumWidth(self.button_size.width()*3 + 6)
|
||||
elif index == page_count-3:
|
||||
self.start_ellipses.setMinimumWidth(self.button_size.width()*2 + 3)
|
||||
self.start_ellipses.setMaximumWidth(self.button_size.width()*2 + 3)
|
||||
else:
|
||||
self.start_ellipses.setMinimumWidth(self.button_size.width())
|
||||
self.start_ellipses.setMaximumWidth(self.button_size.width())
|
||||
elif page_count == 11:
|
||||
if index == 0:
|
||||
self.end_ellipses.setMinimumWidth(self.button_size.width()*5 + 12)
|
||||
self.end_ellipses.setMaximumWidth(self.button_size.width()*5 + 12)
|
||||
elif index == 1:
|
||||
self.end_ellipses.setMinimumWidth(self.button_size.width()*4 + 9)
|
||||
self.end_ellipses.setMaximumWidth(self.button_size.width()*4 + 9)
|
||||
elif index == 2:
|
||||
self.end_ellipses.setMinimumWidth(self.button_size.width()*3 + 6)
|
||||
self.end_ellipses.setMaximumWidth(self.button_size.width()*3 + 6)
|
||||
elif index == 3:
|
||||
self.end_ellipses.setMinimumWidth(self.button_size.width()*2 + 3)
|
||||
self.end_ellipses.setMaximumWidth(self.button_size.width()*2 + 3)
|
||||
else:
|
||||
self.end_ellipses.setMinimumWidth(self.button_size.width())
|
||||
self.end_ellipses.setMaximumWidth(self.button_size.width())
|
||||
if index == page_count-1:
|
||||
self.start_ellipses.setMinimumWidth(self.button_size.width()*5 + 12)
|
||||
self.start_ellipses.setMaximumWidth(self.button_size.width()*5 + 12)
|
||||
elif index == page_count-2:
|
||||
self.start_ellipses.setMinimumWidth(self.button_size.width()*4 + 9)
|
||||
self.start_ellipses.setMaximumWidth(self.button_size.width()*4 + 9)
|
||||
elif index == page_count-3:
|
||||
self.start_ellipses.setMinimumWidth(self.button_size.width()*3 + 6)
|
||||
self.start_ellipses.setMaximumWidth(self.button_size.width()*3 + 6)
|
||||
elif index == page_count-4:
|
||||
self.start_ellipses.setMinimumWidth(self.button_size.width()*2 + 3)
|
||||
self.start_ellipses.setMaximumWidth(self.button_size.width()*2 + 3)
|
||||
else:
|
||||
self.start_ellipses.setMinimumWidth(self.button_size.width())
|
||||
self.start_ellipses.setMaximumWidth(self.button_size.width())
|
||||
elif page_count > 11:
|
||||
if index == 0:
|
||||
self.end_ellipses.setMinimumWidth(self.button_size.width()*7 + 18)
|
||||
self.end_ellipses.setMaximumWidth(self.button_size.width()*7 + 18)
|
||||
elif index == 1:
|
||||
self.end_ellipses.setMinimumWidth(self.button_size.width()*6 + 15)
|
||||
self.end_ellipses.setMaximumWidth(self.button_size.width()*6 + 15)
|
||||
elif index == 2:
|
||||
self.end_ellipses.setMinimumWidth(self.button_size.width()*5 + 12)
|
||||
self.end_ellipses.setMaximumWidth(self.button_size.width()*5 + 12)
|
||||
elif index == 3:
|
||||
self.end_ellipses.setMinimumWidth(self.button_size.width()*4 + 9)
|
||||
self.end_ellipses.setMaximumWidth(self.button_size.width()*4 + 9)
|
||||
elif index == 4:
|
||||
self.end_ellipses.setMinimumWidth(self.button_size.width()*3 + 6)
|
||||
self.end_ellipses.setMaximumWidth(self.button_size.width()*3 + 6)
|
||||
elif index == 5:
|
||||
self.end_ellipses.setMinimumWidth(self.button_size.width()*2 + 3)
|
||||
self.end_ellipses.setMaximumWidth(self.button_size.width()*2 + 3)
|
||||
else:
|
||||
self.end_ellipses.setMinimumWidth(self.button_size.width())
|
||||
self.end_ellipses.setMaximumWidth(self.button_size.width())
|
||||
if index == page_count-1:
|
||||
self.start_ellipses.setMinimumWidth(self.button_size.width()*7 + 18)
|
||||
self.start_ellipses.setMaximumWidth(self.button_size.width()*7 + 18)
|
||||
elif index == page_count-2:
|
||||
self.start_ellipses.setMinimumWidth(self.button_size.width()*6 + 15)
|
||||
self.start_ellipses.setMaximumWidth(self.button_size.width()*6 + 15)
|
||||
elif index == page_count-3:
|
||||
self.start_ellipses.setMinimumWidth(self.button_size.width()*5 + 12)
|
||||
self.start_ellipses.setMaximumWidth(self.button_size.width()*5 + 12)
|
||||
elif index == page_count-4:
|
||||
self.start_ellipses.setMinimumWidth(self.button_size.width()*4 + 9)
|
||||
self.start_ellipses.setMaximumWidth(self.button_size.width()*4 + 9)
|
||||
elif index == page_count-5:
|
||||
self.start_ellipses.setMinimumWidth(self.button_size.width()*3 + 6)
|
||||
self.start_ellipses.setMaximumWidth(self.button_size.width()*3 + 6)
|
||||
elif index == page_count-6:
|
||||
self.start_ellipses.setMinimumWidth(self.button_size.width()*2 + 3)
|
||||
self.start_ellipses.setMaximumWidth(self.button_size.width()*2 + 3)
|
||||
else:
|
||||
self.start_ellipses.setMinimumWidth(self.button_size.width())
|
||||
self.start_ellipses.setMaximumWidth(self.button_size.width())
|
||||
|
||||
# Enable/Disable Ellipses
|
||||
# if index <= max(self.buffer_page_count, 5)+1:
|
||||
if index <= self.buffer_page_count+1:
|
||||
self.start_ellipses.setHidden(True)
|
||||
# self.start_button.setHidden(True)
|
||||
else:
|
||||
self.start_ellipses.setHidden(False)
|
||||
# self.start_button.setHidden(False)
|
||||
# self.start_button.setText('1')
|
||||
self._assign_click(self.start_button, 0)
|
||||
# if index >=(page_count-max(self.buffer_page_count, 5)-2):
|
||||
if index >= (page_count-self.buffer_page_count-2):
|
||||
self.end_ellipses.setHidden(True)
|
||||
# self.end_button.setHidden(True)
|
||||
else:
|
||||
self.end_ellipses.setHidden(False)
|
||||
# self.end_button.setHidden(False)
|
||||
# self.end_button.setText(str(page_count))
|
||||
# self.assign_click(self.end_button, page_count-1)
|
||||
|
||||
# Hide/Unhide Start+End Buttons
|
||||
if index != 0:
|
||||
self.start_button.setText('1')
|
||||
self._assign_click(self.start_button, 0)
|
||||
self.start_button.setHidden(False)
|
||||
# self.start_buffer_layout.setContentsMargins(3,0,0,0)
|
||||
else:
|
||||
self.start_button.setHidden(True)
|
||||
# self.start_buffer_layout.setContentsMargins(0,0,0,0)
|
||||
if index != page_count-1:
|
||||
self.end_button.setText(str(page_count))
|
||||
self._assign_click(self.end_button, page_count-1)
|
||||
self.end_button.setHidden(False)
|
||||
# self.end_buffer_layout.setContentsMargins(0,0,3,0)
|
||||
else:
|
||||
self.end_button.setHidden(True)
|
||||
# self.end_buffer_layout.setContentsMargins(0,0,0,0)
|
||||
|
||||
if index == 0 or index == 1:
|
||||
self.start_buffer_container.setHidden(True)
|
||||
else:
|
||||
self.start_buffer_container.setHidden(False)
|
||||
|
||||
if index == page_count-1 or index == page_count-2:
|
||||
self.end_buffer_container.setHidden(True)
|
||||
else:
|
||||
self.end_buffer_container.setHidden(False)
|
||||
|
||||
# for i in range(0, self.buffer_page_count):
|
||||
# self.start_buffer_layout.itemAt(i).widget().setHidden(True)
|
||||
|
||||
# Current Field and Buffer Pages
|
||||
sbc = 0
|
||||
# for i in range(0, max(self.buffer_page_count*2, 11)):
|
||||
for i in range(0, page_count):
|
||||
# for j in range(0, self.buffer_page_count+1):
|
||||
# self.start_buffer_layout.itemAt(j).widget().setHidden(True)
|
||||
# if i == 1:
|
||||
# self.start_buffer_layout.itemAt(i).widget().setHidden(True)
|
||||
# elif i == page_count-2:
|
||||
# self.end_buffer_layout.itemAt(i).widget().setHidden(True)
|
||||
|
||||
# Set Field
|
||||
if i == index:
|
||||
# print(f'Current Index: {i}')
|
||||
if self.start_buffer_layout.itemAt(i):
|
||||
self.start_buffer_layout.itemAt(i).widget().setHidden(True)
|
||||
if self.end_buffer_layout.itemAt(i):
|
||||
self.end_buffer_layout.itemAt(i).widget().setHidden(True)
|
||||
sbc += 1
|
||||
self.current_page_field.setText((str(i+1)))
|
||||
# elif index == page_count-1:
|
||||
# self.start_button.setText(str(page_count))
|
||||
|
||||
start_offset = max(0, (index-4)-4)
|
||||
end_offset = min(page_count-1, (index+4)-4)
|
||||
if i < index:
|
||||
# if i != 0 and ((i-self.buffer_page_count) >= 0 or i <= self.buffer_page_count):
|
||||
if (i != 0) and i >= index-4:
|
||||
# print(f' Start i: {i}')
|
||||
# print(f'Start Offset: {start_offset}')
|
||||
# print(f' Requested i: {i-start_offset}')
|
||||
# print(f'Setting Text "{str(i+1)}" for Local Start i:{i-start_offset}, Global i:{i}')
|
||||
self.start_buffer_layout.itemAt(i-start_offset).widget().setHidden(False)
|
||||
self.start_buffer_layout.itemAt(i-start_offset).widget().setText(str(i+1))
|
||||
self._assign_click(self.start_buffer_layout.itemAt(i-start_offset).widget(), i)
|
||||
sbc += 1
|
||||
else:
|
||||
if self.start_buffer_layout.itemAt(i):
|
||||
# print(f'Removing S-Start {i}')
|
||||
self.start_buffer_layout.itemAt(i).widget().setHidden(True)
|
||||
if self.end_buffer_layout.itemAt(i):
|
||||
# print(f'Removing S-End {i}')
|
||||
self.end_buffer_layout.itemAt(i).widget().setHidden(True)
|
||||
elif i > index:
|
||||
# if i != page_count-1:
|
||||
if i != page_count-1 and i <= index+4:
|
||||
# print(f'End Buffer: {i}')
|
||||
# print(f' End i: {i}')
|
||||
# print(f' End Offset: {end_offset}')
|
||||
# print(f'Requested i: {i-end_offset}')
|
||||
# print(f'Requested i: {end_offset-sbc-i}')
|
||||
# if self.start_buffer_layout.itemAt(i):
|
||||
# self.start_buffer_layout.itemAt(i).widget().setHidden(True)
|
||||
# print(f'Setting Text "{str(i+1)}" for Local End i:{i-end_offset}, Global i:{i}')
|
||||
self.end_buffer_layout.itemAt(i-end_offset).widget().setHidden(False)
|
||||
self.end_buffer_layout.itemAt(i-end_offset).widget().setText(str(i+1))
|
||||
self._assign_click(self.end_buffer_layout.itemAt(i-end_offset).widget(), i)
|
||||
else:
|
||||
# if self.start_buffer_layout.itemAt(i-1):
|
||||
# print(f'Removing E-Start {i-1}')
|
||||
# self.start_buffer_layout.itemAt(i-1).widget().setHidden(True)
|
||||
# if self.start_buffer_layout.itemAt(i-start_offset):
|
||||
# print(f'Removing E-Start Offset {i-end_offset}')
|
||||
# self.start_buffer_layout.itemAt(i-end_offset).widget().setHidden(True)
|
||||
|
||||
if self.end_buffer_layout.itemAt(i):
|
||||
# print(f'Removing E-End {i}')
|
||||
self.end_buffer_layout.itemAt(i).widget().setHidden(True)
|
||||
for j in range(0,self.buffer_page_count):
|
||||
if self.end_buffer_layout.itemAt(i-end_offset+j):
|
||||
# print(f'Removing E-End-Offset {i-end_offset+j}')
|
||||
self.end_buffer_layout.itemAt(i-end_offset+j).widget().setHidden(True)
|
||||
|
||||
# if self.end_buffer_layout.itemAt(i+1):
|
||||
# print(f'Removing T-End {i+1}')
|
||||
# self.end_buffer_layout.itemAt(i+1).widget().setHidden(True)
|
||||
|
||||
if self.start_buffer_layout.itemAt(i-1):
|
||||
# print(f'Removing T-Start {i-1}')
|
||||
self.start_buffer_layout.itemAt(i-1).widget().setHidden(True)
|
||||
|
||||
|
||||
# if index == 0 or index == 1:
|
||||
# print(f'Removing Start i: {i}')
|
||||
# if self.start_buffer_layout.itemAt(i):
|
||||
# self.start_buffer_layout.itemAt(i).widget().setHidden(True)
|
||||
|
||||
# elif index == page_count-1 or index == page_count-2 or index == page_count-3 or index == page_count-4:
|
||||
# print(f' Removing End i: {i}')
|
||||
# if self.end_buffer_layout.itemAt(i):
|
||||
# self.end_buffer_layout.itemAt(i).widget().setHidden(True)
|
||||
|
||||
|
||||
|
||||
# else:
|
||||
# print(f'Truncate: {i}')
|
||||
# if self.start_buffer_layout.itemAt(i):
|
||||
# self.start_buffer_layout.itemAt(i).widget().setHidden(True)
|
||||
# if self.end_buffer_layout.itemAt(i):
|
||||
# self.end_buffer_layout.itemAt(i).widget().setHidden(True)
|
||||
|
||||
# if i < self.buffer_page_count:
|
||||
# print(f'start {i}')
|
||||
# if i == 0:
|
||||
# self.start_buffer_layout.itemAt(i).widget().setHidden(True)
|
||||
# self.current_page_field.setText((str(i+1)))
|
||||
# else:
|
||||
# self.start_buffer_layout.itemAt(i).widget().setHidden(False)
|
||||
# self.start_buffer_layout.itemAt(i).widget().setText(str(i+1))
|
||||
# elif i >= self.buffer_page_count and i < count:
|
||||
# print(f'end {i}')
|
||||
# self.end_buffer_layout.itemAt(i-self.buffer_page_count).widget().setHidden(False)
|
||||
# self.end_buffer_layout.itemAt(i-self.buffer_page_count).widget().setText(str(i+1))
|
||||
# else:
|
||||
# self.end_buffer_layout.itemAt(i-self.buffer_page_count).widget().setHidden(True)
|
||||
|
||||
self.setHidden(False)
|
||||
# elif page_count >= 7:
|
||||
# # Show everything, except truncate the buffers as needed.
|
||||
# # [<] [1]...[3] [4] [5]...[7] [>]
|
||||
# self.start_button.setHidden(False)
|
||||
# self.start_ellipses.setHidden(False)
|
||||
# self.end_ellipses.setHidden(False)
|
||||
# self.end_button.setHidden(False)
|
||||
|
||||
# if index == 0:
|
||||
# self.prev_button.setDisabled(True)
|
||||
# self.start_buffer_layout.setContentsMargins(0,0,3,0)
|
||||
# else:
|
||||
# self.start_buffer_layout.setContentsMargins(3,0,3,0)
|
||||
# self.assign_click(self.prev_button, index-1)
|
||||
# self.prev_button.setDisabled(False)
|
||||
|
||||
# if index == page_count-1:
|
||||
# self.next_button.setDisabled(True)
|
||||
# self.end_buffer_layout.setContentsMargins(3,0,0,0)
|
||||
# else:
|
||||
# self.end_buffer_layout.setContentsMargins(3,0,3,0)
|
||||
# self.assign_click(self.next_button, index+1)
|
||||
# self.next_button.setDisabled(False)
|
||||
|
||||
# self.start_button.setText('1')
|
||||
# self.assign_click(self.start_button, 0)
|
||||
# self.end_button.setText(str(page_count))
|
||||
# self.assign_click(self.end_button, page_count-1)
|
||||
|
||||
# self.setHidden(False)
|
||||
|
||||
self.validator.setTop(page_count)
|
||||
# if self.current_page_index != index:
|
||||
if emit:
|
||||
print(f'[PAGINATION] Emitting {index}')
|
||||
self.index.emit(index)
|
||||
self.current_page_index = index
|
||||
self.page_count = page_count
|
||||
|
||||
|
||||
def _goto_page(self, index:int):
|
||||
# print(f'GOTO PAGE: {index}')
|
||||
self.update_buttons(self.page_count, index)
|
||||
|
||||
def _assign_click(self, button:QPushButton, index):
|
||||
try:
|
||||
button.clicked.disconnect()
|
||||
except RuntimeError:
|
||||
pass
|
||||
button.clicked.connect(lambda checked=False, i=index: self._goto_page(i))
|
||||
|
||||
def _populate_buffer_buttons(self):
|
||||
for i in range(max(self.buffer_page_count*2, 5)):
|
||||
button = QPushButton()
|
||||
button.setMinimumSize(self.button_size)
|
||||
button.setMaximumSize(self.button_size)
|
||||
button.setHidden(True)
|
||||
# button.setMaximumHeight(self.button_size.height())
|
||||
self.start_buffer_layout.addWidget(button)
|
||||
|
||||
for i in range(max(self.buffer_page_count*2, 5)):
|
||||
button = QPushButton()
|
||||
button.setMinimumSize(self.button_size)
|
||||
button.setMaximumSize(self.button_size)
|
||||
button.setHidden(True)
|
||||
# button.setMaximumHeight(self.button_size.height())
|
||||
self.end_buffer_layout.addWidget(button)
|
||||
|
||||
class Validator(QIntValidator):
|
||||
def __init__(self, bottom: int, top: int, parent=None) -> None:
|
||||
super().__init__(bottom, top, parent)
|
||||
|
||||
def fixup(self, input: str) -> str:
|
||||
# print(input)
|
||||
input = input.strip('0')
|
||||
print(input)
|
||||
return super().fixup(str(self.top()) if input else '1')
|
||||
15428
tagstudio/src/qt/resources.py
Normal file
20
tagstudio/src/qt/resources.qrc
Normal file
@@ -0,0 +1,20 @@
|
||||
<!-- TO COMPILE: pyside6-rcc resources.qrc -o resources.py (in \tagstudio\src\qt)-->
|
||||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file alias = "images/star_icon_empty_128.png">../../resources/qt/images/star_icon_empty_128.png</file>
|
||||
<file alias = "images/star_icon_filled_128.png">../../resources/qt/images/star_icon_filled_128.png</file>
|
||||
<file alias = "images/box_icon_empty_128.png">../../resources/qt/images/box_icon_empty_128.png</file>
|
||||
<file alias = "images/box_icon_filled_128.png">../../resources/qt/images/box_icon_filled_128.png</file>
|
||||
<file alias = "images/edit_icon_128.png">../../resources/qt/images/edit_icon_128.png</file>
|
||||
<file alias = "images/trash_icon_128.png">../../resources/qt/images/trash_icon_128.png</file>
|
||||
<file alias = "images/clipboard_icon_128.png">../../resources/qt/images/clipboard_icon_128.png</file>
|
||||
<file alias = "images/splash.png">../../resources/qt/images/splash.png</file>
|
||||
<!-- <file>../../CommonClasses/PNGWriter/report_text/GothamBlackRegular.otf</file>
|
||||
<file>../../CommonClasses/PNGWriter/report_text/GothamBold.otf</file>
|
||||
<file>../../CommonClasses/PNGWriter/report_text/GothamBook.otf</file>
|
||||
<file>../../CommonClasses/PNGWriter/report_text/GothamLight.otf</file>
|
||||
<file>../../CommonClasses/PNGWriter/report_text/GothamMedium.otf</file>
|
||||
<file>../../CommonClasses/PNGWriter/report_text/Spanish</file>
|
||||
<file>../../CommonClasses/PNGWriter/report_text/viewmind.png</file> -->
|
||||
</qresource>
|
||||
</RCC>
|
||||
16910
tagstudio/src/qt/resources_rc.py
Normal file
4475
tagstudio/src/qt/ts_qt.py
Normal file
212
tagstudio/src/qt/ui/home.ui
Normal file
@@ -0,0 +1,212 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>MainWindow</class>
|
||||
<widget class="QMainWindow" name="MainWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1280</width>
|
||||
<height>720</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>MainWindow</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget">
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="5" column="0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QScrollArea" name="scrollArea">
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::WheelFocus</enum>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Plain</enum>
|
||||
</property>
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="scrollAreaWidgetContents">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1260</width>
|
||||
<height>590</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>8</number>
|
||||
</property>
|
||||
<property name="spacing">
|
||||
<number>8</number>
|
||||
</property>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetMinimumSize</enum>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QPushButton" name="backButton">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>14</pointsize>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string><</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="forwardButton">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>14</pointsize>
|
||||
<bold>true</bold>
|
||||
<kerning>true</kerning>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="searchField">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>11</pointsize>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>Search Entries</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="searchButton">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>11</pointsize>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Search</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="4" column="0" alignment="Qt::AlignRight">
|
||||
<widget class="QComboBox" name="comboBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>128</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>256</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="currentText">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>Thumbnail Size</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QMenuBar" name="menubar">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1280</width>
|
||||
<height>22</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QStatusBar" name="statusbar">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
140
tagstudio/src/qt/ui/home_ui.py
Normal file
@@ -0,0 +1,140 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
################################################################################
|
||||
## Form generated from reading UI file 'home.ui'
|
||||
##
|
||||
## Created by: Qt User Interface Compiler version 6.5.1
|
||||
##
|
||||
## WARNING! All changes made in this file will be lost when recompiling UI file!
|
||||
################################################################################
|
||||
|
||||
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
|
||||
QMetaObject, QObject, QPoint, QRect,
|
||||
QSize, QTime, QUrl, Qt)
|
||||
from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
|
||||
QFont, QFontDatabase, QGradient, QIcon,
|
||||
QImage, QKeySequence, QLinearGradient, QPainter,
|
||||
QPalette, QPixmap, QRadialGradient, QTransform)
|
||||
from PySide6.QtWidgets import (QApplication, QComboBox, QFrame, QGridLayout,
|
||||
QHBoxLayout, QLayout, QLineEdit, QMainWindow,
|
||||
QMenuBar, QPushButton, QScrollArea, QSizePolicy,
|
||||
QStatusBar, QWidget)
|
||||
|
||||
class Ui_MainWindow(object):
|
||||
def setupUi(self, MainWindow):
|
||||
if not MainWindow.objectName():
|
||||
MainWindow.setObjectName(u"MainWindow")
|
||||
MainWindow.resize(1280, 720)
|
||||
self.centralwidget = QWidget(MainWindow)
|
||||
self.centralwidget.setObjectName(u"centralwidget")
|
||||
self.gridLayout = QGridLayout(self.centralwidget)
|
||||
self.gridLayout.setObjectName(u"gridLayout")
|
||||
self.horizontalLayout = QHBoxLayout()
|
||||
self.horizontalLayout.setObjectName(u"horizontalLayout")
|
||||
self.scrollArea = QScrollArea(self.centralwidget)
|
||||
self.scrollArea.setObjectName(u"scrollArea")
|
||||
self.scrollArea.setFocusPolicy(Qt.WheelFocus)
|
||||
self.scrollArea.setFrameShape(QFrame.NoFrame)
|
||||
self.scrollArea.setFrameShadow(QFrame.Plain)
|
||||
self.scrollArea.setWidgetResizable(True)
|
||||
self.scrollAreaWidgetContents = QWidget()
|
||||
self.scrollAreaWidgetContents.setObjectName(u"scrollAreaWidgetContents")
|
||||
self.scrollAreaWidgetContents.setGeometry(QRect(0, 0, 1260, 590))
|
||||
self.gridLayout_2 = QGridLayout(self.scrollAreaWidgetContents)
|
||||
self.gridLayout_2.setSpacing(8)
|
||||
self.gridLayout_2.setObjectName(u"gridLayout_2")
|
||||
self.gridLayout_2.setContentsMargins(0, 0, 0, 8)
|
||||
self.scrollArea.setWidget(self.scrollAreaWidgetContents)
|
||||
|
||||
self.horizontalLayout.addWidget(self.scrollArea)
|
||||
|
||||
|
||||
self.gridLayout.addLayout(self.horizontalLayout, 5, 0, 1, 1)
|
||||
|
||||
self.horizontalLayout_2 = QHBoxLayout()
|
||||
self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
|
||||
self.horizontalLayout_2.setSizeConstraint(QLayout.SetMinimumSize)
|
||||
self.backButton = QPushButton(self.centralwidget)
|
||||
self.backButton.setObjectName(u"backButton")
|
||||
self.backButton.setMinimumSize(QSize(0, 32))
|
||||
self.backButton.setMaximumSize(QSize(32, 16777215))
|
||||
font = QFont()
|
||||
font.setPointSize(14)
|
||||
font.setBold(True)
|
||||
self.backButton.setFont(font)
|
||||
|
||||
self.horizontalLayout_2.addWidget(self.backButton)
|
||||
|
||||
self.forwardButton = QPushButton(self.centralwidget)
|
||||
self.forwardButton.setObjectName(u"forwardButton")
|
||||
self.forwardButton.setMinimumSize(QSize(0, 32))
|
||||
self.forwardButton.setMaximumSize(QSize(32, 16777215))
|
||||
font1 = QFont()
|
||||
font1.setPointSize(14)
|
||||
font1.setBold(True)
|
||||
font1.setKerning(True)
|
||||
self.forwardButton.setFont(font1)
|
||||
|
||||
self.horizontalLayout_2.addWidget(self.forwardButton)
|
||||
|
||||
self.searchField = QLineEdit(self.centralwidget)
|
||||
self.searchField.setObjectName(u"searchField")
|
||||
self.searchField.setMinimumSize(QSize(0, 32))
|
||||
font2 = QFont()
|
||||
font2.setPointSize(11)
|
||||
font2.setBold(False)
|
||||
self.searchField.setFont(font2)
|
||||
|
||||
self.horizontalLayout_2.addWidget(self.searchField)
|
||||
|
||||
self.searchButton = QPushButton(self.centralwidget)
|
||||
self.searchButton.setObjectName(u"searchButton")
|
||||
self.searchButton.setMinimumSize(QSize(0, 32))
|
||||
self.searchButton.setFont(font2)
|
||||
|
||||
self.horizontalLayout_2.addWidget(self.searchButton)
|
||||
|
||||
|
||||
self.gridLayout.addLayout(self.horizontalLayout_2, 3, 0, 1, 1)
|
||||
|
||||
self.comboBox = QComboBox(self.centralwidget)
|
||||
self.comboBox.setObjectName(u"comboBox")
|
||||
sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.comboBox.sizePolicy().hasHeightForWidth())
|
||||
self.comboBox.setSizePolicy(sizePolicy)
|
||||
self.comboBox.setMinimumSize(QSize(128, 0))
|
||||
self.comboBox.setMaximumSize(QSize(256, 32))
|
||||
|
||||
self.gridLayout.addWidget(self.comboBox, 4, 0, 1, 1, Qt.AlignRight)
|
||||
|
||||
MainWindow.setCentralWidget(self.centralwidget)
|
||||
self.menubar = QMenuBar(MainWindow)
|
||||
self.menubar.setObjectName(u"menubar")
|
||||
self.menubar.setGeometry(QRect(0, 0, 1280, 22))
|
||||
MainWindow.setMenuBar(self.menubar)
|
||||
self.statusbar = QStatusBar(MainWindow)
|
||||
self.statusbar.setObjectName(u"statusbar")
|
||||
sizePolicy1 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Maximum)
|
||||
sizePolicy1.setHorizontalStretch(0)
|
||||
sizePolicy1.setVerticalStretch(0)
|
||||
sizePolicy1.setHeightForWidth(self.statusbar.sizePolicy().hasHeightForWidth())
|
||||
self.statusbar.setSizePolicy(sizePolicy1)
|
||||
MainWindow.setStatusBar(self.statusbar)
|
||||
|
||||
self.retranslateUi(MainWindow)
|
||||
|
||||
QMetaObject.connectSlotsByName(MainWindow)
|
||||
# setupUi
|
||||
|
||||
def retranslateUi(self, MainWindow):
|
||||
MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"MainWindow", None))
|
||||
self.backButton.setText(QCoreApplication.translate("MainWindow", u"<", None))
|
||||
self.forwardButton.setText(QCoreApplication.translate("MainWindow", u">", None))
|
||||
self.searchField.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Search Entries", None))
|
||||
self.searchButton.setText(QCoreApplication.translate("MainWindow", u"Search", None))
|
||||
self.comboBox.setCurrentText("")
|
||||
self.comboBox.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Thumbnail Size", None))
|
||||
# retranslateUi
|
||||
|
||||
60
tagstudio/tagstudio.py
Normal file
@@ -0,0 +1,60 @@
|
||||
# Copyright (C) 2024 Travis Abendshien (CyanVoxel).
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
"""TagStudio launcher."""
|
||||
|
||||
from src.core.ts_core import TagStudioCore
|
||||
from src.cli.ts_cli import CliDriver
|
||||
from src.qt.ts_qt import QtDriver
|
||||
import argparse
|
||||
import traceback
|
||||
# import ctypes
|
||||
|
||||
|
||||
def main():
|
||||
# appid = "cyanvoxel.tagstudio.9"
|
||||
# ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(appid)
|
||||
|
||||
# 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('--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)')
|
||||
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 selection based on parameters.
|
||||
if args.ui and args.ui == 'qt':
|
||||
driver = QtDriver(core, args)
|
||||
ui_name='Qt'
|
||||
elif args.ui and args.ui == 'cli':
|
||||
driver = CliDriver(core, args)
|
||||
ui_name='CLI'
|
||||
else:
|
||||
driver = QtDriver(core, args)
|
||||
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...')
|
||||
input()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
0
tagstudio/tests/__init__.py
Normal file
0
tagstudio/tests/core/__init__.py
Normal file
12
tagstudio/tests/core/test_tags.py
Normal file
@@ -0,0 +1,12 @@
|
||||
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)
|
||||
|
||||
def test_empty_construction(self):
|
||||
tag = Tag(id=1, name='', shorthand='', aliases=[], subtags_ids=[], color='')
|
||||
assert (tag)
|
||||