feat(settings): added advanced
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				Code check / Check code (push) Successful in 1m6s
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	Code check / Check code (push) Successful in 1m6s
				
			Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
This commit is contained in:
		| @@ -5,9 +5,8 @@ from typing import cast, TYPE_CHECKING | ||||
| from PySide6.QtGui import QPixmap, QIcon, QTextCursor, QColor | ||||
| from PySide6.QtWidgets import ( | ||||
|     QDialog, QFormLayout, QHBoxLayout, QLabel, QVBoxLayout, QListWidget, QScrollArea, QWidget, QListWidgetItem, QSizePolicy, QApplication, QProgressBar, QScroller, | ||||
|     QTabWidget, QTableWidget, QHeaderView, QMessageBox, QTableWidgetItem, QTextEdit, QAbstractItemView, QStackedWidget | ||||
|     QTabWidget, QTableWidget, QHeaderView, QMessageBox, QTableWidgetItem, QTextEdit, QAbstractItemView, QStackedWidget, QComboBox | ||||
| ) | ||||
|  | ||||
| from PySide6.QtCore import Qt, QObject, Signal, QMimeDatabase, QTimer, QThreadPool, QRunnable, Slot, QProcess, QProcessEnvironment | ||||
| from icoextract import IconExtractor, IconExtractorError | ||||
| from PIL import Image | ||||
| @@ -1674,27 +1673,32 @@ class WinetricksDialog(QDialog): | ||||
|         if self.input_manager: | ||||
|             self.input_manager.disable_winetricks_mode() | ||||
|         super().reject() | ||||
|  | ||||
| class ExeSettingsDialog(QDialog): | ||||
|     def __init__(self, parent=None, theme=None, exe_path=None): | ||||
|         super().__init__(parent) | ||||
|         self.theme = theme if theme else theme_manager.apply_theme(read_theme_from_config()) | ||||
|         self.exe_path = exe_path | ||||
|         if not self.exe_path: | ||||
|             logger.error("Exe path not provided") | ||||
|             return | ||||
|         self.portproton_path = get_portproton_location() | ||||
|         if self.portproton_path is None: | ||||
|             logger.error("PortProton location not found") | ||||
|             return | ||||
|         base_path = os.path.join(self.portproton_path, "data") | ||||
|         self.start_sh = os.path.join(base_path, "scripts", "start.sh") | ||||
|         self.ppdb_path = self.exe_path + ".ppdb" if not self.exe_path.endswith('.ppdb') else self.exe_path | ||||
|         self.start_sh = [os.path.join(base_path, "scripts", "start.sh")] | ||||
|  | ||||
|         self.current_settings = {} | ||||
|         self.value_widgets = {} | ||||
|         self.original_values = {} | ||||
|         self.advanced_widgets = {} | ||||
|         self.original_display_values = {} | ||||
|         self.available_keys = set() | ||||
|         self.blocked_keys = set() | ||||
|         self.numa_nodes = {} | ||||
|         self.is_amd = False | ||||
|         self.locale_options = [] | ||||
|         self.logical_core_options = [] | ||||
|         self.amd_vulkan_drivers = [] | ||||
|         self.branch_name = _("Unknown") | ||||
|  | ||||
|         self.setWindowTitle(_("Exe Settings")) | ||||
| @@ -1717,25 +1721,16 @@ class ExeSettingsDialog(QDialog): | ||||
|  | ||||
|         self.current_theme_name = read_theme_from_config() | ||||
|  | ||||
|         # Create hints widget using common function | ||||
|         self.hints_widget, self.hints_labels = create_dialog_hints_widget( | ||||
|             self.theme, self.main_window, self.input_manager, context='winetricks' | ||||
|         ) | ||||
|         self.main_layout.addWidget(self.hints_widget) | ||||
|  | ||||
|         # Connect signals | ||||
|         if self.input_manager: | ||||
|             self.input_manager.button_event.connect( | ||||
|                 lambda *args: update_dialog_hints(self.hints_labels, self.main_window, self.input_manager, theme_manager, self.current_theme_name) | ||||
|             ) | ||||
|             self.input_manager.dpad_moved.connect( | ||||
|                 lambda *args: update_dialog_hints(self.hints_labels, self.main_window, self.input_manager, theme_manager, self.current_theme_name) | ||||
|             ) | ||||
|             update_dialog_hints(self.hints_labels, self.main_window, self.input_manager, theme_manager, self.current_theme_name) | ||||
|  | ||||
|         # Load current settings (includes list-db) | ||||
|         self.load_current_settings() | ||||
|  | ||||
|     def _get_process_args(self, subcommand_args): | ||||
|         """Get the full arguments for QProcess.start, handling flatpak separator.""" | ||||
|         if self.start_sh[0] == "flatpak": | ||||
|             return self.start_sh[1:] + ["--"] + subcommand_args | ||||
|         else: | ||||
|             return self.start_sh + subcommand_args | ||||
|  | ||||
|     def init_toggle_settings(self): | ||||
|         """Initialize predefined toggle settings with descriptions.""" | ||||
|         self.toggle_settings = { | ||||
| @@ -1786,10 +1781,15 @@ class ExeSettingsDialog(QDialog): | ||||
|         self.main_layout.setContentsMargins(10, 10, 10, 10) | ||||
|         self.main_layout.setSpacing(10) | ||||
|  | ||||
|         # Метка с текущей веткой (STABLE / DEVEL) | ||||
|         self.branch_label = QLabel(_("Detected branch: Unknown")) | ||||
|         self.branch_label.setStyleSheet("font-weight: bold;") | ||||
|         self.main_layout.addWidget(self.branch_label) | ||||
|         # Tab widget | ||||
|         self.tab_widget = QTabWidget() | ||||
|         self.main_tab = QWidget() | ||||
|         self.main_tab_layout = QVBoxLayout(self.main_tab) | ||||
|         self.advanced_tab = QWidget() | ||||
|         self.advanced_tab_layout = QVBoxLayout(self.advanced_tab) | ||||
|  | ||||
|         self.tab_widget.addTab(self.main_tab, _("Main")) | ||||
|         self.tab_widget.addTab(self.advanced_tab, _("Advanced")) | ||||
|  | ||||
|         # Таблица настроек | ||||
|         self.settings_table = QTableWidget() | ||||
| @@ -1806,12 +1806,31 @@ class ExeSettingsDialog(QDialog): | ||||
|         self.settings_table.verticalHeader().setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents) | ||||
|         self.settings_table.setTextElideMode(Qt.TextElideMode.ElideNone) | ||||
|         self.settings_table.setStyleSheet(self.theme.WINETRICKS_TABBLE_STYLE) | ||||
|         self.main_layout.addWidget(self.settings_table) | ||||
|         self.main_tab_layout.addWidget(self.settings_table) | ||||
|  | ||||
|         # Таблица Advanced | ||||
|         self.advanced_table = QTableWidget() | ||||
|         self.advanced_table.setFocusPolicy(Qt.FocusPolicy.StrongFocus) | ||||
|         self.advanced_table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) | ||||
|         self.advanced_table.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection) | ||||
|         self.advanced_table.setColumnCount(3) | ||||
|         self.advanced_table.setHorizontalHeaderLabels([_("Setting"), _("Value"), _("Description")]) | ||||
|         self.advanced_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents) | ||||
|         self.advanced_table.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.Fixed) | ||||
|         self.advanced_table.horizontalHeader().setSectionResizeMode(2, QHeaderView.ResizeMode.Stretch) | ||||
|         self.advanced_table.horizontalHeader().resizeSection(1, 200) | ||||
|         self.advanced_table.setWordWrap(True) | ||||
|         self.advanced_table.verticalHeader().setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents) | ||||
|         self.advanced_table.setTextElideMode(Qt.TextElideMode.ElideNone) | ||||
|         self.advanced_table.setStyleSheet(self.theme.WINETRICKS_TABBLE_STYLE) | ||||
|         self.advanced_tab_layout.addWidget(self.advanced_table) | ||||
|  | ||||
|         self.main_layout.addWidget(self.tab_widget) | ||||
|  | ||||
|         # Кнопки | ||||
|         button_layout = QHBoxLayout() | ||||
|         self.apply_button = AutoSizeButton(_("Apply"), icon=theme_manager.get_icon("apply")) | ||||
|         self.cancel_button = AutoSizeButton(_("Cancel"), icon=theme_manager.get_icon("cancel")) | ||||
|         self.apply_button = AutoSizeButton(_("Apply"), icon=ThemeManager().get_icon("apply")) | ||||
|         self.cancel_button = AutoSizeButton(_("Cancel"), icon=ThemeManager().get_icon("cancel")) | ||||
|         self.apply_button.setStyleSheet(self.theme.ACTION_BUTTON_STYLE) | ||||
|         self.cancel_button.setStyleSheet(self.theme.ACTION_BUTTON_STYLE) | ||||
|         button_layout.addWidget(self.apply_button) | ||||
| @@ -1821,69 +1840,107 @@ class ExeSettingsDialog(QDialog): | ||||
|         self.apply_button.clicked.connect(self.apply_changes) | ||||
|         self.cancel_button.clicked.connect(self.reject) | ||||
|  | ||||
|  | ||||
|     def load_current_settings(self): | ||||
|         """Load available toggles first, then current settings.""" | ||||
|         process = QProcess(self) | ||||
|         process.finished.connect(self.on_list_db_finished) | ||||
|         process.start(self.start_sh, ["cli", "--list-db"]) | ||||
|         process.start(self.start_sh[0], ["cli", "--list-db"]) | ||||
|  | ||||
|  | ||||
|     def on_list_db_finished(self, exit_code, exit_status): | ||||
|         """Handle --list-db output and extract available keys.""" | ||||
|         """Handle --list-db output and extract available keys and system info.""" | ||||
|         process = cast(QProcess, self.sender()) | ||||
|         self.available_keys = set() | ||||
|         self.blocked_keys = set() | ||||
|         if exit_code == 0 and exit_status == QProcess.ExitStatus.NormalExit: | ||||
|             output = bytes(process.readAllStandardOutput().data()).decode('utf-8', 'ignore') | ||||
|             for line in output.splitlines(): | ||||
|                 if "Branch in used:" in line: | ||||
|                     self.branch_name = line.split(":", 1)[1].strip() | ||||
|                     self.branch_label.setText(_("Detected branch: ") + self.branch_name) | ||||
|             lines = output.splitlines() | ||||
|             self.numa_nodes = {} | ||||
|             self.is_amd = False | ||||
|             self.logical_core_options = [] | ||||
|             self.locale_options = [] | ||||
|             self.amd_vulkan_drivers = [] | ||||
|             for line in lines: | ||||
|                 line_stripped = line.strip() | ||||
|                 if not line_stripped: | ||||
|                     continue | ||||
|                 stripped_line = line.strip() | ||||
|                 if stripped_line.startswith("PW_"): | ||||
|                     parts = stripped_line.split(maxsplit=1) | ||||
|                 if re.match(r'^[A-Z_0-9]+=[^=]+$', line_stripped) and not line_stripped.startswith('PW_'): | ||||
|                     # System info | ||||
|                     k, v = line_stripped.split('=', 1) | ||||
|                     if k.startswith('NUMA_NODE_'): | ||||
|                         node_id = k[10:] | ||||
|                         self.numa_nodes[node_id] = v | ||||
|                     elif k == 'IS_AMD': | ||||
|                         self.is_amd = v.lower() == 'true' | ||||
|                     elif k == 'LOGICAL_CORE_OPTIONS': | ||||
|                         self.logical_core_options = v.split('!') if v else [] | ||||
|                     elif k == 'LOCALE_LIST': | ||||
|                         self.locale_options = v.split('!') if v else [] | ||||
|                     elif k == 'AMD_VULKAN_DRIVER_LIST': | ||||
|                         self.amd_vulkan_drivers = v.split('!') if v else [] | ||||
|                     continue | ||||
|                 if line_stripped.startswith('PW_'): | ||||
|                     parts = line_stripped.split(maxsplit=1) | ||||
|                     key = parts[0] | ||||
|                     self.available_keys.add(key) | ||||
|                     if len(parts) > 1 and parts[1] == "blocked": | ||||
|                     if len(parts) > 1 and 'blocked' in parts[1]: | ||||
|                         self.blocked_keys.add(key) | ||||
|  | ||||
|             # Показываем только пересечение | ||||
|             self.available_keys &= set(self.toggle_settings.keys()) | ||||
|             logger.debug(f"Filtered available keys (intersection): {self.available_keys}") | ||||
|         else: | ||||
|             logger.warning("Failed to get --list-db output; showing all toggles") | ||||
|             self.available_keys = set(self.toggle_settings.keys()) | ||||
|  | ||||
|         # Загружаем текущие настройки | ||||
|         process = QProcess(self) | ||||
|         process.finished.connect(self.on_show_ppdb_finished) | ||||
|         process.start(self.start_sh, ["cli", "--show-ppdb", self.ppdb_path]) | ||||
|         process.start(self.start_sh[0], ["cli", "--show-ppdb", f"{self.exe_path}.ppdb"]) | ||||
|  | ||||
|     def on_show_ppdb_finished(self, exit_code, exit_status): | ||||
|         """Handle --show-ppdb output.""" | ||||
|         process = cast(QProcess, self.sender()) | ||||
|         if exit_code != 0 or exit_status != QProcess.ExitStatus.NormalExit: | ||||
|             logger.warning("Failed to load settings, using defaults") | ||||
|             # Fallback to defaults if load fails | ||||
|             for key in self.toggle_settings: | ||||
|                 self.current_settings[key] = '0' | ||||
|             for adv_key in ['PW_WINDOWS_VER', 'WINEDLLOVERRIDES', 'LAUNCH_PARAMETERS', | ||||
|                             'PW_WINE_CPU_TOPOLOGY', 'PW_MESA_GL_VERSION_OVERRIDE', | ||||
|                             'PW_VKD3D_FEATURE_LEVEL', 'PW_LOCALE_SELECT', | ||||
|                             'PW_MESA_VK_WSI_PRESENT_MODE', 'PW_AMD_VULKAN_USE', | ||||
|                             'PW_CPU_NUMA_NODE_INDEX']: | ||||
|                 self.current_settings[adv_key] = 'disabled' if 'TOPOLOGY' in adv_key or 'SELECT' in adv_key or 'MODE' in adv_key or 'LEVEL' in adv_key or 'GL_VERSION' in adv_key or 'NUMA' in adv_key else '' | ||||
|         else: | ||||
|             output = bytes(process.readAllStandardOutput().data()).decode('utf-8', 'ignore').strip() | ||||
|             self.current_settings = {} | ||||
|             for line in output.split('\n'): | ||||
|                 if '=' in line and line.strip().startswith('PW_'): | ||||
|                     key, val = line.split('=', 1) | ||||
|                     self.current_settings[key.strip()] = val.strip() | ||||
|             logger.debug(f"Loaded current settings: {self.current_settings}") | ||||
|                 line_stripped = line.strip() | ||||
|                 if '=' in line_stripped: | ||||
|                     # Parse all KEY=VALUE lines, not just specific prefixes, to catch more | ||||
|                     try: | ||||
|                         key, val = line_stripped.split('=', 1) | ||||
|                         if key in self.toggle_settings or key in ['PW_WINDOWS_VER', 'WINEDLLOVERRIDES', 'LAUNCH_PARAMETERS', | ||||
|                                                                  'PW_WINE_CPU_TOPOLOGY', 'PW_MESA_GL_VERSION_OVERRIDE', | ||||
|                                                                  'PW_VKD3D_FEATURE_LEVEL', 'PW_LOCALE_SELECT', | ||||
|                                                                  'PW_MESA_VK_WSI_PRESENT_MODE', 'PW_AMD_VULKAN_USE', | ||||
|                                                                  'PW_CPU_NUMA_NODE_INDEX', 'PW_TASKSET_SLR']: | ||||
|                             self.current_settings[key] = val | ||||
|                     except ValueError: | ||||
|                         continue | ||||
|  | ||||
|         # Force blocked settings to '0' | ||||
|         for key in self.blocked_keys: | ||||
|             self.current_settings[key] = '0' | ||||
|  | ||||
|         self.original_values = self.current_settings.copy() | ||||
|         for key in set(self.toggle_settings.keys()): | ||||
|             self.original_values.setdefault(key, '0') | ||||
|  | ||||
|         self.populate_table() | ||||
|         self.populate_advanced() | ||||
|  | ||||
|     def populate_table(self): | ||||
|         """Populate the table with settings that are available in both lists.""" | ||||
|         self.settings_table.setRowCount(0) | ||||
|         self.value_widgets.clear() | ||||
|         self.original_values.clear() | ||||
|         self.settings_table.verticalHeader().setVisible(False) | ||||
|  | ||||
|         visible_keys = sorted(self.available_keys) if self.available_keys else sorted(self.toggle_settings.keys()) | ||||
| @@ -1922,16 +1979,187 @@ class ExeSettingsDialog(QDialog): | ||||
|  | ||||
|             self.settings_table.setItem(row, 0, name_item) | ||||
|             self.value_widgets[(row, 1)] = checkbox | ||||
|             self.original_values[toggle] = current_val | ||||
|  | ||||
|         self.settings_table.resizeRowsToContents() | ||||
|         if self.settings_table.rowCount() > 0: | ||||
|             self.settings_table.setCurrentCell(0, 0) | ||||
|             self.settings_table.setFocus(Qt.FocusReason.OtherFocusReason) | ||||
|  | ||||
|     def populate_advanced(self): | ||||
|         """Populate the advanced tab with table format.""" | ||||
|         self.advanced_table.setRowCount(0) | ||||
|         self.advanced_widgets.clear() | ||||
|         self.original_display_values = {} | ||||
|         self.advanced_table.verticalHeader().setVisible(False) | ||||
|  | ||||
|         current = self.current_settings | ||||
|         disabled_text = _('disabled') | ||||
|  | ||||
|         # Define advanced settings configuration | ||||
|         advanced_settings = [] | ||||
|  | ||||
|         # 1. Windows version | ||||
|         advanced_settings.append({ | ||||
|             'key': 'PW_WINDOWS_VER', | ||||
|             'name': _("Windows version"), | ||||
|             'description': _("Changing the WINDOWS emulation version may be required to run older games. WINDOWS versions below 10 do not support new games with DirectX 12"), | ||||
|             'type': 'combo', | ||||
|             'options': ['11', '10', '7', 'XP'], | ||||
|             'default': '10' | ||||
|         }) | ||||
|  | ||||
|         # 2. Forced to use/disable libraries | ||||
|         advanced_settings.append({ | ||||
|             'key': 'WINEDLLOVERRIDES', | ||||
|             'name': _("DLL Overrides"), | ||||
|             'description': _("Forced to use/disable the library only for the given application.\n\nA brief instruction:\n* libraries are written WITHOUT the .dll file extension\n* libraries are separated by semicolons - ;\n* library=n - use the WINDOWS (third-party) library\n* library=b - use WINE (built-in) library\n* library=n,b - use WINDOWS library and then WINE\n* library=b,n - use WINE library and then WINDOWS\n* library= - disable the use of this library\n\nExample: libglesv2=;d3dx9_36,d3dx9_42=n,b;mfc120=b,n"), | ||||
|             'type': 'text', | ||||
|             'default': '' | ||||
|         }) | ||||
|  | ||||
|         # 3. Launch arguments | ||||
|         advanced_settings.append({ | ||||
|             'key': 'LAUNCH_PARAMETERS', | ||||
|             'name': _("Launch Arguments"), | ||||
|             'description': _("Adding an argument after the .exe file, just like you would add an argument in a shortcut on a WINDOWS system.\n\nExample: -dx11 -skipintro 1"), | ||||
|             'type': 'text', | ||||
|             'default': '' | ||||
|         }) | ||||
|  | ||||
|         # 4. CPU cores limit | ||||
|         advanced_settings.append({ | ||||
|             'key': 'PW_WINE_CPU_TOPOLOGY', | ||||
|             'name': _("CPU Cores Limit"), | ||||
|             'description': _("Limiting the number of CPU cores is useful for Unity games (It is recommended to set the value equal to 8)"), | ||||
|             'type': 'combo', | ||||
|             'options': [disabled_text] + self.logical_core_options, | ||||
|             'default': disabled_text | ||||
|         }) | ||||
|  | ||||
|         # 5. OpenGL version | ||||
|         advanced_settings.append({ | ||||
|             'key': 'PW_MESA_GL_VERSION_OVERRIDE', | ||||
|             'name': _("OpenGL Version"), | ||||
|             'description': _("You can select the required OpenGL version, some games require a forced Compatibility Profile (COMP)."), | ||||
|             'type': 'combo', | ||||
|             'options': [disabled_text, '4.6COMPAT', '4.5COMPAT', '4.3COMPAT', '4.1COMPAT', '3.3COMPAT', '3.2COMPAT'], | ||||
|             'default': disabled_text | ||||
|         }) | ||||
|  | ||||
|         # 6. VKD3D feature level | ||||
|         advanced_settings.append({ | ||||
|             'key': 'PW_VKD3D_FEATURE_LEVEL', | ||||
|             'name': _("VKD3D Feature Level"), | ||||
|             'description': _("You can set a forced feature level VKD3D for games on DirectX12"), | ||||
|             'type': 'combo', | ||||
|             'options': [disabled_text, '12_2', '12_1', '12_0', '11_1', '11_0'], | ||||
|             'default': disabled_text | ||||
|         }) | ||||
|  | ||||
|         # 7. Locale | ||||
|         advanced_settings.append({ | ||||
|             'key': 'PW_LOCALE_SELECT', | ||||
|             'name': _("Locale"), | ||||
|             'description': _("Force certain locale for an app. Fixes encoding issues in legacy software"), | ||||
|             'type': 'combo', | ||||
|             'options': [disabled_text] + self.locale_options, | ||||
|             'default': disabled_text | ||||
|         }) | ||||
|  | ||||
|         # 8. Present mode | ||||
|         advanced_settings.append({ | ||||
|             'key': 'PW_MESA_VK_WSI_PRESENT_MODE', | ||||
|             'name': _("Window Mode"), | ||||
|             'description': _("Window mode (for Vulkan and OpenGL):\nfifo - First in, first out. Limits the frame rate + no tearing. (VSync)\nimmediate - Unlimited frame rate + tearing.\nmailbox - Triple buffering. Unlimited frame rate + no tearing.\nrelaxed - Same as fifo but allows tearing when below the monitors refresh rate."), | ||||
|             'type': 'combo', | ||||
|             'options': [disabled_text, 'fifo', 'immediate', 'mailbox', 'relaxed'], | ||||
|             'default': disabled_text | ||||
|         }) | ||||
|  | ||||
|         # 9. AMD Vulkan (always show, block if not applicable) | ||||
|         amd_options = [disabled_text] + self.amd_vulkan_drivers if self.is_amd and self.amd_vulkan_drivers else [disabled_text] | ||||
|         advanced_settings.append({ | ||||
|             'key': 'PW_AMD_VULKAN_USE', | ||||
|             'name': _("AMD Vulkan Driver"), | ||||
|             'description': _("Select needed AMD vulkan implementation. Choosing which implementation of vulkan will be used to run the game"), | ||||
|             'type': 'combo', | ||||
|             'options': amd_options, | ||||
|             'default': disabled_text | ||||
|         }) | ||||
|  | ||||
|         # 10. NUMA node (always show if numa_nodes exist, block if <=1) | ||||
|         numa_ids = sorted(self.numa_nodes.keys()) | ||||
|         numa_options = [disabled_text] + numa_ids if len(numa_ids) > 1 else [disabled_text] | ||||
|         advanced_settings.append({ | ||||
|             'key': 'PW_CPU_NUMA_NODE_INDEX', | ||||
|             'name': _("NUMA Node"), | ||||
|             'description': _("NUMA node for CPU affinity. In multi-core systems, CPUs are split into NUMA nodes, each with its own local memory and cores. Binding a game to a single node reduces memory-access latency and limits costly core-to-core switches."), | ||||
|             'type': 'combo', | ||||
|             'options': numa_options, | ||||
|             'default': disabled_text | ||||
|         }) | ||||
|  | ||||
|         # Populate table | ||||
|         for setting in advanced_settings: | ||||
|             row = self.advanced_table.rowCount() | ||||
|             self.advanced_table.insertRow(row) | ||||
|  | ||||
|             # Name column | ||||
|             name_item = QTableWidgetItem(setting['name']) | ||||
|             name_item.setFlags(Qt.ItemFlag.ItemIsSelectable | Qt.ItemFlag.ItemIsEnabled) | ||||
|             self.advanced_table.setItem(row, 0, name_item) | ||||
|  | ||||
|             # Value column (widget) | ||||
|             if setting['type'] == 'combo': | ||||
|                 combo = QComboBox() | ||||
|                 combo.addItems(setting['options']) | ||||
|  | ||||
|                 # Get current value | ||||
|                 current_raw = current.get(setting['key'], setting['default']) | ||||
|                 if setting['key'] == 'PW_WINE_CPU_TOPOLOGY': | ||||
|                     current_val = disabled_text if current_raw == 'disabled' else (current_raw.split(':')[0] if isinstance(current_raw, str) and ':' in current_raw else current_raw) | ||||
|                 elif setting['key'] == 'PW_AMD_VULKAN_USE': | ||||
|                     current_val = disabled_text if not current_raw or current_raw == '' else current_raw | ||||
|                 else: | ||||
|                     current_val = disabled_text if current_raw == 'disabled' else current_raw | ||||
|  | ||||
|                 if current_val not in setting['options']: | ||||
|                     combo.addItem(current_val) | ||||
|                 combo.setCurrentText(current_val) | ||||
|  | ||||
|                 # Block if only disabled option | ||||
|                 if len(setting['options']) == 1: | ||||
|                     combo.setEnabled(False) | ||||
|  | ||||
|                 self.advanced_table.setCellWidget(row, 1, combo) | ||||
|                 self.advanced_widgets[setting['key']] = combo | ||||
|                 self.original_display_values[setting['key']] = current_val | ||||
|  | ||||
|             elif setting['type'] == 'text': | ||||
|                 text_edit = QTextEdit() | ||||
|                 current_val = current.get(setting['key'], setting['default']) | ||||
|                 text_edit.setPlainText(current_val) | ||||
|  | ||||
|                 self.advanced_table.setCellWidget(row, 1, text_edit) | ||||
|                 self.advanced_widgets[setting['key']] = text_edit | ||||
|                 self.original_display_values[setting['key']] = current_val | ||||
|  | ||||
|             # Description column | ||||
|             desc_item = QTableWidgetItem(setting['description']) | ||||
|             desc_item.setFlags(Qt.ItemFlag.ItemIsSelectable | Qt.ItemFlag.ItemIsEnabled) | ||||
|             desc_item.setToolTip(setting['description']) | ||||
|             desc_item.setTextAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter) | ||||
|             self.advanced_table.setItem(row, 2, desc_item) | ||||
|  | ||||
|         self.advanced_table.resizeRowsToContents() | ||||
|         if self.advanced_table.rowCount() > 0: | ||||
|             self.advanced_table.setCurrentCell(0, 0) | ||||
|  | ||||
|     def apply_changes(self): | ||||
|         """Apply changes by collecting diffs and running --edit-db.""" | ||||
|         """Apply changes by collecting diffs from both main and advanced tabs.""" | ||||
|         changes = [] | ||||
|  | ||||
|         # --- 1. Обычные (toggle) настройки --- | ||||
|         for key, orig_val in self.original_values.items(): | ||||
|             if key in self.blocked_keys: | ||||
|                 continue  # Skip blocked keys | ||||
| @@ -1952,14 +2180,32 @@ class ExeSettingsDialog(QDialog): | ||||
|             if new_val != orig_val: | ||||
|                 changes.append(f"{key}={new_val}") | ||||
|  | ||||
|         # --- 2. Advanced настройки --- | ||||
|         for key, widget in self.advanced_widgets.items(): | ||||
|             orig_val = self.original_display_values.get(key, '') | ||||
|             if isinstance(widget, QComboBox): | ||||
|                 new_val = widget.currentText() | ||||
|                 # приведение disabled к 'disabled' | ||||
|                 if new_val.lower() == _('disabled').lower(): | ||||
|                     new_val = 'disabled' | ||||
|             elif isinstance(widget, QTextEdit): | ||||
|                 new_val = widget.toPlainText().strip() | ||||
|             else: | ||||
|                 continue | ||||
|  | ||||
|             if new_val != orig_val: | ||||
|                 changes.append(f"{key}={new_val}") | ||||
|  | ||||
|         # --- 3. Проверка на изменения --- | ||||
|         if not changes: | ||||
|             QMessageBox.information(self, _("Info"), _("No changes to apply.")) | ||||
|             return | ||||
|  | ||||
|         # --- 4. Запуск процесса сохранения --- | ||||
|         process = QProcess(self) | ||||
|         process.finished.connect(self.on_edit_db_finished) | ||||
|         args = ["cli", "--edit-db", self.exe_path] + changes | ||||
|         process.start(self.start_sh, args) | ||||
|         process.start(self.start_sh[0], args) | ||||
|         self.apply_button.setEnabled(False) | ||||
|  | ||||
|     def on_edit_db_finished(self, exit_code, exit_status): | ||||
|   | ||||
		Reference in New Issue
	
	Block a user