From dccf5f28be2cd0092d011a165b8ebba9785bb0b6 Mon Sep 17 00:00:00 2001 From: Creepler13 Date: Fri, 10 May 2024 00:23:48 +0200 Subject: [PATCH 01/36] Ability to drop local files in to TagStudio to add to library --- tagstudio/src/qt/modals/drop_import.py | 60 ++++++++++++++++++++++++++ tagstudio/src/qt/ts_qt.py | 6 +++ 2 files changed, 66 insertions(+) create mode 100644 tagstudio/src/qt/modals/drop_import.py diff --git a/tagstudio/src/qt/modals/drop_import.py b/tagstudio/src/qt/modals/drop_import.py new file mode 100644 index 00000000..31bc3e43 --- /dev/null +++ b/tagstudio/src/qt/modals/drop_import.py @@ -0,0 +1,60 @@ +import os +import shutil +import typing + +from PySide6.QtGui import QDropEvent, QDragEnterEvent, QDragMoveEvent +from PySide6.QtWidgets import QMessageBox + +if typing.TYPE_CHECKING: + from src.qt.ts_qt import QtDriver + + +def dropEvent(driver: "QtDriver", event: QDropEvent): + if event.mimeData().hasUrls(): + if event.mimeData().urls()[0].isLocalFile(): + urls = event.mimeData().urls() + duplicate_filesnames = [] + for url in urls: + if os.path.exists(driver.lib.library_dir + "/" + url.fileName()): + duplicate_filesnames.append(url) + + ret = -1 + + if len(duplicate_filesnames) > 0: + msgBox = QMessageBox() + msgBox.setText( + f"The files {", ".join(map(lambda url:url.fileName(),duplicate_filesnames))} have filenames that already exist in the library folder." + ) + msgBox.addButton("Skip", QMessageBox.ButtonRole.YesRole) + msgBox.addButton("Override", QMessageBox.ButtonRole.DestructiveRole) + msgBox.addButton("Cancel", QMessageBox.ButtonRole.NoRole) + ret = msgBox.exec() + + if ret == 2: + return + + for url in urls: + if ret == 0 and url in duplicate_filesnames: + continue + shutil.copyfile( + url.toLocalFile(), driver.lib.library_dir + "/" + url.fileName() + ) + if ret == 1 and url in duplicate_filesnames: + continue + driver.lib.files_not_in_library.append(url.fileName()) + + driver.add_new_files_runnable() + + +def dragEnterEvent(event: QDragEnterEvent): + if event.mimeData().hasUrls(): + event.accept() + else: + event.ignore() + + +def dragMoveEvent(event: QDragMoveEvent): + if event.mimeData().hasUrls(): + event.accept() + else: + event.ignore() diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index 8b286859..35723da8 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -89,6 +89,7 @@ FixUnlinkedEntriesModal, FixDupeFilesModal, FoldersToTagsModal, + drop_import, ) import src.qt.resources_rc @@ -508,6 +509,11 @@ def start(self): # self.render_times: list = [] # self.main_window.setWindowFlag(Qt.FramelessWindowHint) + self.main_window.setAcceptDrops(True) + self.main_window.dragEnterEvent = drop_import.dragEnterEvent + self.main_window.dropEvent = lambda event: drop_import.dropEvent(self, event) + self.main_window.dragMoveEvent = drop_import.dragMoveEvent + # NOTE: Putting this early will result in a white non-responsive # window until everything is loaded. Consider adding a splash screen # or implementing some clever loading tricks. From 44c6a969446f7d66a0566a37927bbfdcdc75ce79 Mon Sep 17 00:00:00 2001 From: Creepler13 Date: Fri, 10 May 2024 00:47:46 +0200 Subject: [PATCH 02/36] Added renaming option to drop import --- tagstudio/src/qt/modals/drop_import.py | 33 ++++++++++++++++++++------ 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/tagstudio/src/qt/modals/drop_import.py b/tagstudio/src/qt/modals/drop_import.py index 31bc3e43..f5151a0b 100644 --- a/tagstudio/src/qt/modals/drop_import.py +++ b/tagstudio/src/qt/modals/drop_import.py @@ -27,21 +27,40 @@ def dropEvent(driver: "QtDriver", event: QDropEvent): ) msgBox.addButton("Skip", QMessageBox.ButtonRole.YesRole) msgBox.addButton("Override", QMessageBox.ButtonRole.DestructiveRole) + msgBox.addButton("Rename", QMessageBox.ButtonRole.DestructiveRole) msgBox.addButton("Cancel", QMessageBox.ButtonRole.NoRole) ret = msgBox.exec() - if ret == 2: + if ret == 3: # cancel return for url in urls: - if ret == 0 and url in duplicate_filesnames: - continue + filename = url.fileName() + + if url in duplicate_filesnames: + if ret == 0: # skip duplicates + continue + if ret == 1: # only override file contents of duplicates + shutil.copyfile( + url.toLocalFile(), driver.lib.library_dir + "/" + filename + ) + continue + if ret == 2: # rename duplicates + index = 2 + o_filename = url.fileName() + dot_idx = o_filename.index(".") + while os.path.exists(driver.lib.library_dir + "/" + filename): + filename = ( + o_filename[:dot_idx] + + f" ({index})" + + o_filename[dot_idx:] + ) + index += 1 + shutil.copyfile( - url.toLocalFile(), driver.lib.library_dir + "/" + url.fileName() + url.toLocalFile(), driver.lib.library_dir + "/" + filename ) - if ret == 1 and url in duplicate_filesnames: - continue - driver.lib.files_not_in_library.append(url.fileName()) + driver.lib.files_not_in_library.append(filename) driver.add_new_files_runnable() From 95fe39392e75e6a2a41838e9053e4f396c413498 Mon Sep 17 00:00:00 2001 From: Creepler13 Date: Fri, 10 May 2024 16:38:59 +0200 Subject: [PATCH 03/36] Improved readability and switched to pathLib --- tagstudio/src/qt/modals/drop_import.py | 103 +++++++++++++------------ 1 file changed, 55 insertions(+), 48 deletions(-) diff --git a/tagstudio/src/qt/modals/drop_import.py b/tagstudio/src/qt/modals/drop_import.py index f5151a0b..23411468 100644 --- a/tagstudio/src/qt/modals/drop_import.py +++ b/tagstudio/src/qt/modals/drop_import.py @@ -1,4 +1,4 @@ -import os +from pathlib import Path import shutil import typing @@ -10,59 +10,52 @@ def dropEvent(driver: "QtDriver", event: QDropEvent): - if event.mimeData().hasUrls(): - if event.mimeData().urls()[0].isLocalFile(): - urls = event.mimeData().urls() - duplicate_filesnames = [] - for url in urls: - if os.path.exists(driver.lib.library_dir + "/" + url.fileName()): - duplicate_filesnames.append(url) - - ret = -1 + if not event.mimeData().hasUrls(): + return + + if not event.mimeData().urls()[0].isLocalFile(): + return + + urls = event.mimeData().urls() + duplicate_filesnames = [] + for url in urls: + if Path(driver.lib.library_dir + "/" + url.fileName()).exists(): + duplicate_filesnames.append(url) - if len(duplicate_filesnames) > 0: - msgBox = QMessageBox() - msgBox.setText( - f"The files {", ".join(map(lambda url:url.fileName(),duplicate_filesnames))} have filenames that already exist in the library folder." - ) - msgBox.addButton("Skip", QMessageBox.ButtonRole.YesRole) - msgBox.addButton("Override", QMessageBox.ButtonRole.DestructiveRole) - msgBox.addButton("Rename", QMessageBox.ButtonRole.DestructiveRole) - msgBox.addButton("Cancel", QMessageBox.ButtonRole.NoRole) - ret = msgBox.exec() + ret = -1 - if ret == 3: # cancel - return + if len(duplicate_filesnames) > 0: + msgBox = QMessageBox() + msgBox.setText( + f"The files {", ".join(map(lambda url:url.fileName(),duplicate_filesnames))} have filenames that already exist in the library folder." + ) + msgBox.addButton("Skip", QMessageBox.ButtonRole.YesRole) + msgBox.addButton("Override", QMessageBox.ButtonRole.DestructiveRole) + msgBox.addButton("Rename", QMessageBox.ButtonRole.DestructiveRole) + msgBox.addButton("Cancel", QMessageBox.ButtonRole.NoRole) + ret = msgBox.exec() - for url in urls: - filename = url.fileName() + if ret == 3: # cancel + return - if url in duplicate_filesnames: - if ret == 0: # skip duplicates - continue - if ret == 1: # only override file contents of duplicates - shutil.copyfile( - url.toLocalFile(), driver.lib.library_dir + "/" + filename - ) - continue - if ret == 2: # rename duplicates - index = 2 - o_filename = url.fileName() - dot_idx = o_filename.index(".") - while os.path.exists(driver.lib.library_dir + "/" + filename): - filename = ( - o_filename[:dot_idx] - + f" ({index})" - + o_filename[dot_idx:] - ) - index += 1 + for url in urls: + filename = url.fileName() - shutil.copyfile( - url.toLocalFile(), driver.lib.library_dir + "/" + filename - ) + if url in duplicate_filesnames: + if ret == 0: # skip duplicates + continue + + if ret == 2: # rename + filename = get_renamed_duplicate_filename(driver.lib.library_dir,filename) driver.lib.files_not_in_library.append(filename) - - driver.add_new_files_runnable() + else: # override is simply copying but not adding a new entry + driver.lib.files_not_in_library.append(filename) + + shutil.copyfile( + url.toLocalFile(), driver.lib.library_dir + "/" + filename + ) + + driver.add_new_files_runnable() def dragEnterEvent(event: QDragEnterEvent): @@ -77,3 +70,17 @@ def dragMoveEvent(event: QDragMoveEvent): event.accept() else: event.ignore() + + +def get_renamed_duplicate_filename(path,filename)->str: + index = 2 + o_filename = filename + dot_idx = o_filename.index(".") + while Path(path + "/" + filename).exists(): + filename = ( + o_filename[:dot_idx] + + f" ({index})" + + o_filename[dot_idx:] + ) + index += 1 + return filename \ No newline at end of file From c3793d539afb53a5e8fbaeab55db118f2a5fad7c Mon Sep 17 00:00:00 2001 From: Creepler13 Date: Fri, 10 May 2024 16:40:03 +0200 Subject: [PATCH 04/36] format --- tagstudio/src/qt/modals/drop_import.py | 50 ++++++++++++-------------- 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/tagstudio/src/qt/modals/drop_import.py b/tagstudio/src/qt/modals/drop_import.py index 23411468..a55c728b 100644 --- a/tagstudio/src/qt/modals/drop_import.py +++ b/tagstudio/src/qt/modals/drop_import.py @@ -10,16 +10,16 @@ def dropEvent(driver: "QtDriver", event: QDropEvent): - if not event.mimeData().hasUrls(): + if not event.mimeData().hasUrls(): return - - if not event.mimeData().urls()[0].isLocalFile(): - return - + + if not event.mimeData().urls()[0].isLocalFile(): + return + urls = event.mimeData().urls() duplicate_filesnames = [] for url in urls: - if Path(driver.lib.library_dir + "/" + url.fileName()).exists(): + if Path(driver.lib.library_dir + "/" + url.fileName()).exists(): duplicate_filesnames.append(url) ret = -1 @@ -44,17 +44,17 @@ def dropEvent(driver: "QtDriver", event: QDropEvent): if url in duplicate_filesnames: if ret == 0: # skip duplicates continue - - if ret == 2: # rename - filename = get_renamed_duplicate_filename(driver.lib.library_dir,filename) + + if ret == 2: # rename + filename = get_renamed_duplicate_filename( + driver.lib.library_dir, filename + ) driver.lib.files_not_in_library.append(filename) - else: # override is simply copying but not adding a new entry + else: # override is simply copying but not adding a new entry driver.lib.files_not_in_library.append(filename) - - shutil.copyfile( - url.toLocalFile(), driver.lib.library_dir + "/" + filename - ) - + + shutil.copyfile(url.toLocalFile(), driver.lib.library_dir + "/" + filename) + driver.add_new_files_runnable() @@ -72,15 +72,11 @@ def dragMoveEvent(event: QDragMoveEvent): event.ignore() -def get_renamed_duplicate_filename(path,filename)->str: - index = 2 - o_filename = filename - dot_idx = o_filename.index(".") - while Path(path + "/" + filename).exists(): - filename = ( - o_filename[:dot_idx] - + f" ({index})" - + o_filename[dot_idx:] - ) - index += 1 - return filename \ No newline at end of file +def get_renamed_duplicate_filename(path, filename) -> str: + index = 2 + o_filename = filename + dot_idx = o_filename.index(".") + while Path(path + "/" + filename).exists(): + filename = o_filename[:dot_idx] + f" ({index})" + o_filename[dot_idx:] + index += 1 + return filename From f0cf8534074196b500e0ff444bc0dd7b392ea043 Mon Sep 17 00:00:00 2001 From: Creepler13 Date: Fri, 10 May 2024 18:14:03 +0200 Subject: [PATCH 05/36] Apply suggestions from code review Co-authored-by: yed podtrzitko --- tagstudio/src/qt/modals/drop_import.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tagstudio/src/qt/modals/drop_import.py b/tagstudio/src/qt/modals/drop_import.py index a55c728b..76072875 100644 --- a/tagstudio/src/qt/modals/drop_import.py +++ b/tagstudio/src/qt/modals/drop_import.py @@ -19,7 +19,7 @@ def dropEvent(driver: "QtDriver", event: QDropEvent): urls = event.mimeData().urls() duplicate_filesnames = [] for url in urls: - if Path(driver.lib.library_dir + "/" + url.fileName()).exists(): + if Path(driver.lib.library_dir / url.fileName()).exists(): duplicate_filesnames.append(url) ret = -1 @@ -27,7 +27,7 @@ def dropEvent(driver: "QtDriver", event: QDropEvent): if len(duplicate_filesnames) > 0: msgBox = QMessageBox() msgBox.setText( - f"The files {", ".join(map(lambda url:url.fileName(),duplicate_filesnames))} have filenames that already exist in the library folder." + f"The files {', '.join(map(lambda url:url.fileName(),duplicate_filesnames))} have filenames that already exist in the library folder." ) msgBox.addButton("Skip", QMessageBox.ButtonRole.YesRole) msgBox.addButton("Override", QMessageBox.ButtonRole.DestructiveRole) From e835a00170168d1ec2d167aa1420607aa93d2c7f Mon Sep 17 00:00:00 2001 From: Creepler13 Date: Fri, 10 May 2024 18:38:28 +0200 Subject: [PATCH 06/36] Revert Change --- tagstudio/src/qt/modals/drop_import.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tagstudio/src/qt/modals/drop_import.py b/tagstudio/src/qt/modals/drop_import.py index 76072875..5afa16be 100644 --- a/tagstudio/src/qt/modals/drop_import.py +++ b/tagstudio/src/qt/modals/drop_import.py @@ -19,7 +19,7 @@ def dropEvent(driver: "QtDriver", event: QDropEvent): urls = event.mimeData().urls() duplicate_filesnames = [] for url in urls: - if Path(driver.lib.library_dir / url.fileName()).exists(): + if Path(driver.lib.library_dir + "/" + url.fileName()).exists(): duplicate_filesnames.append(url) ret = -1 From 741b2d6cdd34244fcc48e791672b3d438505031a Mon Sep 17 00:00:00 2001 From: Creepler13 Date: Fri, 10 May 2024 18:49:10 +0200 Subject: [PATCH 07/36] Update tagstudio/src/qt/modals/drop_import.py Co-authored-by: yed podtrzitko --- tagstudio/src/qt/modals/drop_import.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tagstudio/src/qt/modals/drop_import.py b/tagstudio/src/qt/modals/drop_import.py index 5afa16be..5a3ed310 100644 --- a/tagstudio/src/qt/modals/drop_import.py +++ b/tagstudio/src/qt/modals/drop_import.py @@ -19,7 +19,7 @@ def dropEvent(driver: "QtDriver", event: QDropEvent): urls = event.mimeData().urls() duplicate_filesnames = [] for url in urls: - if Path(driver.lib.library_dir + "/" + url.fileName()).exists(): + if (Path(driver.lib.library_dir) / url.fileName()).exists(): duplicate_filesnames.append(url) ret = -1 From 5149855ae87911f4f832ff7ac9ec17dc2677079c Mon Sep 17 00:00:00 2001 From: Creepler13 Date: Sun, 12 May 2024 02:17:13 +0200 Subject: [PATCH 08/36] Added support for folders --- tagstudio/src/qt/modals/drop_import.py | 138 ++++++++++++++++++------- 1 file changed, 103 insertions(+), 35 deletions(-) diff --git a/tagstudio/src/qt/modals/drop_import.py b/tagstudio/src/qt/modals/drop_import.py index 5a3ed310..539314a4 100644 --- a/tagstudio/src/qt/modals/drop_import.py +++ b/tagstudio/src/qt/modals/drop_import.py @@ -1,6 +1,7 @@ from pathlib import Path import shutil import typing +import logging from PySide6.QtGui import QDropEvent, QDragEnterEvent, QDragMoveEvent from PySide6.QtWidgets import QMessageBox @@ -8,26 +9,64 @@ if typing.TYPE_CHECKING: from src.qt.ts_qt import QtDriver +logging.basicConfig(format="[DROP IMPORT] %(message)s", level=logging.INFO) + def dropEvent(driver: "QtDriver", event: QDropEvent): if not event.mimeData().hasUrls(): return - if not event.mimeData().urls()[0].isLocalFile(): - return + import_files(driver, event.mimeData().urls()) + + +def dragEnterEvent(event: QDragEnterEvent): + if event.mimeData().hasUrls(): + event.accept() + else: + event.ignore() + + +def dragMoveEvent(event: QDragMoveEvent): + if event.mimeData().hasUrls(): + event.accept() + else: + event.ignore() + + +def import_files(driver: "QtDriver", urls): + files: list[Path] = [] + dirs_in_root: list[Path] = [] + duplicate_files: list[Path] = [] - urls = event.mimeData().urls() - duplicate_filesnames = [] for url in urls: - if (Path(driver.lib.library_dir) / url.fileName()).exists(): - duplicate_filesnames.append(url) + if url.isLocalFile(): + file = Path(url.toLocalFile()) + + if file.is_dir(): + files += get_files_in_folder(file) + dirs_in_root.append(file.parent) + + dupes = get_files_exists_in_library( + dirs_in_root, driver.lib.library_dir, file + ) + duplicate_files += dupes + else: + files.append(file) + + if file.parent not in dirs_in_root: + dirs_in_root.append( + file.parent + ) # to create relative path of files not in folder + + if (Path(driver.lib.library_dir) / file.name).exists(): + duplicate_files.append(file) ret = -1 - if len(duplicate_filesnames) > 0: + if len(duplicate_files) > 0: msgBox = QMessageBox() msgBox.setText( - f"The files {', '.join(map(lambda url:url.fileName(),duplicate_filesnames))} have filenames that already exist in the library folder." + f"The files {', '.join(map(lambda path: str(path),get_relativ_paths(dirs_in_root,duplicate_files)))} have filenames that already exist in the library folder." ) msgBox.addButton("Skip", QMessageBox.ButtonRole.YesRole) msgBox.addButton("Override", QMessageBox.ButtonRole.DestructiveRole) @@ -37,46 +76,75 @@ def dropEvent(driver: "QtDriver", event: QDropEvent): if ret == 3: # cancel return + logging.info(files) + for file in files: + if file.is_dir(): + continue - for url in urls: - filename = url.fileName() + dest_file = get_relativ_path(dirs_in_root, file) - if url in duplicate_filesnames: + if file in duplicate_files: if ret == 0: # skip duplicates continue if ret == 2: # rename - filename = get_renamed_duplicate_filename( - driver.lib.library_dir, filename + dest_file = dest_file.with_name( + get_renamed_duplicate_filename( + Path(driver.lib.library_dir), dest_file + ) ) - driver.lib.files_not_in_library.append(filename) + driver.lib.files_not_in_library.append(dest_file) else: # override is simply copying but not adding a new entry - driver.lib.files_not_in_library.append(filename) + driver.lib.files_not_in_library.append(dest_file) - shutil.copyfile(url.toLocalFile(), driver.lib.library_dir + "/" + filename) + (driver.lib.library_dir / dest_file).parent.mkdir(parents=True, exist_ok=True) + shutil.copyfile(file, driver.lib.library_dir / dest_file) driver.add_new_files_runnable() -def dragEnterEvent(event: QDragEnterEvent): - if event.mimeData().hasUrls(): - event.accept() - else: - event.ignore() - - -def dragMoveEvent(event: QDragMoveEvent): - if event.mimeData().hasUrls(): - event.accept() - else: - event.ignore() - - -def get_renamed_duplicate_filename(path, filename) -> str: +def get_renamed_duplicate_filename(path: Path, filePath: Path) -> str: index = 2 - o_filename = filename + o_filename = filePath.name dot_idx = o_filename.index(".") - while Path(path + "/" + filename).exists(): - filename = o_filename[:dot_idx] + f" ({index})" + o_filename[dot_idx:] + while (path / filePath).exists(): + filePath = filePath.with_name( + o_filename[:dot_idx] + f" ({index})" + o_filename[dot_idx:] + ) index += 1 - return filename + return filePath.name + + +def get_files_in_folder(path: Path) -> list[Path]: + files = [] + for file in path.glob("**/*"): + files.append(file) + return files + + +def get_files_exists_in_library( + dirs_in_root: list[Path], lib_dir: str, path: Path +) -> list[Path]: + exists: list[Path] = [] + if path.is_dir(): + files = get_files_in_folder(path) + for file in files: + if file.is_dir(): + exists += get_files_exists_in_library(dirs_in_root, lib_dir, file) + elif (lib_dir / get_relativ_path(dirs_in_root, file)).exists(): + exists.append(file) + return exists + + +def get_relativ_paths(roots: list[Path], paths: list[Path]) -> list[Path]: + relativ_paths = [] + for file in paths: + relativ_paths.append(get_relativ_path(roots, file)) + return relativ_paths + + +def get_relativ_path(roots: list[Path], path: Path) -> Path: + for dir in roots: + if path.is_relative_to(dir): + return path.relative_to(dir) + return path.name From 12c4c437ef33c98e3249896ffcd585c56d3869f9 Mon Sep 17 00:00:00 2001 From: Creepler13 Date: Sun, 12 May 2024 13:57:35 +0200 Subject: [PATCH 09/36] formatting --- tagstudio/src/qt/modals/drop_import.py | 83 ++++++++++++++------------ 1 file changed, 45 insertions(+), 38 deletions(-) diff --git a/tagstudio/src/qt/modals/drop_import.py b/tagstudio/src/qt/modals/drop_import.py index 539314a4..d6139a69 100644 --- a/tagstudio/src/qt/modals/drop_import.py +++ b/tagstudio/src/qt/modals/drop_import.py @@ -39,44 +39,38 @@ def import_files(driver: "QtDriver", urls): duplicate_files: list[Path] = [] for url in urls: - if url.isLocalFile(): - file = Path(url.toLocalFile()) + if not url.isLocalFile(): + continue - if file.is_dir(): - files += get_files_in_folder(file) - dirs_in_root.append(file.parent) + file = Path(url.toLocalFile()) - dupes = get_files_exists_in_library( - dirs_in_root, driver.lib.library_dir, file - ) - duplicate_files += dupes - else: - files.append(file) + if file.is_dir(): + files += get_files_in_folder(file) + dirs_in_root.append(file.parent) - if file.parent not in dirs_in_root: - dirs_in_root.append( - file.parent - ) # to create relative path of files not in folder + dupes = get_files_exists_in_library( + dirs_in_root, driver.lib.library_dir, file + ) + duplicate_files += dupes + else: + files.append(file) - if (Path(driver.lib.library_dir) / file.name).exists(): - duplicate_files.append(file) + if file.parent not in dirs_in_root: + dirs_in_root.append( + file.parent + ) # to create relative path of files not in folder + + if (Path(driver.lib.library_dir) / file.name).exists(): + duplicate_files.append(file) ret = -1 if len(duplicate_files) > 0: - msgBox = QMessageBox() - msgBox.setText( - f"The files {', '.join(map(lambda path: str(path),get_relativ_paths(dirs_in_root,duplicate_files)))} have filenames that already exist in the library folder." - ) - msgBox.addButton("Skip", QMessageBox.ButtonRole.YesRole) - msgBox.addButton("Override", QMessageBox.ButtonRole.DestructiveRole) - msgBox.addButton("Rename", QMessageBox.ButtonRole.DestructiveRole) - msgBox.addButton("Cancel", QMessageBox.ButtonRole.NoRole) - ret = msgBox.exec() + ret = duplicates_choice(dirs_in_root, duplicate_files) if ret == 3: # cancel return - logging.info(files) + for file in files: if file.is_dir(): continue @@ -88,11 +82,10 @@ def import_files(driver: "QtDriver", urls): continue if ret == 2: # rename - dest_file = dest_file.with_name( - get_renamed_duplicate_filename( - Path(driver.lib.library_dir), dest_file - ) + new_name = get_renamed_duplicate_filename( + Path(driver.lib.library_dir), dest_file ) + dest_file = dest_file.with_name(new_name) driver.lib.files_not_in_library.append(dest_file) else: # override is simply copying but not adding a new entry driver.lib.files_not_in_library.append(dest_file) @@ -103,6 +96,18 @@ def import_files(driver: "QtDriver", urls): driver.add_new_files_runnable() +def duplicates_choice(dirs_in_root: list[Path], duplicate_files: list[Path]) -> int: + msgBox = QMessageBox() + msgBox.setText( + f"The files {', '.join(map(lambda path: str(path),get_relativ_paths(dirs_in_root, duplicate_files)))} have filenames that already exist in the library folder." + ) + msgBox.addButton("Skip", QMessageBox.ButtonRole.YesRole) + msgBox.addButton("Override", QMessageBox.ButtonRole.DestructiveRole) + msgBox.addButton("Rename", QMessageBox.ButtonRole.DestructiveRole) + msgBox.addButton("Cancel", QMessageBox.ButtonRole.NoRole) + return msgBox.exec() + + def get_renamed_duplicate_filename(path: Path, filePath: Path) -> str: index = 2 o_filename = filePath.name @@ -126,13 +131,15 @@ def get_files_exists_in_library( dirs_in_root: list[Path], lib_dir: str, path: Path ) -> list[Path]: exists: list[Path] = [] - if path.is_dir(): - files = get_files_in_folder(path) - for file in files: - if file.is_dir(): - exists += get_files_exists_in_library(dirs_in_root, lib_dir, file) - elif (lib_dir / get_relativ_path(dirs_in_root, file)).exists(): - exists.append(file) + if not path.is_dir(): + return exists + + files = get_files_in_folder(path) + for file in files: + if file.is_dir(): + exists += get_files_exists_in_library(dirs_in_root, lib_dir, file) + elif (lib_dir / get_relativ_path(dirs_in_root, file)).exists(): + exists.append(file) return exists From aeb119e7da311f3c69f3b6d150873f377fc09e80 Mon Sep 17 00:00:00 2001 From: Creepler13 Date: Sun, 12 May 2024 15:56:04 +0200 Subject: [PATCH 10/36] Progress bars added --- tagstudio/src/qt/modals/__init__.py | 1 + tagstudio/src/qt/modals/drop_import.py | 311 +++++++++++++++---------- tagstudio/src/qt/ts_qt.py | 7 +- 3 files changed, 192 insertions(+), 127 deletions(-) diff --git a/tagstudio/src/qt/modals/__init__.py b/tagstudio/src/qt/modals/__init__.py index a4fe31b6..68fe8d04 100644 --- a/tagstudio/src/qt/modals/__init__.py +++ b/tagstudio/src/qt/modals/__init__.py @@ -9,3 +9,4 @@ from .mirror_entities import MirrorEntriesModal from .fix_dupes import FixDupeFilesModal from .folders_to_tags import FoldersToTagsModal +from .drop_import import DropImport diff --git a/tagstudio/src/qt/modals/drop_import.py b/tagstudio/src/qt/modals/drop_import.py index d6139a69..4bcd7e7f 100644 --- a/tagstudio/src/qt/modals/drop_import.py +++ b/tagstudio/src/qt/modals/drop_import.py @@ -1,157 +1,218 @@ from pathlib import Path import shutil import typing -import logging +from PySide6.QtCore import QThreadPool from PySide6.QtGui import QDropEvent, QDragEnterEvent, QDragMoveEvent from PySide6.QtWidgets import QMessageBox +from src.qt.widgets import ProgressWidget +from src.qt.helpers import FunctionIterator, CustomRunnable + if typing.TYPE_CHECKING: from src.qt.ts_qt import QtDriver -logging.basicConfig(format="[DROP IMPORT] %(message)s", level=logging.INFO) - -def dropEvent(driver: "QtDriver", event: QDropEvent): - if not event.mimeData().hasUrls(): - return - - import_files(driver, event.mimeData().urls()) +class DropImport: + def __init__(self, driver: "QtDriver"): + self.driver = driver + def dropEvent(self, event: QDropEvent): + if not event.mimeData().hasUrls(): + return -def dragEnterEvent(event: QDragEnterEvent): - if event.mimeData().hasUrls(): - event.accept() - else: - event.ignore() + self.urls = event.mimeData().urls() + self.import_files() + def dragEnterEvent(self, event: QDragEnterEvent): + if event.mimeData().hasUrls(): + event.accept() + else: + event.ignore() -def dragMoveEvent(event: QDragMoveEvent): - if event.mimeData().hasUrls(): - event.accept() - else: - event.ignore() + def dragMoveEvent(self, event: QDragMoveEvent): + if event.mimeData().hasUrls(): + event.accept() + else: + event.ignore() + + def import_files(self): + self.files: list[Path] = [] + self.dirs_in_root: list[Path] = [] + self.duplicate_files: list[Path] = [] + + self.start_collection_progress() + + def start_collection_progress(self): + iterator = FunctionIterator(self.collect_files_to_import) + pw = ProgressWidget( + window_title="Searching Files", + label_text="Searching New Files...\nPreparing...", + cancel_button_text=None, + minimum=0, + maximum=0, + ) + pw.show() + iterator.value.connect(lambda x: pw.update_progress(x[0] + 1)) + iterator.value.connect( + lambda x: pw.update_label( + f'Searching New Files...\n{x[0]+1} File{"s" if x[0]+1 != 1 else ""} Found. {(f"{x[1]} Already exist in the library folders") if x[1]>0 else ""}' + ) + ) + r = CustomRunnable(lambda: iterator.run()) + r.done.connect(lambda: (pw.hide(), self.ask_user())) + QThreadPool.globalInstance().start(r) + def collect_files_to_import(self): + for url in self.urls: + if not url.isLocalFile(): + continue -def import_files(driver: "QtDriver", urls): - files: list[Path] = [] - dirs_in_root: list[Path] = [] - duplicate_files: list[Path] = [] + file = Path(url.toLocalFile()) + + if file.is_dir(): + for f in self.get_files_in_folder(file): + if f.is_dir(): + continue + self.files.append(f) + if ( + self.driver.lib.library_dir / self.get_relativ_path(file) + ).exists(): + self.duplicate_files.append(f) + yield [len(self.files), len(self.duplicate_files)] + + self.dirs_in_root.append(file.parent) + else: + self.files.append(file) + + if file.parent not in self.dirs_in_root: + self.dirs_in_root.append( + file.parent + ) # to create relative path of files not in folder + + if (Path(self.driver.lib.library_dir) / file.name).exists(): + self.duplicate_files.append(file) + + yield [len(self.files), len(self.duplicate_files)] + + def start_copy_progress(self): + iterator = FunctionIterator(self.copy_files) + pw = ProgressWidget( + window_title="Import Files", + label_text="Importing New Files...\nPreparing...", + cancel_button_text=None, + minimum=0, + maximum=len(self.files), + ) + pw.show() + iterator.value.connect(lambda x: pw.update_progress(x[0] + 1)) + dupes_choice_text = ( + "Skipped" + if self.choice == 0 + else ("Overridden" if self.choice == 1 else "Renamed") + ) + iterator.value.connect( + lambda x: pw.update_label( + f'Importing New Files...\n{x[0]+1} File{"s" if x[0]+1 != 1 else ""} Imported. {(f"{x[1]} {dupes_choice_text}") if x[1]>0 else ""}' + ) + ) + r = CustomRunnable(lambda: iterator.run()) + r.done.connect(lambda: (pw.hide(), self.driver.add_new_files_runnable())) + QThreadPool.globalInstance().start(r) + + def copy_files(self): + fileCount = 0 + duplicated_files_progress = 0 + for file in self.files: + if file.is_dir(): + continue - for url in urls: - if not url.isLocalFile(): - continue + dest_file = self.get_relativ_path(file) - file = Path(url.toLocalFile()) + if file in self.duplicate_files: + duplicated_files_progress += 1 + if self.choice == 0: # skip duplicates + continue - if file.is_dir(): - files += get_files_in_folder(file) - dirs_in_root.append(file.parent) + if self.choice == 2: # rename + new_name = self.get_renamed_duplicate_filename_in_lib(dest_file) + dest_file = dest_file.with_name(new_name) + self.driver.lib.files_not_in_library.append(dest_file) + else: # override is simply copying but not adding a new entry + self.driver.lib.files_not_in_library.append(dest_file) - dupes = get_files_exists_in_library( - dirs_in_root, driver.lib.library_dir, file + (self.driver.lib.library_dir / dest_file).parent.mkdir( + parents=True, exist_ok=True ) - duplicate_files += dupes - else: - files.append(file) - - if file.parent not in dirs_in_root: - dirs_in_root.append( - file.parent - ) # to create relative path of files not in folder + shutil.copyfile(file, self.driver.lib.library_dir / dest_file) - if (Path(driver.lib.library_dir) / file.name).exists(): - duplicate_files.append(file) + fileCount += 1 + yield [fileCount, duplicated_files_progress] - ret = -1 + def ask_user(self): + self.choice = -1 - if len(duplicate_files) > 0: - ret = duplicates_choice(dirs_in_root, duplicate_files) - - if ret == 3: # cancel - return + if len(self.duplicate_files) > 0: + self.choice = self.duplicates_choice() - for file in files: - if file.is_dir(): - continue + if self.choice == 3: # cancel + return - dest_file = get_relativ_path(dirs_in_root, file) + self.start_copy_progress() - if file in duplicate_files: - if ret == 0: # skip duplicates - continue + def duplicates_choice(self) -> int: + msgBox = QMessageBox() + dupes_to_show = self.duplicate_files + if len(self.duplicate_files) > 20: + dupes_to_show = dupes_to_show[0:20] - if ret == 2: # rename - new_name = get_renamed_duplicate_filename( - Path(driver.lib.library_dir), dest_file - ) - dest_file = dest_file.with_name(new_name) - driver.lib.files_not_in_library.append(dest_file) - else: # override is simply copying but not adding a new entry - driver.lib.files_not_in_library.append(dest_file) - - (driver.lib.library_dir / dest_file).parent.mkdir(parents=True, exist_ok=True) - shutil.copyfile(file, driver.lib.library_dir / dest_file) - - driver.add_new_files_runnable() - - -def duplicates_choice(dirs_in_root: list[Path], duplicate_files: list[Path]) -> int: - msgBox = QMessageBox() - msgBox.setText( - f"The files {', '.join(map(lambda path: str(path),get_relativ_paths(dirs_in_root, duplicate_files)))} have filenames that already exist in the library folder." - ) - msgBox.addButton("Skip", QMessageBox.ButtonRole.YesRole) - msgBox.addButton("Override", QMessageBox.ButtonRole.DestructiveRole) - msgBox.addButton("Rename", QMessageBox.ButtonRole.DestructiveRole) - msgBox.addButton("Cancel", QMessageBox.ButtonRole.NoRole) - return msgBox.exec() - - -def get_renamed_duplicate_filename(path: Path, filePath: Path) -> str: - index = 2 - o_filename = filePath.name - dot_idx = o_filename.index(".") - while (path / filePath).exists(): - filePath = filePath.with_name( - o_filename[:dot_idx] + f" ({index})" + o_filename[dot_idx:] + msgBox.setText( + f"The files {', '.join(map(lambda path: str(path),self.get_relativ_paths(dupes_to_show)))} {(f"and {len(self.duplicate_files)-20} more") if len(self.duplicate_files)>20 else ""} have filenames that already exist in the library folder." ) - index += 1 - return filePath.name - - -def get_files_in_folder(path: Path) -> list[Path]: - files = [] - for file in path.glob("**/*"): - files.append(file) - return files - - -def get_files_exists_in_library( - dirs_in_root: list[Path], lib_dir: str, path: Path -) -> list[Path]: - exists: list[Path] = [] - if not path.is_dir(): + msgBox.addButton("Skip", QMessageBox.ButtonRole.YesRole) + msgBox.addButton("Override", QMessageBox.ButtonRole.DestructiveRole) + msgBox.addButton("Rename", QMessageBox.ButtonRole.DestructiveRole) + msgBox.addButton("Cancel", QMessageBox.ButtonRole.NoRole) + return msgBox.exec() + + def get_files_exists_in_library(self, path: Path) -> list[Path]: + exists: list[Path] = [] + if not path.is_dir(): + return exists + + files = self.get_files_in_folder(path) + for file in files: + if file.is_dir(): + exists += self.get_files_exists_in_library(file) + elif (self.driver.lib.library_dir / self.get_relativ_path(file)).exists(): + exists.append(file) return exists - files = get_files_in_folder(path) - for file in files: - if file.is_dir(): - exists += get_files_exists_in_library(dirs_in_root, lib_dir, file) - elif (lib_dir / get_relativ_path(dirs_in_root, file)).exists(): - exists.append(file) - return exists - - -def get_relativ_paths(roots: list[Path], paths: list[Path]) -> list[Path]: - relativ_paths = [] - for file in paths: - relativ_paths.append(get_relativ_path(roots, file)) - return relativ_paths - - -def get_relativ_path(roots: list[Path], path: Path) -> Path: - for dir in roots: - if path.is_relative_to(dir): - return path.relative_to(dir) - return path.name + def get_relativ_paths(self, paths: list[Path]) -> list[Path]: + relativ_paths = [] + for file in paths: + relativ_paths.append(self.get_relativ_path(file)) + return relativ_paths + + def get_relativ_path(self, path: Path) -> Path: + for dir in self.dirs_in_root: + if path.is_relative_to(dir): + return path.relative_to(dir) + return Path(path.name) + + def get_files_in_folder(self, path: Path) -> list[Path]: + files = [] + for file in path.glob("**/*"): + files.append(file) + return files + + def get_renamed_duplicate_filename_in_lib(self, filePath: Path) -> str: + index = 2 + o_filename = filePath.name + dot_idx = o_filename.index(".") + while (self.driver.lib.library_dir / filePath).exists(): + filePath = filePath.with_name( + o_filename[:dot_idx] + f" ({index})" + o_filename[dot_idx:] + ) + index += 1 + return filePath.name diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index 35723da8..4abacc53 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -82,6 +82,8 @@ PreviewPanel, ItemThumb, ) + + from src.qt.modals import ( BuildTagPanel, TagDatabasePanel, @@ -89,7 +91,7 @@ FixUnlinkedEntriesModal, FixDupeFilesModal, FoldersToTagsModal, - drop_import, + DropImport, ) import src.qt.resources_rc @@ -509,9 +511,10 @@ def start(self): # self.render_times: list = [] # self.main_window.setWindowFlag(Qt.FramelessWindowHint) + drop_import = DropImport(self) self.main_window.setAcceptDrops(True) self.main_window.dragEnterEvent = drop_import.dragEnterEvent - self.main_window.dropEvent = lambda event: drop_import.dropEvent(self, event) + self.main_window.dropEvent = drop_import.dropEvent self.main_window.dragMoveEvent = drop_import.dragMoveEvent # NOTE: Putting this early will result in a white non-responsive From dbbe1b0cd63c6ac34ce8ef1c89b561a73a756f3b Mon Sep 17 00:00:00 2001 From: Creepler13 Date: Wed, 22 May 2024 18:50:21 +0200 Subject: [PATCH 11/36] Added Ability to Drag out of window --- tagstudio/src/qt/modals/drop_import.py | 36 +++++++++++++++++++++++--- tagstudio/src/qt/ts_qt.py | 17 +++++++----- 2 files changed, 44 insertions(+), 9 deletions(-) diff --git a/tagstudio/src/qt/modals/drop_import.py b/tagstudio/src/qt/modals/drop_import.py index 4bcd7e7f..fa5578c6 100644 --- a/tagstudio/src/qt/modals/drop_import.py +++ b/tagstudio/src/qt/modals/drop_import.py @@ -2,28 +2,55 @@ import shutil import typing -from PySide6.QtCore import QThreadPool -from PySide6.QtGui import QDropEvent, QDragEnterEvent, QDragMoveEvent +from PySide6.QtCore import QThreadPool,Qt,QMimeData,QUrl +from PySide6.QtGui import QDropEvent, QDragEnterEvent,QImage, QDragMoveEvent,QMouseEvent,QDrag,QDragLeaveEvent from PySide6.QtWidgets import QMessageBox from src.qt.widgets import ProgressWidget from src.qt.helpers import FunctionIterator, CustomRunnable +from ctypes import wintypes,windll if typing.TYPE_CHECKING: from src.qt.ts_qt import QtDriver +import logging class DropImport: def __init__(self, driver: "QtDriver"): self.driver = driver - + + def mouseMoveEvent(self,event:QMouseEvent): + if event.buttons() is not Qt.MouseButton.LeftButton: return + if len(self.driver.selected) == 0: return + + drag = QDrag(self.driver) + paths = [] + mimedata = QMimeData() + for selected in self.driver.selected: + entry =self.driver.lib.get_entry(selected[1]) + url = QUrl.fromLocalFile(self.driver.lib.library_dir+"/"+entry.path+"/"+entry.filename) + paths.append(url) + + mimedata.setUrls(paths) + drag.setMimeData(mimedata) + drag.exec(Qt.DropAction.CopyAction) + def dropEvent(self, event: QDropEvent): + if event.source() is self.driver: # change that if you want to drop something originating from tagstudio, for moving or so + return + if not event.mimeData().hasUrls(): return self.urls = event.mimeData().urls() self.import_files() + def dragLeaveEvent(self,event:QDragLeaveEvent): + if event.mimeData().hasUrls(): + event.accept() + else: + event.ignore() + def dragEnterEvent(self, event: QDragEnterEvent): if event.mimeData().hasUrls(): event.accept() @@ -34,6 +61,7 @@ def dragMoveEvent(self, event: QDragMoveEvent): if event.mimeData().hasUrls(): event.accept() else: + logging.info(self.driver.selected) event.ignore() def import_files(self): @@ -216,3 +244,5 @@ def get_renamed_duplicate_filename_in_lib(self, filePath: Path) -> str: ) index += 1 return filePath.name + + diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index 4abacc53..0acdcb1f 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -253,6 +253,13 @@ def start(self): # f'QScrollBar::{{background:red;}}' # ) + self.drop_import = DropImport(self) + self.main_window.setAcceptDrops(True) + self.main_window.dragEnterEvent = self.drop_import.dragEnterEvent + self.main_window.dropEvent = self.drop_import.dropEvent + self.main_window.dragMoveEvent = self.drop_import.dragMoveEvent + + # # self.main_window.windowFlags() & # # self.main_window.setWindowFlag(Qt.WindowType.FramelessWindowHint, True) # self.main_window.setWindowFlag(Qt.WindowType.NoDropShadowWindowHint, True) @@ -511,12 +518,6 @@ def start(self): # self.render_times: list = [] # self.main_window.setWindowFlag(Qt.FramelessWindowHint) - drop_import = DropImport(self) - self.main_window.setAcceptDrops(True) - self.main_window.dragEnterEvent = drop_import.dragEnterEvent - self.main_window.dropEvent = drop_import.dropEvent - self.main_window.dragMoveEvent = drop_import.dragMoveEvent - # NOTE: Putting this early will result in a white non-responsive # window until everything is loaded. Consider adding a splash screen # or implementing some clever loading tricks. @@ -1015,6 +1016,10 @@ def _init_thumb_grid(self): item_thumb = ItemThumb( None, self.lib, self.preview_panel, (self.thumb_size, self.thumb_size) ) + + item_thumb.setMouseTracking(True) + item_thumb.mouseMoveEvent = self.drop_import.mouseMoveEvent + layout.addWidget(item_thumb) self.item_thumbs.append(item_thumb) From 9a45ae1765feb87f7bfa7b2eb236188abdea868d Mon Sep 17 00:00:00 2001 From: Creepler13 Date: Wed, 22 May 2024 19:22:33 +0200 Subject: [PATCH 12/36] f --- tagstudio/src/qt/modals/drop_import.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tagstudio/src/qt/modals/drop_import.py b/tagstudio/src/qt/modals/drop_import.py index fa5578c6..3b0ebf72 100644 --- a/tagstudio/src/qt/modals/drop_import.py +++ b/tagstudio/src/qt/modals/drop_import.py @@ -2,6 +2,7 @@ import shutil import typing + from PySide6.QtCore import QThreadPool,Qt,QMimeData,QUrl from PySide6.QtGui import QDropEvent, QDragEnterEvent,QImage, QDragMoveEvent,QMouseEvent,QDrag,QDragLeaveEvent from PySide6.QtWidgets import QMessageBox From 1a70369a7ee6fbe475d80f2cfd26e2d2f046c0f9 Mon Sep 17 00:00:00 2001 From: Creepler13 Date: Wed, 22 May 2024 19:25:00 +0200 Subject: [PATCH 13/36] format --- tagstudio/src/qt/modals/drop_import.py | 47 +++++++++++++++----------- tagstudio/src/qt/ts_qt.py | 5 ++- 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/tagstudio/src/qt/modals/drop_import.py b/tagstudio/src/qt/modals/drop_import.py index 3b0ebf72..3cdcf251 100644 --- a/tagstudio/src/qt/modals/drop_import.py +++ b/tagstudio/src/qt/modals/drop_import.py @@ -2,42 +2,53 @@ import shutil import typing - -from PySide6.QtCore import QThreadPool,Qt,QMimeData,QUrl -from PySide6.QtGui import QDropEvent, QDragEnterEvent,QImage, QDragMoveEvent,QMouseEvent,QDrag,QDragLeaveEvent +from PySide6.QtCore import QThreadPool, Qt, QMimeData, QUrl +from PySide6.QtGui import ( + QDropEvent, + QDragEnterEvent, + QDragMoveEvent, + QMouseEvent, + QDrag, + QDragLeaveEvent, +) from PySide6.QtWidgets import QMessageBox from src.qt.widgets import ProgressWidget from src.qt.helpers import FunctionIterator, CustomRunnable -from ctypes import wintypes,windll - if typing.TYPE_CHECKING: from src.qt.ts_qt import QtDriver import logging + class DropImport: def __init__(self, driver: "QtDriver"): self.driver = driver - - def mouseMoveEvent(self,event:QMouseEvent): - if event.buttons() is not Qt.MouseButton.LeftButton: return - if len(self.driver.selected) == 0: return - + + def mouseMoveEvent(self, event: QMouseEvent): + if event.buttons() is not Qt.MouseButton.LeftButton: + return + if len(self.driver.selected) == 0: + return + drag = QDrag(self.driver) paths = [] mimedata = QMimeData() - for selected in self.driver.selected: - entry =self.driver.lib.get_entry(selected[1]) - url = QUrl.fromLocalFile(self.driver.lib.library_dir+"/"+entry.path+"/"+entry.filename) + for selected in self.driver.selected: + entry = self.driver.lib.get_entry(selected[1]) + url = QUrl.fromLocalFile( + self.driver.lib.library_dir + "/" + entry.path + "/" + entry.filename + ) paths.append(url) - + mimedata.setUrls(paths) drag.setMimeData(mimedata) drag.exec(Qt.DropAction.CopyAction) - + def dropEvent(self, event: QDropEvent): - if event.source() is self.driver: # change that if you want to drop something originating from tagstudio, for moving or so + if ( + event.source() is self.driver + ): # change that if you want to drop something originating from tagstudio, for moving or so return if not event.mimeData().hasUrls(): @@ -46,7 +57,7 @@ def dropEvent(self, event: QDropEvent): self.urls = event.mimeData().urls() self.import_files() - def dragLeaveEvent(self,event:QDragLeaveEvent): + def dragLeaveEvent(self, event: QDragLeaveEvent): if event.mimeData().hasUrls(): event.accept() else: @@ -245,5 +256,3 @@ def get_renamed_duplicate_filename_in_lib(self, filePath: Path) -> str: ) index += 1 return filePath.name - - diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index 0acdcb1f..5ed34797 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -259,7 +259,6 @@ def start(self): self.main_window.dropEvent = self.drop_import.dropEvent self.main_window.dragMoveEvent = self.drop_import.dragMoveEvent - # # self.main_window.windowFlags() & # # self.main_window.setWindowFlag(Qt.WindowType.FramelessWindowHint, True) # self.main_window.setWindowFlag(Qt.WindowType.NoDropShadowWindowHint, True) @@ -1016,10 +1015,10 @@ def _init_thumb_grid(self): item_thumb = ItemThumb( None, self.lib, self.preview_panel, (self.thumb_size, self.thumb_size) ) - + item_thumb.setMouseTracking(True) item_thumb.mouseMoveEvent = self.drop_import.mouseMoveEvent - + layout.addWidget(item_thumb) self.item_thumbs.append(item_thumb) From 0ac25d6f13489669ee9e9ed781cad75e09ff360b Mon Sep 17 00:00:00 2001 From: Creepler13 Date: Fri, 10 May 2024 00:23:48 +0200 Subject: [PATCH 14/36] Ability to drop local files in to TagStudio to add to library --- tagstudio/src/qt/modals/drop_import.py | 60 ++++++++++++++++++++++++++ tagstudio/src/qt/ts_qt.py | 6 +++ 2 files changed, 66 insertions(+) create mode 100644 tagstudio/src/qt/modals/drop_import.py diff --git a/tagstudio/src/qt/modals/drop_import.py b/tagstudio/src/qt/modals/drop_import.py new file mode 100644 index 00000000..31bc3e43 --- /dev/null +++ b/tagstudio/src/qt/modals/drop_import.py @@ -0,0 +1,60 @@ +import os +import shutil +import typing + +from PySide6.QtGui import QDropEvent, QDragEnterEvent, QDragMoveEvent +from PySide6.QtWidgets import QMessageBox + +if typing.TYPE_CHECKING: + from src.qt.ts_qt import QtDriver + + +def dropEvent(driver: "QtDriver", event: QDropEvent): + if event.mimeData().hasUrls(): + if event.mimeData().urls()[0].isLocalFile(): + urls = event.mimeData().urls() + duplicate_filesnames = [] + for url in urls: + if os.path.exists(driver.lib.library_dir + "/" + url.fileName()): + duplicate_filesnames.append(url) + + ret = -1 + + if len(duplicate_filesnames) > 0: + msgBox = QMessageBox() + msgBox.setText( + f"The files {", ".join(map(lambda url:url.fileName(),duplicate_filesnames))} have filenames that already exist in the library folder." + ) + msgBox.addButton("Skip", QMessageBox.ButtonRole.YesRole) + msgBox.addButton("Override", QMessageBox.ButtonRole.DestructiveRole) + msgBox.addButton("Cancel", QMessageBox.ButtonRole.NoRole) + ret = msgBox.exec() + + if ret == 2: + return + + for url in urls: + if ret == 0 and url in duplicate_filesnames: + continue + shutil.copyfile( + url.toLocalFile(), driver.lib.library_dir + "/" + url.fileName() + ) + if ret == 1 and url in duplicate_filesnames: + continue + driver.lib.files_not_in_library.append(url.fileName()) + + driver.add_new_files_runnable() + + +def dragEnterEvent(event: QDragEnterEvent): + if event.mimeData().hasUrls(): + event.accept() + else: + event.ignore() + + +def dragMoveEvent(event: QDragMoveEvent): + if event.mimeData().hasUrls(): + event.accept() + else: + event.ignore() diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index a34e10eb..43c630cc 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -90,6 +90,7 @@ from src.qt.modals.fix_unlinked import FixUnlinkedEntriesModal from src.qt.modals.fix_dupes import FixDupeFilesModal from src.qt.modals.folders_to_tags import FoldersToTagsModal +from src.qt.modals.drop_import import DropImport # this import has side-effect of import PySide resources import src.qt.resources_rc # pylint: disable=unused-import @@ -574,6 +575,11 @@ def init_library_window(self): # self.render_times: list = [] # self.main_window.setWindowFlag(Qt.FramelessWindowHint) + self.main_window.setAcceptDrops(True) + self.main_window.dragEnterEvent = drop_import.dragEnterEvent + self.main_window.dropEvent = lambda event: drop_import.dropEvent(self, event) + self.main_window.dragMoveEvent = drop_import.dragMoveEvent + # NOTE: Putting this early will result in a white non-responsive # window until everything is loaded. Consider adding a splash screen # or implementing some clever loading tricks. From d7a930593bd5adbd1d30da0f40471f50871fd00c Mon Sep 17 00:00:00 2001 From: Creepler13 Date: Fri, 10 May 2024 00:47:46 +0200 Subject: [PATCH 15/36] Added renaming option to drop import --- tagstudio/src/qt/modals/drop_import.py | 33 ++++++++++++++++++++------ 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/tagstudio/src/qt/modals/drop_import.py b/tagstudio/src/qt/modals/drop_import.py index 31bc3e43..f5151a0b 100644 --- a/tagstudio/src/qt/modals/drop_import.py +++ b/tagstudio/src/qt/modals/drop_import.py @@ -27,21 +27,40 @@ def dropEvent(driver: "QtDriver", event: QDropEvent): ) msgBox.addButton("Skip", QMessageBox.ButtonRole.YesRole) msgBox.addButton("Override", QMessageBox.ButtonRole.DestructiveRole) + msgBox.addButton("Rename", QMessageBox.ButtonRole.DestructiveRole) msgBox.addButton("Cancel", QMessageBox.ButtonRole.NoRole) ret = msgBox.exec() - if ret == 2: + if ret == 3: # cancel return for url in urls: - if ret == 0 and url in duplicate_filesnames: - continue + filename = url.fileName() + + if url in duplicate_filesnames: + if ret == 0: # skip duplicates + continue + if ret == 1: # only override file contents of duplicates + shutil.copyfile( + url.toLocalFile(), driver.lib.library_dir + "/" + filename + ) + continue + if ret == 2: # rename duplicates + index = 2 + o_filename = url.fileName() + dot_idx = o_filename.index(".") + while os.path.exists(driver.lib.library_dir + "/" + filename): + filename = ( + o_filename[:dot_idx] + + f" ({index})" + + o_filename[dot_idx:] + ) + index += 1 + shutil.copyfile( - url.toLocalFile(), driver.lib.library_dir + "/" + url.fileName() + url.toLocalFile(), driver.lib.library_dir + "/" + filename ) - if ret == 1 and url in duplicate_filesnames: - continue - driver.lib.files_not_in_library.append(url.fileName()) + driver.lib.files_not_in_library.append(filename) driver.add_new_files_runnable() From f9d1b55d47d479e51ac37fdbaa75641578140f51 Mon Sep 17 00:00:00 2001 From: Creepler13 Date: Fri, 10 May 2024 16:38:59 +0200 Subject: [PATCH 16/36] Improved readability and switched to pathLib --- tagstudio/src/qt/modals/drop_import.py | 103 +++++++++++++------------ 1 file changed, 55 insertions(+), 48 deletions(-) diff --git a/tagstudio/src/qt/modals/drop_import.py b/tagstudio/src/qt/modals/drop_import.py index f5151a0b..23411468 100644 --- a/tagstudio/src/qt/modals/drop_import.py +++ b/tagstudio/src/qt/modals/drop_import.py @@ -1,4 +1,4 @@ -import os +from pathlib import Path import shutil import typing @@ -10,59 +10,52 @@ def dropEvent(driver: "QtDriver", event: QDropEvent): - if event.mimeData().hasUrls(): - if event.mimeData().urls()[0].isLocalFile(): - urls = event.mimeData().urls() - duplicate_filesnames = [] - for url in urls: - if os.path.exists(driver.lib.library_dir + "/" + url.fileName()): - duplicate_filesnames.append(url) - - ret = -1 + if not event.mimeData().hasUrls(): + return + + if not event.mimeData().urls()[0].isLocalFile(): + return + + urls = event.mimeData().urls() + duplicate_filesnames = [] + for url in urls: + if Path(driver.lib.library_dir + "/" + url.fileName()).exists(): + duplicate_filesnames.append(url) - if len(duplicate_filesnames) > 0: - msgBox = QMessageBox() - msgBox.setText( - f"The files {", ".join(map(lambda url:url.fileName(),duplicate_filesnames))} have filenames that already exist in the library folder." - ) - msgBox.addButton("Skip", QMessageBox.ButtonRole.YesRole) - msgBox.addButton("Override", QMessageBox.ButtonRole.DestructiveRole) - msgBox.addButton("Rename", QMessageBox.ButtonRole.DestructiveRole) - msgBox.addButton("Cancel", QMessageBox.ButtonRole.NoRole) - ret = msgBox.exec() + ret = -1 - if ret == 3: # cancel - return + if len(duplicate_filesnames) > 0: + msgBox = QMessageBox() + msgBox.setText( + f"The files {", ".join(map(lambda url:url.fileName(),duplicate_filesnames))} have filenames that already exist in the library folder." + ) + msgBox.addButton("Skip", QMessageBox.ButtonRole.YesRole) + msgBox.addButton("Override", QMessageBox.ButtonRole.DestructiveRole) + msgBox.addButton("Rename", QMessageBox.ButtonRole.DestructiveRole) + msgBox.addButton("Cancel", QMessageBox.ButtonRole.NoRole) + ret = msgBox.exec() - for url in urls: - filename = url.fileName() + if ret == 3: # cancel + return - if url in duplicate_filesnames: - if ret == 0: # skip duplicates - continue - if ret == 1: # only override file contents of duplicates - shutil.copyfile( - url.toLocalFile(), driver.lib.library_dir + "/" + filename - ) - continue - if ret == 2: # rename duplicates - index = 2 - o_filename = url.fileName() - dot_idx = o_filename.index(".") - while os.path.exists(driver.lib.library_dir + "/" + filename): - filename = ( - o_filename[:dot_idx] - + f" ({index})" - + o_filename[dot_idx:] - ) - index += 1 + for url in urls: + filename = url.fileName() - shutil.copyfile( - url.toLocalFile(), driver.lib.library_dir + "/" + filename - ) + if url in duplicate_filesnames: + if ret == 0: # skip duplicates + continue + + if ret == 2: # rename + filename = get_renamed_duplicate_filename(driver.lib.library_dir,filename) driver.lib.files_not_in_library.append(filename) - - driver.add_new_files_runnable() + else: # override is simply copying but not adding a new entry + driver.lib.files_not_in_library.append(filename) + + shutil.copyfile( + url.toLocalFile(), driver.lib.library_dir + "/" + filename + ) + + driver.add_new_files_runnable() def dragEnterEvent(event: QDragEnterEvent): @@ -77,3 +70,17 @@ def dragMoveEvent(event: QDragMoveEvent): event.accept() else: event.ignore() + + +def get_renamed_duplicate_filename(path,filename)->str: + index = 2 + o_filename = filename + dot_idx = o_filename.index(".") + while Path(path + "/" + filename).exists(): + filename = ( + o_filename[:dot_idx] + + f" ({index})" + + o_filename[dot_idx:] + ) + index += 1 + return filename \ No newline at end of file From 374ddbc7f69e9a234d9f09ed22fe4ad98016ad7a Mon Sep 17 00:00:00 2001 From: Creepler13 Date: Fri, 10 May 2024 16:40:03 +0200 Subject: [PATCH 17/36] format --- tagstudio/src/qt/modals/drop_import.py | 50 ++++++++++++-------------- 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/tagstudio/src/qt/modals/drop_import.py b/tagstudio/src/qt/modals/drop_import.py index 23411468..a55c728b 100644 --- a/tagstudio/src/qt/modals/drop_import.py +++ b/tagstudio/src/qt/modals/drop_import.py @@ -10,16 +10,16 @@ def dropEvent(driver: "QtDriver", event: QDropEvent): - if not event.mimeData().hasUrls(): + if not event.mimeData().hasUrls(): return - - if not event.mimeData().urls()[0].isLocalFile(): - return - + + if not event.mimeData().urls()[0].isLocalFile(): + return + urls = event.mimeData().urls() duplicate_filesnames = [] for url in urls: - if Path(driver.lib.library_dir + "/" + url.fileName()).exists(): + if Path(driver.lib.library_dir + "/" + url.fileName()).exists(): duplicate_filesnames.append(url) ret = -1 @@ -44,17 +44,17 @@ def dropEvent(driver: "QtDriver", event: QDropEvent): if url in duplicate_filesnames: if ret == 0: # skip duplicates continue - - if ret == 2: # rename - filename = get_renamed_duplicate_filename(driver.lib.library_dir,filename) + + if ret == 2: # rename + filename = get_renamed_duplicate_filename( + driver.lib.library_dir, filename + ) driver.lib.files_not_in_library.append(filename) - else: # override is simply copying but not adding a new entry + else: # override is simply copying but not adding a new entry driver.lib.files_not_in_library.append(filename) - - shutil.copyfile( - url.toLocalFile(), driver.lib.library_dir + "/" + filename - ) - + + shutil.copyfile(url.toLocalFile(), driver.lib.library_dir + "/" + filename) + driver.add_new_files_runnable() @@ -72,15 +72,11 @@ def dragMoveEvent(event: QDragMoveEvent): event.ignore() -def get_renamed_duplicate_filename(path,filename)->str: - index = 2 - o_filename = filename - dot_idx = o_filename.index(".") - while Path(path + "/" + filename).exists(): - filename = ( - o_filename[:dot_idx] - + f" ({index})" - + o_filename[dot_idx:] - ) - index += 1 - return filename \ No newline at end of file +def get_renamed_duplicate_filename(path, filename) -> str: + index = 2 + o_filename = filename + dot_idx = o_filename.index(".") + while Path(path + "/" + filename).exists(): + filename = o_filename[:dot_idx] + f" ({index})" + o_filename[dot_idx:] + index += 1 + return filename From 5b7f2093695e5581268ed34914079e73647bbb4f Mon Sep 17 00:00:00 2001 From: Creepler13 Date: Fri, 10 May 2024 18:14:03 +0200 Subject: [PATCH 18/36] Apply suggestions from code review Co-authored-by: yed podtrzitko --- tagstudio/src/qt/modals/drop_import.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tagstudio/src/qt/modals/drop_import.py b/tagstudio/src/qt/modals/drop_import.py index a55c728b..76072875 100644 --- a/tagstudio/src/qt/modals/drop_import.py +++ b/tagstudio/src/qt/modals/drop_import.py @@ -19,7 +19,7 @@ def dropEvent(driver: "QtDriver", event: QDropEvent): urls = event.mimeData().urls() duplicate_filesnames = [] for url in urls: - if Path(driver.lib.library_dir + "/" + url.fileName()).exists(): + if Path(driver.lib.library_dir / url.fileName()).exists(): duplicate_filesnames.append(url) ret = -1 @@ -27,7 +27,7 @@ def dropEvent(driver: "QtDriver", event: QDropEvent): if len(duplicate_filesnames) > 0: msgBox = QMessageBox() msgBox.setText( - f"The files {", ".join(map(lambda url:url.fileName(),duplicate_filesnames))} have filenames that already exist in the library folder." + f"The files {', '.join(map(lambda url:url.fileName(),duplicate_filesnames))} have filenames that already exist in the library folder." ) msgBox.addButton("Skip", QMessageBox.ButtonRole.YesRole) msgBox.addButton("Override", QMessageBox.ButtonRole.DestructiveRole) From 76879daf4f2490542d234fa842093b55fc420d43 Mon Sep 17 00:00:00 2001 From: Creepler13 Date: Fri, 10 May 2024 18:38:28 +0200 Subject: [PATCH 19/36] Revert Change --- tagstudio/src/qt/modals/drop_import.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tagstudio/src/qt/modals/drop_import.py b/tagstudio/src/qt/modals/drop_import.py index 76072875..5afa16be 100644 --- a/tagstudio/src/qt/modals/drop_import.py +++ b/tagstudio/src/qt/modals/drop_import.py @@ -19,7 +19,7 @@ def dropEvent(driver: "QtDriver", event: QDropEvent): urls = event.mimeData().urls() duplicate_filesnames = [] for url in urls: - if Path(driver.lib.library_dir / url.fileName()).exists(): + if Path(driver.lib.library_dir + "/" + url.fileName()).exists(): duplicate_filesnames.append(url) ret = -1 From cf1402feb11d745753a8eda9e7dbafd41ac3538b Mon Sep 17 00:00:00 2001 From: Creepler13 Date: Fri, 10 May 2024 18:49:10 +0200 Subject: [PATCH 20/36] Update tagstudio/src/qt/modals/drop_import.py Co-authored-by: yed podtrzitko --- tagstudio/src/qt/modals/drop_import.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tagstudio/src/qt/modals/drop_import.py b/tagstudio/src/qt/modals/drop_import.py index 5afa16be..5a3ed310 100644 --- a/tagstudio/src/qt/modals/drop_import.py +++ b/tagstudio/src/qt/modals/drop_import.py @@ -19,7 +19,7 @@ def dropEvent(driver: "QtDriver", event: QDropEvent): urls = event.mimeData().urls() duplicate_filesnames = [] for url in urls: - if Path(driver.lib.library_dir + "/" + url.fileName()).exists(): + if (Path(driver.lib.library_dir) / url.fileName()).exists(): duplicate_filesnames.append(url) ret = -1 From db8d94eb380863ef6ada7036cd684770e607c093 Mon Sep 17 00:00:00 2001 From: Creepler13 Date: Sun, 12 May 2024 02:17:13 +0200 Subject: [PATCH 21/36] Added support for folders --- tagstudio/src/qt/modals/drop_import.py | 138 ++++++++++++++++++------- 1 file changed, 103 insertions(+), 35 deletions(-) diff --git a/tagstudio/src/qt/modals/drop_import.py b/tagstudio/src/qt/modals/drop_import.py index 5a3ed310..539314a4 100644 --- a/tagstudio/src/qt/modals/drop_import.py +++ b/tagstudio/src/qt/modals/drop_import.py @@ -1,6 +1,7 @@ from pathlib import Path import shutil import typing +import logging from PySide6.QtGui import QDropEvent, QDragEnterEvent, QDragMoveEvent from PySide6.QtWidgets import QMessageBox @@ -8,26 +9,64 @@ if typing.TYPE_CHECKING: from src.qt.ts_qt import QtDriver +logging.basicConfig(format="[DROP IMPORT] %(message)s", level=logging.INFO) + def dropEvent(driver: "QtDriver", event: QDropEvent): if not event.mimeData().hasUrls(): return - if not event.mimeData().urls()[0].isLocalFile(): - return + import_files(driver, event.mimeData().urls()) + + +def dragEnterEvent(event: QDragEnterEvent): + if event.mimeData().hasUrls(): + event.accept() + else: + event.ignore() + + +def dragMoveEvent(event: QDragMoveEvent): + if event.mimeData().hasUrls(): + event.accept() + else: + event.ignore() + + +def import_files(driver: "QtDriver", urls): + files: list[Path] = [] + dirs_in_root: list[Path] = [] + duplicate_files: list[Path] = [] - urls = event.mimeData().urls() - duplicate_filesnames = [] for url in urls: - if (Path(driver.lib.library_dir) / url.fileName()).exists(): - duplicate_filesnames.append(url) + if url.isLocalFile(): + file = Path(url.toLocalFile()) + + if file.is_dir(): + files += get_files_in_folder(file) + dirs_in_root.append(file.parent) + + dupes = get_files_exists_in_library( + dirs_in_root, driver.lib.library_dir, file + ) + duplicate_files += dupes + else: + files.append(file) + + if file.parent not in dirs_in_root: + dirs_in_root.append( + file.parent + ) # to create relative path of files not in folder + + if (Path(driver.lib.library_dir) / file.name).exists(): + duplicate_files.append(file) ret = -1 - if len(duplicate_filesnames) > 0: + if len(duplicate_files) > 0: msgBox = QMessageBox() msgBox.setText( - f"The files {', '.join(map(lambda url:url.fileName(),duplicate_filesnames))} have filenames that already exist in the library folder." + f"The files {', '.join(map(lambda path: str(path),get_relativ_paths(dirs_in_root,duplicate_files)))} have filenames that already exist in the library folder." ) msgBox.addButton("Skip", QMessageBox.ButtonRole.YesRole) msgBox.addButton("Override", QMessageBox.ButtonRole.DestructiveRole) @@ -37,46 +76,75 @@ def dropEvent(driver: "QtDriver", event: QDropEvent): if ret == 3: # cancel return + logging.info(files) + for file in files: + if file.is_dir(): + continue - for url in urls: - filename = url.fileName() + dest_file = get_relativ_path(dirs_in_root, file) - if url in duplicate_filesnames: + if file in duplicate_files: if ret == 0: # skip duplicates continue if ret == 2: # rename - filename = get_renamed_duplicate_filename( - driver.lib.library_dir, filename + dest_file = dest_file.with_name( + get_renamed_duplicate_filename( + Path(driver.lib.library_dir), dest_file + ) ) - driver.lib.files_not_in_library.append(filename) + driver.lib.files_not_in_library.append(dest_file) else: # override is simply copying but not adding a new entry - driver.lib.files_not_in_library.append(filename) + driver.lib.files_not_in_library.append(dest_file) - shutil.copyfile(url.toLocalFile(), driver.lib.library_dir + "/" + filename) + (driver.lib.library_dir / dest_file).parent.mkdir(parents=True, exist_ok=True) + shutil.copyfile(file, driver.lib.library_dir / dest_file) driver.add_new_files_runnable() -def dragEnterEvent(event: QDragEnterEvent): - if event.mimeData().hasUrls(): - event.accept() - else: - event.ignore() - - -def dragMoveEvent(event: QDragMoveEvent): - if event.mimeData().hasUrls(): - event.accept() - else: - event.ignore() - - -def get_renamed_duplicate_filename(path, filename) -> str: +def get_renamed_duplicate_filename(path: Path, filePath: Path) -> str: index = 2 - o_filename = filename + o_filename = filePath.name dot_idx = o_filename.index(".") - while Path(path + "/" + filename).exists(): - filename = o_filename[:dot_idx] + f" ({index})" + o_filename[dot_idx:] + while (path / filePath).exists(): + filePath = filePath.with_name( + o_filename[:dot_idx] + f" ({index})" + o_filename[dot_idx:] + ) index += 1 - return filename + return filePath.name + + +def get_files_in_folder(path: Path) -> list[Path]: + files = [] + for file in path.glob("**/*"): + files.append(file) + return files + + +def get_files_exists_in_library( + dirs_in_root: list[Path], lib_dir: str, path: Path +) -> list[Path]: + exists: list[Path] = [] + if path.is_dir(): + files = get_files_in_folder(path) + for file in files: + if file.is_dir(): + exists += get_files_exists_in_library(dirs_in_root, lib_dir, file) + elif (lib_dir / get_relativ_path(dirs_in_root, file)).exists(): + exists.append(file) + return exists + + +def get_relativ_paths(roots: list[Path], paths: list[Path]) -> list[Path]: + relativ_paths = [] + for file in paths: + relativ_paths.append(get_relativ_path(roots, file)) + return relativ_paths + + +def get_relativ_path(roots: list[Path], path: Path) -> Path: + for dir in roots: + if path.is_relative_to(dir): + return path.relative_to(dir) + return path.name From 75acd2376130aa56ad15a0cfbb26b0265fe6e9a3 Mon Sep 17 00:00:00 2001 From: Creepler13 Date: Sun, 12 May 2024 13:57:35 +0200 Subject: [PATCH 22/36] formatting --- tagstudio/src/qt/modals/drop_import.py | 83 ++++++++++++++------------ 1 file changed, 45 insertions(+), 38 deletions(-) diff --git a/tagstudio/src/qt/modals/drop_import.py b/tagstudio/src/qt/modals/drop_import.py index 539314a4..d6139a69 100644 --- a/tagstudio/src/qt/modals/drop_import.py +++ b/tagstudio/src/qt/modals/drop_import.py @@ -39,44 +39,38 @@ def import_files(driver: "QtDriver", urls): duplicate_files: list[Path] = [] for url in urls: - if url.isLocalFile(): - file = Path(url.toLocalFile()) + if not url.isLocalFile(): + continue - if file.is_dir(): - files += get_files_in_folder(file) - dirs_in_root.append(file.parent) + file = Path(url.toLocalFile()) - dupes = get_files_exists_in_library( - dirs_in_root, driver.lib.library_dir, file - ) - duplicate_files += dupes - else: - files.append(file) + if file.is_dir(): + files += get_files_in_folder(file) + dirs_in_root.append(file.parent) - if file.parent not in dirs_in_root: - dirs_in_root.append( - file.parent - ) # to create relative path of files not in folder + dupes = get_files_exists_in_library( + dirs_in_root, driver.lib.library_dir, file + ) + duplicate_files += dupes + else: + files.append(file) - if (Path(driver.lib.library_dir) / file.name).exists(): - duplicate_files.append(file) + if file.parent not in dirs_in_root: + dirs_in_root.append( + file.parent + ) # to create relative path of files not in folder + + if (Path(driver.lib.library_dir) / file.name).exists(): + duplicate_files.append(file) ret = -1 if len(duplicate_files) > 0: - msgBox = QMessageBox() - msgBox.setText( - f"The files {', '.join(map(lambda path: str(path),get_relativ_paths(dirs_in_root,duplicate_files)))} have filenames that already exist in the library folder." - ) - msgBox.addButton("Skip", QMessageBox.ButtonRole.YesRole) - msgBox.addButton("Override", QMessageBox.ButtonRole.DestructiveRole) - msgBox.addButton("Rename", QMessageBox.ButtonRole.DestructiveRole) - msgBox.addButton("Cancel", QMessageBox.ButtonRole.NoRole) - ret = msgBox.exec() + ret = duplicates_choice(dirs_in_root, duplicate_files) if ret == 3: # cancel return - logging.info(files) + for file in files: if file.is_dir(): continue @@ -88,11 +82,10 @@ def import_files(driver: "QtDriver", urls): continue if ret == 2: # rename - dest_file = dest_file.with_name( - get_renamed_duplicate_filename( - Path(driver.lib.library_dir), dest_file - ) + new_name = get_renamed_duplicate_filename( + Path(driver.lib.library_dir), dest_file ) + dest_file = dest_file.with_name(new_name) driver.lib.files_not_in_library.append(dest_file) else: # override is simply copying but not adding a new entry driver.lib.files_not_in_library.append(dest_file) @@ -103,6 +96,18 @@ def import_files(driver: "QtDriver", urls): driver.add_new_files_runnable() +def duplicates_choice(dirs_in_root: list[Path], duplicate_files: list[Path]) -> int: + msgBox = QMessageBox() + msgBox.setText( + f"The files {', '.join(map(lambda path: str(path),get_relativ_paths(dirs_in_root, duplicate_files)))} have filenames that already exist in the library folder." + ) + msgBox.addButton("Skip", QMessageBox.ButtonRole.YesRole) + msgBox.addButton("Override", QMessageBox.ButtonRole.DestructiveRole) + msgBox.addButton("Rename", QMessageBox.ButtonRole.DestructiveRole) + msgBox.addButton("Cancel", QMessageBox.ButtonRole.NoRole) + return msgBox.exec() + + def get_renamed_duplicate_filename(path: Path, filePath: Path) -> str: index = 2 o_filename = filePath.name @@ -126,13 +131,15 @@ def get_files_exists_in_library( dirs_in_root: list[Path], lib_dir: str, path: Path ) -> list[Path]: exists: list[Path] = [] - if path.is_dir(): - files = get_files_in_folder(path) - for file in files: - if file.is_dir(): - exists += get_files_exists_in_library(dirs_in_root, lib_dir, file) - elif (lib_dir / get_relativ_path(dirs_in_root, file)).exists(): - exists.append(file) + if not path.is_dir(): + return exists + + files = get_files_in_folder(path) + for file in files: + if file.is_dir(): + exists += get_files_exists_in_library(dirs_in_root, lib_dir, file) + elif (lib_dir / get_relativ_path(dirs_in_root, file)).exists(): + exists.append(file) return exists From 8f8b93b178c7fefa85b2e3e1619b6bcd34e1361f Mon Sep 17 00:00:00 2001 From: Creepler13 Date: Sun, 12 May 2024 15:56:04 +0200 Subject: [PATCH 23/36] Progress bars added --- tagstudio/src/qt/modals/drop_import.py | 311 +++++++++++++++---------- tagstudio/src/qt/ts_qt.py | 3 +- 2 files changed, 188 insertions(+), 126 deletions(-) diff --git a/tagstudio/src/qt/modals/drop_import.py b/tagstudio/src/qt/modals/drop_import.py index d6139a69..4bcd7e7f 100644 --- a/tagstudio/src/qt/modals/drop_import.py +++ b/tagstudio/src/qt/modals/drop_import.py @@ -1,157 +1,218 @@ from pathlib import Path import shutil import typing -import logging +from PySide6.QtCore import QThreadPool from PySide6.QtGui import QDropEvent, QDragEnterEvent, QDragMoveEvent from PySide6.QtWidgets import QMessageBox +from src.qt.widgets import ProgressWidget +from src.qt.helpers import FunctionIterator, CustomRunnable + if typing.TYPE_CHECKING: from src.qt.ts_qt import QtDriver -logging.basicConfig(format="[DROP IMPORT] %(message)s", level=logging.INFO) - -def dropEvent(driver: "QtDriver", event: QDropEvent): - if not event.mimeData().hasUrls(): - return - - import_files(driver, event.mimeData().urls()) +class DropImport: + def __init__(self, driver: "QtDriver"): + self.driver = driver + def dropEvent(self, event: QDropEvent): + if not event.mimeData().hasUrls(): + return -def dragEnterEvent(event: QDragEnterEvent): - if event.mimeData().hasUrls(): - event.accept() - else: - event.ignore() + self.urls = event.mimeData().urls() + self.import_files() + def dragEnterEvent(self, event: QDragEnterEvent): + if event.mimeData().hasUrls(): + event.accept() + else: + event.ignore() -def dragMoveEvent(event: QDragMoveEvent): - if event.mimeData().hasUrls(): - event.accept() - else: - event.ignore() + def dragMoveEvent(self, event: QDragMoveEvent): + if event.mimeData().hasUrls(): + event.accept() + else: + event.ignore() + + def import_files(self): + self.files: list[Path] = [] + self.dirs_in_root: list[Path] = [] + self.duplicate_files: list[Path] = [] + + self.start_collection_progress() + + def start_collection_progress(self): + iterator = FunctionIterator(self.collect_files_to_import) + pw = ProgressWidget( + window_title="Searching Files", + label_text="Searching New Files...\nPreparing...", + cancel_button_text=None, + minimum=0, + maximum=0, + ) + pw.show() + iterator.value.connect(lambda x: pw.update_progress(x[0] + 1)) + iterator.value.connect( + lambda x: pw.update_label( + f'Searching New Files...\n{x[0]+1} File{"s" if x[0]+1 != 1 else ""} Found. {(f"{x[1]} Already exist in the library folders") if x[1]>0 else ""}' + ) + ) + r = CustomRunnable(lambda: iterator.run()) + r.done.connect(lambda: (pw.hide(), self.ask_user())) + QThreadPool.globalInstance().start(r) + def collect_files_to_import(self): + for url in self.urls: + if not url.isLocalFile(): + continue -def import_files(driver: "QtDriver", urls): - files: list[Path] = [] - dirs_in_root: list[Path] = [] - duplicate_files: list[Path] = [] + file = Path(url.toLocalFile()) + + if file.is_dir(): + for f in self.get_files_in_folder(file): + if f.is_dir(): + continue + self.files.append(f) + if ( + self.driver.lib.library_dir / self.get_relativ_path(file) + ).exists(): + self.duplicate_files.append(f) + yield [len(self.files), len(self.duplicate_files)] + + self.dirs_in_root.append(file.parent) + else: + self.files.append(file) + + if file.parent not in self.dirs_in_root: + self.dirs_in_root.append( + file.parent + ) # to create relative path of files not in folder + + if (Path(self.driver.lib.library_dir) / file.name).exists(): + self.duplicate_files.append(file) + + yield [len(self.files), len(self.duplicate_files)] + + def start_copy_progress(self): + iterator = FunctionIterator(self.copy_files) + pw = ProgressWidget( + window_title="Import Files", + label_text="Importing New Files...\nPreparing...", + cancel_button_text=None, + minimum=0, + maximum=len(self.files), + ) + pw.show() + iterator.value.connect(lambda x: pw.update_progress(x[0] + 1)) + dupes_choice_text = ( + "Skipped" + if self.choice == 0 + else ("Overridden" if self.choice == 1 else "Renamed") + ) + iterator.value.connect( + lambda x: pw.update_label( + f'Importing New Files...\n{x[0]+1} File{"s" if x[0]+1 != 1 else ""} Imported. {(f"{x[1]} {dupes_choice_text}") if x[1]>0 else ""}' + ) + ) + r = CustomRunnable(lambda: iterator.run()) + r.done.connect(lambda: (pw.hide(), self.driver.add_new_files_runnable())) + QThreadPool.globalInstance().start(r) + + def copy_files(self): + fileCount = 0 + duplicated_files_progress = 0 + for file in self.files: + if file.is_dir(): + continue - for url in urls: - if not url.isLocalFile(): - continue + dest_file = self.get_relativ_path(file) - file = Path(url.toLocalFile()) + if file in self.duplicate_files: + duplicated_files_progress += 1 + if self.choice == 0: # skip duplicates + continue - if file.is_dir(): - files += get_files_in_folder(file) - dirs_in_root.append(file.parent) + if self.choice == 2: # rename + new_name = self.get_renamed_duplicate_filename_in_lib(dest_file) + dest_file = dest_file.with_name(new_name) + self.driver.lib.files_not_in_library.append(dest_file) + else: # override is simply copying but not adding a new entry + self.driver.lib.files_not_in_library.append(dest_file) - dupes = get_files_exists_in_library( - dirs_in_root, driver.lib.library_dir, file + (self.driver.lib.library_dir / dest_file).parent.mkdir( + parents=True, exist_ok=True ) - duplicate_files += dupes - else: - files.append(file) - - if file.parent not in dirs_in_root: - dirs_in_root.append( - file.parent - ) # to create relative path of files not in folder + shutil.copyfile(file, self.driver.lib.library_dir / dest_file) - if (Path(driver.lib.library_dir) / file.name).exists(): - duplicate_files.append(file) + fileCount += 1 + yield [fileCount, duplicated_files_progress] - ret = -1 + def ask_user(self): + self.choice = -1 - if len(duplicate_files) > 0: - ret = duplicates_choice(dirs_in_root, duplicate_files) - - if ret == 3: # cancel - return + if len(self.duplicate_files) > 0: + self.choice = self.duplicates_choice() - for file in files: - if file.is_dir(): - continue + if self.choice == 3: # cancel + return - dest_file = get_relativ_path(dirs_in_root, file) + self.start_copy_progress() - if file in duplicate_files: - if ret == 0: # skip duplicates - continue + def duplicates_choice(self) -> int: + msgBox = QMessageBox() + dupes_to_show = self.duplicate_files + if len(self.duplicate_files) > 20: + dupes_to_show = dupes_to_show[0:20] - if ret == 2: # rename - new_name = get_renamed_duplicate_filename( - Path(driver.lib.library_dir), dest_file - ) - dest_file = dest_file.with_name(new_name) - driver.lib.files_not_in_library.append(dest_file) - else: # override is simply copying but not adding a new entry - driver.lib.files_not_in_library.append(dest_file) - - (driver.lib.library_dir / dest_file).parent.mkdir(parents=True, exist_ok=True) - shutil.copyfile(file, driver.lib.library_dir / dest_file) - - driver.add_new_files_runnable() - - -def duplicates_choice(dirs_in_root: list[Path], duplicate_files: list[Path]) -> int: - msgBox = QMessageBox() - msgBox.setText( - f"The files {', '.join(map(lambda path: str(path),get_relativ_paths(dirs_in_root, duplicate_files)))} have filenames that already exist in the library folder." - ) - msgBox.addButton("Skip", QMessageBox.ButtonRole.YesRole) - msgBox.addButton("Override", QMessageBox.ButtonRole.DestructiveRole) - msgBox.addButton("Rename", QMessageBox.ButtonRole.DestructiveRole) - msgBox.addButton("Cancel", QMessageBox.ButtonRole.NoRole) - return msgBox.exec() - - -def get_renamed_duplicate_filename(path: Path, filePath: Path) -> str: - index = 2 - o_filename = filePath.name - dot_idx = o_filename.index(".") - while (path / filePath).exists(): - filePath = filePath.with_name( - o_filename[:dot_idx] + f" ({index})" + o_filename[dot_idx:] + msgBox.setText( + f"The files {', '.join(map(lambda path: str(path),self.get_relativ_paths(dupes_to_show)))} {(f"and {len(self.duplicate_files)-20} more") if len(self.duplicate_files)>20 else ""} have filenames that already exist in the library folder." ) - index += 1 - return filePath.name - - -def get_files_in_folder(path: Path) -> list[Path]: - files = [] - for file in path.glob("**/*"): - files.append(file) - return files - - -def get_files_exists_in_library( - dirs_in_root: list[Path], lib_dir: str, path: Path -) -> list[Path]: - exists: list[Path] = [] - if not path.is_dir(): + msgBox.addButton("Skip", QMessageBox.ButtonRole.YesRole) + msgBox.addButton("Override", QMessageBox.ButtonRole.DestructiveRole) + msgBox.addButton("Rename", QMessageBox.ButtonRole.DestructiveRole) + msgBox.addButton("Cancel", QMessageBox.ButtonRole.NoRole) + return msgBox.exec() + + def get_files_exists_in_library(self, path: Path) -> list[Path]: + exists: list[Path] = [] + if not path.is_dir(): + return exists + + files = self.get_files_in_folder(path) + for file in files: + if file.is_dir(): + exists += self.get_files_exists_in_library(file) + elif (self.driver.lib.library_dir / self.get_relativ_path(file)).exists(): + exists.append(file) return exists - files = get_files_in_folder(path) - for file in files: - if file.is_dir(): - exists += get_files_exists_in_library(dirs_in_root, lib_dir, file) - elif (lib_dir / get_relativ_path(dirs_in_root, file)).exists(): - exists.append(file) - return exists - - -def get_relativ_paths(roots: list[Path], paths: list[Path]) -> list[Path]: - relativ_paths = [] - for file in paths: - relativ_paths.append(get_relativ_path(roots, file)) - return relativ_paths - - -def get_relativ_path(roots: list[Path], path: Path) -> Path: - for dir in roots: - if path.is_relative_to(dir): - return path.relative_to(dir) - return path.name + def get_relativ_paths(self, paths: list[Path]) -> list[Path]: + relativ_paths = [] + for file in paths: + relativ_paths.append(self.get_relativ_path(file)) + return relativ_paths + + def get_relativ_path(self, path: Path) -> Path: + for dir in self.dirs_in_root: + if path.is_relative_to(dir): + return path.relative_to(dir) + return Path(path.name) + + def get_files_in_folder(self, path: Path) -> list[Path]: + files = [] + for file in path.glob("**/*"): + files.append(file) + return files + + def get_renamed_duplicate_filename_in_lib(self, filePath: Path) -> str: + index = 2 + o_filename = filePath.name + dot_idx = o_filename.index(".") + while (self.driver.lib.library_dir / filePath).exists(): + filePath = filePath.with_name( + o_filename[:dot_idx] + f" ({index})" + o_filename[dot_idx:] + ) + index += 1 + return filePath.name diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index 43c630cc..f303c149 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -575,9 +575,10 @@ def init_library_window(self): # self.render_times: list = [] # self.main_window.setWindowFlag(Qt.FramelessWindowHint) + drop_import = DropImport(self) self.main_window.setAcceptDrops(True) self.main_window.dragEnterEvent = drop_import.dragEnterEvent - self.main_window.dropEvent = lambda event: drop_import.dropEvent(self, event) + self.main_window.dropEvent = drop_import.dropEvent self.main_window.dragMoveEvent = drop_import.dragMoveEvent # NOTE: Putting this early will result in a white non-responsive From c7f420be57f5221f49340431d2c9d179562c5f45 Mon Sep 17 00:00:00 2001 From: Creepler13 Date: Wed, 22 May 2024 18:50:21 +0200 Subject: [PATCH 24/36] Added Ability to Drag out of window --- tagstudio/src/qt/modals/drop_import.py | 36 +++++++++++++++++++++++--- tagstudio/src/qt/ts_qt.py | 17 +++++++----- 2 files changed, 44 insertions(+), 9 deletions(-) diff --git a/tagstudio/src/qt/modals/drop_import.py b/tagstudio/src/qt/modals/drop_import.py index 4bcd7e7f..fa5578c6 100644 --- a/tagstudio/src/qt/modals/drop_import.py +++ b/tagstudio/src/qt/modals/drop_import.py @@ -2,28 +2,55 @@ import shutil import typing -from PySide6.QtCore import QThreadPool -from PySide6.QtGui import QDropEvent, QDragEnterEvent, QDragMoveEvent +from PySide6.QtCore import QThreadPool,Qt,QMimeData,QUrl +from PySide6.QtGui import QDropEvent, QDragEnterEvent,QImage, QDragMoveEvent,QMouseEvent,QDrag,QDragLeaveEvent from PySide6.QtWidgets import QMessageBox from src.qt.widgets import ProgressWidget from src.qt.helpers import FunctionIterator, CustomRunnable +from ctypes import wintypes,windll if typing.TYPE_CHECKING: from src.qt.ts_qt import QtDriver +import logging class DropImport: def __init__(self, driver: "QtDriver"): self.driver = driver - + + def mouseMoveEvent(self,event:QMouseEvent): + if event.buttons() is not Qt.MouseButton.LeftButton: return + if len(self.driver.selected) == 0: return + + drag = QDrag(self.driver) + paths = [] + mimedata = QMimeData() + for selected in self.driver.selected: + entry =self.driver.lib.get_entry(selected[1]) + url = QUrl.fromLocalFile(self.driver.lib.library_dir+"/"+entry.path+"/"+entry.filename) + paths.append(url) + + mimedata.setUrls(paths) + drag.setMimeData(mimedata) + drag.exec(Qt.DropAction.CopyAction) + def dropEvent(self, event: QDropEvent): + if event.source() is self.driver: # change that if you want to drop something originating from tagstudio, for moving or so + return + if not event.mimeData().hasUrls(): return self.urls = event.mimeData().urls() self.import_files() + def dragLeaveEvent(self,event:QDragLeaveEvent): + if event.mimeData().hasUrls(): + event.accept() + else: + event.ignore() + def dragEnterEvent(self, event: QDragEnterEvent): if event.mimeData().hasUrls(): event.accept() @@ -34,6 +61,7 @@ def dragMoveEvent(self, event: QDragMoveEvent): if event.mimeData().hasUrls(): event.accept() else: + logging.info(self.driver.selected) event.ignore() def import_files(self): @@ -216,3 +244,5 @@ def get_renamed_duplicate_filename_in_lib(self, filePath: Path) -> str: ) index += 1 return filePath.name + + diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index f303c149..fa9247fc 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -272,6 +272,13 @@ def start(self) -> None: # f'QScrollBar::{{background:red;}}' # ) + self.drop_import = DropImport(self) + self.main_window.setAcceptDrops(True) + self.main_window.dragEnterEvent = self.drop_import.dragEnterEvent + self.main_window.dropEvent = self.drop_import.dropEvent + self.main_window.dragMoveEvent = self.drop_import.dragMoveEvent + + # # self.main_window.windowFlags() & # # self.main_window.setWindowFlag(Qt.WindowType.FramelessWindowHint, True) # self.main_window.setWindowFlag(Qt.WindowType.NoDropShadowWindowHint, True) @@ -575,12 +582,6 @@ def init_library_window(self): # self.render_times: list = [] # self.main_window.setWindowFlag(Qt.FramelessWindowHint) - drop_import = DropImport(self) - self.main_window.setAcceptDrops(True) - self.main_window.dragEnterEvent = drop_import.dragEnterEvent - self.main_window.dropEvent = drop_import.dropEvent - self.main_window.dragMoveEvent = drop_import.dragMoveEvent - # NOTE: Putting this early will result in a white non-responsive # window until everything is loaded. Consider adding a splash screen # or implementing some clever loading tricks. @@ -1066,6 +1067,10 @@ def _init_thumb_grid(self): item_thumb = ItemThumb( None, self.lib, self.preview_panel, (self.thumb_size, self.thumb_size) ) + + item_thumb.setMouseTracking(True) + item_thumb.mouseMoveEvent = self.drop_import.mouseMoveEvent + layout.addWidget(item_thumb) self.item_thumbs.append(item_thumb) From 40873ffc43b58cb197d7f1070a7365e05104347c Mon Sep 17 00:00:00 2001 From: Creepler13 Date: Wed, 22 May 2024 19:22:33 +0200 Subject: [PATCH 25/36] f --- tagstudio/src/qt/modals/drop_import.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tagstudio/src/qt/modals/drop_import.py b/tagstudio/src/qt/modals/drop_import.py index fa5578c6..3b0ebf72 100644 --- a/tagstudio/src/qt/modals/drop_import.py +++ b/tagstudio/src/qt/modals/drop_import.py @@ -2,6 +2,7 @@ import shutil import typing + from PySide6.QtCore import QThreadPool,Qt,QMimeData,QUrl from PySide6.QtGui import QDropEvent, QDragEnterEvent,QImage, QDragMoveEvent,QMouseEvent,QDrag,QDragLeaveEvent from PySide6.QtWidgets import QMessageBox From 234a9cb73869d8487e1e835eaf4eb4f825db4f2f Mon Sep 17 00:00:00 2001 From: Creepler13 Date: Wed, 22 May 2024 19:25:00 +0200 Subject: [PATCH 26/36] format --- tagstudio/src/qt/modals/drop_import.py | 47 +++++++++++++++----------- tagstudio/src/qt/ts_qt.py | 5 ++- 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/tagstudio/src/qt/modals/drop_import.py b/tagstudio/src/qt/modals/drop_import.py index 3b0ebf72..3cdcf251 100644 --- a/tagstudio/src/qt/modals/drop_import.py +++ b/tagstudio/src/qt/modals/drop_import.py @@ -2,42 +2,53 @@ import shutil import typing - -from PySide6.QtCore import QThreadPool,Qt,QMimeData,QUrl -from PySide6.QtGui import QDropEvent, QDragEnterEvent,QImage, QDragMoveEvent,QMouseEvent,QDrag,QDragLeaveEvent +from PySide6.QtCore import QThreadPool, Qt, QMimeData, QUrl +from PySide6.QtGui import ( + QDropEvent, + QDragEnterEvent, + QDragMoveEvent, + QMouseEvent, + QDrag, + QDragLeaveEvent, +) from PySide6.QtWidgets import QMessageBox from src.qt.widgets import ProgressWidget from src.qt.helpers import FunctionIterator, CustomRunnable -from ctypes import wintypes,windll - if typing.TYPE_CHECKING: from src.qt.ts_qt import QtDriver import logging + class DropImport: def __init__(self, driver: "QtDriver"): self.driver = driver - - def mouseMoveEvent(self,event:QMouseEvent): - if event.buttons() is not Qt.MouseButton.LeftButton: return - if len(self.driver.selected) == 0: return - + + def mouseMoveEvent(self, event: QMouseEvent): + if event.buttons() is not Qt.MouseButton.LeftButton: + return + if len(self.driver.selected) == 0: + return + drag = QDrag(self.driver) paths = [] mimedata = QMimeData() - for selected in self.driver.selected: - entry =self.driver.lib.get_entry(selected[1]) - url = QUrl.fromLocalFile(self.driver.lib.library_dir+"/"+entry.path+"/"+entry.filename) + for selected in self.driver.selected: + entry = self.driver.lib.get_entry(selected[1]) + url = QUrl.fromLocalFile( + self.driver.lib.library_dir + "/" + entry.path + "/" + entry.filename + ) paths.append(url) - + mimedata.setUrls(paths) drag.setMimeData(mimedata) drag.exec(Qt.DropAction.CopyAction) - + def dropEvent(self, event: QDropEvent): - if event.source() is self.driver: # change that if you want to drop something originating from tagstudio, for moving or so + if ( + event.source() is self.driver + ): # change that if you want to drop something originating from tagstudio, for moving or so return if not event.mimeData().hasUrls(): @@ -46,7 +57,7 @@ def dropEvent(self, event: QDropEvent): self.urls = event.mimeData().urls() self.import_files() - def dragLeaveEvent(self,event:QDragLeaveEvent): + def dragLeaveEvent(self, event: QDragLeaveEvent): if event.mimeData().hasUrls(): event.accept() else: @@ -245,5 +256,3 @@ def get_renamed_duplicate_filename_in_lib(self, filePath: Path) -> str: ) index += 1 return filePath.name - - diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index fa9247fc..d245d8b4 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -278,7 +278,6 @@ def start(self) -> None: self.main_window.dropEvent = self.drop_import.dropEvent self.main_window.dragMoveEvent = self.drop_import.dragMoveEvent - # # self.main_window.windowFlags() & # # self.main_window.setWindowFlag(Qt.WindowType.FramelessWindowHint, True) # self.main_window.setWindowFlag(Qt.WindowType.NoDropShadowWindowHint, True) @@ -1067,10 +1066,10 @@ def _init_thumb_grid(self): item_thumb = ItemThumb( None, self.lib, self.preview_panel, (self.thumb_size, self.thumb_size) ) - + item_thumb.setMouseTracking(True) item_thumb.mouseMoveEvent = self.drop_import.mouseMoveEvent - + layout.addWidget(item_thumb) self.item_thumbs.append(item_thumb) From 06774b69ca359ebe2a30fec9a1f589159eb1a553 Mon Sep 17 00:00:00 2001 From: Creepler13 Date: Wed, 22 May 2024 20:30:51 +0200 Subject: [PATCH 27/36] format --- tagstudio/src/qt/modals/drop_import.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tagstudio/src/qt/modals/drop_import.py b/tagstudio/src/qt/modals/drop_import.py index 9034a5df..daa78233 100644 --- a/tagstudio/src/qt/modals/drop_import.py +++ b/tagstudio/src/qt/modals/drop_import.py @@ -13,7 +13,7 @@ ) from PySide6.QtWidgets import QMessageBox from src.qt.widgets.progress import ProgressWidget -from src.qt.helpers.custom_runnable import CustomRunnable +from src.qt.helpers.custom_runnable import CustomRunnable from src.qt.helpers.function_iterator import FunctionIterator if typing.TYPE_CHECKING: From b453c11c174355c76aa24a14ac6f62258eda43a9 Mon Sep 17 00:00:00 2001 From: Creepler13 Date: Wed, 22 May 2024 20:57:02 +0200 Subject: [PATCH 28/36] formatting and refactor --- tagstudio/src/qt/modals/drop_import.py | 86 ++++++++++++-------------- 1 file changed, 39 insertions(+), 47 deletions(-) diff --git a/tagstudio/src/qt/modals/drop_import.py b/tagstudio/src/qt/modals/drop_import.py index daa78233..13682673 100644 --- a/tagstudio/src/qt/modals/drop_import.py +++ b/tagstudio/src/qt/modals/drop_import.py @@ -38,7 +38,7 @@ def mouseMoveEvent(self, event: QMouseEvent): for selected in self.driver.selected: entry = self.driver.lib.get_entry(selected[1]) url = QUrl.fromLocalFile( - self.driver.lib.library_dir + "/" + entry.path + "/" + entry.filename + Path(self.driver.lib.library_dir) / entry.path / entry.filename ) paths.append(url) @@ -82,27 +82,13 @@ def import_files(self): self.dirs_in_root: list[Path] = [] self.duplicate_files: list[Path] = [] - self.start_collection_progress() - - def start_collection_progress(self): - iterator = FunctionIterator(self.collect_files_to_import) - pw = ProgressWidget( - window_title="Searching Files", - label_text="Searching New Files...\nPreparing...", - cancel_button_text=None, - minimum=0, - maximum=0, - ) - pw.show() - iterator.value.connect(lambda x: pw.update_progress(x[0] + 1)) - iterator.value.connect( - lambda x: pw.update_label( - f'Searching New Files...\n{x[0]+1} File{"s" if x[0]+1 != 1 else ""} Found. {(f"{x[1]} Already exist in the library folders") if x[1]>0 else ""}' - ) + create_progress_bar( + self.collect_files_to_import, + "Searching Files", + "Searching New Files...\nPreparing...", + lambda x: f'Searching New Files...\n{x[0]+1} File{"s" if x[0]+1 != 1 else ""} Found. {(f"{x[1]} Already exist in the library folders") if x[1]>0 else ""}', + self.ask_user ) - r = CustomRunnable(lambda: iterator.run()) - r.done.connect(lambda: (pw.hide(), self.ask_user())) - QThreadPool.globalInstance().start(r) def collect_files_to_import(self): for url in self.urls: @@ -136,31 +122,6 @@ def collect_files_to_import(self): yield [len(self.files), len(self.duplicate_files)] - def start_copy_progress(self): - iterator = FunctionIterator(self.copy_files) - pw = ProgressWidget( - window_title="Import Files", - label_text="Importing New Files...\nPreparing...", - cancel_button_text=None, - minimum=0, - maximum=len(self.files), - ) - pw.show() - iterator.value.connect(lambda x: pw.update_progress(x[0] + 1)) - dupes_choice_text = ( - "Skipped" - if self.choice == 0 - else ("Overridden" if self.choice == 1 else "Renamed") - ) - iterator.value.connect( - lambda x: pw.update_label( - f'Importing New Files...\n{x[0]+1} File{"s" if x[0]+1 != 1 else ""} Imported. {(f"{x[1]} {dupes_choice_text}") if x[1]>0 else ""}' - ) - ) - r = CustomRunnable(lambda: iterator.run()) - r.done.connect(lambda: (pw.hide(), self.driver.add_new_files_runnable())) - QThreadPool.globalInstance().start(r) - def copy_files(self): fileCount = 0 duplicated_files_progress = 0 @@ -199,7 +160,19 @@ def ask_user(self): if self.choice == 3: # cancel return - self.start_copy_progress() + dupes_choice_text = ( + "Skipped" + if self.choice == 0 + else ("Overridden" if self.choice == 1 else "Renamed") + ) + create_progress_bar( + self.copy_files, + "Import Files", + "Importing New Files...\nPreparing...", + lambda x: f'Importing New Files...\n{x[0]+1} File{"s" if x[0]+1 != 1 else ""} Imported. {(f"{x[1]} {dupes_choice_text}") if x[1]>0 else ""}', + self.driver.add_new_files_runnable, + len(self.files), + ) def duplicates_choice(self) -> int: msgBox = QMessageBox() @@ -257,3 +230,22 @@ def get_renamed_duplicate_filename_in_lib(self, filePath: Path) -> str: ) index += 1 return filePath.name + + +def create_progress_bar( + function, title: str, text: str, update_label_callback, done_callback, max=0 +): + iterator = FunctionIterator(function) + pw = ProgressWidget( + window_title=title, + label_text=text, + cancel_button_text=None, + minimum=0, + maximum=max, + ) + pw.show() + iterator.value.connect(lambda x: pw.update_progress(x[0] + 1)) + iterator.value.connect(lambda x: pw.update_label(update_label_callback(x))) + r = CustomRunnable(lambda: iterator.run()) + r.done.connect(lambda: (pw.hide(), done_callback())) + QThreadPool.globalInstance().start(r) \ No newline at end of file From 20209850fca24a0b6f24720f098cf8e283a011eb Mon Sep 17 00:00:00 2001 From: Creepler13 Date: Wed, 22 May 2024 20:58:26 +0200 Subject: [PATCH 29/36] format again --- tagstudio/src/qt/modals/drop_import.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tagstudio/src/qt/modals/drop_import.py b/tagstudio/src/qt/modals/drop_import.py index 13682673..cca30cf4 100644 --- a/tagstudio/src/qt/modals/drop_import.py +++ b/tagstudio/src/qt/modals/drop_import.py @@ -84,10 +84,10 @@ def import_files(self): create_progress_bar( self.collect_files_to_import, - "Searching Files", - "Searching New Files...\nPreparing...", + "Searching Files", + "Searching New Files...\nPreparing...", lambda x: f'Searching New Files...\n{x[0]+1} File{"s" if x[0]+1 != 1 else ""} Found. {(f"{x[1]} Already exist in the library folders") if x[1]>0 else ""}', - self.ask_user + self.ask_user, ) def collect_files_to_import(self): @@ -248,4 +248,4 @@ def create_progress_bar( iterator.value.connect(lambda x: pw.update_label(update_label_callback(x))) r = CustomRunnable(lambda: iterator.run()) r.done.connect(lambda: (pw.hide(), done_callback())) - QThreadPool.globalInstance().start(r) \ No newline at end of file + QThreadPool.globalInstance().start(r) From acb27c761a1afd040f31837517cc25fa49649b6f Mon Sep 17 00:00:00 2001 From: Creepler13 Date: Wed, 22 May 2024 21:03:53 +0200 Subject: [PATCH 30/36] formatting for mypy --- tagstudio/src/qt/modals/drop_import.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tagstudio/src/qt/modals/drop_import.py b/tagstudio/src/qt/modals/drop_import.py index cca30cf4..9491adf0 100644 --- a/tagstudio/src/qt/modals/drop_import.py +++ b/tagstudio/src/qt/modals/drop_import.py @@ -86,7 +86,7 @@ def import_files(self): self.collect_files_to_import, "Searching Files", "Searching New Files...\nPreparing...", - lambda x: f'Searching New Files...\n{x[0]+1} File{"s" if x[0]+1 != 1 else ""} Found. {(f"{x[1]} Already exist in the library folders") if x[1]>0 else ""}', + lambda x: f'Searching New Files...\n{x[0]+1} File{'s' if x[0]+1 != 1 else ''} Found. {(f'{x[1]} Already exist in the library folders') if x[1]>0 else ''}', self.ask_user, ) @@ -169,7 +169,7 @@ def ask_user(self): self.copy_files, "Import Files", "Importing New Files...\nPreparing...", - lambda x: f'Importing New Files...\n{x[0]+1} File{"s" if x[0]+1 != 1 else ""} Imported. {(f"{x[1]} {dupes_choice_text}") if x[1]>0 else ""}', + lambda x: f'Importing New Files...\n{x[0]+1} File{'s' if x[0]+1 != 1 else ''} Imported. {(f'{x[1]} {dupes_choice_text}') if x[1]>0 else ''}', self.driver.add_new_files_runnable, len(self.files), ) @@ -181,7 +181,7 @@ def duplicates_choice(self) -> int: dupes_to_show = dupes_to_show[0:20] msgBox.setText( - f"The files {', '.join(map(lambda path: str(path),self.get_relativ_paths(dupes_to_show)))} {(f"and {len(self.duplicate_files)-20} more") if len(self.duplicate_files)>20 else ""} have filenames that already exist in the library folder." + f"The files {', '.join(map(lambda path: str(path),self.get_relativ_paths(dupes_to_show)))} {(f'and {len(self.duplicate_files)-20} more') if len(self.duplicate_files)>20 else ''} have filenames that already exist in the library folder." ) msgBox.addButton("Skip", QMessageBox.ButtonRole.YesRole) msgBox.addButton("Override", QMessageBox.ButtonRole.DestructiveRole) From fb1c3a256a030967266e6179848f2b812e740649 Mon Sep 17 00:00:00 2001 From: Creepler13 Date: Fri, 24 May 2024 17:31:05 +0200 Subject: [PATCH 31/36] convert lambda to func for clarity --- tagstudio/src/qt/modals/drop_import.py | 27 +++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/tagstudio/src/qt/modals/drop_import.py b/tagstudio/src/qt/modals/drop_import.py index 9491adf0..d209707c 100644 --- a/tagstudio/src/qt/modals/drop_import.py +++ b/tagstudio/src/qt/modals/drop_import.py @@ -82,11 +82,17 @@ def import_files(self): self.dirs_in_root: list[Path] = [] self.duplicate_files: list[Path] = [] + def displayed_text(x): + text = f"Searching New Files...\n{x[0]+1} File{'s' if x[0]+1 != 1 else ''} Found." + if x[1] == 0: + return text + return text + f" {x[1]} Already exist in the library folders" + create_progress_bar( self.collect_files_to_import, "Searching Files", "Searching New Files...\nPreparing...", - lambda x: f'Searching New Files...\n{x[0]+1} File{'s' if x[0]+1 != 1 else ''} Found. {(f'{x[1]} Already exist in the library folders') if x[1]>0 else ''}', + displayed_text, self.ask_user, ) @@ -160,16 +166,23 @@ def ask_user(self): if self.choice == 3: # cancel return - dupes_choice_text = ( - "Skipped" - if self.choice == 0 - else ("Overridden" if self.choice == 1 else "Renamed") - ) + def displayed_text(x): + dupes_choice_text = ( + "Skipped" + if self.choice == 0 + else ("Overridden" if self.choice == 1 else "Renamed") + ) + + text = f"Importing New Files...\n{x[0]+1} File{'s' if x[0]+1 != 1 else ''} Imported." + if x[1] == 0: + return text + return text + f" {x[1]} {dupes_choice_text}" + create_progress_bar( self.copy_files, "Import Files", "Importing New Files...\nPreparing...", - lambda x: f'Importing New Files...\n{x[0]+1} File{'s' if x[0]+1 != 1 else ''} Imported. {(f'{x[1]} {dupes_choice_text}') if x[1]>0 else ''}', + displayed_text, self.driver.add_new_files_runnable, len(self.files), ) From dfbf31ffa17e62208a95a536c9b1e38441b61e51 Mon Sep 17 00:00:00 2001 From: Creepler13 Date: Fri, 24 May 2024 17:50:58 +0200 Subject: [PATCH 32/36] mypy fixes --- tagstudio/src/qt/modals/drop_import.py | 8 +------- tagstudio/src/qt/ts_qt.py | 6 +++--- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/tagstudio/src/qt/modals/drop_import.py b/tagstudio/src/qt/modals/drop_import.py index d209707c..c920a333 100644 --- a/tagstudio/src/qt/modals/drop_import.py +++ b/tagstudio/src/qt/modals/drop_import.py @@ -58,12 +58,6 @@ def dropEvent(self, event: QDropEvent): self.urls = event.mimeData().urls() self.import_files() - def dragLeaveEvent(self, event: QDragLeaveEvent): - if event.mimeData().hasUrls(): - event.accept() - else: - event.ignore() - def dragEnterEvent(self, event: QDragEnterEvent): if event.mimeData().hasUrls(): event.accept() @@ -260,5 +254,5 @@ def create_progress_bar( iterator.value.connect(lambda x: pw.update_progress(x[0] + 1)) iterator.value.connect(lambda x: pw.update_label(update_label_callback(x))) r = CustomRunnable(lambda: iterator.run()) - r.done.connect(lambda: (pw.hide(), done_callback())) + r.done.connect(lambda: (pw.hide(), done_callback())) # type: ignore QThreadPool.globalInstance().start(r) diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index d245d8b4..3bbc05f3 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -274,9 +274,9 @@ def start(self) -> None: self.drop_import = DropImport(self) self.main_window.setAcceptDrops(True) - self.main_window.dragEnterEvent = self.drop_import.dragEnterEvent - self.main_window.dropEvent = self.drop_import.dropEvent - self.main_window.dragMoveEvent = self.drop_import.dragMoveEvent + self.main_window.dragEnterEvent = self.drop_import.dragEnterEvent # type: ignore + self.main_window.dropEvent = self.drop_import.dropEvent # type: ignore + self.main_window.dragMoveEvent = self.drop_import.dragMoveEvent # type: ignore # # self.main_window.windowFlags() & # # self.main_window.setWindowFlag(Qt.WindowType.FramelessWindowHint, True) From 45056f154b310ebe2d5fefb73bd2e1a1059152bb Mon Sep 17 00:00:00 2001 From: Creepler13 Date: Fri, 24 May 2024 20:57:49 +0200 Subject: [PATCH 33/36] fixed dragout only worked on selected --- tagstudio/src/qt/modals/drop_import.py | 31 ++------------------------ tagstudio/src/qt/ts_qt.py | 3 --- tagstudio/src/qt/widgets/item_thumb.py | 28 +++++++++++++++++++++-- 3 files changed, 28 insertions(+), 34 deletions(-) diff --git a/tagstudio/src/qt/modals/drop_import.py b/tagstudio/src/qt/modals/drop_import.py index c920a333..498859ad 100644 --- a/tagstudio/src/qt/modals/drop_import.py +++ b/tagstudio/src/qt/modals/drop_import.py @@ -2,15 +2,8 @@ import shutil import typing -from PySide6.QtCore import QThreadPool, Qt, QMimeData, QUrl -from PySide6.QtGui import ( - QDropEvent, - QDragEnterEvent, - QDragMoveEvent, - QMouseEvent, - QDrag, - QDragLeaveEvent, -) +from PySide6.QtCore import QThreadPool +from PySide6.QtGui import QDropEvent, QDragEnterEvent, QDragMoveEvent from PySide6.QtWidgets import QMessageBox from src.qt.widgets.progress import ProgressWidget from src.qt.helpers.custom_runnable import CustomRunnable @@ -26,26 +19,6 @@ class DropImport: def __init__(self, driver: "QtDriver"): self.driver = driver - def mouseMoveEvent(self, event: QMouseEvent): - if event.buttons() is not Qt.MouseButton.LeftButton: - return - if len(self.driver.selected) == 0: - return - - drag = QDrag(self.driver) - paths = [] - mimedata = QMimeData() - for selected in self.driver.selected: - entry = self.driver.lib.get_entry(selected[1]) - url = QUrl.fromLocalFile( - Path(self.driver.lib.library_dir) / entry.path / entry.filename - ) - paths.append(url) - - mimedata.setUrls(paths) - drag.setMimeData(mimedata) - drag.exec(Qt.DropAction.CopyAction) - def dropEvent(self, event: QDropEvent): if ( event.source() is self.driver diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index 3bbc05f3..fa08c1c3 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -1067,9 +1067,6 @@ def _init_thumb_grid(self): None, self.lib, self.preview_panel, (self.thumb_size, self.thumb_size) ) - item_thumb.setMouseTracking(True) - item_thumb.mouseMoveEvent = self.drop_import.mouseMoveEvent - layout.addWidget(item_thumb) self.item_thumbs.append(item_thumb) diff --git a/tagstudio/src/qt/widgets/item_thumb.py b/tagstudio/src/qt/widgets/item_thumb.py index bcd0283d..db951e83 100644 --- a/tagstudio/src/qt/widgets/item_thumb.py +++ b/tagstudio/src/qt/widgets/item_thumb.py @@ -12,8 +12,8 @@ from typing import Optional from PIL import Image, ImageQt -from PySide6.QtCore import Qt, QSize, QEvent -from PySide6.QtGui import QPixmap, QEnterEvent, QAction +from PySide6.QtCore import Qt, QSize, QEvent, QMimeData, QUrl +from PySide6.QtGui import QPixmap, QEnterEvent, QAction, QDrag from PySide6.QtWidgets import ( QWidget, QVBoxLayout, @@ -108,6 +108,7 @@ def __init__( self.thumb_size: tuple[int, int] = thumb_size self.setMinimumSize(*thumb_size) self.setMaximumSize(*thumb_size) + self.setMouseTracking(True) check_size = 24 # self.setStyleSheet('background-color:red;') @@ -495,3 +496,26 @@ def toggle_tag(entry: Entry): if self.panel.isOpen: self.panel.update_widgets() self.panel.driver.update_badges() + + def mouseMoveEvent(self, event): + if event.buttons() is not Qt.MouseButton.LeftButton: + return + + drag = QDrag(self.panel.driver) + paths = [] + mimedata = QMimeData() + + selected_ids = list(map(lambda x: x[1], self.panel.driver.selected)) + if self.item_id not in selected_ids: + selected_ids = [self.item_id] + + for id in selected_ids: + entry = self.lib.get_entry(id) + url = QUrl.fromLocalFile( + Path(self.lib.library_dir) / entry.path / entry.filename + ) + paths.append(url) + + mimedata.setUrls(paths) + drag.setMimeData(mimedata) + drag.exec(Qt.DropAction.CopyAction) From 301a7b0b3bcdb19f4598719ffa6edf6550853b90 Mon Sep 17 00:00:00 2001 From: Travis Abendshien Date: Thu, 13 Jun 2024 10:19:40 -0700 Subject: [PATCH 34/36] Refactor typo, Add license --- tagstudio/src/qt/modals/drop_import.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/tagstudio/src/qt/modals/drop_import.py b/tagstudio/src/qt/modals/drop_import.py index 498859ad..97a2fe52 100644 --- a/tagstudio/src/qt/modals/drop_import.py +++ b/tagstudio/src/qt/modals/drop_import.py @@ -1,3 +1,6 @@ +# Licensed under the GPL-3.0 License. +# Created for TagStudio: https://github.com/CyanVoxel/TagStudio + from pathlib import Path import shutil import typing @@ -76,7 +79,7 @@ def collect_files_to_import(self): continue self.files.append(f) if ( - self.driver.lib.library_dir / self.get_relativ_path(file) + self.driver.lib.library_dir / self.get_relative_path(file) ).exists(): self.duplicate_files.append(f) yield [len(self.files), len(self.duplicate_files)] @@ -102,7 +105,7 @@ def copy_files(self): if file.is_dir(): continue - dest_file = self.get_relativ_path(file) + dest_file = self.get_relative_path(file) if file in self.duplicate_files: duplicated_files_progress += 1 @@ -161,7 +164,7 @@ def duplicates_choice(self) -> int: dupes_to_show = dupes_to_show[0:20] msgBox.setText( - f"The files {', '.join(map(lambda path: str(path),self.get_relativ_paths(dupes_to_show)))} {(f'and {len(self.duplicate_files)-20} more') if len(self.duplicate_files)>20 else ''} have filenames that already exist in the library folder." + f"The files {', '.join(map(lambda path: str(path),self.get_relative_paths(dupes_to_show)))} {(f'and {len(self.duplicate_files)-20} more') if len(self.duplicate_files)>20 else ''} have filenames that already exist in the library folder." ) msgBox.addButton("Skip", QMessageBox.ButtonRole.YesRole) msgBox.addButton("Override", QMessageBox.ButtonRole.DestructiveRole) @@ -178,17 +181,17 @@ def get_files_exists_in_library(self, path: Path) -> list[Path]: for file in files: if file.is_dir(): exists += self.get_files_exists_in_library(file) - elif (self.driver.lib.library_dir / self.get_relativ_path(file)).exists(): + elif (self.driver.lib.library_dir / self.get_relative_path(file)).exists(): exists.append(file) return exists - def get_relativ_paths(self, paths: list[Path]) -> list[Path]: - relativ_paths = [] + def get_relative_paths(self, paths: list[Path]) -> list[Path]: + relative_paths = [] for file in paths: - relativ_paths.append(self.get_relativ_path(file)) - return relativ_paths + relative_paths.append(self.get_relative_path(file)) + return relative_paths - def get_relativ_path(self, path: Path) -> Path: + def get_relative_path(self, path: Path) -> Path: for dir in self.dirs_in_root: if path.is_relative_to(dir): return path.relative_to(dir) From 763d189b2fd3bbaf264c7eba30c8c58e9a5d7dbe Mon Sep 17 00:00:00 2001 From: Travis Abendshien Date: Thu, 13 Jun 2024 10:39:44 -0700 Subject: [PATCH 35/36] Reformat QMessageBox --- tagstudio/src/qt/modals/drop_import.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tagstudio/src/qt/modals/drop_import.py b/tagstudio/src/qt/modals/drop_import.py index 97a2fe52..2796a81a 100644 --- a/tagstudio/src/qt/modals/drop_import.py +++ b/tagstudio/src/qt/modals/drop_import.py @@ -158,13 +158,18 @@ def displayed_text(x): ) def duplicates_choice(self) -> int: + display_limit: int = 5 msgBox = QMessageBox() + msgBox.setWindowTitle( + f"File Conflict{'s' if len(self.duplicate_files) > 1 else ''}" + ) + dupes_to_show = self.duplicate_files - if len(self.duplicate_files) > 20: - dupes_to_show = dupes_to_show[0:20] + if len(self.duplicate_files) > display_limit: + dupes_to_show = dupes_to_show[0:display_limit] msgBox.setText( - f"The files {', '.join(map(lambda path: str(path),self.get_relative_paths(dupes_to_show)))} {(f'and {len(self.duplicate_files)-20} more') if len(self.duplicate_files)>20 else ''} have filenames that already exist in the library folder." + f"The following files:\n {'\n '.join(map(lambda path: str(path),self.get_relative_paths(dupes_to_show)))} {(f'\nand {len(self.duplicate_files)-display_limit} more ') if len(self.duplicate_files)>display_limit else '\n'}have filenames that already exist in the library folder." ) msgBox.addButton("Skip", QMessageBox.ButtonRole.YesRole) msgBox.addButton("Override", QMessageBox.ButtonRole.DestructiveRole) From 9549d923bd3b4171e01ee32ee1e136edf8bba701 Mon Sep 17 00:00:00 2001 From: Travis Abendshien Date: Thu, 13 Jun 2024 10:50:24 -0700 Subject: [PATCH 36/36] Disable drops when no library is open --- tagstudio/src/qt/ts_qt.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index fa08c1c3..4812223a 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -273,7 +273,6 @@ def start(self) -> None: # ) self.drop_import = DropImport(self) - self.main_window.setAcceptDrops(True) self.main_window.dragEnterEvent = self.drop_import.dragEnterEvent # type: ignore self.main_window.dropEvent = self.drop_import.dropEvent # type: ignore self.main_window.dragMoveEvent = self.drop_import.dragMoveEvent # type: ignore @@ -664,6 +663,7 @@ def close_library(self): self.lib.clear_internal_vars() title_text = f"{self.base_title}" self.main_window.setWindowTitle(title_text) + self.main_window.setAcceptDrops(False) self.nav_frames = [] self.cur_frame_idx = -1 @@ -1423,6 +1423,7 @@ def open_library(self, path): self.update_libs_list(path) title_text = f"{self.base_title} - Library '{self.lib.library_dir}'" self.main_window.setWindowTitle(title_text) + self.main_window.setAcceptDrops(True) self.nav_frames = [] self.cur_frame_idx = -1