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