feat: 完善一级关系编辑 UI 与视图体验
This commit is contained in:
+755
-17
@@ -8,11 +8,18 @@ from PySide6.QtCore import Qt, QThread, QTimer, Slot
|
||||
from PySide6.QtWidgets import (
|
||||
QApplication,
|
||||
QFileDialog,
|
||||
QFrame,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QLineEdit,
|
||||
QMessageBox,
|
||||
QPushButton,
|
||||
QSizePolicy,
|
||||
QTableWidgetItem,
|
||||
QTreeWidgetItem,
|
||||
QToolTip,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
from PySide6.QtGui import QColor
|
||||
|
||||
@@ -25,13 +32,43 @@ from .workers import EditWorker, LoadWorker, ScanWorker
|
||||
|
||||
|
||||
PROPERTY_VALUE_TOLERANCE = 1e-9
|
||||
PROPERTY_LABEL_COLUMN = 0
|
||||
PROPERTY_CURRENT_COLUMN = 1
|
||||
PROPERTY_SCOPE_COLUMN = 2
|
||||
PROPERTY_TARGET_COLUMN = 3
|
||||
PROPERTY_ACTION_COLUMN = 4
|
||||
PROPERTY_TABLE_MIN_COLUMN_WIDTHS = (54, 118, 48, 66, 74)
|
||||
PROPERTY_TABLE_PREFERRED_COLUMN_WIDTHS = (68, 168, 58, 92, 82)
|
||||
PROPERTY_COMMAND_ORDER = ("offset", "move", "scale", "rotate", "feature", "diagnostics")
|
||||
PROPERTY_COMMAND_LABELS = {
|
||||
"offset": "偏移",
|
||||
"move": "移动",
|
||||
"scale": "缩放",
|
||||
"rotate": "旋转",
|
||||
"feature": "特征",
|
||||
"diagnostics": "诊断",
|
||||
}
|
||||
PROPERTY_COMMAND_SUBTITLES = {
|
||||
"offset": "沿法线调面",
|
||||
"move": "移动中心/坐标",
|
||||
"scale": "改尺寸/半径",
|
||||
"rotate": "绕轴旋转",
|
||||
"feature": "孔槽/圆角",
|
||||
"diagnostics": "识别与关系",
|
||||
}
|
||||
PROPERTY_COMMAND_HELP = {
|
||||
"offset": "沿 Face 法线调整位置,适合平面推拉、偏移或切除深度类修改。",
|
||||
"move": "移动当前 Face、Edge、特征或 Solid 的中心/轴心,不直接修改尺寸。",
|
||||
"scale": "修改面积、宽度、高度、半径、直径、长度等尺寸参数。",
|
||||
"rotate": "设置旋转轴或旋转角度,适合 Part/Solid 的整体姿态调整。",
|
||||
"feature": "执行孔、槽、圆角、倒角、封堵等离散特征命令。",
|
||||
"diagnostics": "查看识别依据、一级拓扑关系和当前不支持修改的原因。",
|
||||
}
|
||||
|
||||
FEATURE_EDIT_SEMANTICS_KEYS = {
|
||||
"cad_modeling_form",
|
||||
"cad_recommended_operation",
|
||||
"edge_first_level_topology",
|
||||
"face_first_level_topology",
|
||||
"cylindrical_feature_first_level_topology",
|
||||
"slot_edit_semantics",
|
||||
@@ -44,6 +81,57 @@ FEATURE_EDIT_SEMANTICS_KEYS = {
|
||||
}
|
||||
|
||||
|
||||
def _property_table_column_widths(available_width: int) -> tuple[int, int, int, int, int]:
|
||||
"""Prefer current value and action visibility over a wide parameter-name column."""
|
||||
column_count = len(PROPERTY_TABLE_MIN_COLUMN_WIDTHS)
|
||||
available = max(int(available_width or 0), column_count * 44)
|
||||
minimum = PROPERTY_TABLE_MIN_COLUMN_WIDTHS
|
||||
preferred = PROPERTY_TABLE_PREFERRED_COLUMN_WIDTHS
|
||||
min_total = sum(minimum)
|
||||
preferred_total = sum(preferred)
|
||||
|
||||
if available >= preferred_total:
|
||||
widths = [int(value) for value in preferred]
|
||||
extra = available - preferred_total
|
||||
weights = (0.05, 0.62, 0.04, 0.19, 0.10)
|
||||
for index, weight in enumerate(weights):
|
||||
addition = int(extra * weight)
|
||||
widths[index] += addition
|
||||
widths[1] += available - sum(widths)
|
||||
return tuple(widths) # type: ignore[return-value]
|
||||
|
||||
if available >= min_total:
|
||||
scale = (available - min_total) / max(preferred_total - min_total, 1)
|
||||
widths = [
|
||||
int(round(min_width + (pref_width - min_width) * scale))
|
||||
for min_width, pref_width in zip(minimum, preferred)
|
||||
]
|
||||
widths[1] += available - sum(widths)
|
||||
return tuple(widths) # type: ignore[return-value]
|
||||
|
||||
floors = (48, 96, 44, 56, 70)
|
||||
widths = [int(value) for value in minimum]
|
||||
deficit = min_total - available
|
||||
for index in (0, 2, 3, 1, 4):
|
||||
if deficit <= 0:
|
||||
break
|
||||
reducible = max(0, widths[index] - floors[index])
|
||||
reduction = min(reducible, deficit)
|
||||
widths[index] -= reduction
|
||||
deficit -= reduction
|
||||
if deficit > 0:
|
||||
widths[1] = max(44, widths[1] - deficit)
|
||||
widths[1] += available - sum(widths)
|
||||
return tuple(max(44, width) for width in widths) # type: ignore[return-value]
|
||||
|
||||
|
||||
def _compact_property_card_text(text: str, limit: int = 220) -> str:
|
||||
compact = " ".join(str(text or "").replace("\r", "\n").split())
|
||||
if len(compact) <= limit:
|
||||
return compact
|
||||
return f"{compact[: max(0, limit - 1)].rstrip()}…"
|
||||
|
||||
|
||||
def _feature_dimension_keys(action_info: dict[str, object]) -> tuple[str, ...]:
|
||||
"""Return the independent, user-facing dimensions for a feature candidate."""
|
||||
surface = str(action_info.get("surface", "") or "")
|
||||
@@ -895,6 +983,13 @@ class WindowStateMixin:
|
||||
"查看当前选中对象的属性;可修改的行可以输入目标值。",
|
||||
"请先选择一个对象。",
|
||||
)
|
||||
if hasattr(self, "property_card_scroll"):
|
||||
self._set_control_state(
|
||||
self.property_card_scroll,
|
||||
has_selection and has_current_info,
|
||||
"查看当前选中对象的参数卡片;可修改的卡片可以输入目标值。",
|
||||
"请先选择一个对象。",
|
||||
)
|
||||
self._update_edit_action_states(has_model)
|
||||
|
||||
def _update_edit_action_states(self, has_model: bool) -> None:
|
||||
@@ -1321,14 +1416,475 @@ class WindowStateMixin:
|
||||
return
|
||||
self.property_editor_specs = []
|
||||
self.property_table_expanded = False
|
||||
self.property_editor_selected_row = None
|
||||
self.property_command_active_key = ""
|
||||
was_blocked = self.property_table.blockSignals(True)
|
||||
try:
|
||||
self.property_table.clearSpans()
|
||||
self.property_table.setRowCount(0)
|
||||
finally:
|
||||
self.property_table.blockSignals(was_blocked)
|
||||
self._clear_property_cards()
|
||||
self._clear_property_command_bar()
|
||||
self._resize_property_table_height()
|
||||
self._update_property_apply_state(False)
|
||||
|
||||
def _clear_property_cards(self) -> None:
|
||||
self.property_card_rows = {}
|
||||
layout = getattr(self, "property_card_layout", None)
|
||||
if layout is None:
|
||||
return
|
||||
while layout.count():
|
||||
item = layout.takeAt(0)
|
||||
widget = item.widget()
|
||||
if widget is not None:
|
||||
widget.hide()
|
||||
widget.setParent(None)
|
||||
widget.deleteLater()
|
||||
|
||||
def _clear_property_command_bar(self) -> None:
|
||||
self.property_command_buttons = {}
|
||||
if hasattr(self, "property_command_summary_label"):
|
||||
self.property_command_summary_label.setText("未选择可编辑对象")
|
||||
self.property_command_summary_label.setToolTip("")
|
||||
if hasattr(self, "property_command_help_label"):
|
||||
self.property_command_help_label.setText("")
|
||||
self.property_command_help_label.setToolTip("")
|
||||
self.property_command_help_label.setVisible(False)
|
||||
layout = getattr(self, "property_command_layout", None)
|
||||
if layout is not None:
|
||||
while layout.count():
|
||||
item = layout.takeAt(0)
|
||||
widget = item.widget()
|
||||
if widget is not None:
|
||||
widget.hide()
|
||||
widget.setParent(None)
|
||||
widget.deleteLater()
|
||||
if hasattr(self, "property_command_bar"):
|
||||
self.property_command_bar.setVisible(False)
|
||||
if hasattr(self, "property_command_summary_label"):
|
||||
self.property_command_summary_label.setVisible(False)
|
||||
|
||||
def _is_actionable_property_row(self, row: int, spec: dict[str, object]) -> bool:
|
||||
effective_spec = self._effective_property_spec(spec, row=row)
|
||||
return bool(effective_spec.get("editable") and effective_spec.get("enabled") and effective_spec.get("action"))
|
||||
|
||||
def _actionable_property_rows(self) -> list[tuple[int, dict[str, object]]]:
|
||||
specs = list(getattr(self, "property_editor_specs", []) or [])
|
||||
return [(row, spec) for row, spec in enumerate(specs) if self._is_actionable_property_row(row, spec)]
|
||||
|
||||
def _property_command_summary_text(self) -> tuple[str, str]:
|
||||
specs = list(getattr(self, "property_editor_specs", []) or [])
|
||||
topology_text = ""
|
||||
for spec in specs:
|
||||
key = str(spec.get("key", "") or "")
|
||||
current_text = str(spec.get("current_text", "") or "").strip()
|
||||
if key in {
|
||||
"face_first_level_topology",
|
||||
"edge_first_level_topology",
|
||||
"cylindrical_feature_first_level_topology",
|
||||
} and current_text:
|
||||
topology_text = _compact_property_card_text(current_text, 86)
|
||||
actionable = self._actionable_property_rows()
|
||||
labels = [str(spec.get("label", "") or "").strip() for _row, spec in actionable]
|
||||
labels = [label for label in labels if label]
|
||||
if labels:
|
||||
summary = f"可修改项 {len(labels)} 个:{'、'.join(labels[:4])}"
|
||||
if topology_text:
|
||||
summary = f"{summary} | {topology_text}"
|
||||
else:
|
||||
summary = topology_text or "当前对象没有稳定可修改项;请查看诊断信息。"
|
||||
tooltip_parts = [str(spec.get("current_text", "") or "") for spec in specs if bool(spec.get("pin_top"))]
|
||||
return summary, "\n\n".join(part for part in tooltip_parts if part)
|
||||
|
||||
def _rebuild_property_command_bar(self) -> None:
|
||||
layout = getattr(self, "property_command_layout", None)
|
||||
if layout is None:
|
||||
return
|
||||
self.property_command_buttons = {}
|
||||
while layout.count():
|
||||
item = layout.takeAt(0)
|
||||
widget = item.widget()
|
||||
if widget is not None:
|
||||
widget.deleteLater()
|
||||
self.property_command_active_key = ""
|
||||
summary, tooltip = self._property_command_summary_text()
|
||||
if hasattr(self, "property_command_summary_label"):
|
||||
self.property_command_summary_label.setText(summary)
|
||||
self.property_command_summary_label.setToolTip(tooltip or summary)
|
||||
self.property_command_summary_label.setVisible(True)
|
||||
if hasattr(self, "property_command_help_label"):
|
||||
self.property_command_help_label.setText("")
|
||||
self.property_command_help_label.setToolTip("")
|
||||
self.property_command_help_label.setVisible(False)
|
||||
if hasattr(self, "property_command_bar"):
|
||||
self.property_command_bar.setVisible(False)
|
||||
|
||||
def _select_property_command(self, command_key: str) -> None:
|
||||
if not command_key:
|
||||
return
|
||||
|
||||
def _visible_property_card_rows(self) -> list[tuple[int, dict[str, object]]]:
|
||||
specs = list(getattr(self, "property_editor_specs", []) or [])
|
||||
if not specs:
|
||||
return []
|
||||
if bool(getattr(self, "property_table_expanded", False)):
|
||||
return [(row, spec) for row, spec in enumerate(specs)]
|
||||
|
||||
visible = self._actionable_property_rows()
|
||||
if not visible:
|
||||
visible = [
|
||||
(row, spec)
|
||||
for row, spec in enumerate(specs)
|
||||
if bool(spec.get("editable") or spec.get("action") or spec.get("pin_top"))
|
||||
]
|
||||
if not visible:
|
||||
visible = [(row, spec) for row, spec in enumerate(specs)]
|
||||
selected_row = getattr(self, "property_editor_selected_row", None)
|
||||
if selected_row is not None:
|
||||
selected_items = [(row, spec) for row, spec in enumerate(specs) if row == selected_row]
|
||||
if selected_items:
|
||||
visible = selected_items + [(row, spec) for row, spec in visible if row != selected_row]
|
||||
collapsed_rows = max(int(getattr(self, "property_table_collapsed_rows", 6) or 6), 6)
|
||||
return visible[:collapsed_rows]
|
||||
|
||||
def _rebuild_property_cards(self) -> None:
|
||||
layout = getattr(self, "property_card_layout", None)
|
||||
if layout is None:
|
||||
return
|
||||
self._clear_property_cards()
|
||||
visible_rows = self._visible_property_card_rows()
|
||||
visible_row_ids = {row for row, _spec in visible_rows}
|
||||
selected_row = getattr(self, "property_editor_selected_row", None)
|
||||
if selected_row not in visible_row_ids:
|
||||
self.property_editor_selected_row = None
|
||||
for row, spec in visible_rows:
|
||||
card = self._build_property_card(row, spec)
|
||||
layout.addWidget(card)
|
||||
|
||||
def _select_property_card_row(self, row: int) -> None:
|
||||
specs = getattr(self, "property_editor_specs", [])
|
||||
if row < 0 or row >= len(specs):
|
||||
return
|
||||
if getattr(self, "property_editor_selected_row", None) == row:
|
||||
return
|
||||
self.property_editor_selected_row = row
|
||||
self._rebuild_property_cards()
|
||||
self._resize_property_table_height()
|
||||
self._update_property_apply_state()
|
||||
self._scroll_property_card_row_to_top(row)
|
||||
|
||||
def _toggle_property_card_row(self, row: int) -> None:
|
||||
QToolTip.hideText()
|
||||
specs = getattr(self, "property_editor_specs", [])
|
||||
if row < 0 or row >= len(specs):
|
||||
return
|
||||
if getattr(self, "property_editor_selected_row", None) == row:
|
||||
self.property_editor_selected_row = None
|
||||
self._rebuild_property_cards()
|
||||
self._resize_property_table_height()
|
||||
self._update_property_apply_state()
|
||||
self._scroll_property_cards_to_top()
|
||||
return
|
||||
self._select_property_card_row(row)
|
||||
|
||||
def _scroll_property_cards_to_top(self) -> None:
|
||||
scroll = getattr(self, "property_card_scroll", None)
|
||||
if scroll is None:
|
||||
return
|
||||
bar = scroll.verticalScrollBar()
|
||||
if bar is None:
|
||||
return
|
||||
bar.setValue(0)
|
||||
QTimer.singleShot(0, lambda target_bar=bar: target_bar.setValue(0))
|
||||
|
||||
def _scroll_property_card_row_to_top(self, row: int) -> None:
|
||||
scroll = getattr(self, "property_card_scroll", None)
|
||||
card = self._property_card_widgets(row).get("card")
|
||||
if scroll is None or not isinstance(card, QWidget):
|
||||
return
|
||||
bar = scroll.verticalScrollBar()
|
||||
if bar is None:
|
||||
return
|
||||
|
||||
def apply_scroll() -> None:
|
||||
bar.setValue(max(0, int(card.y()) - 4))
|
||||
|
||||
apply_scroll()
|
||||
QTimer.singleShot(0, apply_scroll)
|
||||
|
||||
def _property_card_widgets(self, row: int) -> dict[str, object]:
|
||||
rows = getattr(self, "property_card_rows", {})
|
||||
if isinstance(rows, dict):
|
||||
widgets = rows.get(row, {})
|
||||
if isinstance(widgets, dict):
|
||||
return widgets
|
||||
return {}
|
||||
|
||||
def _build_property_card(self, row: int, spec: dict[str, object]) -> QFrame:
|
||||
effective_spec = self._effective_property_spec(spec, row=row)
|
||||
editable = bool(effective_spec.get("editable") and effective_spec.get("enabled"))
|
||||
value_type = str(effective_spec.get("value_type", "number"))
|
||||
input_editable = editable and value_type != "command"
|
||||
span_value_columns = bool(effective_spec.get("span_value_columns")) and not editable
|
||||
selected = row == getattr(self, "property_editor_selected_row", None)
|
||||
|
||||
card = QFrame()
|
||||
card.setObjectName("propertyCard")
|
||||
card.setProperty("editable", bool(editable))
|
||||
card.setProperty("pinTop", bool(effective_spec.get("pin_top")))
|
||||
card.setProperty("selected", bool(selected))
|
||||
card.setProperty("compact", bool(not selected))
|
||||
card.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||||
card.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
card.mousePressEvent = lambda _event, target_row=row: self._toggle_property_card_row(target_row)
|
||||
card_layout = QVBoxLayout(card)
|
||||
card_layout.setContentsMargins(7 if not selected else 8, 2 if not selected else 7, 7 if not selected else 8, 2 if not selected else 7)
|
||||
card_layout.setSpacing(0 if not selected else 5)
|
||||
diagnostics_expanded = bool(getattr(self, "property_table_expanded", False))
|
||||
|
||||
header = QHBoxLayout()
|
||||
header.setContentsMargins(0, 0, 0, 0)
|
||||
header.setSpacing(8 if not selected else 4)
|
||||
title = QLabel(str(effective_spec.get("label", "")))
|
||||
title.setObjectName("propertyCardTitle")
|
||||
title.setMinimumWidth(0)
|
||||
title.setSizePolicy(QSizePolicy.Policy.Ignored, QSizePolicy.Policy.Fixed)
|
||||
if selected or span_value_columns:
|
||||
title.setToolTip(str(effective_spec.get("disabled_tip") or effective_spec.get("enabled_tip") or ""))
|
||||
header.addWidget(title, 1)
|
||||
compact_detail_below_header = False
|
||||
if not selected and not span_value_columns:
|
||||
current_text = str(effective_spec.get("current_text", ""))
|
||||
detail_parts = [current_text] if current_text else []
|
||||
scope_text = str(effective_spec.get("scope_text") or effective_spec.get("scope_label") or "").strip()
|
||||
target_text = self._property_target_text(row) or str(effective_spec.get("target_text", ""))
|
||||
if editable and value_type != "command":
|
||||
validation_error = (
|
||||
"" if not target_text.strip() else self._property_target_validation_error(effective_spec, target_text)
|
||||
)
|
||||
row_changed = self._property_target_changed(effective_spec, target_text)
|
||||
if validation_error:
|
||||
scope_text = "目标无效"
|
||||
elif row_changed:
|
||||
scope_text = f"目标 {target_text}"
|
||||
elif editable and value_type == "command":
|
||||
scope_text = str(effective_spec.get("button_text") or scope_text or "可执行").strip()
|
||||
if scope_text:
|
||||
detail_parts.append(scope_text)
|
||||
compact_detail_text = _compact_property_card_text(" · ".join(detail_parts), 150)
|
||||
compact_multiline = len(str(effective_spec.get("label", ""))) >= 14 or len(compact_detail_text) >= 30
|
||||
title.setWordWrap(compact_multiline)
|
||||
if compact_multiline:
|
||||
title.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred)
|
||||
card_layout.setSpacing(1)
|
||||
compact_detail = QLabel(_compact_property_card_text(" · ".join(detail_parts), 150 if compact_multiline else 96))
|
||||
compact_detail.setObjectName("propertyCardCompactValue")
|
||||
compact_detail.setAlignment(
|
||||
(Qt.AlignmentFlag.AlignLeft if compact_multiline else Qt.AlignmentFlag.AlignRight)
|
||||
| Qt.AlignmentFlag.AlignVCenter
|
||||
)
|
||||
compact_detail.setMinimumWidth(0)
|
||||
compact_detail.setSizePolicy(
|
||||
QSizePolicy.Policy.Expanding if compact_multiline else QSizePolicy.Policy.Fixed,
|
||||
QSizePolicy.Policy.Fixed,
|
||||
)
|
||||
if compact_multiline:
|
||||
compact_detail_below_header = True
|
||||
else:
|
||||
header.addWidget(compact_detail, 0)
|
||||
row_widgets_placeholder = compact_detail
|
||||
status_text = "可修改" if editable else str(effective_spec.get("status_text", ""))
|
||||
else:
|
||||
status_text = str(effective_spec.get("status_text", ""))
|
||||
status_label = QLabel(str(effective_spec.get("status_text", "")), card)
|
||||
status_label.setObjectName("propertyCardMetaLabel")
|
||||
status_label.setText(status_text)
|
||||
status_label.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
|
||||
header.addWidget(status_label, 0)
|
||||
card_layout.addLayout(header)
|
||||
status_label.setVisible(bool(status_text))
|
||||
if compact_detail_below_header:
|
||||
card_layout.addWidget(row_widgets_placeholder)
|
||||
|
||||
row_widgets: dict[str, object] = {
|
||||
"card": card,
|
||||
"status_label": status_label,
|
||||
}
|
||||
if not selected and not span_value_columns:
|
||||
row_widgets["current_value"] = row_widgets_placeholder
|
||||
self.property_card_rows[row] = row_widgets
|
||||
card.style().unpolish(card)
|
||||
card.style().polish(card)
|
||||
return card
|
||||
|
||||
if span_value_columns:
|
||||
current_text = str(effective_spec.get("current_text", ""))
|
||||
show_full_span_text = bool(selected or diagnostics_expanded)
|
||||
span_limit = 520 if show_full_span_text else 96
|
||||
value = QLabel(_compact_property_card_text(current_text, span_limit))
|
||||
value.setObjectName("propertyCardValue")
|
||||
value.setWordWrap(show_full_span_text)
|
||||
value.setToolTip(str(effective_spec.get("disabled_tip") or current_text))
|
||||
card_layout.addWidget(value)
|
||||
hint_text = _compact_property_card_text(str(effective_spec.get("disabled_tip") or ""), 150)
|
||||
if selected and hint_text and hint_text != value.text():
|
||||
hint = QLabel(hint_text)
|
||||
hint.setObjectName("propertyCardHint")
|
||||
hint.setWordWrap(True)
|
||||
card_layout.addWidget(hint)
|
||||
row_widgets["hint_label"] = hint
|
||||
self.property_card_rows[row] = row_widgets
|
||||
card.style().unpolish(card)
|
||||
card.style().polish(card)
|
||||
return card
|
||||
|
||||
current_row = QHBoxLayout()
|
||||
current_row.setContentsMargins(0, 0, 0, 0)
|
||||
current_label = QLabel("当前值")
|
||||
current_label.setObjectName("propertyCardMetaLabel")
|
||||
current_row.addWidget(current_label, 0)
|
||||
current_text = str(effective_spec.get("current_text", ""))
|
||||
current_value = QLabel(_compact_property_card_text(current_text, 220 if selected else 118))
|
||||
current_value.setObjectName("propertyCardValue")
|
||||
current_value.setWordWrap(bool(selected))
|
||||
current_value.setToolTip(current_text)
|
||||
current_row.addWidget(current_value, 1)
|
||||
card_layout.addLayout(current_row)
|
||||
|
||||
if not selected:
|
||||
compact_parts: list[str] = []
|
||||
scope_text = str(effective_spec.get("scope_text") or effective_spec.get("scope_label") or "").strip()
|
||||
if scope_text:
|
||||
compact_parts.append(scope_text)
|
||||
target_text = self._property_target_text(row) or str(effective_spec.get("target_text", ""))
|
||||
if editable and value_type != "command":
|
||||
validation_error = (
|
||||
"" if not target_text.strip() else self._property_target_validation_error(effective_spec, target_text)
|
||||
)
|
||||
row_changed = self._property_target_changed(effective_spec, target_text)
|
||||
if validation_error:
|
||||
compact_parts.append("目标无效")
|
||||
elif row_changed:
|
||||
compact_parts.append(f"目标 {target_text}")
|
||||
elif editable and value_type == "command":
|
||||
button_text = str(effective_spec.get("button_text") or "可执行").strip()
|
||||
if button_text:
|
||||
compact_parts.append(button_text)
|
||||
status_text = str(effective_spec.get("status_text") or "").strip()
|
||||
if status_text and not compact_parts:
|
||||
compact_parts.append(status_text)
|
||||
if compact_parts:
|
||||
compact_meta = QLabel(_compact_property_card_text(" · ".join(compact_parts), 118))
|
||||
compact_meta.setObjectName("propertyCardCompactMeta")
|
||||
compact_meta.setWordWrap(True)
|
||||
compact_meta.setToolTip(" · ".join(compact_parts))
|
||||
card_layout.addWidget(compact_meta)
|
||||
row_widgets["compact_meta"] = compact_meta
|
||||
self.property_card_rows[row] = row_widgets
|
||||
card.style().unpolish(card)
|
||||
card.style().polish(card)
|
||||
return card
|
||||
|
||||
if input_editable:
|
||||
target_row = QHBoxLayout()
|
||||
target_row.setContentsMargins(0, 0, 0, 0)
|
||||
target_label = QLabel("目标值")
|
||||
target_label.setObjectName("propertyCardMetaLabel")
|
||||
target_row.addWidget(target_label, 0)
|
||||
editor = QLineEdit(self._property_target_text(row) or str(effective_spec.get("target_text", "")))
|
||||
editor.setObjectName("propertyCardTargetEditor")
|
||||
editor.setToolTip(self._property_target_tooltip(effective_spec, editable=True))
|
||||
editor.setPlaceholderText("输入目标值")
|
||||
editor.setCursor(Qt.CursorShape.IBeamCursor)
|
||||
editor.textChanged.connect(lambda _text="", _row=row: self._on_property_card_target_changed(_row))
|
||||
editor.returnPressed.connect(lambda target_row=row: self.apply_property_row_edit(target_row))
|
||||
target_row.addWidget(editor, 1)
|
||||
card_layout.addLayout(target_row)
|
||||
row_widgets["target_editor"] = editor
|
||||
|
||||
modes = spec.get("scope_modes")
|
||||
if isinstance(modes, dict) and modes:
|
||||
scope_row = QHBoxLayout()
|
||||
scope_row.setContentsMargins(0, 0, 0, 0)
|
||||
scope_label = QLabel("建模意图")
|
||||
scope_label.setObjectName("propertyCardMetaLabel")
|
||||
scope_row.addWidget(scope_label, 0)
|
||||
combo = self._make_property_scope_combo(row, spec)
|
||||
scope_row.addWidget(combo, 1)
|
||||
card_layout.addLayout(scope_row)
|
||||
row_widgets["scope_combo"] = combo
|
||||
|
||||
hint_text = self._property_card_hint_text(effective_spec, editable=editable)
|
||||
hint = QLabel(hint_text, card)
|
||||
hint.setObjectName("propertyCardHint")
|
||||
hint.setWordWrap(True)
|
||||
hint.setToolTip(str(effective_spec.get("enabled_tip") or effective_spec.get("disabled_tip") or hint_text))
|
||||
card_layout.addWidget(hint)
|
||||
hint.setVisible(bool(hint_text))
|
||||
row_widgets["hint_label"] = hint
|
||||
|
||||
if editable and effective_spec.get("action"):
|
||||
button = QPushButton("未改动")
|
||||
button.setObjectName("propertyRowEditButton")
|
||||
button.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
button.setProperty("changed", False)
|
||||
button.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||||
button.clicked.connect(lambda _checked=False, target_row=row: self.apply_property_row_edit(target_row))
|
||||
card_layout.addWidget(button)
|
||||
status_label.setVisible(False)
|
||||
row_widgets["action_button"] = button
|
||||
|
||||
self.property_card_rows[row] = row_widgets
|
||||
card.style().unpolish(card)
|
||||
card.style().polish(card)
|
||||
return card
|
||||
|
||||
def _property_card_hint_text(self, spec: dict[str, object], *, editable: bool) -> str:
|
||||
parts: list[str] = []
|
||||
if editable:
|
||||
scope_label = str(spec.get("scope_label") or "").strip()
|
||||
if scope_label:
|
||||
parts.append(f"建模意图:{scope_label}")
|
||||
tip = str(spec.get("enabled_tip") or "").strip()
|
||||
if tip:
|
||||
parts.append(tip)
|
||||
range_hint = self._property_range_hint(spec)
|
||||
if range_hint:
|
||||
parts.append(range_hint)
|
||||
else:
|
||||
tip = str(spec.get("disabled_tip") or spec.get("enabled_tip") or "").strip()
|
||||
status = str(spec.get("status_text") or "").strip()
|
||||
if tip:
|
||||
parts.append(tip)
|
||||
elif status:
|
||||
parts.append(status)
|
||||
return _compact_property_card_text(" ".join(part for part in parts if part), 150)
|
||||
|
||||
def _make_property_scope_combo(self, row: int, spec: dict[str, object]) -> NoWheelComboBox:
|
||||
combo = NoWheelComboBox()
|
||||
combo.setObjectName("propertyScopeCombo")
|
||||
modes = spec.get("scope_modes")
|
||||
default_scope = self._property_scope_value(row, spec) or self._property_scope_default(spec)
|
||||
selected_index = 0
|
||||
if isinstance(modes, dict):
|
||||
for index, (scope_key, mode) in enumerate(modes.items()):
|
||||
if not isinstance(mode, dict):
|
||||
continue
|
||||
label = str(mode.get("label") or scope_key)
|
||||
if not bool(mode.get("enabled", True)):
|
||||
label = f"{label}(不可用)"
|
||||
combo.addItem(label, scope_key)
|
||||
tip = str(mode.get("enabled_tip") or mode.get("disabled_tip") or "").strip()
|
||||
if tip:
|
||||
combo.setItemData(index, tip, Qt.ItemDataRole.ToolTipRole)
|
||||
if scope_key == default_scope:
|
||||
selected_index = index
|
||||
combo.setCurrentIndex(selected_index)
|
||||
combo.setToolTip(self._property_scope_tooltip(spec, default_scope))
|
||||
combo.currentIndexChanged.connect(lambda _index=0, target_row=row: self._on_property_scope_changed(target_row))
|
||||
return combo
|
||||
|
||||
def _refresh_property_editor(self) -> None:
|
||||
if not hasattr(self, "property_table"):
|
||||
return
|
||||
@@ -1352,9 +1908,12 @@ class WindowStateMixin:
|
||||
specs = self._sort_property_specs_for_display(self._property_editor_specs(info, action_info))
|
||||
self.property_editor_specs = specs
|
||||
self.property_table_expanded = False
|
||||
self.property_editor_selected_row = None
|
||||
self.property_command_active_key = ""
|
||||
self.property_editor_updating = True
|
||||
was_blocked = self.property_table.blockSignals(True)
|
||||
try:
|
||||
self.property_table.clearSpans()
|
||||
self.property_table.setRowCount(len(specs))
|
||||
for row, spec in enumerate(specs):
|
||||
effective_spec = self._effective_property_spec(spec)
|
||||
@@ -1363,12 +1922,16 @@ class WindowStateMixin:
|
||||
input_editable = editable and value_type != "command"
|
||||
label_item = self._property_table_item(str(effective_spec.get("label", "")), editable=False)
|
||||
current_item = self._property_table_item(str(effective_spec.get("current_text", "")), editable=False)
|
||||
scope_item = self._property_table_item(str(effective_spec.get("scope_text", "")), editable=False)
|
||||
target_item = self._property_table_item(
|
||||
"" if input_editable else str(effective_spec.get("target_text", "")),
|
||||
span_value_columns = bool(effective_spec.get("span_value_columns")) and not editable
|
||||
scope_item = self._property_table_item(
|
||||
"" if span_value_columns else str(effective_spec.get("scope_text", "")),
|
||||
editable=False,
|
||||
)
|
||||
action_text = "" if editable else str(effective_spec.get("status_text", ""))
|
||||
target_item = self._property_table_item(
|
||||
"" if input_editable or span_value_columns else str(effective_spec.get("target_text", "")),
|
||||
editable=False,
|
||||
)
|
||||
action_text = "" if editable or span_value_columns else str(effective_spec.get("status_text", ""))
|
||||
action_item = self._property_table_item(action_text, editable=False)
|
||||
target_item.setToolTip(self._property_target_tooltip(effective_spec, editable=editable))
|
||||
row_items = (label_item, current_item, scope_item, target_item, action_item)
|
||||
@@ -1376,6 +1939,9 @@ class WindowStateMixin:
|
||||
for column, item in enumerate(row_items):
|
||||
item.setToolTip(item.toolTip() or item.text())
|
||||
self.property_table.setItem(row, column, item)
|
||||
if span_value_columns:
|
||||
current_item.setToolTip(str(effective_spec.get("disabled_tip") or current_item.text()))
|
||||
self.property_table.setSpan(row, PROPERTY_CURRENT_COLUMN, 1, 4)
|
||||
self.property_table.setRowHeight(row, 28 if editable else 24)
|
||||
if spec.get("scope_modes"):
|
||||
self._set_property_scope_editor(row, spec)
|
||||
@@ -1390,10 +1956,13 @@ class WindowStateMixin:
|
||||
else:
|
||||
self.property_table.removeCellWidget(row, PROPERTY_TARGET_COLUMN)
|
||||
self.property_table.removeCellWidget(row, PROPERTY_ACTION_COLUMN)
|
||||
self._rebuild_property_command_bar()
|
||||
self._rebuild_property_cards()
|
||||
self._resize_property_table_height()
|
||||
finally:
|
||||
self.property_table.blockSignals(was_blocked)
|
||||
self.property_editor_updating = False
|
||||
self._resize_property_table_columns()
|
||||
self._update_property_apply_state()
|
||||
|
||||
def _sort_property_specs_for_display(self, specs: list[dict[str, object]]) -> list[dict[str, object]]:
|
||||
@@ -1479,6 +2048,11 @@ class WindowStateMixin:
|
||||
modes = spec.get("scope_modes")
|
||||
if not isinstance(modes, dict) or not modes:
|
||||
return ""
|
||||
card_widget = self._property_card_widgets(row).get("scope_combo")
|
||||
if isinstance(card_widget, NoWheelComboBox):
|
||||
value = card_widget.currentData()
|
||||
if value in modes:
|
||||
return str(value)
|
||||
widget = self.property_table.cellWidget(row, PROPERTY_SCOPE_COLUMN) if hasattr(self, "property_table") else None
|
||||
if isinstance(widget, NoWheelComboBox):
|
||||
value = widget.currentData()
|
||||
@@ -1578,8 +2152,30 @@ class WindowStateMixin:
|
||||
effective_spec = self._effective_property_spec(specs[row], row=row)
|
||||
editable = bool(effective_spec.get("editable") and effective_spec.get("enabled"))
|
||||
input_editable = editable and str(effective_spec.get("value_type", "number")) != "command"
|
||||
card_widgets = self._property_card_widgets(row)
|
||||
card_scope_widget = card_widgets.get("scope_combo")
|
||||
if isinstance(card_scope_widget, NoWheelComboBox):
|
||||
selected_scope = card_scope_widget.currentData()
|
||||
card_scope_widget.setToolTip(self._property_scope_tooltip(specs[row], self._property_scope_value(row, specs[row])))
|
||||
card_target_widget = card_widgets.get("target_editor")
|
||||
if isinstance(card_target_widget, QLineEdit):
|
||||
card_target_widget.setEnabled(input_editable)
|
||||
card_target_widget.setToolTip(self._property_target_tooltip(effective_spec, editable=editable))
|
||||
hint_label = card_widgets.get("hint_label")
|
||||
if isinstance(hint_label, QLabel):
|
||||
hint_text = self._property_card_hint_text(effective_spec, editable=editable)
|
||||
hint_label.setText(hint_text)
|
||||
hint_label.setVisible(bool(hint_text))
|
||||
scope_widget = self.property_table.cellWidget(row, PROPERTY_SCOPE_COLUMN)
|
||||
if isinstance(scope_widget, NoWheelComboBox):
|
||||
if "selected_scope" in locals():
|
||||
target_index = scope_widget.findData(selected_scope)
|
||||
if target_index >= 0 and target_index != scope_widget.currentIndex():
|
||||
was_blocked = scope_widget.blockSignals(True)
|
||||
try:
|
||||
scope_widget.setCurrentIndex(target_index)
|
||||
finally:
|
||||
scope_widget.blockSignals(was_blocked)
|
||||
scope_widget.setToolTip(self._property_scope_tooltip(specs[row], self._property_scope_value(row, specs[row])))
|
||||
target_widget = self.property_table.cellWidget(row, PROPERTY_TARGET_COLUMN)
|
||||
if isinstance(target_widget, QLineEdit):
|
||||
@@ -1593,6 +2189,19 @@ class WindowStateMixin:
|
||||
action_item.setText(str(effective_spec.get("status_text", "")))
|
||||
self._update_property_apply_state()
|
||||
|
||||
def _on_property_card_target_changed(self, row: int) -> None:
|
||||
card_widget = self._property_card_widgets(row).get("target_editor")
|
||||
table_widget = self.property_table.cellWidget(row, PROPERTY_TARGET_COLUMN) if hasattr(self, "property_table") else None
|
||||
if isinstance(card_widget, QLineEdit) and isinstance(table_widget, QLineEdit):
|
||||
text = card_widget.text()
|
||||
if table_widget.text() != text:
|
||||
was_blocked = table_widget.blockSignals(True)
|
||||
try:
|
||||
table_widget.setText(text)
|
||||
finally:
|
||||
table_widget.blockSignals(was_blocked)
|
||||
self._update_property_apply_state()
|
||||
|
||||
def _property_target_tooltip(self, spec: dict[str, object], *, editable: bool) -> str:
|
||||
tip_key = "enabled_tip" if editable else "disabled_tip"
|
||||
parts = [str(spec.get(tip_key, "")).strip()]
|
||||
@@ -1625,7 +2234,7 @@ class WindowStateMixin:
|
||||
def _resize_property_table_height(self) -> None:
|
||||
if not hasattr(self, "property_table"):
|
||||
return
|
||||
row_count = self.property_table.rowCount()
|
||||
row_count = len(getattr(self, "property_editor_specs", []) or [])
|
||||
collapsed_rows = int(getattr(self, "property_table_collapsed_rows", 6) or 6)
|
||||
expanded = bool(getattr(self, "property_table_expanded", False))
|
||||
visible_rows = row_count if expanded else min(row_count, collapsed_rows)
|
||||
@@ -1642,19 +2251,54 @@ class WindowStateMixin:
|
||||
if expanded or not has_hidden_rows
|
||||
else Qt.ScrollBarPolicy.ScrollBarAsNeeded
|
||||
)
|
||||
if hasattr(self, "property_card_scroll"):
|
||||
card_visible_rows = self._visible_property_card_rows()
|
||||
card_heights: list[int] = []
|
||||
for row, _spec in card_visible_rows:
|
||||
card = self._property_card_widgets(row).get("card")
|
||||
if isinstance(card, QFrame):
|
||||
card_heights.append(max(int(card.sizeHint().height()), 40))
|
||||
if card_heights:
|
||||
content_height = sum(card_heights) + max(0, len(card_heights) - 1) * 4 + 10
|
||||
else:
|
||||
content_height = 96
|
||||
card_height = content_height
|
||||
card_height = min(card_height, 560 if expanded else 360)
|
||||
card_height = max(96, card_height)
|
||||
if hasattr(self, "property_card_container"):
|
||||
self.property_card_container.setMinimumHeight(content_height)
|
||||
self.property_card_scroll.setMinimumHeight(card_height)
|
||||
self.property_card_scroll.setMaximumHeight(card_height)
|
||||
self.property_card_scroll.setVerticalScrollBarPolicy(
|
||||
Qt.ScrollBarPolicy.ScrollBarAsNeeded if content_height > card_height else Qt.ScrollBarPolicy.ScrollBarAlwaysOff
|
||||
)
|
||||
if hasattr(self, "property_expand_button"):
|
||||
self.property_expand_button.setVisible(has_hidden_rows)
|
||||
if expanded:
|
||||
self.property_expand_button.setText(f"收起到前 {collapsed_rows} 项")
|
||||
self.property_expand_button.setToolTip("收起参数列表,只保留最常用的前几项。")
|
||||
self.property_expand_button.setText(f"收起诊断信息,保留前 {collapsed_rows} 项")
|
||||
self.property_expand_button.setToolTip("收起只读诊断信息,保留可编辑参数和关键说明。")
|
||||
else:
|
||||
self.property_expand_button.setText(f"展开全部参数 ({row_count} 项)")
|
||||
self.property_expand_button.setToolTip("展开完整参数列表;参数化建模按钮会继续留在下方。")
|
||||
self.property_expand_button.setText(f"更多诊断信息 ({row_count} 项)")
|
||||
self.property_expand_button.setToolTip("展开完整参数、识别依据和一级关系诊断;参数化建模按钮会继续留在下方。")
|
||||
self._resize_property_table_columns()
|
||||
|
||||
def _resize_property_table_columns(self) -> None:
|
||||
if not hasattr(self, "property_table"):
|
||||
return
|
||||
table = self.property_table
|
||||
viewport_width = int(table.viewport().width()) if table.viewport() is not None else int(table.width())
|
||||
if viewport_width <= 0:
|
||||
return
|
||||
widths = _property_table_column_widths(viewport_width)
|
||||
for column, width in enumerate(widths):
|
||||
table.setColumnWidth(column, width)
|
||||
|
||||
def toggle_property_table_expanded(self) -> None:
|
||||
if not hasattr(self, "property_table"):
|
||||
return
|
||||
self.property_table_expanded = not bool(getattr(self, "property_table_expanded", False))
|
||||
self._rebuild_property_command_bar()
|
||||
self._rebuild_property_cards()
|
||||
self._resize_property_table_height()
|
||||
|
||||
def _property_table_item(self, text: str, *, editable: bool) -> QTableWidgetItem:
|
||||
@@ -1764,9 +2408,34 @@ class WindowStateMixin:
|
||||
action_info: dict[str, object],
|
||||
) -> list[dict[str, object]]:
|
||||
root_rows = self._feature_property_specs(root_specs, action_info)
|
||||
context_rows: list[dict[str, object]] = []
|
||||
context_note = str(action_info.get("feature_context_note") or "").strip()
|
||||
if context_note:
|
||||
level_text = str(action_info.get("feature_detection_level") or "").strip()
|
||||
associated_count = _int_or_none(action_info.get("associated_feature_count"))
|
||||
count_text = "" if associated_count is None else f";关联特征 {associated_count} 项"
|
||||
context_rows.append(
|
||||
{
|
||||
"key": "feature_context_note",
|
||||
"label": "关联探测",
|
||||
"current_text": context_note,
|
||||
"current_raw": context_note,
|
||||
"target_text": "",
|
||||
"editable": False,
|
||||
"enabled": False,
|
||||
"status_text": "说明",
|
||||
"scope_text": f"{level_text}{count_text}".strip(";"),
|
||||
"disabled_tip": (
|
||||
"这里说明当前特征探测级别和已经找到的一级/二级关联特征;"
|
||||
"有关联尺寸时,会作为带“关联 Face”的可修改行显示在同一张参数表里。"
|
||||
),
|
||||
"pin_top": True,
|
||||
"span_value_columns": True,
|
||||
}
|
||||
)
|
||||
associated = action_info.get("associated_feature_infos")
|
||||
if not isinstance(associated, (list, tuple)) or not associated:
|
||||
return root_rows
|
||||
return context_rows + root_rows
|
||||
|
||||
root_dimensions = [dict(spec) for spec in root_rows if spec.get("parameter_role") == "dimension"]
|
||||
root_explanations = [dict(spec) for spec in root_rows if spec.get("parameter_role") != "dimension"]
|
||||
@@ -1789,7 +2458,7 @@ class WindowStateMixin:
|
||||
related["association_index"] = index
|
||||
related_rows.append(related)
|
||||
|
||||
return root_dimensions + related_rows + root_explanations
|
||||
return context_rows + root_dimensions + related_rows + root_explanations
|
||||
|
||||
def _ordered_property_info_items(self, info: dict[str, object]) -> list[tuple[str, object]]:
|
||||
items = self._ordered_info_items(info)
|
||||
@@ -2266,6 +2935,7 @@ class WindowStateMixin:
|
||||
"status_text": "说明",
|
||||
"disabled_tip": tip or text,
|
||||
"pin_top": pin_top,
|
||||
"span_value_columns": True,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -2624,6 +3294,36 @@ class WindowStateMixin:
|
||||
)
|
||||
|
||||
if has_edge:
|
||||
topology_depth = _int_or_none(action_info.get("topology_relation_depth"))
|
||||
if topology_depth == 1:
|
||||
selected_edge_count = _int_or_none(action_info.get("selected_edge_count")) or 1
|
||||
vertex_count = _int_or_none(action_info.get("first_level_vertex_count")) or 0
|
||||
adjacent_edge_count = _int_or_none(action_info.get("first_level_adjacent_edge_count")) or 0
|
||||
adjacent_face_count = _int_or_none(action_info.get("first_level_adjacent_face_count")) or 0
|
||||
included_edge_count = _int_or_none(action_info.get("first_level_edge_count")) or 0
|
||||
topology_note = str(action_info.get("first_level_topology_note") or "").strip()
|
||||
fact_summary = str(action_info.get("first_level_fact_summary") or "").strip()
|
||||
ignored_note = str(action_info.get("topology_ignored_relation_note") or "").strip()
|
||||
topology_tip = "\n".join(
|
||||
item
|
||||
for item in (
|
||||
topology_note,
|
||||
fact_summary,
|
||||
ignored_note,
|
||||
"当前阶段只把被选 Edge、端点 Vertex、共享端点相邻 Edge 和直接包含该 Edge 的 Face 作为一级关系;不会自动递归传播到二级、三级关系。",
|
||||
)
|
||||
if item
|
||||
)
|
||||
add_readonly_spec(
|
||||
key="edge_first_level_topology",
|
||||
label="一级关系",
|
||||
text=(
|
||||
f"当前 Edge {selected_edge_count} 条;端点 Vertex {vertex_count} 个;"
|
||||
f"共享端点相邻 Edge {adjacent_edge_count} 条;直接相邻 Face {adjacent_face_count} 个;"
|
||||
f"一级范围 Edge {included_edge_count} 条。"
|
||||
),
|
||||
tip=topology_tip,
|
||||
)
|
||||
add_readonly_spec(
|
||||
key="edge_edit_semantics",
|
||||
label="建模意图",
|
||||
@@ -4702,18 +5402,46 @@ class WindowStateMixin:
|
||||
)
|
||||
for row, spec in enumerate(getattr(self, "property_editor_specs", [])):
|
||||
effective_spec = self._effective_property_spec(spec, row=row)
|
||||
target_widget = self.property_table.cellWidget(row, PROPERTY_TARGET_COLUMN)
|
||||
card_widgets = self._property_card_widgets(row)
|
||||
target_widget = card_widgets.get("target_editor")
|
||||
if not isinstance(target_widget, QLineEdit):
|
||||
target_widget = self.property_table.cellWidget(row, PROPERTY_TARGET_COLUMN)
|
||||
if isinstance(target_widget, QLineEdit):
|
||||
editable = bool(effective_spec.get("editable") and effective_spec.get("enabled"))
|
||||
input_editable = editable and str(effective_spec.get("value_type", "number")) != "command"
|
||||
target_widget.setEnabled(input_editable)
|
||||
target_widget.setToolTip(self._property_target_tooltip(effective_spec, editable=editable))
|
||||
widget = self.property_table.cellWidget(row, PROPERTY_ACTION_COLUMN)
|
||||
hint_label = card_widgets.get("hint_label")
|
||||
if isinstance(hint_label, QLabel):
|
||||
hint_text = self._property_card_hint_text(
|
||||
effective_spec,
|
||||
editable=bool(effective_spec.get("editable") and effective_spec.get("enabled")),
|
||||
)
|
||||
hint_label.setText(hint_text)
|
||||
hint_label.setVisible(bool(hint_text))
|
||||
card = card_widgets.get("card")
|
||||
card_changed = False
|
||||
card_invalid = False
|
||||
if str(effective_spec.get("value_type", "number")) != "command":
|
||||
text = self._property_target_text(row)
|
||||
card_changed = self._property_target_changed(effective_spec, text)
|
||||
card_invalid = bool(text.strip() and self._property_target_validation_error(effective_spec, text))
|
||||
if isinstance(card, QFrame):
|
||||
card_state = (bool(card_changed and not card_invalid), bool(card_invalid))
|
||||
if getattr(card, "_geom_param_card_state", None) != card_state:
|
||||
card.setProperty("changed", card_state[0])
|
||||
card.setProperty("invalid", card_state[1])
|
||||
card.style().unpolish(card)
|
||||
card.style().polish(card)
|
||||
setattr(card, "_geom_param_card_state", card_state)
|
||||
widget = card_widgets.get("action_button")
|
||||
if not isinstance(widget, QPushButton):
|
||||
widget = self.property_table.cellWidget(row, PROPERTY_ACTION_COLUMN)
|
||||
if isinstance(widget, QPushButton):
|
||||
validation_error = ""
|
||||
if str(effective_spec.get("value_type", "number")) == "command":
|
||||
row_changed = True
|
||||
widget.setText(str(effective_spec.get("button_text") or "执行"))
|
||||
widget.setText(str(effective_spec.get("button_text") or "执行当前命令"))
|
||||
enabled = bool(has_model and effective_spec.get("enabled") and effective_spec.get("action"))
|
||||
tooltip = f"执行“{effective_spec.get('label', '当前操作')}”。"
|
||||
else:
|
||||
@@ -4722,11 +5450,11 @@ class WindowStateMixin:
|
||||
empty_target = not bool(text.strip())
|
||||
validation_error = "" if empty_target else self._property_target_validation_error(effective_spec, text)
|
||||
if empty_target:
|
||||
widget.setText("未输入")
|
||||
widget.setText("输入目标值后应用")
|
||||
elif validation_error:
|
||||
widget.setText("无效")
|
||||
widget.setText("目标无效")
|
||||
else:
|
||||
widget.setText("应用" if row_changed else "未改动")
|
||||
widget.setText("应用当前命令" if row_changed else "修改目标值后应用")
|
||||
enabled = bool(
|
||||
has_model
|
||||
and effective_spec.get("enabled")
|
||||
@@ -4753,6 +5481,10 @@ class WindowStateMixin:
|
||||
tooltip = f"{tooltip}\n\n{range_hint}"
|
||||
widget.setProperty("changed", bool(row_changed and not validation_error))
|
||||
widget.setProperty("invalid", bool(validation_error))
|
||||
card = card_widgets.get("card")
|
||||
if isinstance(card, QFrame):
|
||||
card.setProperty("changed", bool(row_changed and not validation_error))
|
||||
card.setProperty("invalid", bool(validation_error))
|
||||
button_state = (
|
||||
widget.text(),
|
||||
bool(enabled),
|
||||
@@ -4766,6 +5498,9 @@ class WindowStateMixin:
|
||||
widget.style().unpolish(widget)
|
||||
widget.style().polish(widget)
|
||||
widget.setEnabled(enabled)
|
||||
if isinstance(card, QFrame):
|
||||
card.style().unpolish(card)
|
||||
card.style().polish(card)
|
||||
setattr(widget, "_geom_param_button_state", button_state)
|
||||
if not hasattr(self, "apply_property_button"):
|
||||
return
|
||||
@@ -4807,6 +5542,9 @@ class WindowStateMixin:
|
||||
def _property_target_text(self, row: int) -> str:
|
||||
if not hasattr(self, "property_table"):
|
||||
return ""
|
||||
card_widget = self._property_card_widgets(row).get("target_editor")
|
||||
if isinstance(card_widget, QLineEdit):
|
||||
return card_widget.text().strip()
|
||||
widget = self.property_table.cellWidget(row, PROPERTY_TARGET_COLUMN)
|
||||
if isinstance(widget, QLineEdit):
|
||||
return widget.text().strip()
|
||||
|
||||
Reference in New Issue
Block a user