Add GUI Support for OpCore Simplify (#512)

* Refactor OpCore-Simplify to GUI version

* New ConfigEditor

* Add requirement checks and installation in launchers

* Add GitHub Actions workflow to generate manifest.json

* Set compression level for asset

* Skip .git and __pycache__ folders

* Refactor update process to include integrity checker

* Add SMBIOS model selection

* Update README.md

* Update to main branch
This commit is contained in:
Hoang Hong Quan
2025-12-30 14:19:47 +07:00
committed by GitHub
parent 871d826ea4
commit 0e608a56ce
38 changed files with 4948 additions and 1636 deletions

View File

@@ -0,0 +1,310 @@
from PyQt6.QtWidgets import QWidget, QVBoxLayout, QTreeWidgetItem, QHeaderView, QAbstractItemView
from PyQt6.QtCore import Qt, pyqtSignal, QTimer
from PyQt6.QtGui import QBrush, QColor
from qfluentwidgets import CardWidget, TreeWidget, BodyLabel, StrongBodyLabel
from Scripts.datasets.config_tooltips import get_tooltip
from Scripts.value_formatters import format_value, get_value_type
from Scripts.styles import SPACING, COLORS, RADIUS
class ConfigEditor(QWidget):
config_changed = pyqtSignal(dict)
def __init__(self, parent=None):
super().__init__(parent)
self.setObjectName("configEditor")
self.original_config = None
self.modified_config = None
self.context = {}
self.mainLayout = QVBoxLayout(self)
self._init_ui()
def _init_ui(self):
self.mainLayout.setContentsMargins(0, 0, 0, 0)
self.mainLayout.setSpacing(0)
card = CardWidget()
card.setBorderRadius(RADIUS["card"])
card_layout = QVBoxLayout(card)
card_layout.setContentsMargins(SPACING["large"], SPACING["large"], SPACING["large"], SPACING["large"])
card_layout.setSpacing(SPACING["medium"])
title = StrongBodyLabel("Config Editor")
card_layout.addWidget(title)
description = BodyLabel("View differences between original and modified config.plist")
description.setStyleSheet("color: {}; font-size: 13px;".format(COLORS["text_secondary"]))
card_layout.addWidget(description)
self.tree = TreeWidget()
self.tree.setHeaderLabels(["Key", "", "Original", "Modified"])
self.tree.setColumnCount(4)
self.tree.setRootIsDecorated(True)
self.tree.setItemsExpandable(True)
self.tree.setExpandsOnDoubleClick(False)
self.tree.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
self.tree.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
self.tree.itemExpanded.connect(self._update_tree_height)
self.tree.itemCollapsed.connect(self._update_tree_height)
header = self.tree.header()
header.setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents)
header.setSectionResizeMode(1, QHeaderView.ResizeMode.ResizeToContents)
header.setSectionResizeMode(2, QHeaderView.ResizeMode.Stretch)
header.setSectionResizeMode(3, QHeaderView.ResizeMode.Stretch)
self.tree.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
card_layout.addWidget(self.tree)
self.mainLayout.addWidget(card)
def load_configs(self, original, modified, context=None):
self.original_config = original
self.modified_config = modified
self.context = context or {}
self.tree.clear()
self._render_config(self.original_config, self.modified_config, [])
QTimer.singleShot(0, self._update_tree_height)
def _get_all_keys_from_both_configs(self, original, modified):
original_keys = set(original.keys()) if isinstance(original, dict) else set()
modified_keys = set(modified.keys()) if isinstance(modified, dict) else set()
return sorted(original_keys | modified_keys)
def _determine_change_type(self, original_value, modified_value, key_in_original, key_in_modified):
if not key_in_original:
return "added"
elif not key_in_modified:
return "removed"
elif original_value != modified_value:
return "modified"
return None
def _get_effective_value(self, original_value, modified_value):
return modified_value if modified_value is not None else original_value
def _get_safe_value(self, value, default = None):
return value if value is not None else default
def _set_value_columns(self, item, original_value, modified_value, change_type, is_dict=False, is_array=False):
if original_value is not None:
if is_dict:
item.setText(2, "<object: {} keys>".format(len(original_value)))
elif is_array:
item.setText(2, "<array: {} items>".format(len(original_value)))
else:
item.setText(2, format_value(original_value))
item.setData(2, Qt.ItemDataRole.UserRole, get_value_type(original_value))
else:
item.setText(2, "")
if change_type is not None and modified_value is not None:
if is_dict:
item.setText(3, "<object: {} keys>".format(len(modified_value)))
elif is_array:
item.setText(3, "<array: {} items>".format(len(modified_value)))
else:
item.setText(3, format_value(modified_value))
item.setData(3, Qt.ItemDataRole.UserRole, get_value_type(modified_value))
else:
item.setText(3, "")
def _build_path_string(self, path_parts):
if not path_parts:
return ""
return ".".join(path_parts)
def _build_array_path(self, path_parts, index):
return path_parts + [f"[{index}]"]
def _render_config(self, original, modified, path_parts, parent_item=None):
if parent_item is None:
parent_item = self.tree.invisibleRootItem()
all_keys = self._get_all_keys_from_both_configs(original, modified)
for key in all_keys:
current_path_parts = path_parts + [key]
current_path = self._build_path_string(current_path_parts)
original_value = original.get(key) if isinstance(original, dict) else None
modified_value = modified.get(key) if isinstance(modified, dict) else None
key_in_original = key in original if isinstance(original, dict) else False
key_in_modified = key in modified if isinstance(modified, dict) else False
change_type = self._determine_change_type(
original_value, modified_value, key_in_original, key_in_modified
)
effective_value = self._get_effective_value(original_value, modified_value)
if isinstance(effective_value, list):
self._render_array(
original_value if isinstance(original_value, list) else [],
modified_value if isinstance(modified_value, list) else [],
current_path_parts,
parent_item
)
continue
item = QTreeWidgetItem(parent_item)
item.setText(0, key)
item.setData(0, Qt.ItemDataRole.UserRole, current_path)
self._apply_highlighting(item, change_type)
self._setup_tooltip(item, current_path, modified_value, original_value)
if isinstance(effective_value, dict):
item.setData(3, Qt.ItemDataRole.UserRole, "dict")
self._set_value_columns(item, original_value, modified_value, change_type, is_dict=True)
self._render_config(
self._get_safe_value(original_value, {}),
self._get_safe_value(modified_value, {}),
current_path_parts,
item
)
else:
self._set_value_columns(item, original_value, modified_value, change_type)
def _render_array(self, original_array, modified_array, path_parts, parent_item):
change_type = self._determine_change_type(
original_array, modified_array,
original_array is not None, modified_array is not None
)
effective_original = self._get_safe_value(original_array, [])
effective_modified = self._get_safe_value(modified_array, [])
path_string = self._build_path_string(path_parts)
array_key = path_parts[-1] if path_parts else "array"
item = QTreeWidgetItem(parent_item)
item.setText(0, array_key)
item.setData(0, Qt.ItemDataRole.UserRole, path_string)
item.setData(3, Qt.ItemDataRole.UserRole, "array")
self._apply_highlighting(item, change_type)
self._setup_tooltip(item, path_string, modified_array, original_array)
self._set_value_columns(item, original_array, modified_array, change_type, is_array=True)
original_len = len(effective_original)
modified_len = len(effective_modified)
max_len = max(original_len, modified_len)
for i in range(max_len):
original_element = effective_original[i] if i < original_len else None
modified_element = effective_modified[i] if i < modified_len else None
element_change_type = self._determine_change_type(
original_element, modified_element,
original_element is not None, modified_element is not None
)
effective_element = self._get_effective_value(original_element, modified_element)
if effective_element is None:
continue
element_path_parts = self._build_array_path(path_parts, i)
element_path = self._build_path_string(element_path_parts)
element_item = QTreeWidgetItem(item)
element_item.setText(0, "[{}]".format(i))
element_item.setData(0, Qt.ItemDataRole.UserRole, element_path)
self._apply_highlighting(element_item, element_change_type)
self._setup_tooltip(element_item, element_path, modified_element, original_element)
if isinstance(effective_element, dict):
element_item.setData(3, Qt.ItemDataRole.UserRole, "dict")
self._set_value_columns(element_item, original_element, modified_element, element_change_type, is_dict=True)
self._render_config(
self._get_safe_value(original_element, {}),
self._get_safe_value(modified_element, {}),
element_path_parts,
element_item
)
elif isinstance(effective_element, list):
element_item.setData(3, Qt.ItemDataRole.UserRole, "array")
self._set_value_columns(element_item, original_element, modified_element, element_change_type, is_array=True)
self._render_array(
self._get_safe_value(original_element, []),
self._get_safe_value(modified_element, []),
element_path_parts,
element_item
)
else:
self._set_value_columns(element_item, original_element, modified_element, element_change_type)
def _apply_highlighting(self, item, change_type=None):
if change_type == "added":
color = "#E3F2FD"
status_text = "A"
elif change_type == "removed":
color = "#FFEBEE"
status_text = "R"
elif change_type == "modified":
color = "#FFF9C4"
status_text = "M"
else:
color = None
status_text = ""
item.setText(1, status_text)
if color:
brush = QBrush(QColor(color))
else:
brush = QBrush()
for col in range(4):
item.setBackground(col, brush)
def _setup_tooltip(self, item, key_path, value, original_value=None):
tooltip_text = get_tooltip(key_path, value, original_value, self.context)
item.setToolTip(0, tooltip_text)
def _calculate_tree_height(self):
if self.tree.topLevelItemCount() == 0:
return self.tree.header().height() if self.tree.header().isVisible() else 0
header_height = self.tree.header().height() if self.tree.header().isVisible() else 0
first_item = self.tree.topLevelItem(0)
row_height = 24
if first_item:
rect = self.tree.visualItemRect(first_item)
if rect.height() > 0:
row_height = rect.height()
else:
font_metrics = self.tree.fontMetrics()
row_height = font_metrics.height() + 6
def count_visible_rows(item):
count = 1
if item.isExpanded():
for i in range(item.childCount()):
count += count_visible_rows(item.child(i))
return count
total_rows = 0
for i in range(self.tree.topLevelItemCount()):
total_rows += count_visible_rows(self.tree.topLevelItem(i))
padding = 10
return header_height + (total_rows * row_height) + padding
def _update_tree_height(self):
height = self._calculate_tree_height()
if height > 0:
self.tree.setFixedHeight(height)