From 1bf0f7daab6e0c4e4a5319a814c3896fc55cffb8 Mon Sep 17 00:00:00 2001 From: James Westman Date: Mon, 9 Dec 2024 20:29:08 -0600 Subject: [PATCH] language: Add not-swapped flag for signals This is needed because GtkBuilder defaults to swapped when you specify the object attribute. --- blueprintcompiler/language/gobject_signal.py | 41 ++++++++++++++++--- blueprintcompiler/outputs/xml/__init__.py | 2 +- docs/reference/objects.rst | 5 ++- .../sample_errors/signal_exclusive_flags.blp | 5 +++ .../sample_errors/signal_exclusive_flags.err | 1 + .../signal_unnecessary_flags.blp | 6 +++ .../signal_unnecessary_flags.err | 2 + tests/samples/signal_not_swapped.blp | 5 +++ tests/samples/signal_not_swapped.ui | 12 ++++++ tests/test_samples.py | 1 + 10 files changed, 72 insertions(+), 8 deletions(-) create mode 100644 tests/sample_errors/signal_exclusive_flags.blp create mode 100644 tests/sample_errors/signal_exclusive_flags.err create mode 100644 tests/sample_errors/signal_unnecessary_flags.blp create mode 100644 tests/sample_errors/signal_unnecessary_flags.err create mode 100644 tests/samples/signal_not_swapped.blp create mode 100644 tests/samples/signal_not_swapped.ui diff --git a/blueprintcompiler/language/gobject_signal.py b/blueprintcompiler/language/gobject_signal.py index 79f9ae7..1db57e8 100644 --- a/blueprintcompiler/language/gobject_signal.py +++ b/blueprintcompiler/language/gobject_signal.py @@ -27,6 +27,7 @@ from .gtkbuilder_template import Template class SignalFlag(AstNode): grammar = AnyOf( UseExact("flag", "swapped"), + UseExact("flag", "not-swapped"), UseExact("flag", "after"), ) @@ -40,6 +41,27 @@ class SignalFlag(AstNode): f"Duplicate flag '{self.flag}'", lambda x: x.flag == self.flag ) + @validate() + def swapped_exclusive(self): + if self.flag in ["swapped", "not-swapped"]: + self.validate_unique_in_parent( + "'swapped' and 'not-swapped' flags cannot be used together", + lambda x: x.flag in ["swapped", "not-swapped"], + ) + + @validate() + def swapped_unnecessary(self): + if self.flag == "not-swapped" and self.parent.object_id is None: + raise CompileWarning( + "'not-swapped' is the default for handlers that do not specify an object", + actions=[CodeAction("Remove 'not-swapped' flag", "")], + ) + elif self.flag == "swapped" and self.parent.object_id is not None: + raise CompileWarning( + "'swapped' is the default for handlers that specify an object", + actions=[CodeAction("Remove 'swapped' flag", "")], + ) + @docs() def ref_docs(self): return get_docs_section("Syntax Signal") @@ -92,9 +114,17 @@ class Signal(AstNode): def flags(self) -> T.List[SignalFlag]: return self.children[SignalFlag] + # Returns True if the "swapped" flag is present, False if "not-swapped" is present, and None if neither are present. + # GtkBuilder's default if swapped is not specified is to not swap the arguments if no object is specified, and to + # swap them if an object is specified. @property - def is_swapped(self) -> bool: - return any(x.flag == "swapped" for x in self.flags) + def is_swapped(self) -> T.Optional[bool]: + for flag in self.flags: + if flag.flag == "swapped": + return True + elif flag.flag == "not-swapped": + return False + return None @property def is_after(self) -> bool: @@ -194,15 +224,16 @@ class Signal(AstNode): @decompiler("signal") -def decompile_signal( - ctx, gir, name, handler, swapped="false", after="false", object=None -): +def decompile_signal(ctx, gir, name, handler, swapped=None, after="false", object=None): object_name = object or "" name = name.replace("_", "-") line = f"{name} => ${handler}({object_name})" if decompile.truthy(swapped): line += " swapped" + elif swapped is not None: + line += " not-swapped" + if decompile.truthy(after): line += " after" diff --git a/blueprintcompiler/outputs/xml/__init__.py b/blueprintcompiler/outputs/xml/__init__.py index 5e43834..f6a0d1c 100644 --- a/blueprintcompiler/outputs/xml/__init__.py +++ b/blueprintcompiler/outputs/xml/__init__.py @@ -169,7 +169,7 @@ class XmlOutput(OutputFormat): "signal", name=name, handler=signal.handler, - swapped=signal.is_swapped or None, + swapped=signal.is_swapped, after=signal.is_after or None, object=( self._object_id(signal, signal.object_id) if signal.object_id else None diff --git a/docs/reference/objects.rst b/docs/reference/objects.rst index 699db49..09f5af8 100644 --- a/docs/reference/objects.rst +++ b/docs/reference/objects.rst @@ -91,7 +91,7 @@ Signal Handlers .. rst-class:: grammar-block Signal = `> ('::' `>)? '=>' '$' `> '(' `>? ')' (SignalFlag)* ';' - SignalFlag = 'after' | 'swapped' + SignalFlag = 'after' | 'swapped' | 'not-swapped' Signals are one way to respond to user input (another is `actions `_, which use the `action-name property `_). @@ -99,6 +99,8 @@ Signals provide a handle for your code to listen to events in the UI. The handle Optionally, you can provide an object ID to use when connecting the signal. +The ``swapped`` flag is used to swap the order of the object and userdata arguments in C applications. If an object argument is specified, then this is the default behavior, so the ``not-swapped`` flag can be used to prevent the swap. + Example ~~~~~~~ @@ -108,7 +110,6 @@ Example clicked => $on_button_clicked(); } - .. _Syntax Child: Children diff --git a/tests/sample_errors/signal_exclusive_flags.blp b/tests/sample_errors/signal_exclusive_flags.blp new file mode 100644 index 0000000..6432965 --- /dev/null +++ b/tests/sample_errors/signal_exclusive_flags.blp @@ -0,0 +1,5 @@ +using Gtk 4.0; + +$MyObject obj { + signal1 => $handler() swapped not-swapped; +} diff --git a/tests/sample_errors/signal_exclusive_flags.err b/tests/sample_errors/signal_exclusive_flags.err new file mode 100644 index 0000000..5176510 --- /dev/null +++ b/tests/sample_errors/signal_exclusive_flags.err @@ -0,0 +1 @@ +4,33,11,'swapped' and 'not-swapped' flags cannot be used together \ No newline at end of file diff --git a/tests/sample_errors/signal_unnecessary_flags.blp b/tests/sample_errors/signal_unnecessary_flags.blp new file mode 100644 index 0000000..ed95a9b --- /dev/null +++ b/tests/sample_errors/signal_unnecessary_flags.blp @@ -0,0 +1,6 @@ +using Gtk 4.0; + +$MyObject obj { + signal1 => $handler() not-swapped; + signal2 => $handler(obj) swapped; +} diff --git a/tests/sample_errors/signal_unnecessary_flags.err b/tests/sample_errors/signal_unnecessary_flags.err new file mode 100644 index 0000000..7586085 --- /dev/null +++ b/tests/sample_errors/signal_unnecessary_flags.err @@ -0,0 +1,2 @@ +4,25,11,'not-swapped' is the default for handlers that do not specify an object +5,28,7,'swapped' is the default for handlers that specify an object \ No newline at end of file diff --git a/tests/samples/signal_not_swapped.blp b/tests/samples/signal_not_swapped.blp new file mode 100644 index 0000000..835ab17 --- /dev/null +++ b/tests/samples/signal_not_swapped.blp @@ -0,0 +1,5 @@ +using Gtk 4.0; + +Button obj { + clicked => $handler(obj) not-swapped; +} \ No newline at end of file diff --git a/tests/samples/signal_not_swapped.ui b/tests/samples/signal_not_swapped.ui new file mode 100644 index 0000000..c9dcd8e --- /dev/null +++ b/tests/samples/signal_not_swapped.ui @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/tests/test_samples.py b/tests/test_samples.py index fe90774..cc2fa73 100644 --- a/tests/test_samples.py +++ b/tests/test_samples.py @@ -196,6 +196,7 @@ class TestSamples(unittest.TestCase): "expr_closure_args", "parseable", "signal", + "signal_not_swapped", "template", "template_binding", "template_binding_extern",