diff --git a/blueprintcompiler/decompiler.py b/blueprintcompiler/decompiler.py index cd66386..2b93bd2 100644 --- a/blueprintcompiler/decompiler.py +++ b/blueprintcompiler/decompiler.py @@ -260,7 +260,7 @@ def decompile_property(ctx, gir, name, cdata, bind_source=None, bind_property=No flags += " inverted" if "bidirectional" in bind_flags: flags += " bidirectional" - ctx.print(f"{name}: bind {bind_source}.{bind_property}{flags};") + ctx.print(f"{name}: bind-prop {bind_source}.{bind_property}{flags};") elif truthy(translatable): if context is not None: ctx.print(f"{name}: C_(\"{escape_quote(context)}\", \"{escape_quote(cdata)}\");") diff --git a/blueprintcompiler/language/__init__.py b/blueprintcompiler/language/__init__.py index 3f87faf..d7cb76e 100644 --- a/blueprintcompiler/language/__init__.py +++ b/blueprintcompiler/language/__init__.py @@ -10,6 +10,7 @@ from .gtk_a11y import A11y from .gtk_combo_box_text import Items from .gtk_file_filter import mime_types, patterns, suffixes from .gtk_layout import Layout +from .gtk_list_item_factory import ListItemFactory from .gtk_menu import menu from .gtk_size_group import Widgets from .gtk_string_list import Strings @@ -26,6 +27,7 @@ from .common import * OBJECT_HOOKS.children = [ menu, Object, + ListItemFactory, ] OBJECT_CONTENT_HOOKS.children = [ diff --git a/blueprintcompiler/language/gobject_property.py b/blueprintcompiler/language/gobject_property.py index f0a2ef4..3f9dc6c 100644 --- a/blueprintcompiler/language/gobject_property.py +++ b/blueprintcompiler/language/gobject_property.py @@ -21,6 +21,7 @@ from .expression import Expr from .gobject_object import Object from .gtkbuilder_template import Template +from .gtk_list_item_factory import ListItemFactory from .values import Value, TranslatedStringValue from .common import * @@ -30,7 +31,7 @@ class Property(AstNode): [ UseIdent("name"), ":", - Keyword("bind"), + Keyword("bind-prop", "bind"), UseIdent("bind_source"), ".", UseIdent("bind_property"), @@ -46,7 +47,7 @@ class Property(AstNode): UseIdent("name"), UseLiteral("binding", True), ":", - "bind", + Keyword("bind"), Expr, ), Statement( @@ -162,6 +163,10 @@ class Property(AstNode): xml.start_tag("property", **props) self.children[Object][0].emit_xml(xml) xml.end_tag() + elif len(self.children[ListItemFactory]) == 1: + xml.start_tag("property", **props) + self.children[ListItemFactory][0].emit_xml(xml) + xml.end_tag() elif value is None: if self.tokens["binding"]: xml.start_tag("binding", **props) diff --git a/blueprintcompiler/language/gtk_list_item_factory.py b/blueprintcompiler/language/gtk_list_item_factory.py new file mode 100644 index 0000000..07b29d8 --- /dev/null +++ b/blueprintcompiler/language/gtk_list_item_factory.py @@ -0,0 +1,87 @@ +# gtk_list_item_factory.py +# +# Copyright 2022 James Westman +# +# This file is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This file is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program. If not, see . +# +# SPDX-License-Identifier: LGPL-3.0-or-later + +from .types import ClassName +from .gobject_object import ObjectContent +from .common import * + +class ListItemFactoryContent(AstNode): + grammar = ObjectContent + + @property + def gir_class(self): + return self.root.gir.namespaces["Gtk"].lookup_type("Gtk.ListItem") + + def emit_xml(self, xml: XmlEmitter): + self.children[ObjectContent][0].emit_xml(xml) + + +class ListItemFactory(AstNode, Scope): + grammar = [ + "list_item_factory", + "(", + ClassName, + ")", + Optional(UseIdent("id")), + ListItemFactoryContent, + ] + + @property + def variables(self) -> T.Dict[str, ScopeVariable]: + def emit_xml(xml: XmlEmitter, id: str): + xml.start_tag("constant") + xml.put_text(id) + xml.end_tag() + + def emit_item_xml(xml: XmlEmitter): + xml.start_tag("lookup", name="item") + xml.put_text("GtkListItem") + xml.end_tag() + + return { + **{ + obj.tokens["id"]: ScopeVariable(obj.tokens["id"], obj.gir_class, lambda xml: emit_xml(xml, obj.tokens["id"])) + for obj in self.iterate_children_recursive() + if obj.tokens["id"] is not None + }, + "item": ScopeVariable("item", self.gir_class, emit_item_xml), + } + + @property + def gir_class(self): + return self.root.gir.namespaces["Gtk"].lookup_type("Gtk.ListItemFactory") + + @property + def item_type(self): + return self.children[ClassName][0].gir_type + + def emit_xml(self, xml: XmlEmitter): + sub = XmlEmitter() + sub.start_tag("interface") + sub.put_self_closing("requires", lib="gtk", version="4.0") + sub.start_tag("template", **{"class": "GtkListItem"}) + self.children[ListItemFactoryContent][0].emit_xml(sub) + sub.end_tag() + sub.end_tag() + + xml.start_tag("object", **{"class": "GtkBuilderListItemFactory"}, id=self.tokens["id"]) + xml.start_tag("property", name="bytes") + xml.put_cdata("\n" + sub.result) + xml.end_tag() + xml.end_tag() diff --git a/blueprintcompiler/parse_tree.py b/blueprintcompiler/parse_tree.py index bc36415..aed573b 100644 --- a/blueprintcompiler/parse_tree.py +++ b/blueprintcompiler/parse_tree.py @@ -536,13 +536,13 @@ class UseLiteral(ParseNode): class Keyword(ParseNode): """ Matches the given identifier and sets it as a named token, with the name being the identifier itself. """ - def __init__(self, kw): + def __init__(self, kw, token=None): self.kw = kw - self.set_token = True + self.token = token or kw def _parse(self, ctx: ParseContext): token = ctx.next_token() - ctx.set_group_val(self.kw, True, token) + ctx.set_group_val(self.token, True, token) return str(token) == self.kw diff --git a/blueprintcompiler/xml_emitter.py b/blueprintcompiler/xml_emitter.py index d92d1bd..7f02b42 100644 --- a/blueprintcompiler/xml_emitter.py +++ b/blueprintcompiler/xml_emitter.py @@ -59,6 +59,10 @@ class XmlEmitter: self.result += saxutils.escape(str(text)) self._needs_newline = False + def put_cdata(self, text): + self.result += f"" + self._needs_newline = False + def _indent(self): if self.indent is not None: self.result += "\n" + " " * (self.indent * len(self._tag_stack)) diff --git a/tests/samples/binding.blp b/tests/samples/binding.blp index 39e4458..176d364 100644 --- a/tests/samples/binding.blp +++ b/tests/samples/binding.blp @@ -1,9 +1,9 @@ using Gtk 4.0; Box { - visible: bind box2.visible inverted; - orientation: bind box2.orientation; - spacing: bind box2.spacing no-sync-create; + visible: bind-prop box2.visible inverted; + orientation: bind-prop box2.orientation; + spacing: bind-prop box2.spacing no-sync-create; } Box box2 { diff --git a/tests/samples/list_item_factory.blp b/tests/samples/list_item_factory.blp new file mode 100644 index 0000000..7681263 --- /dev/null +++ b/tests/samples/list_item_factory.blp @@ -0,0 +1,9 @@ +using Gtk 4.0; + +ListView { + factory: list_item_factory(CheckButton) { + child: Label { + label: bind item.group.label; + }; + }; +} \ No newline at end of file diff --git a/tests/samples/list_item_factory.ui b/tests/samples/list_item_factory.ui new file mode 100644 index 0000000..a3c0856 --- /dev/null +++ b/tests/samples/list_item_factory.ui @@ -0,0 +1,28 @@ + + + + + + + + + + +]]> + + + + diff --git a/tests/test_samples.py b/tests/test_samples.py index 80937fb..67b4719 100644 --- a/tests/test_samples.py +++ b/tests/test_samples.py @@ -145,6 +145,7 @@ class TestSamples(unittest.TestCase): self.assert_sample("inline_menu") self.assert_sample("lambda") self.assert_sample("layout") + self.assert_sample("list_item_factory") self.assert_sample("menu") self.assert_sample("numbers") self.assert_sample("object_prop")