From 650db1d93c42330e329acfea101af64349aaefff Mon Sep 17 00:00:00 2001 From: amish0 Date: Sat, 17 Aug 2024 20:14:48 +0530 Subject: [PATCH 1/2] There was an issues, When last label was being deleted, object list was not clearing: now its removed, by changing the condition in file list_widget.py in Class:CustomObjectListWidget in Methods: set_label_list() --- labelvim/labelvim/widgets/list_widgets.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/labelvim/labelvim/widgets/list_widgets.py b/labelvim/labelvim/widgets/list_widgets.py index c93e13d..992a01a 100644 --- a/labelvim/labelvim/widgets/list_widgets.py +++ b/labelvim/labelvim/widgets/list_widgets.py @@ -493,15 +493,15 @@ def set_label_list(self, category_id: list = [], object_id: list = []): label_list (list, optional): A list of label names to display. Defaults to an empty list. """ # Check if the label list is a non-empty list - if isinstance(category_id, list) and len(category_id) > 0 and isinstance(object_id, list) and len(object_id) > 0: + # if isinstance(category_id, list) and len(category_id) > 0 and isinstance(object_id, list) and len(object_id) > 0: # Clear the model before updating - self.clear_list() - # Update the label list and model - self.category_id = category_id - self.object_id = object_id - object_list = [f"{self.label_list[id]}" for id in category_id] - # update the model - self.model.setStringList(object_list) + self.clear_list() + # Update the label list and model + self.category_id = category_id + self.object_id = object_id + object_list = [f"{self.label_list[id]}" for id in category_id] + # update the model + self.model.setStringList(object_list) def clear_list(self): """ @@ -534,6 +534,7 @@ def remove_label(self, data: list): Args: label (str): The label to remove. """ + print(f"Data To Be remoed: {data}") category_id = [label["category_id"] for label in data] object_id = [label['id'] for label in data] self.object = {label['id']: label['category_id'] for label in data} From ddff12a9a2446b0f28bedf10be93ae721854b4d7 Mon Sep 17 00:00:00 2001 From: amish0 Date: Sat, 17 Aug 2024 23:36:51 +0530 Subject: [PATCH 2/2] added custom_delegets.py to check an duplicate list in listViewWidget, It is being called in CustomLabelWidget class, When user will click on any item in list for edit, it will check duplicate entry, if exits it will show a msg & revert back to origional state. - 2. in main.py two changes: a) enable edit, save, clear, delete btn when save directory loaded and it find an label for corresponding image_index. b) added one function refresh list to directly reflect the updated_modified_object list from by user from CustomLabelWidget Class. -3 in list_widget.py added custom_delegets as attribute in CustomLabelWidget class and refresh_list method in CustomObjectListWidget class --- labelvim/labelvim/widgets/custom_delegets.py | 75 ++++++++++++++++++ labelvim/labelvim/widgets/list_widgets.py | 82 +++++++++++++++++--- labelvim/main.py | 21 +++-- 3 files changed, 164 insertions(+), 14 deletions(-) create mode 100644 labelvim/labelvim/widgets/custom_delegets.py diff --git a/labelvim/labelvim/widgets/custom_delegets.py b/labelvim/labelvim/widgets/custom_delegets.py new file mode 100644 index 0000000..47d0ab9 --- /dev/null +++ b/labelvim/labelvim/widgets/custom_delegets.py @@ -0,0 +1,75 @@ +from PyQt5.QtWidgets import QStyledItemDelegate, QMessageBox, QLineEdit +from PyQt5.QtCore import Qt + +""" +CustomDelegate: Now checks for duplicates in the QStringList +Model and handles committing data appropriately. + +The setModelData method iterates through the string list to +check for duplicates and updates the model if the new text is valid. +CustomObjectListWidget: Uses QStringListModel for the list data. + +add_item: Appends new items to the list. +This setup integrates QStringListModel with the custom delegate, +allowing you to edit list items and ensure that no duplicates are added. + +""" + +class CustomDelegate(QStyledItemDelegate): + def __init__(self, parent=None): + super().__init__(parent) + self.parent_view = parent + + def createEditor(self, parent, option, index): + """ + Create and return a new editor widget for the item. + + Args: + parent (QWidget): The parent widget. + option (QStyleOptionViewItem): The option for the item. + index (QModelIndex): The index of the item. + + Returns: + QLineEdit: The editor widget. + """ + editor = QLineEdit(parent) + return editor + + def setEditorData(self, editor, index): + """ + Set the data for the editor widget. + + Args: + editor (QWidget): The editor widget. + index (QModelIndex): The index of the item. + """ + data = index.model().data(index, Qt.EditRole) + editor.setText(data) + + def setModelData(self, editor, model, index): + """ + Retrieve the data from the editor widget and set it in the model. + + Args: + editor (QWidget): The editor widget. + model (QAbstractItemModel): The model. + index (QModelIndex): The index of the item. + """ + new_text = editor.text() + # Check if the new text already exists in the model + for row in range(model.rowCount()): + item_text = model.data(model.index(row, 0), Qt.DisplayRole) + if item_text == new_text and model.index(row, 0) != index: + QMessageBox.warning(self.parent_view, 'Validation Error', 'The entered text already exists in the list.') + return + + model.setData(index, new_text, Qt.EditRole) + + def commitData(self, editor): + """ + Validate and commit the edited data. + + Args: + editor (QWidget): The editor widget. + """ + super().commitData(editor) diff --git a/labelvim/labelvim/widgets/list_widgets.py b/labelvim/labelvim/widgets/list_widgets.py index 992a01a..867f90a 100644 --- a/labelvim/labelvim/widgets/list_widgets.py +++ b/labelvim/labelvim/widgets/list_widgets.py @@ -1,10 +1,11 @@ from PyQt5 import QtWidgets -from PyQt5.QtCore import QStringListModel, Qt, QRect, pyqtSignal +from PyQt5.QtCore import QStringListModel, Qt, QRect, pyqtSignal, QModelIndex, pyqtSlot from PyQt5.QtGui import QStandardItemModel, QStandardItem from PyQt5.QtWidgets import QInputDialog, QMessageBox, QMenu, QAction # External imports from labelvim.utils.config import ANNOTATION_TYPE, OBJECT_LIST_ACTION +from labelvim.widgets.custom_delegets import CustomDelegate from enum import Enum class CustomListViewWidget(QtWidgets.QListView): @@ -62,7 +63,7 @@ def set_label_list(self, label_list=[]): Args: label_list (list, optional): A list of label names to display. Defaults to an empty list. """ - print(f"Label List in set label list: {label_list}") + # print(f"Label List in set label list: {label_list}") if isinstance(label_list, list) and len(label_list) > 0: self.model.clear() # Clear the model before updating self.label_list = label_list @@ -176,8 +177,8 @@ def mousePressEvent(self, event): event (QMouseEvent): The mouse event object. """ index = self.indexAt(event.pos()) - print(f"Index: {index.row()}") - print(f"model row count {self.model.rowCount()}") + # print(f"Index: {index.row()}") + # print(f"model row count {self.model.rowCount()}") if index.isValid(): item = self.model.itemFromIndex(index) print(f"Item: {item.text()}") @@ -230,6 +231,8 @@ def __init__(self, parent=None): # Set the geometry, model self.set_geometry() self.set_model() + self.delegate = CustomDelegate(self) + self.setItemDelegate(self.delegate) # Set the annotation type to None self.annotation_type = "Rectangle" # Connect the signals @@ -387,6 +390,7 @@ def edit_label(self, index): # Start editing the item at the specified index self.edit(index) + def on_data_changed(self, topLeft, bottomRight, roles): """ Slot that is triggered when the data in the model is changed. @@ -438,7 +442,7 @@ def __init__(self, parent=None): self.set_label_list(self.label_list) # Connect the signals self.object_list_slot_receiver.connect(self.__receiver_action) # Connect the signal to update the label list - # self.model.dataChanged.connect(self.on_data_changed) # Connect the dataChanged signal + # self.model.dataChanged.connect(self.handle_data_changed) # Connect the dataChanged signal def set_geometry(self): """ @@ -455,7 +459,7 @@ def set_model(self): self.setModel(self.model) self.setUpdatesEnabled(True) # # Set the edit triggers to NoEditTriggers - # self.setEditTriggers(QtWidgets.QListView.NoEditTriggers) + self.setEditTriggers(QtWidgets.QListView.NoEditTriggers) def __receiver_action(self, data: any, action: Enum): data = data[0] @@ -556,6 +560,14 @@ def edit_label(self, object_id, category_id): object_list = [f"{self.label_list[id]}" for id in self.category_id] self.model.setStringList(object_list) + def refresh_list(self, label_list: list): + """ + Refresh the list view + """ + self.label_list = label_list + object_list = [f"{self.label_list[id]}" for id in self.category_id] + self.model.setStringList(object_list) + def on_item_clicked(self, index): """ Emits the signal with the currently selected item's text. @@ -564,8 +576,8 @@ def on_item_clicked(self, index): index (QModelIndex): The index of the current item. """ # Emit the signal with the selected item's text - print(f"Index: {index.row()}") - print(f"Item: {self.model.data(index, Qt.DisplayRole)}") + # print(f"Index: {index.row()}") + # print(f"Item: {self.model.data(index, Qt.DisplayRole)}") id = self.object_id[index.row()] self.object_selection_notification_slot.emit(id) @@ -581,4 +593,56 @@ def mousePressEvent(self, event) -> None: print("Clearing selection") self.object_selection_notification_slot.emit(-1) # Call the parent class's mousePressEvent to handle normal clicks - super().mousePressEvent(event) \ No newline at end of file + super().mousePressEvent(event) + + # def contextMenuEvent(self, event): + # """ + # Handles the context menu event, providing an option to edit the selected item. + + # Args: + # event (QContextMenuEvent): The context menu event object. + # """ + # # Get the index at the mouse position + # index = self.indexAt(event.pos()) + # # Check if the index is valid + # if index.isValid(): + # # Create a context menu + # menu = QMenu(self) + # # Create an action to edit the item + # edit_action = QAction('Edit', self) + # # Connect the action to the edit_label method + # edit_action.triggered.connect(lambda: self.edit_label_at_index(index)) + # # Add the action to the menu + # menu.addAction(edit_action) + # # Show the context menu at the mouse position + # menu.exec_(event.globalPos()) + + # def edit_label_at_index(self, index): + # """ + # Triggers inline editing for the selected item. + + # Args: + # index (QModelIndex): The index of the item to be edited. + # """ + # # Start editing the item at the specified index + # self.edit(index) + + # @pyqtSlot(QModelIndex, QModelIndex) + # def handle_data_changed(self, top_left: QModelIndex, bottom_right: QModelIndex): + # """ + # Slot to handle data changes in the list view. + + # Args: + # top_left (QModelIndex): The top-left index of the changed data. + # bottom_right (QModelIndex): The bottom-right index of the changed data. + # """ + # row = top_left.row() + # # get index of the item and old data + + + + + + # new_data = self.model.data(top_left, Qt.EditRole) + # print(f"Item at row {row} was changed to '{new_data}'") + # # Optionally, you can update your data model or UI here \ No newline at end of file diff --git a/labelvim/main.py b/labelvim/main.py index d6355ca..1709c00 100644 --- a/labelvim/main.py +++ b/labelvim/main.py @@ -191,7 +191,10 @@ def __save_directory(self): # Emit the annotation data to the display self.Display.annotation_data_slot_receiver.emit(self.annotaion_data['annotations']) - + self.SaveBtn.setEnabled(True) + self.DeleteAnnotationBtn.setEnabled(True) + self.EditObjectBtn.setEnabled(True) + self.ClearAnnotationBtn.setEnabled(True) def __delete_file(self): """ Deletes the currently selected file from the list. Removes the file from @@ -353,10 +356,16 @@ def __create_object(self): # Enable the save button self.SaveBtn.setEnabled(True) + self.DeleteAnnotationBtn.setEnabled(True) + self.EditObjectBtn.setEnabled(True) self.ClearAnnotationBtn.setEnabled(True) else: # Display a message dialog if save directory is not selected self.msg_dialog("Save Directory Not Selected", "Please select the save directory first.") + self.SaveBtn.setEnabled(False) + self.DeleteAnnotationBtn.setEnabled(False) + self.EditObjectBtn.setEnabled(False) + self.ClearAnnotationBtn.setEnabled(False) def __edit_object(self): print("Edit Object") @@ -439,16 +448,18 @@ def msg_dialog(self, title, msg): ## Signal and Slot def update_label_list_to_Display(self, label_list): - print(f"update_label_list: {label_list}") + # print(f"update_label_list: {label_list}") self.label_list_manager.update(label_list) self.Display.update_label_list_slot_receiver.emit(label_list) - self.ObjectLabelListWidget.label_list = label_list # Update the label list in the object list widget + self.ObjectLabelListWidget.refresh_list(label_list) # Update the label list in the object list widget + # self.ObjectLabelListWidget.label_list = label_list def update_label_list_to_Label_Widget(self, label_list): - print(f"update_label_list: {label_list}") + # print(f"update_label_list: {label_list}") self.label_list_manager.update(label_list) self.LabelWidget.update_label_list_slot_receiver.emit(label_list) - self.ObjectLabelListWidget.label_list = label_list # Update the label list in the object list widget + self.ObjectLabelListWidget.refresh_list(label_list) # Update the label list in the object list widget + # self.ObjectLabelListWidget.label_list = label_list # Update the label list in the object list widget def update_data_to_ObjectListWidget(self, data, action): print(f"update_data_to_ObjectListWidget: {data}")