From fba2f8f46ba9e45591b4dc3c5ffce59033d8acaf Mon Sep 17 00:00:00 2001 From: DrRetro Date: Wed, 24 Apr 2024 11:31:53 -0400 Subject: [PATCH 01/23] Multi-Select Tag Adding --- .vscode/launch.json | 4 ++-- start_win.bat | 2 +- tagstudio/src/qt/ts_qt.py | 22 +++++++++++++--------- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index e9505d33..10f880f3 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -6,12 +6,12 @@ "configurations": [ { "name": "TagStudio", - "type": "python", + "type": "debugpy", "request": "launch", "program": "${workspaceRoot}\\TagStudio\\tagstudio.py", "console": "integratedTerminal", "justMyCode": true, - "args": [] + "args": ["--debug"] } ] } diff --git a/start_win.bat b/start_win.bat index 51a5ddf8..9258657f 100644 --- a/start_win.bat +++ b/start_win.bat @@ -1,2 +1,2 @@ @echo off -.venv\Scripts\python.exe .\TagStudio\tagstudio.py --ui qt %* \ No newline at end of file +.venv\Scripts\python.exe .\TagStudio\tagstudio.py --ui qt %* --debug \ No newline at end of file diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index 45ebd0d8..2d007f46 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -395,15 +395,19 @@ class TagBoxWidget(FieldWidget): # self.base_layout.addWidget(TagWidget(self.lib, self.lib.get_tag(tag), True)) # self.tags.append(tag) logging.info(f'[TAG BOX WIDGET] ADD TAG CALLBACK: T:{tag_id} to E:{self.item.id}') - if type(self.item) == Entry: - self.item.add_tag(self.lib, tag_id, field_id=-1, field_index=self.field_index) - logging.info(f'[TAG BOX WIDGET] UPDATED EMITTED: {tag_id}') - self.updated.emit() - # logging.info(f'I want to add tag ID {tag_id} to entry {self.item.filename}') - # self.updated.emit() - # if tag_id not in self.tags: - # self.tags.append(tag_id) - # self.set_tags(self.tags) + logging.info(f'[TAG BOX WIDGET] SELECTED T:{self.driver.selected}') + for x in self.driver.selected: + if x[0] == ItemType.ENTRY: + self.driver.lib.get_entry(x[1]).add_tag(self.driver.lib, tag_id, field_id=-1, field_index=self.field_index) + logging.info(f'[TAG BOX WIDGET] UPDATED EMITTED: {tag_id}') + self.updated.emit() + # logging.info(f'I want to add tag ID {tag_id} to entry {self.item.filename}') + # self.updated.emit() + # if tag_id not in self.tags: + # self.tags.append(tag_id) + # self.set_tags(self.tags) + # elif type((x[0]) == ThumbButton): + def edit_tag_callback(self, tag:Tag): self.lib.update_tag(tag) From 3974c1b031e4b500abb855741075c4ccfaef216e Mon Sep 17 00:00:00 2001 From: DrRetro Date: Wed, 24 Apr 2024 12:09:07 -0400 Subject: [PATCH 02/23] Multi-Select Removing Tags --- tagstudio/src/qt/ts_qt.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index 2d007f46..329cdf55 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -360,7 +360,7 @@ class TagBoxWidget(FieldWidget): # ) tw = TagWidget(self.lib, self.lib.get_tag(tag), True, True) tw.on_click.connect(lambda checked=False, q=f'tag_id: {tag}': (self.driver.main_window.searchField.setText(q), self.driver.filter_items(q))) - tw.on_remove.connect(lambda checked=False, t=tag: (self.lib.get_entry(self.item.id).remove_tag(self.lib, t, self.field_index), self.updated.emit())) + tw.on_remove.connect(lambda checked=False, t=tag: (self.remove_tag(t))) tw.on_edit.connect(lambda checked=False, t=tag: (self.edit_tag(t))) self.base_layout.addWidget(tw) self.tags = tags @@ -375,7 +375,8 @@ class TagBoxWidget(FieldWidget): # doesn't move all the way to the left. if self.base_layout.itemAt(0) and not self.base_layout.itemAt(1): self.base_layout.update() - + + def edit_tag(self, tag_id:int): btp = BuildTagPanel(self.lib, tag_id) # btp.on_edit.connect(lambda x: self.edit_tag_callback(x)) @@ -396,6 +397,7 @@ class TagBoxWidget(FieldWidget): # self.tags.append(tag) logging.info(f'[TAG BOX WIDGET] ADD TAG CALLBACK: T:{tag_id} to E:{self.item.id}') logging.info(f'[TAG BOX WIDGET] SELECTED T:{self.driver.selected}') + #Uses selected list from driver to remove tag from all selected files. for x in self.driver.selected: if x[0] == ItemType.ENTRY: self.driver.lib.get_entry(x[1]).add_tag(self.driver.lib, tag_id, field_id=-1, field_index=self.field_index) @@ -413,9 +415,14 @@ class TagBoxWidget(FieldWidget): self.lib.update_tag(tag) - def remove_tag(self): - # NOTE: You'll need to account for the add button at the end. - pass + def remove_tag(self, tag_id): + #Uses selected list from driver to remove tag from all selected files. + logging.info(f'[TAG BOX WIDGET] SELECTED T:{self.driver.selected}') + for x in self.driver.selected: + if x[0] == ItemType.ENTRY: + self.driver.lib.get_entry(x[1]).remove_tag(self.driver.lib, tag_id, field_index=self.field_index) + self.updated.emit() + # def show_add_button(self, value:bool): # self.add_button.setHidden(not value) From def244b7325dff89784dbad6c27d271b6c401998 Mon Sep 17 00:00:00 2001 From: DrRetro Date: Wed, 24 Apr 2024 15:18:22 -0400 Subject: [PATCH 03/23] Fixed bug that occured with adding other fields to one entry. --- tagstudio/src/qt/ts_qt.py | 100 +++++++++++++++++++++++++++++++++++--- 1 file changed, 93 insertions(+), 7 deletions(-) diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index 329cdf55..62a36af0 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -397,11 +397,51 @@ class TagBoxWidget(FieldWidget): # self.tags.append(tag) logging.info(f'[TAG BOX WIDGET] ADD TAG CALLBACK: T:{tag_id} to E:{self.item.id}') logging.info(f'[TAG BOX WIDGET] SELECTED T:{self.driver.selected}') - #Uses selected list from driver to remove tag from all selected files. + # TODO: Make this more efficient. + # Iterate through the fields of the item and find the field that + # contains the tags. + correct_field_id = 0 # Initialize the field index + skip = False # Initialize the skip flag + temp_tags = self.tags.copy() # Make a copy of the tags + while correct_field_id < len(self.item.fields): + field = list(self.item.fields)[correct_field_id] + for key in dict(field).keys(): + temp = field[key] + if type(temp) == list: + # Check if the this field's tags list to see if it matches the current field's list + if self.tags == temp: + # Update the correct_field_id if a match is found + correct_field_id = key + skip = True + break + if skip: + break + correct_field_id += 1 for x in self.driver.selected: if x[0] == ItemType.ENTRY: - self.driver.lib.get_entry(x[1]).add_tag(self.driver.lib, tag_id, field_id=-1, field_index=self.field_index) - logging.info(f'[TAG BOX WIDGET] UPDATED EMITTED: {tag_id}') + logging.info(f'[TAG BOX WIDGET] FIELDS:{self.driver.lib.get_entry(x[1]).fields}') + index = 0 # Initialize the index + skip = False # Initialize the skip flag + if not skip: + # Iterate through the fields of the current entry + while index < len(self.driver.lib.get_entry(x[1]).fields): + # Get the current fields + field = list(self.driver.lib.get_entry(x[1]).fields)[index] + for key in dict(field).keys(): + temp = field[key] + if type(temp) == list: + if temp_tags == temp and key == correct_field_id: + skip = True + break + if skip: + break + else: + index += 1 # Increment the index + if not skip and index >= len(self.driver.lib.get_entry(x[1]).fields): + # Add the field to the entry + self.driver.lib.add_field_to_entry(x[1],correct_field_id) + index = 0 + self.driver.lib.get_entry(x[1]).add_tag(self.driver.lib, tag_id, field_id=correct_field_id, field_index=index) self.updated.emit() # logging.info(f'I want to add tag ID {tag_id} to entry {self.item.filename}') # self.updated.emit() @@ -413,14 +453,59 @@ class TagBoxWidget(FieldWidget): def edit_tag_callback(self, tag:Tag): self.lib.update_tag(tag) - - + def remove_tag(self, tag_id): - #Uses selected list from driver to remove tag from all selected files. logging.info(f'[TAG BOX WIDGET] SELECTED T:{self.driver.selected}') + + # Iterate through the fields of the item and find the field that + # contains the tags. + # TODO: Make this more efficient. + correct_field_id = 0 # Initialize the field index + skip = False # Initialize the skip flag + temp_tags = self.tags.copy() # Make a copy of the tags + while correct_field_id < len(self.item.fields): + # Get the current field + field = list(self.item.fields)[correct_field_id] + for key in dict(field).keys(): + temp = field[key] + if type(temp) == list: + # Check if the this field's tags list to see if it matches the current field's list + if self.tags == temp: + # Update the correct_field_id if a match is found + correct_field_id = key + skip = True + break + if skip: + break + correct_field_id += 1 + # Iterate through the selected entries for x in self.driver.selected: + # Check if the current entry is of type Entry if x[0] == ItemType.ENTRY: - self.driver.lib.get_entry(x[1]).remove_tag(self.driver.lib, tag_id, field_index=self.field_index) + index = 0 # Initialize the index + skip = False # Initialize the skip flag + # Check if the skip flag is False + if not skip: + # Iterate through the fields of the current entry + while index < len(self.driver.lib.get_entry(x[1]).fields): + # Get the current fields + field = list(self.driver.lib.get_entry(x[1]).fields)[index] + for key in dict(field).keys(): + temp = field[key] + if type(temp) == list: + if temp_tags == temp and key == correct_field_id: + skip = True + break + if skip: + break + else: + index += 1 # Increment the index + # Check if the skip flag is False and the index is out of bounds + if not skip and index >= len(self.driver.lib.get_entry(x[1]).fields): + # Add the field to the entry + self.driver.lib.add_field_to_entry(x[1],correct_field_id) + index = 0 # Reset the index + self.driver.lib.get_entry(x[1]).remove_tag(self.driver.lib, tag_id, field_index=index) self.updated.emit() # def show_add_button(self, value:bool): @@ -428,6 +513,7 @@ class TagBoxWidget(FieldWidget): class TextWidget(FieldWidget): + def __init__(self, title, text:str) -> None: super().__init__(title) # self.item = item From 9e61d45ea502d8d9deb7cf59245361a3f3232536 Mon Sep 17 00:00:00 2001 From: DrRetro Date: Wed, 24 Apr 2024 18:11:41 -0400 Subject: [PATCH 04/23] Rewrote Multi-Select to use field templates. --- tagstudio/src/qt/ts_qt.py | 114 +++++--------------------------------- 1 file changed, 13 insertions(+), 101 deletions(-) diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index 62a36af0..c5d2f942 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -292,7 +292,7 @@ class FieldWidget(QWidget): class TagBoxWidget(FieldWidget): updated = Signal() - + def __init__(self, item, title, field_index, library:Library, tags:list[int], driver:'QtDriver') -> None: super().__init__(title) # QObject.__init__(self) @@ -397,58 +397,17 @@ class TagBoxWidget(FieldWidget): # self.tags.append(tag) logging.info(f'[TAG BOX WIDGET] ADD TAG CALLBACK: T:{tag_id} to E:{self.item.id}') logging.info(f'[TAG BOX WIDGET] SELECTED T:{self.driver.selected}') - # TODO: Make this more efficient. - # Iterate through the fields of the item and find the field that - # contains the tags. - correct_field_id = 0 # Initialize the field index - skip = False # Initialize the skip flag - temp_tags = self.tags.copy() # Make a copy of the tags - while correct_field_id < len(self.item.fields): - field = list(self.item.fields)[correct_field_id] - for key in dict(field).keys(): - temp = field[key] - if type(temp) == list: - # Check if the this field's tags list to see if it matches the current field's list - if self.tags == temp: - # Update the correct_field_id if a match is found - correct_field_id = key - skip = True - break - if skip: - break - correct_field_id += 1 + logging.info(f'[TAG BOX WIDGET] SELECTED T:{self.title}') + id = list(self.lib.filter_field_templates(str(self.title).removesuffix(' (Tag Box)')))[0] for x in self.driver.selected: - if x[0] == ItemType.ENTRY: - logging.info(f'[TAG BOX WIDGET] FIELDS:{self.driver.lib.get_entry(x[1]).fields}') - index = 0 # Initialize the index - skip = False # Initialize the skip flag - if not skip: - # Iterate through the fields of the current entry - while index < len(self.driver.lib.get_entry(x[1]).fields): - # Get the current fields - field = list(self.driver.lib.get_entry(x[1]).fields)[index] - for key in dict(field).keys(): - temp = field[key] - if type(temp) == list: - if temp_tags == temp and key == correct_field_id: - skip = True - break - if skip: - break - else: - index += 1 # Increment the index - if not skip and index >= len(self.driver.lib.get_entry(x[1]).fields): - # Add the field to the entry - self.driver.lib.add_field_to_entry(x[1],correct_field_id) - index = 0 - self.driver.lib.get_entry(x[1]).add_tag(self.driver.lib, tag_id, field_id=correct_field_id, field_index=index) + self.driver.lib.get_entry(x[1]).add_tag(self.driver.lib, tag_id, field_id=id, field_index=-1) self.updated.emit() - # logging.info(f'I want to add tag ID {tag_id} to entry {self.item.filename}') - # self.updated.emit() - # if tag_id not in self.tags: - # self.tags.append(tag_id) - # self.set_tags(self.tags) - # elif type((x[0]) == ThumbButton): + # logging.info(f'I want to add tag ID {tag_id} to entry {self.item.filename}') + # self.updated.emit() + # if tag_id not in self.tags: + # self.tags.append(tag_id) + # self.set_tags(self.tags) + # elif type((x[0]) == ThumbButton): def edit_tag_callback(self, tag:Tag): @@ -456,57 +415,10 @@ class TagBoxWidget(FieldWidget): def remove_tag(self, tag_id): logging.info(f'[TAG BOX WIDGET] SELECTED T:{self.driver.selected}') - - # Iterate through the fields of the item and find the field that - # contains the tags. - # TODO: Make this more efficient. - correct_field_id = 0 # Initialize the field index - skip = False # Initialize the skip flag - temp_tags = self.tags.copy() # Make a copy of the tags - while correct_field_id < len(self.item.fields): - # Get the current field - field = list(self.item.fields)[correct_field_id] - for key in dict(field).keys(): - temp = field[key] - if type(temp) == list: - # Check if the this field's tags list to see if it matches the current field's list - if self.tags == temp: - # Update the correct_field_id if a match is found - correct_field_id = key - skip = True - break - if skip: - break - correct_field_id += 1 - # Iterate through the selected entries + id = list(self.lib.filter_field_templates(str(self.title).removesuffix(' (Tag Box)')))[0] for x in self.driver.selected: - # Check if the current entry is of type Entry - if x[0] == ItemType.ENTRY: - index = 0 # Initialize the index - skip = False # Initialize the skip flag - # Check if the skip flag is False - if not skip: - # Iterate through the fields of the current entry - while index < len(self.driver.lib.get_entry(x[1]).fields): - # Get the current fields - field = list(self.driver.lib.get_entry(x[1]).fields)[index] - for key in dict(field).keys(): - temp = field[key] - if type(temp) == list: - if temp_tags == temp and key == correct_field_id: - skip = True - break - if skip: - break - else: - index += 1 # Increment the index - # Check if the skip flag is False and the index is out of bounds - if not skip and index >= len(self.driver.lib.get_entry(x[1]).fields): - # Add the field to the entry - self.driver.lib.add_field_to_entry(x[1],correct_field_id) - index = 0 # Reset the index - self.driver.lib.get_entry(x[1]).remove_tag(self.driver.lib, tag_id, field_index=index) - self.updated.emit() + self.driver.lib.get_entry(x[1]).remove_tag(self.driver.lib, tag_id, field_id=id, field_index=-1) + self.updated.emit() # def show_add_button(self, value:bool): # self.add_button.setHidden(not value) From b2fbc4b4a2e31fa9eecdedc555bc4cbd11419fde Mon Sep 17 00:00:00 2001 From: DrRetro Date: Thu, 25 Apr 2024 09:34:14 -0400 Subject: [PATCH 05/23] Large Changes to how code gets Field ID. --- tagstudio/src/qt/ts_qt.py | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index c5d2f942..6aeccd4b 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -284,12 +284,14 @@ class FieldContainer(QWidget): class FieldWidget(QWidget): + field = dict def __init__(self, title) -> None: super().__init__() # self.item = item self.title = title + class TagBoxWidget(FieldWidget): updated = Signal() @@ -397,11 +399,14 @@ class TagBoxWidget(FieldWidget): # self.tags.append(tag) logging.info(f'[TAG BOX WIDGET] ADD TAG CALLBACK: T:{tag_id} to E:{self.item.id}') logging.info(f'[TAG BOX WIDGET] SELECTED T:{self.driver.selected}') - logging.info(f'[TAG BOX WIDGET] SELECTED T:{self.title}') - id = list(self.lib.filter_field_templates(str(self.title).removesuffix(' (Tag Box)')))[0] + id = list(self.field.keys())[0] for x in self.driver.selected: self.driver.lib.get_entry(x[1]).add_tag(self.driver.lib, tag_id, field_id=id, field_index=-1) self.updated.emit() + self.driver.update_thumbs() + + # if type((x[0]) == ThumbButton): + # # TODO: Remove space from the special search here (tag_id:x) once that system is finalized. # logging.info(f'I want to add tag ID {tag_id} to entry {self.item.filename}') # self.updated.emit() # if tag_id not in self.tags: @@ -415,10 +420,12 @@ class TagBoxWidget(FieldWidget): def remove_tag(self, tag_id): logging.info(f'[TAG BOX WIDGET] SELECTED T:{self.driver.selected}') - id = list(self.lib.filter_field_templates(str(self.title).removesuffix(' (Tag Box)')))[0] + id = list(self.field.keys())[0] for x in self.driver.selected: - self.driver.lib.get_entry(x[1]).remove_tag(self.driver.lib, tag_id, field_id=id, field_index=-1) + index = self.driver.lib.get_field_index_in_entry(self.driver.lib.get_entry(x[1]),id) + self.driver.lib.get_entry(x[1]).remove_tag(self.driver.lib, tag_id,field_index=index[0]) self.updated.emit() + self.driver.update_thumbs() # def show_add_button(self, value:bool): # self.add_button.setHidden(not value) @@ -2320,7 +2327,6 @@ class PreviewPanel(QWidget): container = self.containers[index] # container.inner_layout.removeItem(container.inner_layout.itemAt(1)) # container.setHidden(False) - if self.lib.get_field_attr(field, 'type') == 'tag_box': # logging.info(f'WRITING TAGBOX FOR ITEM {item.id}') container.set_title(self.lib.get_field_attr(field, 'name')) @@ -2342,7 +2348,7 @@ class PreviewPanel(QWidget): inner_container = TagBoxWidget(item, title, index, self.lib, self.lib.get_field_attr(field, 'content'), self.driver) container.set_inner_widget(inner_container) - + inner_container.field = field inner_container.updated.connect(lambda: (self.write_container(index, field), self.tags_updated.emit())) # if type(item) == Entry: # NOTE: Tag Boxes have no Edit Button (But will when you can convert field types) @@ -2949,15 +2955,17 @@ class ItemThumb(FlowWidget): # logging.info(f'Favorite Check: {value}, Mode: {self.mode}') if self.mode == ItemType.ENTRY: self.isFavorite = value - e = self.lib.get_entry(self.item_id) - if value: - self.favorite_badge.setHidden(False) - DEFAULT_META_TAG_FIELD = 8 - e.add_tag(self.lib, 1, DEFAULT_META_TAG_FIELD) - else: - e.remove_tag(self.lib, 1) + DEFAULT_META_TAG_FIELD = 8 + for x in self.panel.driver.selected: + e = self.lib.get_entry(x[1]) + if value: + self.favorite_badge.setHidden(False) + e.add_tag(self.panel.driver.lib, 1, field_id=DEFAULT_META_TAG_FIELD, field_index=-1) + else: + e.remove_tag(self.panel.driver.lib, 1) if self.panel.isOpen: self.panel.update_widgets() + self.panel.driver.update_thumbs() # def on_favorite_uncheck(self): # if self.mode == SearchItemType.ENTRY: From 794401ae5898446b9b02af9c025ca7b155e0838e Mon Sep 17 00:00:00 2001 From: DrRetro Date: Thu, 25 Apr 2024 09:40:12 -0400 Subject: [PATCH 06/23] Archive Button and Favorite Button Now work --- tagstudio/src/qt/ts_qt.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index 6aeccd4b..7ddb03bd 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -2936,15 +2936,18 @@ class ItemThumb(FlowWidget): # logging.info(f'Archived Check: {value}, Mode: {self.mode}') if self.mode == ItemType.ENTRY: self.isArchived = value - e = self.lib.get_entry(self.item_id) - if value: - self.archived_badge.setHidden(False) - DEFAULT_META_TAG_FIELD = 8 - e.add_tag(self.lib, 0, DEFAULT_META_TAG_FIELD) - else: - e.remove_tag(self.lib, 0) + DEFAULT_META_TAG_FIELD = 8 + for x in self.panel.driver.selected: + e = self.lib.get_entry(x[1]) + if value: + self.archived_badge.setHidden(False) + e.add_tag(self.panel.driver.lib, 0, field_id=DEFAULT_META_TAG_FIELD, field_index=-1) + else: + e.remove_tag(self.panel.driver.lib, 0) if self.panel.isOpen: self.panel.update_widgets() + self.panel.driver.update_thumbs() + # def on_archived_uncheck(self): # if self.mode == SearchItemType.ENTRY: From 2a46251831dc97df31a8b209138bea7270f715fb Mon Sep 17 00:00:00 2001 From: DrRetro Date: Thu, 25 Apr 2024 09:57:37 -0400 Subject: [PATCH 07/23] Fixed slow down from refreshing all thumbnails for every added and removed tag. --- tagstudio/src/qt/ts_qt.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index 5a8122ec..19daa657 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -418,7 +418,8 @@ class TagBoxWidget(FieldWidget): for x in self.driver.selected: self.driver.lib.get_entry(x[1]).add_tag(self.driver.lib, tag_id, field_id=id, field_index=-1) self.updated.emit() - self.driver.update_thumbs() + if tag_id == 0 or tag_id == 1: + self.driver.update_thumbs() # if type((x[0]) == ThumbButton): # # TODO: Remove space from the special search here (tag_id:x) once that system is finalized. @@ -440,7 +441,8 @@ class TagBoxWidget(FieldWidget): index = self.driver.lib.get_field_index_in_entry(self.driver.lib.get_entry(x[1]),id) self.driver.lib.get_entry(x[1]).remove_tag(self.driver.lib, tag_id,field_index=index[0]) self.updated.emit() - self.driver.update_thumbs() + if tag_id == 0 or tag_id == 1: + self.driver.update_thumbs() # def show_add_button(self, value:bool): # self.add_button.setHidden(not value) From 201a63e27363b16516aeb74394c7a939ba7a176c Mon Sep 17 00:00:00 2001 From: DrRetro Date: Thu, 25 Apr 2024 20:51:12 -0400 Subject: [PATCH 08/23] Refresh_badges added to QtDriver, and favorite and archived badges checks selection. --- .vscode/launch.json | 4 ++-- tagstudio/src/qt/ts_qt.py | 45 +++++++++++++++++++++++++++++++-------- 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index a6c172fe..8838fbb3 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -6,12 +6,12 @@ "configurations": [ { "name": "TagStudio", - "type": "debugpy", + "type": "python", "request": "launch", "program": "${workspaceRoot}/tagstudio/tag_studio.py", "console": "integratedTerminal", "justMyCode": true, - "args": ["--debug"] + "args": [] } ] } diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index 19daa657..653e9b67 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -419,7 +419,7 @@ class TagBoxWidget(FieldWidget): self.driver.lib.get_entry(x[1]).add_tag(self.driver.lib, tag_id, field_id=id, field_index=-1) self.updated.emit() if tag_id == 0 or tag_id == 1: - self.driver.update_thumbs() + self.driver.update_badges() # if type((x[0]) == ThumbButton): # # TODO: Remove space from the special search here (tag_id:x) once that system is finalized. @@ -442,7 +442,7 @@ class TagBoxWidget(FieldWidget): self.driver.lib.get_entry(x[1]).remove_tag(self.driver.lib, tag_id,field_index=index[0]) self.updated.emit() if tag_id == 0 or tag_id == 1: - self.driver.update_thumbs() + self.driver.update_badges() # def show_add_button(self, value:bool): # self.add_button.setHidden(not value) @@ -2954,16 +2954,27 @@ class ItemThumb(FlowWidget): if self.mode == ItemType.ENTRY: self.isArchived = value DEFAULT_META_TAG_FIELD = 8 - for x in self.panel.driver.selected: - e = self.lib.get_entry(x[1]) + temp = (ItemType.ENTRY,self.item_id) + if list(self.panel.driver.selected).count(temp) > 0: # Is the archived badge apart of the selection? + # Yes, then add archived tag to all selected. + for x in self.panel.driver.selected: + e = self.lib.get_entry(x[1]) + if value: + self.archived_badge.setHidden(False) + e.add_tag(self.panel.driver.lib, 0, field_id=DEFAULT_META_TAG_FIELD, field_index=-1) + else: + e.remove_tag(self.panel.driver.lib, 0) + else: + # No, then add archived tag to the entry this badge is on. + e = self.lib.get_entry(self.item_id) if value: - self.archived_badge.setHidden(False) + self.favorite_badge.setHidden(False) e.add_tag(self.panel.driver.lib, 0, field_id=DEFAULT_META_TAG_FIELD, field_index=-1) else: e.remove_tag(self.panel.driver.lib, 0) if self.panel.isOpen: self.panel.update_widgets() - self.panel.driver.update_thumbs() + self.panel.driver.update_badges() # def on_archived_uncheck(self): @@ -2976,8 +2987,19 @@ class ItemThumb(FlowWidget): if self.mode == ItemType.ENTRY: self.isFavorite = value DEFAULT_META_TAG_FIELD = 8 - for x in self.panel.driver.selected: - e = self.lib.get_entry(x[1]) + temp = (ItemType.ENTRY,self.item_id) + if list(self.panel.driver.selected).count(temp) > 0: # Is the favorite badge apart of the selection? + # Yes, then add favorite tag to all selected. + for x in self.panel.driver.selected: + e = self.lib.get_entry(x[1]) + if value: + self.favorite_badge.setHidden(False) + e.add_tag(self.panel.driver.lib, 1, field_id=DEFAULT_META_TAG_FIELD, field_index=-1) + else: + e.remove_tag(self.panel.driver.lib, 1) + else: + # No, then add favorite tag to the entry this badge is on. + e = self.lib.get_entry(self.item_id) if value: self.favorite_badge.setHidden(False) e.add_tag(self.panel.driver.lib, 1, field_id=DEFAULT_META_TAG_FIELD, field_index=-1) @@ -2985,7 +3007,8 @@ class ItemThumb(FlowWidget): e.remove_tag(self.panel.driver.lib, 1) if self.panel.isOpen: self.panel.update_widgets() - self.panel.driver.update_thumbs() + self.panel.driver.update_badges() + # def on_favorite_uncheck(self): # if self.mode == SearchItemType.ENTRY: @@ -4357,6 +4380,10 @@ class QtDriver(QObject): # logging.info( # f'[MAIN] Elements thumbs updated in {(end_time - start_time):.3f} seconds') + def update_badges(self): + for i, item_thumb in enumerate(self.item_thumbs, start=0): + item_thumb.update_badges() + def expand_collation(self, collation_entries: list[tuple[int, int]]): self.nav_forward([(ItemType.ENTRY, x[0]) for x in collation_entries]) From b7638046a34e48cfb0d46e6bc57e18f724b61291 Mon Sep 17 00:00:00 2001 From: Travis Abendshien Date: Thu, 25 Apr 2024 22:22:28 -0700 Subject: [PATCH 09/23] Addded TagStudio.ini to gitignore; Updated Qt resource comment --- .gitignore | 1 + tagstudio/src/qt/resources_rc.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index c5b97173..0a5d8271 100644 --- a/.gitignore +++ b/.gitignore @@ -249,5 +249,6 @@ compile_commands.json # TagStudio .TagStudio +TagStudio.ini # End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,python,qt diff --git a/tagstudio/src/qt/resources_rc.py b/tagstudio/src/qt/resources_rc.py index 0a3759ac..aca38cd1 100644 --- a/tagstudio/src/qt/resources_rc.py +++ b/tagstudio/src/qt/resources_rc.py @@ -1,6 +1,6 @@ # Resource object code (Python 3) # Created by: object code -# Created by: The Resource Compiler for Qt version 6.5.1 +# Created by: The Resource Compiler for Qt version 6.6.3 # WARNING! All changes made in this file will be lost! from PySide6 import QtCore From 00651e6242f97c1945b06674f622b73699c836a6 Mon Sep 17 00:00:00 2001 From: Travis Abendshien Date: Fri, 26 Apr 2024 01:45:03 -0700 Subject: [PATCH 10/23] Fixed Incorrect Fields Being Updated in Multi-Selection Fixes #55 Co-Authored-By: Andrew Arneson Co-Authored-By: Xarvex <60973030+xarvex@users.noreply.github.com> --- tagstudio/src/qt/ts_qt.py | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index 12b0d6ea..306fe857 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -2375,14 +2375,12 @@ class PreviewPanel(QWidget): # f'Are you sure you want to remove this \"{self.lib.get_field_attr(field, "name")}\" field?' # container.set_remove_callback(lambda: (self.lib.get_entry(item.id).fields.pop(index), self.update_widgets(item))) prompt=f'Are you sure you want to remove this \"{self.lib.get_field_attr(field, "name")}\" field?' - callback = lambda: (self.remove_field(item.fields[index]), self.update_widgets()) + callback = lambda: (self.remove_field(field), self.update_widgets()) container.set_remove_callback(lambda: self.remove_message_box( prompt=prompt, callback=callback)) container.set_copy_callback(None) container.set_edit_callback(None) - # logging.info(self.common_fields) - # logging.info(f'index:{index}') else: text = 'Mixed Data' title = f"{self.lib.get_field_attr(field, 'name')} (Wacky Tag Box)" @@ -2412,15 +2410,14 @@ class PreviewPanel(QWidget): container.set_inner_widget(inner_container) # if type(item) == Entry: if not mixed: - item = self.lib.get_entry(self.selected[0][1]) # TODO TODO TODO: TEMPORARY modal = PanelModal(EditTextLine(self.lib.get_field_attr(field, 'content')), title=title, window_title=f'Edit {self.lib.get_field_attr(field, "name")}', - save_callback=(lambda content: (self.update_field(item.fields[index], content), self.update_widgets())) + save_callback=(lambda content: (self.update_field(field, content), self.update_widgets())) ) container.set_edit_callback(modal.show) prompt=f'Are you sure you want to remove this \"{self.lib.get_field_attr(field, "name")}\" field?' - callback = lambda: (self.remove_field(item.fields[index]), self.update_widgets()) + callback = lambda: (self.remove_field(field), self.update_widgets()) container.set_remove_callback(lambda: self.remove_message_box( prompt=prompt, callback=callback)) @@ -2448,17 +2445,15 @@ class PreviewPanel(QWidget): container.set_inner_widget(inner_container) # if type(item) == Entry: if not mixed: - item = self.lib.get_entry(self.selected[0][1]) # TODO TODO TODO: TEMPORARY container.set_copy_callback(None) modal = PanelModal(EditTextBox(self.lib.get_field_attr(field, 'content')), title=title, window_title=f'Edit {self.lib.get_field_attr(field, "name")}', - save_callback=(lambda content: (self.update_field(item.fields[index], content), self.update_widgets())) + save_callback=(lambda content: (self.update_field(field, content), self.update_widgets())) ) container.set_edit_callback(modal.show) - # container.set_remove_callback(lambda: (self.lib.get_entry(item.id).fields.pop(index), self.update_widgets(item))) prompt=f'Are you sure you want to remove this \"{self.lib.get_field_attr(field, "name")}\" field?' - callback = lambda: (self.remove_field(item.fields[index]), self.update_widgets()) + callback = lambda: (self.remove_field(field), self.update_widgets()) container.set_remove_callback(lambda: self.remove_message_box( prompt=prompt, callback=callback)) @@ -2483,7 +2478,7 @@ class PreviewPanel(QWidget): # container.set_edit_callback(None) # container.set_remove_callback(lambda: (self.lib.get_entry(item.id).fields.pop(index), self.update_widgets(item))) prompt=f'Are you sure you want to remove this \"{self.lib.get_field_attr(field, "name")}\" field?' - callback = lambda: (self.remove_field(item.fields[index]), self.update_widgets()) + callback = lambda: (self.remove_field(field), self.update_widgets()) container.set_remove_callback(lambda: self.remove_message_box( prompt=prompt, callback=callback)) @@ -2511,7 +2506,7 @@ class PreviewPanel(QWidget): container.set_edit_callback(None) # container.set_remove_callback(lambda: (self.lib.get_entry(item.id).fields.pop(index), self.update_widgets(item))) prompt=f'Are you sure you want to remove this \"{self.lib.get_field_attr(field, "name")}\" field?' - callback = lambda: (self.remove_field(item.fields[index]), self.update_widgets()) + callback = lambda: (self.remove_field(field), self.update_widgets()) container.set_remove_callback(lambda: self.remove_message_box( prompt=prompt, callback=callback)) @@ -2536,7 +2531,7 @@ class PreviewPanel(QWidget): container.set_edit_callback(None) # container.set_remove_callback(lambda: (self.lib.get_entry(item.id).fields.pop(index), self.update_widgets(item))) prompt=f'Are you sure you want to remove this \"{self.lib.get_field_attr(field, "name")}\" field?' - callback = lambda: (self.remove_field(item.fields[index]), self.update_widgets()) + callback = lambda: (self.remove_field(field), self.update_widgets()) # callback = lambda: (self.lib.get_entry(item.id).fields.pop(index), self.update_widgets()) container.set_remove_callback(lambda: self.remove_message_box( prompt=prompt, From 1774a00d3441ec7794a058913039b957f9013339 Mon Sep 17 00:00:00 2001 From: Creepler13 Date: Fri, 26 Apr 2024 00:06:34 +0200 Subject: [PATCH 11/23] first prototype --- tagstudio/src/qt/ts_qt.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index 306fe857..8833c0c8 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -3749,6 +3749,10 @@ class QtDriver(QObject): create_collage_action = QAction('Create Collage', menu_bar) create_collage_action.triggered.connect(lambda: self.create_collage()) tools_menu.addAction(create_collage_action) + + folders_to_tags_action = QAction('Folders to Tags', menu_bar) + folders_to_tags_action.triggered.connect(lambda: self.folders_to_tags(self.lib)) + tools_menu.addAction(folders_to_tags_action) # Macros Menu ========================================================== self.autofill_action = QAction('Autofill', menu_bar) @@ -4580,6 +4584,39 @@ class QtDriver(QObject): ))) i = i+1 + def folders_to_tags(self,library:Library)->None: + + def find_tag(tag_list:list[str],last_tag:Tag) ->Tag: + tag_name = tag_list.pop(0) + logging.info(tag_name) + if(last_tag!=None): + tag_id = next((tag_id for tag_id in last_tag.subtag_ids if library.get_tag(tag_id).name == tag_name), None) + if(tag_id!=None): + tag = library.get_tag(tag_id) + else: + tag=None + else: + tag = next((tag for tag in library.tags if tag.name == tag_name), None) + logging.info(tag) + if tag == None: + tag_list.append(tag_name) + for tag_name in tag_list: + new_tag = Tag(-1, tag_name,"",[],([last_tag.id] if last_tag!=None else []),"black") + library.add_tag_to_library(new_tag) + last_tag = new_tag + return last_tag + if len(tag_list) == 0: + return tag + else: + return find_tag(tag_list,tag) + + + for ent in library.entries: + tag_list = ent.path.split("\\") + tag = find_tag(tag_list,None) + logging.info(tag) + ent.add_tag(library,tag.id,6) + def try_save_collage(self, increment_progress:bool): if increment_progress: self.completed += 1 From 749d7c8fc094de80cdade7046ad305a8a4e86158 Mon Sep 17 00:00:00 2001 From: Creepler13 Date: Fri, 26 Apr 2024 14:58:21 +0200 Subject: [PATCH 12/23] Finished folders to Tags tool --- tagstudio/src/qt/ts_qt.py | 75 +++++++++++++++++++++++---------------- 1 file changed, 44 insertions(+), 31 deletions(-) diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index 8833c0c8..f003fd2d 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -4561,7 +4561,7 @@ class QtDriver(QObject): if not data_only_mode: time.sleep(5) - + self.collage = Image.new('RGB', (img_size,img_size)) i = 0 self.completed = 0 @@ -4584,38 +4584,51 @@ class QtDriver(QObject): ))) i = i+1 - def folders_to_tags(self,library:Library)->None: - - def find_tag(tag_list:list[str],last_tag:Tag) ->Tag: - tag_name = tag_list.pop(0) - logging.info(tag_name) - if(last_tag!=None): - tag_id = next((tag_id for tag_id in last_tag.subtag_ids if library.get_tag(tag_id).name == tag_name), None) - if(tag_id!=None): - tag = library.get_tag(tag_id) - else: - tag=None - else: - tag = next((tag for tag in library.tags if tag.name == tag_name), None) - logging.info(tag) - if tag == None: - tag_list.append(tag_name) - for tag_name in tag_list: - new_tag = Tag(-1, tag_name,"",[],([last_tag.id] if last_tag!=None else []),"black") + def folders_to_tags(self,library:Library): + logging.info("Converting folders to Tags") + tree = dict(dirs={}) + + def add_tag_to_tree(list:list[Tag]): + branch = tree + for tag in list: + if tag.name not in branch["dirs"]: + branch["dirs"][tag.name] = dict(dirs={},tag=tag) + branch = branch["dirs"][tag.name] + + def add_folders_to_tree(list:list[str])->Tag: + branch = tree + for folder in list: + if folder not in branch["dirs"]: + new_tag = Tag(-1, folder,"",[],([branch["tag"].id] if "tag" in branch else []),"black") library.add_tag_to_library(new_tag) - last_tag = new_tag - return last_tag - if len(tag_list) == 0: - return tag - else: - return find_tag(tag_list,tag) + branch["dirs"][folder] = dict(dirs={},tag=new_tag) + branch = branch["dirs"][folder] + return branch["tag"] + + def reverse_tag(tag:Tag,list:list[Tag]): + if list != None: + list.append(tag) + else: + list = [tag] + + if len(tag.subtag_ids) == 0: + add_tag_to_tree(list) + else: + for subtag_id in tag.subtag_ids: + subtag = library.get_tag(subtag_id) + reverse_tag(subtag,list) + + for tag in library.tags: + reverse_tag(tag,None) - - for ent in library.entries: - tag_list = ent.path.split("\\") - tag = find_tag(tag_list,None) - logging.info(tag) - ent.add_tag(library,tag.id,6) + for entry in library.entries: + folders = entry.path.split("\\") + tag = add_folders_to_tree(folders) + if tag: + if not entry.has_tag(library,tag.id): + entry.add_tag(library,tag.id,6) + + logging.info("Done") def try_save_collage(self, increment_progress:bool): if increment_progress: From 898ce5fdc73e4b186228fc69beb7a2ca6b80438e Mon Sep 17 00:00:00 2001 From: Theasacraft <91694323+Thesacraft@users.noreply.github.com> Date: Fri, 26 Apr 2024 15:34:06 +0200 Subject: [PATCH 13/23] Update ts_qt.py to update badges if meta tag field is removed --- tagstudio/src/qt/ts_qt.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index 306fe857..78e2989d 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -2546,8 +2546,13 @@ class PreviewPanel(QWidget): entry = self.lib.get_entry(item_pair[1]) try: index = entry.fields.index(field) + updated_badges = False + if 8 in entry.fields[index].keys() and (1 in entry.fields[index][8] or 0 in entry.fields[index][8]): + updated_badges = True # TODO: Create a proper Library/Entry method to manage fields. entry.fields.pop(index) + if updated_badges: + self.driver.update_badges() except ValueError: logging.info(f'[PREVIEW PANEL][ERROR?] Tried to remove field from Entry ({entry.id}) that never had it') pass From 243d7862980d00c69883f2a32e5e99df6a1d0b3b Mon Sep 17 00:00:00 2001 From: Theasacraft <91694323+Thesacraft@users.noreply.github.com> Date: Fri, 26 Apr 2024 15:41:37 +0200 Subject: [PATCH 14/23] Update ts_qt.py to update badges if meta tag field is removed --- tagstudio/src/qt/ts_qt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index 78e2989d..6740767f 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -2546,7 +2546,7 @@ class PreviewPanel(QWidget): entry = self.lib.get_entry(item_pair[1]) try: index = entry.fields.index(field) - updated_badges = False + updated_badges = False if 8 in entry.fields[index].keys() and (1 in entry.fields[index][8] or 0 in entry.fields[index][8]): updated_badges = True # TODO: Create a proper Library/Entry method to manage fields. From 6b1035b0f61b97d71e97d7a85aaf4503b13b1587 Mon Sep 17 00:00:00 2001 From: DrRetro Date: Fri, 26 Apr 2024 10:07:59 -0400 Subject: [PATCH 15/23] A simple TagDatabasePanel has been created based off TagSearchPanel. To access, go to Edit > Tag Database --- tagstudio/src/qt/ts_qt.py | 130 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index 306fe857..612b7f20 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -1147,6 +1147,126 @@ class BuildTagPanel(PanelWidget): # self.search_field.setFocus() # self.parentWidget().hide() +class TagDatabasePanel(PanelWidget): + tag_chosen = Signal(int) + def __init__(self, library): + super().__init__() + self.lib: Library = library + # self.callback = callback + self.first_tag_id = -1 + self.tag_limit = 30 + # self.selected_tag: int = 0 + + self.setMinimumSize(300, 400) + self.root_layout = QVBoxLayout(self) + self.root_layout.setContentsMargins(6,0,6,0) + + self.search_field = QLineEdit() + self.search_field.setObjectName('searchField') + self.search_field.setMinimumSize(QSize(0, 32)) + self.search_field.setPlaceholderText('Search Tags') + self.search_field.textEdited.connect(lambda x=self.search_field.text(): self.update_tags(x)) + self.search_field.returnPressed.connect(lambda checked=False: self.on_return(self.search_field.text())) + + # self.content_container = QWidget() + # self.content_layout = QHBoxLayout(self.content_container) + + self.scroll_contents = QWidget() + self.scroll_layout = QVBoxLayout(self.scroll_contents) + self.scroll_layout.setContentsMargins(6,0,6,0) + self.scroll_layout.setAlignment(Qt.AlignmentFlag.AlignTop) + + self.scroll_area = QScrollArea() + # self.scroll_area.setStyleSheet('background: #000000;') + self.scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn) + # self.scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) + self.scroll_area.setWidgetResizable(True) + self.scroll_area.setFrameShadow(QFrame.Shadow.Plain) + self.scroll_area.setFrameShape(QFrame.Shape.NoFrame) + # sa.setMaximumWidth(self.preview_size[0]) + self.scroll_area.setWidget(self.scroll_contents) + + # self.add_button = QPushButton() + # self.root_layout.addWidget(self.add_button) + # self.add_button.setText('Add Tag') + # # self.done_button.clicked.connect(lambda checked=False, x=1101: (callback(x), self.hide())) + # self.add_button.clicked.connect(lambda checked=False, x=1101: callback(x)) + # # self.setLayout(self.root_layout) + + self.root_layout.addWidget(self.search_field) + self.root_layout.addWidget(self.scroll_area) + self.update_tags('') + + # def reset(self): + # self.search_field.setText('') + # self.update_tags('') + # self.search_field.setFocus() + + def on_return(self, text:str): + if text and self.first_tag_id >= 0: + # callback(self.first_tag_id) + self.search_field.setText('') + self.update_tags('') + else: + self.search_field.setFocus() + self.parentWidget().hide() + + def update_tags(self, query:str): + # for c in self.scroll_layout.children(): + # c.widget().deleteLater() + while self.scroll_layout.itemAt(0): + # logging.info(f"I'm deleting { self.scroll_layout.itemAt(0).widget()}") + self.scroll_layout.takeAt(0).widget().deleteLater() + + if query: + first_id_set = False + for tag_id in self.lib.search_tags(query, include_cluster=True)[:self.tag_limit-1]: + if not first_id_set: + self.first_tag_id = tag_id + first_id_set = True + c = QWidget() + l = QHBoxLayout(c) + l.setContentsMargins(0,0,0,0) + l.setSpacing(3) + tw = TagWidget(self.lib, self.lib.get_tag(tag_id), True, False) + + l.addWidget(tw) + self.scroll_layout.addWidget(c) + else: + first_id_set = False + for tag in self.lib.tags: + if not first_id_set: + self.first_tag_id = tag.id + first_id_set = True + c = QWidget() + l = QHBoxLayout(c) + l.setContentsMargins(0,0,0,0) + l.setSpacing(3) + tw = TagWidget(self.lib, tag, True, False) + tw.on_edit.connect(lambda checked=False, t=tag: (self.edit_tag(t.id))) + l.addWidget(tw) + self.scroll_layout.addWidget(c) + + self.search_field.setFocus() + + def edit_tag(self, tag_id:int): + btp = BuildTagPanel(self.lib, tag_id) + # btp.on_edit.connect(lambda x: self.edit_tag_callback(x)) + self.edit_modal = PanelModal(btp, + self.lib.get_tag(tag_id).display_name(self.lib), + 'Edit Tag', + done_callback=(self.update_tags('')), + has_save=True) + # self.edit_modal.widget.update_display_name.connect(lambda t: self.edit_modal.title_widget.setText(t)) + panel: BuildTagPanel = self.edit_modal.widget + self.edit_modal.saved.connect(lambda: self.lib.update_tag(btp.build_tag())) + # panel.tag_updated.connect(lambda tag: self.lib.update_tag(tag)) + self.edit_modal.show() + # def enterEvent(self, event: QEnterEvent) -> None: + # self.search_field.setFocus() + # return super().enterEvent(event) + # self.focusOutEvent + class FunctionIterator(QObject): """Iterates over a yielding function and emits progress as the 'value' signal.\n\nThread-Safe Guaranteeā„¢""" @@ -3735,6 +3855,12 @@ class QtDriver(QObject): new_tag_action.triggered.connect(lambda: self.add_tag_action_callback()) edit_menu.addAction(new_tag_action) + edit_menu.addSeparator() + + tag_database_action = QAction('Tag Database', menu_bar) + tag_database_action.triggered.connect(lambda: self.show_tag_database()) + edit_menu.addAction(tag_database_action) + # Tools Menu =========================================================== fix_unlinked_entries_action = QAction('Fix &Unlinked Entries', menu_bar) fue_modal = FixUnlinkedEntriesModal(self.lib, self) @@ -3901,6 +4027,10 @@ class QtDriver(QObject): # panel.tag_updated.connect(lambda tag: self.lib.update_tag(tag)) self.modal.show() + def show_tag_database(self): + self.modal = PanelModal(TagDatabasePanel(self.lib),'Tag Database', 'Tag Database', has_save=False) + self.modal.show() + def add_new_files_callback(self): """Runs when user initiates adding new files to the Library.""" # # if self.lib.files_not_in_library: From f23ff1669eece3d92f04157e9906ccb96bddc728 Mon Sep 17 00:00:00 2001 From: DrRetro Date: Fri, 26 Apr 2024 10:50:19 -0400 Subject: [PATCH 16/23] Fixed Issue when Searching Tags, Editing Option was not Connected to Searched TagWidets --- tagstudio/src/qt/ts_qt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index 612b7f20..7d788c46 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -1229,7 +1229,7 @@ class TagDatabasePanel(PanelWidget): l.setContentsMargins(0,0,0,0) l.setSpacing(3) tw = TagWidget(self.lib, self.lib.get_tag(tag_id), True, False) - + tw.on_edit.connect(lambda checked=False, t=tag: (self.edit_tag(t.id))) l.addWidget(tw) self.scroll_layout.addWidget(c) else: From 5feb3a6d203a1d5e5b572fe88df3e937541e0e6b Mon Sep 17 00:00:00 2001 From: DrRetro Date: Fri, 26 Apr 2024 10:51:49 -0400 Subject: [PATCH 17/23] Fixed Issue with Previous Commit --- tagstudio/src/qt/ts_qt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index 7d788c46..6bedc6f0 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -1229,7 +1229,7 @@ class TagDatabasePanel(PanelWidget): l.setContentsMargins(0,0,0,0) l.setSpacing(3) tw = TagWidget(self.lib, self.lib.get_tag(tag_id), True, False) - tw.on_edit.connect(lambda checked=False, t=tag: (self.edit_tag(t.id))) + tw.on_edit.connect(lambda checked=False, t=self.lib.get_tag(tag_id): (self.edit_tag(t.id))) l.addWidget(tw) self.scroll_layout.addWidget(c) else: From 85ae16781796ad8d38907fb10ddebb05bebaf912 Mon Sep 17 00:00:00 2001 From: DrRetro Date: Fri, 26 Apr 2024 11:01:09 -0400 Subject: [PATCH 18/23] Fixed Issue with Tags in Database not refreshing after Tag was Edited. --- tagstudio/src/qt/ts_qt.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index 6bedc6f0..114fe1ed 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -1255,13 +1255,18 @@ class TagDatabasePanel(PanelWidget): self.edit_modal = PanelModal(btp, self.lib.get_tag(tag_id).display_name(self.lib), 'Edit Tag', - done_callback=(self.update_tags('')), + done_callback=(self.update_tags(self.search_field.text())), has_save=True) # self.edit_modal.widget.update_display_name.connect(lambda t: self.edit_modal.title_widget.setText(t)) panel: BuildTagPanel = self.edit_modal.widget - self.edit_modal.saved.connect(lambda: self.lib.update_tag(btp.build_tag())) + self.edit_modal.saved.connect(lambda: self.edit_tag_callback(btp)) # panel.tag_updated.connect(lambda tag: self.lib.update_tag(tag)) self.edit_modal.show() + + def edit_tag_callback(self, btp:BuildTagPanel): + self.lib.update_tag(btp.build_tag()) + self.update_tags(self.search_field.text()) + # def enterEvent(self, event: QEnterEvent) -> None: # self.search_field.setFocus() # return super().enterEvent(event) From dde7eec946a9e7b1df407375694d7e9a496aa990 Mon Sep 17 00:00:00 2001 From: DrRetro Date: Fri, 26 Apr 2024 12:01:02 -0400 Subject: [PATCH 19/23] Keyboard Shortcuts Added to basic Functions --- tagstudio/src/qt/ts_qt.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index 114fe1ed..20e30940 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -3832,14 +3832,20 @@ class QtDriver(QObject): open_library_action = QAction('&Open/Create Library', menu_bar) open_library_action.triggered.connect(lambda: self.open_library_from_dialog()) + open_library_action.setShortcut(QtCore.QKeyCombination(QtCore.Qt.KeyboardModifier(QtCore.Qt.KeyboardModifier.ControlModifier), QtCore.Qt.Key.Key_O)) + open_library_action.setToolTip("Ctrl+O") file_menu.addAction(open_library_action) save_library_action = QAction('&Save Library', menu_bar) save_library_action.triggered.connect(lambda: self.callback_library_needed_check(self.save_library)) + save_library_action.setShortcut(QtCore.QKeyCombination(QtCore.Qt.KeyboardModifier(QtCore.Qt.KeyboardModifier.ControlModifier), QtCore.Qt.Key.Key_S)) + save_library_action.setStatusTip("Ctrl+S") file_menu.addAction(save_library_action) - save_library_backup_action = QAction('Save Library &Backup', menu_bar) + save_library_backup_action = QAction('&Save Library Backup', menu_bar) save_library_backup_action.triggered.connect(lambda: self.callback_library_needed_check(self.backup_library)) + save_library_backup_action.setShortcut(QtCore.QKeyCombination(QtCore.Qt.KeyboardModifier(QtCore.Qt.KeyboardModifier.ControlModifier | QtCore.Qt.KeyboardModifier.ShiftModifier), QtCore.Qt.Key.Key_S)) + save_library_backup_action.setStatusTip("Ctrl+Shift+S") file_menu.addAction(save_library_backup_action) file_menu.addSeparator() @@ -3848,6 +3854,8 @@ class QtDriver(QObject): # refresh_lib_action.triggered.connect(lambda: self.lib.refresh_dir()) add_new_files_action = QAction('&Refresh Directories', menu_bar) add_new_files_action.triggered.connect(lambda: self.callback_library_needed_check(self.add_new_files_callback)) + add_new_files_action.setShortcut(QtCore.QKeyCombination(QtCore.Qt.KeyboardModifier(QtCore.Qt.KeyboardModifier.ControlModifier), QtCore.Qt.Key.Key_R)) + add_new_files_action.setStatusTip("Ctrl+R") # file_menu.addAction(refresh_lib_action) file_menu.addAction(add_new_files_action) @@ -3856,8 +3864,10 @@ class QtDriver(QObject): file_menu.addAction(QAction('&Close Library', menu_bar)) # Edit Menu ============================================================ - new_tag_action = QAction('New Tag', menu_bar) + new_tag_action = QAction('New &Tag', menu_bar) new_tag_action.triggered.connect(lambda: self.add_tag_action_callback()) + new_tag_action.setShortcut(QtCore.QKeyCombination(QtCore.Qt.KeyboardModifier(QtCore.Qt.KeyboardModifier.ControlModifier), QtCore.Qt.Key.Key_T)) + new_tag_action.setToolTip('Ctrl+T') edit_menu.addAction(new_tag_action) edit_menu.addSeparator() @@ -3886,8 +3896,10 @@ class QtDriver(QObject): self.autofill_action.triggered.connect(lambda: (self.run_macros('autofill', [x[1] for x in self.selected if x[0] == ItemType.ENTRY]), self.preview_panel.update_widgets())) macros_menu.addAction(self.autofill_action) - self.sort_fields_action = QAction('Sort Fields', menu_bar) + self.sort_fields_action = QAction('&Sort Fields', menu_bar) self.sort_fields_action.triggered.connect(lambda: (self.run_macros('sort-fields', [x[1] for x in self.selected if x[0] == ItemType.ENTRY]), self.preview_panel.update_widgets())) + self.sort_fields_action.setShortcut(QtCore.QKeyCombination(QtCore.Qt.KeyboardModifier(QtCore.Qt.KeyboardModifier.AltModifier), QtCore.Qt.Key.Key_S)) + self.sort_fields_action.setToolTip('Alt+S') macros_menu.addAction(self.sort_fields_action) self.set_macro_menu_viability() @@ -4612,6 +4624,7 @@ class QtDriver(QObject): self.preview_panel.update_widgets() self.filter_items() + def create_collage(self) -> None: """Generates and saves an image collage based on Library Entries.""" From 1f7a5d3cbb00ceb33aa7edcf87dee72c2c19ac1f Mon Sep 17 00:00:00 2001 From: Xarvex Date: Fri, 26 Apr 2024 14:46:13 -0500 Subject: [PATCH 20/23] Windows: fix files w/ spaces opening cmd rather than associated program --- 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 306fe857..bda97854 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -68,7 +68,8 @@ QSettings.setPath(QSettings.IniFormat, QSettings.UserScope, os.getcwd()) def open_file(path: str): try: if sys.platform == "win32": - subprocess.Popen(["start", path], shell=True, close_fds=True, creationflags=subprocess.DETACHED_PROCESS) + # Windows needs special attention to handle spaces in the file + subprocess.Popen(["start", f'"{path.replace('"', '\"')}"'], shell=True, close_fds=True, creationflags=subprocess.DETACHED_PROCESS) else: if sys.platform == "darwin": command_name = "open" From 66fec731368895f41c640e22d56e58fef559e747 Mon Sep 17 00:00:00 2001 From: Xarvex Date: Fri, 26 Apr 2024 15:05:07 -0500 Subject: [PATCH 21/23] Correct usage of start command --- 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 bda97854..fbd05432 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -69,7 +69,8 @@ def open_file(path: str): try: if sys.platform == "win32": # Windows needs special attention to handle spaces in the file - subprocess.Popen(["start", f'"{path.replace('"', '\"')}"'], shell=True, close_fds=True, creationflags=subprocess.DETACHED_PROCESS) + # first parameter is for title, NOT filepath + subprocess.Popen(["start", "", os.path.normpath(path)], shell=True, close_fds=True, creationflags=subprocess.DETACHED_PROCESS) else: if sys.platform == "darwin": command_name = "open" From 0541c9fb016feef55473ecbd182ab3ded6e9fd7c Mon Sep 17 00:00:00 2001 From: Creepler13 Date: Fri, 26 Apr 2024 00:06:34 +0200 Subject: [PATCH 22/23] first prototype --- tagstudio/src/qt/ts_qt.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index 7e397f90..83d8ec0b 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -3897,6 +3897,10 @@ class QtDriver(QObject): create_collage_action = QAction('Create Collage', menu_bar) create_collage_action.triggered.connect(lambda: self.create_collage()) tools_menu.addAction(create_collage_action) + + folders_to_tags_action = QAction('Folders to Tags', menu_bar) + folders_to_tags_action.triggered.connect(lambda: self.folders_to_tags(self.lib)) + tools_menu.addAction(folders_to_tags_action) # Macros Menu ========================================================== self.autofill_action = QAction('Autofill', menu_bar) @@ -4735,6 +4739,39 @@ class QtDriver(QObject): ))) i = i+1 + def folders_to_tags(self,library:Library)->None: + + def find_tag(tag_list:list[str],last_tag:Tag) ->Tag: + tag_name = tag_list.pop(0) + logging.info(tag_name) + if(last_tag!=None): + tag_id = next((tag_id for tag_id in last_tag.subtag_ids if library.get_tag(tag_id).name == tag_name), None) + if(tag_id!=None): + tag = library.get_tag(tag_id) + else: + tag=None + else: + tag = next((tag for tag in library.tags if tag.name == tag_name), None) + logging.info(tag) + if tag == None: + tag_list.append(tag_name) + for tag_name in tag_list: + new_tag = Tag(-1, tag_name,"",[],([last_tag.id] if last_tag!=None else []),"black") + library.add_tag_to_library(new_tag) + last_tag = new_tag + return last_tag + if len(tag_list) == 0: + return tag + else: + return find_tag(tag_list,tag) + + + for ent in library.entries: + tag_list = ent.path.split("\\") + tag = find_tag(tag_list,None) + logging.info(tag) + ent.add_tag(library,tag.id,6) + def try_save_collage(self, increment_progress:bool): if increment_progress: self.completed += 1 From 3cd6fa136f3856e6e9f38c8690f37016177b1abc Mon Sep 17 00:00:00 2001 From: Creepler13 Date: Fri, 26 Apr 2024 14:58:21 +0200 Subject: [PATCH 23/23] Finished folders to Tags tool --- tagstudio/src/qt/ts_qt.py | 75 +++++++++++++++++++++++---------------- 1 file changed, 44 insertions(+), 31 deletions(-) diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index 83d8ec0b..42ef953b 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -4716,7 +4716,7 @@ class QtDriver(QObject): if not data_only_mode: time.sleep(5) - + self.collage = Image.new('RGB', (img_size,img_size)) i = 0 self.completed = 0 @@ -4739,38 +4739,51 @@ class QtDriver(QObject): ))) i = i+1 - def folders_to_tags(self,library:Library)->None: - - def find_tag(tag_list:list[str],last_tag:Tag) ->Tag: - tag_name = tag_list.pop(0) - logging.info(tag_name) - if(last_tag!=None): - tag_id = next((tag_id for tag_id in last_tag.subtag_ids if library.get_tag(tag_id).name == tag_name), None) - if(tag_id!=None): - tag = library.get_tag(tag_id) - else: - tag=None - else: - tag = next((tag for tag in library.tags if tag.name == tag_name), None) - logging.info(tag) - if tag == None: - tag_list.append(tag_name) - for tag_name in tag_list: - new_tag = Tag(-1, tag_name,"",[],([last_tag.id] if last_tag!=None else []),"black") + def folders_to_tags(self,library:Library): + logging.info("Converting folders to Tags") + tree = dict(dirs={}) + + def add_tag_to_tree(list:list[Tag]): + branch = tree + for tag in list: + if tag.name not in branch["dirs"]: + branch["dirs"][tag.name] = dict(dirs={},tag=tag) + branch = branch["dirs"][tag.name] + + def add_folders_to_tree(list:list[str])->Tag: + branch = tree + for folder in list: + if folder not in branch["dirs"]: + new_tag = Tag(-1, folder,"",[],([branch["tag"].id] if "tag" in branch else []),"black") library.add_tag_to_library(new_tag) - last_tag = new_tag - return last_tag - if len(tag_list) == 0: - return tag - else: - return find_tag(tag_list,tag) + branch["dirs"][folder] = dict(dirs={},tag=new_tag) + branch = branch["dirs"][folder] + return branch["tag"] + + def reverse_tag(tag:Tag,list:list[Tag]): + if list != None: + list.append(tag) + else: + list = [tag] + + if len(tag.subtag_ids) == 0: + add_tag_to_tree(list) + else: + for subtag_id in tag.subtag_ids: + subtag = library.get_tag(subtag_id) + reverse_tag(subtag,list) + + for tag in library.tags: + reverse_tag(tag,None) - - for ent in library.entries: - tag_list = ent.path.split("\\") - tag = find_tag(tag_list,None) - logging.info(tag) - ent.add_tag(library,tag.id,6) + for entry in library.entries: + folders = entry.path.split("\\") + tag = add_folders_to_tree(folders) + if tag: + if not entry.has_tag(library,tag.id): + entry.add_tag(library,tag.id,6) + + logging.info("Done") def try_save_collage(self, increment_progress:bool): if increment_progress: