diff --git a/ArtNet/__init__.py b/ArtNet/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/ArtNet/artnet_manager.py b/ArtNet/artnet_manager.py
new file mode 100644
index 0000000..c5b4bab
--- /dev/null
+++ b/ArtNet/artnet_manager.py
@@ -0,0 +1,376 @@
+import sys
+import os
+from hashlib import md5
+
+from PyQt5.QtWidgets import QApplication
+
+from ArtNet.db.db_adapter import DBAdapter
+from ArtNet.file.file_reader import FileReader
+from ArtNet.file.config_reader import ConfigReader
+from ArtNet.gui.window import Window
+from ArtNet.gui.dialogs.db_connection_dialog.db_dialog import DBDialog
+from ArtNet.web.link_generator import LinkGenerator
+
+
+class ArtNetManager:
+
+ def __init__(self, config_location: str = "."):
+ self.config = ConfigReader(config_location, "somePassword")
+ self.db_connection = None
+ self.__app = QApplication(sys.argv)
+
+ while self.db_connection is None:
+ try:
+ self.db_connection = self.create_db_connection(user=self.config.data["db"]["user"],
+ password=self.config.data["db"]["password"],
+ host=self.config.data["db"]["host"],
+ port=self.config.data["db"]["port"],
+ database=self.config.data["db"]["database"])
+ except ValueError as e: # db connection didn't work
+ print(e)
+ dialog = DBDialog()
+ prev_db_data = self.get_db_connection_details()
+ dialog.ui.user_line_edit.setText(prev_db_data["user"])
+ dialog.ui.password_line_edit.setText(prev_db_data["password"])
+ dialog.ui.host_line_edit.setText(prev_db_data["host"])
+ dialog.ui.database_line_edit.setText(prev_db_data["database"])
+ dialog.ui.port_line_edit.setText(str(prev_db_data["port"]))
+
+ db_data: dict = dialog.exec_()
+ if len(db_data.keys()) == 0:
+ return
+ self.change_db_connection(host=db_data["host"], port=db_data["port"],
+ user=db_data["user"], password=db_data["password"],
+ database=db_data["database"])
+ self.window = Window(self)
+
+ # TODO prompt for overwriting config when password invalid
+ # TODO sane default location
+ # TODO tag editor, don't kill dialog if fields are missing
+ # TODO display Tag Category when searching for tags
+
+ # TODO remove string limit restrictions on implications & similar
+
+ self.__file_root: str = None
+ self.window.on_artnet_root_change_clicked()
+ self.__file_reader = FileReader(self.__file_root)
+
+ self.curr_image_index: int = None
+
+ self.all_images: list = None
+ self.update_all_images_list()
+
+ self.window.set_temporary_status_message("Hello o7", 5000)
+
+ def run(self):
+ if len(self.all_images) > 0:
+ self.curr_image_index = 0
+ self.change_image()
+
+ self.window.show()
+
+ sys.exit(self.__app.exec_())
+
+ def update_all_images_list(self):
+ """
+ Updates the internal list of all images in the artnet root
+ :return:
+ """
+ images = []
+ artist_list = self.__file_reader.list_artists()
+ artist_list.sort()
+ for artist in artist_list:
+ for image in self.__file_reader.get_files(artist):
+ images.append(image)
+ self.all_images = images
+
+ self.known_image_amount = 0
+ for image in self.all_images:
+ if self.db_connection.get_art_by_path(image) is not None:
+ self.known_image_amount += 1
+
+ def scrape_tags(self, file_name: str, art_ID: int, url: str=None):
+ """
+ Scrape the tags from the given url and return which one's are new
+ :param file_name:
+ :param art_ID:
+ :param url:
+ :return:
+ """
+ if url is None:
+ return None
+
+ #tags = ArtNet.web.Scrap_Tags.scrap_tags(file_name, url, ArtNet.web.link_generator.predict_domain(file_name))
+ tags = LinkGenerator.get_instance().scrape_tags(url, LinkGenerator.get_instance().predict_domain(file_name))
+ if tags is None:
+ return None
+
+ already_applied_tags = self.db_connection.get_art_tags_by_ID(art_ID)
+ for i in range(len(already_applied_tags)):
+ already_applied_tags[i] = self.db_connection.get_tag_by_ID(already_applied_tags[i])[0][1].strip()
+
+ importable_tags = []
+ importable_artists = []
+ scraped_tags = []
+ for i in tags.values():
+ scraped_tags += i
+
+ for tag in scraped_tags:
+ result = self.db_connection.get_tag_by_name(tag)
+ if len(result) == 0: # tag does not exist yet
+ if tag in tags['artists']:
+ importable_artists.append(tag)
+ continue
+ importable_tags.append(tag)
+ continue
+
+ if tag in already_applied_tags: # tag is already applied
+ continue
+
+ result = self.db_connection.get_tag_impliers(tag)
+ if len(result) != 0: # tag is implied by some other tag
+ continue
+
+ result = self.db_connection.get_tag_aliases(tag)
+ if len(result) != 0: # tag is already tagged by an alias
+ continue
+
+ if tag in tags['artists']:
+ importable_artists.append(tag)
+ continue
+ importable_tags.append(tag) # tag must be known and not tagged yet
+
+ return importable_tags, importable_artists
+
+ def import_tags(self, art_ID: int, tags):
+ """
+ Add a list of given tags to the specified art_ID
+ :param art_ID:
+ :param tags:
+ :return:
+ """
+ for tag in tags:
+
+ if len(self.db_connection.get_tag_by_name(tag)) == 0: # tag doesn't exist yet
+ result = self.window.force_edit_tag_dialog(name=tag)
+
+ if result is None: # tag creation was aborted
+ continue
+
+ tag = result['name'] # overwrite with possibly new tag name
+
+ tag = self.db_connection.get_tag_by_name(tag)[0][0]
+ self.window.curr_tags.append(tag)
+ self.window.data_changed = True
+
+ def get_md5_of_image(self, path: str):
+ """
+ Calculate the md5 hash of the image given by the path.
+
+ :param path: assumed to be relative e.g. artist_A/image.jpg
+ :return:
+ """
+ md5_hash = md5()
+ try:
+ with open(self.get_root() + os.path.sep + path,
+ "rb") as file: # read file part by part because of potential memory limits
+ for chunk in iter(lambda: file.read(4096), b""):
+ md5_hash.update(chunk)
+
+ return md5_hash.hexdigest()
+ except FileNotFoundError:
+ self.update_all_images_list()
+ return None
+
+ def delete_image(self, path: str, delete_instead_of_move: bool = False, trash_bin_folder_path: str = "trash"):
+ """
+ Deletes the image from the root folder
+ :param path:
+ :param delete_instead_of_move:
+ :param trash_bin_folder_path:
+ :return:
+ """
+ full_art_path = self.get_root() + os.path.sep + path
+
+ if delete_instead_of_move:
+ print(f"Deleting the actual file {full_art_path} is disabled for now")
+ #os.remove(full_art_path)
+ #return
+
+ trash_dst = trash_bin_folder_path + os.path.sep + path
+ t_splits = trash_dst.split(os.path.sep)
+ t_path = ""
+ for i in range(len(t_splits)-1):
+ t_path += os.path.sep + t_splits[i]
+ t_path = t_path[1:]
+ if not os.path.exists(t_path):
+ print(f"{t_path} did not exist and will be created!")
+ os.makedirs("."+os.path.sep+t_path)
+ os.rename(full_art_path, trash_dst)
+ print(f"Moving image {full_art_path} to {trash_dst}")
+
+ self.update_all_images_list()
+ while self.curr_image_index >= len(self.all_images):
+ self.curr_image_index -= 1
+
+
+
+ @DeprecationWarning
+ def recalculate_hash_for_known_images(self):
+ """
+ Calculate and write the md5 hash for every image known to the database.
+
+ Important: For migration purposes only
+ :return:
+ """
+ for i in range(len(self.all_images)):
+ image_db_result = self.db_connection.get_art_by_path(self.all_images[i])
+
+ if image_db_result is None: # unknown image, skip
+ continue
+
+ hash_digest = self.get_md5_of_image(self.all_images[i])
+
+ authors = self.db_connection.get_authors_of_art(path=image_db_result["path"])
+ tag_ids = self.db_connection.get_art_tags_by_ID(art_ID=image_db_result["ID"])
+ tags = []
+ for tag_id in tag_ids:
+ tags.append(self.db_connection.get_tag_by_ID(tag_id)[0][1].strip())
+
+ self.db_connection.save_image(ID=image_db_result["ID"], path=image_db_result["path"],
+ title=image_db_result["title"],
+ link=image_db_result["link"], authors=authors, md5_hash=hash_digest,
+ tags=tags)
+
+ def get_next_unknown_image(self) -> int:
+ """
+ Search all images for the next image not known to the database in ascending order.
+
+ :return: index of the next unknown image, None if there is no unknown image
+ """
+ next_unknown: int = None
+ curr_searched_image_index = self.curr_image_index + 1
+
+ finished_loop = False
+ while next_unknown is None and not finished_loop:
+ if curr_searched_image_index >= len(self.all_images): # wrap around to the start
+ curr_searched_image_index = 0
+ if curr_searched_image_index == self.curr_image_index: # searched all images, nothing unknown
+ break
+
+ image_db_result = self.db_connection.get_art_by_path(self.all_images[curr_searched_image_index])
+ if image_db_result is None:
+ image_db_result = self.db_connection.get_art_by_hash(
+ self.get_md5_of_image(self.all_images[curr_searched_image_index])
+ )
+
+ if image_db_result is None: # image is unknown to database
+ next_unknown = curr_searched_image_index
+ break
+ else:
+ curr_searched_image_index += 1
+
+ if next_unknown:
+ return curr_searched_image_index
+ else:
+ return None
+
+ def get_prev_unknown_image(self) -> int:
+ """
+ Search all images for the next image not known to the databse in descending order.
+
+ :return:index of the next unknown image, None if there is no unknown image
+ """
+ next_unknown: int = None
+ curr_searched_image_index = self.curr_image_index - 1
+
+ finished_loop = False
+ while next_unknown is None and not finished_loop:
+ if curr_searched_image_index <= 0: # wrap around to the end
+ curr_searched_image_index = len(self.all_images) - 1
+ if curr_searched_image_index == self.curr_image_index: # searched all images, nothing unknown
+ break
+
+ image_db_result = self.db_connection.get_art_by_path(self.all_images[curr_searched_image_index])
+
+ if image_db_result is None: # image is unknown to database
+ next_unknown = curr_searched_image_index
+ break
+ else:
+ curr_searched_image_index -= 1
+
+ if next_unknown:
+ return curr_searched_image_index
+ else:
+ return None
+
+ def change_image(self):
+ """
+ Refresh the image display to show the most current data according to the selected image
+ :return:
+ """
+ self.window.setting_up_data = True
+
+ image_db_result = self.db_connection.get_art_by_hash(
+ self.get_md5_of_image(self.all_images[self.curr_image_index])
+ )
+ #image_db_result = self.db_connection.get_art_by_path(self.all_images[self.curr_image_index])
+ s = self.all_images[self.curr_image_index].split(os.path.sep)
+ if image_db_result is not None:
+ image_title = image_db_result["title"] if len(image_db_result["title"]) > 0 else "-Title Unknown-"
+ image_author = self.db_connection.get_authors_of_art_by_ID(image_db_result["ID"])
+ image_link = image_db_result["link"]
+ art_ID = image_db_result["ID"]
+ if image_author is None or len(image_author) == 0:
+ image_author = [(self.all_images[self.curr_image_index].split(os.path.sep)[0], "(Not in Database)")]
+ else:
+ art_ID = None
+ image_author = [(s[0], "(Not in Database)")]
+ image_title = s[-1] + " (Not in Database)"
+ image_link = "(Unknown)"
+
+ print("Displaying", self.all_images[self.curr_image_index])
+ self.window.display_image(image_title, image_author,
+ os.path.join(self.__file_root, self.all_images[self.curr_image_index]),
+ self.all_images[self.curr_image_index],
+ art_ID, image_link, file_name=s[-1])
+ self.window.set_tag_list([self.db_connection.get_tag_by_ID(x)[0][1].strip() for x in self.db_connection.get_art_tags_by_ID(art_ID)])
+
+ self.window.data_changed = False
+ self.window.setting_up_data = False
+
+ def create_db_connection(self, host: str, port: int, database: str, user: str, password: str) -> DBAdapter:
+ print("Changing db connection to ".format(host, port, user, password))
+ return DBAdapter(user=user, password=password, host=host, port=port, database=database)
+
+ def get_root(self) -> str:
+ return self.__file_root
+
+ def change_root(self, path: str):
+ """
+ Change the Artnet root path and confirm it is working.
+
+ Rewrite config there
+ :param path:
+ :return:
+ """
+ if len(path) == 0:
+ exit(0)
+ print("Changing root to", path)
+ self.__file_root = path
+
+ def get_db_connection_details(self) -> dict:
+ return self.config.data["db"]
+
+ def change_db_connection(self, host: str, port: int, database: str, user: str, password: str):
+ self.config.data["db"]["user"] = user
+ self.config.data["db"]["password"] = password
+ self.config.data["db"]["host"] = host
+ self.config.data["db"]["database"] = database
+ self.config.data["db"]["port"] = str(port)
+
+ self.config.update_config()
+
+ self.db_connection = self.create_db_connection(host, port, database, user, password)
+
+
diff --git a/ArtNet/db/__init__.py b/ArtNet/db/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/ArtNet/db/db_adapter.py b/ArtNet/db/db_adapter.py
new file mode 100644
index 0000000..c43d4a3
--- /dev/null
+++ b/ArtNet/db/db_adapter.py
@@ -0,0 +1,853 @@
+import psycopg2
+from psycopg2.errors import *
+from psycopg2.errorcodes import UNIQUE_VIOLATION
+
+
+class DBAdapter:
+
+ def __init__(self, user: str, password: str, host: str = "localhost", port: int = 5432, database: str = "artnet"):
+ self.db = None
+ self.db_cursor = None
+ try:
+ self.db = psycopg2.connect(host=host, port=port, database=database, user=user, password=password)
+ self.db_cursor = self.db.cursor()
+
+ except psycopg2.OperationalError as e:
+ raise ValueError("Invalid DB credentials!")
+ print("DB connection to {0}:{1}/{2}".format(host, port, database))
+
+ def save_image(self, ID: str, title: str, authors: list, path: str, tags: list, link: str, md5_hash: str):
+ """
+ Updates or saves the given image data to the DB
+ :param ID:
+ :param title:
+ :param authors:
+ :param path:
+ :param tags:
+ :param link:
+ :param md5_hash: md5 hash as a hex digest
+ :return:
+ """
+ print("Saving Image {0}:{1} authors: {2} path: {3} tags: {4} link: {5} hash:{6}"
+ .format(ID, title, authors, path, tags, link, md5_hash))
+ d = {"title": title, "path": path, "id": ID, "link": link, "hash": md5_hash}
+ if self.get_art_by_path(path) is None:
+ if ID is None:
+ self.db_cursor.execute(
+ "INSERT INTO art (path, title, link, md5_hash) VALUES (%(path)s, %(title)s, %(link)s, %(hash)s)",
+ d)
+ ID = self.get_art_by_path(path)["ID"]
+ d["id"] = ID
+ elif self.get_tag_by_ID(ID) is None:
+ self.db_cursor.execute(
+ "INSERT INTO art (path, title, link, md5_hash) VALUES (%(path)s, %(title)s, %(link)s, %(hash)s)", d)
+ else:
+ self.db_cursor.execute("UPDATE art SET path = %(path)s, title = %(title)s, link = %(link)s, " +
+ "md5_hash = %(hash)s WHERE id = %(id)s", d)
+
+ if ID is None:
+ ID = self.get_art_by_path(path)["ID"]
+ assert(ID != None)
+
+ old_tags = [self.get_tag_by_ID(x)[0][1].strip() for x in self.get_art_tags_by_ID(ID)]
+ for tag in tags:
+ if tag in old_tags: # already present
+ continue
+ d = {"id": ID, "tag": self.get_tag_ID(tag)}
+ try:
+ self.db_cursor.execute("INSERT INTO art_tag (art_id, tag_ID) VALUES (%(id)s, %(tag)s)", d)
+ except psycopg2.Error as e:
+ if e.pgcode == UNIQUE_VIOLATION:
+ print(e)
+ print("Skipping Unique Violation ...")
+ else:
+ raise e
+
+ for old_tag in old_tags:
+ if old_tag not in tags: # need to remove old tag
+ self.remove_tag_from_image(art_ID=ID, tag_ID=self.get_tag_ID(old_tag))
+
+ old_authors = self.get_authors_of_art_by_ID(ID)
+ for author in authors:
+ if author in old_authors:
+ continue
+ d = {"id": ID, "author_name": author[0], "author_domain": author[1]}
+ self.db_cursor.execute("INSERT INTO art_author (presence_name, presence_domain, art_ID) " +
+ "VALUES (%(author_name)s, %(author_domain)s, %(id)s)", d)
+ for old_author_name, old_author_domain in old_authors:
+ if (old_author_name, old_author_domain) not in authors: # need to remove old author
+ self.remove_artist_from_image(art_ID=ID, presence_name=old_author_name,
+ presence_domain=old_author_domain)
+
+ self.db.commit()
+
+ def remove_image(self, hash: str):
+ """
+ Removes an image completely from the database.
+
+ Does not affect tags or artists but removes art-tag, art-artist relations
+ :param hash: md5 hash of the image to be deleted
+ :return:
+ """
+ image_data = self.get_art_by_hash(hash)
+ art_ID = image_data["ID"]
+ print(f"Deleting image #{art_ID} {image_data['title']}")
+ tags = self.get_art_tags_by_ID(art_ID)
+ for tag_ID in tags:
+ pass
+ #self.remove_tag_from_image(art_ID=art_ID, tag_ID=tag_ID)
+ authors = self.get_authors_of_art_by_ID(art_ID)
+ for presence_name, presence_domain in authors:
+ pass
+ #self.remove_artist_from_image(art_ID=art_ID, presence_name=presence_name, presence_domain=presence_domain)
+
+ #self.db_cursor.execute("DELETE FROM art WHERE md5_hash = %(hash)s", {"hash": hash})
+
+
+
+ def remove_artist_from_image(self, art_ID, presence_name, presence_domain):
+ """
+ Removes the relationship between an artist and the image
+ :param art_ID:
+ :param presence_name:
+ :param presence_domain:
+ :return:
+ """
+ d = {"name": presence_name, "domain": presence_domain, "id": art_ID}
+ self.db_cursor.execute("DELETE FROM art_author " +
+ "WHERE presence_name = %(name)s and " +
+ "presence_domain = %(domain)s and art_ID = %(id)s",
+ d)
+
+ def remove_tag_from_image(self, art_ID, tag_ID):
+ """
+ Remove the relationship between a tag and the image
+ :param art_ID:
+ :param tag_ID:
+ :return:
+ """
+ d = {"id": art_ID, "tag": tag_ID}
+ self.db_cursor.execute("DELETE FROM art_tag WHERE art_id = %(id)s and tag_id = %(tag)s", d)
+
+ def save_presence(self, name: str, domain: str, artist_ID: int, link: str):
+ """
+ Save the presence entry with the given data.
+
+ If it doesn't exist a new one will be created.
+ If the artist ID is unknown an exception will be thrown
+ :param name:
+ :param domain:
+ :param artist_ID:
+ :param link:
+ :return:
+ """
+ print("Saving Presence {0}:{1} Artist: {2} Link: {3}".format(name, domain, artist_ID, link))
+ artist = self.get_artist(artist_ID)
+ if artist is None or len(artist) == 0:
+ raise Exception("Unknown Artist to create/update Presence with!")
+
+ presence = self.get_presence(name, domain)
+ d = {"name": name, "domain": domain, "artist": artist_ID, "link": link}
+ if len(presence) == 0: # presence does not exist yet
+ self.db_cursor.execute("INSERT INTO presence (name, domain, artist, link) " +
+ "VALUES (%(name)s, %(domain)s, %(artist)s, %(link)s)", d)
+ else: # presence exists, update it
+ self.db_cursor.execute("UPDATE presence SET artist = %(artist)s, link = %(link)s" +
+ "WHERE name = %(name)s and domain = %(domain)s", d)
+
+ self.db.commit()
+
+ def remove_presence(self, name: str, domain: str):
+ """
+ Remove a presence from the database
+ :return:
+ """
+ print("Removing Presence {0}:{1}".format(name, domain))
+ d = {"name": name, "domain": domain}
+ self.db_cursor.execute("DELETE FROM presence WHERE name = %(name)s and domain = %(domain)s", d)
+ self.db.commit()
+
+ def get_presence(self, name: str, domain: str):
+ """
+ Queries the database for the presence and returns the result
+
+ :param name:
+ :param domain:
+ :return:
+ """
+ d = {"name": name, "domain": domain}
+ self.db_cursor.execute("SELECT name, domain, artist, link FROM presence " +
+ "WHERE name = %(name)s and domain = %(domain)s",
+ d)
+ rows = self.db_cursor.fetchall()
+ new_rows = []
+ for row in rows:
+ new_rows.append((row[0].strip(), row[1].strip(), row[2], row[3]))
+
+ return new_rows
+
+ def save_artist(self, ID: int, description: str):
+ """
+ Save (or update if ID is already taken) an artist to the DB
+ :param ID:
+ :param description:
+ :return:
+ """
+ print("Saving artist {0}:{1}".format(ID, description))
+ d = {"id": ID, "description": description}
+ if ID is None: # no ID given, auto generate it
+ self.db_cursor.execute("INSERT INTO artist (description) VALUES (%(description)s)", d)
+ elif len(self.get_artist(ID)) != 0: # artist exists already:
+ self.db_cursor.execute("UPDATE artist SET description = %(description)s WHERE id = %(id)s", d)
+ else: # artist needs to be created
+ self.db_cursor.execute("INSERT INTO artist (id, description) VALUES (%(id)s, %(description)s)", d)
+ self.db.commit()
+
+ def remove_artist(self, ID: int):
+ """
+ Deletes the artist from the DB
+ :param ID:
+ :return:
+ """
+ print("Deleting artist {0}".format(ID))
+ d = {"id": ID}
+ self.db_cursor.execute("DELETE FROM Artist WHERE ID = %(id)s", d)
+ self.db.commit()
+
+ def save_category(self, name: str):
+ """
+ Save the category to the DB.
+
+ Does not check if the category already exists
+ :param name:
+ :return:
+ """
+ print("Saving category {0}!".format(name))
+ d = {"name": name}
+ self.db_cursor.execute("INSERT INTO tag_category (name) VALUES (%(name)s)", d)
+
+ self.db.commit()
+
+ def remove_category(self, name: str):
+ """
+ Remove the category from the DB.
+ :param name:
+ :return:
+ """
+ d = {"name": name}
+ self.db_cursor.execute("DELETE FROM tag_category WHERE name = %(name)s", d)
+
+ self.db.commit()
+
+ def get_artist(self, ID: int):
+ """
+ Queries the database for the artist (not presence) and returns the result
+ :param ID:
+ :return:
+ """
+ d = {"id": ID}
+ self.db_cursor.execute("SELECT id, description FROM artist WHERE id = %(id)s", d)
+
+ return self.db_cursor.fetchall()
+
+ def get_artist_presences(self, id: int):
+ """
+ Queries the databse for all presences associated with the artist and returns the result
+ :param id:
+ :return:
+ """
+ d = {"id": id}
+ self.db_cursor.execute("SELECT name, domain, artist FROM Presence WHERE artist = %(id)s", d)
+
+ return self.db_cursor.fetchall()
+
+ def get_all_artists(self):
+ """
+ Lists all available artists (not presences) and returns the result
+ :return:
+ """
+ self.db_cursor.execute("SELECT id, description FROM artist")
+
+ def get_art_by_hash(self, file_hash: str) -> dict:
+ """
+ Queries the database for the art via its hash and returns it if available.
+
+ Currently uses an md5 hash
+ :param file_hash:
+ :return:
+ """
+ d = {"hash": file_hash}
+ self.db_cursor.execute("SELECT id, path, title, link, md5_hash FROM art where md5_hash = %(hash)s", d)
+
+ result = self.db_cursor.fetchall()
+
+ if len(result) == 0:
+ return None
+ else:
+ result = result[0]
+ image_data = {
+ "ID": result[0],
+ "path": result[1],
+ "title": result[2],
+ "link": result[3],
+ "md5_hash": result[4],
+ }
+ return image_data
+
+ def get_art_by_path(self, path: str) -> dict:
+ """
+ Queries the database for the art via its path and returns it if available.
+
+ Otherwise None
+ :param path:
+ :return:
+ """
+ d = {"path": path}
+ self.db_cursor.execute("SELECT id, path, title, link, md5_hash FROM art where path = %(path)s", d)
+
+ result = self.db_cursor.fetchall()
+
+ if len(result) == 0:
+ return None
+ else:
+ result = result[0]
+ image_data = {
+ "ID": result[0],
+ "path": result[1],
+ "title": result[2],
+ "link": result[3],
+ "md5_hash": result[4],
+ }
+ return image_data
+
+ @DeprecationWarning
+ def get_authors_of_art(self, path: str):
+ """
+ Get the authors (presence and author) of the given art.
+ :param path:
+ :return:
+ """
+ art_data = self.get_art_by_path(path)
+ art_id: int = None
+ if art_data is not None:
+ art_id = art_data["ID"]
+ if art_id is None:
+ return None
+ d = {"id": art_id}
+
+ self.db_cursor.execute("SELECT presence_name, presence_domain FROM art_author WHERE art_ID = %(id)s", d)
+ presences = self.db_cursor.fetchall()
+
+ result = []
+ for name, domain in presences:
+ result.append((name.strip(), domain.strip()))
+
+ return result
+
+ def get_authors_of_art_by_ID(self, id: int):
+ """
+ Get the authors (presence and author) of the given art.
+ :param id:
+ :return:
+ """
+ d = {"id": id}
+
+ self.db_cursor.execute("SELECT presence_name, presence_domain FROM art_author WHERE art_ID = %(id)s", d)
+ presences = self.db_cursor.fetchall()
+
+ result = []
+ for name, domain in presences:
+ result.append((name.strip(), domain.strip()))
+
+ return result
+
+ def get_art_tags_by_ID(self, art_ID: int):
+ """
+ Query the database for all tags associated with a given art ID
+ :param art_ID:
+ :return:
+ """
+ d = {"id": art_ID}
+ self.db_cursor.execute("SELECT tag_ID FROM art_tag WHERE art_id = %(id)s", d)
+
+ rows = self.db_cursor.fetchall()
+ new_rows = []
+ for row in rows:
+ new_rows.append(row[0])
+ return new_rows
+
+ def search_fuzzy_presence(self, name: str, domain: str, all_if_empty: bool = False):
+ """
+ Search a list of presences fitting the (name, domain) fuzzy.
+ :param name:
+ :param domain:
+ :param all_if_empty:
+ :return:
+ """
+ if all_if_empty and (name is None or len(name) == 0) and (domain is None or len(domain) == 0):
+ self.db_cursor.execute("SELECT name, domain, artist FROM presence")
+ else:
+ self.db_cursor.execute("SELECT name, domain, artist FROM presence WHERE LOWER(name) LIKE LOWER('%{0}%')".format(name) +
+ " AND domain LIKE '%{0}%'".format(domain))
+ rows = self.db_cursor.fetchall()
+
+ result = []
+ for row in rows:
+ result.append((row[0].strip(), row[1].strip()))
+ return result
+
+ def get_presences_art(self, name, domain):
+ """
+ Query a list of all art that includes the given presence as their author
+ :param name:
+ :param domain:
+ :return:
+ """
+ d = {"name": name, "domain": domain}
+ self.db_cursor.execute(
+ "SELECT art_ID FROM Art_Author WHERE presence_name = %(name)s and presence_domain = %(domain)s", d)
+
+ return self.db_cursor.fetchall()
+
+ def get_all_categories(self) -> list:
+ """
+ Return all categories in the database.
+ :return:
+ """
+ self.db_cursor.execute("SELECT name FROM tag_category")
+
+ rows = []
+ for row in self.db_cursor.fetchall():
+ rows.append(row[0].strip())
+
+ return rows
+
+ def search_fuzzy_categories(self, search: str) -> list:
+ """
+ Search a list of categories fitting search fuzzy.
+ :param search:
+ :return:
+ """
+ self.db_cursor.execute("SELECT name FROM tag_category WHERE LOWER(name) LIKE LOWER('%{0}%')".format(search))
+
+ rows = []
+ for row in self.db_cursor.fetchall():
+ rows.append(row[0])
+
+ return rows
+
+ def search_fuzzy_tag(self, name: str, all_if_empty: bool = False) -> list:
+ """
+ Search a list of tags fitting name fuzzy.
+ :param name:
+ :param all_if_empty:
+ :return:
+ """
+ if all_if_empty and len(name) == 0:
+ self.db_cursor.execute("SELECT name, description, category FROM tag")
+ else:
+ self.db_cursor.execute("SELECT name, description, category FROM tag WHERE LOWER(name) LIKE LOWER('%{0}%')".format(name))
+
+ rows = self.db_cursor.fetchall()
+ new_rows = []
+ for i in range(len(rows)):
+ new_rows.append([])
+ for j in range(len(rows[i])):
+ if rows[i][j] is None:
+ new_rows[i].append("")
+ else:
+ new_rows[i].append(rows[i][j].strip())
+
+ return new_rows
+
+ def search_fuzzy_artists(self, ID: int, description: str, all_if_empty: bool = False):
+ """
+ Search a list of fitting artists fuzzy.
+
+ If ID is None it will search using the description
+ :param ID:
+ :param description:
+ :param all_if_empty:
+ :return:
+ """
+ if ID is not None:
+ self.db_cursor.execute("SELECT id, description FROM artist WHERE id = %(id)s", {"id": ID})
+ elif all_if_empty and ID is None and len(description) == 0:
+ self.db_cursor.execute("SELECT id, description FROM artist")
+ else:
+ self.db_cursor.execute("SELECT id, description FROM artist WHERE LOWER(description) LIKE LOWER('%{0}%')"
+ .format(description))
+
+ return self.db_cursor.fetchall()
+
+ def create_tag(self, name: str, description: str, aliases: list, implications: list, category: str = None):
+ name = name.strip()
+ if aliases is None:
+ aliases = []
+ if implications is None:
+ implications = []
+ d = {"name": name, "description": description, "category": category}
+ self.db_cursor.execute("INSERT INTO tag (name, description, category) " +
+ "VALUES (LOWER(%(name)s), %(description)s, %(category)s)", d)
+
+ for alias in aliases:
+ self.add_alias(name, alias)
+ for implicant in implications:
+ self.add_implication(name, implicant)
+
+ self.db.commit()
+
+ def edit_tag(self, name: str, description: str, aliases: list=None, implications: list=None, category: str = None,
+ old_tag: str = None):
+ """
+ Edit a tag with the new given data
+ :param name: unique tag name to apply edits to (distinct from old_tag)
+ :param description: new description to apply, set to None to omit
+ :param aliases: list of tag names to be the alias of this tag, set to None to omit
+ :param implications: list of tag names to be implied by this tag, set to None to omit
+ :param category:
+ :param old_tag: old tag name from which to transfer all data
+ :return:
+ """
+ name = name.strip().lower()
+ d = {
+ "name": name,
+ "description": description,
+ "alias": aliases,
+ "implications": implications,
+ "category": category,
+ }
+
+ if old_tag is not None and len(old_tag) > 0:
+ # tag name changed, need to transfer all data and remove old tag
+ old_tag = self.get_tag_by_name(old_tag)
+ d["ID"] = old_tag[0][-1]
+ self.db_cursor.execute("UPDATE tag SET name = %(name)s WHERE tag_ID = %(ID)s", d)
+
+ if description is not None:
+ self.db_cursor.execute("UPDATE tag SET description = %(description)s, category = %(category)s " +
+ "WHERE name = %(name)s", d)
+
+ if aliases is not None:
+ old_aliases = self.get_tag_aliases(name)
+ for alias in aliases:
+ if alias in old_aliases: # is this already set?
+ continue
+ self.add_alias(name, alias)
+
+ for old_alias in old_aliases:
+ if old_alias not in aliases: # got to delete an alias?
+ self.remove_alias(name, old_alias)
+
+ if implications is not None:
+ old_implicants = self.get_tag_implications(name)
+ for implicant in implications:
+ if implicant in old_implicants: # is this already set?
+ continue
+ self.add_implication(name, implicant)
+
+ for old_implicant in old_implicants:
+ if old_implicant not in implications: # got to delete an implicant?
+ self.remove_implication(name, old_implicant)
+
+ def add_alias(self, name: str, alias: str):
+ """
+ Add the alias pair to the database
+ :param name:
+ :param alias:
+ :return:
+ """
+ d = {
+ "name": self.get_tag_ID(name),
+ "alias": self.get_tag_ID(alias)
+ }
+ self.db_cursor.execute("INSERT INTO tag_alias (tag1, tag2) VALUES (%(name)s, %(alias)s)", d)
+
+ def remove_alias(self, name: str, alias: str):
+ """
+ Remove alias pair from the database
+ :param name:
+ :param alias:
+ :return:
+ """
+ d = {
+ "ID": self.get_tag_ID(name),
+ "alias": self.get_tag_ID(alias)
+ }
+ self.db_cursor.execute("DELETE FROM tag_alias WHERE (tag1 = %(ID)s and tag2 = %(alias)s) " +
+ "or (tag1 = %(alias)s and tag2 = %(ID)s)", d)
+
+ def add_implication(self, name: str, implicant: str):
+ """
+ Add the implication to the database
+ :param name:
+ :param implicant:
+ :return:
+ """
+ d = {
+ "name": self.get_tag_ID(name),
+ "implicant": self.get_tag_ID(implicant)
+ }
+ self.db_cursor.execute("INSERT INTO tag_implication (root_tag, implicate) VALUES (%(name)s, %(implicant)s)",
+ d)
+ self.db.commit()
+
+ def remove_implication(self, name: str, implicant: str):
+ """
+ Remove the implication pair from the database
+ :param name:
+ :param implicant:
+ :return:
+ """
+ d = {"name": self.get_tag_ID(name),
+ "implicant": self.get_tag_ID(implicant)}
+ self.db_cursor.execute("DELETE FROM tag_implication WHERE root_tag = %(name)s " +
+ "and implicate = %(implicant)s", d)
+ self.db.commit()
+
+ def remove_tag(self, name: str):
+ """
+ Remove the given tag from the database
+ :param str:
+ :return:
+ """
+ d = {"name": name}
+ self.db_cursor.execute("DELETE FROM tag WHERE name = %(name)s", d)
+
+ self.db.commit()
+
+ def get_tag_ID(self, name: str) -> int:
+ """
+ Get the tag ID by querying it by its unique name
+ :param name:
+ :return:
+ """
+ d = {"name": name}
+ self.db_cursor.execute("SELECT tag_ID, name FROM tag where LOWER(name) = LOWER(%(name)s)", d)
+
+ rows = []
+ for row in self.db_cursor.fetchall():
+ rows.append(row[0])
+
+ if len(rows) > 1:
+ exit(1) # something went horribly horribly wrong!
+ elif len(rows) == 0:
+ return None
+
+ return rows[0]
+
+ def get_tag_by_name(self, name: str) -> list:
+ """
+ Search the tag in the DB via its name.
+
+ Note: name is constrained to be unique by the DB
+ :param name:
+ :return: Returns the tag's name, description, category, tag ID
+ """
+ d = {"name": name}
+ self.db_cursor.execute("SELECT name, description, category, tag_ID FROM tag where LOWER(name) = LOWER(%(name)s)", d)
+
+ rows = []
+ for row in self.db_cursor.fetchall():
+ new_row = []
+ for value in row:
+ if value is None:
+ new_row.append("")
+ elif type(value) == str:
+ new_row.append(value.strip())
+ else:
+ new_row.append(value)
+ rows.append(new_row)
+ return rows
+
+ def get_tag_by_ID(self, ID: int) -> list:
+ """
+ Search the tag in the DB via its ID.
+
+ Note: name is constrained to be unique by the DB
+ :param ID:
+ :return: Returns the tag's ID, name, description, category
+ """
+ d = {"ID": ID}
+ self.db_cursor.execute("SELECT tag_ID, name, description, category FROM tag where tag_ID = %(ID)s", d)
+
+ return self.db_cursor.fetchall()
+
+ def get_tag_aliases(self, name: str) -> list:
+ """
+ Search for the tag's aliases and the tag's aliases
+ :param name:
+ :return: List of tag Names that are aliases of this one
+ """
+ aliases = self.get_tag_aliases_by_ID(self.get_tag_ID(name))
+ result = []
+ for alias in aliases:
+ tag_data = self.get_tag_by_ID(alias)
+ if len(tag_data) > 0:
+ result.append(tag_data[0][1].strip())
+ return result
+
+ def get_tag_aliases_by_ID(self, tag_ID: int) -> list:
+ """
+ Search for the tag's aliases and the tag's aliases
+ :param name:
+ :return: List of tag IDs that are aliases of this one
+ """
+ marked = []
+ to_search = [tag_ID]
+ while len(to_search) != 0:
+ curr_alias = to_search.pop()
+ found_aliases = self.__get_tag_aliases_no_recurse(curr_alias)
+ marked.append(curr_alias)
+
+ for found_alias in found_aliases:
+ if found_alias not in marked:
+ to_search.append(found_alias)
+
+ marked.remove(tag_ID)
+ return marked
+
+ def get_all_tag_implications(self, name: str) -> list:
+ """
+ Search for the tag's implications and those that are implied by them.
+ :param name:
+ :return:
+ """
+ root_ID = self.get_tag_ID(name)
+ collected_tags = self.get_tag_implications_by_ID(root_ID)
+ collected_tags.append(root_ID)
+ to_search = self.get_tag_implications_by_ID(root_ID)
+
+ while len(to_search) != 0:
+ curr_tag = to_search.pop()
+ found_implications = self.get_tag_implications_by_ID(curr_tag)
+ for found in found_implications:
+ if found in collected_tags:
+ continue
+ else:
+ collected_tags.append(found)
+ to_search.append(found)
+
+ collected_tags.remove(root_ID)
+
+ result = []
+ for tag_ID in collected_tags:
+ tag_data = self.get_tag_by_ID(tag_ID)
+ if len(tag_data) != 0:
+ result.append(tag_data[0][1].strip())
+ return result
+
+ def get_tag_implications_by_ID(self, ID: int) -> list:
+ """
+ Search for the tag's implications
+ :param ID: ID of the tag
+ :return: returns a list of IDs that are implied
+ """
+ d = {"ID": ID}
+ if d["ID"] is None:
+ return []
+
+ self.db_cursor.execute("SELECT root_tag, implicate FROM Tag_Implication WHERE root_tag = %(ID)s", d)
+
+ rows = self.db_cursor.fetchall()
+ if len(rows) == 0:
+ return []
+
+ r = []
+ for row in rows:
+ r.append(row[1])
+
+ return r
+
+ def get_tag_implications(self, name: str) -> list:
+ """
+ Search for the tag's implications
+ :param name:
+ :param output_ID: Don't resolve the tag ids into names in the resulting list
+ :return: List of tag names
+ """
+ d = {"ID": self.get_tag_ID(name)}
+ if d["ID"] is None:
+ return []
+
+ self.db_cursor.execute("SELECT root_tag, implicate FROM Tag_Implication WHERE root_tag = %(ID)s", d)
+
+ rows = self.db_cursor.fetchall()
+ if len(rows) == 0:
+ return []
+
+ r = []
+ for row in rows:
+ r.append(row[1])
+
+ result = [self.get_tag_by_ID(v) for v in r]
+
+ r = []
+ for value in result:
+ r.append(value[0][1].strip())
+
+ return r
+
+ def get_tag_impliers(self, name: str) -> list:
+ """
+ Search for tags that imply this one.
+ :param name:
+ :return:
+ """
+ d = {"ID": self.get_tag_ID(name)}
+ self.db_cursor.execute("SELECT root_tag, implicate FROM Tag_Implication WHERE implicate = %(ID)s", d)
+
+ rows = self.db_cursor.fetchall()
+ r = []
+ for row in rows:
+ r.append(row[0])
+ return r
+
+ def __get_tag_aliases_no_recurse(self, tag_id: int) -> list:
+ """
+ Search for the tag's aliases
+ :param tag_id:
+ :return:
+ """
+ d = {"ID": tag_id}
+ if d["ID"] is None:
+ return []
+
+ self.db_cursor.execute("SELECT tag1, tag2 FROM Tag_Alias "
+ "WHERE tag1 = %(ID)s or tag2 = %(ID)s", d)
+ rows = self.db_cursor.fetchall()
+ r = []
+ for row in rows:
+ if row[0] == tag_id:
+ r.append(row[1])
+ elif row[1] == tag_id:
+ r.append(row[0])
+ else:
+ raise Exception("Something went terribly wrong!")
+
+ return r
+
+
+if __name__ == "__main__":
+ db = DBAdapter(user="artnet_editor", password="G606Rm9sFEXe6wfTLxVu",
+ database="artnet", host="localhost", port=5432)
+
+ search = 'Ajin/66699b4e41f533982db1766e8f72494a.gif2222'
+ print("Art search result:", search, db.get_art_by_path('Ajin/66699b4e41f533982db1766e8f72494a.gif'))
+
+ search = "tag"
+ print("Fuzzy Search Result:", search, db.search_fuzzy_tag(search))
+
+ search = "tag1"
+ print("Search Result:", search, db.get_tag_by_name(search))
+
+ search = "tag1"
+ print("Alias Search Result:", search, db.get_tag_aliases(search))
+
+ search = "tag1"
+ print("Implication Search Result:", search, db.get_tag_implications(search))
+
+ target = "tag1"
+ db.edit_tag(target, "new description! ;D", aliases=["tag1_alias"], implications=["tag1_implicated"])
+ print("Editing:", target, db.get_tag_by_name(target), db.get_tag_aliases(target), db.get_tag_implications(target))
+
diff --git a/ArtNet/file/__init__.py b/ArtNet/file/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/ArtNet/file/config_reader.py b/ArtNet/file/config_reader.py
new file mode 100644
index 0000000..abdad1a
--- /dev/null
+++ b/ArtNet/file/config_reader.py
@@ -0,0 +1,135 @@
+import os
+import base64
+import copy
+
+import yaml
+from cryptography.exceptions import AlreadyFinalized
+from cryptography.exceptions import InvalidTag
+from cryptography.exceptions import UnsupportedAlgorithm
+from cryptography.hazmat.backends import default_backend
+from cryptography.hazmat.primitives import hashes
+from cryptography.hazmat.primitives.ciphers.aead import AESGCM
+from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
+
+
+class ConfigReader:
+
+ kdf = None
+ nonce = None
+
+ def __init__(self, root_folder: str, password: str):
+ if root_folder is None:
+ raise Exception("No root folder was defined!")
+ self.__key = None
+ self.__aesgcm = None
+ self.data = {}
+
+ self.__password = password
+ self.__config_location = os.path.join(root_folder, ".artnet", "artnet.config")
+
+ ConfigReader.__create_kdf()
+ self.__key = ConfigReader.kdf.derive(bytes(password.encode('utf-8')))
+ self.__aesgcm = AESGCM(self.__key)
+
+ if os.path.exists(self.__config_location) and os.path.isfile(self.__config_location):
+ self.read_config()
+ else:
+ if not os.path.exists(os.path.join(root_folder, ".artnet")):
+ os.mkdir(os.path.join(root_folder, ".artnet"))
+ self.create_default_config()
+ self.read_config()
+
+ def update_config(self):
+ """
+ Update the written config with the local settings
+ :return:
+ """
+ file = open(self.__config_location, "w")
+ yaml.dump(stream=file, data=self.encrypt_sensitive_data(copy.deepcopy(self.data)))
+
+ def read_config(self):
+ """
+ Read the config from file and overwrite local settings
+ :return:
+ """
+ print(os.path.isfile(self.__config_location))
+ print(os.path.join(os.getcwd(), self.__config_location))
+ file = open(self.__config_location, "r")
+ data = yaml.safe_load(stream=file)
+ self.data = self.decrypt_sensitive_data(data)
+
+ def encrypt_sensitive_data(self, data: dict) -> dict:
+ """
+ Encrypts the sensitive portions of the data
+ :return:
+ """
+ new_data = data
+ new_data["db"]["password"] = self.encrypt_text(data["db"]["password"])
+ new_data["db"]["user"] = self.encrypt_text(data["db"]["user"])
+
+ return new_data
+
+ def decrypt_sensitive_data(self, data: dict) -> dict:
+ """
+ Decrypts the sensitive portions of the data
+ :return:
+ """
+ new_data = data
+ new_data["db"]["password"] = self.decrypt_text(data["db"]["password"])
+ new_data["db"]["user"] = self.decrypt_text(data["db"]["user"])
+
+ return new_data
+
+ def create_default_config(self):
+ """
+ Create a default config, overwrites all settings and generates a new key
+ :return:
+ """
+ self.data = {
+ "db": {
+ "host": "localhost",
+ "port": 5432,
+ "database": "artnet",
+ "user": "your_user",
+ "password": "enter_password_via_gui"
+ },
+
+ }
+ self.update_config()
+
+ @staticmethod
+ def __create_kdf():
+ ConfigReader.kdf = PBKDF2HMAC(algorithm=hashes.SHA512(),
+ length=32,
+ salt=bytes("ArtN3t.WhatElse?".encode('utf-8')),
+ iterations=10000,
+ backend=default_backend()
+ )
+ ConfigReader.nonce = bytes("qt34nvßn".encode('utf-8'))
+
+ def encrypt_text(self, text: str) -> str:
+ cipher_text_bytes = self.__aesgcm.encrypt(data=text.encode('utf-8'),
+ associated_data=None,
+ nonce=ConfigReader.nonce
+ )
+ return base64.urlsafe_b64encode(cipher_text_bytes)
+
+ def decrypt_text(self, cipher: str) -> str:
+ if ConfigReader.kdf is None:
+ ConfigReader.__create_kdf()
+ try:
+ decrypted_cipher_text_bytes = self.__aesgcm.decrypt(
+ nonce=ConfigReader.nonce,
+ data=base64.urlsafe_b64decode(cipher),
+ associated_data=None
+ )
+ except InvalidTag:
+ raise Exception("Could not decrypt Text! Wrong Password?")
+
+ return decrypted_cipher_text_bytes.decode('utf-8')
+
+
+if __name__ == "__main__":
+ cr = ConfigReader(password="MySuperDuperKey", root_folder=".")
+
+ print(cr.data)
diff --git a/ArtNet/file/file_reader.py b/ArtNet/file/file_reader.py
new file mode 100644
index 0000000..93e5ef9
--- /dev/null
+++ b/ArtNet/file/file_reader.py
@@ -0,0 +1,45 @@
+import os
+
+
+class FileReader:
+
+ def __init__(self, root: str):
+ if not os.path.isdir(root):
+ raise Exception("Root was not a valid directory! {0}".format(root))
+ self.__root = root
+
+ def list_artists(self) -> list:
+ """
+ List all Artists within the root folder
+ :return:
+ """
+ l = [os.path.basename(x) for x in os.listdir(self.__root)]
+ if ".artnet" in l:
+ l.remove(".artnet")
+ return l
+
+ def get_files(self, artist: str) -> list:
+ """
+ List all files within the given artists directory with their relative path (to the root).
+ :param artist:
+ :return:
+ """
+ l = []
+
+ root = os.path.join(self.__root)
+ dirs = [artist]
+
+ while len(dirs) != 0:
+ curr_dir = dirs.pop()
+ curr_full_dir = os.path.join(root, curr_dir)
+ l += [os.path.join(curr_dir, f) for f in os.listdir(curr_full_dir) if os.path.isfile(os.path.join(curr_full_dir, f))]
+ for d in [x for x in os.listdir(curr_full_dir) if os.path.isdir(os.path.join(curr_full_dir, x))]:
+ dirs.append(os.path.join(curr_dir, d))
+
+ return l
+
+
+if __name__ == "__main__":
+ fr = FileReader("/home/peery/Software_Projects/ArtNet/App/Fake_Other_Artists")
+ print(fr.list_artists())
+ print(fr.get_files("AkuDrache"))
diff --git a/ArtNet/gui/__init__.py b/ArtNet/gui/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/ArtNet/gui/dialogs/__init__.py b/ArtNet/gui/dialogs/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/ArtNet/gui/dialogs/artist_modify_dialog/__init__.py b/ArtNet/gui/dialogs/artist_modify_dialog/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/ArtNet/gui/dialogs/artist_modify_dialog/artist_mod_dialog.py b/ArtNet/gui/dialogs/artist_modify_dialog/artist_mod_dialog.py
new file mode 100644
index 0000000..0801798
--- /dev/null
+++ b/ArtNet/gui/dialogs/artist_modify_dialog/artist_mod_dialog.py
@@ -0,0 +1,57 @@
+from PyQt5 import QtWidgets
+from PyQt5.QtCore import Qt
+
+from ArtNet.gui.dialogs.artist_modify_dialog.artist_modify_dialog import Ui_Artist_Mod_Dialog
+
+
+class ArtistModDialog(QtWidgets.QDialog):
+
+ def __init__(self, parent=None, create_artist: bool = False):
+ super().__init__(parent)
+ self.parent = parent
+ self.ui = Ui_Artist_Mod_Dialog()
+ self.ui.setupUi(self)
+
+ self.data = {"id": None,
+ "description": None}
+
+ if not create_artist:
+ self.set_available_artists(self.parent.parent.parent.get_all_artists())
+ else:
+ self.ui.id_list = QtWidgets.QLineEdit(self)
+ self.ui.id_spinbox.setParent(None)
+ self.ui.id_spinbox.destroy()
+ self.ui.id_layout.addWidget(self.ui.id_list)
+
+ self.ui.description_line.textChanged.connect(self.on_description_changed)
+
+ def set_available_artists(self, artists: list):
+ if artists is None:
+ self.ui.id_spinbox.setParent(None)
+ self.ui.id_spinbox.destroy()
+ self.ui.id_label.setText("Artist ID (auto-generated)")
+ return
+
+ self.ui.id_spinbox.setMinimum(0)
+ self.ui.id_spinbox.setMaximum(len(artists))
+ self.ui.id_spinbox.valueChanged.connect(self.on_id_spinbox_changed)
+
+ def on_id_spinbox_changed(self):
+ id = self.ui.id_spinbox.value()
+ artist = self.parent.parent.parent.get_artist(id)
+ if len(artist) != 0:
+ self.ui.description_line.setText(artist[0][1])
+ else:
+ self.ui.description_line.setText("Warning! unknown Artist ID!")
+
+ self.data["id"] = id
+
+ def on_description_changed(self):
+ self.data["description"] = self.ui.description_line.text()
+
+ def exec_(self):
+ result = super(ArtistModDialog, self).exec_()
+ if result == QtWidgets.QDialog.Rejected:
+ return None
+
+ return self.data
diff --git a/ArtNet/gui/dialogs/artist_modify_dialog/artist_modify_dialog.py b/ArtNet/gui/dialogs/artist_modify_dialog/artist_modify_dialog.py
new file mode 100644
index 0000000..ddc7de8
--- /dev/null
+++ b/ArtNet/gui/dialogs/artist_modify_dialog/artist_modify_dialog.py
@@ -0,0 +1,101 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'dialogs/artist_modify_dialog/artist_modify_dialog.ui'
+#
+# Created by: PyQt5 UI code generator 5.15.0
+#
+# WARNING: Any manual changes made to this file will be lost when pyuic5 is
+# run again. Do not edit this file unless you know what you are doing.
+
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+
+class Ui_Artist_Mod_Dialog(object):
+ def setupUi(self, Artist_Mod_Dialog):
+ Artist_Mod_Dialog.setObjectName("Artist_Mod_Dialog")
+ Artist_Mod_Dialog.resize(311, 205)
+ self.verticalLayout = QtWidgets.QVBoxLayout(Artist_Mod_Dialog)
+ self.verticalLayout.setObjectName("verticalLayout")
+ self.dialog_frame = QtWidgets.QFrame(Artist_Mod_Dialog)
+ self.dialog_frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
+ self.dialog_frame.setFrameShadow(QtWidgets.QFrame.Raised)
+ self.dialog_frame.setObjectName("dialog_frame")
+ self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.dialog_frame)
+ self.verticalLayout_2.setObjectName("verticalLayout_2")
+ self.dialog_topic = QtWidgets.QLabel(self.dialog_frame)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.dialog_topic.sizePolicy().hasHeightForWidth())
+ self.dialog_topic.setSizePolicy(sizePolicy)
+ font = QtGui.QFont()
+ font.setPointSize(11)
+ font.setBold(True)
+ font.setWeight(75)
+ self.dialog_topic.setFont(font)
+ self.dialog_topic.setAlignment(QtCore.Qt.AlignCenter)
+ self.dialog_topic.setObjectName("dialog_topic")
+ self.verticalLayout_2.addWidget(self.dialog_topic)
+ self.frame = QtWidgets.QFrame(self.dialog_frame)
+ self.frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
+ self.frame.setFrameShadow(QtWidgets.QFrame.Raised)
+ self.frame.setObjectName("frame")
+ self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.frame)
+ self.verticalLayout_5.setObjectName("verticalLayout_5")
+ self.id_label = QtWidgets.QLabel(self.frame)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.id_label.sizePolicy().hasHeightForWidth())
+ self.id_label.setSizePolicy(sizePolicy)
+ self.id_label.setObjectName("id_label")
+ self.verticalLayout_5.addWidget(self.id_label)
+ self.id_layout = QtWidgets.QVBoxLayout()
+ self.id_layout.setObjectName("id_layout")
+ self.id_spinbox = QtWidgets.QDoubleSpinBox(self.frame)
+ self.id_spinbox.setDecimals(0)
+ self.id_spinbox.setMaximum(10000.0)
+ self.id_spinbox.setObjectName("id_spinbox")
+ self.id_layout.addWidget(self.id_spinbox)
+ self.verticalLayout_5.addLayout(self.id_layout)
+ self.description_label = QtWidgets.QLabel(self.frame)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.description_label.sizePolicy().hasHeightForWidth())
+ self.description_label.setSizePolicy(sizePolicy)
+ self.description_label.setObjectName("description_label")
+ self.verticalLayout_5.addWidget(self.description_label)
+ self.description_line = QtWidgets.QLineEdit(self.frame)
+ self.description_line.setObjectName("description_line")
+ self.verticalLayout_5.addWidget(self.description_line)
+ self.verticalLayout_2.addWidget(self.frame)
+ self.verticalLayout.addWidget(self.dialog_frame)
+ self.buttonBox = QtWidgets.QDialogButtonBox(Artist_Mod_Dialog)
+ self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
+ self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
+ self.buttonBox.setObjectName("buttonBox")
+ self.verticalLayout.addWidget(self.buttonBox)
+
+ self.retranslateUi(Artist_Mod_Dialog)
+ self.buttonBox.accepted.connect(Artist_Mod_Dialog.accept)
+ self.buttonBox.rejected.connect(Artist_Mod_Dialog.reject)
+ QtCore.QMetaObject.connectSlotsByName(Artist_Mod_Dialog)
+
+ def retranslateUi(self, Artist_Mod_Dialog):
+ _translate = QtCore.QCoreApplication.translate
+ Artist_Mod_Dialog.setWindowTitle(_translate("Artist_Mod_Dialog", "Dialog"))
+ self.dialog_topic.setText(_translate("Artist_Mod_Dialog", "Artist Topic"))
+ self.id_label.setText(_translate("Artist_Mod_Dialog", "Artist ID:"))
+ self.description_label.setText(_translate("Artist_Mod_Dialog", "Artist Description:"))
+
+
+if __name__ == "__main__":
+ import sys
+ app = QtWidgets.QApplication(sys.argv)
+ Artist_Mod_Dialog = QtWidgets.QDialog()
+ ui = Ui_Artist_Mod_Dialog()
+ ui.setupUi(Artist_Mod_Dialog)
+ Artist_Mod_Dialog.show()
+ sys.exit(app.exec_())
diff --git a/ArtNet/gui/dialogs/artist_modify_dialog/artist_modify_dialog.ui b/ArtNet/gui/dialogs/artist_modify_dialog/artist_modify_dialog.ui
new file mode 100644
index 0000000..85cf331
--- /dev/null
+++ b/ArtNet/gui/dialogs/artist_modify_dialog/artist_modify_dialog.ui
@@ -0,0 +1,154 @@
+
+
+ Artist_Mod_Dialog
+
+
+
+ 0
+ 0
+ 311
+ 205
+
+
+
+ Dialog
+
+
+ -
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 11
+ 75
+ true
+
+
+
+ Artist Topic
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Artist ID:
+
+
+
+ -
+
+
-
+
+
+ 0
+
+
+ 10000.000000000000000
+
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Artist Description:
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+
+
+
+
+
+
+
+
+ buttonBox
+ accepted()
+ Artist_Mod_Dialog
+ accept()
+
+
+ 248
+ 254
+
+
+ 157
+ 274
+
+
+
+
+ buttonBox
+ rejected()
+ Artist_Mod_Dialog
+ reject()
+
+
+ 316
+ 260
+
+
+ 286
+ 274
+
+
+
+
+
diff --git a/ArtNet/gui/dialogs/category_modify_dialog/category_mod_dialog.py b/ArtNet/gui/dialogs/category_modify_dialog/category_mod_dialog.py
new file mode 100644
index 0000000..77f3a2b
--- /dev/null
+++ b/ArtNet/gui/dialogs/category_modify_dialog/category_mod_dialog.py
@@ -0,0 +1,69 @@
+from PyQt5 import QtWidgets
+from PyQt5.QtCore import Qt
+from PyQt5.QtGui import QStandardItem, QStandardItemModel
+
+from ArtNet.gui.dialogs.category_modify_dialog.category_modify_dialog import Ui_category_modify_dialog
+
+
+class CategoryModDialog(QtWidgets.QDialog):
+
+ def __init__(self, parent=None, delete_category: bool = False):
+ super().__init__(parent)
+ self.parent = parent
+
+ self.ui = Ui_category_modify_dialog()
+ self.ui.setupUi(self)
+ self.name_valid: bool = False
+ self.all_categories: list = None
+
+ self.default_style = self.ui.category_name_line.styleSheet()
+
+ self.delete_mode: bool = delete_category
+ if delete_category:
+ self.ui.dialog_topic.setText("Delete Category")
+ else:
+ self.ui.dialog_topic.setText("Create Category")
+
+ self.ui.category_name_line.textChanged.connect(self.on_category_name_change)
+ self.set_all_categories()
+
+ def set_all_categories(self):
+ """
+ Populate the category list with all categories if name line is empty.
+ Otherwise fuzzy search for fitting categories
+ :return:
+ """
+ self.all_categories = self.parent.get_categories(self.ui.category_name_line.text(), all_if_empty=True)
+ categories = []
+ for i in range(len(self.all_categories)):
+ categories.append(self.all_categories[i])
+ self.all_categories = categories
+
+ item_model = QStandardItemModel(self.ui.other_categories_list)
+
+ for category in self.all_categories:
+ item = QStandardItem(category)
+ item_model.appendRow(item)
+
+ self.ui.other_categories_list.setModel(item_model)
+
+ def on_category_name_change(self):
+ # TODO mark input red if it is a taken name (or if it isn't in deletion mode)
+ self.set_all_categories()
+ self.ui.category_name_line.setStyleSheet(self.default_style)
+ self.name_valid = True
+
+ if self.delete_mode:
+ if self.ui.category_name_line.text() not in self.all_categories:
+ self.ui.category_name_line.setStyleSheet("color: red")
+ self.name_valid = False
+ else:
+ if self.ui.category_name_line.text() in self.all_categories:
+ self.ui.category_name_line.setStyleSheet("color: red")
+ self.name_valid = False
+
+ def exec_(self) -> dict:
+ if super(CategoryModDialog, self).exec_() == QtWidgets.QDialog.Rejected or not self.name_valid:
+ return None
+
+ return {"name": self.ui.category_name_line.text()}
diff --git a/ArtNet/gui/dialogs/category_modify_dialog/category_modify_dialog.py b/ArtNet/gui/dialogs/category_modify_dialog/category_modify_dialog.py
new file mode 100644
index 0000000..2bb7e8e
--- /dev/null
+++ b/ArtNet/gui/dialogs/category_modify_dialog/category_modify_dialog.py
@@ -0,0 +1,92 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'dialogs/category_modify_dialog/category_modify_dialog.ui'
+#
+# Created by: PyQt5 UI code generator 5.15.0
+#
+# WARNING: Any manual changes made to this file will be lost when pyuic5 is
+# run again. Do not edit this file unless you know what you are doing.
+
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+
+class Ui_category_modify_dialog(object):
+ def setupUi(self, category_modify_dialog):
+ category_modify_dialog.setObjectName("category_modify_dialog")
+ category_modify_dialog.resize(398, 298)
+ self.verticalLayout = QtWidgets.QVBoxLayout(category_modify_dialog)
+ self.verticalLayout.setObjectName("verticalLayout")
+ self.frame = QtWidgets.QFrame(category_modify_dialog)
+ self.frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
+ self.frame.setFrameShadow(QtWidgets.QFrame.Raised)
+ self.frame.setObjectName("frame")
+ self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.frame)
+ self.verticalLayout_2.setObjectName("verticalLayout_2")
+ self.dialog_topic = QtWidgets.QLabel(self.frame)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.dialog_topic.sizePolicy().hasHeightForWidth())
+ self.dialog_topic.setSizePolicy(sizePolicy)
+ font = QtGui.QFont()
+ font.setPointSize(12)
+ font.setBold(True)
+ font.setWeight(75)
+ self.dialog_topic.setFont(font)
+ self.dialog_topic.setAlignment(QtCore.Qt.AlignCenter)
+ self.dialog_topic.setObjectName("dialog_topic")
+ self.verticalLayout_2.addWidget(self.dialog_topic)
+ self.frame_2 = QtWidgets.QFrame(self.frame)
+ self.frame_2.setFrameShape(QtWidgets.QFrame.StyledPanel)
+ self.frame_2.setFrameShadow(QtWidgets.QFrame.Raised)
+ self.frame_2.setObjectName("frame_2")
+ self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.frame_2)
+ self.verticalLayout_3.setObjectName("verticalLayout_3")
+ self.category_name_label = QtWidgets.QLabel(self.frame_2)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.category_name_label.sizePolicy().hasHeightForWidth())
+ self.category_name_label.setSizePolicy(sizePolicy)
+ font = QtGui.QFont()
+ font.setPointSize(10)
+ font.setBold(True)
+ font.setWeight(75)
+ self.category_name_label.setFont(font)
+ self.category_name_label.setObjectName("category_name_label")
+ self.verticalLayout_3.addWidget(self.category_name_label)
+ self.category_name_line = QtWidgets.QLineEdit(self.frame_2)
+ self.category_name_line.setObjectName("category_name_line")
+ self.verticalLayout_3.addWidget(self.category_name_line)
+ self.other_categories_list = QtWidgets.QListView(self.frame_2)
+ self.other_categories_list.setObjectName("other_categories_list")
+ self.verticalLayout_3.addWidget(self.other_categories_list)
+ self.verticalLayout_2.addWidget(self.frame_2)
+ self.verticalLayout.addWidget(self.frame)
+ self.buttonBox = QtWidgets.QDialogButtonBox(category_modify_dialog)
+ self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
+ self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
+ self.buttonBox.setObjectName("buttonBox")
+ self.verticalLayout.addWidget(self.buttonBox)
+
+ self.retranslateUi(category_modify_dialog)
+ self.buttonBox.accepted.connect(category_modify_dialog.accept)
+ self.buttonBox.rejected.connect(category_modify_dialog.reject)
+ QtCore.QMetaObject.connectSlotsByName(category_modify_dialog)
+
+ def retranslateUi(self, category_modify_dialog):
+ _translate = QtCore.QCoreApplication.translate
+ category_modify_dialog.setWindowTitle(_translate("category_modify_dialog", "Dialog"))
+ self.dialog_topic.setText(_translate("category_modify_dialog", "Category Topic"))
+ self.category_name_label.setText(_translate("category_modify_dialog", "Category Name:"))
+
+
+if __name__ == "__main__":
+ import sys
+ app = QtWidgets.QApplication(sys.argv)
+ category_modify_dialog = QtWidgets.QDialog()
+ ui = Ui_category_modify_dialog()
+ ui.setupUi(category_modify_dialog)
+ category_modify_dialog.show()
+ sys.exit(app.exec_())
diff --git a/ArtNet/gui/dialogs/category_modify_dialog/category_modify_dialog.ui b/ArtNet/gui/dialogs/category_modify_dialog/category_modify_dialog.ui
new file mode 100644
index 0000000..34a447f
--- /dev/null
+++ b/ArtNet/gui/dialogs/category_modify_dialog/category_modify_dialog.ui
@@ -0,0 +1,137 @@
+
+
+ category_modify_dialog
+
+
+
+ 0
+ 0
+ 398
+ 298
+
+
+
+ Dialog
+
+
+ -
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 12
+ 75
+ true
+
+
+
+ Category Topic
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 10
+ 75
+ true
+
+
+
+ Category Name:
+
+
+
+ -
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+
+
+
+
+
+
+
+
+ buttonBox
+ accepted()
+ category_modify_dialog
+ accept()
+
+
+ 248
+ 254
+
+
+ 157
+ 274
+
+
+
+
+ buttonBox
+ rejected()
+ category_modify_dialog
+ reject()
+
+
+ 316
+ 260
+
+
+ 286
+ 274
+
+
+
+
+
diff --git a/ArtNet/gui/dialogs/db_connection_dialog/__init__.py b/ArtNet/gui/dialogs/db_connection_dialog/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/ArtNet/gui/dialogs/db_connection_dialog/db_connection_dialog.py b/ArtNet/gui/dialogs/db_connection_dialog/db_connection_dialog.py
new file mode 100644
index 0000000..0e577ed
--- /dev/null
+++ b/ArtNet/gui/dialogs/db_connection_dialog/db_connection_dialog.py
@@ -0,0 +1,147 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'dialogs/db_connection_dialog/db_connection_dialog.ui'
+#
+# Created by: PyQt5 UI code generator 5.15.0
+#
+# WARNING: Any manual changes made to this file will be lost when pyuic5 is
+# run again. Do not edit this file unless you know what you are doing.
+
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+
+class Ui_DBConnection(object):
+ def setupUi(self, DBConnection):
+ DBConnection.setObjectName("DBConnection")
+ DBConnection.resize(328, 262)
+ self.verticalLayout = QtWidgets.QVBoxLayout(DBConnection)
+ self.verticalLayout.setObjectName("verticalLayout")
+ self.dialog_frame = QtWidgets.QFrame(DBConnection)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.dialog_frame.sizePolicy().hasHeightForWidth())
+ self.dialog_frame.setSizePolicy(sizePolicy)
+ self.dialog_frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
+ self.dialog_frame.setFrameShadow(QtWidgets.QFrame.Raised)
+ self.dialog_frame.setObjectName("dialog_frame")
+ self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.dialog_frame)
+ self.verticalLayout_2.setObjectName("verticalLayout_2")
+ self.title_label = QtWidgets.QLabel(self.dialog_frame)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.title_label.sizePolicy().hasHeightForWidth())
+ self.title_label.setSizePolicy(sizePolicy)
+ font = QtGui.QFont()
+ font.setPointSize(10)
+ font.setBold(True)
+ font.setWeight(75)
+ self.title_label.setFont(font)
+ self.title_label.setLayoutDirection(QtCore.Qt.LeftToRight)
+ self.title_label.setAlignment(QtCore.Qt.AlignCenter)
+ self.title_label.setObjectName("title_label")
+ self.verticalLayout_2.addWidget(self.title_label)
+ self.host_label = QtWidgets.QLabel(self.dialog_frame)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.host_label.sizePolicy().hasHeightForWidth())
+ self.host_label.setSizePolicy(sizePolicy)
+ font = QtGui.QFont()
+ font.setBold(True)
+ font.setWeight(75)
+ self.host_label.setFont(font)
+ self.host_label.setObjectName("host_label")
+ self.verticalLayout_2.addWidget(self.host_label)
+ self.host_line_edit = QtWidgets.QLineEdit(self.dialog_frame)
+ self.host_line_edit.setObjectName("host_line_edit")
+ self.verticalLayout_2.addWidget(self.host_line_edit)
+ self.port_label = QtWidgets.QLabel(self.dialog_frame)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.port_label.sizePolicy().hasHeightForWidth())
+ self.port_label.setSizePolicy(sizePolicy)
+ font = QtGui.QFont()
+ font.setBold(True)
+ font.setWeight(75)
+ self.port_label.setFont(font)
+ self.port_label.setObjectName("port_label")
+ self.verticalLayout_2.addWidget(self.port_label)
+ self.port_line_edit = QtWidgets.QLineEdit(self.dialog_frame)
+ self.port_line_edit.setObjectName("port_line_edit")
+ self.verticalLayout_2.addWidget(self.port_line_edit)
+ self.database_label = QtWidgets.QLabel(self.dialog_frame)
+ font = QtGui.QFont()
+ font.setBold(True)
+ font.setWeight(75)
+ self.database_label.setFont(font)
+ self.database_label.setObjectName("database_label")
+ self.verticalLayout_2.addWidget(self.database_label)
+ self.database_line_edit = QtWidgets.QLineEdit(self.dialog_frame)
+ self.database_line_edit.setObjectName("database_line_edit")
+ self.verticalLayout_2.addWidget(self.database_line_edit)
+ self.user_label = QtWidgets.QLabel(self.dialog_frame)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.user_label.sizePolicy().hasHeightForWidth())
+ self.user_label.setSizePolicy(sizePolicy)
+ font = QtGui.QFont()
+ font.setBold(True)
+ font.setWeight(75)
+ self.user_label.setFont(font)
+ self.user_label.setObjectName("user_label")
+ self.verticalLayout_2.addWidget(self.user_label)
+ self.user_line_edit = QtWidgets.QLineEdit(self.dialog_frame)
+ self.user_line_edit.setObjectName("user_line_edit")
+ self.verticalLayout_2.addWidget(self.user_line_edit)
+ self.password_label = QtWidgets.QLabel(self.dialog_frame)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.password_label.sizePolicy().hasHeightForWidth())
+ self.password_label.setSizePolicy(sizePolicy)
+ font = QtGui.QFont()
+ font.setBold(True)
+ font.setWeight(75)
+ self.password_label.setFont(font)
+ self.password_label.setObjectName("password_label")
+ self.verticalLayout_2.addWidget(self.password_label)
+ self.password_line_edit = QtWidgets.QLineEdit(self.dialog_frame)
+ self.password_line_edit.setEchoMode(QtWidgets.QLineEdit.Password)
+ self.password_line_edit.setObjectName("password_line_edit")
+ self.verticalLayout_2.addWidget(self.password_line_edit)
+ self.verticalLayout.addWidget(self.dialog_frame)
+ self.buttonBox = QtWidgets.QDialogButtonBox(DBConnection)
+ self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
+ self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
+ self.buttonBox.setObjectName("buttonBox")
+ self.verticalLayout.addWidget(self.buttonBox)
+
+ self.retranslateUi(DBConnection)
+ self.buttonBox.accepted.connect(DBConnection.accept)
+ self.buttonBox.rejected.connect(DBConnection.reject)
+ QtCore.QMetaObject.connectSlotsByName(DBConnection)
+
+ def retranslateUi(self, DBConnection):
+ _translate = QtCore.QCoreApplication.translate
+ DBConnection.setWindowTitle(_translate("DBConnection", "Dialog"))
+ self.title_label.setText(_translate("DBConnection", "Edit PostgreSQL Connection"))
+ self.host_label.setText(_translate("DBConnection", "Host:"))
+ self.port_label.setText(_translate("DBConnection", "Port:"))
+ self.database_label.setText(_translate("DBConnection", "Database:"))
+ self.user_label.setText(_translate("DBConnection", "User:"))
+ self.password_label.setText(_translate("DBConnection", "Password:"))
+
+
+if __name__ == "__main__":
+ import sys
+ app = QtWidgets.QApplication(sys.argv)
+ DBConnection = QtWidgets.QDialog()
+ ui = Ui_DBConnection()
+ ui.setupUi(DBConnection)
+ DBConnection.show()
+ sys.exit(app.exec_())
diff --git a/ArtNet/gui/dialogs/db_connection_dialog/db_connection_dialog.ui b/ArtNet/gui/dialogs/db_connection_dialog/db_connection_dialog.ui
new file mode 100644
index 0000000..0b41636
--- /dev/null
+++ b/ArtNet/gui/dialogs/db_connection_dialog/db_connection_dialog.ui
@@ -0,0 +1,216 @@
+
+
+ DBConnection
+
+
+
+ 0
+ 0
+ 328
+ 262
+
+
+
+ Dialog
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 10
+ 75
+ true
+
+
+
+ Qt::LeftToRight
+
+
+ Edit PostgreSQL Connection
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 75
+ true
+
+
+
+ Host:
+
+
+
+ -
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 75
+ true
+
+
+
+ Port:
+
+
+
+ -
+
+
+ -
+
+
+
+ 75
+ true
+
+
+
+ Database:
+
+
+
+ -
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 75
+ true
+
+
+
+ User:
+
+
+
+ -
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 75
+ true
+
+
+
+ Password:
+
+
+
+ -
+
+
+ QLineEdit::Password
+
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+
+
+
+
+
+
+
+
+ buttonBox
+ accepted()
+ DBConnection
+ accept()
+
+
+ 248
+ 254
+
+
+ 157
+ 274
+
+
+
+
+ buttonBox
+ rejected()
+ DBConnection
+ reject()
+
+
+ 316
+ 260
+
+
+ 286
+ 274
+
+
+
+
+
diff --git a/ArtNet/gui/dialogs/db_connection_dialog/db_dialog.py b/ArtNet/gui/dialogs/db_connection_dialog/db_dialog.py
new file mode 100644
index 0000000..79ecde7
--- /dev/null
+++ b/ArtNet/gui/dialogs/db_connection_dialog/db_dialog.py
@@ -0,0 +1,31 @@
+from PyQt5 import QtWidgets
+
+from ArtNet.gui.dialogs.db_connection_dialog.db_connection_dialog import Ui_DBConnection
+
+
+class DBDialog(QtWidgets.QDialog):
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.ui = Ui_DBConnection()
+ self.ui.setupUi(self)
+ self.data = {}
+
+ self.ui.buttonBox.accepted.connect(self.getConnectionDetails)
+
+ def getConnectionDetails(self):
+ self.data = dict()
+
+ self.data["host"] = self.ui.host_line_edit.text()
+ self.data["port"] = self.ui.port_line_edit.text()
+ self.data["database"] = self.ui.database_line_edit.text()
+ self.data["user"] = self.ui.user_line_edit.text()
+ self.data["password"] = self.ui.password_line_edit.text()
+
+ self.accept()
+
+ def exec_(self) -> dict:
+ if super(DBDialog, self).exec_() == QtWidgets.QDialog.Rejected:
+ return None
+ return self.data
+
diff --git a/ArtNet/gui/dialogs/presence_modify_dialog/__init__.py b/ArtNet/gui/dialogs/presence_modify_dialog/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/ArtNet/gui/dialogs/presence_modify_dialog/presence_mod_dialog.py b/ArtNet/gui/dialogs/presence_modify_dialog/presence_mod_dialog.py
new file mode 100644
index 0000000..ecbd557
--- /dev/null
+++ b/ArtNet/gui/dialogs/presence_modify_dialog/presence_mod_dialog.py
@@ -0,0 +1,200 @@
+from PyQt5 import QtWidgets
+from PyQt5.QtCore import Qt
+from PyQt5.QtGui import QStandardItem, QStandardItemModel
+
+from ArtNet.gui.dialogs.presence_modify_dialog.presence_modify_dialog import Ui_PresenceModDialog
+from ArtNet.gui.dialogs.artist_modify_dialog.artist_mod_dialog import ArtistModDialog
+
+
+class PresenceModDialog(QtWidgets.QDialog):
+
+ def __init__(self, parent=None, edit_presence: bool = False):
+ super().__init__(parent)
+ self.parent = parent
+ self.ui = Ui_PresenceModDialog()
+ self.ui.setupUi(self)
+
+ self.curr_artist: tuple = None
+ self.__curr_searched_artists: list = []
+
+ self.data: dict = None
+
+ if edit_presence:
+ self.ui.dialog_topic_label.setText("Edit Presence")
+ self.ui.presence_domain_line.setReadOnly(True)
+ self.ui.presence_name_line.setReadOnly(True)
+ else:
+ self.ui.dialog_topic_label.setText("Create Presence")
+ self.ui.presence_domain_line.setReadOnly(False)
+ self.ui.presence_name_line.setReadOnly(False)
+
+ self.ui.artist_search_line.textChanged.connect(self.on_artist_search_changed)
+
+ self.ui.create_artist_button.clicked.connect(self.on_create_artist_clicked)
+ self.ui.edit_artist_button.clicked.connect(self.on_edit_artist_clicked)
+ self.ui.delete_artist_button.clicked.connect(self.on_delete_artist_clicked)
+
+ def set_presence_name(self, name: str):
+ self.ui.presence_name_line.setText(name)
+
+ def set_presence_domain(self, name: str):
+ self.ui.presence_domain_line.setText(name)
+
+ def on_create_artist_clicked(self):
+ print("Clicked Create artist!")
+ dialog = ArtistModDialog(self)
+
+ data = dialog.exec_()
+ if data is None:
+ return
+
+ self.parent.parent.create_artist(ID=data["id"], description=data["description"])
+
+ def on_edit_artist_clicked(self):
+ print("Clicked Edit artist!")
+
+ def on_delete_artist_clicked(self):
+ print("Clicked Delete artist!")
+
+ if self.curr_artist is None:
+ QtWidgets.QMessageBox.information(self, "No Artist Selected", "There is no artist selected to delete!")
+ return
+
+ answer = QtWidgets.QMessageBox.question(self, "Delete this Artist?",
+ "Do you really want to delete the Artist \"{0}\"?"
+ .format(self.curr_artist[1]))
+ if answer == QtWidgets.QMessageBox.Yes:
+ pass # TODO check if presences are associated & warn
+ presences = self.parent.parent.get_artist_presences(self.curr_artist[0])
+
+ msg = ""
+ affected_art = 0
+ for presence in presences:
+ msg += presence[0]+":"+presence[1] + "\n"
+
+ arts = self.parent.parent.get_presences_art(presence[0], presence[1])
+ affected_art += len(arts)
+
+ answer = QtWidgets.QMessageBox.question(self, "Are you sure?",
+ ("Do you really wish to delete the Artist \"{0}\"?\n" +
+ "following Presences are associated with " +
+ "it and deleted also:\n\n" +
+ "{1}\n" +
+ "This will also remove this presence from following " +
+ "amount of art pieces:" +
+ "\n{2}\n")
+ .format(self.curr_artist[1], msg, affected_art)
+ )
+ if answer == QtWidgets.QMessageBox.Yes:
+ pass
+
+ self.parent.parent.remove_artist(self.curr_artist[0])
+ for presence in presences:
+ self.parent.parent.remove_presence(presence[0], presence[1])
+ self.close()
+
+ else:
+ return
+ else:
+ return
+
+ def on_artist_search_changed(self):
+ if len(self.ui.artist_search_line.text()) == 0: # nothing to search for
+ self.set_artist_result_list([])
+ return
+ artists = self.parent.parent.get_artists(self.ui.artist_search_line.text())
+
+ result = []
+ for ID, desc in artists:
+ result.append(str(ID)+":"+desc)
+
+ self.set_artist_result_list(result)
+
+ def set_artist_result_list(self, artists: list):
+ """
+ Set the list of search result artists.
+ :param artists: [(id, desc)]
+ :return:
+ """
+ item_model = QStandardItemModel(self.ui.artist_result_list)
+
+ self.__curr_searched_artists = []
+ for artist in artists:
+ self.__curr_searched_artists.append(artist)
+
+ item = QStandardItem(artist)
+ item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
+ item.setData(Qt.Unchecked, Qt.CheckStateRole)
+
+ if self.curr_artist is not None:
+ curr_artist = str(self.curr_artist[0]) + ":" + self.curr_artist[1]
+ if artist == curr_artist:
+ item.setCheckState(Qt.Checked)
+
+ item_model.appendRow(item)
+
+ item_model.itemChanged.connect(self.on_search_artist_item_changed)
+ self.ui.artist_result_list.setModel(item_model)
+
+ def set_artist_selected_list(self, artist: tuple):
+ """
+ Set the list of selected artist
+ :param artists: [(id, desc)]
+ :return:
+ """
+ if artist is None:
+ return
+
+ item_model = QStandardItemModel(self.ui.artist_selection_list)
+
+ item = QStandardItem(str(artist[0])+":"+artist[1])
+ item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
+ item.setData(Qt.Unchecked, Qt.CheckStateRole)
+ item.setCheckState(Qt.Checked)
+
+ item_model.appendRow(item)
+
+ item_model.itemChanged.connect(self.on_selected_artist_item_changed)
+ self.ui.artist_selection_list.setModel(item_model)
+
+ def on_search_artist_item_changed(self, item: QStandardItem):
+ artist = item.text().split(":")
+ artist = (artist[0], artist[1])
+ if item.checkState() == Qt.Checked:
+ self.curr_artist = artist
+ elif item.checkState() == Qt.Unchecked:
+ self.curr_artist = None
+ self.set_artist_result_list(self.__curr_searched_artists)
+ self.set_artist_selected_list(self.curr_artist)
+
+ def on_selected_artist_item_changed(self, item: QStandardItem):
+ artist = item.text().split(":")
+ artist = (artist[0].strip(), artist[1].strip())
+ if item.checkState() == Qt.Checked:
+ self.curr_artist = artist
+ elif item.checkState() == Qt.Unchecked:
+ self.curr_artist = artist
+ self.set_artist_result_list(self.__curr_searched_artists)
+ self.set_artist_selected_list(self.curr_artist)
+
+ def collect_presence_details(self):
+ self.data = {
+ "name": self.ui.presence_name_line.text(),
+ "domain": self.ui.presence_domain_line.text(),
+ "artist": self.curr_artist,
+ "link": self.ui.presence_link_list.text(),
+ }
+
+ is_null = True
+ for value in self.data.values():
+ if value is not None and len(value) != 0:
+ is_null = False
+ if is_null:
+ self.data = None
+
+ def exec_(self) -> dict:
+ if super(PresenceModDialog, self).exec_() == QtWidgets.QDialog.Rejected:
+ return None
+ self.collect_presence_details()
+
+ return self.data
diff --git a/ArtNet/gui/dialogs/presence_modify_dialog/presence_modify_dialog.py b/ArtNet/gui/dialogs/presence_modify_dialog/presence_modify_dialog.py
new file mode 100644
index 0000000..460f460
--- /dev/null
+++ b/ArtNet/gui/dialogs/presence_modify_dialog/presence_modify_dialog.py
@@ -0,0 +1,149 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'dialogs/presence_modify_dialog/presence_modify_dialog.ui'
+#
+# Created by: PyQt5 UI code generator 5.15.0
+#
+# WARNING: Any manual changes made to this file will be lost when pyuic5 is
+# run again. Do not edit this file unless you know what you are doing.
+
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+
+class Ui_PresenceModDialog(object):
+ def setupUi(self, PresenceModDialog):
+ PresenceModDialog.setObjectName("PresenceModDialog")
+ PresenceModDialog.resize(376, 405)
+ self.verticalLayout = QtWidgets.QVBoxLayout(PresenceModDialog)
+ self.verticalLayout.setObjectName("verticalLayout")
+ self.frame = QtWidgets.QFrame(PresenceModDialog)
+ self.frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
+ self.frame.setFrameShadow(QtWidgets.QFrame.Raised)
+ self.frame.setObjectName("frame")
+ self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.frame)
+ self.verticalLayout_2.setObjectName("verticalLayout_2")
+ self.dialog_topic_label = QtWidgets.QLabel(self.frame)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.dialog_topic_label.sizePolicy().hasHeightForWidth())
+ self.dialog_topic_label.setSizePolicy(sizePolicy)
+ font = QtGui.QFont()
+ font.setPointSize(11)
+ font.setBold(True)
+ font.setWeight(75)
+ self.dialog_topic_label.setFont(font)
+ self.dialog_topic_label.setAlignment(QtCore.Qt.AlignCenter)
+ self.dialog_topic_label.setObjectName("dialog_topic_label")
+ self.verticalLayout_2.addWidget(self.dialog_topic_label)
+ self.frame_2 = QtWidgets.QFrame(self.frame)
+ self.frame_2.setFrameShape(QtWidgets.QFrame.StyledPanel)
+ self.frame_2.setFrameShadow(QtWidgets.QFrame.Raised)
+ self.frame_2.setObjectName("frame_2")
+ self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.frame_2)
+ self.verticalLayout_3.setSpacing(5)
+ self.verticalLayout_3.setObjectName("verticalLayout_3")
+ self.presence_name_label = QtWidgets.QLabel(self.frame_2)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.presence_name_label.sizePolicy().hasHeightForWidth())
+ self.presence_name_label.setSizePolicy(sizePolicy)
+ font = QtGui.QFont()
+ font.setBold(True)
+ font.setWeight(75)
+ self.presence_name_label.setFont(font)
+ self.presence_name_label.setObjectName("presence_name_label")
+ self.verticalLayout_3.addWidget(self.presence_name_label)
+ self.presence_name_line = QtWidgets.QLineEdit(self.frame_2)
+ self.presence_name_line.setObjectName("presence_name_line")
+ self.verticalLayout_3.addWidget(self.presence_name_line)
+ self.presence_domain_label = QtWidgets.QLabel(self.frame_2)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.presence_domain_label.sizePolicy().hasHeightForWidth())
+ self.presence_domain_label.setSizePolicy(sizePolicy)
+ font = QtGui.QFont()
+ font.setBold(True)
+ font.setWeight(75)
+ self.presence_domain_label.setFont(font)
+ self.presence_domain_label.setObjectName("presence_domain_label")
+ self.verticalLayout_3.addWidget(self.presence_domain_label)
+ self.presence_domain_line = QtWidgets.QLineEdit(self.frame_2)
+ self.presence_domain_line.setObjectName("presence_domain_line")
+ self.verticalLayout_3.addWidget(self.presence_domain_line)
+ self.presence_link_label = QtWidgets.QLabel(self.frame_2)
+ font = QtGui.QFont()
+ font.setBold(True)
+ font.setWeight(75)
+ self.presence_link_label.setFont(font)
+ self.presence_link_label.setObjectName("presence_link_label")
+ self.verticalLayout_3.addWidget(self.presence_link_label)
+ self.presence_link_list = QtWidgets.QLineEdit(self.frame_2)
+ self.presence_link_list.setObjectName("presence_link_list")
+ self.verticalLayout_3.addWidget(self.presence_link_list)
+ self.artist_label = QtWidgets.QLabel(self.frame_2)
+ font = QtGui.QFont()
+ font.setPointSize(10)
+ font.setBold(True)
+ font.setWeight(75)
+ self.artist_label.setFont(font)
+ self.artist_label.setObjectName("artist_label")
+ self.verticalLayout_3.addWidget(self.artist_label)
+ self.artist_search_line = QtWidgets.QLineEdit(self.frame_2)
+ self.artist_search_line.setObjectName("artist_search_line")
+ self.verticalLayout_3.addWidget(self.artist_search_line)
+ self.artist_result_list = QtWidgets.QListView(self.frame_2)
+ self.artist_result_list.setObjectName("artist_result_list")
+ self.verticalLayout_3.addWidget(self.artist_result_list)
+ self.artist_selection_list = QtWidgets.QListView(self.frame_2)
+ self.artist_selection_list.setObjectName("artist_selection_list")
+ self.verticalLayout_3.addWidget(self.artist_selection_list)
+ self.artist_edit_layout = QtWidgets.QHBoxLayout()
+ self.artist_edit_layout.setObjectName("artist_edit_layout")
+ self.create_artist_button = QtWidgets.QPushButton(self.frame_2)
+ self.create_artist_button.setObjectName("create_artist_button")
+ self.artist_edit_layout.addWidget(self.create_artist_button)
+ self.edit_artist_button = QtWidgets.QPushButton(self.frame_2)
+ self.edit_artist_button.setObjectName("edit_artist_button")
+ self.artist_edit_layout.addWidget(self.edit_artist_button)
+ self.delete_artist_button = QtWidgets.QPushButton(self.frame_2)
+ self.delete_artist_button.setObjectName("delete_artist_button")
+ self.artist_edit_layout.addWidget(self.delete_artist_button)
+ self.verticalLayout_3.addLayout(self.artist_edit_layout)
+ self.verticalLayout_2.addWidget(self.frame_2)
+ self.verticalLayout.addWidget(self.frame)
+ self.buttonBox = QtWidgets.QDialogButtonBox(PresenceModDialog)
+ self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
+ self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
+ self.buttonBox.setObjectName("buttonBox")
+ self.verticalLayout.addWidget(self.buttonBox)
+
+ self.retranslateUi(PresenceModDialog)
+ self.buttonBox.accepted.connect(PresenceModDialog.accept)
+ self.buttonBox.rejected.connect(PresenceModDialog.reject)
+ QtCore.QMetaObject.connectSlotsByName(PresenceModDialog)
+
+ def retranslateUi(self, PresenceModDialog):
+ _translate = QtCore.QCoreApplication.translate
+ PresenceModDialog.setWindowTitle(_translate("PresenceModDialog", "Dialog"))
+ self.dialog_topic_label.setText(_translate("PresenceModDialog", "Presence Topic"))
+ self.presence_name_label.setText(_translate("PresenceModDialog", "Presence Name:"))
+ self.presence_domain_label.setText(_translate("PresenceModDialog", "Presence Domain:"))
+ self.presence_link_label.setText(_translate("PresenceModDialog", "Presence Link:"))
+ self.artist_label.setText(_translate("PresenceModDialog", "Artist:"))
+ self.create_artist_button.setText(_translate("PresenceModDialog", "Create Artist"))
+ self.edit_artist_button.setText(_translate("PresenceModDialog", "Edit Artist"))
+ self.delete_artist_button.setText(_translate("PresenceModDialog", "Delete Artist"))
+
+
+if __name__ == "__main__":
+ import sys
+ app = QtWidgets.QApplication(sys.argv)
+ PresenceModDialog = QtWidgets.QDialog()
+ ui = Ui_PresenceModDialog()
+ ui.setupUi(PresenceModDialog)
+ PresenceModDialog.show()
+ sys.exit(app.exec_())
diff --git a/ArtNet/gui/dialogs/presence_modify_dialog/presence_modify_dialog.ui b/ArtNet/gui/dialogs/presence_modify_dialog/presence_modify_dialog.ui
new file mode 100644
index 0000000..d805953
--- /dev/null
+++ b/ArtNet/gui/dialogs/presence_modify_dialog/presence_modify_dialog.ui
@@ -0,0 +1,222 @@
+
+
+ PresenceModDialog
+
+
+
+ 0
+ 0
+ 376
+ 405
+
+
+
+ Dialog
+
+
+ -
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 11
+ 75
+ true
+
+
+
+ Presence Topic
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
+ 5
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 75
+ true
+
+
+
+ Presence Name:
+
+
+
+ -
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 75
+ true
+
+
+
+ Presence Domain:
+
+
+
+ -
+
+
+ -
+
+
+
+ 75
+ true
+
+
+
+ Presence Link:
+
+
+
+ -
+
+
+ -
+
+
+
+ 10
+ 75
+ true
+
+
+
+ Artist:
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
-
+
+
+ Create Artist
+
+
+
+ -
+
+
+ Edit Artist
+
+
+
+ -
+
+
+ Delete Artist
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+
+
+
+
+
+
+
+
+ buttonBox
+ accepted()
+ PresenceModDialog
+ accept()
+
+
+ 248
+ 254
+
+
+ 157
+ 274
+
+
+
+
+ buttonBox
+ rejected()
+ PresenceModDialog
+ reject()
+
+
+ 316
+ 260
+
+
+ 286
+ 274
+
+
+
+
+
diff --git a/ArtNet/gui/dialogs/presence_selection_dialog/__init__.py b/ArtNet/gui/dialogs/presence_selection_dialog/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/ArtNet/gui/dialogs/presence_selection_dialog/presence_select_dialog.py b/ArtNet/gui/dialogs/presence_selection_dialog/presence_select_dialog.py
new file mode 100644
index 0000000..8910cdb
--- /dev/null
+++ b/ArtNet/gui/dialogs/presence_selection_dialog/presence_select_dialog.py
@@ -0,0 +1,70 @@
+from PyQt5 import QtWidgets
+from PyQt5.QtCore import Qt
+from PyQt5.QtGui import QStandardItem, QStandardItemModel
+
+from ArtNet.gui.dialogs.presence_selection_dialog.presence_selection_dialog import Ui_presence_selection_dialog
+
+
+class PresenceSelectDialog(QtWidgets.QDialog):
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.parent = parent
+ self.ui = Ui_presence_selection_dialog()
+ self.ui.setupUi(self)
+
+ self.__curr_presence: list = None
+ self.__curr_search_result: list = None
+
+ self.ui.presence_name_search_line.textChanged.connect(self.on_search_change)
+ self.ui.presence_domain_search_line.textChanged.connect(self.on_search_change)
+
+ def set_search_result_list(self, result: list):
+ self.__curr_search_result = result
+ item_model = QStandardItemModel(self.ui.presence_search_list)
+
+ for presence in result:
+ item = QStandardItem(presence)
+ item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
+ item.setData(Qt.Unchecked, Qt.CheckStateRole)
+
+ if self.__curr_presence is not None:
+ curr_presence = self.__curr_presence[0] + ":" + self.__curr_presence[1]
+ if presence == curr_presence:
+ item.setCheckState(Qt.Checked)
+
+ item_model.appendRow(item)
+
+ item_model.itemChanged.connect(self.on_search_presence_item_changed)
+ self.ui.presence_search_list.setModel(item_model)
+
+ def on_search_change(self):
+ name = self.ui.presence_name_search_line.text()
+ domain = self.ui.presence_domain_search_line.text()
+
+ if len(name) == 0 and len(domain) == 0: # nothing to search for
+ self.set_search_result_list([])
+ return
+ presences = self.parent.parent.get_authors(name, domain)
+
+ result = []
+ for ID, desc in presences:
+ result.append(ID+":"+desc)
+
+ self.set_search_result_list(result)
+
+ def on_search_presence_item_changed(self, item: QStandardItem):
+ name, domain = item.text().split(":")
+ if item.checkState() == Qt.Checked:
+ self.__curr_presence = self.parent.parent.get_presence(name, domain)
+ if self.__curr_presence is not None:
+ self.__curr_presence = self.__curr_presence[0] # unpack from list
+ elif item.checkState() == Qt.Unchecked:
+ self.__curr_presence = None
+ self.set_search_result_list(self.__curr_search_result)
+
+ def exec_(self) -> list:
+ if super(PresenceSelectDialog, self).exec_() == QtWidgets.QDialog.Rejected:
+ return None
+
+ return self.__curr_presence
diff --git a/ArtNet/gui/dialogs/presence_selection_dialog/presence_selection_dialog.py b/ArtNet/gui/dialogs/presence_selection_dialog/presence_selection_dialog.py
new file mode 100644
index 0000000..7bf2683
--- /dev/null
+++ b/ArtNet/gui/dialogs/presence_selection_dialog/presence_selection_dialog.py
@@ -0,0 +1,101 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'dialogs/presence_selection_dialog/presence_selection_dialog.ui'
+#
+# Created by: PyQt5 UI code generator 5.15.0
+#
+# WARNING: Any manual changes made to this file will be lost when pyuic5 is
+# run again. Do not edit this file unless you know what you are doing.
+
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+
+class Ui_presence_selection_dialog(object):
+ def setupUi(self, presence_selection_dialog):
+ presence_selection_dialog.setObjectName("presence_selection_dialog")
+ presence_selection_dialog.resize(330, 260)
+ self.verticalLayout = QtWidgets.QVBoxLayout(presence_selection_dialog)
+ self.verticalLayout.setObjectName("verticalLayout")
+ self.frame = QtWidgets.QFrame(presence_selection_dialog)
+ self.frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
+ self.frame.setFrameShadow(QtWidgets.QFrame.Raised)
+ self.frame.setObjectName("frame")
+ self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.frame)
+ self.verticalLayout_2.setObjectName("verticalLayout_2")
+ self.label = QtWidgets.QLabel(self.frame)
+ font = QtGui.QFont()
+ font.setPointSize(11)
+ font.setBold(True)
+ font.setWeight(75)
+ self.label.setFont(font)
+ self.label.setAlignment(QtCore.Qt.AlignCenter)
+ self.label.setObjectName("label")
+ self.verticalLayout_2.addWidget(self.label)
+ self.frame_2 = QtWidgets.QFrame(self.frame)
+ self.frame_2.setFrameShape(QtWidgets.QFrame.StyledPanel)
+ self.frame_2.setFrameShadow(QtWidgets.QFrame.Raised)
+ self.frame_2.setObjectName("frame_2")
+ self.horizontalLayout = QtWidgets.QHBoxLayout(self.frame_2)
+ self.horizontalLayout.setObjectName("horizontalLayout")
+ self.verticalLayout_4 = QtWidgets.QVBoxLayout()
+ self.verticalLayout_4.setObjectName("verticalLayout_4")
+ self.label_2 = QtWidgets.QLabel(self.frame_2)
+ font = QtGui.QFont()
+ font.setPointSize(10)
+ font.setBold(True)
+ font.setWeight(75)
+ self.label_2.setFont(font)
+ self.label_2.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter)
+ self.label_2.setObjectName("label_2")
+ self.verticalLayout_4.addWidget(self.label_2)
+ self.presence_name_search_line = QtWidgets.QLineEdit(self.frame_2)
+ self.presence_name_search_line.setObjectName("presence_name_search_line")
+ self.verticalLayout_4.addWidget(self.presence_name_search_line)
+ self.horizontalLayout.addLayout(self.verticalLayout_4)
+ self.verticalLayout_3 = QtWidgets.QVBoxLayout()
+ self.verticalLayout_3.setObjectName("verticalLayout_3")
+ self.label_3 = QtWidgets.QLabel(self.frame_2)
+ font = QtGui.QFont()
+ font.setPointSize(10)
+ font.setBold(True)
+ font.setWeight(75)
+ self.label_3.setFont(font)
+ self.label_3.setObjectName("label_3")
+ self.verticalLayout_3.addWidget(self.label_3)
+ self.presence_domain_search_line = QtWidgets.QLineEdit(self.frame_2)
+ self.presence_domain_search_line.setObjectName("presence_domain_search_line")
+ self.verticalLayout_3.addWidget(self.presence_domain_search_line)
+ self.horizontalLayout.addLayout(self.verticalLayout_3)
+ self.verticalLayout_2.addWidget(self.frame_2)
+ self.presence_search_list = QtWidgets.QListView(self.frame)
+ self.presence_search_list.setObjectName("presence_search_list")
+ self.verticalLayout_2.addWidget(self.presence_search_list)
+ self.verticalLayout.addWidget(self.frame)
+ self.buttonBox = QtWidgets.QDialogButtonBox(presence_selection_dialog)
+ self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
+ self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
+ self.buttonBox.setObjectName("buttonBox")
+ self.verticalLayout.addWidget(self.buttonBox)
+
+ self.retranslateUi(presence_selection_dialog)
+ self.buttonBox.accepted.connect(presence_selection_dialog.accept)
+ self.buttonBox.rejected.connect(presence_selection_dialog.reject)
+ QtCore.QMetaObject.connectSlotsByName(presence_selection_dialog)
+
+ def retranslateUi(self, presence_selection_dialog):
+ _translate = QtCore.QCoreApplication.translate
+ presence_selection_dialog.setWindowTitle(_translate("presence_selection_dialog", "Dialog"))
+ self.label.setText(_translate("presence_selection_dialog", "Select Presence"))
+ self.label_2.setText(_translate("presence_selection_dialog", "Name:"))
+ self.label_3.setText(_translate("presence_selection_dialog", "Domain:"))
+
+
+if __name__ == "__main__":
+ import sys
+ app = QtWidgets.QApplication(sys.argv)
+ presence_selection_dialog = QtWidgets.QDialog()
+ ui = Ui_presence_selection_dialog()
+ ui.setupUi(presence_selection_dialog)
+ presence_selection_dialog.show()
+ sys.exit(app.exec_())
diff --git a/ArtNet/gui/dialogs/presence_selection_dialog/presence_selection_dialog.ui b/ArtNet/gui/dialogs/presence_selection_dialog/presence_selection_dialog.ui
new file mode 100644
index 0000000..c4f847a
--- /dev/null
+++ b/ArtNet/gui/dialogs/presence_selection_dialog/presence_selection_dialog.ui
@@ -0,0 +1,153 @@
+
+
+ presence_selection_dialog
+
+
+
+ 0
+ 0
+ 330
+ 260
+
+
+
+ Dialog
+
+
+ -
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
-
+
+
+
+ 11
+ 75
+ true
+
+
+
+ Select Presence
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
-
+
+
-
+
+
+
+ 10
+ 75
+ true
+
+
+
+ Name:
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
+
+
+
+ -
+
+
+
+
+ -
+
+
-
+
+
+
+ 10
+ 75
+ true
+
+
+
+ Domain:
+
+
+
+ -
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+
+
+
+
+
+
+
+
+ buttonBox
+ accepted()
+ presence_selection_dialog
+ accept()
+
+
+ 248
+ 254
+
+
+ 157
+ 274
+
+
+
+
+ buttonBox
+ rejected()
+ presence_selection_dialog
+ reject()
+
+
+ 316
+ 260
+
+
+ 286
+ 274
+
+
+
+
+
diff --git a/ArtNet/gui/dialogs/tag_import_dialog/tag_imp_dialog.py b/ArtNet/gui/dialogs/tag_import_dialog/tag_imp_dialog.py
new file mode 100644
index 0000000..bab2e06
--- /dev/null
+++ b/ArtNet/gui/dialogs/tag_import_dialog/tag_imp_dialog.py
@@ -0,0 +1,122 @@
+from PyQt5 import QtWidgets
+from PyQt5.QtGui import QStandardItemModel, QStandardItem
+from PyQt5.QtCore import Qt
+
+from ArtNet.gui.dialogs.tag_import_dialog.tag_import_dialog import Ui_Dialog
+
+
+class TagImportDialog(QtWidgets.QDialog):
+
+ def __init__(self, parent=None):
+ super().__init__(parent=parent)
+ self.parent = parent
+ self.ui = Ui_Dialog()
+ self.ui.setupUi(self)
+ self.ui.import_all_button.clicked.connect(self.on_import_all_clicked)
+ self.ui.remove_all_button.clicked.connect(self.on_remove_all_clicked)
+
+ self.to_import = []
+ self.not_import = []
+
+ self.data = dict()
+
+ def set_import_tag_list(self, tags: list):
+ """
+ Set the tags in the imported list
+ :param tags:
+ :return:
+ """
+ set_checked = True
+ item_model = QStandardItemModel(self.ui.import_list)
+ self.data = tags
+
+ for tag in tags:
+ item = QStandardItem(tag)
+ item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
+ item.setData(Qt.Unchecked, Qt.CheckStateRole)
+ if set_checked:
+ item.setCheckState(Qt.Checked)
+
+ item_model.appendRow(item)
+
+ item_model.itemChanged.connect(self.on_import_item_changed)
+ self.ui.import_list.setModel(item_model)
+
+ def set_detected_artists(self, artists: list):
+ """
+ Set the label for detected artists
+ :param artists:
+ :return:
+ """
+ s = ""
+ for artist in artists:
+ s += artist + " | "
+ s = s[:-3]
+
+ self.ui.detected_artists_label.setText(s)
+
+ def set_used_link(self, link: str):
+ """
+ Set the label to display the predicted link
+ :param link:
+ :return:
+ """
+ self.ui.used_link_label.setText(link)
+
+ def set_ignore_tag_list(self, tags: list):
+ """
+ Set the tags in the ignore list
+ :param tags:
+ :return:
+ """
+ set_checked = False
+ item_model = QStandardItemModel(self.ui.ignored_list)
+
+ for tag in tags:
+ item = QStandardItem(tag)
+ item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
+ item.setData(Qt.Unchecked, Qt.CheckStateRole)
+ if set_checked:
+ item.setCheckState(Qt.Checked)
+
+ item_model.appendRow(item)
+
+ item_model.itemChanged.connect(self.on_ignore_item_changed)
+ self.ui.ignored_list.setModel(item_model)
+
+ def on_ignore_item_changed(self, item: QStandardItem):
+ if item.checkState() == Qt.Checked:
+ tag = item.text()
+ self.to_import.append(tag)
+ self.not_import.remove(tag)
+
+ self.set_import_tag_list(self.to_import)
+ self.set_ignore_tag_list(self.not_import)
+
+ def on_import_item_changed(self, item: QStandardItem):
+ if item.checkState() == Qt.Unchecked:
+ tag = item.text()
+ self.to_import.remove(tag)
+ self.not_import.append(tag)
+
+ self.set_import_tag_list(self.to_import)
+ self.set_ignore_tag_list(self.not_import)
+
+ def on_import_all_clicked(self):
+ self.to_import += self.not_import
+ self.not_import = []
+
+ self.set_import_tag_list(self.to_import)
+ self.set_ignore_tag_list(self.not_import)
+
+ def on_remove_all_clicked(self):
+ self.not_import += self.to_import
+ self.to_import = []
+
+ self.set_import_tag_list(self.to_import)
+ self.set_ignore_tag_list(self.not_import)
+
+ def exec_(self) -> int:
+ if super(TagImportDialog, self).exec_() == QtWidgets.QDialog.Rejected:
+ return None
+ return self.data
diff --git a/ArtNet/gui/dialogs/tag_import_dialog/tag_import_dialog.py b/ArtNet/gui/dialogs/tag_import_dialog/tag_import_dialog.py
new file mode 100644
index 0000000..6798c28
--- /dev/null
+++ b/ArtNet/gui/dialogs/tag_import_dialog/tag_import_dialog.py
@@ -0,0 +1,143 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'dialogs/tag_import_dialog/tag_import_dialog.ui'
+#
+# Created by: PyQt5 UI code generator 5.15.0
+#
+# WARNING: Any manual changes made to this file will be lost when pyuic5 is
+# run again. Do not edit this file unless you know what you are doing.
+
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+
+class Ui_Dialog(object):
+ def setupUi(self, Dialog):
+ Dialog.setObjectName("Dialog")
+ Dialog.resize(775, 300)
+ self.verticalLayout = QtWidgets.QVBoxLayout(Dialog)
+ self.verticalLayout.setObjectName("verticalLayout")
+ self.label = QtWidgets.QLabel(Dialog)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth())
+ self.label.setSizePolicy(sizePolicy)
+ font = QtGui.QFont()
+ font.setPointSize(12)
+ font.setBold(True)
+ font.setWeight(75)
+ self.label.setFont(font)
+ self.label.setAlignment(QtCore.Qt.AlignCenter)
+ self.label.setObjectName("label")
+ self.verticalLayout.addWidget(self.label)
+ self.frame_4 = QtWidgets.QFrame(Dialog)
+ self.frame_4.setFrameShape(QtWidgets.QFrame.StyledPanel)
+ self.frame_4.setFrameShadow(QtWidgets.QFrame.Raised)
+ self.frame_4.setObjectName("frame_4")
+ self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.frame_4)
+ self.verticalLayout_4.setObjectName("verticalLayout_4")
+ self.label_4 = QtWidgets.QLabel(self.frame_4)
+ font = QtGui.QFont()
+ font.setPointSize(9)
+ font.setBold(True)
+ font.setWeight(75)
+ self.label_4.setFont(font)
+ self.label_4.setAlignment(QtCore.Qt.AlignCenter)
+ self.label_4.setObjectName("label_4")
+ self.verticalLayout_4.addWidget(self.label_4)
+ self.detected_artists_label = QtWidgets.QLabel(self.frame_4)
+ font = QtGui.QFont()
+ font.setPointSize(8)
+ self.detected_artists_label.setFont(font)
+ self.detected_artists_label.setAlignment(QtCore.Qt.AlignCenter)
+ self.detected_artists_label.setObjectName("detected_artists_label")
+ self.verticalLayout_4.addWidget(self.detected_artists_label)
+ self.used_link_label = QtWidgets.QLabel(self.frame_4)
+ self.used_link_label.setAlignment(QtCore.Qt.AlignCenter)
+ self.used_link_label.setObjectName("used_link_label")
+ self.verticalLayout_4.addWidget(self.used_link_label)
+ self.verticalLayout.addWidget(self.frame_4)
+ self.frame = QtWidgets.QFrame(Dialog)
+ self.frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
+ self.frame.setFrameShadow(QtWidgets.QFrame.Raised)
+ self.frame.setObjectName("frame")
+ self.horizontalLayout = QtWidgets.QHBoxLayout(self.frame)
+ self.horizontalLayout.setObjectName("horizontalLayout")
+ self.frame_2 = QtWidgets.QFrame(self.frame)
+ self.frame_2.setFrameShape(QtWidgets.QFrame.StyledPanel)
+ self.frame_2.setFrameShadow(QtWidgets.QFrame.Raised)
+ self.frame_2.setObjectName("frame_2")
+ self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.frame_2)
+ self.verticalLayout_3.setObjectName("verticalLayout_3")
+ self.label_2 = QtWidgets.QLabel(self.frame_2)
+ font = QtGui.QFont()
+ font.setPointSize(10)
+ font.setBold(True)
+ font.setWeight(75)
+ self.label_2.setFont(font)
+ self.label_2.setObjectName("label_2")
+ self.verticalLayout_3.addWidget(self.label_2)
+ self.import_list = QtWidgets.QListView(self.frame_2)
+ self.import_list.setMinimumSize(QtCore.QSize(300, 0))
+ self.import_list.setObjectName("import_list")
+ self.verticalLayout_3.addWidget(self.import_list)
+ self.remove_all_button = QtWidgets.QPushButton(self.frame_2)
+ self.remove_all_button.setObjectName("remove_all_button")
+ self.verticalLayout_3.addWidget(self.remove_all_button)
+ self.horizontalLayout.addWidget(self.frame_2)
+ self.frame_3 = QtWidgets.QFrame(self.frame)
+ self.frame_3.setFrameShape(QtWidgets.QFrame.StyledPanel)
+ self.frame_3.setFrameShadow(QtWidgets.QFrame.Raised)
+ self.frame_3.setObjectName("frame_3")
+ self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.frame_3)
+ self.verticalLayout_2.setObjectName("verticalLayout_2")
+ self.label_3 = QtWidgets.QLabel(self.frame_3)
+ font = QtGui.QFont()
+ font.setPointSize(10)
+ font.setBold(True)
+ font.setWeight(75)
+ self.label_3.setFont(font)
+ self.label_3.setObjectName("label_3")
+ self.verticalLayout_2.addWidget(self.label_3)
+ self.ignored_list = QtWidgets.QListView(self.frame_3)
+ self.ignored_list.setMinimumSize(QtCore.QSize(300, 0))
+ self.ignored_list.setObjectName("ignored_list")
+ self.verticalLayout_2.addWidget(self.ignored_list)
+ self.import_all_button = QtWidgets.QPushButton(self.frame_3)
+ self.import_all_button.setObjectName("import_all_button")
+ self.verticalLayout_2.addWidget(self.import_all_button)
+ self.horizontalLayout.addWidget(self.frame_3)
+ self.verticalLayout.addWidget(self.frame)
+ self.buttonBox = QtWidgets.QDialogButtonBox(Dialog)
+ self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
+ self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
+ self.buttonBox.setObjectName("buttonBox")
+ self.verticalLayout.addWidget(self.buttonBox)
+
+ self.retranslateUi(Dialog)
+ self.buttonBox.accepted.connect(Dialog.accept)
+ self.buttonBox.rejected.connect(Dialog.reject)
+ QtCore.QMetaObject.connectSlotsByName(Dialog)
+
+ def retranslateUi(self, Dialog):
+ _translate = QtCore.QCoreApplication.translate
+ Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
+ self.label.setText(_translate("Dialog", "Detected Tags"))
+ self.label_4.setText(_translate("Dialog", "Detected Artists"))
+ self.detected_artists_label.setText(_translate("Dialog", "(None)"))
+ self.used_link_label.setText(_translate("Dialog", "-No Link Used-"))
+ self.label_2.setText(_translate("Dialog", "To be Imported:"))
+ self.remove_all_button.setText(_translate("Dialog", "Remove All"))
+ self.label_3.setText(_translate("Dialog", "To Be Ignored:"))
+ self.import_all_button.setText(_translate("Dialog", "Import All"))
+
+
+if __name__ == "__main__":
+ import sys
+ app = QtWidgets.QApplication(sys.argv)
+ Dialog = QtWidgets.QDialog()
+ ui = Ui_Dialog()
+ ui.setupUi(Dialog)
+ Dialog.show()
+ sys.exit(app.exec_())
diff --git a/ArtNet/gui/dialogs/tag_import_dialog/tag_import_dialog.ui b/ArtNet/gui/dialogs/tag_import_dialog/tag_import_dialog.ui
new file mode 100644
index 0000000..ae00ada
--- /dev/null
+++ b/ArtNet/gui/dialogs/tag_import_dialog/tag_import_dialog.ui
@@ -0,0 +1,239 @@
+
+
+ Dialog
+
+
+
+ 0
+ 0
+ 775
+ 300
+
+
+
+ Dialog
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 12
+ 75
+ true
+
+
+
+ Detected Tags
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
-
+
+
+
+ 9
+ 75
+ true
+
+
+
+ Detected Artists
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+
+ 8
+
+
+
+ (None)
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+ -No Link Used-
+
+
+ Qt::AlignCenter
+
+
+
+
+
+
+ -
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
-
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
-
+
+
+
+ 10
+ 75
+ true
+
+
+
+ To be Imported:
+
+
+
+ -
+
+
+
+ 300
+ 0
+
+
+
+
+ -
+
+
+ Remove All
+
+
+
+
+
+
+ -
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
-
+
+
+
+ 10
+ 75
+ true
+
+
+
+ To Be Ignored:
+
+
+
+ -
+
+
+
+ 300
+ 0
+
+
+
+
+ -
+
+
+ Import All
+
+
+
+
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+
+
+
+
+
+
+
+
+ buttonBox
+ accepted()
+ Dialog
+ accept()
+
+
+ 248
+ 254
+
+
+ 157
+ 274
+
+
+
+
+ buttonBox
+ rejected()
+ Dialog
+ reject()
+
+
+ 316
+ 260
+
+
+ 286
+ 274
+
+
+
+
+
diff --git a/ArtNet/gui/dialogs/tag_modify_dialog/__init__.py b/ArtNet/gui/dialogs/tag_modify_dialog/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/ArtNet/gui/dialogs/tag_modify_dialog/tag_mod_dialog.py b/ArtNet/gui/dialogs/tag_modify_dialog/tag_mod_dialog.py
new file mode 100644
index 0000000..a315293
--- /dev/null
+++ b/ArtNet/gui/dialogs/tag_modify_dialog/tag_mod_dialog.py
@@ -0,0 +1,285 @@
+from PyQt5 import QtWidgets
+from PyQt5.QtGui import QStandardItemModel, QStandardItem
+from PyQt5.QtCore import Qt
+
+from ArtNet.gui.dialogs.tag_modify_dialog.tag_modify_dialog import Ui_TagModify
+
+
+class TagModifyDialog(QtWidgets.QDialog):
+
+ def __init__(self, parent=None, create_tag: bool = True):
+ super().__init__(parent)
+ self.parent = parent
+ self.ui = Ui_TagModify()
+ self.ui.setupUi(self)
+
+ self.data = dict()
+ self.original_tag_name: str = None
+ self.create_tag = create_tag
+
+ self.alias_selection: list = None
+ self.implication_selection: list = None
+ self.tag_alias_search_result: list = None
+ self.tag_implication_search_result: list = None
+ self.category_selection: str = None
+
+ if create_tag:
+ self.ui.dialog_topic.setText("Create Tag")
+ else:
+ self.ui.dialog_topic.setText("Edit Tag")
+ self.ui.tag_name_line.setReadOnly(False)
+
+ self.ui.buttonBox.accepted.connect(self.getTagDetails)
+
+ self.ui.tag_alias_search_line.textChanged.connect(self.on_tag_alias_search_line_changed)
+ self.ui.tag_implication_search_line.textChanged.connect(self.on_tag_implication_search_line_changed)
+ self.set_all_categories()
+
+ def set_search_alias_tags(self, aliases: list, set_checked: bool = False):
+ """
+ Set the tags in the search result list to tags
+ :param aliases:
+ :param set_checked:
+ :return:
+ """
+ if aliases is None:
+ return
+ item_model = QStandardItemModel(self.ui.tag_alias_search_list)
+
+ for tag in aliases:
+ tag = tag[0].strip()
+ if tag == self.ui.tag_name_line.text():
+ continue
+ item = QStandardItem(tag)
+ item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
+ item.setData(Qt.Unchecked, Qt.CheckStateRole)
+
+ if set_checked:
+ item.setCheckState(Qt.Checked)
+
+ item_model.appendRow(item)
+
+ item_model.itemChanged.connect(self.on_search_alias_tag_item_changed)
+ self.ui.tag_alias_search_list.setModel(item_model)
+
+ def set_search_implicated_tags(self, implications: list, set_checked: bool=False):
+ """
+ Set the tags in the search result list to tags
+ :param implications:
+ :param set_checked:
+ :return:
+ """
+ item_model = QStandardItemModel(self.ui.tag_implication_search_list)
+
+ for tag in implications:
+ tag = tag[0].strip()
+ if tag == self.ui.tag_name_line.text():
+ continue
+ item = QStandardItem(tag)
+ item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
+ item.setData(Qt.Unchecked, Qt.CheckStateRole)
+ if set_checked:
+ item.setCheckState(Qt.Checked)
+
+ item_model.appendRow(item)
+
+ item_model.itemChanged.connect(self.on_search_implicated_tag_item_changed)
+ self.ui.tag_implication_search_list.setModel(item_model)
+
+ def set_all_categories(self):
+ """
+ Populate the categories list and only check the one selection
+ :return:
+ """
+ categories = self.parent.get_categories("", all_if_empty=True)
+ item_model = QStandardItemModel(self.ui.category_list)
+
+ for category in categories:
+ category = category.strip()
+
+ item = QStandardItem(category)
+ item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
+ item.setData(Qt.Unchecked, Qt.CheckStateRole)
+
+ if category == self.category_selection:
+ item.setCheckState(Qt.Checked)
+
+ item_model.appendRow(item)
+
+ item_model.itemChanged.connect(self.on_category_item_changed)
+ self.ui.category_list.setModel(item_model)
+
+ def set_selected_alias_tags(self, aliases: list, set_checked: bool=False):
+ """
+ Set the tags in the search result list to tags
+ :param aliases:
+ :param set_checked:
+ :return:
+ """
+ item_model = QStandardItemModel(self.ui.tag_alias_selection_list)
+
+ for tag in aliases:
+ tag = tag.strip()
+ if tag == self.ui.tag_name_line.text():
+ continue
+ item = QStandardItem(tag)
+ item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
+ item.setData(Qt.Unchecked, Qt.CheckStateRole)
+
+ if set_checked:
+ item.setCheckState(Qt.Checked)
+
+ item_model.appendRow(item)
+
+ item_model.itemChanged.connect(self.on_selected_alias_tag_item_changed)
+ self.ui.tag_alias_selection_list.setModel(item_model)
+
+ def set_selected_implicated_tags(self, implications: list, set_checked: bool=False):
+ """
+ Set the tags in the search result list to tags
+ :param implications:
+ :param set_checked:
+ :return:
+ """
+ item_model = QStandardItemModel(self.ui.tag_implication_search_list)
+
+ for tag in implications:
+ tag = tag.strip()
+ if tag == self.ui.tag_name_line.text():
+ continue
+ item = QStandardItem(tag)
+ item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
+ item.setData(Qt.Unchecked, Qt.CheckStateRole)
+ if set_checked:
+ item.setCheckState(Qt.Checked)
+
+ item_model.appendRow(item)
+
+ item_model.itemChanged.connect(self.on_selected_implicated_tag_item_changed)
+ self.ui.tag_implication_selection_list.setModel(item_model)
+
+ def on_tag_alias_search_line_changed(self):
+ search_text = self.ui.tag_alias_search_line.text()
+ if len(search_text) == 0:
+ tags = []
+ else:
+ tags = self.parent.get_tag_search_result(search_text)
+
+ if tags is None:
+ return
+
+ self.tag_alias_search_result = tags
+ self.set_search_alias_tags(tags)
+
+ def on_tag_implication_search_line_changed(self):
+ search_text = self.ui.tag_implication_search_line.text()
+ if len(search_text) == 0:
+ tags = []
+ else:
+ tags = self.parent.get_tag_search_result(search_text)
+
+ if tags is None:
+ return
+ self.tag_implication_search_result = tags
+ self.set_search_implicated_tags(tags)
+
+ def on_search_implicated_tag_item_changed(self, item: QStandardItem):
+ if item.checkState() == Qt.Checked:
+ if self.implication_selection is None:
+ self.implication_selection = []
+ self.implication_selection.append(item.text())
+ elif item.checkState() == Qt.Unchecked:
+ if item.text() in self.implication_selection:
+ self.implication_selection.remove(item.text())
+
+ self.set_selected_implicated_tags(self.implication_selection, set_checked=True)
+
+ def on_search_alias_tag_item_changed(self, item: QStandardItem):
+ if item.checkState() == Qt.Checked:
+ if self.alias_selection is None:
+ self.alias_selection = []
+ self.alias_selection.append(item.text())
+ elif item.checkState() == Qt.Unchecked:
+ if item.text() in self.alias_selection:
+ self.alias_selection.remove(item.text())
+
+ self.set_selected_alias_tags(self.alias_selection, set_checked=True)
+
+ def on_selected_implicated_tag_item_changed(self, item: QStandardItem):
+ if self.implication_selection is None:
+ self.implication_selection = []
+ self.implication_selection.remove(item.text())
+ self.set_selected_implicated_tags(self.implication_selection, set_checked=True)
+
+ if self.tag_implication_search_result is not None:
+ self.set_search_implicated_tags(self.tag_implication_search_result)
+
+ def on_selected_alias_tag_item_changed(self, item: QStandardItem):
+ if self.alias_selection is None:
+ self.alias_selection = []
+ self.alias_selection.remove(item.text())
+ self.set_selected_alias_tags(self.alias_selection, set_checked=True)
+
+ self.set_search_alias_tags(self.tag_alias_search_result)
+
+ def on_category_item_changed(self, item: QStandardItem):
+ if self.category_selection is None:
+ self.category_selection = []
+
+ if item.checkState() == Qt.Checked:
+ self.category_selection = item.text()
+ elif item.checkState() == Qt.Unchecked:
+ self.category_selection = None
+
+ self.set_all_categories()
+
+ def has_critical_info(self) -> bool:
+ """
+ Checks if all required fields are filled
+ :return:
+ """
+ ok = True
+ if len(self.ui.tag_name_line.text()) == 0:
+ ok = False
+ if self.category_selection is None:
+ ok = False
+
+ if not ok:
+ msg = QtWidgets.QMessageBox()
+ msg.setWindowTitle("Missing Fields")
+ msg.setIcon(QtWidgets.QMessageBox.Critical)
+ msg.setInformativeText(
+ "Either the tag name or the category selection is empty! Can't save the tag this way!")
+
+ msg.exec_()
+ return ok
+
+ def getTagDetails(self):
+ self.data = dict()
+
+ self.data["name"] = self.ui.tag_name_line.text()
+ self.data["description"] = self.ui.tag_description_area.toPlainText()
+
+ self.data["aliases"] = self.alias_selection
+ self.data["implications"] = self.implication_selection
+
+ self.data["category"] = self.category_selection
+
+ if not self.has_critical_info():
+ self.data = None
+ self.reject()
+ return
+
+ self.accept()
+
+ def exec_(self) -> dict:
+ self.original_tag_name = self.ui.tag_name_line.text()
+
+ if super(TagModifyDialog, self).exec_() == QtWidgets.QDialog.Rejected:
+ return None
+
+ if not self.create_tag:
+ if self.data["name"] != self.original_tag_name:
+ self.data["old_tag_name"] = self.original_tag_name
+
+ return self.data
diff --git a/ArtNet/gui/dialogs/tag_modify_dialog/tag_modify_dialog.py b/ArtNet/gui/dialogs/tag_modify_dialog/tag_modify_dialog.py
new file mode 100644
index 0000000..53ad4e0
--- /dev/null
+++ b/ArtNet/gui/dialogs/tag_modify_dialog/tag_modify_dialog.py
@@ -0,0 +1,226 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'dialogs/tag_modify_dialog/tag_modify_dialog.ui'
+#
+# Created by: PyQt5 UI code generator 5.15.0
+#
+# WARNING: Any manual changes made to this file will be lost when pyuic5 is
+# run again. Do not edit this file unless you know what you are doing.
+
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+
+class Ui_TagModify(object):
+ def setupUi(self, TagModify):
+ TagModify.setObjectName("TagModify")
+ TagModify.resize(587, 846)
+ self.verticalLayout = QtWidgets.QVBoxLayout(TagModify)
+ self.verticalLayout.setObjectName("verticalLayout")
+ self.dialog_frame = QtWidgets.QFrame(TagModify)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Minimum)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.dialog_frame.sizePolicy().hasHeightForWidth())
+ self.dialog_frame.setSizePolicy(sizePolicy)
+ self.dialog_frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
+ self.dialog_frame.setFrameShadow(QtWidgets.QFrame.Raised)
+ self.dialog_frame.setObjectName("dialog_frame")
+ self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.dialog_frame)
+ self.verticalLayout_2.setObjectName("verticalLayout_2")
+ self.dialog_topic = QtWidgets.QLabel(self.dialog_frame)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.dialog_topic.sizePolicy().hasHeightForWidth())
+ self.dialog_topic.setSizePolicy(sizePolicy)
+ font = QtGui.QFont()
+ font.setPointSize(10)
+ font.setBold(True)
+ font.setWeight(75)
+ self.dialog_topic.setFont(font)
+ self.dialog_topic.setAlignment(QtCore.Qt.AlignCenter)
+ self.dialog_topic.setObjectName("dialog_topic")
+ self.verticalLayout_2.addWidget(self.dialog_topic)
+ self.tag_name_label = QtWidgets.QLabel(self.dialog_frame)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.tag_name_label.sizePolicy().hasHeightForWidth())
+ self.tag_name_label.setSizePolicy(sizePolicy)
+ font = QtGui.QFont()
+ font.setBold(True)
+ font.setWeight(75)
+ self.tag_name_label.setFont(font)
+ self.tag_name_label.setObjectName("tag_name_label")
+ self.verticalLayout_2.addWidget(self.tag_name_label)
+ self.tag_name_line = QtWidgets.QLineEdit(self.dialog_frame)
+ self.tag_name_line.setObjectName("tag_name_line")
+ self.verticalLayout_2.addWidget(self.tag_name_line)
+ self.tag_description_label = QtWidgets.QLabel(self.dialog_frame)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.tag_description_label.sizePolicy().hasHeightForWidth())
+ self.tag_description_label.setSizePolicy(sizePolicy)
+ self.tag_description_label.setObjectName("tag_description_label")
+ self.verticalLayout_2.addWidget(self.tag_description_label)
+ self.tag_description_area = QtWidgets.QTextEdit(self.dialog_frame)
+ self.tag_description_area.setMaximumSize(QtCore.QSize(16777215, 100))
+ self.tag_description_area.setObjectName("tag_description_area")
+ self.verticalLayout_2.addWidget(self.tag_description_area)
+ self.category_label = QtWidgets.QLabel(self.dialog_frame)
+ self.category_label.setObjectName("category_label")
+ self.verticalLayout_2.addWidget(self.category_label)
+ self.category_list = QtWidgets.QListView(self.dialog_frame)
+ self.category_list.setMaximumSize(QtCore.QSize(16777215, 100))
+ self.category_list.setObjectName("category_list")
+ self.verticalLayout_2.addWidget(self.category_list)
+ self.tag_alias_frame = QtWidgets.QFrame(self.dialog_frame)
+ self.tag_alias_frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
+ self.tag_alias_frame.setFrameShadow(QtWidgets.QFrame.Raised)
+ self.tag_alias_frame.setObjectName("tag_alias_frame")
+ self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.tag_alias_frame)
+ self.verticalLayout_3.setObjectName("verticalLayout_3")
+ self.tag_alias_label = QtWidgets.QLabel(self.tag_alias_frame)
+ font = QtGui.QFont()
+ font.setBold(True)
+ font.setWeight(75)
+ self.tag_alias_label.setFont(font)
+ self.tag_alias_label.setAlignment(QtCore.Qt.AlignCenter)
+ self.tag_alias_label.setObjectName("tag_alias_label")
+ self.verticalLayout_3.addWidget(self.tag_alias_label)
+ self.tag_alias_search_line = QtWidgets.QLineEdit(self.tag_alias_frame)
+ self.tag_alias_search_line.setObjectName("tag_alias_search_line")
+ self.verticalLayout_3.addWidget(self.tag_alias_search_line)
+ self.tag_alias_label_frame = QtWidgets.QFrame(self.tag_alias_frame)
+ self.tag_alias_label_frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
+ self.tag_alias_label_frame.setFrameShadow(QtWidgets.QFrame.Raised)
+ self.tag_alias_label_frame.setObjectName("tag_alias_label_frame")
+ self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.tag_alias_label_frame)
+ self.horizontalLayout_2.setObjectName("horizontalLayout_2")
+ self.tag_alias_search_label = QtWidgets.QLabel(self.tag_alias_label_frame)
+ self.tag_alias_search_label.setObjectName("tag_alias_search_label")
+ self.horizontalLayout_2.addWidget(self.tag_alias_search_label)
+ self.tag_alias_selection_label = QtWidgets.QLabel(self.tag_alias_label_frame)
+ self.tag_alias_selection_label.setObjectName("tag_alias_selection_label")
+ self.horizontalLayout_2.addWidget(self.tag_alias_selection_label)
+ self.verticalLayout_3.addWidget(self.tag_alias_label_frame)
+ self.tag_alias_list_frame = QtWidgets.QFrame(self.tag_alias_frame)
+ self.tag_alias_list_frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
+ self.tag_alias_list_frame.setFrameShadow(QtWidgets.QFrame.Raised)
+ self.tag_alias_list_frame.setObjectName("tag_alias_list_frame")
+ self.horizontalLayout = QtWidgets.QHBoxLayout(self.tag_alias_list_frame)
+ self.horizontalLayout.setObjectName("horizontalLayout")
+ self.tag_alias_search_list = QtWidgets.QListView(self.tag_alias_list_frame)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.tag_alias_search_list.sizePolicy().hasHeightForWidth())
+ self.tag_alias_search_list.setSizePolicy(sizePolicy)
+ self.tag_alias_search_list.setMaximumSize(QtCore.QSize(16777215, 100))
+ self.tag_alias_search_list.setObjectName("tag_alias_search_list")
+ self.horizontalLayout.addWidget(self.tag_alias_search_list)
+ self.tag_alias_selection_list = QtWidgets.QListView(self.tag_alias_list_frame)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.tag_alias_selection_list.sizePolicy().hasHeightForWidth())
+ self.tag_alias_selection_list.setSizePolicy(sizePolicy)
+ self.tag_alias_selection_list.setMaximumSize(QtCore.QSize(16777215, 100))
+ self.tag_alias_selection_list.setObjectName("tag_alias_selection_list")
+ self.horizontalLayout.addWidget(self.tag_alias_selection_list)
+ self.verticalLayout_3.addWidget(self.tag_alias_list_frame)
+ self.verticalLayout_2.addWidget(self.tag_alias_frame)
+ self.tag_implication_frame = QtWidgets.QFrame(self.dialog_frame)
+ self.tag_implication_frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
+ self.tag_implication_frame.setFrameShadow(QtWidgets.QFrame.Raised)
+ self.tag_implication_frame.setObjectName("tag_implication_frame")
+ self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.tag_implication_frame)
+ self.verticalLayout_4.setObjectName("verticalLayout_4")
+ self.tag_implication_label = QtWidgets.QLabel(self.tag_implication_frame)
+ font = QtGui.QFont()
+ font.setBold(True)
+ font.setWeight(75)
+ self.tag_implication_label.setFont(font)
+ self.tag_implication_label.setAlignment(QtCore.Qt.AlignCenter)
+ self.tag_implication_label.setObjectName("tag_implication_label")
+ self.verticalLayout_4.addWidget(self.tag_implication_label)
+ self.tag_implication_search_line = QtWidgets.QLineEdit(self.tag_implication_frame)
+ self.tag_implication_search_line.setObjectName("tag_implication_search_line")
+ self.verticalLayout_4.addWidget(self.tag_implication_search_line)
+ self.tag_implication_label_frame = QtWidgets.QFrame(self.tag_implication_frame)
+ self.tag_implication_label_frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
+ self.tag_implication_label_frame.setFrameShadow(QtWidgets.QFrame.Raised)
+ self.tag_implication_label_frame.setObjectName("tag_implication_label_frame")
+ self.horizontalLayout_3 = QtWidgets.QHBoxLayout(self.tag_implication_label_frame)
+ self.horizontalLayout_3.setObjectName("horizontalLayout_3")
+ self.tag_implication_search_label = QtWidgets.QLabel(self.tag_implication_label_frame)
+ self.tag_implication_search_label.setObjectName("tag_implication_search_label")
+ self.horizontalLayout_3.addWidget(self.tag_implication_search_label)
+ self.tag_implication_selection_label = QtWidgets.QLabel(self.tag_implication_label_frame)
+ self.tag_implication_selection_label.setObjectName("tag_implication_selection_label")
+ self.horizontalLayout_3.addWidget(self.tag_implication_selection_label)
+ self.verticalLayout_4.addWidget(self.tag_implication_label_frame)
+ self.tag_implication_list_frame = QtWidgets.QFrame(self.tag_implication_frame)
+ self.tag_implication_list_frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
+ self.tag_implication_list_frame.setFrameShadow(QtWidgets.QFrame.Raised)
+ self.tag_implication_list_frame.setObjectName("tag_implication_list_frame")
+ self.horizontalLayout_4 = QtWidgets.QHBoxLayout(self.tag_implication_list_frame)
+ self.horizontalLayout_4.setObjectName("horizontalLayout_4")
+ self.tag_implication_search_list = QtWidgets.QListView(self.tag_implication_list_frame)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.tag_implication_search_list.sizePolicy().hasHeightForWidth())
+ self.tag_implication_search_list.setSizePolicy(sizePolicy)
+ self.tag_implication_search_list.setMaximumSize(QtCore.QSize(16777215, 100))
+ self.tag_implication_search_list.setObjectName("tag_implication_search_list")
+ self.horizontalLayout_4.addWidget(self.tag_implication_search_list)
+ self.tag_implication_selection_list = QtWidgets.QListView(self.tag_implication_list_frame)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.tag_implication_selection_list.sizePolicy().hasHeightForWidth())
+ self.tag_implication_selection_list.setSizePolicy(sizePolicy)
+ self.tag_implication_selection_list.setMinimumSize(QtCore.QSize(0, 0))
+ self.tag_implication_selection_list.setMaximumSize(QtCore.QSize(16777215, 100))
+ self.tag_implication_selection_list.setObjectName("tag_implication_selection_list")
+ self.horizontalLayout_4.addWidget(self.tag_implication_selection_list)
+ self.verticalLayout_4.addWidget(self.tag_implication_list_frame)
+ self.verticalLayout_2.addWidget(self.tag_implication_frame)
+ self.verticalLayout.addWidget(self.dialog_frame)
+ self.buttonBox = QtWidgets.QDialogButtonBox(TagModify)
+ self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
+ self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
+ self.buttonBox.setObjectName("buttonBox")
+ self.verticalLayout.addWidget(self.buttonBox)
+
+ self.retranslateUi(TagModify)
+ self.buttonBox.accepted.connect(TagModify.accept)
+ self.buttonBox.rejected.connect(TagModify.reject)
+ QtCore.QMetaObject.connectSlotsByName(TagModify)
+
+ def retranslateUi(self, TagModify):
+ _translate = QtCore.QCoreApplication.translate
+ TagModify.setWindowTitle(_translate("TagModify", "Dialog"))
+ self.dialog_topic.setText(_translate("TagModify", "Tag Topic"))
+ self.tag_name_label.setText(_translate("TagModify", "Tag Name:"))
+ self.tag_description_label.setText(_translate("TagModify", "Description:"))
+ self.category_label.setText(_translate("TagModify", "Category:"))
+ self.tag_alias_label.setText(_translate("TagModify", "Tag Aliases"))
+ self.tag_alias_search_label.setText(_translate("TagModify", "Search Result:"))
+ self.tag_alias_selection_label.setText(_translate("TagModify", "Selection:"))
+ self.tag_implication_label.setText(_translate("TagModify", "Tag Implications"))
+ self.tag_implication_search_label.setText(_translate("TagModify", "Search Result:"))
+ self.tag_implication_selection_label.setText(_translate("TagModify", "Selection:"))
+
+
+if __name__ == "__main__":
+ import sys
+ app = QtWidgets.QApplication(sys.argv)
+ TagModify = QtWidgets.QDialog()
+ ui = Ui_TagModify()
+ ui.setupUi(TagModify)
+ TagModify.show()
+ sys.exit(app.exec_())
diff --git a/ArtNet/gui/dialogs/tag_modify_dialog/tag_modify_dialog.ui b/ArtNet/gui/dialogs/tag_modify_dialog/tag_modify_dialog.ui
new file mode 100644
index 0000000..cb57cb1
--- /dev/null
+++ b/ArtNet/gui/dialogs/tag_modify_dialog/tag_modify_dialog.ui
@@ -0,0 +1,375 @@
+
+
+ TagModify
+
+
+
+ 0
+ 0
+ 587
+ 846
+
+
+
+ Dialog
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 10
+ 75
+ true
+
+
+
+ Tag Topic
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 75
+ true
+
+
+
+ Tag Name:
+
+
+
+ -
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Description:
+
+
+
+ -
+
+
+
+ 16777215
+ 100
+
+
+
+
+ -
+
+
+ Category:
+
+
+
+ -
+
+
+
+ 16777215
+ 100
+
+
+
+
+ -
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
-
+
+
+
+ 75
+ true
+
+
+
+ Tag Aliases
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+ -
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
-
+
+
+ Search Result:
+
+
+
+ -
+
+
+ Selection:
+
+
+
+
+
+
+ -
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 16777215
+ 100
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 16777215
+ 100
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
-
+
+
+
+ 75
+ true
+
+
+
+ Tag Implications
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+ -
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
-
+
+
+ Search Result:
+
+
+
+ -
+
+
+ Selection:
+
+
+
+
+
+
+ -
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 16777215
+ 100
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 0
+
+
+
+
+ 16777215
+ 100
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+
+
+
+
+
+
+
+
+ buttonBox
+ accepted()
+ TagModify
+ accept()
+
+
+ 248
+ 254
+
+
+ 157
+ 274
+
+
+
+
+ buttonBox
+ rejected()
+ TagModify
+ reject()
+
+
+ 316
+ 260
+
+
+ 286
+ 274
+
+
+
+
+
diff --git a/ArtNet/gui/dialogs/tag_select_dialog/__init__.py b/ArtNet/gui/dialogs/tag_select_dialog/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/ArtNet/gui/dialogs/tag_select_dialog/tag_select_dialog.py b/ArtNet/gui/dialogs/tag_select_dialog/tag_select_dialog.py
new file mode 100644
index 0000000..a762aad
--- /dev/null
+++ b/ArtNet/gui/dialogs/tag_select_dialog/tag_select_dialog.py
@@ -0,0 +1,77 @@
+from PyQt5 import QtWidgets
+from PyQt5.QtGui import QStandardItemModel, QStandardItem
+from PyQt5.QtCore import Qt
+
+from ArtNet.gui.dialogs.tag_select_dialog.tag_selection_dialog import Ui_TagSelection
+
+
+class TagSelectDialog(QtWidgets.QDialog):
+
+ def __init__(self, parent=None, delete_tag: bool=False):
+ super().__init__(parent)
+ self.parent = parent
+ self.ui = Ui_TagSelection()
+ self.ui.setupUi(self)
+
+ if delete_tag:
+ self.ui.select_tag_label.setText("Select Tag to delete")
+
+ self.selected_item: QStandardItem = None
+ self.selection: str = None
+
+ self.ui.buttonBox.accepted.connect(self.getTagSelection)
+ self.ui.tag_search_line.textChanged.connect(self.getSearchResult)
+ self.getSearchResult()
+
+ def getSearchResult(self):
+ search_text = self.ui.tag_search_line.text()
+ tags = self.parent.get_tag_search_result(search_text)
+
+ if tags is None:
+ return
+ self.set_search_result_list(tags)
+
+ def getTagSelection(self) -> str:
+ if self.selected_item is None:
+ return
+ self.selection = self.selected_item.text()
+
+ def on_tag_item_changed(self, item: QStandardItem):
+ if self.selected_item is not None:
+ self.selected_item.setCheckState(Qt.Unchecked)
+ self.selected_item = item
+
+ def set_search_result_list(self, tags: list):
+ """
+ Set the tags in the search result list to tags
+ :param tags:
+ :return:
+ """
+ item_model = QStandardItemModel(self.ui.search_result_list)
+
+ for tag in tags:
+ item = QStandardItem(tag[0])
+ item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
+ item.setData(Qt.Unchecked, Qt.CheckStateRole)
+
+ item_model.appendRow(item)
+
+ item_model.itemChanged.connect(self.on_tag_item_changed)
+ self.ui.search_result_list.setModel(item_model)
+
+ def exec_(self) -> dict:
+ if super(TagSelectDialog, self).exec_() == QtWidgets.QDialog.Rejected:
+ return None
+
+ if self.selection is None:
+ return None
+ tag = self.parent.get_tag(self.selection)[0]
+ tag_data = {
+ "ID": tag[3],
+ "name": tag[0],
+ "description": tag[1],
+ "aliases": self.parent.get_tag_aliases(tag[0]),
+ "implications": self.parent.get_tag_implications(tag[0]),
+ "category": tag[2]
+ }
+ return tag_data
diff --git a/ArtNet/gui/dialogs/tag_select_dialog/tag_selection_dialog.py b/ArtNet/gui/dialogs/tag_select_dialog/tag_selection_dialog.py
new file mode 100644
index 0000000..cd885a7
--- /dev/null
+++ b/ArtNet/gui/dialogs/tag_select_dialog/tag_selection_dialog.py
@@ -0,0 +1,70 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'dialogs/tag_select_dialog/tag_selection_dialog.ui'
+#
+# Created by: PyQt5 UI code generator 5.15.0
+#
+# WARNING: Any manual changes made to this file will be lost when pyuic5 is
+# run again. Do not edit this file unless you know what you are doing.
+
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+
+class Ui_TagSelection(object):
+ def setupUi(self, TagSelection):
+ TagSelection.setObjectName("TagSelection")
+ TagSelection.resize(400, 300)
+ self.verticalLayout_2 = QtWidgets.QVBoxLayout(TagSelection)
+ self.verticalLayout_2.setObjectName("verticalLayout_2")
+ self.top_frame = QtWidgets.QFrame(TagSelection)
+ self.top_frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
+ self.top_frame.setFrameShadow(QtWidgets.QFrame.Raised)
+ self.top_frame.setObjectName("top_frame")
+ self.verticalLayout = QtWidgets.QVBoxLayout(self.top_frame)
+ self.verticalLayout.setObjectName("verticalLayout")
+ self.select_tag_label = QtWidgets.QLabel(self.top_frame)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.select_tag_label.sizePolicy().hasHeightForWidth())
+ self.select_tag_label.setSizePolicy(sizePolicy)
+ font = QtGui.QFont()
+ font.setBold(True)
+ font.setWeight(75)
+ self.select_tag_label.setFont(font)
+ self.select_tag_label.setAlignment(QtCore.Qt.AlignCenter)
+ self.select_tag_label.setObjectName("select_tag_label")
+ self.verticalLayout.addWidget(self.select_tag_label)
+ self.tag_search_line = QtWidgets.QLineEdit(self.top_frame)
+ self.tag_search_line.setObjectName("tag_search_line")
+ self.verticalLayout.addWidget(self.tag_search_line)
+ self.search_result_list = QtWidgets.QListView(self.top_frame)
+ self.search_result_list.setObjectName("search_result_list")
+ self.verticalLayout.addWidget(self.search_result_list)
+ self.verticalLayout_2.addWidget(self.top_frame)
+ self.buttonBox = QtWidgets.QDialogButtonBox(TagSelection)
+ self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
+ self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
+ self.buttonBox.setObjectName("buttonBox")
+ self.verticalLayout_2.addWidget(self.buttonBox)
+
+ self.retranslateUi(TagSelection)
+ self.buttonBox.accepted.connect(TagSelection.accept)
+ self.buttonBox.rejected.connect(TagSelection.reject)
+ QtCore.QMetaObject.connectSlotsByName(TagSelection)
+
+ def retranslateUi(self, TagSelection):
+ _translate = QtCore.QCoreApplication.translate
+ TagSelection.setWindowTitle(_translate("TagSelection", "Dialog"))
+ self.select_tag_label.setText(_translate("TagSelection", "Select Tag"))
+
+
+if __name__ == "__main__":
+ import sys
+ app = QtWidgets.QApplication(sys.argv)
+ TagSelection = QtWidgets.QDialog()
+ ui = Ui_TagSelection()
+ ui.setupUi(TagSelection)
+ TagSelection.show()
+ sys.exit(app.exec_())
diff --git a/ArtNet/gui/dialogs/tag_select_dialog/tag_selection_dialog.ui b/ArtNet/gui/dialogs/tag_select_dialog/tag_selection_dialog.ui
new file mode 100644
index 0000000..5f79d98
--- /dev/null
+++ b/ArtNet/gui/dialogs/tag_select_dialog/tag_selection_dialog.ui
@@ -0,0 +1,104 @@
+
+
+ TagSelection
+
+
+
+ 0
+ 0
+ 400
+ 300
+
+
+
+ Dialog
+
+
+ -
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 75
+ true
+
+
+
+ Select Tag
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+ -
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+
+
+
+
+
+
+
+
+ buttonBox
+ accepted()
+ TagSelection
+ accept()
+
+
+ 248
+ 254
+
+
+ 157
+ 274
+
+
+
+
+ buttonBox
+ rejected()
+ TagSelection
+ reject()
+
+
+ 316
+ 260
+
+
+ 286
+ 274
+
+
+
+
+
diff --git a/ArtNet/gui/dockers/__init__.py b/ArtNet/gui/dockers/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/ArtNet/gui/dockers/presence/__init__.py b/ArtNet/gui/dockers/presence/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/ArtNet/gui/dockers/presence/presence_dock.py b/ArtNet/gui/dockers/presence/presence_dock.py
new file mode 100644
index 0000000..b9cdd70
--- /dev/null
+++ b/ArtNet/gui/dockers/presence/presence_dock.py
@@ -0,0 +1,146 @@
+from PyQt5 import QtWidgets
+from PyQt5.QtGui import QStandardItem, QStandardItemModel
+from PyQt5.QtCore import Qt
+
+from ArtNet.gui.dockers.presence.presence_docker import Ui_presence_docker
+from ArtNet.gui.dialogs.presence_modify_dialog.presence_mod_dialog import PresenceModDialog
+from ArtNet.gui.dialogs.presence_selection_dialog.presence_select_dialog import PresenceSelectDialog
+
+
+class PresenceDocker(QtWidgets.QFrame):
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.parent = parent
+
+ self.ui = Ui_presence_docker()
+ self.ui.setupUi(self)
+
+ self.ui.name_line.textChanged.connect(self.on_search_bar_change)
+ self.ui.domain_line.textChanged.connect(self.on_search_bar_change)
+
+ self.ui.create_presence_button.clicked.connect(self.on_create_presence_clicked)
+ self.ui.edit_presence_button.clicked.connect(self.on_edit_presence_clicked)
+ self.ui.delete_presence_button.clicked.connect(self.on_delete_presence_clicked)
+
+ self.on_search_bar_change()
+
+ def set_presence_list(self, presences: list):
+ item_model = QStandardItemModel(self.ui.search_author_list)
+
+ for name, domain in presences:
+ item = QStandardItem(name+":"+domain)
+ item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
+ item.setData(Qt.Unchecked, Qt.CheckStateRole)
+
+ item_model.appendRow(item)
+ if (name, domain) in self.parent.get_current_presences():
+ item.setCheckState(Qt.Checked)
+
+ item_model.itemChanged.connect(self.on_presence_item_change)
+ self.ui.search_author_list.setModel(item_model)
+
+ def set_selected_presences_list(self, presences: list):
+ item_model = QStandardItemModel(self.ui.selected_presence_list)
+
+ for name, domain in presences:
+ item = QStandardItem(name + ":" + domain)
+ item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
+ item.setData(Qt.Unchecked, Qt.CheckStateRole)
+
+ if domain == "(Not in Database)":
+ continue
+
+ item_model.appendRow(item)
+ if (name, domain) in self.parent.get_current_presences():
+ item.setCheckState(Qt.Checked)
+
+ item_model.itemChanged.connect(self.on_selected_presence_item_change)
+ self.ui.selected_presence_list.setModel(item_model)
+
+ def on_search_bar_change(self):
+ authors = self.parent.get_authors(self.ui.name_line.text(), self.ui.domain_line.text())
+ self.set_presence_list(authors)
+
+ def on_presence_item_change(self, item: QStandardItem):
+ if item.checkState() == Qt.Checked:
+ s = item.text().split(":")
+ s = (s[0], s[1])
+ if self.parent.get_current_presences() is None:
+ self.parent.set_current_presences([s])
+ self.set_selected_presences_list([s])
+ else:
+ self.parent.set_current_presences(self.parent.get_current_presences() + [s])
+ self.set_selected_presences_list(self.parent.get_current_presences())
+ elif item.checkState() == Qt.Unchecked:
+ presences = self.parent.get_current_presences()
+ if presences is None:
+ return
+ s = item.text().split(":")
+ s = (s[0], s[1])
+ presences.remove(s)
+ self.parent.set_current_presences(presences)
+ self.set_selected_presences_list(presences)
+
+ def on_selected_presence_item_change(self, item: QStandardItem):
+ item_presence = item.text().split(":")
+ item_presence = (item_presence[0], item_presence[1])
+ if item.checkState() == Qt.Unchecked:
+ presences = self.parent.get_current_presences()
+ presences.remove(item_presence)
+ self.parent.set_current_presences(presences)
+
+ self.set_selected_presences_list(presences)
+ self.on_search_bar_change()
+
+ def on_create_presence_clicked(self):
+ dialog = PresenceModDialog(self, edit_presence=False)
+ data = dialog.exec_()
+ if data is None: # canceled?
+ self.on_search_bar_change()
+ return
+ self.parent.create_presence(name=data["name"], domain=data["domain"], artist=data["artist"],
+ link=data["link"])
+ self.on_search_bar_change()
+
+ def on_edit_presence_clicked(self):
+ select_dialog = PresenceSelectDialog(self)
+ selected_presence = select_dialog.exec_()
+ if selected_presence is None:
+ return
+
+ dialog = PresenceModDialog(self, edit_presence=True)
+ dialog.ui.presence_name_line.setText(selected_presence[0])
+ dialog.ui.presence_domain_line.setText(selected_presence[1])
+
+ if len(selected_presence) > 2:
+ if len(selected_presence) >= 4:
+ if selected_presence[3] is not None:
+ dialog.ui.presence_link_list.setText(selected_presence[3])
+
+ artist = self.parent.get_artists(selected_presence[2])[0]
+ dialog.curr_artist = artist
+ dialog.set_artist_selected_list(dialog.curr_artist)
+
+ data = dialog.exec_()
+ if data is None:
+ self.on_search_bar_change()
+ return
+
+ self.parent.create_presence(name=data["name"], domain=data["domain"], artist=data["artist"], link=data["link"])
+ self.parent.set_temporary_status_message("Saved Presence {0}:{1} Artist:{2} link:{3} to DB!"
+ .format(data["name"], data["domain"], data["artist"], data["link"]),
+ 3000)
+ self.on_search_bar_change()
+
+ def on_delete_presence_clicked(self):
+ print("Clicked delete presence button!")
+ select_dialog = PresenceSelectDialog(self)
+ selected_presence = select_dialog.exec_()
+ if selected_presence is None:
+ self.on_search_bar_change()
+ return
+
+ self.parent.remove_presence(selected_presence[0], selected_presence[1])
+
+ self.on_search_bar_change()
diff --git a/ArtNet/gui/dockers/presence/presence_docker.py b/ArtNet/gui/dockers/presence/presence_docker.py
new file mode 100644
index 0000000..2c56b3e
--- /dev/null
+++ b/ArtNet/gui/dockers/presence/presence_docker.py
@@ -0,0 +1,143 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'dockers/presence/presence_docker.ui'
+#
+# Created by: PyQt5 UI code generator 5.15.0
+#
+# WARNING: Any manual changes made to this file will be lost when pyuic5 is
+# run again. Do not edit this file unless you know what you are doing.
+
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+
+class Ui_presence_docker(object):
+ def setupUi(self, presence_docker):
+ presence_docker.setObjectName("presence_docker")
+ presence_docker.resize(338, 576)
+ self.verticalLayout = QtWidgets.QVBoxLayout(presence_docker)
+ self.verticalLayout.setObjectName("verticalLayout")
+ self.frame = QtWidgets.QFrame(presence_docker)
+ self.frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
+ self.frame.setFrameShadow(QtWidgets.QFrame.Raised)
+ self.frame.setObjectName("frame")
+ self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.frame)
+ self.verticalLayout_2.setObjectName("verticalLayout_2")
+ self.docker_topic_label = QtWidgets.QLabel(self.frame)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.docker_topic_label.sizePolicy().hasHeightForWidth())
+ self.docker_topic_label.setSizePolicy(sizePolicy)
+ font = QtGui.QFont()
+ font.setPointSize(13)
+ font.setBold(True)
+ font.setWeight(75)
+ self.docker_topic_label.setFont(font)
+ self.docker_topic_label.setAlignment(QtCore.Qt.AlignCenter)
+ self.docker_topic_label.setObjectName("docker_topic_label")
+ self.verticalLayout_2.addWidget(self.docker_topic_label)
+ self.search_author_label = QtWidgets.QLabel(self.frame)
+ font = QtGui.QFont()
+ font.setPointSize(10)
+ font.setBold(True)
+ font.setWeight(75)
+ self.search_author_label.setFont(font)
+ self.search_author_label.setObjectName("search_author_label")
+ self.verticalLayout_2.addWidget(self.search_author_label)
+ self.search_frame = QtWidgets.QFrame(self.frame)
+ self.search_frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
+ self.search_frame.setFrameShadow(QtWidgets.QFrame.Raised)
+ self.search_frame.setObjectName("search_frame")
+ self.horizontalLayout_3 = QtWidgets.QHBoxLayout(self.search_frame)
+ self.horizontalLayout_3.setObjectName("horizontalLayout_3")
+ self.name_frame = QtWidgets.QFrame(self.search_frame)
+ self.name_frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
+ self.name_frame.setFrameShadow(QtWidgets.QFrame.Raised)
+ self.name_frame.setObjectName("name_frame")
+ self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.name_frame)
+ self.verticalLayout_3.setObjectName("verticalLayout_3")
+ self.name_label = QtWidgets.QLabel(self.name_frame)
+ font = QtGui.QFont()
+ font.setBold(True)
+ font.setWeight(75)
+ self.name_label.setFont(font)
+ self.name_label.setObjectName("name_label")
+ self.verticalLayout_3.addWidget(self.name_label)
+ self.name_line = QtWidgets.QLineEdit(self.name_frame)
+ self.name_line.setObjectName("name_line")
+ self.verticalLayout_3.addWidget(self.name_line)
+ self.horizontalLayout_3.addWidget(self.name_frame)
+ self.domain_frame = QtWidgets.QFrame(self.search_frame)
+ self.domain_frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
+ self.domain_frame.setFrameShadow(QtWidgets.QFrame.Raised)
+ self.domain_frame.setObjectName("domain_frame")
+ self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.domain_frame)
+ self.verticalLayout_4.setObjectName("verticalLayout_4")
+ self.domain_label = QtWidgets.QLabel(self.domain_frame)
+ font = QtGui.QFont()
+ font.setBold(True)
+ font.setWeight(75)
+ self.domain_label.setFont(font)
+ self.domain_label.setObjectName("domain_label")
+ self.verticalLayout_4.addWidget(self.domain_label)
+ self.domain_line = QtWidgets.QLineEdit(self.domain_frame)
+ self.domain_line.setObjectName("domain_line")
+ self.verticalLayout_4.addWidget(self.domain_line)
+ self.horizontalLayout_3.addWidget(self.domain_frame)
+ self.verticalLayout_2.addWidget(self.search_frame)
+ self.search_author_list = QtWidgets.QListView(self.frame)
+ self.search_author_list.setObjectName("search_author_list")
+ self.verticalLayout_2.addWidget(self.search_author_list)
+ self.selected_presence_label = QtWidgets.QLabel(self.frame)
+ font = QtGui.QFont()
+ font.setBold(True)
+ font.setWeight(75)
+ self.selected_presence_label.setFont(font)
+ self.selected_presence_label.setObjectName("selected_presence_label")
+ self.verticalLayout_2.addWidget(self.selected_presence_label)
+ self.selected_presence_list = QtWidgets.QListView(self.frame)
+ self.selected_presence_list.setObjectName("selected_presence_list")
+ self.verticalLayout_2.addWidget(self.selected_presence_list)
+ self.presence_docker_button_frame = QtWidgets.QFrame(self.frame)
+ self.presence_docker_button_frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
+ self.presence_docker_button_frame.setFrameShadow(QtWidgets.QFrame.Raised)
+ self.presence_docker_button_frame.setObjectName("presence_docker_button_frame")
+ self.horizontalLayout = QtWidgets.QHBoxLayout(self.presence_docker_button_frame)
+ self.horizontalLayout.setObjectName("horizontalLayout")
+ self.create_presence_button = QtWidgets.QPushButton(self.presence_docker_button_frame)
+ self.create_presence_button.setObjectName("create_presence_button")
+ self.horizontalLayout.addWidget(self.create_presence_button)
+ self.edit_presence_button = QtWidgets.QPushButton(self.presence_docker_button_frame)
+ self.edit_presence_button.setObjectName("edit_presence_button")
+ self.horizontalLayout.addWidget(self.edit_presence_button)
+ self.delete_presence_button = QtWidgets.QPushButton(self.presence_docker_button_frame)
+ self.delete_presence_button.setObjectName("delete_presence_button")
+ self.horizontalLayout.addWidget(self.delete_presence_button)
+ self.verticalLayout_2.addWidget(self.presence_docker_button_frame)
+ self.verticalLayout.addWidget(self.frame)
+
+ self.retranslateUi(presence_docker)
+ QtCore.QMetaObject.connectSlotsByName(presence_docker)
+
+ def retranslateUi(self, presence_docker):
+ _translate = QtCore.QCoreApplication.translate
+ presence_docker.setWindowTitle(_translate("presence_docker", "Form"))
+ self.docker_topic_label.setText(_translate("presence_docker", "Author (Presence)"))
+ self.search_author_label.setText(_translate("presence_docker", "Search Authors:"))
+ self.name_label.setText(_translate("presence_docker", "Name:"))
+ self.domain_label.setText(_translate("presence_docker", "Domain:"))
+ self.selected_presence_label.setText(_translate("presence_docker", "Selected Presences:"))
+ self.create_presence_button.setText(_translate("presence_docker", "Create Presence"))
+ self.edit_presence_button.setText(_translate("presence_docker", "Edit Presence"))
+ self.delete_presence_button.setText(_translate("presence_docker", "Delete Presence"))
+
+
+if __name__ == "__main__":
+ import sys
+ app = QtWidgets.QApplication(sys.argv)
+ presence_docker = QtWidgets.QWidget()
+ ui = Ui_presence_docker()
+ ui.setupUi(presence_docker)
+ presence_docker.show()
+ sys.exit(app.exec_())
diff --git a/ArtNet/gui/dockers/presence/presence_docker.ui b/ArtNet/gui/dockers/presence/presence_docker.ui
new file mode 100644
index 0000000..c081f71
--- /dev/null
+++ b/ArtNet/gui/dockers/presence/presence_docker.ui
@@ -0,0 +1,190 @@
+
+
+ presence_docker
+
+
+
+ 0
+ 0
+ 338
+ 576
+
+
+
+ Form
+
+
+ -
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 13
+ 75
+ true
+
+
+
+ Author (Presence)
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+
+ 10
+ 75
+ true
+
+
+
+ Search Authors:
+
+
+
+ -
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
-
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
-
+
+
+
+ 75
+ true
+
+
+
+ Name:
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
-
+
+
+
+ 75
+ true
+
+
+
+ Domain:
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+ -
+
+
+
+ 75
+ true
+
+
+
+ Selected Presences:
+
+
+
+ -
+
+
+ -
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
-
+
+
+ Create Presence
+
+
+
+ -
+
+
+ Edit Presence
+
+
+
+ -
+
+
+ Delete Presence
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ArtNet/gui/manual_picture_importer.py b/ArtNet/gui/manual_picture_importer.py
new file mode 100644
index 0000000..30cfcb0
--- /dev/null
+++ b/ArtNet/gui/manual_picture_importer.py
@@ -0,0 +1,104 @@
+from PyQt5.QtWidgets import QMainWindow, QWidget, QLabel, QDesktopWidget, QToolBar, QPushButton, QGroupBox, QGridLayout
+from PyQt5.QtWidgets import QVBoxLayout
+from PyQt5.QtGui import QPixmap
+from PyQt5.QtCore import Qt
+
+
+class PictureImporter(QMainWindow):
+
+ def __init__(self, parent=None, max_relative_resize_width: float = 0.8, max_relative_resize_height: float = 0.8,):
+ super().__init__(parent)
+
+ # Menubar
+ self.__menu_bar = self.menuBar()
+ self.__menu_bar.addAction("Action1")
+ self.__menu_bar.addAction("Action2")
+
+ # Toolbar
+ open = QToolBar()
+ open.addAction("Open")
+ self.addToolBar(open)
+ close = QToolBar()
+ close.addAction("Close")
+ self.addToolBar(close)
+
+ # layout
+ self.__layout = QGridLayout()
+
+ # Central Component Settings
+ self.__window = QWidget()
+
+ self.setCentralWidget(self.__window)
+
+ # Statusbar Settings
+ self.__status_bar = self.statusBar()
+ self.__status_bar.showMessage("Welcome to ArtNet! :)", 5000)
+ label = QLabel("ArtNet - v0.1")
+ self.__status_bar.addPermanentWidget(label)
+
+ self.resize(800, 600)
+ self.setWindowTitle("ArtNet - Picture Importer")
+ self.center()
+
+ def center(self):
+ """
+ Centers the window in the middle of the screen
+
+ :return:
+ """
+ screen = QDesktopWidget().screenGeometry()
+ size = self.geometry()
+ self.move((screen.width() - size.width()) / 2, (screen.height() - size.height()) / 2)
+
+ @DeprecationWarning
+ def display_geometry(self):
+ x = self.x()
+ y = self.y()
+ print("x: {0}, y: {1}".format(x, y))
+ x = self.pos().x()
+ y = self.pos().y()
+ print("x: {0}, y: {1}".format(x, y))
+ x = self.frameGeometry().x()
+ y = self.frameGeometry().y()
+ print("x: {0}, y: {1}".format(x, y))
+ x = self.geometry().x()
+ y = self.geometry().y()
+ print("x: {0}, y: {1}".format(x, y))
+ print("geometry: ", self.geometry())
+ print("frameGeometry: ", self.frameGeometry())
+
+ def set_temporary_status_message(self, text: str, duration: int):
+ """
+ Set a temporary status message (bottom left) for the given duration in milliseconds.
+ :param text:
+ :param duration:
+ :return:
+ """
+ self.__status_bar.showMessage("Welcome to ArtNet! :)", 5000)
+
+ def display_image(self, full_path: str):
+ """
+ Display an image in the central widget
+ :param full_path:
+ :return:
+ """
+ label = QLabel(self)
+ pixmap = self.__image_resize(QPixmap(full_path))
+
+ label.setPixmap(pixmap)
+ label.setAlignment(Qt.AlignCenter)
+
+ self.setCentralWidget(label)
+ self.center()
+
+ def __image_resize(self, pixmap: QPixmap) -> QPixmap:
+ """
+ Resize the given pixmap so that we're not out of the desktop.
+
+ :return: new scaled QPixmap
+ """
+ return pixmap
+ screen_rect = QDesktopWidget().screenGeometry()
+ print("Resizing pixmap to", int(screen_rect.width()*0.4), int(screen_rect.height()*0.6))
+ return pixmap.scaled(int(screen_rect.width()*0.6), int(screen_rect.height()*0.7), Qt.KeepAspectRatio)
+
diff --git a/ArtNet/gui/picture_importer.py b/ArtNet/gui/picture_importer.py
new file mode 100644
index 0000000..3bc8f48
--- /dev/null
+++ b/ArtNet/gui/picture_importer.py
@@ -0,0 +1,349 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'picture_importer.ui'
+#
+# Created by: PyQt5 UI code generator 5.15.0
+#
+# WARNING: Any manual changes made to this file will be lost when pyuic5 is
+# run again. Do not edit this file unless you know what you are doing.
+
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+
+class Ui_MainWindow(object):
+ def setupUi(self, MainWindow):
+ MainWindow.setObjectName("MainWindow")
+ MainWindow.resize(827, 777)
+ self.centralwidget = QtWidgets.QWidget(MainWindow)
+ self.centralwidget.setEnabled(True)
+ self.centralwidget.setObjectName("centralwidget")
+ self.horizontalLayout = QtWidgets.QHBoxLayout(self.centralwidget)
+ self.horizontalLayout.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint)
+ self.horizontalLayout.setObjectName("horizontalLayout")
+ self.left_frame = QtWidgets.QFrame(self.centralwidget)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.left_frame.sizePolicy().hasHeightForWidth())
+ self.left_frame.setSizePolicy(sizePolicy)
+ self.left_frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
+ self.left_frame.setFrameShadow(QtWidgets.QFrame.Raised)
+ self.left_frame.setObjectName("left_frame")
+ self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.left_frame)
+ self.horizontalLayout_2.setObjectName("horizontalLayout_2")
+ self.presence_docker_button = QtWidgets.QToolButton(self.left_frame)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Expanding)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.presence_docker_button.sizePolicy().hasHeightForWidth())
+ self.presence_docker_button.setSizePolicy(sizePolicy)
+ self.presence_docker_button.setObjectName("presence_docker_button")
+ self.horizontalLayout_2.addWidget(self.presence_docker_button)
+ self.presence_docker_layout = QtWidgets.QVBoxLayout()
+ self.presence_docker_layout.setObjectName("presence_docker_layout")
+ self.horizontalLayout_2.addLayout(self.presence_docker_layout)
+ self.left_layout = QtWidgets.QVBoxLayout()
+ self.left_layout.setSizeConstraint(QtWidgets.QLayout.SetMinimumSize)
+ self.left_layout.setObjectName("left_layout")
+ self.prev_image_button = QtWidgets.QToolButton(self.left_frame)
+ self.prev_image_button.setObjectName("prev_image_button")
+ self.left_layout.addWidget(self.prev_image_button)
+ self.horizontalLayout_2.addLayout(self.left_layout)
+ self.center_frame = QtWidgets.QFrame(self.left_frame)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.center_frame.sizePolicy().hasHeightForWidth())
+ self.center_frame.setSizePolicy(sizePolicy)
+ self.center_frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
+ self.center_frame.setFrameShadow(QtWidgets.QFrame.Raised)
+ self.center_frame.setObjectName("center_frame")
+ self.verticalLayout = QtWidgets.QVBoxLayout(self.center_frame)
+ self.verticalLayout.setObjectName("verticalLayout")
+ self.frame = QtWidgets.QFrame(self.center_frame)
+ self.frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
+ self.frame.setFrameShadow(QtWidgets.QFrame.Raised)
+ self.frame.setObjectName("frame")
+ self.horizontalLayout_4 = QtWidgets.QHBoxLayout(self.frame)
+ self.horizontalLayout_4.setObjectName("horizontalLayout_4")
+ self.image_title_line = QtWidgets.QLineEdit(self.frame)
+ font = QtGui.QFont()
+ font.setPointSize(10)
+ font.setBold(True)
+ font.setWeight(75)
+ self.image_title_line.setFont(font)
+ self.image_title_line.setAutoFillBackground(False)
+ self.image_title_line.setAlignment(QtCore.Qt.AlignCenter)
+ self.image_title_line.setReadOnly(False)
+ self.image_title_line.setObjectName("image_title_line")
+ self.horizontalLayout_4.addWidget(self.image_title_line)
+ self.imageNumberSpinBox = QtWidgets.QSpinBox(self.frame)
+ self.imageNumberSpinBox.setObjectName("imageNumberSpinBox")
+ self.horizontalLayout_4.addWidget(self.imageNumberSpinBox)
+ self.verticalLayout.addWidget(self.frame)
+ self.image_file_label = QtWidgets.QLabel(self.center_frame)
+ self.image_file_label.setAlignment(QtCore.Qt.AlignCenter)
+ self.image_file_label.setObjectName("image_file_label")
+ self.verticalLayout.addWidget(self.image_file_label)
+ self.author_by = QtWidgets.QLabel(self.center_frame)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.author_by.sizePolicy().hasHeightForWidth())
+ self.author_by.setSizePolicy(sizePolicy)
+ font = QtGui.QFont()
+ font.setPointSize(7)
+ font.setBold(False)
+ font.setWeight(50)
+ self.author_by.setFont(font)
+ self.author_by.setAlignment(QtCore.Qt.AlignCenter)
+ self.author_by.setObjectName("author_by")
+ self.verticalLayout.addWidget(self.author_by)
+ self.image_author_label = QtWidgets.QLabel(self.center_frame)
+ font = QtGui.QFont()
+ font.setPointSize(10)
+ font.setBold(False)
+ font.setItalic(False)
+ font.setWeight(50)
+ self.image_author_label.setFont(font)
+ self.image_author_label.setAlignment(QtCore.Qt.AlignCenter)
+ self.image_author_label.setObjectName("image_author_label")
+ self.verticalLayout.addWidget(self.image_author_label)
+ self.image_frame = QtWidgets.QFrame(self.center_frame)
+ self.image_frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
+ self.image_frame.setFrameShadow(QtWidgets.QFrame.Raised)
+ self.image_frame.setObjectName("image_frame")
+ self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.image_frame)
+ self.verticalLayout_2.setObjectName("verticalLayout_2")
+ self.image_label = QtWidgets.QLabel(self.image_frame)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.image_label.sizePolicy().hasHeightForWidth())
+ self.image_label.setSizePolicy(sizePolicy)
+ self.image_label.setAlignment(QtCore.Qt.AlignCenter)
+ self.image_label.setObjectName("image_label")
+ self.verticalLayout_2.addWidget(self.image_label)
+ self.verticalLayout.addWidget(self.image_frame)
+ self.link_label = QtWidgets.QLabel(self.center_frame)
+ font = QtGui.QFont()
+ font.setPointSize(8)
+ font.setBold(True)
+ font.setWeight(75)
+ self.link_label.setFont(font)
+ self.link_label.setAlignment(QtCore.Qt.AlignCenter)
+ self.link_label.setObjectName("link_label")
+ self.verticalLayout.addWidget(self.link_label)
+ self.label_5 = QtWidgets.QLabel(self.center_frame)
+ self.label_5.setObjectName("label_5")
+ self.verticalLayout.addWidget(self.label_5)
+ self.link_line = QtWidgets.QLineEdit(self.center_frame)
+ font = QtGui.QFont()
+ font.setPointSize(7)
+ self.link_line.setFont(font)
+ self.link_line.setObjectName("link_line")
+ self.verticalLayout.addWidget(self.link_line)
+ self.horizontalLayout_2.addWidget(self.center_frame)
+ self.right_layout = QtWidgets.QVBoxLayout()
+ self.right_layout.setSizeConstraint(QtWidgets.QLayout.SetMinimumSize)
+ self.right_layout.setObjectName("right_layout")
+ self.next_image_button = QtWidgets.QToolButton(self.left_frame)
+ self.next_image_button.setObjectName("next_image_button")
+ self.right_layout.addWidget(self.next_image_button)
+ self.horizontalLayout_2.addLayout(self.right_layout)
+ self.horizontalLayout.addWidget(self.left_frame)
+ self.right_frame = QtWidgets.QFrame(self.centralwidget)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Minimum)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.right_frame.sizePolicy().hasHeightForWidth())
+ self.right_frame.setSizePolicy(sizePolicy)
+ self.right_frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
+ self.right_frame.setFrameShadow(QtWidgets.QFrame.Raised)
+ self.right_frame.setObjectName("right_frame")
+ self.horizontalLayout_3 = QtWidgets.QHBoxLayout(self.right_frame)
+ self.horizontalLayout_3.setObjectName("horizontalLayout_3")
+ self.tag_layout = QtWidgets.QVBoxLayout()
+ self.tag_layout.setSizeConstraint(QtWidgets.QLayout.SetMinimumSize)
+ self.tag_layout.setObjectName("tag_layout")
+ self.label = QtWidgets.QLabel(self.right_frame)
+ font = QtGui.QFont()
+ font.setBold(True)
+ font.setWeight(75)
+ self.label.setFont(font)
+ self.label.setObjectName("label")
+ self.tag_layout.addWidget(self.label)
+ self.tag_list_layout = QtWidgets.QVBoxLayout()
+ self.tag_list_layout.setSizeConstraint(QtWidgets.QLayout.SetMinimumSize)
+ self.tag_list_layout.setObjectName("tag_list_layout")
+ self.tag_search_bar = QtWidgets.QLineEdit(self.right_frame)
+ self.tag_search_bar.setMaximumSize(QtCore.QSize(400, 16777215))
+ self.tag_search_bar.setObjectName("tag_search_bar")
+ self.tag_list_layout.addWidget(self.tag_search_bar)
+ self.search_result_list = QtWidgets.QListView(self.right_frame)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.search_result_list.sizePolicy().hasHeightForWidth())
+ self.search_result_list.setSizePolicy(sizePolicy)
+ self.search_result_list.setMinimumSize(QtCore.QSize(0, 0))
+ self.search_result_list.setMaximumSize(QtCore.QSize(400, 16777215))
+ self.search_result_list.setObjectName("search_result_list")
+ self.tag_list_layout.addWidget(self.search_result_list)
+ self.label_2 = QtWidgets.QLabel(self.right_frame)
+ font = QtGui.QFont()
+ font.setBold(True)
+ font.setWeight(75)
+ self.label_2.setFont(font)
+ self.label_2.setObjectName("label_2")
+ self.tag_list_layout.addWidget(self.label_2)
+ self.tag_list = QtWidgets.QListView(self.right_frame)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.tag_list.sizePolicy().hasHeightForWidth())
+ self.tag_list.setSizePolicy(sizePolicy)
+ self.tag_list.setMinimumSize(QtCore.QSize(100, 0))
+ self.tag_list.setMaximumSize(QtCore.QSize(400, 16777215))
+ self.tag_list.setObjectName("tag_list")
+ self.tag_list_layout.addWidget(self.tag_list)
+ self.label_3 = QtWidgets.QLabel(self.right_frame)
+ font = QtGui.QFont()
+ font.setPointSize(9)
+ font.setBold(True)
+ font.setWeight(75)
+ self.label_3.setFont(font)
+ self.label_3.setObjectName("label_3")
+ self.tag_list_layout.addWidget(self.label_3)
+ self.implied_tag_list = QtWidgets.QListView(self.right_frame)
+ self.implied_tag_list.setMinimumSize(QtCore.QSize(100, 0))
+ self.implied_tag_list.setMaximumSize(QtCore.QSize(400, 16777215))
+ self.implied_tag_list.setObjectName("implied_tag_list")
+ self.tag_list_layout.addWidget(self.implied_tag_list)
+ self.tag_layout.addLayout(self.tag_list_layout)
+ self.tag_button_layout = QtWidgets.QHBoxLayout()
+ self.tag_button_layout.setSizeConstraint(QtWidgets.QLayout.SetMinimumSize)
+ self.tag_button_layout.setSpacing(5)
+ self.tag_button_layout.setObjectName("tag_button_layout")
+ self.save_button = QtWidgets.QPushButton(self.right_frame)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.save_button.sizePolicy().hasHeightForWidth())
+ self.save_button.setSizePolicy(sizePolicy)
+ self.save_button.setMaximumSize(QtCore.QSize(16777215, 16777215))
+ self.save_button.setObjectName("save_button")
+ self.tag_button_layout.addWidget(self.save_button)
+ self.import_button = QtWidgets.QPushButton(self.right_frame)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.import_button.sizePolicy().hasHeightForWidth())
+ self.import_button.setSizePolicy(sizePolicy)
+ self.import_button.setMaximumSize(QtCore.QSize(16777215, 16777215))
+ self.import_button.setObjectName("import_button")
+ self.tag_button_layout.addWidget(self.import_button)
+ self.prev_unknown_image_button = QtWidgets.QPushButton(self.right_frame)
+ self.prev_unknown_image_button.setObjectName("prev_unknown_image_button")
+ self.tag_button_layout.addWidget(self.prev_unknown_image_button)
+ self.next_unknown_image_button = QtWidgets.QPushButton(self.right_frame)
+ self.next_unknown_image_button.setObjectName("next_unknown_image_button")
+ self.tag_button_layout.addWidget(self.next_unknown_image_button)
+ self.delete_button = QtWidgets.QPushButton(self.right_frame)
+ self.delete_button.setObjectName("delete_button")
+ self.tag_button_layout.addWidget(self.delete_button)
+ spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
+ self.tag_button_layout.addItem(spacerItem)
+ self.tag_layout.addLayout(self.tag_button_layout)
+ self.horizontalLayout_3.addLayout(self.tag_layout)
+ self.horizontalLayout.addWidget(self.right_frame)
+ MainWindow.setCentralWidget(self.centralwidget)
+ self.statusbar = QtWidgets.QStatusBar(MainWindow)
+ self.statusbar.setObjectName("statusbar")
+ MainWindow.setStatusBar(self.statusbar)
+ self.menuBar = QtWidgets.QMenuBar(MainWindow)
+ self.menuBar.setGeometry(QtCore.QRect(0, 0, 827, 19))
+ self.menuBar.setObjectName("menuBar")
+ self.menuArtNet = QtWidgets.QMenu(self.menuBar)
+ self.menuArtNet.setObjectName("menuArtNet")
+ self.menuTags = QtWidgets.QMenu(self.menuBar)
+ self.menuTags.setObjectName("menuTags")
+ self.menuCategory = QtWidgets.QMenu(self.menuBar)
+ self.menuCategory.setObjectName("menuCategory")
+ MainWindow.setMenuBar(self.menuBar)
+ self.actionChange_Connection_Details = QtWidgets.QAction(MainWindow)
+ self.actionChange_Connection_Details.setObjectName("actionChange_Connection_Details")
+ self.actionChange_ArtNet_Root_Folder = QtWidgets.QAction(MainWindow)
+ self.actionChange_ArtNet_Root_Folder.setObjectName("actionChange_ArtNet_Root_Folder")
+ self.actionCreate_New_Tag = QtWidgets.QAction(MainWindow)
+ self.actionCreate_New_Tag.setObjectName("actionCreate_New_Tag")
+ self.actionDelete_a_Tag = QtWidgets.QAction(MainWindow)
+ self.actionDelete_a_Tag.setObjectName("actionDelete_a_Tag")
+ self.actionEdit_a_Tag = QtWidgets.QAction(MainWindow)
+ self.actionEdit_a_Tag.setObjectName("actionEdit_a_Tag")
+ self.actionCreate_New_Category = QtWidgets.QAction(MainWindow)
+ self.actionCreate_New_Category.setObjectName("actionCreate_New_Category")
+ self.actionDelete_a_Category = QtWidgets.QAction(MainWindow)
+ self.actionDelete_a_Category.setObjectName("actionDelete_a_Category")
+ self.actionCreate_New_Category_2 = QtWidgets.QAction(MainWindow)
+ self.actionCreate_New_Category_2.setObjectName("actionCreate_New_Category_2")
+ self.actionDelete_a_Category_2 = QtWidgets.QAction(MainWindow)
+ self.actionDelete_a_Category_2.setObjectName("actionDelete_a_Category_2")
+ self.menuArtNet.addAction(self.actionChange_Connection_Details)
+ self.menuArtNet.addAction(self.actionChange_ArtNet_Root_Folder)
+ self.menuTags.addAction(self.actionCreate_New_Tag)
+ self.menuTags.addAction(self.actionDelete_a_Tag)
+ self.menuTags.addAction(self.actionEdit_a_Tag)
+ self.menuCategory.addAction(self.actionCreate_New_Category_2)
+ self.menuCategory.addAction(self.actionDelete_a_Category_2)
+ self.menuBar.addAction(self.menuArtNet.menuAction())
+ self.menuBar.addAction(self.menuTags.menuAction())
+ self.menuBar.addAction(self.menuCategory.menuAction())
+
+ self.retranslateUi(MainWindow)
+ QtCore.QMetaObject.connectSlotsByName(MainWindow)
+
+ def retranslateUi(self, MainWindow):
+ _translate = QtCore.QCoreApplication.translate
+ MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
+ self.presence_docker_button.setText(_translate("MainWindow", "<"))
+ self.prev_image_button.setText(_translate("MainWindow", "<"))
+ self.image_title_line.setText(_translate("MainWindow", "Image_Title"))
+ self.image_file_label.setText(_translate("MainWindow", "file_name"))
+ self.author_by.setText(_translate("MainWindow", "by"))
+ self.image_author_label.setText(_translate("MainWindow", "author_name"))
+ self.image_label.setText(_translate("MainWindow", "No Image"))
+ self.link_label.setText(_translate("MainWindow", "Source_Link"))
+ self.label_5.setText(_translate("MainWindow", "Link:"))
+ self.next_image_button.setText(_translate("MainWindow", ">"))
+ self.label.setText(_translate("MainWindow", "Search Tags"))
+ self.label_2.setText(_translate("MainWindow", "Selected Tags:"))
+ self.label_3.setText(_translate("MainWindow", "Implied Tags:"))
+ self.save_button.setText(_translate("MainWindow", "Save"))
+ self.import_button.setText(_translate("MainWindow", "Import Tags"))
+ self.prev_unknown_image_button.setText(_translate("MainWindow", "prev Unknown"))
+ self.next_unknown_image_button.setText(_translate("MainWindow", "next Unknown"))
+ self.delete_button.setText(_translate("MainWindow", "Delete"))
+ self.menuArtNet.setTitle(_translate("MainWindow", "ArtNet"))
+ self.menuTags.setTitle(_translate("MainWindow", "Tags"))
+ self.menuCategory.setTitle(_translate("MainWindow", "Category"))
+ self.actionChange_Connection_Details.setText(_translate("MainWindow", "Change DB Connection"))
+ self.actionChange_ArtNet_Root_Folder.setText(_translate("MainWindow", "Change ArtNet Root"))
+ self.actionCreate_New_Tag.setText(_translate("MainWindow", "Create New Tag"))
+ self.actionDelete_a_Tag.setText(_translate("MainWindow", "Delete a Tag"))
+ self.actionEdit_a_Tag.setText(_translate("MainWindow", "Edit a Tag"))
+ self.actionCreate_New_Category.setText(_translate("MainWindow", "Create New Category"))
+ self.actionDelete_a_Category.setText(_translate("MainWindow", "Delete a Category"))
+ self.actionCreate_New_Category_2.setText(_translate("MainWindow", "Create New Category"))
+ self.actionDelete_a_Category_2.setText(_translate("MainWindow", "Delete a Category"))
+
+
+if __name__ == "__main__":
+ import sys
+ app = QtWidgets.QApplication(sys.argv)
+ MainWindow = QtWidgets.QMainWindow()
+ ui = Ui_MainWindow()
+ ui.setupUi(MainWindow)
+ MainWindow.show()
+ sys.exit(app.exec_())
diff --git a/ArtNet/gui/picture_importer.ui b/ArtNet/gui/picture_importer.ui
new file mode 100644
index 0000000..22fbf14
--- /dev/null
+++ b/ArtNet/gui/picture_importer.ui
@@ -0,0 +1,564 @@
+
+
+ MainWindow
+
+
+
+ 0
+ 0
+ 827
+ 777
+
+
+
+ MainWindow
+
+
+
+ true
+
+
+
+ QLayout::SetDefaultConstraint
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ <
+
+
+
+ -
+
+
+ -
+
+
+ QLayout::SetMinimumSize
+
+
-
+
+
+ <
+
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
-
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
-
+
+
+
+ 10
+ 75
+ true
+
+
+
+ false
+
+
+ Image_Title
+
+
+ Qt::AlignCenter
+
+
+ false
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+ file_name
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 7
+ 50
+ false
+
+
+
+ by
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+
+ 10
+ 50
+ false
+ false
+
+
+
+ author_name
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ No Image
+
+
+ Qt::AlignCenter
+
+
+
+
+
+
+ -
+
+
+
+ 8
+ 75
+ true
+
+
+
+ Source_Link
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+ Link:
+
+
+
+ -
+
+
+
+ 7
+
+
+
+
+
+
+
+ -
+
+
+ QLayout::SetMinimumSize
+
+
-
+
+
+ >
+
+
+
+
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
-
+
+
+ QLayout::SetMinimumSize
+
+
-
+
+
+
+ 75
+ true
+
+
+
+ Search Tags
+
+
+
+ -
+
+
+ QLayout::SetMinimumSize
+
+
-
+
+
+
+ 400
+ 16777215
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 0
+
+
+
+
+ 400
+ 16777215
+
+
+
+
+ -
+
+
+
+ 75
+ true
+
+
+
+ Selected Tags:
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 100
+ 0
+
+
+
+
+ 400
+ 16777215
+
+
+
+
+ -
+
+
+
+ 9
+ 75
+ true
+
+
+
+ Implied Tags:
+
+
+
+ -
+
+
+
+ 100
+ 0
+
+
+
+
+ 400
+ 16777215
+
+
+
+
+
+
+ -
+
+
+ 5
+
+
+ QLayout::SetMinimumSize
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 16777215
+ 16777215
+
+
+
+ Save
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 16777215
+ 16777215
+
+
+
+ Import Tags
+
+
+
+ -
+
+
+ prev Unknown
+
+
+
+ -
+
+
+ next Unknown
+
+
+
+ -
+
+
+ Delete
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Change DB Connection
+
+
+
+
+ Change ArtNet Root
+
+
+
+
+ Create New Tag
+
+
+
+
+ Delete a Tag
+
+
+
+
+ Edit a Tag
+
+
+
+
+ Create New Category
+
+
+
+
+ Delete a Category
+
+
+
+
+ Create New Category
+
+
+
+
+ Delete a Category
+
+
+
+
+
+
diff --git a/ArtNet/gui/window.py b/ArtNet/gui/window.py
new file mode 100644
index 0000000..16b6c09
--- /dev/null
+++ b/ArtNet/gui/window.py
@@ -0,0 +1,999 @@
+import validators, os
+
+from PyQt5 import QtWidgets
+from PyQt5.QtCore import Qt, QSize, QUrl
+from PyQt5.QtGui import QPixmap, QResizeEvent, QKeyEvent, QStandardItemModel, QStandardItem, QMovie
+from PyQt5 import QtMultimedia
+from PyQt5.QtMultimediaWidgets import QVideoWidget
+
+from ArtNet.gui.picture_importer import Ui_MainWindow
+from ArtNet.gui.dialogs.db_connection_dialog.db_dialog import DBDialog
+from ArtNet.gui.dialogs.tag_modify_dialog.tag_mod_dialog import TagModifyDialog
+from ArtNet.gui.dialogs.tag_select_dialog.tag_select_dialog import TagSelectDialog
+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.web.link_generator import LinkGenerator
+
+
+class Window(QtWidgets.QMainWindow):
+
+ def __init__(self, main):
+ super(Window, self).__init__()
+
+ self.__main = main
+
+ self.__pixmap: QPixmap = None
+ self.__video: QVideoWidget = None
+ self.__player: QtMultimedia.QMediaPlayer = None
+ self.__showing_video: bool = False
+
+ self.__tmp_imageid_spinbox: int = None
+
+ self.presence_docker_open: bool = False
+ self.presence_docker: PresenceDocker = None
+ 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()
+
+ self.setting_up_data: bool = True
+ self.__data_changed: bool = False
+
+ self.ui = Ui_MainWindow()
+ self.ui.setupUi(self)
+ self.main_title = "ArtNet Picture Importer"
+
+ self.ui.actionChange_ArtNet_Root_Folder.triggered.connect(self.on_artnet_root_change_clicked)
+ self.ui.actionChange_Connection_Details.triggered.connect(self.on_db_connection_change_clicked)
+ self.ui.actionCreate_New_Tag.triggered.connect(self.on_tag_creation_clicked)
+ self.ui.actionEdit_a_Tag.triggered.connect(self.on_tag_edit_clicked)
+ self.ui.actionDelete_a_Tag.triggered.connect(self.on_tag_deletion_clicked)
+ self.ui.actionCreate_New_Category_2.triggered.connect(self.on_category_creation_clicked)
+ self.ui.actionDelete_a_Category_2.triggered.connect(self.on_category_deletion_clicked)
+
+ self.ui.imageNumberSpinBox.valueChanged.connect(self.on_image_id_spinbox_changed)
+
+ self.ui.next_image_button.clicked.connect(self.on_next_clicked)
+ self.ui.prev_image_button.clicked.connect(self.on_previous_clicked)
+ self.ui.save_button.clicked.connect(self.on_save_clicked)
+ 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.delete_button.clicked.connect(self.on_delete_image_clicked)
+
+ self.ui.presence_docker_button.clicked.connect(self.toggle_presence_docker)
+
+ 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.link_label.setText("No Source Available")
+ self.ui.image_file_label.setTextInteractionFlags(Qt.TextSelectableByMouse)
+ self.set_image_title_link()
+
+ self.on_tag_search_change()
+ self.center()
+
+ @property
+ def data_changed(self):
+ return self.__data_changed
+
+ @data_changed.setter
+ def data_changed(self, v: bool):
+ self.__data_changed = v
+
+ if self.curr_image_title is None:
+ return
+ if " (Not in Database)" in self.curr_image_title and v and not self.setting_up_data:
+ self.curr_image_title = self.curr_image_title.replace(" (Not in Database)", "")
+ self.setting_up_data = True
+ self.ui.image_title_line.setText(self.curr_image_title)
+ self.setting_up_data = False
+
+ def center(self):
+ """
+ Centers the window in the middle of the screen
+
+ Note: actually not the center but a good position due to images changing size!
+ :return:
+ """
+ screen = QtWidgets.QDesktopWidget().screenGeometry()
+ size = self.geometry()
+ self.move((screen.width() - size.width()) / 3, (screen.height() - size.height()) / 5)
+
+ def check_save_changes(self):
+ """
+ Check if there were changes to image settings. If yes ask for confirmation to save them.
+
+ :return:
+ """
+ if self.data_changed:
+ answer = QtWidgets.QMessageBox.question(self, "Save Changes?",
+ "There have been changes. Do you wish to save them?",
+ QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,
+ QtWidgets.QMessageBox.No)
+ if answer == QtWidgets.QMessageBox.Yes:
+ if not self.save_changes():
+ return False
+ return True
+
+ def save_changes(self) -> bool:
+ """
+ Save the changes to image data to the DB.
+ :return:
+ """
+ image_data = {
+ "ID": self.curr_art_id,
+ "title": self.curr_image_title,
+ "authors": self.curr_presences,
+ "path": self.curr_art_path,
+ "tags": self.curr_tags,
+ "link": self.curr_link,
+ "md5_hash": self.__main.get_md5_of_image(self.curr_art_path)
+ }
+
+ for presence in self.curr_presences:
+ if presence[-1] == "(Not in Database)":
+ msg = QtWidgets.QMessageBox()
+ msg.setWindowTitle("Invalid Presence Domain")
+ msg.setInformativeText("You've tried to save with a not working presence entry! " +
+ "Please add one from the database!")
+ msg.setIcon(QtWidgets.QMessageBox.Warning)
+ msg.exec_()
+ 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"])
+
+ self.set_temporary_status_message("Saved {0} ({1}) to ArtNet DB!"
+ .format(self.curr_image_title, self.curr_art_id), 5000)
+ self.update_window_title()
+ self.data_changed = False
+ return True
+
+ def set_temporary_status_message(self, text: str, duration: int):
+ """
+ Set a temporary status message (bottom left) for the given duration in milliseconds.
+ :param text:
+ :param duration:
+ :return:
+ """
+ self.ui.statusbar.showMessage(text, duration)
+
+ def create_presence(self, name: str, domain: str, artist: tuple, link: str):
+ """
+ Create a new Presence Entry with the given data
+ :param name:
+ :param domain:
+ :param artist:
+ :param link:
+ :return:
+ """
+ if len(name) == 0 or len(domain) == 0 or artist is None:
+ return
+ self.__main.db_connection.save_presence(name=name, domain=domain, artist_ID=artist[0], link=link)
+
+ def remove_presence(self, name: str, domain: str):
+ """
+ Remove the presence from the DB
+ :param name:
+ :param domain:
+ :return:
+ """
+ self.__main.db_connection.remove_presence(name, domain)
+
+ def get_authors(self, presence_name: str, presence_domain: str) -> list:
+ """
+ Query a search for the authors fitting the given strings
+ :param presence_name:
+ :param presence_domain:
+ :return: a list of tuples of (presence_name, presence_domain)
+ """
+ return self.__main.db_connection.search_fuzzy_presence(presence_name, presence_domain, all_if_empty=True)
+
+ def create_artist(self, ID: int, description: str):
+ """
+ Create a new artist with the given data (or update an exisitng one if ID is already taken
+ :param ID:
+ :param description:
+ :return:
+ """
+ self.__main.db_connection.save_artist(ID, description)
+ self.set_temporary_status_message("Created Artist {0}!".format(description), 3000)
+
+ def get_artists(self, search: str) -> list:
+ """
+ Query a search for the artists fitting the given data best. Search is fuzzy.
+ :param search: either an ID (int) or the description
+ :return:
+ """
+ try:
+ ID_int = int(search)
+ description = None
+ except ValueError:
+ ID_int = None
+ description = search
+
+ return self.__main.db_connection.search_fuzzy_artists(ID_int, description)
+
+ def get_artist(self, id: int) -> list:
+ """
+ Query for the artist matching id. Returns None if the data does not exactly fit.
+ :param id:
+ :return:
+ """
+ return self.__main.db_connection.get_artist(id)
+
+ def remove_artist(self, id: int):
+ """
+ Delte the given artist from the database.
+ :param id:
+ :return:
+ """
+ self.__main.db_connection.remove_artist(id)
+
+ def get_artist_presences(self, id: int) -> list:
+ """
+ Query for all presences associated with the given artist.
+ :param id:
+ :return:
+ """
+ return self.__main.db_connection.get_artist_presences(id)
+
+ def get_all_artists(self) -> list:
+ """
+ Queries the database for a list of all available arists (not presences).
+ :return:
+ """
+ return self.__main.db_connection.get_all_artists()
+
+ def get_presence(self, name: str, domain: str):
+ """
+ Query a search for the presence fitting the data
+ :param name:
+ :param domain:
+ :return:
+ """
+ result = self.__main.db_connection.get_presence(name, domain)
+
+ return result if len(result) != 0 else None
+
+ def remove_presence(self, name: str, domain: str):
+ """
+ Deletes a presence from the database and removes all Art_Author entries containing this presence.
+ :param name:
+ :param domain:
+ :return:
+ """
+ self.__main.db_connection.remove_presence(name, domain)
+
+ def get_presences_art(self, name: str, domain: str):
+ """
+ Query a list of art owned by the given presence
+ :param name:
+ :param domain:
+ :return:
+ """
+ return self.__main.db_connection.get_presences_art(name, domain)
+
+ def get_current_presences(self) -> list:
+ """
+ Get the presences currently associated with the current art
+ :return:
+ """
+ return self.curr_presences
+
+ def set_current_presences(self, presences: list):
+ """
+ Set the presences associated with the current art
+ :param presences: list of tuples of (name, domain)
+ """
+ if len(presences) > 1:
+ for name, domain in presences:
+ if domain == "(Not in Database)":
+ presences.remove((name, domain))
+ elif len(presences) == 0:
+ presences = [(self.curr_art_path.split("/")[0], "(Not in Database)")]
+ self.curr_presences = presences
+
+ if self.curr_presences is not None:
+ self.set_presence_label_text(self.curr_presences)
+
+ self.data_changed = True
+
+ def get_categories(self, search: str, all_if_empty: bool = False):
+ """
+ Fuzzy Query for categories in the database.
+
+ all_if_empty causes an empty search to return all categories instead of none
+ :param search:
+ :param all_if_empty:
+ :return:
+ """
+ if all_if_empty and len(search) == 0:
+ return self.__main.db_connection.get_all_categories()
+
+ return self.__main.db_connection.search_fuzzy_categories(search)
+
+ def get_image_link_from_line(self) -> str:
+ """
+ Gets the image link from the QLineEdit if it is a valid link.
+
+ Otherwise an empty string
+ :return:
+ """
+ return self.ui.link_line.text()
+
+ def set_presence_label_text(self, presences: list):
+ """
+ Set the label listing all current presences and include links if possible.
+ :param presences:
+ :return:
+ """
+ links = []
+ 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]
+ self.ui.image_author_label.setText(s)
+
+ def set_image_title_link(self) -> str:
+ """
+ Sets the Image title to a link if there is link data given for this image.
+ :return:
+ """
+ self.ui.link_label.setText("No Source Available")
+ link = self.ui.link_line.text()
+
+ if validators.url(link):
+ self.curr_link = link
+ self.data_changed = True
+ hyperlink = "{1}".format(link, "Source")
+ self.ui.link_label.setText(hyperlink)
+ return link
+ elif len(link) == 0:
+ return ""
+ else:
+ self.ui.link_label.setText("No Source Available")
+ self.set_temporary_status_message("Invalid link \"{0}\" detected!".format(link), 5000)
+ return ""
+
+ def get_tag(self, name: str) -> list:
+ """
+ Query a search for the tag to the DB and return the result
+ :param name:
+ :return:
+ """
+ return self.__main.db_connection.get_tag_by_name(name)
+
+ def get_tag_aliases(self, name: str) -> list:
+ """
+ Query a search for the tag's aliases to the DB
+
+ Note: Returns all aliases as a list of their IDs
+ :param name:
+ :return:
+ """
+ return self.__main.db_connection.get_tag_aliases(name)
+
+ def get_tag_implications(self, name: str) -> list:
+ """
+ Query a search for the tag's implications to the DB
+ :param name:
+ :return:
+ """
+ return self.__main.db_connection.get_tag_implications(name)
+
+ def get_tag_search_result(self, name: str) -> list:
+ """
+ Query a search for tags to the DB that are like name
+ :return:
+ """
+ return self.__main.db_connection.search_fuzzy_tag(name, all_if_empty=True)
+
+ def set_search_result_list(self, tags: list):
+ """
+ Set the tags in the search result list to tags
+ :param tags:
+ :return:
+ """
+ item_model = QStandardItemModel(self.ui.search_result_list)
+
+ for tag in tags:
+ 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:
+ 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):
+ # already selected, implied or aliased tags
+ item.setCheckState(Qt.Checked)
+ item.setFlags(flags)
+ item_model.appendRow(item)
+
+ item_model.itemChanged.connect(self.on_tag_search_item_changed)
+ self.ui.search_result_list.setModel(item_model)
+
+ def set_tag_list(self, tags: list, set_checked: bool = True, no_implication: bool = False):
+ """
+ Set the tags in the tag list to this.
+ Also updates the tag implication list if no_implication is False
+ :param tags:
+ :param set_checked:
+ :param no_implication: bool indicating if the implication list should also be updated
+ :return:
+ """
+ self.curr_tags = tags
+ item_model = QStandardItemModel(self.ui.tag_list)
+
+ for tag in tags:
+ item = QStandardItem(tag)
+ item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
+ item.setData(Qt.Unchecked, Qt.CheckStateRole)
+
+ item_model.appendRow(item)
+
+ if set_checked:
+ item.setCheckState(Qt.Checked)
+
+ item_model.itemChanged.connect(self.on_tag_item_changed)
+ self.ui.tag_list.setModel(item_model)
+
+ implied_tags = []
+ for x in self.curr_tags:
+ # collect all implied tags into a list
+ implied_tags += self.__main.db_connection.get_all_tag_implications(x)
+
+ self.set_implied_list(implied_tags)
+ self.curr_tag_aliases = list()
+ for tag in tags+implied_tags:
+ self.curr_tag_aliases += self.__main.db_connection.get_tag_aliases(tag)
+
+ self.data_changed = True
+
+ def set_implied_list(self, tags: list):
+ """
+ Sets the implied tags in the imply list
+ :param tags:
+ :return:
+ """
+ self.curr_imply_tags = tags
+ item_model = QStandardItemModel(self.ui.implied_tag_list)
+ done = []
+ for tag in tags:
+ if tag in done:
+ continue
+ else:
+ done.append(tag)
+ item = QStandardItem(tag)
+ item_model.appendRow(item)
+
+ self.ui.implied_tag_list.setModel(item_model)
+
+ self.data_changed = True
+
+ def display_image(self, image_title: str, image_authors: list, full_path: str, relative_path: str, art_ID: int,
+ link: str, file_name: str):
+ """
+ Display an image in the central widget
+ :param image_authors:
+ :param image_title:
+ :param full_path:
+ :param relative_path:
+ :param art_ID:
+ :param link:
+ :param file_name:
+ :return:
+ """
+ self.curr_art_id = art_ID
+ self.curr_art_path = relative_path
+ self.curr_image_title = image_title
+ self.curr_file_name = os.path.basename(full_path)
+ self.curr_link = link
+ self.set_current_presences(image_authors)
+
+ 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.__video.hide()
+ self.ui.image_label.show()
+ if file_ending in ["gif"]:
+ self.__showing_video = False
+ self.__pixmap = QMovie(full_path)
+ self.ui.image_label.setMovie(self.__pixmap)
+ self.__pixmap.start()
+ self.__pixmap.frameChanged.connect(self.on_movie_frame_changed)
+ elif file_ending in ["webm"]:
+ self.__showing_video = True
+ self.__video = QVideoWidget()
+ self.__player = QtMultimedia.QMediaPlayer(None, QtMultimedia.QMediaPlayer.VideoSurface)
+ self.__player.setVideoOutput(self.__video)
+ self.__player.setMedia(QtMultimedia.QMediaContent(QUrl.fromLocalFile(full_path)))
+ self.__player.play()
+
+ self.ui.image_frame.layout().addWidget(self.__video)
+ self.ui.image_label.hide()
+
+ self.__player.stateChanged.connect(self.on_movie_player_state_changed)
+ self.__player.positionChanged.connect(self.on_movie_position_changed)
+ else:
+ self.__showing_video = False
+ self.__pixmap = QPixmap(full_path)
+ self.ui.image_label.setPixmap(self.__pixmap)
+
+ self.ui.image_label.setScaledContents(True)
+ self.ui.image_label.setFixedSize(0, 0)
+ self.__image_resize()
+
+ 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.set_image_title_link()
+ self.set_image_id_spinbox()
+
+ def update_window_title(self):
+ """
+ Update the title of the window with the newest image title as given in text field
+ :return:
+ """
+ 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)}%)")
+
+ def set_image_id_spinbox(self):
+ """
+ Sets the imageIDSpinBox to the image ID of the currently displayed image
+ :return:
+ """
+ self.ui.imageNumberSpinBox.setMinimum(0)
+ self.ui.imageNumberSpinBox.setMaximum(len(self.__main.all_images)-1)
+ self.ui.imageNumberSpinBox.setValue(self.__main.curr_image_index)
+
+ def __image_resize(self):
+ """
+ Resize the given pixmap so that we're not out of the desktop.
+
+ :return: new scaled QPixmap
+ """
+ if self.ui.image_label.movie() is not None or self.__showing_video: # if QMovie was used instead of image
+ rect = self.geometry()
+ size = QSize(min(rect.width(), rect.height()), min(rect.width(), rect.height()))
+ if type(self.__pixmap) != QMovie: # using QVideoWidget?
+ pass
+ #self.__player.setScaledSize(size)
+ else:
+ self.__pixmap.setScaledSize(size)
+ return
+
+ size = self.__pixmap.size()
+ screen_rect = QtWidgets.QDesktopWidget().screenGeometry()
+
+ size.scale(int(screen_rect.width() * 0.6), int(screen_rect.height() * 0.6),
+ Qt.KeepAspectRatio)
+ self.ui.image_label.setFixedSize(size)
+
+ def resizeEvent(self, a0: QResizeEvent) -> None:
+ self.__image_resize()
+
+ def keyPressEvent(self, a0: QKeyEvent) -> None:
+ super(Window, self).keyPressEvent(a0)
+ if a0.key() == Qt.Key_Left:
+ self.on_previous_clicked()
+ elif a0.key() == Qt.Key_Right:
+ self.on_next_clicked()
+ elif a0.key() == Qt.Key_Return:
+ print("Pressed Enter!")
+ if self.__showing_video:
+ s = self.__player.state()
+ if self.__player.state() == QtMultimedia.QMediaPlayer.PlayingState:
+ self.__player.pause()
+ self.set_temporary_status_message("Paused the Video!", 3000)
+ elif self.__player.state() == QtMultimedia.QMediaPlayer.PausedState:
+ self.__player.play()
+ self.set_temporary_status_message("Started the Video!", 3000)
+ elif self.__player.state() == QtMultimedia.QMediaPlayer.StoppedState:
+ self.__player.play()
+ self.set_temporary_status_message("Restarted the Video!", 3000)
+
+ elif type(self.__pixmap) == QMovie:
+ if self.__pixmap.state() == QMovie.Paused:
+ self.__pixmap.start()
+ self.set_temporary_status_message("Started the Video!", 3000)
+ elif self.__pixmap.state() == QMovie.Running:
+ self.__pixmap.setPaused(True)
+ self.set_temporary_status_message("Paused the Video!", 3000)
+
+ def on_movie_player_state_changed(self, state: int):
+ self.__image_resize()
+ if QtMultimedia.QMediaPlayer.StoppedState == state: # player stopped
+ self.set_temporary_status_message("Reached end of Video!", 2000)
+
+ def on_movie_position_changed(self, position):
+ pass
+
+ def on_movie_frame_changed(self, frame_number: int):
+ if type(self.__pixmap) != QMovie:
+ return
+ if frame_number == 0:
+ self.set_temporary_status_message("Reached end of Video!", 2000)
+ self.__pixmap.setPaused(True)
+
+ def on_save_clicked(self):
+ print("Clicked Save!")
+ self.set_image_title_link()
+ self.save_changes()
+
+ def on_import_tags_clicked(self):
+ print("Clicked Import!")
+ dialog = TagImportDialog(self)
+ if len(self.get_image_link_from_line()) == 0 or self.get_image_link_from_line() == '(Unknown)':
+ 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
+ else:
+ url = self.get_image_link_from_line()
+
+ r = self.__main.scrape_tags(self.curr_file_name, url=url,
+ art_ID=self.curr_art_id)
+
+ if r is None:
+ msg = QtWidgets.QMessageBox()
+ msg.setWindowTitle("Unsupported Domain")
+ msg.setInformativeText("Could not predict a supported domain!")
+ msg.setIcon(QtWidgets.QMessageBox.Warning)
+ msg.exec_()
+ return
+
+ self.ui.link_line.setText(url)
+ self.set_image_title_link()
+ tags, artists = r
+
+ i = 0
+ while i < len(tags): # workaround for an issue with altering lists during iteration
+ r = self.__main.db_connection.get_tag_by_name(tags[i])
+ if len(r) > 0:
+ self.curr_tags.append(tags[i])
+ self.data_changed = True
+ tags.remove(tags[i])
+ continue
+ else:
+ i += 1
+ self.set_tag_list(self.curr_tags)
+
+ if len(tags) == 0:
+ msg = QtWidgets.QMessageBox()
+ msg.setWindowTitle("Nothing to import!")
+ msg.setInformativeText("There were no tags to import for this art!")
+ msg.setIcon(QtWidgets.QMessageBox.Information)
+ msg.exec_()
+ return
+ dialog.set_import_tag_list(tags)
+ dialog.set_detected_artists(artists)
+ dialog.set_used_link(url)
+ dialog.to_import = tags
+
+ result = dialog.exec_()
+ if result is None:
+ self.set_tag_list(self.curr_tags)
+ return
+
+ self.__main.import_tags(self.curr_art_id, result)
+ self.set_tag_list(self.curr_tags)
+
+ def on_next_clicked(self):
+ print("Clicked Next!")
+ if not self.check_save_changes():
+ return
+
+ self.__main.curr_image_index += 1
+ if self.__main.curr_image_index >= len(self.__main.all_images):
+ self.__main.curr_image_index = 0
+ self.__main.change_image()
+
+ if self.presence_docker_open:
+ self.toggle_presence_docker()
+ self.on_tag_search_change()
+
+ def on_image_title_change(self):
+ self.data_changed = True
+ self.curr_image_title = self.ui.image_title_line.text()
+
+ def on_previous_clicked(self):
+ print("Clicked previous!")
+ if not self.check_save_changes():
+ return
+
+ self.__main.curr_image_index -= 1
+ if self.__main.curr_image_index < 0:
+ self.__main.curr_image_index += len(self.__main.all_images)
+
+ self.__main.change_image()
+
+ if self.presence_docker_open:
+ self.toggle_presence_docker()
+ self.on_tag_search_change()
+
+ def toggle_presence_docker(self):
+ print("Clicked presence docker button!")
+ if not self.presence_docker_open:
+ 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.presence_docker_open = True
+ else:
+ self.presence_docker.setParent(None)
+ self.ui.presence_docker_button.setText("<")
+
+ self.presence_docker.destroy()
+ self.presence_docker = None
+ self.presence_docker_open = False
+
+ def on_artnet_root_change_clicked(self):
+ print("Clicked changing ArtNet root!")
+ dialog = QtWidgets.QFileDialog(self, 'Choose new ArtNet root:')
+ dialog.setFileMode(QtWidgets.QFileDialog.Directory)
+ dialog.setOptions(QtWidgets.QFileDialog.ShowDirsOnly)
+
+ directory = dialog.getExistingDirectory()
+ self.__main.change_root(directory)
+
+ def on_db_connection_change_clicked(self):
+ print("Clicked db connection change!")
+ dialog = DBDialog(self)
+ prev_db_data = self.__main.get_db_connection_details()
+ dialog.ui.user_line_edit.setText(prev_db_data["user"])
+ dialog.ui.password_line_edit.setText(prev_db_data["password"])
+ dialog.ui.host_line_edit.setText(prev_db_data["host"])
+ dialog.ui.database_line_edit.setText(prev_db_data["database"])
+ dialog.ui.port_line_edit.setText(str(prev_db_data["port"]))
+
+ db_data: dict = dialog.exec_()
+ 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"])
+
+ def on_tag_creation_clicked(self):
+ print("Clicked Tag Creation!")
+ dialog = TagModifyDialog(self, create_tag=True)
+
+ tag_data: dict = dialog.exec_()
+ print("Got Tag data", tag_data)
+ if tag_data is None or len(tag_data.keys()) == 0: # got canceled?
+ return
+
+ if len(self.get_tag(tag_data["name"])) > 0:
+ QtWidgets.QMessageBox.information(self, "Duplicate Tag", "The tag \"{0}\" already exists in the db!"
+ .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"])
+ self.on_tag_search_change()
+
+ def on_tag_deletion_clicked(self):
+ print("Clicked Tag Deletion!")
+ dialog = TagSelectDialog(self, delete_tag=True)
+
+ tag = dialog.exec_()
+ print("Got Tag", tag)
+ if tag is None or len(tag) == 0:
+ return
+
+ confirmation_reply = QtWidgets.QMessageBox.question(self, "Delete Tag \"{0}\"".format(tag["name"]),
+ "Are you sure you want to delete this Tag?",
+ QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,
+ QtWidgets.QMessageBox.No)
+ if confirmation_reply == QtWidgets.QMessageBox.No:
+ return
+
+ self.__main.db_connection.remove_tag(tag["name"])
+ self.on_tag_search_change()
+
+ def force_edit_tag_dialog(self, name: str):
+
+ edit_dialog = TagModifyDialog(self, create_tag=True)
+ edit_dialog.ui.tag_name_line.setText(name)
+ edit_dialog.ui.tag_description_area.setText("")
+ edit_dialog.set_selected_alias_tags([], set_checked=True)
+ edit_dialog.alias_selection = []
+ edit_dialog.set_selected_implicated_tags([], set_checked=True)
+ edit_dialog.implication_selection = []
+ edit_dialog.category_selection = []
+ edit_dialog.set_all_categories()
+
+ tag_data = edit_dialog.exec_()
+ print("Got Tag data", tag_data)
+ if tag_data is None or len(tag_data.keys()) == 0:
+ return None
+ if len(tag_data["category"]) == 0:
+ answer = QtWidgets.QMessageBox.information(self, "No Category",
+ "There has been no Category selected for this tag! "
+ "No tag is allowed without a category!")
+ return None
+
+ if len(self.get_tag(tag_data['name'])) > 0:
+ QtWidgets.QMessageBox.information(self, "Tag already exists",
+ "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"])
+ self.on_tag_search_change()
+ return tag_data
+
+ def on_tag_edit_clicked(self):
+ print("Clicked Tag Editing!")
+ select_dialog = TagSelectDialog(self, delete_tag=False)
+
+ tag = select_dialog.exec_()
+ if tag is None or len(tag) == 0:
+ return
+
+ tag['aliases'] = self.__main.db_connection.get_tag_aliases(tag["name"])
+ tag['implications'] = self.__main.db_connection.get_tag_implications(tag["name"])
+
+ edit_dialog = TagModifyDialog(self, create_tag=False)
+ edit_dialog.ui.tag_name_line.setText(tag["name"])
+ edit_dialog.ui.tag_description_area.setText(tag["description"])
+ edit_dialog.set_selected_alias_tags(tag["aliases"], set_checked=True)
+ edit_dialog.alias_selection = tag["aliases"]
+ edit_dialog.set_selected_implicated_tags(tag["implications"], set_checked=True)
+ edit_dialog.implication_selection = tag["implications"]
+ edit_dialog.category_selection = tag["category"]
+ edit_dialog.set_all_categories()
+
+ tag_data = edit_dialog.exec_()
+ print("Got Tag data", tag_data)
+ if tag_data is None or len(tag_data.keys()) == 0:
+ return
+
+ if "old_tag_name" not in tag_data.keys():
+ tag_data["old_tag_name"] = None
+
+ self.__main.db_connection.edit_tag(name=tag_data["name"], description=tag_data["description"],
+ aliases=tag_data["aliases"], implications=tag_data["implications"],
+ category=tag_data["category"], old_tag=tag_data["old_tag_name"])
+ self.on_tag_search_change()
+
+ def on_tag_search_item_changed(self, item: QStandardItem):
+ if item.checkState() == Qt.Checked:
+ self.curr_tags.append(item.text())
+ if item.checkState() == Qt.Unchecked:
+ if item.text() in self.curr_tags:
+ self.curr_tags.remove(item.text())
+ else:
+ return
+ self.set_tag_list(self.curr_tags)
+
+ def on_tag_item_changed(self, item: QStandardItem):
+ print("Item {0} has changed!".format(item.text()))
+ if item.checkState() == Qt.Unchecked:
+ if item.text() in self.curr_tags:
+ self.curr_tags.remove(item.text())
+ self.set_tag_list(self.curr_tags)
+ self.on_tag_search_change()
+ else:
+ raise Exception("Something went terribly wrong!")
+
+ def on_tag_search_change(self):
+ tags = self.__main.db_connection.search_fuzzy_tag(self.ui.tag_search_bar.text(), all_if_empty=True)
+
+ result = []
+ for tag_name, tag_desc, tag_category in tags:
+ result.append(tag_name)
+
+ self.set_search_result_list(result)
+
+ def on_category_creation_clicked(self):
+ dialog = CategoryModDialog(self, delete_category=False)
+
+ data = dialog.exec_()
+ if data is None:
+ return
+
+ self.__main.db_connection.save_category(data["name"])
+
+ def on_category_deletion_clicked(self):
+ dialog = CategoryModDialog(self, delete_category=True)
+
+ data = dialog.exec_()
+ if data is None:
+ return
+
+ 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()
+ print("Previous unknown image clicked!")
+
+ result = QtWidgets.QMessageBox.question(self, "Switch Image?",
+ "Do you really want to skip to image #{1} \"{0}\"?"
+ .format(self.__main.all_images[unknown_image_index],
+ unknown_image_index))
+ if result == QtWidgets.QMessageBox.Yes:
+ self.__main.curr_image_index = unknown_image_index
+ self.__main.change_image()
+
+ def on_next_unknown_image_clicked(self):
+ unknown_image_index = self.__main.get_next_unknown_image()
+ print("Next unknown image clicked!")
+
+ result = QtWidgets.QMessageBox.question(self, "Switch Image?",
+ "Do you really want to skip to image #{1} \"{0}\"?"
+ .format(self.__main.all_images[unknown_image_index],
+ unknown_image_index))
+ if result == QtWidgets.QMessageBox.Yes:
+ self.__main.curr_image_index = unknown_image_index
+ self.__main.change_image()
+
+ def on_image_id_spinbox_changed(self, v: int):
+ if self.__tmp_imageid_spinbox == v:
+ print("SpinBox change detected!")
+ result = QtWidgets.QMessageBox.question(self, "Switch Image?",
+ "Do you really want to skip to image #{1} \"{0}\"?"
+ .format(self.__main.all_images[v],
+ v))
+ if result == QtWidgets.QMessageBox.Yes:
+ self.__main.curr_image_index = v
+ self.__main.change_image()
+
+ self.__tmp_imageid_spinbox: int = v
+
+ def on_delete_image_clicked(self):
+ print("Delete clicked!")
+ art_hash = self.__main.get_md5_of_image(self.curr_art_path)
+ if self.__main.db_connection.get_art_by_hash(art_hash) is not None:
+ print("Delete on known image")
+ confirm_result = QtWidgets.QMessageBox.question(self, "Delete data?", "Do you really wish to delete all "
+ "data from the DB about this image?")
+
+ if confirm_result == QtWidgets.QMessageBox.Yes:
+ print(f"deleting image data of \"{self.curr_image_title}\"")
+ self.__main.db_connection.remove_image(art_hash)
+ else:
+ return
+
+ else:
+ print("Delete on unknown image")
+ confirm_result = QtWidgets.QMessageBox.question(self, "Delete image?", "Do you really wish to delete this "
+ "image?")
+
+ if confirm_result == QtWidgets.QMessageBox.Yes:
+ print("deleting image file")
+ self.__main.delete_image(self.curr_art_path)
+ else:
+ return
+
+ self.__main.change_image()
diff --git a/ArtNet/web/__init__.py b/ArtNet/web/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/ArtNet/web/link_generator.py b/ArtNet/web/link_generator.py
new file mode 100644
index 0000000..5de42c6
--- /dev/null
+++ b/ArtNet/web/link_generator.py
@@ -0,0 +1,169 @@
+import re
+import requests
+import lxml.html
+from urllib.parse import urlparse
+
+DOMAIN_UNKNOWN = -1
+
+
+class DomainIdentifier:
+ """
+ Makeshift-automated enum class to allow runtime expansion of an enum.
+
+ Create an instance to get a new unique domain identifier that can resolve to an int.
+
+ TODO find better way :D
+ """
+
+ __domain_list = []
+
+ def __init__(self, name: str):
+ self.__name = name
+ self.__domain_number = len(DomainIdentifier.__domain_list)
+
+ if DomainIdentifier.get_identifier(name) is not None:
+ raise Exception("The domain \"{0}\" is already in the list! Tried to create a duplicate!".format(name))
+
+ DomainIdentifier.__domain_list.append(self)
+
+ @property
+ def name(self) -> str:
+ return self.__name
+
+ @property
+ def identifier(self) -> int:
+ return self.__domain_number
+
+ @staticmethod
+ def get_identifier(name: str) -> "DomainIdentifier":
+ for d in DomainIdentifier.__domain_list:
+ if d.name == name:
+ return d
+ return None
+
+
+class DomainLinkGenerator:
+
+ def __init__(self, domain: DomainIdentifier):
+ self.__identifier = domain
+
+ def match_file_name(self, file_name) -> bool:
+ """
+ Checks if a given file name is plausible to be used by the given domain
+ :param file_name:
+ :return:
+ """
+ raise NotImplementedError
+
+ def get_domain_name(self) -> str:
+ return self.__identifier.name
+
+ def get_identifier(self) -> int:
+ """
+ Return the Identifier for this Predictors domain.
+ :return:
+ """
+ return self.__identifier.identifier
+
+ def construct_link(self, file_name: str) -> str:
+ """
+ Construct a link by inserting the file_name into the known link pattern
+ :param file_name:
+ :return:
+ """
+ raise NotImplementedError
+
+ def scrape_tags(self, url: str, headers: dict) -> list:
+ """
+ Scrape the tags from the given url for all tags associated with the work.
+ :param url:
+ :param headers:
+ :return:
+ """
+ raise NotImplementedError
+
+
+class LinkGenerator:
+ """
+ Predict and generate valid links to the file
+ by matching the given file names against known patterns of the origin domain.
+ """
+
+ __instance = None # Singleton holder
+
+ def __init__(self):
+ self.__link_generators = []
+ import ArtNet.web.domains # implements return_all_domains() which returns instances of all domains
+ # return_all_domains() is to return a list of all DomainLinkGenerator instances that are to be used
+
+ for p in ArtNet.web.domains.return_all_domains():
+ self.register_domain_predictor(p)
+
+ @staticmethod
+ def get_instance() -> "LinkGenerator":
+ """
+ Gets the current instance
+ :return:
+ """
+ if LinkGenerator.__instance is None:
+ LinkGenerator.__instance = LinkGenerator()
+ return LinkGenerator.__instance
+
+ def register_domain_predictor(self, predictor: DomainLinkGenerator):
+ """
+ Register another DomainValidator to be used by this LinkPredictor
+ :param predictor:
+ :param domain: int identifier for the domain
+ :return:
+ """
+ if predictor not in self.__link_generators:
+ self.__link_generators.append(predictor)
+
+ def predict_domain(self, file_name: str) -> int:
+ """
+ Predict the possible domains of the given file by guessing via the filename
+ :param file_name:
+ :return:
+ """
+ for g in self.__link_generators:
+ try:
+ if g.match_file_name(file_name): # TODO stop accepting first match
+ return g.get_identifier()
+ except NotImplementedError:
+ pass
+ return DOMAIN_UNKNOWN
+
+ def construct_link(self, file_name: str, domain: int) -> str:
+ """
+ Construct a valid link to access the web page where the file name (hopefully) originated from.
+ :param file_name:
+ :param domain:
+ :return:
+ """
+ for g in self.__link_generators:
+ if g.get_identifier() == domain: # TODO stop accepting first match
+ try:
+ return g.construct_link(file_name)
+ except NotImplementedError:
+ return None
+
+ return None
+
+ def scrape_tags(self, url: str, domain: int) -> dict:
+ """
+ Scrapes the tags from the given url
+ :param url:
+ :param domain:
+ :return:
+ """
+ headers = {
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; rv:76.0) Gecko/20100101 Firefox/76.0"
+ }
+ url_domain = urlparse(url).netloc
+ for g in self.__link_generators:
+ if g.get_identifier() == domain or g.get_domain_name() == url_domain:
+ try:
+ return g.scrape_tags(url, headers)
+ except NotImplementedError:
+ pass
+ return None
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..7092126
--- /dev/null
+++ b/README.md
@@ -0,0 +1,90 @@
+# ArtNet
+
+ArtNet is a database schema to tag and organize images, videos and
+other media files for easy search & traceability.
+Create your very own reference database!
+
+## Dependencies
+The database schema has been developed with [PostgreSQL 13](https://www.postgresql.org/).
+
+The GUI runs with Python 3.9 as well as:
+ * PyQT5
+ * PyYAML
+ * other dependencies listed in requirements.txt
+
+## Features
+
+### Editor GUI
+The GUI allows easy editing, creation and deletion of tags, art entries,
+presence entries and artists while viewing the file in question.
+
+The GUI can connect to different databases via
+"ArtNet" > "Change DB Connection" .
+The login credentials are saved in the directory `.artnet`
+located in the current working directory.
+
+**Important:** Configuration Encryption is experimental and **unsafe**!
+
+While the GUI makes an attempt to encrypt the configuration
+file, the password for encryption is, as of writing, inside the source code.
+The login data should therefore to be treated as plain text and
+additionally secured in better ways such as proper file permissions.
+
+### Tagging
+**You can:**
+ * create, edit & delete your own tags
+ * imply tags with other tags (e.g. *banana* implies *fruit*)
+ * create aliases for different tags that mean the same (e.g. *posing* being an alias to *pose*)
+ * categorize your tags
+ * create modules to import tagging from known sources
+
+## Concepts & Names
+
+### ArtNet Root
+The root folder in which your references are stored.
+Ultimately the structure is up to you but it is assumed for
+new entries to be somewhat like this:
+```
+ArtNetRoot/
+ presence_name_A/
+ image_A.jpg
+ vid_B.mp4
+ presence_name_B/
+ image_D.jpg
+ presence_name_C/
+ image_C.jpg
+```
+A structure like this will allow to the GUI to show a correct guess
+for the presence name when encountering an unknown art entry.
+
+### Presences & Authors
+In ArtNet you can create a presence for every account an artist
+has on different or the same website. These presences are connected to
+an Artist entry and therefore allow the connection of many accounts to the same artist.
+
+A presence consists of the presence name and the domain it is used on as well as an
+optional direct link to the account.
+
+Multiple presences can be associated with any given art allowing for correct
+attribution for collaborations and similar.
+
+-- TODO add artist_presence_relation_example_diagram here --
+
+### Art
+Basically the files to be organized and tagged.
+Each art piece is a file associated with many tags, a source link and
+one or more presences.
+
+### Tag
+A tag is keyword that is later to be associated with Art.
+
+#### Aliases
+Each tag can have aliases which are to be considered equal to
+the current tag and therefore also associated with the art.
+This relation is bidirectional.
+
+#### Implications
+Each tag can also imply other tags, meaning when
+the current tag is associated with an art piece the implied tags
+are to be considered associated too but not vice versa.
+This relation is unidirectional.
diff --git a/__main__.py b/__main__.py
new file mode 100644
index 0000000..80ae341
--- /dev/null
+++ b/__main__.py
@@ -0,0 +1,10 @@
+from ArtNet.artnet_manager import ArtNetManager
+
+# TODO fix DB bug
+# TODO 1. Open known image
+# TODO 2. edit a tag on the current image & save the edit
+# TODO 3. attempt to save the current image
+
+if __name__ == "__main__":
+ am = ArtNetManager()
+ am.run()