Alpha v9.1.0

Initial public release
This commit is contained in:
Travis Abendshien
2024-04-22 11:52:22 -07:00
parent bb5dff7daa
commit d63a978fb0
59 changed files with 44943 additions and 4 deletions

0
tagstudio/__init__.py Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 628 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 992 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

3300
tagstudio/src/cli/ts_cli.py Normal file

File diff suppressed because it is too large Load Diff

View File

View 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

File diff suppressed because it is too large Load Diff

View 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'

View 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 ''

View File

View 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

View 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(' ', '')

View 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

View File

View 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())

View 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)

View 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

File diff suppressed because it is too large Load Diff

View 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>

File diff suppressed because it is too large Load Diff

4475
tagstudio/src/qt/ts_qt.py Normal file

File diff suppressed because it is too large Load Diff

212
tagstudio/src/qt/ui/home.ui Normal file
View 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>&lt;</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>&gt;</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>

View 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
View 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()

View File

View File

View 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)