ArtNet App

Moved ArtNet from another repo here
master
Peery 4 years ago
parent a179ed4fb6
commit 99069ba77d

@ -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)

@ -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))

@ -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)

@ -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"))

@ -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

@ -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_())

@ -0,0 +1,154 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Artist_Mod_Dialog</class>
<widget class="QDialog" name="Artist_Mod_Dialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>311</width>
<height>205</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QFrame" name="dialog_frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="dialog_topic">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<pointsize>11</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Artist Topic</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QLabel" name="id_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Artist ID:</string>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="id_layout">
<item>
<widget class="QDoubleSpinBox" name="id_spinbox">
<property name="decimals">
<number>0</number>
</property>
<property name="maximum">
<double>10000.000000000000000</double>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="description_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Artist Description:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="description_line"/>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>Artist_Mod_Dialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>Artist_Mod_Dialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

@ -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()}

@ -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_())

@ -0,0 +1,137 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>category_modify_dialog</class>
<widget class="QDialog" name="category_modify_dialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>398</width>
<height>298</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="dialog_topic">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<pointsize>12</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Category Topic</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QFrame" name="frame_2">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="category_name_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<pointsize>10</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Category Name:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="category_name_line"/>
</item>
<item>
<widget class="QListView" name="other_categories_list"/>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>category_modify_dialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>category_modify_dialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

@ -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_())

@ -0,0 +1,216 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DBConnection</class>
<widget class="QDialog" name="DBConnection">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>328</width>
<height>262</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QFrame" name="dialog_frame">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="title_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<pointsize>10</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="text">
<string>Edit PostgreSQL Connection</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="host_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Host:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="host_line_edit"/>
</item>
<item>
<widget class="QLabel" name="port_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Port:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="port_line_edit"/>
</item>
<item>
<widget class="QLabel" name="database_label">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Database:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="database_line_edit"/>
</item>
<item>
<widget class="QLabel" name="user_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>User:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="user_line_edit"/>
</item>
<item>
<widget class="QLabel" name="password_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Password:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="password_line_edit">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>DBConnection</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>DBConnection</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

@ -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

@ -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

@ -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_())

@ -0,0 +1,222 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PresenceModDialog</class>
<widget class="QDialog" name="PresenceModDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>376</width>
<height>405</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="dialog_topic_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<pointsize>11</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Presence Topic</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QFrame" name="frame_2">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="spacing">
<number>5</number>
</property>
<item>
<widget class="QLabel" name="presence_name_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Presence Name:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="presence_name_line"/>
</item>
<item>
<widget class="QLabel" name="presence_domain_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Presence Domain:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="presence_domain_line"/>
</item>
<item>
<widget class="QLabel" name="presence_link_label">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Presence Link:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="presence_link_list"/>
</item>
<item>
<widget class="QLabel" name="artist_label">
<property name="font">
<font>
<pointsize>10</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Artist:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="artist_search_line"/>
</item>
<item>
<widget class="QListView" name="artist_result_list"/>
</item>
<item>
<widget class="QListView" name="artist_selection_list"/>
</item>
<item>
<layout class="QHBoxLayout" name="artist_edit_layout">
<item>
<widget class="QPushButton" name="create_artist_button">
<property name="text">
<string>Create Artist</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="edit_artist_button">
<property name="text">
<string>Edit Artist</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="delete_artist_button">
<property name="text">
<string>Delete Artist</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>PresenceModDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>PresenceModDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

@ -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

@ -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_())

@ -0,0 +1,153 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>presence_selection_dialog</class>
<widget class="QDialog" name="presence_selection_dialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>330</width>
<height>260</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label">
<property name="font">
<font>
<pointsize>11</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Select Presence</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QFrame" name="frame_2">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QLabel" name="label_2">
<property name="font">
<font>
<pointsize>10</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Name:</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="presence_name_search_line"/>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="label_3">
<property name="font">
<font>
<pointsize>10</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Domain:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="presence_domain_search_line"/>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QListView" name="presence_search_list"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>presence_selection_dialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>presence_selection_dialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

@ -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

@ -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_())

