You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1082 lines
43 KiB
Python

import validators, os
import logging
import re
from PyQt5 import QtWidgets
from PyQt5.QtCore import Qt, QSize, QUrl
from PyQt5.QtGui import QPixmap, QResizeEvent, QKeyEvent, QStandardItemModel, QStandardItem, QMovie, QDesktopServices
from PyQt5 import QtMultimedia
from PyQt5.QtMultimediaWidgets import QVideoWidget
from ArtNet.gui.windows.importer.picture_importer import Ui_MainWindow
from ArtNet.gui.windows.artnet_mainwindow import ArtnetMainWindow
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.gui.dialogs.link_input_dialog.link_dialog import LinkInputDialog
from ArtNet.web.link_generator import LinkGenerator
class ImporterWindow(ArtnetMainWindow):
UNKNOWN_TITLE = "-Title Unknown-"
UNKNOWN_PRESENCE = "(Not in Database)"
UNKNOWN_LINK = "No Source Available"
def __init__(self, main):
super().__init__(main)
self.__pixmap: QPixmap = None
self.__video: QVideoWidget = None
self.__player: QtMultimedia.QMediaPlayer = None
self.__text_player: QtWidgets.QTextEdit = None
self.__showing_video: bool = False
self.__showing_text: 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.__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.actionArtNet_Browser.triggered.connect(self.on_browser_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.collections_button.clicked.connect(self.on_collection_button_clicked)
self.ui.delete_button.clicked.connect(self.on_delete_image_clicked)
self.ui.link_button.clicked.connect(self.on_source_link_button_clicked)
self.ui.link_label.linkActivated.connect(self.on_link_label_activated)
self.ui.image_author_label.linkActivated.connect(self.on_image_author_label_activated)
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.description_edit.textChanged.connect(self.on_description_change)
self.ui.link_label.setText(ImporterWindow.UNKNOWN_LINK)
self.ui.image_file_label.setTextInteractionFlags(Qt.TextSelectableByMouse)
self.ui.description_edit.setReadOnly(False)
self.on_tag_search_change()
self.center()
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(int((screen.width() - size.width()) / 3), int((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:
"""
new_title = self.curr_image_title
new_description = self.ui.description_edit.toPlainText().strip()
if new_title == self.curr_file_name or len(new_title) == 0 or new_title == ImporterWindow.UNKNOWN_TITLE:
new_title = None
if new_description is None or len(new_description) == 0:
new_description = None
else:
new_description = new_description.strip()
image_data = {
"ID": self.curr_art_id,
"title": new_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),
"description": new_description,
}
for presence in self.curr_presences:
if presence[-1] == ImporterWindow.UNKNOWN_PRESENCE:
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"], desc=image_data["description"])
self.set_temporary_status_message("Saved {0} ({1}) to ArtNet DB!"
.format(image_data["title"], image_data["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 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 == ImporterWindow.UNKNOWN_PRESENCE:
presences.remove((name, domain))
elif len(presences) == 0:
presences = [(self.curr_art_path.split("/")[0], ImporterWindow.UNKNOWN_PRESENCE)]
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_label(self) -> str:
"""
Gets the image link from the label if it is a valid link.
Otherwise an empty string
:return:
"""
result = re.match('[ a-zA-Z<=>"]*((https|http)://[a-zA-Z0-9]+\.[a-zA-Z0-9]+/[a-zA-Z0-9/]+)',
self.ui.link_label.text()).groups()
if result is not None:
result = result[0]
else:
result = ""
return result
def set_presence_label_text(self, presences: list):
"""
Set the label listing all current presences and include links if possible.
:param presences:
:return:
"""
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, link: str) -> str:
"""
Sets the Image title to a link if there is link data given for this image.
:return: Returns the result that has been set to see if a successful link was constructed
"""
self.ui.link_label.setText(ImporterWindow.UNKNOWN_LINK)
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)
self.ui.link_label.setToolTip(link)
return link
elif len(link) == 0:
return ""
else:
self.ui.link_label.setText(ImporterWindow.UNKNOWN_LINK)
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_by_name(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_tag_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:
# new tag and not implied yet
item.setData(Qt.Unchecked, Qt.CheckStateRole)
flags |= Qt.ItemIsUserCheckable
if self.curr_tags is not None and tag in (self.curr_tags + self.curr_imply_tags + self.curr_tag_aliases):
# 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_description_text(self, text: str):
"""
Set the text for the description field
"""
self.ui.description_edit.setText(text)
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_by_name(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_by_name(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, description: 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 self.__showing_text: # remove text are from image layout
self.__text_player.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", "mp4", "mov", "mkv"]:
self.__showing_video = True
if isinstance(self.__video, QVideoWidget):
self.__video.show()
else:
self.__video = QVideoWidget() if self.__video is None else self.__video
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)
elif file_ending in ["txt"]: # for stories or text files
self.__showing_text = True
self.ui.image_label.hide()
self.__text_player = QtWidgets.QTextEdit() if self.__text_player is None else self.__text_player
with open(full_path, "r") as text_file:
story = text_file.read()
text_file.close()
self.__text_player.setText(story)
self.__text_player.setReadOnly(True)
self.ui.image_frame.layout().addWidget(self.__text_player)
self.__text_player.show()
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.image_file_label.setText(file_name)
self.ui.description_edit.setText(description)
self.set_image_title_link(link)
self.set_image_id_spinbox()
self.data_changed = False # reset any triggered change detection
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(ImporterWindow, 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:
logging.debug("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)
elif QtWidgets.QApplication.focusWidget() == self.ui.tag_search_bar: # quick select for the search bar
if self.ui.search_result_list.model().rowCount() == 1: # only 1 search result left
model_index = self.ui.search_result_list.model().index(0, 0)
item_data = self.ui.search_result_list.model().itemData(model_index)
if item_data[0] not in self.curr_tags: # add/remove new selected tag to the lists
self.curr_tags.append(item_data[0])
else:
self.curr_tags.remove(item_data[0])
self.set_tag_list(self.curr_tags) # update relevant lists
self.set_tag_search_result_list([item_data[0]])
logging.debug(item_data)
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):
logging.info("Clicked Save!")
self.save_changes()
def on_import_tags_clicked(self):
logging.info("Clicked Import!")
dialog = TagImportDialog(self)
if len(self.get_image_link_from_label()) == 0 \
or self.get_image_link_from_label() == ImporterWindow.UNKNOWN_LINK:
url = LinkGenerator.get_instance().construct_link(self.curr_file_name,
LinkGenerator.get_instance()
.predict_domain(self.curr_file_name))
self.set_image_title_link(url) # Update no link to the predicted link
else:
url = self.get_image_link_from_label()
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.set_image_title_link(url)
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(result)
self.set_tag_list(self.curr_tags)
def on_next_clicked(self):
logging.info("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.refresh_shown_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):
logging.info("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.refresh_shown_image()
if self.presence_docker_open:
self.toggle_presence_docker()
self.on_tag_search_change()
def toggle_presence_docker(self):
if not self.presence_docker_open:
logging.info("Opened presence docker!")
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.setArrowType(Qt.RightArrow)
self.presence_docker_open = True
else:
logging.info("Closed presence docker!")
self.presence_docker.setParent(None)
self.ui.presence_docker_button.setArrowType(Qt.LeftArrow)
self.presence_docker.destroy()
self.presence_docker = None
self.presence_docker_open = False
def on_artnet_root_change_clicked(self):
logging.info("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):
logging.info("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):
logging.info("Clicked Tag Creation!")
dialog = TagModifyDialog(self, create_tag=True)
tag_data: dict = dialog.exec_()
logging.debug(f"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):
logging.info("Clicked Tag Deletion!")
dialog = TagSelectDialog(self, delete_tag=True)
tag = dialog.exec_()
logging.debug("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_by_name(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_()
logging.debug(f"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):
logging.info("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_by_name(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 = self.main.db_connection.get_category_by_ID(tag["category_id"])[1]
edit_dialog.set_all_categories()
tag_data = edit_dialog.exec_()
logging.debug(f"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(tag_id=tag["ID"],
name=tag_data["name"], description=tag_data["description"],
aliases=tag_data["aliases"], implications=tag_data["implications"],
category_id=self.main.db_connection.get_category_by_name(tag_data["category"])[
0])
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):
logging.debug("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_tag_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_prev_unknown_image_clicked(self):
unknown_image_index = self.main.get_prev_unknown_image()
logging.info("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.refresh_shown_image()
def on_next_unknown_image_clicked(self):
unknown_image_index = self.main.get_next_unknown_image()
logging.info("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.refresh_shown_image()
def on_image_id_spinbox_changed(self, v: int):
if self.__tmp_imageid_spinbox == v:
logging.info("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.refresh_shown_image()
self.__tmp_imageid_spinbox: int = v
def on_delete_image_clicked(self):
logging.info("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:
logging.debug("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:
logging.info(f"deleting image data of \"{self.curr_image_title}\"")
self.main.db_connection.remove_image(art_hash)
else:
return
else:
logging.debug("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:
logging.info(f"deleting image file {self.curr_art_path}")
self.main.delete_image(self.curr_art_path)
else:
return
self.main.refresh_shown_image()
def on_link_label_activated(self, link: str):
logging.debug(f"Source link activated! {link}")
QDesktopServices.openUrl(QUrl(link))
def on_image_author_label_activated(self, link: str):
logging.debug(f"Image author link activated! {link}")
QDesktopServices.openUrl(QUrl(link))
def on_description_change(self):
self.data_changed = True
def on_browser_clicked(self):
logging.debug("Clicked on open ArtNet browser!")
self.main.switch_to_browser()
def on_source_link_button_clicked(self):
logging.debug("Clicked on link button!")
dialog = LinkInputDialog(self, self.curr_link)
link = dialog.exec_()
if link is None: # dialog was cancelled
logging.debug("Cancelled link dialog.")
return
logging.info(f"Setting source link to \"{link}\"")
self.set_image_title_link(link)
def on_collection_button_clicked(self):
logging.debug("Clicked on collection button!")
QtWidgets.QMessageBox.information(self, "Not Implemented", "This feature has not been implemented yet!")
# TODO open dialog with collection selection and buttons for edit, creation and deletion
# TODO allow editing of the rank string
# TODO make collection inspectable (a list of all entries with their rank string would suffice)