diff --git a/blueprintcompiler/decompiler.py b/blueprintcompiler/decompiler.py
index f5c45fb..81072d9 100644
--- a/blueprintcompiler/decompiler.py
+++ b/blueprintcompiler/decompiler.py
@@ -56,7 +56,6 @@ class DecompileCtx:
self.gir = GirContext()
self._blocks_need_end: T.List[str] = []
self._last_line_type: LineType = LineType.NONE
- self.template_class: T.Optional[str] = None
self._obj_type_stack: list[T.Optional[GirType]] = []
self._node_stack: list[Element] = []
@@ -112,6 +111,45 @@ class DecompileCtx:
else:
return self._node_stack[-1]
+ @property
+ def parent_node(self) -> T.Optional[Element]:
+ if len(self._node_stack) < 2:
+ return None
+ else:
+ return self._node_stack[-2]
+
+ @property
+ def root_node(self) -> T.Optional[Element]:
+ if len(self._node_stack) == 0:
+ return None
+ else:
+ return self._node_stack[0]
+
+ @property
+ def template_class(self) -> T.Optional[str]:
+ assert self.root_node is not None
+ for child in self.root_node.children:
+ if child.tag == "template":
+ return child["class"]
+ return None
+
+ def find_object(self, id: str) -> T.Optional[Element]:
+ assert self.root_node is not None
+ for child in self.root_node.children:
+ if child.tag == "template" and child["class"] == id:
+ return child
+
+ def find_in_children(node: Element) -> T.Optional[Element]:
+ if node.tag in ["object", "menu"] and node["id"] == id:
+ return node
+ else:
+ for child in node.children:
+ if result := find_in_children(child):
+ return result
+ return None
+
+ return find_in_children(self.root_node)
+
def end_block_with(self, text: str) -> None:
self._blocks_need_end[-1] = text
@@ -122,7 +160,15 @@ class DecompileCtx:
if len(self._blocks_need_end):
self._blocks_need_end[-1] = _CLOSING[line[-1]]
- def print_value(self, value: str, type: T.Optional[GirType]) -> None:
+ # Converts a value from an XML element to a blueprint string
+ # based on the given type. Returns a tuple of translator comments
+ # (if any) and the decompiled syntax.
+ def decompile_value(
+ self,
+ value: str,
+ type: T.Optional[GirType],
+ translatable: T.Optional[T.Tuple[str, str, str]] = None,
+ ) -> T.Tuple[str, str]:
def get_enum_name(value):
for member in type.members.values():
if (
@@ -133,16 +179,18 @@ class DecompileCtx:
return member.name
return value.replace("-", "_")
- if type is None:
- self.print(f"{escape_quote(value)}")
+ if translatable is not None and truthy(translatable[0]):
+ return decompile_translatable(value, *translatable)
+ elif type is None:
+ return "", f"{escape_quote(value)}"
elif type.assignable_to(FloatType()):
- self.print(str(value))
+ return "", str(value)
elif type.assignable_to(BoolType()):
val = truthy(value)
- self.print("true" if val else "false")
+ return "", ("true" if val else "false")
elif type.assignable_to(ArrayType(StringType())):
items = ", ".join([escape_quote(x) for x in value.split("\n")])
- self.print(f"[{items}]")
+ return "", f"[{items}]"
elif (
type.assignable_to(self.gir.namespaces["Gtk"].lookup_type("Gdk.Pixbuf"))
or type.assignable_to(self.gir.namespaces["Gtk"].lookup_type("Gdk.Texture"))
@@ -156,30 +204,25 @@ class DecompileCtx:
self.gir.namespaces["Gtk"].lookup_type("Gtk.ShortcutTrigger")
)
):
- self.print(f"{escape_quote(value)}")
+ return "", escape_quote(value)
elif value == self.template_class:
- self.print("template")
+ return "", "template"
elif type.assignable_to(
self.gir.namespaces["Gtk"].lookup_type("GObject.Object")
) or isinstance(type, Interface):
- self.print(value)
+ return "", ("null" if value == "" else value)
elif isinstance(type, Bitfield):
flags = [get_enum_name(flag) for flag in value.split("|")]
- self.print(" | ".join(flags))
+ return "", " | ".join(flags)
elif isinstance(type, Enumeration):
- self.print(get_enum_name(value))
+ return "", get_enum_name(value)
elif isinstance(type, TypeType):
if t := self.type_by_cname(value):
- self.print(f"typeof<{full_name(t)}>")
+ return "", f"typeof<{full_name(t)}>"
else:
- self.print(f"typeof<${value}>")
+ return "", f"typeof<${value}>"
else:
- self.print(f"{escape_quote(value)}")
-
- def print_attribute(self, name: str, value: str, type: GirType) -> None:
- self.print(f"{name}: ")
- self.print_value(value, type)
- self.print(";")
+ return "", escape_quote(value)
def decompile_element(
@@ -247,7 +290,7 @@ def canon(string: str) -> str:
def truthy(string: str) -> bool:
- return string.lower() in ["yes", "true", "t", "y", "1"]
+ return string is not None and string.lower() in ["yes", "true", "t", "y", "1"]
def full_name(gir: GirType) -> str:
@@ -318,9 +361,11 @@ def decompile_translatable(
translatable: T.Optional[str],
context: T.Optional[str],
comments: T.Optional[str],
-) -> T.Tuple[T.Optional[str], str]:
+) -> T.Tuple[str, str]:
if translatable is not None and truthy(translatable):
- if comments is not None:
+ if comments is None:
+ comments = ""
+ else:
comments = comments.replace("/*", " ").replace("*/", " ")
comments = f"/* Translators: {comments} */"
@@ -329,7 +374,7 @@ def decompile_translatable(
else:
return comments, f"_({escape_quote(string)})"
else:
- return comments, f"{escape_quote(string)}"
+ return "", f"{escape_quote(string)}"
@decompiler("property", cdata=True)
@@ -376,7 +421,8 @@ def decompile_property(
elif gir is None or gir.properties.get(name) is None:
ctx.print(f"{name}: {escape_quote(cdata)};")
else:
- ctx.print_attribute(name, cdata, gir.properties.get(name).type)
+ _, string = ctx.decompile_value(cdata, gir.properties.get(name).type)
+ ctx.print(f"{name}: {string};")
return gir
diff --git a/blueprintcompiler/language/adw_breakpoint.py b/blueprintcompiler/language/adw_breakpoint.py
index 4927c11..eec7c7e 100644
--- a/blueprintcompiler/language/adw_breakpoint.py
+++ b/blueprintcompiler/language/adw_breakpoint.py
@@ -195,3 +195,42 @@ class AdwBreakpointSetters(AstNode):
@validate()
def unique(self):
self.validate_unique_in_parent("Duplicate setters block")
+
+
+@decompiler("condition", cdata=True)
+def decompile_condition(ctx: DecompileCtx, gir, cdata):
+ ctx.print(f"condition({escape_quote(cdata)})")
+
+
+@decompiler("setter", element=True)
+def decompile_setter(ctx: DecompileCtx, gir, element):
+ assert ctx.parent_node is not None
+ # only run for the first setter
+ for child in ctx.parent_node.children:
+ if child.tag == "setter":
+ if child != element:
+ # already decompiled
+ return
+ else:
+ break
+
+ ctx.print("setters {")
+ for child in ctx.parent_node.children:
+ if child.tag == "setter":
+ object_id = child["object"]
+ property_name = child["property"]
+ obj = ctx.find_object(object_id)
+ if obj is not None:
+ gir_class = ctx.type_by_cname(obj["class"])
+ else:
+ gir_class = None
+
+ if object_id == ctx.template_class:
+ object_id = "template"
+
+ comments, string = ctx.decompile_value(
+ child.cdata,
+ gir_class,
+ (child["translatable"], child["context"], child["comments"]),
+ )
+ ctx.print(f"{comments} {object_id}.{property_name}: {string};")
diff --git a/blueprintcompiler/language/expression.py b/blueprintcompiler/language/expression.py
index 117fd30..37e73e8 100644
--- a/blueprintcompiler/language/expression.py
+++ b/blueprintcompiler/language/expression.py
@@ -319,7 +319,8 @@ def decompile_constant(
else:
ctx.print(cdata)
else:
- ctx.print_value(cdata, ctx.type_by_cname(type))
+ _, string = ctx.decompile_value(cdata, ctx.type_by_cname(type))
+ ctx.print(string)
@decompiler("closure", skip_children=True)
diff --git a/blueprintcompiler/language/gtk_a11y.py b/blueprintcompiler/language/gtk_a11y.py
index ec49002..8870a74 100644
--- a/blueprintcompiler/language/gtk_a11y.py
+++ b/blueprintcompiler/language/gtk_a11y.py
@@ -249,17 +249,12 @@ def a11y_name_completer(lsp, ast_node, match_variables):
)
-def decompile_attr(ctx: DecompileCtx, attr):
- if attr["translatable"] is not None and decompile.truthy(attr["translatable"]):
- ctx.print(f"_({escape_quote(attr.cdata)})")
- else:
- ctx.print_value(attr.cdata, get_types(ctx.gir).get(attr["name"]))
-
-
@decompiler("accessibility", skip_children=True, element=True)
def decompile_accessibility(ctx: DecompileCtx, _gir, element):
ctx.print("accessibility {")
already_printed = set()
+ types = get_types(ctx.gir)
+
for child in element.children:
name = child["name"]
@@ -270,13 +265,20 @@ def decompile_accessibility(ctx: DecompileCtx, _gir, element):
ctx.print(f"{name}: [")
for value in element.children:
if value["name"] == name:
- decompile_attr(ctx, value)
- ctx.print(", ")
+ comments, string = ctx.decompile_value(
+ value.cdata,
+ types.get(value["name"]),
+ (value["translatable"], value["context"], value["comments"]),
+ )
+ ctx.print(f"{comments} {string},")
ctx.print("];")
else:
- ctx.print(f"{name}:")
- decompile_attr(ctx, child)
- ctx.print(";")
+ comments, string = ctx.decompile_value(
+ child.cdata,
+ types.get(child["name"]),
+ (child["translatable"], child["context"], child["comments"]),
+ )
+ ctx.print(f"{comments} {name}: {string};")
already_printed.add(name)
ctx.print("}")
diff --git a/blueprintcompiler/language/gtkbuilder_template.py b/blueprintcompiler/language/gtkbuilder_template.py
index be8473c..29f7b37 100644
--- a/blueprintcompiler/language/gtkbuilder_template.py
+++ b/blueprintcompiler/language/gtkbuilder_template.py
@@ -102,6 +102,4 @@ def decompile_template(ctx: DecompileCtx, gir, klass, parent=None):
else:
ctx.print(f"template {class_name(klass)} : {class_name(parent)} {{")
- ctx.template_class = klass
-
return ctx.type_by_cname(klass) or ctx.type_by_cname(parent)
diff --git a/tests/samples/adw_breakpoint.blp b/tests/samples/adw_breakpoint.blp
index 1d085e4..dfdfd9f 100644
--- a/tests/samples/adw_breakpoint.blp
+++ b/tests/samples/adw_breakpoint.blp
@@ -1,13 +1,14 @@
using Gtk 4.0;
using Adw 1;
-Gtk.Label label {}
+Label label {}
Adw.Breakpoint {
condition ("max-width: 600px")
+
setters {
- label.label: _("Hello, world!");
+ label.label: C_("test", "Hello, world!");
label.visible: false;
label.extra-menu: null;
}
-}
\ No newline at end of file
+}
diff --git a/tests/samples/adw_breakpoint.ui b/tests/samples/adw_breakpoint.ui
index 29cbf9b..8b7dc0e 100644
--- a/tests/samples/adw_breakpoint.ui
+++ b/tests/samples/adw_breakpoint.ui
@@ -9,7 +9,7 @@ corresponding .blp file and regenerate this file with blueprint-compiler.
diff --git a/tests/samples/adw_breakpoint_template_dec.blp b/tests/samples/adw_breakpoint_template_dec.blp
new file mode 100644
index 0000000..dc8afb2
--- /dev/null
+++ b/tests/samples/adw_breakpoint_template_dec.blp
@@ -0,0 +1,10 @@
+using Gtk 4.0;
+using Adw 1;
+
+template $MyTemplate {
+ Adw.Breakpoint {
+ setters {
+ template.some-prop: "true";
+ }
+ }
+}
diff --git a/tests/test_samples.py b/tests/test_samples.py
index 41d572f..11fe6f8 100644
--- a/tests/test_samples.py
+++ b/tests/test_samples.py
@@ -210,10 +210,6 @@ class TestSamples(unittest.TestCase):
# Not implemented yet
"action_widgets",
# Not implemented yet
- "adw_breakpoint",
- # Not implemented yet
- "adw_breakpoint_template",
- # Not implemented yet
"gtkcolumnview",
# Comments are not preserved in either direction
"comments",