diff --git a/.gitignore b/.gitignore index 0a5d8271..5f5f4002 100644 --- a/.gitignore +++ b/.gitignore @@ -250,5 +250,4 @@ compile_commands.json # TagStudio .TagStudio TagStudio.ini - # End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,python,qt diff --git a/Build_MacOS_app.sh b/Build_MacOS_app.sh new file mode 100755 index 00000000..eeed6b7c --- /dev/null +++ b/Build_MacOS_app.sh @@ -0,0 +1,69 @@ +#! /usr/bin/env bash +# GETTING BASE DIR +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + +# SETTING UP CONSTANTS +TAGSTUDIO_NAME="TagStudio" +TAGSTUDIO_DIR="$SCRIPT_DIR/tagstudio" +TAGSTUDIO_DIR_RESOURCES="$TAGSTUDIO_DIR/resources" +TAGSTUDIO_ICON="$TAGSTUDIO_DIR/resources/icon.ico" +TAGSTUDIO_SRC="$TAGSTUDIO_DIR/src" +TAGSTUDIO_MAIN="$TAGSTUDIO_DIR/tag_studio.py" +DIST_PATH="$SCRIPT_DIR/dist" +BUILD_PATH="$SCRIPT_DIR/build" +LOGS_PATH="$BUILD_PATH/logs" + +printf -- "🏁 Starting Script \n" + +# CREATE VENV AND INSTALL REQUIREMENTS +printf -- "🐍 Creating Python virtual env\n" +python3 -m venv .venv +source .venv/bin/activate + +if [ ! -d $LOGS_PATH ]; then + printf -- "📁 Creating Logs folder\n" + mkdir -p $LOGS_PATH; +fi + +printf -- "💻 Installing Requirements \n" +pip install -r requirements.txt > "$LOGS_PATH/pip.log" 2>&1 +pip install PyInstaller > "$LOGS_PATH/pip.log" 2>&1 + + +if [[ "$OSTYPE" == "darwin"* ]]; then + printf -- "🍏 MacOS Detected \n" + SYS_CMD="--windowed" + OS=0 +fi + +SECONDS=0 + +# CREATE COMMAND +printf -- "⏳ Building App \n" + +COMMAND=$( python -m PyInstaller \ + --name "$TAGSTUDIO_NAME" \ + --icon "$TAGSTUDIO_ICON" \ + --add-data "$TAGSTUDIO_DIR_RESOURCES:./resources" \ + --add-data "$TAGSTUDIO_SRC:./src" \ + --distpath "$DIST_PATH" \ + -p "$TAGSTUDIO_DIR" \ + --noconsole \ + --workpath "$BUILD_PATH" \ + -y "$SYS_CMD" "$TAGSTUDIO_MAIN" \ + > "$LOGS_PATH/pyinstaller.log" 2>&1 ) + +duration=$SECONDS + +if $COMMAND; then + printf -- "✅ Build Successfull \n" + printf -- "⌛ $((duration)) seconds of build\n" + if [[ "$OS" == 0 ]]; then + printf -- "📁 Opening App folder \n" + open $DIST_PATH + fi +else + printf -- "❌ Error Building the app\nPlease read the logs\navailable at build/logs\n" +fi + +printf -- "🏁 END OF TRANSMISSION" diff --git a/Build_win.bat b/Build_win.bat new file mode 100644 index 00000000..3e99fdcf --- /dev/null +++ b/Build_win.bat @@ -0,0 +1,31 @@ +@echo off +set TAGSTUDIO_NAME=TagStudio +set TAGSTUDIO_DIR=tagstudio +set TAGSTUDIO_DIR_RESOURCES=%TAGSTUDIO_DIR%/resources +set TAGSTUDIO_ICON=%TAGSTUDIO_DIR%/resources/icon.ico +set TAGSTUDIO_SRC=%TAGSTUDIO_DIR%/src +set TAGSTUDIO_MAIN=%TAGSTUDIO_DIR%/tag_studio.py +set BUILD_MODE=--onedir + + +if "%1" == "--help" ( + echo run "%~nx0" for normal Build + echo run "%~nx0 --portable" for Build packaged into one file + goto end +) +if "%1" == "--portable" ( + echo Building portable executable... + set BUILD_MODE=--onefile + goto run +) +if not "%1" == "" ( + echo Invalid argument run "%~nx0 --help" for help + goto end +) +:run +echo Building executable... +set COMMAND=PyInstaller --name "%TAGSTUDIO_NAME%" --icon "%TAGSTUDIO_ICON%" --add-data "%TAGSTUDIO_DIR_RESOURCES%:./resources" --add-data "%TAGSTUDIO_SRC%:./src" -p "%TAGSTUDIO_DIR%" --console %BUILD_MODE% "%TAGSTUDIO_MAIN%" -y +call .venv\Scripts\activate.bat +%COMMAND% +deactivate +:end diff --git a/requirements.txt b/requirements.txt index 0456b920..7c1b4c35 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,3 +7,4 @@ PySide6_Addons>=6.5.1.1,<=6.6.3.1 PySide6_Essentials>=6.5.1.1,<=6.6.3.1 typing_extensions>=3.10.0.0,<=4.11.0 ujson>=5.8.0,<=5.9.0 +Pyinstaller==6.6.0 diff --git a/tagstudio/src/core/palette.py b/tagstudio/src/core/palette.py index 0faf3277..dc67b093 100644 --- a/tagstudio/src/core/palette.py +++ b/tagstudio/src/core/palette.py @@ -15,11 +15,11 @@ class ColorType(Enum): _TAG_COLORS = { "": { - ColorType.PRIMARY: "#1E1A33", + ColorType.PRIMARY: "#1e1e1e", ColorType.TEXT: ColorType.LIGHT_ACCENT, - ColorType.BORDER: "#2B2547", - ColorType.LIGHT_ACCENT: "#CDA7F7", - ColorType.DARK_ACCENT: "#1E1A33", + ColorType.BORDER: "#333333", + ColorType.LIGHT_ACCENT: "#FFFFFF", + ColorType.DARK_ACCENT: "#222222", }, "black": { ColorType.PRIMARY: "#111018", diff --git a/tagstudio/src/qt/main_window.py b/tagstudio/src/qt/main_window.py index 8055d930..e285b48b 100644 --- a/tagstudio/src/qt/main_window.py +++ b/tagstudio/src/qt/main_window.py @@ -38,54 +38,29 @@ class Ui_MainWindow(QMainWindow): # # 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) @@ -102,12 +77,6 @@ class Ui_MainWindow(QMainWindow): 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) @@ -118,7 +87,6 @@ class Ui_MainWindow(QMainWindow): # 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) @@ -154,13 +122,6 @@ class Ui_MainWindow(QMainWindow): 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) @@ -174,7 +135,6 @@ class Ui_MainWindow(QMainWindow): 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) @@ -213,7 +173,6 @@ class Ui_MainWindow(QMainWindow): self.frame_layout.addWidget(menu_bar) self.retranslateUi(MainWindow) - # self.dumpObjectTree() QMetaObject.connectSlotsByName(MainWindow) # setupUi diff --git a/tagstudio/src/qt/modals/folders_to_tags.py b/tagstudio/src/qt/modals/folders_to_tags.py index bfb0f8d6..5163e412 100644 --- a/tagstudio/src/qt/modals/folders_to_tags.py +++ b/tagstudio/src/qt/modals/folders_to_tags.py @@ -165,25 +165,26 @@ class FoldersToTagsModal(QWidget): self.count = -1 self.filename = "" - self.setWindowTitle(f"Folders To Tags") + self.setWindowTitle(f"Create Tags From Folders") self.setWindowModality(Qt.WindowModality.ApplicationModal) - self.setMinimumSize(500, 800) + self.setMinimumSize(640, 640) self.root_layout = QVBoxLayout(self) self.root_layout.setContentsMargins(6, 6, 6, 6) + self.title_widget = QLabel() + self.title_widget.setObjectName("title") + self.title_widget.setWordWrap(True) + self.title_widget.setStyleSheet( + "font-weight:bold;" "font-size:14px;" "padding-top: 6px" + ) + self.title_widget.setText("Create Tags From Folders") + self.title_widget.setAlignment(Qt.AlignmentFlag.AlignCenter) + self.desc_widget = QLabel() self.desc_widget.setObjectName("descriptionLabel") self.desc_widget.setWordWrap(True) - self.desc_widget.setStyleSheet( - # 'background:blue;' - "text-align:left;" - # 'font-weight:bold;' - "font-size:18px;" - # 'padding-top: 6px' - "" - ) self.desc_widget.setText( - """Creates tags based on the folder structure and applies them to entries.\n The Structure below shows all the tags that would be added and to which files they would be added. It being empty means that there are no Tag to be created or assigned""" + """Creates tags based on your folder structure and applies them to your entries.\n The structure below shows all the tags that will be created and what entries they will be applied to.""" ) self.desc_widget.setAlignment(Qt.AlignmentFlag.AlignCenter) @@ -216,14 +217,18 @@ class FoldersToTagsModal(QWidget): self.apply_button = QPushButton() self.apply_button.setText("&Apply") + self.apply_button.setMinimumWidth(100) self.apply_button.clicked.connect(self.on_apply) self.showEvent = self.on_open + self.root_layout.addWidget(self.title_widget) self.root_layout.addWidget(self.desc_widget) self.root_layout.addWidget(self.open_close_button_w) self.root_layout.addWidget(self.scroll_area) - self.root_layout.addWidget(self.apply_button) + self.root_layout.addWidget( + self.apply_button, alignment=Qt.AlignmentFlag.AlignCenter + ) def on_apply(self, event): folders_to_tags(self.library) diff --git a/tagstudio/src/qt/pagination.py b/tagstudio/src/qt/pagination.py index bf4a8b57..64b159fa 100644 --- a/tagstudio/src/qt/pagination.py +++ b/tagstudio/src/qt/pagination.py @@ -133,6 +133,10 @@ class Pagination(QWidget, QObject): # self.update_buttons(page_count=9, index=0) def update_buttons(self, page_count: int, index: int, emit: bool = True): + # Guard + if index < 0: + raise ValueError("Negative index detected") + # Screw it for i in range(0, 10): if self.start_buffer_layout.itemAt(i): @@ -140,6 +144,8 @@ class Pagination(QWidget, QObject): if self.end_buffer_layout.itemAt(i): self.end_buffer_layout.itemAt(i).widget().setHidden(True) + end_page = page_count - 1 + if page_count <= 1: # Hide everything if there are only one or less pages. # [-------------- HIDDEN --------------] @@ -172,7 +178,7 @@ class Pagination(QWidget, QObject): # 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: + if index == end_page: self.next_button.setDisabled(True) # self.end_buffer_layout.setContentsMargins(0,0,0,0) else: @@ -181,201 +187,22 @@ class Pagination(QWidget, QObject): 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()) + # I do not know where these magic values were derived from, but + # this is better than the chain elif's that were here before + if 8 <= page_count <= 11: + end_scale = max(1, page_count - index - 6) + srt_scale = max(1, index - 5) 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()) + end_scale = max(1, 7 - index) + srt_scale = max(1, (7 - (end_page - index))) + + if page_count >= 8: + end_size = self.button_size.width() * end_scale + (3 * (end_scale - 1)) + srt_size = self.button_size.width() * srt_scale + (3 * (srt_scale - 1)) + self.end_ellipses.setMinimumWidth(end_size) + self.end_ellipses.setMaximumWidth(end_size) + self.start_ellipses.setMinimumWidth(srt_size) + self.start_ellipses.setMaximumWidth(srt_size) # Enable/Disable Ellipses # if index <= max(self.buffer_page_count, 5)+1: diff --git a/tagstudio/src/qt/resources_rc.py b/tagstudio/src/qt/resources_rc.py index 97845b94..aca38cd1 100644 --- a/tagstudio/src/qt/resources_rc.py +++ b/tagstudio/src/qt/resources_rc.py @@ -16901,17 +16901,10 @@ qt_resource_struct = b"\ \x00\x00\x01\x8a\xd2\x83?\x9d\ " - def qInitResources(): - QtCore.qRegisterResourceData( - 0x03, qt_resource_struct, qt_resource_name, qt_resource_data - ) - + QtCore.qRegisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) def qCleanupResources(): - QtCore.qUnregisterResourceData( - 0x03, qt_resource_struct, qt_resource_name, qt_resource_data - ) - + QtCore.qUnregisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) qInitResources() diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index 114651df..05f7eb94 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -13,6 +13,7 @@ import math import os import sys import time +import webbrowser from datetime import datetime as dt from pathlib import Path from queue import Empty, Queue @@ -132,19 +133,20 @@ class NavigationState: class Consumer(QThread): + MARKER_QUIT = "MARKER_QUIT" + def __init__(self, queue) -> None: self.queue = queue QThread.__init__(self) def run(self): - self.active = True - while self.active: + while True: try: - job = self.queue.get(timeout=0.2) - # print('Running job...') - # logging.info(*job[1]) + job = self.queue.get() + if job == self.MARKER_QUIT: + break job[0](*job[1]) - except (Empty, RuntimeError): + except RuntimeError: pass def set_page_count(self, count: int): @@ -179,7 +181,7 @@ class QtDriver(QObject): # self.title_text: str = self.base_title # self.buffer = {} self.thumb_job_queue: Queue = Queue() - self.thumb_threads = [] + self.thumb_threads: list[Consumer] = [] self.thumb_cutoff: float = time.time() # self.selected: list[tuple[int,int]] = [] # (Thumb Index, Page Index) self.selected: list[tuple[ItemType, int]] = [] # (Item Type, Item ID) @@ -264,8 +266,11 @@ class QtDriver(QObject): self.splash.show() menu_bar = self.main_window.menuBar() - menu_bar.setNativeMenuBar(False) - # menu_bar.setStyleSheet('background:#00000000;') + + # Allow the use of the native macOS menu bar. + if sys.platform != "darwin": + menu_bar.setNativeMenuBar(False) + file_menu = QMenu("&File", menu_bar) edit_menu = QMenu("&Edit", menu_bar) tools_menu = QMenu("&Tools", menu_bar) @@ -354,13 +359,13 @@ class QtDriver(QObject): edit_menu.addSeparator() - manage_file_extensions_action = QAction("Ignore File Extensions", menu_bar) + manage_file_extensions_action = QAction("Ignored File Extensions", menu_bar) manage_file_extensions_action.triggered.connect( lambda: self.show_file_extension_modal() ) edit_menu.addAction(manage_file_extensions_action) - tag_database_action = QAction("Tag Database", menu_bar) + tag_database_action = QAction("Manage Tags", menu_bar) tag_database_action.triggered.connect(lambda: self.show_tag_database()) edit_menu.addAction(tag_database_action) @@ -410,11 +415,18 @@ class QtDriver(QObject): self.sort_fields_action.setToolTip("Alt+S") macros_menu.addAction(self.sort_fields_action) - folders_to_tags_action = QAction("Folders to Tags", menu_bar) + folders_to_tags_action = QAction("Create Tags From Folders", menu_bar) ftt_modal = FoldersToTagsModal(self.lib, self) folders_to_tags_action.triggered.connect(lambda: ftt_modal.show()) macros_menu.addAction(folders_to_tags_action) + # Help Menu ========================================================== + self.repo_action = QAction("Visit GitHub Repository", menu_bar) + self.repo_action.triggered.connect( + lambda: webbrowser.open("https://github.com/TagStudioDev/TagStudio") + ) + help_menu.addAction(self.repo_action) + self.set_macro_menu_viability() menu_bar.addMenu(file_menu) @@ -545,10 +557,14 @@ class QtDriver(QObject): self.settings.setValue("last_library", self.lib.library_dir) self.settings.sync() logging.info("[SHUTDOWN] Ending Thumbnail Threads...") + for _ in self.thumb_threads: + self.thumb_job_queue.put(Consumer.MARKER_QUIT) + + # wait for threads to quit for thread in self.thumb_threads: - thread.active = False thread.quit() thread.wait() + QApplication.quit() def save_library(self, show_status=True): @@ -630,7 +646,7 @@ class QtDriver(QObject): def show_tag_database(self): self.modal = PanelModal( - TagDatabasePanel(self.lib), "Tag Database", "Tag Database", has_save=False + TagDatabasePanel(self.lib), "Library Tags", "Library Tags", has_save=False ) self.modal.show() diff --git a/tagstudio/src/qt/widgets/item_thumb.py b/tagstudio/src/qt/widgets/item_thumb.py index dc4a4770..5bfd870f 100644 --- a/tagstudio/src/qt/widgets/item_thumb.py +++ b/tagstudio/src/qt/widgets/item_thumb.py @@ -23,7 +23,8 @@ from PySide6.QtWidgets import ( QCheckBox, ) -from src.core.library import ItemType, Library + +from src.core.library import ItemType, Library, Entry from src.core.constants import AUDIO_TYPES, VIDEO_TYPES, IMAGE_TYPES from src.qt.flowlayout import FlowWidget from src.qt.helpers import FileOpenerHelper @@ -32,11 +33,14 @@ from src.qt.widgets import ThumbRenderer, ThumbButton if typing.TYPE_CHECKING: from src.qt.widgets import PreviewPanel - ERROR = f"[ERROR]" WARNING = f"[WARNING]" INFO = f"[INFO]" +DEFAULT_META_TAG_FIELD = 8 +TAG_FAVORITE = 1 +TAG_ARCHIVED = 0 + logging.basicConfig(format="%(message)s", level=logging.INFO) @@ -62,7 +66,7 @@ class ItemThumb(FlowWidget): tag_group_icon_128.load() small_text_style = ( - f"background-color:rgba(0, 0, 0, 128);" + f"background-color:rgba(0, 0, 0, 192);" f"font-family:Oxanium;" f"font-weight:bold;" f"font-size:12px;" @@ -74,7 +78,7 @@ class ItemThumb(FlowWidget): ) med_text_style = ( - f"background-color:rgba(17, 15, 27, 192);" + f"background-color:rgba(0, 0, 0, 192);" f"font-family:Oxanium;" f"font-weight:bold;" f"font-size:18px;" @@ -453,91 +457,40 @@ class ItemThumb(FlowWidget): self.show_check_badges(False) return super().leaveEvent(event) - def on_archived_check(self, value: bool): - # logging.info(f'Archived Check: {value}, Mode: {self.mode}') + def on_archived_check(self, toggle_value: bool): if self.mode == ItemType.ENTRY: - self.isArchived = value - DEFAULT_META_TAG_FIELD = 8 - temp = (ItemType.ENTRY, self.item_id) - if ( - list(self.panel.driver.selected).count(temp) > 0 - ): # Is the archived badge apart of the selection? - # Yes, then add archived tag to all selected. - for x in self.panel.driver.selected: - e = self.lib.get_entry(x[1]) - if value: - self.archived_badge.setHidden(False) - e.add_tag( - self.panel.driver.lib, - 0, - field_id=DEFAULT_META_TAG_FIELD, - field_index=-1, - ) - else: - e.remove_tag(self.panel.driver.lib, 0) - else: - # No, then add archived tag to the entry this badge is on. - e = self.lib.get_entry(self.item_id) - if value: - self.favorite_badge.setHidden(False) - e.add_tag( - self.panel.driver.lib, - 0, - field_id=DEFAULT_META_TAG_FIELD, - field_index=-1, - ) - else: - e.remove_tag(self.panel.driver.lib, 0) - if self.panel.isOpen: - self.panel.update_widgets() - self.panel.driver.update_badges() + self.isArchived = toggle_value + self.toggle_item_tag(toggle_value, TAG_ARCHIVED) - # def on_archived_uncheck(self): - # if self.mode == SearchItemType.ENTRY: - # self.isArchived = False - # e = self.lib.get_entry(self.item_id) - - def on_favorite_check(self, value: bool): - # logging.info(f'Favorite Check: {value}, Mode: {self.mode}') + def on_favorite_check(self, toggle_value: bool): if self.mode == ItemType.ENTRY: - self.isFavorite = value - DEFAULT_META_TAG_FIELD = 8 - temp = (ItemType.ENTRY, self.item_id) - if ( - list(self.panel.driver.selected).count(temp) > 0 - ): # Is the favorite badge apart of the selection? - # Yes, then add favorite tag to all selected. - for x in self.panel.driver.selected: - e = self.lib.get_entry(x[1]) - if value: - self.favorite_badge.setHidden(False) - e.add_tag( - self.panel.driver.lib, - 1, - field_id=DEFAULT_META_TAG_FIELD, - field_index=-1, - ) - else: - e.remove_tag(self.panel.driver.lib, 1) - else: - # No, then add favorite tag to the entry this badge is on. - e = self.lib.get_entry(self.item_id) - if value: - self.favorite_badge.setHidden(False) - e.add_tag( - self.panel.driver.lib, - 1, - field_id=DEFAULT_META_TAG_FIELD, - field_index=-1, - ) - else: - e.remove_tag(self.panel.driver.lib, 1) - if self.panel.isOpen: - self.panel.update_widgets() - self.panel.driver.update_badges() + self.isFavorite = toggle_value + self.toggle_item_tag(toggle_value, TAG_FAVORITE) - # def on_favorite_uncheck(self): - # if self.mode == SearchItemType.ENTRY: - # self.isFavorite = False - # e = self.lib.get_entry(self.item_id) - # e.remove_tag(1) + def toggle_item_tag(self, toggle_value: bool, tag_id: int): + def toggle_tag(entry: Entry): + if toggle_value: + self.favorite_badge.setHidden(False) + entry.add_tag( + self.panel.driver.lib, + tag_id, + field_id=DEFAULT_META_TAG_FIELD, + field_index=-1, + ) + else: + entry.remove_tag(self.panel.driver.lib, tag_id) + + # Is the badge a part of the selection? + if (ItemType.ENTRY, self.item_id) in self.panel.driver.selected: + # Yes, add chosen tag to all selected. + for _, item_id in self.panel.driver.selected: + entry = self.lib.get_entry(item_id) + toggle_tag(entry) + else: + # No, add tag to the entry this badge is on. + entry = self.lib.get_entry(self.item_id) + toggle_tag(entry) + + if self.panel.isOpen: + self.panel.update_widgets() + self.panel.driver.update_badges() diff --git a/tagstudio/src/qt/widgets/panel.py b/tagstudio/src/qt/widgets/panel.py index 7ba3d4d2..7221e73d 100644 --- a/tagstudio/src/qt/widgets/panel.py +++ b/tagstudio/src/qt/widgets/panel.py @@ -38,9 +38,7 @@ class PanelModal(QWidget): self.title_widget.setObjectName("fieldTitle") self.title_widget.setWordWrap(True) self.title_widget.setStyleSheet( - # 'background:blue;' - # 'text-align:center;' - "font-weight:bold;" "font-size:14px;" "padding-top: 6px" "" + "font-weight:bold;" "font-size:14px;" "padding-top: 6px" ) self.title_widget.setText(title) self.title_widget.setAlignment(Qt.AlignmentFlag.AlignCenter) diff --git a/tagstudio/src/qt/widgets/preview_panel.py b/tagstudio/src/qt/widgets/preview_panel.py index d1e7c17c..7505283d 100644 --- a/tagstudio/src/qt/widgets/preview_panel.py +++ b/tagstudio/src/qt/widgets/preview_panel.py @@ -136,19 +136,20 @@ class PreviewPanel(QWidget): self.dimensions_label.setWordWrap(True) # self.dim_label.setTextInteractionFlags( # Qt.TextInteractionFlag.TextSelectableByMouse) - self.dimensions_label.setStyleSheet(ItemThumb.small_text_style) - # small_text_style = ( - # f'background-color:rgba(17, 15, 27, 192);' - # f'font-family:Oxanium;' - # f'font-weight:bold;' - # f'font-size:12px;' - # f'border-radius:3px;' - # f'padding-top: 4px;' - # f'padding-right: 1px;' - # f'padding-bottom: 1px;' - # f'padding-left: 1px;' - # ) + properties_style = ( + f"background-color:#65000000;" + f"font-family:Oxanium;" + f"font-weight:bold;" + f"font-size:12px;" + f"border-radius:6px;" + f"padding-top: 4px;" + f"padding-right: 1px;" + f"padding-bottom: 1px;" + f"padding-left: 1px;" + ) + + self.dimensions_label.setStyleSheet(properties_style) self.scroll_layout = QVBoxLayout() self.scroll_layout.setAlignment(Qt.AlignmentFlag.AlignTop) @@ -157,20 +158,14 @@ class PreviewPanel(QWidget): scroll_container: QWidget = QWidget() scroll_container.setObjectName("entryScrollContainer") scroll_container.setLayout(self.scroll_layout) - # scroll_container.setStyleSheet('background:#080716; border-radius:12px;') - scroll_container.setStyleSheet( - "background:#00000000;" - "border-style:none;" - f"QScrollBar::{{background:red;}}" - ) info_section = QWidget() info_layout = QVBoxLayout(info_section) info_layout.setContentsMargins(0, 0, 0, 0) info_layout.setSpacing(6) - self.setStyleSheet("background:#00000000;" f"QScrollBar::{{background:red;}}") scroll_area = QScrollArea() + scroll_area.setObjectName("entryScrollArea") scroll_area.setSizePolicy( QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding ) @@ -178,15 +173,15 @@ class PreviewPanel(QWidget): scroll_area.setWidgetResizable(True) scroll_area.setFrameShadow(QFrame.Shadow.Plain) scroll_area.setFrameShape(QFrame.Shape.NoFrame) + # NOTE: I would rather have this style applied to the scroll_area + # background and NOT the scroll container background, so that the + # rounded corners are maintained when scrolling. I was unable to + # find the right trick to only select that particular element. scroll_area.setStyleSheet( - "background:#55000000;" - "border-radius:12px;" - "border-style:solid;" - "border-width:1px;" - "border-color:#11FFFFFF;" - # f'QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal {{border: none;background: none;}}' - # f'QScrollBar::left-arrow:horizontal, QScrollBar::right-arrow:horizontal, QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal {{border: none;background: none;color: none;}}' - f"QScrollBar::{{background:red;}}" + f"QWidget#entryScrollContainer{{" + "background:#65000000;" + "border-radius:6px;" + f"}}" ) scroll_area.setWidget(scroll_container) @@ -207,29 +202,6 @@ class PreviewPanel(QWidget): self.add_field_button.setMinimumSize(96, 28) self.add_field_button.setMaximumSize(96, 28) self.add_field_button.setText("Add Field") - self.add_field_button.setStyleSheet( - f"QPushButton{{" - # f'background: #1E1A33;' - # f'color: #CDA7F7;' - f"font-weight: bold;" - # f"border-color: #2B2547;" - f"border-radius: 6px;" - f"border-style:solid;" - # f'border-width:{math.ceil(1*self.devicePixelRatio())}px;' - "background:#55000000;" - "border-width:1px;" - "border-color:#11FFFFFF;" - # f'padding-top: 1.5px;' - # f'padding-right: 4px;' - # f'padding-bottom: 5px;' - # f'padding-left: 4px;' - f"font-size: 13px;" - f"}}" - f"QPushButton::hover" - f"{{" - f"background: #333333;" - f"}}" - ) self.afb_layout.addWidget(self.add_field_button) self.afm = AddFieldModal(self.lib) self.place_add_field_button() @@ -398,7 +370,7 @@ class PreviewPanel(QWidget): if extension in IMAGE_TYPES: image = Image.open(filepath) if image.mode == "RGBA": - new_bg = Image.new("RGB", image.size, color="#222222") + new_bg = Image.new("RGB", image.size, color="#1e1e1e") new_bg.paste(image, mask=image.getchannel(3)) image = new_bg if image.mode != "RGB": diff --git a/tagstudio/src/qt/widgets/tag_box.py b/tagstudio/src/qt/widgets/tag_box.py index a6fd71c0..86fc25b0 100644 --- a/tagstudio/src/qt/widgets/tag_box.py +++ b/tagstudio/src/qt/widgets/tag_box.py @@ -52,10 +52,10 @@ class TagBoxWidget(FieldWidget): self.add_button.setText("+") self.add_button.setStyleSheet( f"QPushButton{{" - # f'background: #1E1A33;' - # f'color: #CDA7F7;' + f"background: #1e1e1e;" + f"color: #FFFFFF;" f"font-weight: bold;" - # f"border-color: #2B2547;" + f"border-color: #333333;" f"border-radius: 6px;" f"border-style:solid;" f"border-width:{math.ceil(1*self.devicePixelRatio())}px;" @@ -67,7 +67,8 @@ class TagBoxWidget(FieldWidget): f"}}" f"QPushButton::hover" f"{{" - # f'background: #2B2547;' + f"border-color: #CCCCCC;" + f"background: #555555;" f"}}" ) tsp = TagSearchPanel(self.lib) diff --git a/tagstudio/src/qt/widgets/thumb_renderer.py b/tagstudio/src/qt/widgets/thumb_renderer.py index 8d45ee40..64f397e6 100644 --- a/tagstudio/src/qt/widgets/thumb_renderer.py +++ b/tagstudio/src/qt/widgets/thumb_renderer.py @@ -19,11 +19,13 @@ from PIL import ( ImageFont, ImageEnhance, ImageOps, + ImageFile, ) from PySide6.QtCore import QObject, Signal, QSize from PySide6.QtGui import QPixmap from src.core.constants import PLAINTEXT_TYPES, VIDEO_TYPES, IMAGE_TYPES +ImageFile.LOAD_TRUNCATED_IMAGES = True ERROR = f"[ERROR]" WARNING = f"[WARNING]" @@ -140,7 +142,7 @@ class ThumbRenderer(QObject): # image = self.thumb_debug if image.mode == "RGBA": # logging.info(image.getchannel(3).tobytes()) - new_bg = Image.new("RGB", image.size, color="#222222") + new_bg = Image.new("RGB", image.size, color="#1e1e1e") new_bg.paste(image, mask=image.getchannel(3)) image = new_bg if image.mode != "RGB": @@ -171,7 +173,7 @@ class ThumbRenderer(QObject): text: str = extension with open(filepath, "r", encoding="utf-8") as text_file: text = text_file.read(256) - bg = Image.new("RGB", (256, 256), color="#222222") + bg = Image.new("RGB", (256, 256), color="#1e1e1e") draw = ImageDraw.Draw(bg) draw.text((16, 16), text, file=(255, 255, 255)) image = bg @@ -323,7 +325,7 @@ class ThumbRenderer(QObject): # image = self.thumb_debug if image.mode == "RGBA": # logging.info(image.getchannel(3).tobytes()) - new_bg = Image.new("RGB", image.size, color="#222222") + new_bg = Image.new("RGB", image.size, color="#1e1e1e") new_bg.paste(image, mask=image.getchannel(3)) image = new_bg if image.mode != "RGB": @@ -353,7 +355,7 @@ class ThumbRenderer(QObject): text: str = extension with open(filepath, "r", encoding="utf-8") as text_file: text = text_file.read(256) - bg = Image.new("RGB", (256, 256), color="#222222") + bg = Image.new("RGB", (256, 256), color="#1e1e1e") draw = ImageDraw.Draw(bg) draw.text((16, 16), text, file=(255, 255, 255)) image = bg