Merge remote-tracking branch 'upstream/main' into bugfix/right-click-file-path/#116

This commit is contained in:
gabrieljreed 2024-05-01 21:56:29 -07:00
commit 0e1e9e924b
8 changed files with 138 additions and 154 deletions

View file

@ -23,6 +23,7 @@ from src.core.ts_core import *
from src.core.utils.web import *
from src.core.utils.fs import *
from src.core.library import *
from src.qt.helpers.file_opener import open_file
WHITE_FG = '\033[37m'
WHITE_BG = '\033[47m'
@ -352,8 +353,8 @@ class CliDriver:
if not os.path.isfile(external_preview_path):
temp = self.external_preview_default
temp.save(external_preview_path)
if os.path.isfile(external_preview_path):
os.startfile(external_preview_path)
open_file(external_preview_path)
def set_external_preview_default(self) -> None:
"""Sets the external preview to its default image."""
@ -1703,11 +1704,9 @@ class CliDriver:
elif (com[0].lower() == 'open' or com[0].lower() == 'o'):
if len(com) > 1:
if com[1].lower() == 'location' or com[1].lower() == 'l':
args = ['explorer', '/select,', filename]
subprocess.call(args)
open_file(filename, True)
else:
if os.path.isfile(filename):
os.startfile(filename)
open_file(filename)
# refresh=False
# self.scr_browse_entries_gallery(index)
# Add Field ============================================================
@ -2152,9 +2151,8 @@ class CliDriver:
# Open =============================================================
elif (com[0].lower() == 'open' or com[0].lower() == 'o'):
for match in self.lib.missing_matches[filename]:
fn = f'{os.path.normpath(self.lib.library_dir + "/" + match + "/" + entry.filename)}'
if os.path.isfile(fn):
os.startfile(fn)
fn = os.path.normpath(self.lib.library_dir + "/" + match + "/" + entry.filename)
open_file(fn)
refresh = False
# clear()
# return self.scr_choose_missing_match(index, clear_scr=False)
@ -2274,10 +2272,9 @@ class CliDriver:
elif (com[0].lower() == 'open' or com[0].lower() == 'o'):
# for match in self.lib.missing_matches[filename]:
# fn = f'{os.path.normpath(self.lib.library_dir + "/" + match + "/" + entry_1.filename)}'
# if os.path.isfile(fn):
# os.startfile(fn)
os.startfile(dupe[0])
os.startfile(dupe[1])
# open_file(fn)
open_file(dupe[0])
open_file(dupe[1])
# clear()
# return self.scr_resolve_dupe_files(index, clear_scr=False)
# Mirror Entries ===================================================
@ -2385,8 +2382,7 @@ class CliDriver:
# Open with Default Application ========================================
if (com[0].lower() == 'open' or com[0].lower() == 'o'):
if os.path.isfile(filename):
os.startfile(filename)
open_file(filename)
# self.scr_edit_entry_tag_box(entry_index, field_index)
# return
# Close View ===========================================================

View file

@ -1577,7 +1577,8 @@ class Library:
# NOTE: I'd expect a blank query to return all with the other implementation, but
# it misses stuff like Archive (id 0) so here's this as a catch-all.
if query == '':
query = query.strip()
if not query:
all: list[int] = []
for tag in self.tags:
if ignore_builtin and tag.id >= 1000:

View file

@ -1,4 +1,3 @@
from .open_file import open_file
from .file_opener import FileOpenerHelper, FileOpenerLabel
from .file_opener import open_file, FileOpenerHelper, FileOpenerLabel
from .function_iterator import FunctionIterator
from .custom_runnable import CustomRunnable
from .custom_runnable import CustomRunnable

View file

