diff --git a/blueprintcompiler/language/__init__.py b/blueprintcompiler/language/__init__.py index feeb301..cf52c97 100644 --- a/blueprintcompiler/language/__init__.py +++ b/blueprintcompiler/language/__init__.py @@ -1,6 +1,7 @@ """ Contains all the syntax beyond basic objects, properties, signal, and templates. """ +from .adw_message_dialog import Responses from .attributes import BaseAttribute, BaseTypedAttribute from .expression import Expr from .gobject_object import Object, ObjectContent @@ -39,6 +40,7 @@ OBJECT_CONTENT_HOOKS.children = [ Widgets, Items, Strings, + Responses, Child, ] diff --git a/blueprintcompiler/language/adw_message_dialog.py b/blueprintcompiler/language/adw_message_dialog.py new file mode 100644 index 0000000..0140eb4 --- /dev/null +++ b/blueprintcompiler/language/adw_message_dialog.py @@ -0,0 +1,108 @@ +# adw_message_dialog.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 .gobject_object import ObjectContent, validate_parent_type +from .attributes import BaseAttribute +from .common import * + + +class AppearanceClass(AstNode): + grammar = AnyOf(Keyword("destructive"), Keyword("suggested")) + + @property + def name(self): + if self.tokens["destructive"]: + return "destructive" + else: + return "suggested" + + @validate() + def unique_in_parent(self): + self.validate_unique_in_parent("Only one of 'destructive' or 'suggested' is allowed") + + +class Response(BaseAttribute): + tag_name = "response" + attr_name = "id" + + value_type = gir.StringType() + + grammar = [ + UseIdent("name"), + ":", + VALUE_HOOKS, + ZeroOrMore(AnyOf( + Keyword("disabled"), + AppearanceClass, + )), + ] + + @validate("name") + def unique_in_parent(self): + self.validate_unique_in_parent( + f"Duplicate response ID '{self.tokens['name']}'", + lambda other: other.tokens["name"] == self.tokens["name"], + ) + + def extra_attributes(self): + attrs = {} + + if len(self.children[AppearanceClass]) == 1: + attrs["appearance"] = self.children[AppearanceClass][0].name + + if self.tokens["disabled"]: + attrs["enabled"] = "false" + + return attrs + + def emit_xml(self, xml): + xml.put_self_closing("response", name=self.tokens["name"]) + + +class Responses(AstNode): + grammar = [ + Keyword("responses"), + "{", + Delimited(Response, ","), + "}", + ] + + @validate("responses") + def container_is_adw_message_dialog(self): + validate_parent_type(self, "Adw", "MessageDialog", "responses") + + @validate("responses") + def unique_in_parent(self): + self.validate_unique_in_parent("Duplicate responses block") + + def emit_xml(self, xml: XmlEmitter): + xml.start_tag("responses") + for child in self.children: + child.emit_xml(xml) + xml.end_tag() + +@completer( + applies_in=[ObjectContent], + applies_in_subclass=("Gtk", "Widget"), + matches=new_statement_patterns, +) +def style_completer(ast_node, match_variables): + yield Completion("styles", CompletionItemKind.Keyword, snippet="styles [\"$0\"]") + diff --git a/blueprintcompiler/language/attributes.py b/blueprintcompiler/language/attributes.py index a6db9f1..d0a7cbc 100644 --- a/blueprintcompiler/language/attributes.py +++ b/blueprintcompiler/language/attributes.py @@ -32,6 +32,9 @@ class BaseAttribute(AstNode): def name(self): return self.tokens["name"] + def extra_attributes(self): + return {} + def emit_xml(self, xml: XmlEmitter): value = self.children[Value][0] attrs = { self.attr_name: self.name } @@ -39,6 +42,8 @@ class BaseAttribute(AstNode): if isinstance(value, TranslatedStringValue): attrs = { **attrs, **value.attrs } + attrs = { **attrs, **self.extra_attributes() } + xml.start_tag(self.tag_name, **attrs) value.emit_xml(xml) xml.end_tag() diff --git a/tests/sample_errors/responses.blp b/tests/sample_errors/responses.blp new file mode 100644 index 0000000..b97b5da --- /dev/null +++ b/tests/sample_errors/responses.blp @@ -0,0 +1,12 @@ +using Gtk 4.0; +using Adw 1; + +Adw.MessageDialog { + responses { + duplicate: _("Hello"), + duplicate_appearance: _("1") destructive suggested, + duplicate: _("Goodbye"), + } + + responses {} +} \ No newline at end of file diff --git a/tests/sample_errors/responses.err b/tests/sample_errors/responses.err new file mode 100644 index 0000000..4c0346d --- /dev/null +++ b/tests/sample_errors/responses.err @@ -0,0 +1,3 @@ +7,46,9,Only one of 'destructive' or 'suggested' is allowed +8,5,9,Duplicate response ID 'duplicate' +11,3,9,Duplicate responses block \ No newline at end of file diff --git a/tests/samples/responses.blp b/tests/samples/responses.blp new file mode 100644 index 0000000..6c1d099 --- /dev/null +++ b/tests/samples/responses.blp @@ -0,0 +1,12 @@ +using Gtk 4.0; +using Adw 1; + +Adw.MessageDialog { + responses { + cancel: _("Cancel"), + discard: _("Discard") destructive, + save: "Save" suggested disabled, + } + + response::save => on_response_save(); +} \ No newline at end of file diff --git a/tests/samples/responses.ui b/tests/samples/responses.ui new file mode 100644 index 0000000..b9f746d --- /dev/null +++ b/tests/samples/responses.ui @@ -0,0 +1,12 @@ + + + + + + Cancel + Discard + Save + + + + diff --git a/tests/test_samples.py b/tests/test_samples.py index 4fdb5c0..5f33106 100644 --- a/tests/test_samples.py +++ b/tests/test_samples.py @@ -147,6 +147,7 @@ class TestSamples(unittest.TestCase): self.assert_sample("object_prop") self.assert_sample("parseable") self.assert_sample("property") + self.assert_sample("responses") self.assert_sample("signal") self.assert_sample("size_group") self.assert_sample("string_list") @@ -197,6 +198,7 @@ class TestSamples(unittest.TestCase): self.assert_sample_error("obj_prop_type") self.assert_sample_error("property_dne") self.assert_sample_error("read_only_properties") + self.assert_sample_error("responses") self.assert_sample_error("signal_dne") self.assert_sample_error("signal_object_dne") self.assert_sample_error("size_group_non_widget")