from typing import List
import validators
import os
import logging
import re
import json
from PyQt5 import QtWidgets
from PyQt5 . QtCore import Qt , QSize , QUrl
from PyQt5 . QtGui import QPixmap , QResizeEvent , QKeyEvent , QStandardItemModel , QStandardItem , QMovie , QDesktopServices , \
QIcon , QCursor
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 . gui . dialogs . collection_select_dialog . collection_sel_dialog import CollectionSelectDialog
from ArtNet . gui . dialogs . collection_modify_dialog . collection_dialog import CollectionModDialog
from ArtNet . web . link_generator import LinkGenerator
class ImporterWindow ( ArtnetMainWindow ) :
UNKNOWN_TITLE = " -Title Unknown- "
UNKNOWN_PRESENCE = " (Not in Database) "
UNKNOWN_LINK = " No Source Available "
NO_COLLECTION = " No Collections "
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_implied_tags : list = list ( )
self . curr_tag_aliases : list = list ( )
self . curr_collections : 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_file_label . linkActivated . connect ( self . on_link_file_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 . description_edit . setReadOnly ( False )
self . ui . image_label . setContextMenuPolicy ( Qt . CustomContextMenu )
self . ui . image_label . customContextMenuRequested . connect ( self . on_custom_context_menu_requested )
self . on_tag_search_change ( )
self . center ( )
if os . path . isfile ( " ./application_icon.png " ) :
self . setWindowIcon ( QIcon ( " ./application_icon.png " ) )
else :
logging . warning ( " Didn ' t find application icon! " )
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 ,
" collections " : self . curr_collections ,
}
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
for coll_id , coll_name in self . curr_collections :
msg = QtWidgets . QMessageBox ( )
msg . setWindowTitle ( " Could not save collection! " )
msg . setInformativeText ( " The saving and proper selection of collections is not supported as of now! " )
msg . setIcon ( QtWidgets . QMessageBox . Warning )
msg . exec_ ( )
break
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 " ] ,
collections = image_data [ " collections " ] )
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_current_collections ( self ) - > list :
"""
Get the collections currently associated with the current art
"""
return self . curr_collections
def set_current_collections ( self , collections : list ) :
"""
Set the collections associated with the current art
: param collections : List [ Tuple [ coll_id , coll_name ] ]
"""
self . curr_collections = collections
if len ( collections ) == 0 :
self . ui . collections_label . setText ( ImporterWindow . NO_COLLECTION )
else :
s = " "
for coll_id , coll_name , coll_desc in collections :
s + = " <a href= \" {0} \" > {1} </a> " . format ( " " , f " # { coll_id } : { coll_name } " )
# currently not linking to anything
s + = " | "
s = s [ : - 1 ]
self . ui . collections_label . setText ( s )
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 create_collection ( self , collection_name : str , description : str = None ) - > int :
"""
Create a new collection with the given name and description
Returns the assigned ID of the collection
"""
return self . main . db_connection . create_collection ( collection_name , description )
def get_collection ( self , collection_id : int ) :
"""
Fetch the collection given by the id . If not present None is returned .
"""
return self . main . db_connection . get_collection ( collection_id )
def delete_collection ( self , collection_id : int ) :
"""
Delete the collection given by the id from the database .
"""
self . main . db_connection . delete_collection ( collection_id )
def search_collections ( self , collection_name : str = None , presence_name : str = None ,
presence_domain : str = None , art_name : str = None ) :
"""
Search for collections that fit the given parameters . All parameters are searched fuzzy .
"""
return self . main . db_connection . search_collections ( collection_name = collection_name ,
presence_name = presence_name , presence_domain = presence_domain ,
art_name = art_name )
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 ( ) )
if result is not None :
result = result . groups ( ) [ 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 link is not None and 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 link is None or 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 [ dict ] ) :
"""
Set the tags in the search result list to tags
: param tags :
: return :
"""
tags . sort ( key = lambda x : x [ " name " ] )
item_model = QStandardItemModel ( self . ui . search_result_list )
for tag in tags :
tag_name = tag [ " name " ]
item = QStandardItem ( tag_name )
flags = Qt . ItemIsEnabled
if tag_name not in self . curr_implied_tags :
# new tag and not implied yet
item . setData ( Qt . Unchecked , Qt . CheckStateRole )
if ' id ' in tag . keys ( ) and ' description ' in tag . keys ( ) and ' category ' in tag . keys ( ) :
s = f " Tag ID: { tag [ ' id ' ] } \n \n "
if len ( tag [ " description " ] ) != 0 :
s + = tag [ " description " ] + " \n \n "
s + = f " Category: { tag [ ' category ' ] } "
item . setToolTip ( s )
item . setData ( json . dumps ( tag ) , Qt . UserRole )
flags | = Qt . ItemIsUserCheckable
is_in_tags = False
for i in range ( len ( ( self . curr_tags + self . curr_implied_tags + self . curr_tag_aliases ) ) ) :
if item . text ( ) == ( self . curr_tags + self . curr_implied_tags + self . curr_tag_aliases ) [ i ] [ " name " ] :
is_in_tags = True
if self . curr_tags is not None and is_in_tags :
# 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 : not used
: return :
"""
tags . sort ( key = lambda x : x [ " name " ] )
self . curr_tags = tags
item_model = QStandardItemModel ( self . ui . tag_list )
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 [ " name " ] )
self . set_implied_list ( implied_tags )
for tag in tags :
tag_name = tag [ " name " ]
item = QStandardItem ( tag_name )
flags = Qt . ItemIsEnabled
if tag_name not in self . curr_implied_tags :
# new tag and not implied yet
item . setData ( Qt . Unchecked , Qt . CheckStateRole )
if ' id ' in tag . keys ( ) and ' description ' in tag . keys ( ) and ' category ' in tag . keys ( ) :
s = f " Tag ID: { tag [ ' id ' ] } \n \n "
if len ( tag [ " description " ] ) != 0 :
s + = tag [ " description " ] + " \n \n "
s + = f " Category: { tag [ ' category ' ] } "
item . setToolTip ( s )
flags | = Qt . ItemIsUserCheckable
if set_checked :
if tag_name not in self . curr_implied_tags :
item . setCheckState ( Qt . Checked )
else :
item . setCheckState ( Qt . Unchecked )
item . setFlags ( flags )
item_model . appendRow ( item )
item_model . itemChanged . connect ( self . on_tag_item_changed )
self . ui . tag_list . setModel ( item_model )
self . data_changed = True
def set_implied_list ( self , tags : list ) :
"""
Sets the implied tags in the imply list
: param tags :
: return :
"""
self . curr_implied_tags = tags
item_model = QStandardItemModel ( self . ui . implied_tag_list )
done = [ ]
for tag in tags :
tag_name = tag [ " name " ]
if tag_name in done :
continue
else :
done . append ( tag_name )
item = QStandardItem ( tag_name )
if ' id ' in tag . keys ( ) and ' description ' in tag . keys ( ) and ' category ' in tag . keys ( ) :
s = f " Tag ID: { tag [ ' id ' ] } \n \n "
if len ( tag [ " description " ] ) != 0 :
s + = tag [ " description " ] + " \n \n "
s + = f " Category: { tag [ ' category ' ] } "
item . setToolTip ( s )
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 , collections : list ) :
"""
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 :
: param description :
: param collections :
: 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_collections ( collections )
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 ( " <a href= \" {0} \" style= \" color: white; text-decoration: none \" > {1} </a> "
. format ( f " file:/// { full_path [ : - 1 * len ( self . curr_file_name ) ] } " ,
file_name ) )
self . ui . image_file_label . setToolTip ( relative_path )
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 = json . loads ( self . ui . search_result_list . model ( ) . itemData ( model_index ) [ Qt . UserRole ] )
if item_data not in self . curr_tags : # add/remove new selected tag to the lists
self . curr_tags . append ( item_data )
else :
self . curr_tags . remove ( item_data )
self . set_tag_list ( self . curr_tags ) # update relevant lists
self . set_tag_search_result_list ( [ item_data ] )
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 r is not None :
self . curr_tags . append ( r )
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 db_data is None or len ( db_data . keys ( ) ) == 0 :
return
try :
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 " ] )
except ValueError as e : # details were wrong (probably)
QtWidgets . QMessageBox . warning ( self , " Wrong Credentials? " , f " Error: { e } " )
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 self . get_tag ( tag_data [ " name " ] ) is not None : # check if tag exists
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 ( )
implied_tags = [ ] # refresh implied tags, the edit/addition might have changed smth
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 )
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 self . get_tag ( tag_data [ ' name ' ] ) is not None :
QtWidgets . QMessageBox . information ( self , " Tag already exists " ,
f " The Tag \" { tag_data [ ' name ' ] } \" you wanted to create already exists! Skipping... " )
return None
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 :
tag_data = self . main . db_connection . get_tag_by_name ( item . text ( ) )
self . curr_tags . append ( tag_data )
aliases = self . main . db_connection . get_tag_aliases_by_name ( item . text ( ) )
for alias in aliases :
self . curr_tags . append ( alias )
implications = self . main . db_connection . get_all_tag_implications_by_name ( item . text ( ) )
for implication in implications :
self . curr_tags . append ( implication )
if item . checkState ( ) == Qt . Unchecked :
is_in_tags = False
for i in range ( len ( self . curr_tags ) ) :
if item . text ( ) == self . curr_tags [ i ] [ " name " ] :
tags_index = i
is_in_tags = True
if is_in_tags :
self . curr_tags . pop ( tags_index )
aliases = self . main . db_connection . get_tag_aliases_by_name ( item . text ( ) )
for alias in aliases :
if alias in self . curr_tags :
self . curr_tags . remove ( alias )
implications = self . main . db_connection . get_all_tag_implications_by_name ( item . text ( ) )
for implication in implications :
self . curr_tags . remove ( { " name " : implication } )
else :
raise Exception ( " Something went terribly wrong! " )
self . set_tag_list ( self . curr_tags )
self . on_tag_search_change ( )
def on_tag_item_changed ( self , item : QStandardItem ) :
logging . debug ( " Item {0} has changed! " . format ( item . text ( ) ) )
if item . checkState ( ) == Qt . Unchecked :
is_in_tags = False
for i in range ( len ( self . curr_tags ) ) :
if item . text ( ) == self . curr_tags [ i ] [ " name " ] :
tags_index = i
is_in_tags = True
if is_in_tags :
aliases = self . main . db_connection . get_tag_aliases_by_name ( item . text ( ) )
if len ( aliases ) > 0 : # tag has aliases, might need to also remove them
for alias in aliases :
self . curr_tags . remove ( alias )
for i in range ( len ( self . curr_tags ) ) :
if item . text ( ) == self . curr_tags [ i ] [ " name " ] :
tags_index = i
self . curr_tags . pop ( tags_index )
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 )
self . set_tag_search_result_list ( tags )
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_link_file_label_activated ( self , link : str ) :
logging . debug ( f " File label 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! " )
selected_collections = CollectionSelectDialog ( self ) . exec_ ( self . curr_collections )
selected_collections = [ self . get_collection ( collection_id = c [ 0 ] ) for c in selected_collections ] # TODO verify that it returns the art-collection details if there are any
for selected_collection in selected_collections : # allow editing the collection rankings
result = CollectionModDialog ( self , db_connection = self . main . db_connection , edit_collection = False ) \
. exec_ ( selected_collection )
print ( )
self . set_current_collections ( list ( set ( list ( self . curr_collections + selected_collections ) ) ) )
def on_custom_context_menu_requested ( self ) :
action = QtWidgets . QAction ( ' Copy URL to clipboard ' )
action . triggered . connect ( self . on_copy_url_to_clipboard )
menu = QtWidgets . QMenu ( )
menu . addAction ( action )
menu . exec_ ( QCursor . pos ( ) )
def on_copy_url_to_clipboard ( self ) :
QtWidgets . QApplication . clipboard ( ) . setText ( os . path . join ( self . main . config . data [ ' file_root ' ] , self . curr_art_path ) )