From 8a30dc2cb9e571229c4071d9c5d95125e232c50c Mon Sep 17 00:00:00 2001 From: Peery Date: Wed, 13 Apr 2022 09:06:53 +0200 Subject: [PATCH] Changed Source Link editing; Added Collection UI Replaced the link LineEdit with a tool button and a dialog to make room for another label that displays selected collections. --- ArtNet/artnet_manager.py | 11 +- ArtNet/gui/browse_window.py | 105 +++++++++++++++++ .../category_modify_dialog/__init__.py | 0 .../gui/dialogs/image_info_dialog/__init__.py | 0 .../dialogs/image_info_dialog/image_dialog.py | 25 ++++ .../gui/dialogs/link_input_dialog/__init__.py | 0 .../dialogs/link_input_dialog/link_dialog.py | 23 ++++ .../gui/dialogs/tag_import_dialog/__init__.py | 0 ArtNet/gui/importer_window.py | 107 ++++++++++-------- 9 files changed, 221 insertions(+), 50 deletions(-) create mode 100644 ArtNet/gui/dialogs/category_modify_dialog/__init__.py create mode 100644 ArtNet/gui/dialogs/image_info_dialog/__init__.py create mode 100644 ArtNet/gui/dialogs/image_info_dialog/image_dialog.py create mode 100644 ArtNet/gui/dialogs/link_input_dialog/__init__.py create mode 100644 ArtNet/gui/dialogs/link_input_dialog/link_dialog.py create mode 100644 ArtNet/gui/dialogs/tag_import_dialog/__init__.py diff --git a/ArtNet/artnet_manager.py b/ArtNet/artnet_manager.py index 9b2831d..d76bb5e 100644 --- a/ArtNet/artnet_manager.py +++ b/ArtNet/artnet_manager.py @@ -75,6 +75,8 @@ class ArtNetManager: self.import_window = ImporterWindow(self) self.browse_window = BrowseWindow(self) + self.curr_active_window = self.import_window + if len(self.config.data["file_root"]) == 0 or not os.path.isdir( self.config.data["file_root"]): # no file_root given by config or invalid logging.info("Querying for new file root due to lack of valid one ...") @@ -92,10 +94,9 @@ class ArtNetManager: if len(self.all_images) == 0: raise FileNotFoundError("No files or folders were in artnet root!") self.curr_image_index = 0 - self.curr_active_window = self.import_window self.refresh_shown_image() - self.import_window.show() + self.curr_active_window.show() status = self.__app.exec_() logging.info(f"Shutting client down with status: {status}") @@ -106,7 +107,8 @@ class ArtNetManager: if displayed_image_index is not None: self.curr_image_index = displayed_image_index self.refresh_shown_image() - self.curr_active_window.hide() + if self.curr_active_window is not None: + self.curr_active_window.hide() self.browse_window.show() self.curr_active_window = self.browse_window @@ -116,7 +118,8 @@ class ArtNetManager: if displayed_image_index is not None: self.curr_image_index = displayed_image_index self.refresh_shown_image() - self.curr_active_window.hide() + if self.curr_active_window is not None: + self.curr_active_window.hide() self.import_window.show() self.curr_active_window = self.browse_window diff --git a/ArtNet/gui/browse_window.py b/ArtNet/gui/browse_window.py index f3cbc6b..dfdc39f 100644 --- a/ArtNet/gui/browse_window.py +++ b/ArtNet/gui/browse_window.py @@ -1,12 +1,17 @@ import logging +from PyQt5.QtCore import Qt + from ArtNet.gui.windows.browser.picture_browser import Ui_MainWindow from ArtNet.gui.windows.artnet_mainwindow import ArtnetMainWindow +from ArtNet.gui.dialogs.image_info_dialog.image_dialog import ArtInfoDialog + class BrowseWindow(ArtnetMainWindow): UNKNOWN_TITLE = "-Title Unknown-" + UNKNOWN_PRESENCE = "(Not in Database)" def __init__(self, main): super().__init__(main) @@ -14,14 +19,114 @@ class BrowseWindow(ArtnetMainWindow): self.ui = Ui_MainWindow() self.ui.setupUi(self) + self.__showing_search_param_frame: bool = True + self.__showing_detail_frame: bool = True + + self.curr_art_id: int = None + self.curr_image_title: str = None + self.curr_link: str = None + self.curr_art_path: str = None + self.curr_file_name: str = None + self.curr_presences: list = list() + self.curr_tags: list = list() + self.curr_imply_tags: list = list() + self.curr_tag_aliases: list = list() + + # callbacks + # action self.ui.actionArtNet_Importer.triggered.connect(self.on_importer_clicked) + # buttons + self.ui.toggle_search_frame_button.clicked.connect(self.on_toggle_search_frame_clicked) + self.ui.toggle_detail_frame_button.clicked.connect(self.on_toggle_detail_frame_clicked) + self.ui.image_info_button.clicked.connect(self.on_image_info_clicked) def display_image(self, image_title: str, image_authors: list, full_path: str, relative_path: str, art_ID: int, link: str, file_name: str, description: str): """ Display the described image in the GraphicsView """ + logging.debug(f"Asked browser to display image ID:\"{art_ID}\", title:\"{image_title}\", path:\"{full_path}\"") + self.set_presences(image_authors) + + def set_tag_list(self, tags: list, set_checked: bool = True, no_implication: bool = False): + pass # TODO to implement + + def set_presences(self, presences: list): + """ + Set the presences for this window + """ + if len(presences) > 1: + for name, domain in presences: + if domain == BrowseWindow.UNKNOWN_PRESENCE: + presences.remove((name, domain)) + elif len(presences) == 0: + presences = [(self.curr_art_path.split("/")[0], BrowseWindow.UNKNOWN_PRESENCE)] + self.curr_presences = presences + + self.data_changed = True + + def convert_presences_to_text(self, presences: list) -> str: + """ + Prepares the presence variable to be printed as a label. + """ + s = "" + for name, domain in presences: + full_data = self.get_presence(name, domain) + if full_data is None: + link = "" + else: + name, domain, _, link = full_data[0] + text = name + ":" + domain + if link is None or len(link) == 0: # no link, then just do plain text + hyperlink = text + else: + hyperlink = "{1}".format(link, text) + + s += hyperlink + s += "|" + + s = s[:-1] + return s + + # callback methods def on_importer_clicked(self): logging.debug("Clicked on open ArtNet importer!") self.main.switch_to_importer() + + def on_toggle_search_frame_clicked(self): + logging.debug("Clicked on button to toggle the search frame!") + + if self.__showing_search_param_frame: + logging.debug("Hiding search param frame") + self.ui.search_param_frame.hide() + self.__showing_search_param_frame = False + self.ui.toggle_search_frame_button.setArrowType(Qt.RightArrow) + else: + logging.debug("Showing search param frame") + self.ui.search_param_frame.show() + self.__showing_search_param_frame = True + self.ui.toggle_search_frame_button.setArrowType(Qt.LeftArrow) + + def on_toggle_detail_frame_clicked(self): + logging.debug("Clicked on button to toggle the detail frame!") + + if self.__showing_detail_frame: + logging.debug("Hiding detail frame") + self.ui.bottom_frame.hide() + self.__showing_detail_frame = False + self.ui.toggle_detail_frame_button.setArrowType(Qt.DownArrow) + else: + logging.debug("Showing detail frame") + self.ui.bottom_frame.show() + self.__showing_detail_frame = True + self.ui.toggle_detail_frame_button.setArrowType(Qt.UpArrow) + + def on_image_info_clicked(self): + logging.debug("Clicked on button to show image info!") + if self.main.curr_image_index is not None: + dialog = ArtInfoDialog(self) + #dialog.set_image_info() # TODO see image_info_dialog/ + dialog.exec_() + else: + pass diff --git a/ArtNet/gui/dialogs/category_modify_dialog/__init__.py b/ArtNet/gui/dialogs/category_modify_dialog/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ArtNet/gui/dialogs/image_info_dialog/__init__.py b/ArtNet/gui/dialogs/image_info_dialog/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ArtNet/gui/dialogs/image_info_dialog/image_dialog.py b/ArtNet/gui/dialogs/image_info_dialog/image_dialog.py new file mode 100644 index 0000000..1369170 --- /dev/null +++ b/ArtNet/gui/dialogs/image_info_dialog/image_dialog.py @@ -0,0 +1,25 @@ + +from PyQt5 import QtWidgets +from PyQt5.QtGui import QStandardItemModel, QStandardItem + +from ArtNet.gui.dialogs.image_info_dialog.image_info_dialog import Ui_Dialog + + +class ArtInfoDialog(QtWidgets.QDialog): + """ + Dialog popup to display more detailed information about a given Art Entry + """ + + def __init__(self, parent=None): + super().__init__(parent) + self.ui = Ui_Dialog() + self.ui.setupUi(self) + + def set_image_info(self, image_title: str, presences: str, path: str, artists: str, collections: str, source: str): + """ + Set the image info of this dialog. + """ + self.ui.table_view + + self.ui.table_view.setModel() + diff --git a/ArtNet/gui/dialogs/link_input_dialog/__init__.py b/ArtNet/gui/dialogs/link_input_dialog/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ArtNet/gui/dialogs/link_input_dialog/link_dialog.py b/ArtNet/gui/dialogs/link_input_dialog/link_dialog.py new file mode 100644 index 0000000..441db27 --- /dev/null +++ b/ArtNet/gui/dialogs/link_input_dialog/link_dialog.py @@ -0,0 +1,23 @@ +from PyQt5 import QtWidgets + +from ArtNet.gui.dialogs.link_input_dialog.link_input_dialog import Ui_Dialog + + +class LinkInputDialog(QtWidgets.QDialog): + + def __init__(self, parent=None, link: str = None): + super().__init__(parent) + self.ui = Ui_Dialog() + self.ui.setupUi(self) + + if link is not None: + self.ui.link_line.setText(link) + + def get_link_text(self) -> str: + return self.ui.link_line.text() + + def exec_(self) -> str: + if super(LinkInputDialog, self).exec_() == QtWidgets.QDialog.Rejected: + return None + return self.get_link_text() + diff --git a/ArtNet/gui/dialogs/tag_import_dialog/__init__.py b/ArtNet/gui/dialogs/tag_import_dialog/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ArtNet/gui/importer_window.py b/ArtNet/gui/importer_window.py index 2216d59..a0e35c9 100644 --- a/ArtNet/gui/importer_window.py +++ b/ArtNet/gui/importer_window.py @@ -16,14 +16,15 @@ from ArtNet.gui.dialogs.tag_select_dialog.tag_select_dialog import TagSelectDial from ArtNet.gui.dockers.presence.presence_dock import PresenceDocker from ArtNet.gui.dialogs.category_modify_dialog.category_mod_dialog import CategoryModDialog from ArtNet.gui.dialogs.tag_import_dialog.tag_imp_dialog import TagImportDialog +from ArtNet.gui.dialogs.link_input_dialog.link_dialog import LinkInputDialog from ArtNet.web.link_generator import LinkGenerator class ImporterWindow(ArtnetMainWindow): - UNKNOWN_TITLE = "-Title Unknown-" UNKNOWN_PRESENCE = "(Not in Database)" + UNKNOWN_LINK = "No Source Available" def __init__(self, main): super().__init__(main) @@ -72,7 +73,9 @@ class ImporterWindow(ArtnetMainWindow): self.ui.import_button.clicked.connect(self.on_import_tags_clicked) self.ui.prev_unknown_image_button.clicked.connect(self.on_prev_unknown_image_clicked) self.ui.next_unknown_image_button.clicked.connect(self.on_next_unknown_image_clicked) + self.ui.collections_button.clicked.connect(self.on_collection_button_clicked) self.ui.delete_button.clicked.connect(self.on_delete_image_clicked) + self.ui.link_button.clicked.connect(self.on_source_link_button_clicked) self.ui.link_label.linkActivated.connect(self.on_link_label_activated) self.ui.image_author_label.linkActivated.connect(self.on_image_author_label_activated) @@ -81,12 +84,10 @@ class ImporterWindow(ArtnetMainWindow): self.ui.tag_search_bar.textChanged.connect(self.on_tag_search_change) self.ui.image_title_line.textChanged.connect(self.on_image_title_change) - self.ui.link_line.textChanged.connect(self.on_link_line_change) self.ui.description_edit.textChanged.connect(self.on_description_change) - self.ui.link_label.setText("No Source Available") + self.ui.link_label.setText(ImporterWindow.UNKNOWN_LINK) self.ui.image_file_label.setTextInteractionFlags(Qt.TextSelectableByMouse) - self.set_image_title_link() self.ui.description_edit.setReadOnly(False) @@ -155,9 +156,9 @@ class ImporterWindow(ArtnetMainWindow): return False self.main.db_connection.save_image(ID=image_data["ID"], title=image_data["title"], - authors=image_data["authors"], path=image_data["path"], - tags=image_data["tags"], link=image_data["link"], - md5_hash=image_data["md5_hash"], desc=image_data["description"]) + authors=image_data["authors"], path=image_data["path"], + tags=image_data["tags"], link=image_data["link"], + md5_hash=image_data["md5_hash"], desc=image_data["description"]) self.set_temporary_status_message("Saved {0} ({1}) to ArtNet DB!" .format(image_data["title"], image_data["ID"]), 5000) @@ -320,14 +321,14 @@ class ImporterWindow(ArtnetMainWindow): return self.main.db_connection.search_fuzzy_categories(search) - def get_image_link_from_line(self) -> str: + def get_image_link_from_label(self) -> str: """ - Gets the image link from the QLineEdit if it is a valid link. + Gets the image link from the label if it is a valid link. Otherwise an empty string :return: """ - return self.ui.link_line.text() + return self.ui.link_label.text() def set_presence_label_text(self, presences: list): """ @@ -335,7 +336,6 @@ class ImporterWindow(ArtnetMainWindow): :param presences: :return: """ - links = [] s = "" for name, domain in presences: full_data = self.get_presence(name, domain) @@ -355,24 +355,24 @@ class ImporterWindow(ArtnetMainWindow): s = s[:-1] self.ui.image_author_label.setText(s) - def set_image_title_link(self) -> str: + def set_image_title_link(self, link: str) -> str: """ Sets the Image title to a link if there is link data given for this image. - :return: + :return: Returns the result that has been set to see if a successful link was constructed """ - self.ui.link_label.setText("No Source Available") - link = self.ui.link_line.text() + self.ui.link_label.setText(ImporterWindow.UNKNOWN_LINK) if validators.url(link): self.curr_link = link self.data_changed = True hyperlink = "{1}".format(link, "Source") self.ui.link_label.setText(hyperlink) + self.ui.link_label.setToolTip(link) return link elif len(link) == 0: return "" else: - self.ui.link_label.setText("No Source Available") + self.ui.link_label.setText(ImporterWindow.UNKNOWN_LINK) self.set_temporary_status_message("Invalid link \"{0}\" detected!".format(link), 5000) return "" @@ -421,11 +421,11 @@ class ImporterWindow(ArtnetMainWindow): item = QStandardItem(tag) flags = Qt.ItemIsEnabled - if tag not in self.curr_imply_tags+self.curr_tag_aliases and tag not in self.curr_tags: + if tag not in self.curr_imply_tags + self.curr_tag_aliases and tag not in self.curr_tags: # new tag and not implied yet item.setData(Qt.Unchecked, Qt.CheckStateRole) flags |= Qt.ItemIsUserCheckable - if self.curr_tags is not None and tag in (self.curr_tags+self.curr_imply_tags+self.curr_tag_aliases): + if self.curr_tags is not None and tag in (self.curr_tags + self.curr_imply_tags + self.curr_tag_aliases): # already selected, implied or aliased tags item.setCheckState(Qt.Checked) item.setFlags(flags) @@ -472,7 +472,7 @@ class ImporterWindow(ArtnetMainWindow): self.set_implied_list(implied_tags) self.curr_tag_aliases = list() - for tag in tags+implied_tags: + for tag in tags + implied_tags: self.curr_tag_aliases += self.main.db_connection.get_tag_aliases_by_name(tag) self.data_changed = True @@ -520,7 +520,7 @@ class ImporterWindow(ArtnetMainWindow): file_ending = relative_path.split(".")[-1] if self.__showing_video: # remove old video from image layout - #self.ui.image_frame.layout().removeWidget(self.__video) + # self.ui.image_frame.layout().removeWidget(self.__video) self.__video.hide() self.ui.image_label.show() if self.__showing_text: # remove text are from image layout @@ -576,10 +576,9 @@ class ImporterWindow(ArtnetMainWindow): self.ui.image_label.setAlignment(Qt.AlignCenter) self.ui.image_title_line.setText(image_title) self.update_window_title() - self.ui.link_line.setText(link) self.ui.image_file_label.setText(file_name) self.ui.description_edit.setText(description) - self.set_image_title_link() + self.set_image_title_link(link) self.set_image_id_spinbox() self.data_changed = False # reset any triggered change detection @@ -591,7 +590,7 @@ class ImporterWindow(ArtnetMainWindow): """ image_title = self.ui.image_title_line.text() self.setWindowTitle(self.main_title + " - " + image_title - + f" ({round(self.main.known_image_amount/len(self.main.all_images), 5)}%)") + + f" ({round(self.main.known_image_amount / len(self.main.all_images), 5)}%)") def set_image_id_spinbox(self): """ @@ -599,7 +598,7 @@ class ImporterWindow(ArtnetMainWindow): :return: """ self.ui.imageNumberSpinBox.setMinimum(0) - self.ui.imageNumberSpinBox.setMaximum(len(self.main.all_images)-1) + self.ui.imageNumberSpinBox.setMaximum(len(self.main.all_images) - 1) self.ui.imageNumberSpinBox.setValue(self.main.curr_image_index) def __image_resize(self): @@ -613,7 +612,7 @@ class ImporterWindow(ArtnetMainWindow): size = QSize(min(rect.width(), rect.height()), min(rect.width(), rect.height())) if type(self.__pixmap) != QMovie: # using QVideoWidget? pass - #self.__player.setScaledSize(size) + # self.__player.setScaledSize(size) else: self.__pixmap.setScaledSize(size) return @@ -688,22 +687,22 @@ class ImporterWindow(ArtnetMainWindow): def on_save_clicked(self): logging.info("Clicked Save!") - self.set_image_title_link() self.save_changes() def on_import_tags_clicked(self): logging.info("Clicked Import!") dialog = TagImportDialog(self) - if len(self.get_image_link_from_line()) == 0 or self.get_image_link_from_line() == '(Unknown)': + if len(self.get_image_link_from_label()) == 0 \ + or self.get_image_link_from_label() == ImporterWindow.UNKNOWN_LINK: url = LinkGenerator.get_instance().construct_link(self.curr_file_name, LinkGenerator.get_instance() .predict_domain(self.curr_file_name)) - self.ui.link_line.setText(url) # Update no link to the predicted link + self.set_image_title_link(url) # Update no link to the predicted link else: - url = self.get_image_link_from_line() + url = self.get_image_link_from_label() r = self.main.scrape_tags(self.curr_file_name, url=url, - art_ID=self.curr_art_id) + art_ID=self.curr_art_id) if r is None: msg = QtWidgets.QMessageBox() @@ -713,8 +712,7 @@ class ImporterWindow(ArtnetMainWindow): msg.exec_() return - self.ui.link_line.setText(url) - self.set_image_title_link() + self.set_image_title_link(url) tags, artists = r i = 0 @@ -788,12 +786,12 @@ class ImporterWindow(ArtnetMainWindow): self.presence_docker = PresenceDocker(self) self.ui.presence_docker_layout.addWidget(self.presence_docker) self.presence_docker.set_selected_presences_list(self.get_current_presences()) - self.ui.presence_docker_button.setText(">") + self.ui.presence_docker_button.setArrowType(Qt.RightArrow) self.presence_docker_open = True else: logging.info("Closed presence docker!") self.presence_docker.setParent(None) - self.ui.presence_docker_button.setText("<") + self.ui.presence_docker_button.setArrowType(Qt.LeftArrow) self.presence_docker.destroy() self.presence_docker = None @@ -822,8 +820,8 @@ class ImporterWindow(ArtnetMainWindow): if len(db_data.keys()) == 0: return self.main.change_db_connection(host=db_data["host"], port=db_data["port"], - user=db_data["user"], password=db_data["password"], - database=db_data["database"]) + user=db_data["user"], password=db_data["password"], + database=db_data["database"]) def on_tag_creation_clicked(self): logging.info("Clicked Tag Creation!") @@ -839,8 +837,8 @@ class ImporterWindow(ArtnetMainWindow): .format(tag_data["name"])) return self.main.db_connection.create_tag(name=tag_data["name"], description=tag_data["description"], - aliases=tag_data["aliases"], implications=tag_data["implications"], - category=tag_data["category"]) + aliases=tag_data["aliases"], implications=tag_data["implications"], + category=tag_data["category"]) self.on_tag_search_change() def on_tag_deletion_clicked(self): @@ -889,8 +887,8 @@ class ImporterWindow(ArtnetMainWindow): "The Tag \"{0}\" you wanted to create already exists! Skipping...") else: self.main.db_connection.create_tag(name=tag_data["name"], description=tag_data["description"], - aliases=tag_data["aliases"], implications=tag_data["implications"], - category=tag_data["category"]) + aliases=tag_data["aliases"], implications=tag_data["implications"], + category=tag_data["category"]) self.on_tag_search_change() return tag_data @@ -924,9 +922,10 @@ class ImporterWindow(ArtnetMainWindow): tag_data["old_tag_name"] = None self.main.db_connection.edit_tag(tag_id=tag["ID"], - name=tag_data["name"], description=tag_data["description"], - aliases=tag_data["aliases"], implications=tag_data["implications"], - category_id=self.main.db_connection.get_category_by_name(tag_data["category"])[0]) + name=tag_data["name"], description=tag_data["description"], + aliases=tag_data["aliases"], implications=tag_data["implications"], + category_id=self.main.db_connection.get_category_by_name(tag_data["category"])[ + 0]) self.on_tag_search_change() def on_tag_search_item_changed(self, item: QStandardItem): @@ -976,9 +975,6 @@ class ImporterWindow(ArtnetMainWindow): self.main.db_connection.remove_category(data["name"]) - def on_link_line_change(self): - self.data_changed = True - def on_prev_unknown_image_clicked(self): unknown_image_index = self.main.get_prev_unknown_image() logging.info("Previous unknown image clicked!") @@ -1057,3 +1053,22 @@ class ImporterWindow(ArtnetMainWindow): def on_browser_clicked(self): logging.debug("Clicked on open ArtNet browser!") self.main.switch_to_browser() + + def on_source_link_button_clicked(self): + logging.debug("Clicked on link button!") + dialog = LinkInputDialog(self, self.curr_link) + link = dialog.exec_() + + if link is None: # dialog was cancelled + logging.debug("Cancelled link dialog.") + return + + logging.info(f"Setting source link to \"{link}\"") + self.set_image_title_link(link) + + def on_collection_button_clicked(self): + logging.debug("Clicked on collection button!") + QtWidgets.QMessageBox.information(self, "Not Implemented", "This feature has not been implemented yet!") + # TODO open dialog with collection selection and buttons for edit, creation and deletion + # TODO allow editing of the rank string + # TODO make collection inspectable (a list of all entries with their rank string would suffice)