@ -5,7 +5,9 @@
import logging
import os
import subprocess
import shutil
import sys
import traceback
from PySide6.QtWidgets import QLabel
from PySide6.QtCore import Qt
@ -17,7 +19,49 @@ INFO = f'[INFO]'
logging.basicConfig(format="%(message)s", level=logging.INFO)
class FileOpenerHelper():
def open_file(path: str, file_manager: bool = False):
logging.info(f'Opening file: {path}')
if not os.path.exists(path):
logging.error(f'File not found: {path}')
return
try:
if sys.platform == "win32":
normpath = os.path.normpath(path)
if file_manager:
command_name = "explorer"
command_args = [f"/select,{normpath}"]
else:
command_name = "start"
# first parameter is for title, NOT filepath
command_args = ["", normpath]
subprocess.Popen([command_name] + command_args, shell=True, close_fds=True, creationflags=subprocess.CREATE_NEW_PROCESS_GROUP | subprocess.CREATE_BREAKAWAY_FROM_JOB)
else:
if sys.platform == "darwin":
command_name = "open"
command_args = [path]
if file_manager:
# will reveal in Finder
command_args.append("-R")
else:
if file_manager:
command_name = "dbus-send"
# might not be guaranteed to launch default?
command_args = ["--session", "--dest=org.freedesktop.FileManager1", "--type=method_call",
"/org/freedesktop/FileManager1", "org.freedesktop.FileManager1.ShowItems",
f"array:string:file://{path}", "string:"]
else:
command_name = "xdg-open"
command_args = [path]
command = shutil.which(command_name)
if command is not None:
subprocess.Popen([command] + command_args, close_fds=True)
else:
logging.info(f"Could not find {command_name} on system PATH")
except:
traceback.print_exc()
class FileOpenerHelper:
def __init__(self, filepath:str):
self.filepath = filepath
@ -25,40 +69,10 @@ class FileOpenerHelper():
self.filepath = filepath
def open_file(self):
"""Open the file in the default program."""
if not os.path.exists(self.filepath):
logging.error(f'File not found: {self.filepath}')
return
if sys.platform == 'win32':
os.startfile(self.filepath)
elif sys.platform == 'linux':
subprocess.run(['xdg-open', self.filepath])
elif sys.platform == 'darwin':
subprocess.run(['open', self.filepath])
open_file(self.filepath)
def open_explorer(self):
"""Open the file in the default file explorer."""
if os.path.exists(self.filepath):
logging.info(f'Opening file: {self.filepath}')
if os.name == 'nt': # Windows
command = f'explorer /select,"{self.filepath}"'
subprocess.run(command, shell=True)
elif sys.platform == 'linux':
command = f'nautilus --select "{self.filepath}"' # Adjust for your Linux file manager if different
if subprocess.run(command, shell=True).returncode == 0:
file_loc = os.path.dirname(self.filepath)
file_loc = os.path.normpath(file_loc)
os.startfile(file_loc)
elif sys.platform == 'darwin':
command = f'open -R "{self.filepath}"'
result = subprocess.run(command, shell=True)
if result.returncode == 0:
logging.info('Opening file in Finder')
else:
logging.error(f'Failed to open file in Finder: {self.filepath}')
else:
logging.error(f'File not found: {self.filepath}')
open_file(self.filepath, True)
class FileOpenerLabel(QLabel):

View file

@ -1,49 +0,0 @@
# Copyright (C) 2024 Travis Abendshien (CyanVoxel).
# Licensed under the GPL-3.0 License.
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
import logging
import os
import sys
import traceback
import shutil
import subprocess
def open_file(path: str, file_manager: bool = False):
try:
if sys.platform == "win32":
normpath = os.path.normpath(path)
if file_manager:
command_name = "explorer"
command_args = [f"/select,{normpath}"]
else:
command_name = "start"
# first parameter is for title, NOT filepath
command_args = ["", normpath]
subprocess.Popen([command_name] + command_args, shell=True, close_fds=True, creationflags=subprocess.CREATE_NEW_PROCESS_GROUP | subprocess.CREATE_BREAKAWAY_FROM_JOB)
else:
if sys.platform == "darwin":
command_name = "open"
command_args = [path]
if file_manager:
# will reveal in Finder
command_args.append("-R")
else:
if file_manager:
command_name = "dbus-send"
# might not be guaranteed to launch default?
command_args = ["--session", "--dest=org.freedesktop.FileManager1", "--type=method_call",
"/org/freedesktop/FileManager1", "org.freedesktop.FileManager1.ShowItems",
f"array:string:file://{path}", "string:"]
else:
command_name = "xdg-open"
command_args = [path]
command = shutil.which(command_name)
if command is not None:
subprocess.Popen([command] + command_args, close_fds=True)
else:
logging.info(f"Could not find {command_name} on system PATH")
except:
traceback.print_exc()

View file