@ -0,0 +1,239 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Dialog</class>
<widget class="QDialog" name="Dialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>775</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<pointsize>12</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Detected Tags</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QFrame" name="frame_4">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QLabel" name="label_4">
<property name="font">
<font>
<pointsize>9</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Detected Artists</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="detected_artists_label">
<property name="font">
<font>
<pointsize>8</pointsize>
</font>
</property>
<property name="text">
<string>(None)</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="used_link_label">
<property name="text">
<string>-No Link Used-</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QFrame" name="frame_2">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="label_2">
<property name="font">
<font>
<pointsize>10</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>To be Imported:</string>
</property>
</widget>
</item>
<item>
<widget class="QListView" name="import_list">
<property name="minimumSize">
<size>
<width>300</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="remove_all_button">
<property name="text">
<string>Remove All</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QFrame" name="frame_3">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label_3">
<property name="font">
<font>
<pointsize>10</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>To Be Ignored:</string>
</property>
</widget>
</item>
<item>
<widget class="QListView" name="ignored_list">
<property name="minimumSize">
<size>
<width>300</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="import_all_button">
<property name="text">
<string>Import All</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>Dialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>Dialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

@ -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

@ -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_())

@ -0,0 +1,375 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TagModify</class>
<widget class="QDialog" name="TagModify">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>587</width>
<height>846</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QFrame" name="dialog_frame">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="dialog_topic">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<pointsize>10</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Tag Topic</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="tag_name_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Tag Name:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="tag_name_line"/>
</item>
<item>
<widget class="QLabel" name="tag_description_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Description:</string>
</property>
</widget>
</item>
<item>
<widget class="QTextEdit" name="tag_description_area">
<property name="maximumSize">
<size>
<width>16777215</width>
<height>100</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="category_label">
<property name="text">
<string>Category:</string>
</property>
</widget>
</item>
<item>
<widget class="QListView" name="category_list">
<property name="maximumSize">
<size>
<width>16777215</width>
<height>100</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QFrame" name="tag_alias_frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="tag_alias_label">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Tag Aliases</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="tag_alias_search_line"/>
</item>
<item>
<widget class="QFrame" name="tag_alias_label_frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="tag_alias_search_label">
<property name="text">
<string>Search Result:</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="tag_alias_selection_label">
<property name="text">
<string>Selection:</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QFrame" name="tag_alias_list_frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QListView" name="tag_alias_search_list">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>100</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QListView" name="tag_alias_selection_list">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>100</height>
</size>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QFrame" name="tag_implication_frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QLabel" name="tag_implication_label">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Tag Implications</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="tag_implication_search_line"/>
</item>
<item>
<widget class="QFrame" name="tag_implication_label_frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="tag_implication_search_label">
<property name="text">
<string>Search Result:</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="tag_implication_selection_label">
<property name="text">
<string>Selection:</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QFrame" name="tag_implication_list_frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QListView" name="tag_implication_search_list">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>100</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QListView" name="tag_implication_selection_list">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>100</height>
</size>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>TagModify</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>TagModify</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

@ -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

@ -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_())

@ -0,0 +1,104 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TagSelection</class>
<widget class="QDialog" name="TagSelection">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QFrame" name="top_frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="select_tag_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Select Tag</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="tag_search_line"/>
</item>
<item>
<widget class="QListView" name="search_result_list"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>TagSelection</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>TagSelection</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

@ -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()

@ -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_())

@ -0,0 +1,190 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>presence_docker</class>
<widget class="QWidget" name="presence_docker">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>338</width>
<height>576</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="docker_topic_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<pointsize>13</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Author (Presence)</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="search_author_label">
<property name="font">
<font>
<pointsize>10</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Search Authors:</string>
</property>
</widget>
</item>
<item>
<widget class="QFrame" name="search_frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QFrame" name="name_frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="name_label">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Name:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="name_line"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QFrame" name="domain_frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QLabel" name="domain_label">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Domain:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="domain_line"/>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QListView" name="search_author_list"/>
</item>
<item>
<widget class="QLabel" name="selected_presence_label">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Selected Presences:</string>
</property>
</widget>
</item>
<item>
<widget class="QListView" name="selected_presence_list"/>
</item>
<item>
<widget class="QFrame" name="presence_docker_button_frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="create_presence_button">
<property name="text">
<string>Create Presence</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="edit_presence_button">
<property name="text">
<string>Edit Presence</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="delete_presence_button">
<property name="text">
<string>Delete Presence</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

@ -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)

@ -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_())

