EnvVars: Handle environment variables with a QAbstractTableModel
Using a ChainMap and this custom model, we can display global environ variables in the per-game settings, allowin better overview and simpler override.
This commit is contained in:
parent
b1d438c34c
commit
4b2bc7f91f
4 changed files with 336 additions and 435 deletions
|
@ -1,310 +1,85 @@
|
|||
from logging import getLogger
|
||||
|
||||
from PyQt5.QtCore import Qt, QFileSystemWatcher
|
||||
from PyQt5.QtGui import QFont
|
||||
from PyQt5.QtWidgets import QGroupBox, QTableWidgetItem, QMessageBox, QPushButton, QHeaderView, QFrame
|
||||
from PyQt5.QtCore import QFileSystemWatcher
|
||||
from PyQt5.QtWidgets import (
|
||||
QGroupBox,
|
||||
QHeaderView,
|
||||
QVBoxLayout,
|
||||
QTableView,
|
||||
)
|
||||
|
||||
from rare.shared import LegendaryCoreSingleton
|
||||
from rare.ui.components.tabs.settings.widgets.env_vars import Ui_EnvVars
|
||||
from rare.utils import config_helper
|
||||
from rare.utils.misc import icon
|
||||
from .env_vars_model import EnvVarsTableModel
|
||||
|
||||
logger = getLogger("EnvVars")
|
||||
|
||||
|
||||
class EnvVars(QGroupBox, Ui_EnvVars):
|
||||
class EnvVars(QGroupBox):
|
||||
def __init__(self, parent):
|
||||
super(EnvVars, self).__init__(parent=parent)
|
||||
self.setupUi(self)
|
||||
self.app_name = None
|
||||
self.setTitle(self.tr("Environment variables"))
|
||||
|
||||
self.core = LegendaryCoreSingleton()
|
||||
self.latest_item = None
|
||||
self.list_of_readonly = [
|
||||
"STEAM_COMPAT_DATA_PATH",
|
||||
"STEAM_COMPAT_CLIENT_INSTALL_PATH",
|
||||
"WINEPREFIX",
|
||||
"DXVK_HUD",
|
||||
"MANGOHUD_CONFIG",
|
||||
]
|
||||
self.warn_msg = self.tr("Readonly, please edit this via the appropriate setting above.")
|
||||
self.setup_file_watcher()
|
||||
self.env_vars_table.cellChanged.connect(self.update_env_vars)
|
||||
self.env_vars_table.verticalHeader().sectionClicked.connect(self.remove_row)
|
||||
|
||||
# We use this function to keep an eye on the config.
|
||||
# When the user uses for example the wineprefix settings, we need to update the table.
|
||||
# With this function, when the config file changes, we update the table.
|
||||
def setup_file_watcher(self):
|
||||
self.table_model = EnvVarsTableModel(self.core)
|
||||
self.table_view = QTableView(self)
|
||||
self.table_view.setModel(self.table_model)
|
||||
self.table_view.verticalHeader().sectionPressed.disconnect()
|
||||
self.table_view.horizontalHeader().sectionPressed.disconnect()
|
||||
self.table_view.verticalHeader().sectionClicked.connect(self.table_model.removeRow)
|
||||
self.table_view.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
|
||||
self.table_view.horizontalHeader().setStretchLastSection(True)
|
||||
self.table_view.setCornerButtonEnabled(False)
|
||||
|
||||
# FIXME: investigate signaling between widgets
|
||||
# We use this function to keep an eye on the config.
|
||||
# When the user uses for example the wineprefix settings, we need to update the table.
|
||||
# With this function, when the config file changes, we update the table.
|
||||
self.config_file_watcher = QFileSystemWatcher([str(self.core.lgd.config_path)], self)
|
||||
self.config_file_watcher.fileChanged.connect(self.import_env_vars)
|
||||
self.config_file_watcher.fileChanged.connect(self.table_model.reset)
|
||||
|
||||
def append_row(self):
|
||||
# If the last row is not None, we insert a new one and set the correct icon.
|
||||
row_count = self.env_vars_table.rowCount()
|
||||
row_height = self.table_view.rowHeight(0)
|
||||
self.table_view.setMinimumHeight(row_height * 7)
|
||||
|
||||
if row_count == 0:
|
||||
self.env_vars_table.insertRow(0)
|
||||
trash_icon = QTableWidgetItem()
|
||||
trash_icon.setIcon(icon("mdi.delete", "ei.minus"))
|
||||
self.env_vars_table.setVerticalHeaderItem(row_count, trash_icon)
|
||||
return
|
||||
layout = QVBoxLayout(self)
|
||||
layout.addWidget(self.table_view)
|
||||
|
||||
last_item = self.env_vars_table.item(self.env_vars_table.rowCount() - 1, 0)
|
||||
|
||||
if last_item is not None:
|
||||
self.env_vars_table.insertRow(row_count)
|
||||
trash_icon = QTableWidgetItem()
|
||||
trash_icon.setIcon(icon("mdi.delete", "ei.minus"))
|
||||
self.env_vars_table.setVerticalHeaderItem(row_count, trash_icon)
|
||||
|
||||
def import_env_vars(self):
|
||||
self.env_vars_table.blockSignals(True)
|
||||
self.env_vars_table.clearContents()
|
||||
|
||||
# If the config file doesnt have an env var section, we just set RowCount to 1 and return.
|
||||
if not self.core.lgd.config.has_section(f"{self.app_name}.env"):
|
||||
self.env_vars_table.setRowCount(1)
|
||||
trash_icon = QTableWidgetItem()
|
||||
trash_icon.setIcon(icon("mdi.delete", "ei.minus"))
|
||||
self.env_vars_table.setVerticalHeaderItem(0, trash_icon)
|
||||
self.env_vars_table.blockSignals(False)
|
||||
return
|
||||
|
||||
# We count how many keys we have and insert new lines
|
||||
# (as many as we need).
|
||||
self.env_vars_table.setRowCount(len(self.core.lgd.config[f"{self.app_name}.env"]) + 1)
|
||||
|
||||
# Each iteration we have to create a new QTableWidgetItem object,
|
||||
# else we segfault. (For using the same object in multiple references.)
|
||||
for i, (key, value) in enumerate(self.core.lgd.config[f"{self.app_name}.env"].items()):
|
||||
trash_icon = QTableWidgetItem()
|
||||
trash_icon.setIcon(icon("mdi.delete", "ei.minus"))
|
||||
self.env_vars_table.setVerticalHeaderItem(i, trash_icon)
|
||||
|
||||
font = QFont("Monospace")
|
||||
font.setStyleHint(QFont.Monospace)
|
||||
|
||||
key_item = QTableWidgetItem()
|
||||
key_item.setText(key)
|
||||
key_item.setFont(font)
|
||||
self.env_vars_table.setItem(i, 0, key_item)
|
||||
|
||||
value_item = QTableWidgetItem()
|
||||
value_item.setFont(font)
|
||||
value_item.setText(value)
|
||||
self.env_vars_table.setItem(i, 1, value_item)
|
||||
if key in self.list_of_readonly:
|
||||
key_item.setFlags(key_item.flags() ^ Qt.ItemIsEnabled)
|
||||
key_item.setToolTip(self.warn_msg)
|
||||
|
||||
value_item.setFlags(value_item.flags() ^ Qt.ItemIsEnabled)
|
||||
value_item.setToolTip(self.warn_msg)
|
||||
|
||||
trash_icon = QTableWidgetItem()
|
||||
trash_icon.setIcon(icon("mdi.delete", "ei.minus"))
|
||||
self.env_vars_table.setVerticalHeaderItem(self.env_vars_table.rowCount() - 1, trash_icon)
|
||||
self.env_vars_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
|
||||
|
||||
self.env_vars_table.blockSignals(False)
|
||||
|
||||
def update_env_vars(self, row, column):
|
||||
self.config_file_watcher.removePath(str(self.core.lgd.config_path))
|
||||
row_count = self.env_vars_table.rowCount()
|
||||
key_item = self.env_vars_table.item(row, 0)
|
||||
value_item = self.env_vars_table.item(row, 1)
|
||||
|
||||
if key_item is not None and not key_item.text():
|
||||
try:
|
||||
list_of_config_keys = list(self.core.lgd.config[f"{self.app_name}.env"].keys())
|
||||
except KeyError:
|
||||
list_of_config_keys = []
|
||||
try:
|
||||
config_helper.remove_option(f"{self.app_name}.env", list_of_config_keys[row])
|
||||
except IndexError:
|
||||
# Item hasnt been saved to the config yet.
|
||||
pass
|
||||
self.env_vars_table.removeRow(key_item.row())
|
||||
self.append_row()
|
||||
return
|
||||
|
||||
# get all config keys
|
||||
try:
|
||||
list_of_config_keys = list(self.core.lgd.config[f"{self.app_name}.env"].keys())
|
||||
except KeyError:
|
||||
list_of_config_keys = []
|
||||
|
||||
# get all table keys
|
||||
list_of_keys_in_table = []
|
||||
for i in range(row_count):
|
||||
item = self.env_vars_table.item(i, 0)
|
||||
if item:
|
||||
list_of_keys_in_table.append(item.text())
|
||||
|
||||
missing_item = list(set(list_of_config_keys) - set(list_of_keys_in_table))
|
||||
if len(missing_item) != 0:
|
||||
config_helper.remove_option(f"{self.app_name}.env", missing_item[0])
|
||||
|
||||
# A env var always needs to have a key.
|
||||
# If it's none, we return.
|
||||
if key_item is None:
|
||||
return
|
||||
|
||||
if key_item.text() in self.list_of_readonly:
|
||||
error_dialog = QMessageBox()
|
||||
error_dialog.setText(
|
||||
self.tr("Please don't manually add this environment variable. Use the appropriate game setting above."))
|
||||
error_dialog.exec()
|
||||
key_item.setText("")
|
||||
if value_item is not None:
|
||||
value_item.setText("")
|
||||
config_helper.remove_option(f"{self.app_name}.env", key_item.text())
|
||||
return
|
||||
|
||||
if key_item.text():
|
||||
if "=" in key_item.text():
|
||||
error_dialog = QMessageBox()
|
||||
error_dialog.setText(
|
||||
self.tr("Please don't use an equal sign in an env var."))
|
||||
error_dialog.exec()
|
||||
self.env_vars_table.removeRow(row)
|
||||
self.append_row()
|
||||
return
|
||||
|
||||
if key_item.text() in list_of_config_keys and column == 0:
|
||||
ask_user = QMessageBox()
|
||||
ask_user.setText(
|
||||
self.tr("The config already contains this environment variable."))
|
||||
ask_user.setInformativeText(
|
||||
self.tr("Do you want to keep the newer one or the older one?"))
|
||||
|
||||
ask_user.addButton(QPushButton("Keep the newer one"), QMessageBox.YesRole)
|
||||
ask_user.addButton(QPushButton("Keep the older one"), QMessageBox.NoRole)
|
||||
|
||||
response = ask_user.exec()
|
||||
|
||||
if response == 0:
|
||||
if value_item is not None:
|
||||
config_helper.add_option(f"{self.app_name}.env", key_item.text(), value_item.text())
|
||||
else:
|
||||
config_helper.add_option(f"{self.app_name}.env", key_item.text(), "")
|
||||
|
||||
item_to_safe = self.env_vars_table.findItems(key_item.text(), Qt.MatchExactly)
|
||||
|
||||
# aznd:
|
||||
# This is to fix an issue where the user updates a env var, thats above the older one.
|
||||
# so say if you have two env vars:
|
||||
# something = newkey
|
||||
# yes = oldkey
|
||||
# if the user updates the something key to yes, it would delete the wrong row,
|
||||
# since item_to_safe[0] is the first search result. but we dont want to delete that,
|
||||
# we want to delete the second result.
|
||||
# we use this simple if check to find out which case we have.
|
||||
if key_item.row() < item_to_safe[0].row():
|
||||
self.env_vars_table.removeRow(item_to_safe[0].row())
|
||||
elif key_item.row() > item_to_safe[0].row():
|
||||
self.env_vars_table.removeRow(item_to_safe[0].row())
|
||||
else:
|
||||
self.env_vars_table.removeRow(item_to_safe[1].row())
|
||||
|
||||
self.append_row()
|
||||
return
|
||||
|
||||
elif response == 1:
|
||||
self.env_vars_table.removeRow(row)
|
||||
self.append_row()
|
||||
return
|
||||
|
||||
# When the value_item is None, we just use an empty string for the value.
|
||||
if value_item is None:
|
||||
if self.latest_item in list_of_config_keys:
|
||||
config_helper.remove_option(f"{self.app_name}.env", self.latest_item)
|
||||
config_helper.save_config()
|
||||
|
||||
config_helper.add_option(f"{self.app_name}.env", key_item.text(), "")
|
||||
config_helper.save_config()
|
||||
else:
|
||||
if self.latest_item in list_of_config_keys:
|
||||
config_helper.remove_option(f"{self.app_name}.env", self.latest_item)
|
||||
config_helper.save_config()
|
||||
|
||||
config_helper.add_option(
|
||||
f"{self.app_name}.env",
|
||||
key_item.text(),
|
||||
value_item.text()
|
||||
)
|
||||
config_helper.save_config()
|
||||
|
||||
self.append_row()
|
||||
self.config_file_watcher.addPath(str(self.core.lgd.config_path))
|
||||
|
||||
def remove_row(self, index):
|
||||
self.config_file_watcher.removePath(str(self.core.lgd.config_path))
|
||||
key_item = self.env_vars_table.item(index, 0)
|
||||
value_item = self.env_vars_table.item(index, 1)
|
||||
|
||||
if key_item is None:
|
||||
if value_item is not None:
|
||||
value_item.setText("")
|
||||
return
|
||||
|
||||
# If the user tries to delete one of the readonly vars, we return immediately.
|
||||
if key_item.text() in self.list_of_readonly:
|
||||
return
|
||||
|
||||
if key_item is not None:
|
||||
self.env_vars_table.removeRow(index)
|
||||
try:
|
||||
list_of_keys = []
|
||||
for key in self.core.lgd.config[f"{self.app_name}.env"]:
|
||||
list_of_keys.append(key)
|
||||
config_helper.remove_option(f"{self.app_name}.env", list_of_keys[index])
|
||||
except (KeyError, IndexError):
|
||||
pass
|
||||
self.config_file_watcher.addPath(str(self.core.lgd.config_path))
|
||||
|
||||
def check_if_item(self, item: QTableWidgetItem) -> bool:
|
||||
item_to_check = self.env_vars_table.findItems(item.text(), Qt.MatchExactly)
|
||||
if item_to_check[0].column() == 0:
|
||||
return False
|
||||
return True
|
||||
|
||||
def keyPressEvent(self, e):
|
||||
if e.key() == Qt.Key_Delete or e.key() == Qt.Key_Backspace:
|
||||
selected_items = self.env_vars_table.selectedItems()
|
||||
|
||||
if len(selected_items) == 0:
|
||||
return
|
||||
|
||||
item_in_table = self.env_vars_table.findItems(selected_items[0].text(), Qt.MatchExactly)
|
||||
|
||||
# Our first selection is in column 0. So, we have to find out if the user
|
||||
# only selected keys, or keys and values. we use the check_if_item func
|
||||
if item_in_table[0].column() == 0:
|
||||
which_index_to_use = 1
|
||||
if len(selected_items) == 1:
|
||||
which_index_to_use = 0
|
||||
if self.check_if_item(selected_items[which_index_to_use]):
|
||||
# User selected keys and values, so we skip the values
|
||||
for i in selected_items[::2]:
|
||||
if i:
|
||||
config_helper.remove_option(f"{self.app_name}.env", i.text())
|
||||
self.env_vars_table.removeRow(i.row())
|
||||
self.append_row()
|
||||
else:
|
||||
# user only selected keys
|
||||
for i in selected_items:
|
||||
if i:
|
||||
config_helper.remove_option(f"{self.app_name}.env", i.text())
|
||||
self.env_vars_table.removeRow(i.row())
|
||||
self.append_row()
|
||||
|
||||
# User only selected values, so we just set the text to ""
|
||||
elif item_in_table[0].column() == 1:
|
||||
[i.setText("") for i in selected_items]
|
||||
|
||||
elif e.key() == Qt.Key_Escape:
|
||||
e.ignore()
|
||||
# def keyPressEvent(self, e):
|
||||
# if e.key() == Qt.Key_Delete or e.key() == Qt.Key_Backspace:
|
||||
# selected_items = self.ui.env_vars_table.selectedItems()
|
||||
#
|
||||
# if len(selected_items) == 0:
|
||||
# return
|
||||
#
|
||||
# item_in_table = self.ui.env_vars_table.findItems(selected_items[0].text(), Qt.MatchExactly)
|
||||
#
|
||||
# # Our first selection is in column 0. So, we have to find out if the user
|
||||
# # only selected keys, or keys and values. we use the check_if_item func
|
||||
# if item_in_table[0].column() == 0:
|
||||
# which_index_to_use = 1
|
||||
# if len(selected_items) == 1:
|
||||
# which_index_to_use = 0
|
||||
# if self.check_if_item(selected_items[which_index_to_use]):
|
||||
# # User selected keys and values, so we skip the values
|
||||
# for i in selected_items[::2]:
|
||||
# if i:
|
||||
# config_helper.remove_option(f"{self.app_name}.env", i.text())
|
||||
# self.ui.env_vars_table.removeRow(i.row())
|
||||
# self.append_row()
|
||||
# else:
|
||||
# # user only selected keys
|
||||
# for i in selected_items:
|
||||
# if i:
|
||||
# config_helper.remove_option(f"{self.app_name}.env", i.text())
|
||||
# self.ui.env_vars_table.removeRow(i.row())
|
||||
# self.append_row()
|
||||
#
|
||||
# # User only selected values, so we just set the text to ""
|
||||
# elif item_in_table[0].column() == 1:
|
||||
# [i.setText("") for i in selected_items]
|
||||
#
|
||||
# elif e.key() == Qt.Key_Escape:
|
||||
# e.ignore()
|
||||
|
||||
def update_game(self, app_name):
|
||||
self.app_name = app_name
|
||||
self.import_env_vars()
|
||||
self.table_model.load(app_name)
|
||||
|
|
269
rare/components/tabs/settings/widgets/env_vars_model.py
Normal file
269
rare/components/tabs/settings/widgets/env_vars_model.py
Normal file
|
@ -0,0 +1,269 @@
|
|||
import re
|
||||
import sys
|
||||
from collections import ChainMap
|
||||
from pprint import pprint
|
||||
from typing import Any, Union
|
||||
|
||||
from rare.utils.misc import icon
|
||||
from PyQt5.QtCore import Qt, QModelIndex, QAbstractTableModel, pyqtSlot
|
||||
from PyQt5.QtGui import QFont
|
||||
from PyQt5.QtWidgets import QTableView, QHeaderView, QApplication, QDialog, QVBoxLayout
|
||||
from legendary.core import LegendaryCore
|
||||
|
||||
|
||||
class EnvVarsTableModel(QAbstractTableModel):
|
||||
def __init__(self, core: LegendaryCore, parent = None):
|
||||
super(EnvVarsTableModel, self).__init__(parent=parent)
|
||||
self.core = core
|
||||
|
||||
self.__validator = re.compile(r"(^[A-Za-z_][A-Za-z0-9_]*)")
|
||||
self.__data_map: ChainMap = ChainMap()
|
||||
|
||||
self.__readonly = [
|
||||
"STEAM_COMPAT_DATA_PATH",
|
||||
"STEAM_COMPAT_CLIENT_INSTALL_PATH",
|
||||
"WINEPREFIX",
|
||||
"DXVK_HUD",
|
||||
"MANGOHUD_CONFIG",
|
||||
]
|
||||
|
||||
self.__default: str = "default"
|
||||
self.__appname: str = None
|
||||
|
||||
@pyqtSlot(str)
|
||||
def reset(self):
|
||||
if not self.__appname:
|
||||
return
|
||||
self.load(self.__appname)
|
||||
|
||||
def load(self, app_name: str):
|
||||
self.__appname = app_name
|
||||
self.beginResetModel()
|
||||
if not self.core.lgd.config.has_section(f"{self.__appname}.env"):
|
||||
self.core.lgd.config[f"{self.__appname}.env"] = {}
|
||||
self.__data_map = ChainMap(
|
||||
self.core.lgd.config[f"{self.__appname}.env"],
|
||||
self.core.lgd.config[f"{self.__default}.env"] if self.__appname != self.__default else {}
|
||||
)
|
||||
self.endResetModel()
|
||||
|
||||
def __key(self, index: Union[QModelIndex, int]):
|
||||
if isinstance(index, QModelIndex):
|
||||
index = index.row()
|
||||
try:
|
||||
return list(self.__data_map)[index]
|
||||
except Exception as e:
|
||||
print(e, index)
|
||||
return ""
|
||||
|
||||
def __is_local(self, index: Union[QModelIndex, int]):
|
||||
key = self.__key(index)
|
||||
return key in self.__data_map.maps[0].keys()
|
||||
|
||||
def __is_global(self, index: Union[QModelIndex, int]):
|
||||
key = self.__key(index)
|
||||
return key in self.__data_map.maps[1].keys()
|
||||
|
||||
def __is_readonly(self, index: Union[QModelIndex, int]):
|
||||
key = self.__key(index)
|
||||
return key in self.__readonly
|
||||
|
||||
def __value(self, index: Union[QModelIndex, int]):
|
||||
if isinstance(index, QModelIndex):
|
||||
index = index.row()
|
||||
return self.__data_map[self.__key(index)]
|
||||
|
||||
def __title(self, section: int):
|
||||
if section == 0:
|
||||
return self.tr("Key")
|
||||
elif section == 1:
|
||||
return self.tr("Value")
|
||||
|
||||
def __data_length(self):
|
||||
return len(self.__data_map)
|
||||
|
||||
def __is_key_valid(self, value: str):
|
||||
match = self.__validator.match(value)
|
||||
if not match:
|
||||
return False
|
||||
return value == match.group(0)
|
||||
|
||||
def data(self, index: QModelIndex, role: int = Qt.DisplayRole) -> Any:
|
||||
if role == Qt.DisplayRole or role == Qt.EditRole:
|
||||
if index.row() == self.__data_length():
|
||||
return ""
|
||||
if index.column() == 0:
|
||||
return self.__key(index)
|
||||
else:
|
||||
return self.__value(index)
|
||||
|
||||
if role == Qt.TextAlignmentRole:
|
||||
if index.column() == 0:
|
||||
return Qt.AlignVCenter + Qt.AlignRight
|
||||
else:
|
||||
return Qt.AlignVCenter + Qt.AlignLeft
|
||||
|
||||
if role == Qt.FontRole:
|
||||
font = QFont("Monospace")
|
||||
font.setStyleHint(QFont.Monospace)
|
||||
if index.row() < self.__data_length() and not self.__is_local(index):
|
||||
font.setWeight(QFont.Bold)
|
||||
else:
|
||||
font.setWeight(QFont.Normal)
|
||||
return font
|
||||
|
||||
if role == Qt.ToolTipRole:
|
||||
if index.row() == self.__data_length():
|
||||
if index.column() == 1:
|
||||
return self.tr("Disabled, please set the variable name first.")
|
||||
else:
|
||||
return None
|
||||
if self.__key(index) in self.__readonly:
|
||||
return self.tr("Readonly, please edit this via setting the appropriate setting.")
|
||||
|
||||
def headerData(self, section: int, orientation: Qt.Orientation, role: int = Qt.DisplayRole) -> Any:
|
||||
if role == Qt.DisplayRole:
|
||||
if orientation == Qt.Horizontal:
|
||||
return self.__title(section)
|
||||
if role == Qt.DecorationRole:
|
||||
if orientation == Qt.Vertical:
|
||||
if section < self.__data_length() and not self.__is_readonly(section):
|
||||
if self.__is_global(section) and self.__is_local(section):
|
||||
return icon("mdi.refresh-circle", "ei.refresh")
|
||||
if self.__is_local(section):
|
||||
return icon("mdi.delete", "ei.remove-sign")
|
||||
return icon("mdi.lock", "ei.lock")
|
||||
if role == Qt.TextAlignmentRole:
|
||||
return Qt.AlignVCenter + Qt.AlignHCenter
|
||||
return None
|
||||
|
||||
def flags(self, index: QModelIndex):
|
||||
# Disable the value cell on rows without a name
|
||||
if index.row() == self.__data_length():
|
||||
if index.column() == 1:
|
||||
return super().flags(index) ^ Qt.ItemIsEnabled
|
||||
else:
|
||||
return Qt.ItemIsEditable | super().flags(index)
|
||||
|
||||
# Disable readonly variables
|
||||
if self.__is_readonly(index):
|
||||
return super().flags(index) ^ Qt.ItemIsEnabled
|
||||
|
||||
return Qt.ItemIsEditable | super().flags(index)
|
||||
|
||||
def rowCount(self, parent: QModelIndex = None) -> int:
|
||||
parent = parent if parent else QModelIndex()
|
||||
# The length of the outer list.
|
||||
return self.__data_length() + 1
|
||||
|
||||
def columnCount(self, parent: QModelIndex = None) -> int:
|
||||
parent = parent if parent else QModelIndex()
|
||||
return 2
|
||||
|
||||
def setData(self, index: QModelIndex, value: Any, role: int = Qt.DisplayRole) -> bool:
|
||||
if role != Qt.EditRole:
|
||||
return False
|
||||
# Do not accept unchanged contents
|
||||
if index.row() < self.__data_length() and (value == self.__key(index) or value == self.__value(index)):
|
||||
return False
|
||||
|
||||
# TODO: restrict spaces in variable names
|
||||
|
||||
if index.column() == 0:
|
||||
if not self.__is_key_valid(value):
|
||||
return False
|
||||
# Do not accept existing variable names
|
||||
if value in self.__data_map.keys():
|
||||
return False
|
||||
else:
|
||||
if index.row() == self.__data_length():
|
||||
self.beginInsertRows(QModelIndex(), self.rowCount(index), self.rowCount(index))
|
||||
self.endInsertRows()
|
||||
self.__data_map[value] = ""
|
||||
self.core.lgd.save_config()
|
||||
self.dataChanged.emit(index, index, [])
|
||||
self.headerDataChanged.emit(Qt.Vertical, index.row(), index.row())
|
||||
# if we are on the last row, add a new last row to the table when setting the variable name
|
||||
else:
|
||||
# if we are not in the last row, we have to update an existing variable name
|
||||
old_key = self.__key(index)
|
||||
self.__data_map[value] = self.__data_map[old_key]
|
||||
self.core.lgd.save_config()
|
||||
# since we delete and add a new key, the new key will be moved at the end
|
||||
# deleting a local key can have the following effects
|
||||
# unique local key:
|
||||
# old key deleted, new key added -> update range from index to end
|
||||
# old local key masking global key:
|
||||
# old key remains, new key added -> insert row for new key, update from index to end
|
||||
# new key masking global key:
|
||||
# can't happen because we do not accept existing keys
|
||||
if old_key in self.__data_map.maps[0].keys():
|
||||
# delete the old key if it is a local one, replacing a local key
|
||||
del self.__data_map[old_key]
|
||||
self.core.lgd.save_config()
|
||||
if old_key in self.__data_map.maps[1].keys():
|
||||
self.beginInsertRows(QModelIndex(), self.__data_length(), self.__data_length())
|
||||
self.endInsertRows()
|
||||
self.dataChanged.emit(index, self.index(index.row(), 1), [])
|
||||
self.dataChanged.emit(self.index(self.__data_length() - 1, 0), self.index(self.__data_length() - 1, 1), [])
|
||||
self.headerDataChanged.emit(Qt.Vertical, index.row(), index.row())
|
||||
self.headerDataChanged.emit(Qt.Vertical, self.__data_length() - 1, self.__data_length() - 1)
|
||||
else:
|
||||
self.__data_map[self.__key(index)] = value
|
||||
self.core.lgd.save_config()
|
||||
self.dataChanged.emit(self.index(index.row(), 0), index, [])
|
||||
self.headerDataChanged.emit(Qt.Vertical, index.row(), index.row())
|
||||
# pprint([item for item in self.__data_map.items()])
|
||||
return True
|
||||
|
||||
def removeRow(self, row: int, parent: QModelIndex = None) -> bool:
|
||||
parent = parent if parent else QModelIndex()
|
||||
# Refuse to remove the last row
|
||||
if row == self.__data_length():
|
||||
return False
|
||||
# Refuse to remove readonly keys
|
||||
if self.__is_readonly(row):
|
||||
return False
|
||||
# Refuse to remove keys that don't have our app_name (in this case the default)
|
||||
if not self.__is_local(row):
|
||||
return False
|
||||
if self.__is_global(row):
|
||||
del self.__data_map[self.__key(row)]
|
||||
self.core.lgd.save_config()
|
||||
self.dataChanged.emit(self.index(row, 0), self.index(row, 1), [])
|
||||
self.headerDataChanged.emit(Qt.Vertical, row, row)
|
||||
else:
|
||||
self.beginRemoveRows(QModelIndex(), row, row)
|
||||
del self.__data_map[self.__key(row)]
|
||||
self.core.lgd.save_config()
|
||||
self.endRemoveRows()
|
||||
# pprint([item for item in self.__data_map.items()])
|
||||
return True
|
||||
|
||||
|
||||
class MainDialog(QDialog):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.table = QTableView()
|
||||
self.model = EnvVarsTableModel(LegendaryCore())
|
||||
self.model.load("Tamarind")
|
||||
|
||||
self.table.setModel(self.model)
|
||||
self.table.verticalHeader().sectionPressed.disconnect()
|
||||
self.table.horizontalHeader().sectionPressed.disconnect()
|
||||
self.table.verticalHeader().sectionClicked.connect(self.model.removeRow)
|
||||
self.table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
|
||||
self.table.horizontalHeader().setStretchLastSection(True)
|
||||
self.table.setCornerButtonEnabled(False)
|
||||
|
||||
self.setLayout(QVBoxLayout(self))
|
||||
self.layout().addWidget(self.table)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
window = MainDialog()
|
||||
window.setFixedSize(800, 600)
|
||||
window.show()
|
||||
app.exec()
|
|
@ -1,64 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'rare/ui/components/tabs/settings/widgets/env_vars.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.15.7
|
||||
#
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_EnvVars(object):
|
||||
def setupUi(self, EnvVars):
|
||||
EnvVars.setObjectName("EnvVars")
|
||||
EnvVars.resize(592, 200)
|
||||
EnvVars.setMinimumSize(QtCore.QSize(0, 200))
|
||||
EnvVars.setWindowTitle("GroupBox")
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(EnvVars)
|
||||
self.verticalLayout.setContentsMargins(0, -1, 0, 0)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.env_vars_table = QtWidgets.QTableWidget(EnvVars)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.env_vars_table.sizePolicy().hasHeightForWidth())
|
||||
self.env_vars_table.setSizePolicy(sizePolicy)
|
||||
self.env_vars_table.setFrameShape(QtWidgets.QFrame.NoFrame)
|
||||
self.env_vars_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
|
||||
self.env_vars_table.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
|
||||
self.env_vars_table.setObjectName("env_vars_table")
|
||||
self.env_vars_table.setColumnCount(2)
|
||||
self.env_vars_table.setRowCount(1)
|
||||
item = QtWidgets.QTableWidgetItem()
|
||||
item.setText("-")
|
||||
self.env_vars_table.setVerticalHeaderItem(0, item)
|
||||
item = QtWidgets.QTableWidgetItem()
|
||||
self.env_vars_table.setHorizontalHeaderItem(0, item)
|
||||
item = QtWidgets.QTableWidgetItem()
|
||||
self.env_vars_table.setHorizontalHeaderItem(1, item)
|
||||
self.env_vars_table.horizontalHeader().setStretchLastSection(True)
|
||||
self.env_vars_table.verticalHeader().setStretchLastSection(False)
|
||||
self.verticalLayout.addWidget(self.env_vars_table)
|
||||
|
||||
self.retranslateUi(EnvVars)
|
||||
|
||||
def retranslateUi(self, EnvVars):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
EnvVars.setTitle(_translate("EnvVars", "Environment variables"))
|
||||
item = self.env_vars_table.horizontalHeaderItem(0)
|
||||
item.setText(_translate("EnvVars", "Key"))
|
||||
item = self.env_vars_table.horizontalHeaderItem(1)
|
||||
item.setText(_translate("EnvVars", "Value"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
EnvVars = QtWidgets.QGroupBox()
|
||||
ui = Ui_EnvVars()
|
||||
ui.setupUi(EnvVars)
|
||||
EnvVars.show()
|
||||
sys.exit(app.exec_())
|
|
@ -1,79 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>EnvVars</class>
|
||||
<widget class="QGroupBox" name="EnvVars">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>592</width>
|
||||
<height>200</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>200</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string notr="true">GroupBox</string>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Environment variables</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QTableWidget" name="env_vars_table">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="verticalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAsNeeded</enum>
|
||||
</property>
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QAbstractScrollArea::AdjustToContents</enum>
|
||||
</property>
|
||||
<attribute name="horizontalHeaderStretchLastSection">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<attribute name="verticalHeaderStretchLastSection">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<row>
|
||||
<property name="text">
|
||||
<string notr="true">-</string>
|
||||
</property>
|
||||
</row>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Key</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Value</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
Loading…
Reference in a new issue