@ -27,7 +27,7 @@ class TagSearchPanel(PanelWidget):
super().__init__()
self.lib: Library = library
# self.callback = callback
self.first_tag_id = -1
self.first_tag_id = None
self.tag_limit = 30
# self.selected_tag: int = 0
self.setMinimumSize(300, 400)
@ -68,6 +68,7 @@ class TagSearchPanel(PanelWidget):
self.root_layout.addWidget(self.search_field)
self.root_layout.addWidget(self.scroll_area)
self.update_tags('')
# def reset(self):
# self.search_field.setText('')
@ -75,7 +76,7 @@ class TagSearchPanel(PanelWidget):
# self.search_field.setFocus()
def on_return(self, text:str):
if text and self.first_tag_id >= 0:
if text and self.first_tag_id is not None:
# callback(self.first_tag_id)
self.tag_chosen.emit(self.first_tag_id)
self.search_field.setText('')
@ -87,57 +88,52 @@ class TagSearchPanel(PanelWidget):
def update_tags(self, query:str):
# for c in self.scroll_layout.children():
# c.widget().deleteLater()
while self.scroll_layout.itemAt(0):
while self.scroll_layout.count():
# logging.info(f"I'm deleting { self.scroll_layout.itemAt(0).widget()}")
self.scroll_layout.takeAt(0).widget().deleteLater()
if query:
first_id_set = False
for tag_id in self.lib.search_tags(query, include_cluster=True)[:self.tag_limit-1]:
if not first_id_set:
self.first_tag_id = tag_id
first_id_set = True
c = QWidget()
l = QHBoxLayout(c)
l.setContentsMargins(0,0,0,0)
l.setSpacing(3)
tw = TagWidget(self.lib, self.lib.get_tag(tag_id), False, False)
ab = QPushButton()
ab.setMinimumSize(23, 23)
ab.setMaximumSize(23, 23)
ab.setText('+')
ab.setStyleSheet(
f'QPushButton{{'
f'background: {get_tag_color(ColorType.PRIMARY, self.lib.get_tag(tag_id).color)};'
# f'background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 {get_tag_color(ColorType.PRIMARY, tag.color)}, stop:1.0 {get_tag_color(ColorType.BORDER, tag.color)});'
# f"border-color:{get_tag_color(ColorType.PRIMARY, tag.color)};"
f"color: {get_tag_color(ColorType.TEXT, self.lib.get_tag(tag_id).color)};"
f'font-weight: 600;'
f"border-color:{get_tag_color(ColorType.BORDER, self.lib.get_tag(tag_id).color)};"
f'border-radius: 6px;'
f'border-style:solid;'
f'border-width: {math.ceil(1*self.devicePixelRatio())}px;'
# f'padding-top: 1.5px;'
# f'padding-right: 4px;'
f'padding-bottom: 5px;'
# f'padding-left: 4px;'
f'font-size: 20px;'
f'}}'
f'QPushButton::hover'
f'{{'
f"border-color:{get_tag_color(ColorType.LIGHT_ACCENT, self.lib.get_tag(tag_id).color)};"
f"color: {get_tag_color(ColorType.DARK_ACCENT, self.lib.get_tag(tag_id).color)};"
f'background: {get_tag_color(ColorType.LIGHT_ACCENT, self.lib.get_tag(tag_id).color)};'
f'}}')
found_tags = self.lib.search_tags(query, include_cluster=True)[:self.tag_limit - 1]
self.first_tag_id = found_tags[0] if found_tags else None
ab.clicked.connect(lambda checked=False, x=tag_id: self.tag_chosen.emit(x))
for tag_id in found_tags:
c = QWidget()
l = QHBoxLayout(c)
l.setContentsMargins(0, 0, 0, 0)
l.setSpacing(3)
tw = TagWidget(self.lib, self.lib.get_tag(tag_id), False, False)
ab = QPushButton()
ab.setMinimumSize(23, 23)
ab.setMaximumSize(23, 23)
ab.setText('+')
ab.setStyleSheet(
f'QPushButton{{'
f'background: {get_tag_color(ColorType.PRIMARY, self.lib.get_tag(tag_id).color)};'
# f'background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 {get_tag_color(ColorType.PRIMARY, tag.color)}, stop:1.0 {get_tag_color(ColorType.BORDER, tag.color)});'
# f"border-color:{get_tag_color(ColorType.PRIMARY, tag.color)};"
f"color: {get_tag_color(ColorType.TEXT, self.lib.get_tag(tag_id).color)};"
f'font-weight: 600;'
f"border-color:{get_tag_color(ColorType.BORDER, self.lib.get_tag(tag_id).color)};"
f'border-radius: 6px;'
f'border-style:solid;'
f'border-width: {math.ceil(1*self.devicePixelRatio())}px;'
# f'padding-top: 1.5px;'
# f'padding-right: 4px;'
f'padding-bottom: 5px;'
# f'padding-left: 4px;'
f'font-size: 20px;'
f'}}'
f'QPushButton::hover'
f'{{'
f"border-color:{get_tag_color(ColorType.LIGHT_ACCENT, self.lib.get_tag(tag_id).color)};"
f"color: {get_tag_color(ColorType.DARK_ACCENT, self.lib.get_tag(tag_id).color)};"
f'background: {get_tag_color(ColorType.LIGHT_ACCENT, self.lib.get_tag(tag_id).color)};'
f'}}')
l.addWidget(tw)
l.addWidget(ab)
self.scroll_layout.addWidget(c)
else:
self.first_tag_id = -1
ab.clicked.connect(lambda checked=False, x=tag_id: self.tag_chosen.emit(x))
l.addWidget(tw)
l.addWidget(ab)
self.scroll_layout.addWidget(c)
self.search_field.setFocus()