@ -0,0 +1,564 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>827</width>
<height>777</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<property name="enabled">
<bool>true</bool>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
</property>
<item>
<widget class="QFrame" name="left_frame">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QToolButton" name="presence_docker_button">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&lt;</string>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="presence_docker_layout"/>
</item>
<item>
<layout class="QVBoxLayout" name="left_layout">
<property name="sizeConstraint">
<enum>QLayout::SetMinimumSize</enum>
</property>
<item>
<widget class="QToolButton" name="prev_image_button">
<property name="text">
<string>&lt;</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QFrame" name="center_frame">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLineEdit" name="image_title_line">
<property name="font">
<font>
<pointsize>10</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="text">
<string>Image_Title</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="readOnly">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="imageNumberSpinBox"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QLabel" name="image_file_label">
<property name="text">
<string>file_name</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="author_by">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<pointsize>7</pointsize>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>by</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="image_author_label">
<property name="font">
<font>
<pointsize>10</pointsize>
<weight>50</weight>
<italic>false</italic>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>author_name</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QFrame" name="image_frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="image_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>No Image</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QLabel" name="link_label">
<property name="font">
<font>
<pointsize>8</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Source_Link</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>Link:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="link_line">
<property name="font">
<font>
<pointsize>7</pointsize>
</font>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="right_layout">
<property name="sizeConstraint">
<enum>QLayout::SetMinimumSize</enum>
</property>
<item>
<widget class="QToolButton" name="next_image_button">
<property name="text">
<string>&gt;</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QFrame" name="right_frame">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<layout class="QVBoxLayout" name="tag_layout">
<property name="sizeConstraint">
<enum>QLayout::SetMinimumSize</enum>
</property>
<item>
<widget class="QLabel" name="label">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Search Tags</string>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="tag_list_layout">
<property name="sizeConstraint">
<enum>QLayout::SetMinimumSize</enum>
</property>
<item>
<widget class="QLineEdit" name="tag_search_bar">
<property name="maximumSize">
<size>
<width>400</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QListView" name="search_result_list">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>400</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Selected Tags:</string>
</property>
</widget>
</item>
<item>
<widget class="QListView" name="tag_list">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>400</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="font">
<font>
<pointsize>9</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Implied Tags:</string>
</property>
</widget>
</item>
<item>
<widget class="QListView" name="implied_tag_list">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>400</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="tag_button_layout">
<property name="spacing">
<number>5</number>
</property>
<property name="sizeConstraint">
<enum>QLayout::SetMinimumSize</enum>
</property>
<item>
<widget class="QPushButton" name="save_button">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Save</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="import_button">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Import Tags</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="prev_unknown_image_button">
<property name="text">
<string>prev Unknown</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="next_unknown_image_button">
<property name="text">
<string>next Unknown</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="delete_button">
<property name="text">
<string>Delete</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>827</width>
<height>19</height>
</rect>
</property>
<widget class="QMenu" name="menuArtNet">
<property name="title">
<string>ArtNet</string>
</property>
<addaction name="actionChange_Connection_Details"/>
<addaction name="actionChange_ArtNet_Root_Folder"/>
</widget>
<widget class="QMenu" name="menuTags">
<property name="title">
<string>Tags</string>
</property>
<addaction name="actionCreate_New_Tag"/>
<addaction name="actionDelete_a_Tag"/>
<addaction name="actionEdit_a_Tag"/>
</widget>
<widget class="QMenu" name="menuCategory">
<property name="title">
<string>Category</string>
</property>
<addaction name="actionCreate_New_Category_2"/>
<addaction name="actionDelete_a_Category_2"/>
</widget>
<addaction name="menuArtNet"/>
<addaction name="menuTags"/>
<addaction name="menuCategory"/>
</widget>
<action name="actionChange_Connection_Details">
<property name="text">
<string>Change DB Connection</string>
</property>
</action>
<action name="actionChange_ArtNet_Root_Folder">
<property name="text">
<string>Change ArtNet Root</string>
</property>
</action>
<action name="actionCreate_New_Tag">
<property name="text">
<string>Create New Tag</string>
</property>
</action>
<action name="actionDelete_a_Tag">
<property name="text">
<string>Delete a Tag</string>
</property>
</action>
<action name="actionEdit_a_Tag">
<property name="text">
<string>Edit a Tag</string>
</property>
</action>
<action name="actionCreate_New_Category">
<property name="text">
<string>Create New Category</string>
</property>
</action>
<action name="actionDelete_a_Category">
<property name="text">
<string>Delete a Category</string>
</property>
</action>
<action name="actionCreate_New_Category_2">
<property name="text">
<string>Create New Category</string>
</property>
</action>
<action name="actionDelete_a_Category_2">
<property name="text">
<string>Delete a Category</string>
</property>
</action>
</widget>
<resources/>
<connections/>
</ui>

@ -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 = "<a href=\"{0}\">{1}</a>".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 = "<a href=\"{0}\">{1}</a>".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()

@ -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

@ -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.

@ -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()
Loading…
Cancel
Save