mirror of
https://github.com/TagStudioDev/TagStudio.git
synced 2024-07-30 21:27:34 +00:00
Merge remote-tracking branch 'upstream/main' into bugfix/right-click-file-path/#116
This commit is contained in:
commit
0e1e9e924b
8 changed files with 138 additions and 154 deletions
|
@ -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 ===========================================================
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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()
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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...')
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in a new issue