1
0
Fork 0
mirror of synced 2024-06-26 18:20:50 +12:00

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:
loathingKernel 2023-03-29 14:07:19 +03:00
parent b1d438c34c
commit 4b2bc7f91f
No known key found for this signature in database
GPG key ID: CE0C72D0B53821FD
4 changed files with 336 additions and 435 deletions

View file

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

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

View file

@ -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_())

View file

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