feat(create): manifest viewer — read-only MDI widget for silo/manifest.json (#38)
All checks were successful
Build and Test / build (pull_request) Successful in 29m13s
All checks were successful
Build and Test / build (pull_request) Successful in 29m13s
Add SiloManifestViewer widget that opens in an MDI subwindow when the user double-clicks the Manifest node in the Silo tree. Displays all manifest.json fields in a read-only QFormLayout with copy buttons for Part UUID and Silo Instance. New files: - silo_viewers.py: SiloManifestViewer widget + create_viewer_widget() factory with _VIEWER_REGISTRY for future viewer classes Modified files: - silo_viewproviders.py: doubleClicked() wired to open MDI subwindow with deduplication via widget objectName() - CMakeLists.txt: add silo_viewers.py to install list Closes #38
This commit is contained in:
@@ -25,6 +25,7 @@ install(
|
||||
silo_document.py
|
||||
silo_objects.py
|
||||
silo_tree.py
|
||||
silo_viewers.py
|
||||
silo_viewproviders.py
|
||||
update_checker.py
|
||||
${CMAKE_CURRENT_BINARY_DIR}/version.py
|
||||
|
||||
151
src/Mod/Create/silo_viewers.py
Normal file
151
src/Mod/Create/silo_viewers.py
Normal file
@@ -0,0 +1,151 @@
|
||||
"""
|
||||
silo_viewers.py — Read-only MDI viewer widgets for Silo tree leaf nodes.
|
||||
|
||||
Each viewer is a plain QWidget suitable for embedding in an MDI subwindow.
|
||||
The ``create_viewer_widget`` factory routes a SiloViewerObject to the
|
||||
appropriate viewer class based on its SiloPath property.
|
||||
"""
|
||||
|
||||
import json
|
||||
|
||||
import FreeCAD
|
||||
from PySide import QtCore, QtWidgets
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Manifest Viewer
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_MANIFEST_FIELDS = [
|
||||
("Part UUID", "part_uuid", True),
|
||||
("Silo Instance", "silo_instance", True),
|
||||
("Revision Hash", "revision_hash", False),
|
||||
("KC Version", "kc_version", False),
|
||||
("Created", "created_at", False),
|
||||
("Modified", "modified_at", False),
|
||||
("Created By", "created_by", False),
|
||||
]
|
||||
|
||||
|
||||
class SiloManifestViewer(QtWidgets.QWidget):
|
||||
"""Read-only form displaying ``silo/manifest.json`` fields."""
|
||||
|
||||
WINDOW_TITLE = "Silo \u2014 Manifest"
|
||||
|
||||
def __init__(self, obj, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setObjectName(f"SiloViewer_{obj.Name}")
|
||||
self._build_ui(obj.RawContent)
|
||||
|
||||
# -- layout --------------------------------------------------------------
|
||||
|
||||
def _build_ui(self, raw_content):
|
||||
try:
|
||||
data = json.loads(raw_content)
|
||||
except Exception:
|
||||
data = {}
|
||||
|
||||
outer = QtWidgets.QVBoxLayout(self)
|
||||
outer.setContentsMargins(16, 16, 16, 16)
|
||||
outer.setSpacing(12)
|
||||
|
||||
title = QtWidgets.QLabel("Silo Manifest")
|
||||
font = title.font()
|
||||
font.setPointSize(font.pointSize() + 2)
|
||||
font.setBold(True)
|
||||
title.setFont(font)
|
||||
outer.addWidget(title)
|
||||
|
||||
line = QtWidgets.QFrame()
|
||||
line.setFrameShape(QtWidgets.QFrame.HLine)
|
||||
line.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||||
outer.addWidget(line)
|
||||
|
||||
form = QtWidgets.QFormLayout()
|
||||
form.setLabelAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
form.setHorizontalSpacing(16)
|
||||
form.setVerticalSpacing(8)
|
||||
outer.addLayout(form)
|
||||
|
||||
for label_text, key, copyable in _MANIFEST_FIELDS:
|
||||
raw_val = str(data.get(key, "") or "")
|
||||
display_val = _format_value(key, raw_val)
|
||||
_add_row(form, label_text, display_val, raw_val, copyable)
|
||||
|
||||
outer.addStretch()
|
||||
|
||||
# -- no state to serialize -----------------------------------------------
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _format_value(key, value):
|
||||
"""Format a manifest value for display."""
|
||||
if not value:
|
||||
return "\u2014" # em-dash
|
||||
if key in ("created_at", "modified_at"):
|
||||
try:
|
||||
s = str(value).replace("Z", "").replace("T", " ")
|
||||
if len(s) >= 16:
|
||||
s = s[:16]
|
||||
return s + " UTC"
|
||||
except Exception:
|
||||
pass
|
||||
return str(value)
|
||||
|
||||
|
||||
def _add_row(form, label_text, display_val, raw_val, copyable):
|
||||
"""Add a single row to the form layout."""
|
||||
value_label = QtWidgets.QLabel(display_val)
|
||||
value_label.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse)
|
||||
|
||||
if copyable and raw_val:
|
||||
row = QtWidgets.QWidget()
|
||||
row_layout = QtWidgets.QHBoxLayout(row)
|
||||
row_layout.setContentsMargins(0, 0, 0, 0)
|
||||
row_layout.setSpacing(4)
|
||||
row_layout.addWidget(value_label)
|
||||
|
||||
btn = QtWidgets.QToolButton()
|
||||
btn.setText("\u29c9") # ⧉
|
||||
btn.setFixedWidth(24)
|
||||
btn.setToolTip(f"Copy {label_text}")
|
||||
btn.clicked.connect(
|
||||
lambda checked=False, v=raw_val: QtWidgets.QApplication.clipboard().setText(
|
||||
v
|
||||
)
|
||||
)
|
||||
row_layout.addWidget(btn)
|
||||
row_layout.addStretch()
|
||||
form.addRow(label_text + ":", row)
|
||||
else:
|
||||
form.addRow(label_text + ":", value_label)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Viewer factory
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_VIEWER_REGISTRY = {
|
||||
"silo/manifest.json": SiloManifestViewer,
|
||||
}
|
||||
|
||||
|
||||
def create_viewer_widget(obj):
|
||||
"""Route a Silo tree node to the appropriate viewer widget.
|
||||
|
||||
Returns a QWidget ready for MDI embedding, or None if no viewer
|
||||
is registered for this node's SiloPath.
|
||||
"""
|
||||
cls = _VIEWER_REGISTRY.get(obj.SiloPath)
|
||||
if cls is None:
|
||||
return None
|
||||
try:
|
||||
return cls(obj)
|
||||
except Exception as exc:
|
||||
FreeCAD.Console.PrintWarning(
|
||||
f"silo_viewers: failed to create viewer for {obj.SiloPath!r}: {exc}\n"
|
||||
)
|
||||
return None
|
||||
@@ -63,8 +63,44 @@ class SiloViewerViewProvider:
|
||||
return ""
|
||||
|
||||
def doubleClicked(self, vobj):
|
||||
"""Phase 1: no action on double-click."""
|
||||
return False
|
||||
"""Open a read-only MDI viewer for this silo node."""
|
||||
try:
|
||||
import FreeCADGui
|
||||
from PySide import QtWidgets
|
||||
from silo_viewers import create_viewer_widget
|
||||
|
||||
obj = vobj.Object
|
||||
widget = create_viewer_widget(obj)
|
||||
if widget is None:
|
||||
return False
|
||||
|
||||
mw = FreeCADGui.getMainWindow()
|
||||
mdi = mw.findChild(QtWidgets.QMdiArea)
|
||||
if mdi is None:
|
||||
return False
|
||||
|
||||
# Reuse existing subwindow if already open for this object
|
||||
target_name = widget.objectName()
|
||||
for sw in mdi.subWindowList():
|
||||
if sw.widget() and sw.widget().objectName() == target_name:
|
||||
widget.deleteLater()
|
||||
mdi.setActiveSubWindow(sw)
|
||||
sw.show()
|
||||
return True
|
||||
|
||||
sw = mdi.addSubWindow(widget)
|
||||
sw.setWindowTitle(getattr(widget, "WINDOW_TITLE", "Silo Viewer"))
|
||||
sw.show()
|
||||
mdi.setActiveSubWindow(sw)
|
||||
return True
|
||||
|
||||
except Exception as exc:
|
||||
import FreeCAD
|
||||
|
||||
FreeCAD.Console.PrintWarning(
|
||||
f"silo_viewproviders: doubleClicked failed: {exc}\n"
|
||||
)
|
||||
return False
|
||||
|
||||
def setupContextMenu(self, vobj, menu):
|
||||
"""Phase 1: no context menu items."""
|
||||
|
||||
Reference in New Issue
Block a user