View file

@ -250,7 +250,9 @@ class QtDriver(QObject):
file_menu.addSeparator()
file_menu.addAction(QAction('&Close Library', menu_bar))
close_library_action = QAction('&Close Library', menu_bar)
close_library_action.triggered.connect(lambda: self.close_library())
file_menu.addAction(close_library_action)
# Edit Menu ============================================================
new_tag_action = QAction('New &Tag', menu_bar)
@ -428,6 +430,30 @@ class QtDriver(QObject):
end_time = time.time()
self.main_window.statusbar.showMessage(f'Library Saved! ({format_timespan(end_time - start_time)})')
def close_library(self):
if self.lib.library_dir:
# TODO: it is kinda the same code from "save_library"...
logging.info(f'Closing & Saving Library...')
self.main_window.statusbar.showMessage(f'Closed & Saving Library...')
start_time = time.time()
self.lib.save_library_to_disk()
self.settings.setValue("last_library", self.lib.library_dir)
self.settings.sync()
self.lib.clear_internal_vars()
title_text = f'{self.base_title}'
self.main_window.setWindowTitle(title_text)
self.nav_frames: list[NavigationState] = []
self.cur_frame_idx: int = -1
self.cur_query: str = ''
self.selected.clear()
self.preview_panel.update_widgets()
self.filter_items()
end_time = time.time()
self.main_window.statusbar.showMessage(f'Library Saved and Closed! ({format_timespan(end_time - start_time)})')
def backup_library(self):
logging.info(f'Backing Up Library...')
self.main_window.statusbar.showMessage(f'Saving Library...')

View file

@ -10,7 +10,7 @@ import os
from pathlib import Path
import cv2
from PIL import Image, ImageChops, UnidentifiedImageError, ImageQt, ImageDraw, ImageFont, ImageEnhance
from PIL import Image, ImageChops, UnidentifiedImageError, ImageQt, ImageDraw, ImageFont, ImageEnhance, ImageOps
from PySide6.QtCore import QObject, Signal, QSize
from PySide6.QtGui import QPixmap
from src.core.ts_core import PLAINTEXT_TYPES, VIDEO_TYPES, IMAGE_TYPES
@ -60,9 +60,6 @@ class ThumbRenderer(QObject):
ext_font = ImageFont.truetype(os.path.normpath(
f'{Path(__file__).parent.parent.parent.parent}/resources/qt/fonts/Oxanium-Bold.ttf'), math.floor(12*font_pixel_ratio))
def __init__(self):
QObject.__init__(self)
def render(self, timestamp: float, filepath, base_size: tuple[int, int], pixelRatio: float, isLoading=False):
"""Renders an entry/element thumbnail for the GUI."""
adj_size: int = 1
@ -107,6 +104,8 @@ class ThumbRenderer(QObject):
if image.mode != 'RGB':
image = image.convert(mode='RGB')
image = ImageOps.exif_transpose(image)
# Videos =======================================================
elif extension in VIDEO_TYPES:
video = cv2.VideoCapture(filepath)
@ -265,6 +264,8 @@ class ThumbRenderer(QObject):
image = new_bg
if image.mode != 'RGB':
image = image.convert(mode='RGB')
image = ImageOps.exif_transpose(image)
# Videos =======================================================
elif extension in VIDEO_TYPES: