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.

195 lines
7.9 KiB
Python

import logging
import os.path
from typing import List, Tuple
from PyQt5 import QtWidgets
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QStandardItem, QStandardItemModel
from ArtNet.gui.dialogs.collection_modify_dialog.collection_modify_dialog import Ui_Dialog
from ArtNet.gui.dialogs.image_select_dialog.image_sel_dialog import ImageSelectDialog
from ArtNet.db.db_adapter import DBAdapter
class CollectionModDialog(QtWidgets.QDialog):
"""
Dialog to edit and modify a collection and its entries' ranking
"""
# TODO add way to increase and lower ranking for an entry (up and down buttons?)
# TODO add way to delete entry (button)
# TODO can I catch which item in a list is selected?
def __init__(self, parent, db_connection: DBAdapter, edit_collection: bool = False):
super().__init__(parent)
self.parent = parent
self.__db = db_connection
self.__curr_entries: List[Tuple[str, dict]] = list() # [(entry_str, Dict[title, path, ranking])]
self.data = dict()
self.setWindowTitle("Creating Collection")
self.ui = Ui_Dialog()
self.ui.setupUi(self)
if not edit_collection:
self.ui.collection_name_line.setReadOnly(True)
self.ui.description_browser.setReadOnly(True)
self.ui.add_image_button.clicked.connect(self.on_add_art_clicked)
self.ui.optimize_ranking_button.clicked.connect(self.on_optimize_ranking_clicked)
@staticmethod
def create_entry_string(art_data: dict) -> str:
"""
Creates a string representation of the given art in the collection including its ranking string
"""
title: str = art_data["title"] if art_data["title"] is not None else os.path.basename(art_data["path"])
if len(title) > 20:
title = title[-20:]
elif len(title) < 20:
while len(title) < 20:
title += " "
entry_string = title + " | " + art_data["ranking"]
return entry_string
def append_collection_entry(self, art_data: dict):
"""
Append a new art entry into the collection
"""
if len(self.__curr_entries) > 0: # need to create even lower entry
lowest_r = None
for s, data in self.__curr_entries:
r = data["ranking"]
if lowest_r is None or lowest_r < r:
lowest_r = r
if lowest_r[-1] < "z": # lower and not 'z'
ranking = lowest_r
ranking = ranking[:-1]
ranking += chr(ord(lowest_r[-1]) + (ord('z')-ord(lowest_r[-1]))//2 + 1) # last character + 1
else:
ranking = lowest_r if lowest_r is not None else ""
ranking += "m"
else: # first to add
ranking = "m" # the middle
art_data["ranking"] = ranking
title: str = art_data["title"] if art_data["title"] is not None else os.path.basename(art_data["path"])
if len(title) > 20:
title = title[-20:]
elif len(title) < 20:
while len(title) < 20:
title += " "
entry_string = title + " | " + art_data["ranking"]
self.__curr_entries.append((entry_string, art_data))
self.__curr_entries.sort(key=lambda x: x[1]["ranking"])
logging.debug(f"New collection list is: {self.__curr_entries}")
self.set_collection_entry_list(self.__curr_entries)
def insert_collection_entry(self, art_data: dict, index: int):
"""
Insert the art entry into the collection at position index.
This might shift other entries in their index but tries to minimize ranking string changes.
"""
assert(index >= 0)
if len(self.__curr_entries)-1 > index: # inserts between entries or in front of entries
conflicting_art = self.__curr_entries[index][1]
following_art = self.__curr_entries[index+1][1]
if len(conflicting_art["ranking"]) == len(following_art["ranking"]): # can I squeeze it in?
if following_art["ranking"][-1] < conflicting_art["ranking"][-1] < "z":
# both are not same and not "z" -> there might be space
char_diff = ord(conflicting_art["ranking"][-1]) - ord(following_art["ranking"][-1])
if char_diff > 1: # can squeeze a letter between them
ranking = conflicting_art["ranking"][:-1]
ranking += chr(ord(conflicting_art["ranking"][-1]) + char_diff//2)
else:
ranking = conflicting_art["ranking"] + "m"
else:
ranking = conflicting_art["ranking"] + "m"
elif conflicting_art["ranking"][-1] < "z": # just insert it the plain way
ranking = conflicting_art["ranking"]
ranking = chr(ord(ranking[-1])+1)
else:
ranking = conflicting_art["ranking"] + "m"
art_data["ranking"] = ranking
entry_string = CollectionModDialog.create_entry_string(art_data)
self.__curr_entries.append((entry_string, art_data))
self.__curr_entries.sort(key=lambda x: x[1]["ranking"])
logging.debug(f"New collection list is: {self.__curr_entries}")
self.set_collection_entry_list(self.__curr_entries)
else: # inserts at the end
self.append_collection_entry(art_data)
def collect_collection_details(self):
"""
Collect the entered details about the collection and save them as self.data
"""
self.data["name"] = self.ui.collection_name_line.text()
self.data["description"] = self.ui.description_browser.toPlainText().strip()
self.data["entries"] = self.__curr_entries
def set_collection_entry_list(self, entries: list):
"""
Set the entries in the list
:param entries:
:return:
"""
item_model = QStandardItemModel(self.ui.entry_list)
entries.sort(key=lambda x: x[1]["ranking"])
for entry, _ in entries:
item = QStandardItem(entry)
flags = Qt.ItemIsEnabled
item.setFlags(flags)
item.setData(Qt.Unchecked, Qt.CheckStateRole)
item.setCheckState(Qt.Checked) # can just be checked, otherwise not in the entry list
item_model.appendRow(item)
item_model.itemChanged.connect(self.on_collection_entry_changed)
self.ui.entry_list.setModel(item_model)
def exec_(self, collection_details: dict = None) -> dict:
if collection_details is not None:
self.ui.collection_name_line.setText(collection_details["name"])
self.ui.description_browser.setText(collection_details["description"])
self.__curr_entries = collection_details["entries"]
self.set_collection_entry_list([CollectionModDialog.create_entry_string(entry) for entry in
collection_details["entries"]])
if super(CollectionModDialog, self).exec_() == QtWidgets.QDialog.Rejected:
return None
self.collect_collection_details()
return self.data
# callback method
def on_add_art_clicked(self):
logging.info("Clicked add art for collection")
dialog = ImageSelectDialog(db_connection=self.__db, parent=self)
art_data = dialog.exec_()
logging.info(f"Received selected art: {art_data}")
if art_data is None: # nothing was selected
return
self.append_collection_entry(art_data)
def on_optimize_ranking_clicked(self):
logging.info("Clicked optimize ranking for collection")
# TODO use some optimization algo to change the ranking strings to be minimal in length, with max distance
def on_collection_entry_changed(self, item: QStandardItem):
logging.info(f"Removing entry {item.text()}")
entry_id = item.text().split(":")[0]
self.__curr_entries.remove(entry_id)