diff --git a/configurator/configurator.py b/configurator/configurator.py index a182d012cb..e40931ee11 100755 --- a/configurator/configurator.py +++ b/configurator/configurator.py @@ -139,6 +139,10 @@ def load_ui(builder: Gtk.Builder) -> None: background: alpha(@theme_fg_color, 0.12); font-size: 0.85em; } +.option-value { + font-size: 1.35em; + font-weight: 700; +} .selected-option { background: alpha(@theme_selected_bg_color, 0.10); } @@ -330,8 +334,16 @@ def __init__(self, owner: "ConfiguratorWindow", option: EnvOption) -> None: self.description = make_label(css_class="choice-doc", ellipsize=True) text_box.pack_start(self.description, False, False, 0) + value_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) + box.pack_end(value_box, False, False, 0) + + self.value = make_label(css_class="option-value", selectable=True, ellipsize=True) + self.value.set_xalign(1) + self.value.set_max_width_chars(28) + value_box.pack_start(self.value, False, False, 0) + self.chevron = Gtk.Image.new_from_icon_name("pan-end-symbolic", Gtk.IconSize.MENU) - box.pack_end(self.chevron, False, False, 0) + value_box.pack_start(self.chevron, False, False, 0) def update( self, @@ -356,7 +368,8 @@ def update( value_label = tr(language, "default_dynamic") else: value_label = state.value if state.value != "" else "" - self.badge.set_text(f"{source_label}: {value_label}") + self.badge.set_text(source_label) + self.value.set_text(value_label) if language != self.language: self.language = language @@ -470,10 +483,12 @@ def _build_ui(self) -> None: self.detail_box.set_border_width(18) self.detail_header = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6) self.effective_group = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + self.often_used_group = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) self.unsupported_group = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) self.defaults_group = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) self.detail_box.pack_start(self.detail_header, False, False, 0) self.detail_box.pack_start(self.effective_group, False, False, 0) + self.detail_box.pack_start(self.often_used_group, False, False, 0) self.detail_box.pack_start(self.unsupported_group, False, False, 0) self.detail_box.pack_start(self.defaults_group, False, False, 0) detail_scroll.add(self.detail_box) @@ -662,6 +677,7 @@ def _refresh_details(self) -> None: for container in ( self.detail_header, self.effective_group, + self.often_used_group, self.unsupported_group, self.defaults_group, ): @@ -669,6 +685,7 @@ def _refresh_details(self) -> None: child.destroy() self.effective_group.hide() + self.often_used_group.hide() self.unsupported_group.hide() self.defaults_group.hide() @@ -684,13 +701,21 @@ def _refresh_details(self) -> None: return self._add_entry_header(entry) - active, defaults = self.store.effective_values(entry) - visible_names = {state.option.name for state in (*active, *defaults)} + active, often_used, defaults = self.store.effective_values(entry) + visible_names = {state.option.name for state in (*active, *often_used, *defaults)} if self.selected_option_name not in visible_names: self.selected_option_name = None self._add_option_group( self.effective_group, tr(self.language, "effective"), active, entry, default_rows=False ) + if often_used: + self._add_option_group( + self.often_used_group, + tr(self.language, "often_used"), + often_used, + entry, + default_rows=True, + ) self._add_unsupported(entry) self._add_option_group( self.defaults_group, tr(self.language, "defaults"), defaults, entry, default_rows=True @@ -698,6 +723,10 @@ def _refresh_details(self) -> None: self.detail_header.show_all() self.effective_group.show_all() + if often_used: + self.often_used_group.show_all() + else: + self.often_used_group.hide() self.defaults_group.show_all() if self.unsupported_group.get_children(): self.unsupported_group.show_all() @@ -878,8 +907,8 @@ def toggle_option(self, entry: EntryRecord, option_name: str) -> None: self._insert_option_editor_after(row.get_parent(), row, entry, state) def _state_for_option(self, entry: EntryRecord, option_name: str) -> Optional[ValueState]: - active, defaults = self.store.effective_values(entry) - for state in (*active, *defaults): + active, often_used, defaults = self.store.effective_values(entry) + for state in (*active, *often_used, *defaults): if state.option.name == option_name: return state return None diff --git a/configurator/model.py b/configurator/model.py index 8d0a3ae5f3..529df81637 100644 --- a/configurator/model.py +++ b/configurator/model.py @@ -81,6 +81,54 @@ class ValueState: SectionIdentity = Tuple[str, str] +OFTEN_USED_OPTION_NAMES = { + "BOX64_DYNAREC_BIGBLOCK", + "BOX64_DYNAREC_CALLRET", + "BOX64_DYNAREC_SAFEFLAGS", + "BOX64_DYNAREC_STRONGMEM", + "BOX64_DYNAREC_DIRTY", + "BOX64_DYNAREC_FASTROUND", + "BOX64_DYNAREC_FASTNAN", + "BOX64_MAXCPU", +} + + +PROFILE_OPTION_NAME = "BOX64_PROFILE" +PROFILE_DEFAULT_VALUE = "default" +PROFILE_SETTINGS = { + "safest": ( + ("BOX64_DYNAREC_FASTNAN", "0"), + ("BOX64_DYNAREC_FASTROUND", "0"), + ("BOX64_DYNAREC_BIGBLOCK", "0"), + ("BOX64_DYNAREC_SAFEFLAGS", "2"), + ("BOX64_DYNAREC_STRONGMEM", "2"), + ), + "safe": ( + ("BOX64_DYNAREC_BIGBLOCK", "0"), + ("BOX64_DYNAREC_SAFEFLAGS", "2"), + ("BOX64_DYNAREC_STRONGMEM", "1"), + ), + "fast": ( + ("BOX64_DYNAREC_CALLRET", "1"), + ("BOX64_DYNAREC_SEP", "1"), + ("BOX64_DYNAREC_BIGBLOCK", "3"), + ("BOX64_DYNAREC_SAFEFLAGS", "0"), + ("BOX64_DYNAREC_STRONGMEM", "1"), + ("BOX64_DYNAREC_DIRTY", "1"), + ("BOX64_DYNAREC_FORWARD", "1024"), + ), + "fastest": ( + ("BOX64_DYNAREC_CALLRET", "1"), + ("BOX64_DYNAREC_SEP", "2"), + ("BOX64_DYNAREC_BIGBLOCK", "3"), + ("BOX64_DYNAREC_SAFEFLAGS", "0"), + ("BOX64_DYNAREC_STRONGMEM", "0"), + ("BOX64_DYNAREC_DIRTY", "1"), + ("BOX64_DYNAREC_FORWARD", "1024"), + ), +} + + class ConfigStore: def __init__( self, system_path: Path, user_path: Path, catalog: UsageCatalog, machine_arch: str = "" @@ -183,7 +231,10 @@ def fork_entry(self, entry: EntryRecord) -> Tuple[SectionKey, bool]: def set_option(self, entry: EntryRecord, option_name: str, value: str) -> Tuple[SectionKey, bool]: section, forked = self._ensure_user_section(entry) - new_key = self.user.set_assignment(section.key, option_name, value) + if self._is_default_profile(option_name, value): + new_key = self.user.remove_assignment(section.key, option_name) + else: + new_key = self.user.set_assignment(section.key, option_name, value) self._mark_dirty() return new_key, forked @@ -196,11 +247,10 @@ def reset_option(self, entry: EntryRecord, option_name: str) -> SectionKey: if entry.system_section is not None: system_value = entry.system_section.last_value(option_name) - new_key = ( - self.user.set_assignment(user_section.key, option_name, system_value) - if system_value is not None - else self.user.remove_assignment(user_section.key, option_name) - ) + if system_value is not None and not self._is_default_profile(option_name, system_value): + new_key = self.user.set_assignment(user_section.key, option_name, system_value) + else: + new_key = self.user.remove_assignment(user_section.key, option_name) self._mark_dirty() return new_key @@ -210,7 +260,9 @@ def unsupported_assignments(self, entry: EntryRecord) -> Tuple[Assignment, ...]: assignment for assignment in direct_box64_assignments(section) if assignment.key not in self.catalog ) - def effective_values(self, entry: EntryRecord) -> Tuple[List[ValueState], List[ValueState]]: + def effective_values( + self, entry: EntryRecord + ) -> Tuple[List[ValueState], List[ValueState], List[ValueState]]: states: Dict[str, ValueState] = {} if entry.key.kind != "shared": @@ -218,23 +270,60 @@ def effective_values(self, entry: EntryRecord) -> Tuple[List[ValueState], List[V self._apply_matching_wildcards(states, entry) self._apply_direct(states, entry) + self._apply_profile(states) active: List[ValueState] = [] + often_used: List[ValueState] = [] defaults: List[ValueState] = [] for option in self.catalog.options: state = states.get(option.name) if state is None: - defaults.append( - ValueState( - option=option, - value=option.default_value, - source="default", - ) + default_state = ValueState( + option=option, + value=option.default_value, + source="default", ) + if option.name in OFTEN_USED_OPTION_NAMES: + often_used.append(default_state) + else: + defaults.append(default_state) else: active.append(state) - return active, defaults + active.sort(key=lambda state: state.option.name != PROFILE_OPTION_NAME) + return active, often_used, defaults + + def _apply_profile(self, states: Dict[str, ValueState]) -> None: + profile_option = self.catalog.by_name.get(PROFILE_OPTION_NAME) + if profile_option is None: + return + + profile_state = states.get(PROFILE_OPTION_NAME) + if profile_state is None: + profile_state = ValueState( + option=profile_option, + value=PROFILE_DEFAULT_VALUE, + source="default", + ) + states[PROFILE_OPTION_NAME] = profile_state + + for option_name, value in PROFILE_SETTINGS.get(profile_state.value.lower(), ()): + if option_name in states: + continue + option = self.catalog.by_name.get(option_name) + if option is None: + continue + states[option_name] = ValueState( + option=option, + value=value, + source="profile", + ) + + def _is_default_profile(self, option_name: str, value: Optional[str]) -> bool: + return ( + option_name == PROFILE_OPTION_NAME + and (value or "").strip().lower() == PROFILE_DEFAULT_VALUE + ) def _ensure_user_section(self, entry: EntryRecord) -> Tuple[RcSection, bool]: user_section = self.user.section_for_key(entry.key) diff --git a/configurator/text_en.json b/configurator/text_en.json index db8606a6f2..8945df22c0 100644 --- a/configurator/text_en.json +++ b/configurator/text_en.json @@ -9,6 +9,7 @@ "save": "Save", "select_entry": "Select an entry on the left.", "effective": "Effective options", + "often_used": "Often used options", "defaults": "Default values", "unsupported": "Other rcfile keys", "unsupported_help": "These keys are preserved when a system entry is forked, but this tool does not edit them.", @@ -23,6 +24,7 @@ "source_shared-system": "System shared", "source_wildcard-user": "User wildcard", "source_wildcard-system": "System wildcard", + "source_profile": "Profile", "source_default": "Default", "default_dynamic": "build dependent", "apply": "Apply", diff --git a/configurator/text_zh.json b/configurator/text_zh.json index c942737110..46b1fbad53 100644 --- a/configurator/text_zh.json +++ b/configurator/text_zh.json @@ -9,6 +9,7 @@ "save": "保存", "select_entry": "请在左侧选择一个条目。", "effective": "生效选项", + "often_used": "常用选项", "defaults": "默认值", "unsupported": "其他配置项", "unsupported_help": "系统条目复制到用户时会保留这些项,但此工具不会编辑它们。", @@ -23,6 +24,7 @@ "source_shared-system": "系统共享", "source_wildcard-user": "用户通配", "source_wildcard-system": "系统通配", + "source_profile": "预设", "source_default": "默认", "default_dynamic": "随构建而定", "apply": "应用", diff --git a/configurator/usage.py b/configurator/usage.py index 641a3c7d60..0056bea834 100644 --- a/configurator/usage.py +++ b/configurator/usage.py @@ -18,6 +18,7 @@ _EMBEDDED_USAGE_DATA: Optional[List[Dict[str, Any]]] = globals().get("_EMBEDDED_USAGE_DATA") _EMBEDDED_USAGE_CN_DATA: Optional[Dict[str, Any]] = globals().get("_EMBEDDED_USAGE_CN_DATA") +ALWAYS_CONFIGURATOR_OPTIONS = {"BOX64_PROFILE"} def clean_choice_value(raw_key: str) -> str: @@ -111,10 +112,10 @@ def load_usage_catalog_from_data( ) -> UsageCatalog: options: List[EnvOption] = [] for entry in english_data: - if not entry.get("configurator", False): + name = entry["name"] + if not entry.get("configurator", False) and name not in ALWAYS_CONFIGURATOR_OPTIONS: continue - name = entry["name"] chinese_entry = chinese_data.get(name, {}) chinese_choices = chinese_entry.get("options", {}) choices = [] diff --git a/docs/gen/usage.json b/docs/gen/usage.json index 0b12600fb8..fb142a9b5d 100644 --- a/docs/gen/usage.json +++ b/docs/gen/usage.json @@ -1821,7 +1821,7 @@ "description": "Predefined sets of environment variables with compatibility or performance in mind.", "category": "Compatibility", "wine": true, - "configurator": false, + "configurator": true, "options": [ { "key": "safest",