I have a working QDialog that shows a table of craftable items, the recipe to craft them, what ingredients are missing, and a button to craft if all ingredients are in the inventory. I connect to a SQLite database to handle the data operations. But there are a few things I’d like to work out:
-
The “what ingredients are missing” cell requires recalculating after each craft operation (2 onions in the inventory to make French Onion Soup? If you craft the soup, you delete the 2 onions, now we need to update that row to say “you need 2 onions to make more”). Right now I’m dealing with this pretty inelegantly by reloading the whole dialog each time, but I’m sure there’s a better way.
-
I want to be able to search. I have search working properly on my simpler Inventory menu, but I’m having trouble figuring out how to get the
QPushButton()
to show on each row when using aQAbstractTableModel
. And I haven’t figured out where the signal/slot should happen for the click with this structure.
Any help is greatly appreciated!
Simpler InventoryDialog
example:
class InventoryTableModel(QAbstractTableModel):
def __init__(self, data, parent=None):
super().__init__(parent)
self.horizontalHeaders = [''] * 3
self.setHeaderData(0, Qt.Orientation.Horizontal, "Item Image")
self.setHeaderData(1, Qt.Orientation.Horizontal, "Item Name")
self.setHeaderData(2, Qt.Orientation.Horizontal, "Count")
self._data = data
def rowCount(self, index):
return len(self._data)
def columnCount(self, index):
return len(self._data[0])
def setHeaderData(self, section, orientation, data, role=Qt.ItemDataRole.EditRole):
if orientation == Qt.Orientation.Horizontal and role in (Qt.ItemDataRole.DisplayRole, Qt.ItemDataRole.EditRole):
try:
self.horizontalHeaders[section] = data
return True
except:
return False
return super().setHeaderData(section, orientation, data, role)
def headerData(self, section, orientation, role=Qt.ItemDataRole.DisplayRole):
if orientation == Qt.Orientation.Horizontal and role == Qt.ItemDataRole.DisplayRole:
try:
return self.horizontalHeaders[section]
except:
pass
return super().headerData(section, orientation, role)
def data(self, index, role=Qt.ItemDataRole.DisplayRole):
if not index.isValid():
return QVariant()
if role == Qt.ItemDataRole.DecorationRole:
if index.column() == 0:
image_path = self._data[index.row()][index.column()]
pixmap = QPixmap(image_path)
return pixmap
else:
return QVariant()
if role == Qt.ItemDataRole.DisplayRole:
if index.column() != 0:
return self._data[index.row()][index.column()]
return QVariant()
class InventoryDialog2(QDialog):
def __init__(self, parent, rewards_repo):
super().__init__(parent)
self.table = QTableView()
# Set up Data
self._rewards_repo = rewards_repo
inventory = rewards_repo.retrieve_inventory()
data = []
for i, (item_name, image_path, count) in enumerate(inventory):
image_path = Path(PATH_THIS_ADDON) / image_path
data.append([str(image_path), item_name, count])
self.model = InventoryTableModel(data)
self.proxy_model = QSortFilterProxyModel()
self.proxy_model.setSourceModel(self.model)
self.proxy_model.setFilterKeyColumn(1)
self.proxy_model.setFilterCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive)
self.proxy_model.sort(1, Qt.SortOrder.AscendingOrder)
self.table.setModel(self.proxy_model)
self.table.setFixedWidth(800)
self.table.setFixedHeight(1000)
self.table.resizeRowsToContents()
self.table.resizeColumnsToContents()
self.searchbar = QLineEdit()
self.searchbar.textChanged.connect(self.proxy_model.setFilterFixedString)
self.searchbar.textChanged.connect(self.table.resizeRowsToContents)
self.searchbar.textChanged.connect(self.table.resizeColumnsToContents)
layout = QVBoxLayout()
layout.addWidget(self.searchbar)
layout.addWidget(self.table)
self.setWindowTitle("Testing")
self.setLayout(layout)
Current CraftingDialog
without QAbstractTableModel
:
class CraftingDialog(QDialog):
def __init__(self, parent, rewards_repo):
super().__init__(parent)
self.rewards_repo = rewards_repo
self.parent = parent
crafting_level = self.rewards_repo.get_crafting_level()
current_recipes = get_alphabetized_recipes_by_level(crafting_level)
self.table = QTableWidget()
self.table.setGeometry(100, 60, 1200, 800)
self.table.setRowCount(len(current_recipes))
self.table.setColumnCount(8)
self.table.setHorizontalHeaderLabels(
[
"Key Item",
"Recipe Image",
"Recipe Name",
"Ingredients",
"Number Owned",
"Craftable",
"Missing",
"Craft?",
]
)
self.table.setIconSize(QSize(72, 72))
for i, recipe in enumerate(current_recipes):
image_path = Path(PATH_THIS_ADDON) / recipe["image_path"]
is_key_item = False
if recipe["key_item"][crafting_level]:
key_item = QTableWidgetItem(f"U0001F31F")
is_key_item = True
else:
key_item = QTableWidgetItem("")
item_image = QTableWidgetItem()
item_image.setIcon(QIcon(QPixmap(str(image_path))))
item_name = QTableWidgetItem(recipe["item_name"])
ingredients_string = QTableWidgetItem(self.craft_ingredients_string(recipe))
number_owned = QTableWidgetItem(
str(rewards_repo.count_item(recipe["item_name"]))
)
if is_key_item and rewards_repo.count_item(recipe["item_name"]) >= 1:
key_item.setBackground(get_rgb_from_hex("#009933"))
if self.is_craftable(rewards_repo, recipe):
craftable_cell = QTableWidgetItem("Yes")
craftable_cell.setBackground(get_rgb_from_hex("#009933"))
craft_button = QPushButton()
craft_button.setText("Craft!")
craft_button.clicked.connect(self.button_clicked)
self.table.setCellWidget(i, 7, craft_button)
else:
craftable_cell = QTableWidgetItem("No")
craftable_cell.setBackground(get_rgb_from_hex("#FF0000"))
missing_string = QTableWidgetItem(rewards_repo.missing_ingredients(recipe))
self.table.setItem(i, 0, key_item)
self.table.setItem(i, 1, item_image)
self.table.setItem(i, 2, item_name)
self.table.setItem(i, 3, ingredients_string)
self.table.setItem(i, 4, number_owned)
self.table.setItem(i, 5, craftable_cell)
self.table.setItem(i, 6, missing_string)
self.table.setFixedWidth(1250)
self.table.setFixedHeight(1000)
self.table.resizeRowsToContents()
self.table.resizeColumnsToContents()
layout = QVBoxLayout()
layout.addWidget(self.table)
self.setWindowTitle("Recipes")
self.setLayout(layout)
def craft_ingredients_string(self, recipe):
ingredients_string = ""
for ingredient, amount in recipe["ingredients"].items():
ingredients_string += f"{amount} {ingredient} n"
return ingredients_string
def is_craftable(self, rewards_repo, recipe):
craftable = True
for ingredient, amount in recipe["ingredients"].items():
if rewards_repo.count_item(ingredient) >= amount:
craftable = craftable and True
else:
craftable = craftable and False
return craftable
def button_clicked(self):
button = self.sender()
if button:
row = self.table.indexAt(button.pos()).row()
item = self.table.item(row, 2).text()
self.rewards_repo.craft_item(self.search_recipes(item))
self.close()
show_dialog(self.parent, self.rewards_repo, dialog="crafting")
def search_recipes(self, search_term):
return next(filter(lambda item: item["item_name"] == search_term, RECIPES))
WIP CraftingDialog
with QAbstractTableModel
:
class CraftingDialog2(QDialog):
def __init__(self, parent, rewards_repo):
super().__init__(parent)
self.table = QTableView()
# Set up Data
self._rewards_repo = rewards_repo
crafting_level = rewards_repo.get_crafting_level()
current_recipes = get_alphabetized_recipes_by_level(crafting_level)
data = []
for i, recipe in enumerate(current_recipes):
is_key_item = False
if recipe["key_item"][crafting_level]:
key_item = f"U0001F31F"
else:
key_item = ""
image_path = Path(PATH_THIS_ADDON) / recipe["image_path"]
item_name = recipe["item_name"]
ingredients_string = self.craft_ingredients_string(recipe)
number_owned = str(rewards_repo.count_item(recipe["item_name"]))
if self.is_craftable(rewards_repo, recipe):
craftable = "Yes"
else:
craftable = "No"
craft_button = QPushButton()
craft_button.setText("Craft!")
craft_button.clicked.connect(self.button_clicked)
missing_string = rewards_repo.missing_ingredients(recipe)
data.append([key_item, str(image_path), item_name, ingredients_string, number_owned, craftable, missing_string, self.is_craftable(rewards_repo, recipe)])
self.model = CraftingTableModel(data)
self.proxy_model = QSortFilterProxyModel()
self.proxy_model.setSourceModel(self.model)
self.proxy_model.setFilterKeyColumn(2)
self.proxy_model.setFilterCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive)
self.proxy_model.sort(2, Qt.SortOrder.AscendingOrder)
self.table.setModel(self.proxy_model)
self.table.setFixedWidth(1200)
self.table.setFixedHeight(1000)
self.table.resizeRowsToContents()
self.table.resizeColumnsToContents()
self.searchbar = QLineEdit()
self.searchbar.textChanged.connect(self.proxy_model.setFilterFixedString)
self.searchbar.textChanged.connect(self.table.resizeRowsToContents)
self.searchbar.textChanged.connect(self.table.resizeColumnsToContents)
layout = QVBoxLayout()
layout.addWidget(self.searchbar)
layout.addWidget(self.table)
self.setWindowTitle("Craft Testing")
self.setLayout(layout)
class CraftingTableModel(QAbstractTableModel):
def __init__(self, data, parent=None):
super().__init__(parent)
self.horizontalHeaders = [''] * 8
self.setHeaderData(0, Qt.Orientation.Horizontal, "Key Item")
self.setHeaderData(1, Qt.Orientation.Horizontal, "Recipe Image")
self.setHeaderData(2, Qt.Orientation.Horizontal, "Recipe Name")
self.setHeaderData(3, Qt.Orientation.Horizontal, "Ingredients")
self.setHeaderData(4, Qt.Orientation.Horizontal, "Number Owned")
self.setHeaderData(5, Qt.Orientation.Horizontal, "Craftable")
self.setHeaderData(6, Qt.Orientation.Horizontal, "Missing")
self.setHeaderData(7, Qt.Orientation.Horizontal, "Craft?")
self._data = data
def rowCount(self, index):
return len(self._data)
def columnCount(self, index):
return len(self._data[0])
def setHeaderData(self, section, orientation, data, role=Qt.ItemDataRole.EditRole):
if orientation == Qt.Orientation.Horizontal and role in (Qt.ItemDataRole.DisplayRole, Qt.ItemDataRole.EditRole):
try:
self.horizontalHeaders[section] = data
return True
except:
return False
return super().setHeaderData(section, orientation, data, role)
def headerData(self, section, orientation, role=Qt.ItemDataRole.DisplayRole):
if orientation == Qt.Orientation.Horizontal and role == Qt.ItemDataRole.DisplayRole:
try:
return self.horizontalHeaders[section]
except:
pass
return super().headerData(section, orientation, role)
def data(self, index, role=Qt.ItemDataRole.DisplayRole):
if not index.isValid():
return QVariant()
if role == Qt.ItemDataRole.DecorationRole:
if index.column() == 1:
image_path = self._data[index.row()][index.column()]
pixmap = QPixmap(image_path)
return pixmap
else:
return QVariant()
if role == Qt.ItemDataRole.DisplayRole:
if index.column() != 1:
return self._data[index.row()][index.column()]
# if role == Qt.ItemDataRole.BackgroundRole:
# if self._data[index.row()][index.column()] == f"U0001F31F" and self._data[index.row()][4] > 0:
# return QtGui.Color("#009933")
if index.column() == 7:
craft_cell = ""
if self._data[index.row()][index.column()] == True:
craft_cell = QPushButton()
craft_cell.setText("Craft!")
return craft_cell
# craft_button.clicked.connect(self.button_clicked)
return QVariant()
# def button_clicked(self, index):
# signal should come from the view, right?
1