From ba8b4921341c518c09e42853d4610feaf61239c1 Mon Sep 17 00:00:00 2001 From: Sonny Piers Date: Sun, 28 Jan 2024 02:03:29 +0100 Subject: [PATCH] Add support for Adw.AlertDialog --- NEWS.md | 4 ++ blueprintcompiler/gir.py | 7 ++- blueprintcompiler/language/__init__.py | 4 +- ...ssage_dialog.py => adw_response_dialog.py} | 38 ++++++++++----- blueprintcompiler/outputs/xml/__init__.py | 2 +- docs/reference/extensions.rst | 37 +++++++++++++-- .../adw_alert_dialog_duplicate_flags.blp | 9 ++++ .../adw_alert_dialog_duplicate_flags.err | 2 + tests/samples/adw_alertdialog_responses.blp | 10 ++++ tests/samples/adw_alertdialog_responses.ui | 16 +++++++ ...es.blp => adw_messagedialog_responses.blp} | 0 ...nses.ui => adw_messagedialog_responses.ui} | 0 tests/test_samples.py | 47 +++++++++++-------- 13 files changed, 136 insertions(+), 40 deletions(-) rename blueprintcompiler/language/{adw_message_dialog.py => adw_response_dialog.py} (81%) create mode 100644 tests/sample_errors/adw_alert_dialog_duplicate_flags.blp create mode 100644 tests/sample_errors/adw_alert_dialog_duplicate_flags.err create mode 100644 tests/samples/adw_alertdialog_responses.blp create mode 100644 tests/samples/adw_alertdialog_responses.ui rename tests/samples/{responses.blp => adw_messagedialog_responses.blp} (100%) rename tests/samples/{responses.ui => adw_messagedialog_responses.ui} (100%) diff --git a/NEWS.md b/NEWS.md index 706f4f0..ef294b7 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,7 @@ +# v0.11.0 + +- Added support for [Adw.AlertDialog](https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.AlertDialog.html#adwalertdialog-as-gtkbuildable) custom syntax. + # v0.10.0 ## Added diff --git a/blueprintcompiler/gir.py b/blueprintcompiler/gir.py index 9ad5699..142bbd2 100644 --- a/blueprintcompiler/gir.py +++ b/blueprintcompiler/gir.py @@ -79,7 +79,12 @@ def get_available_namespaces() -> T.List[T.Tuple[str, str]]: ] for search_path in search_paths: - for filename in os.listdir(search_path): + try: + filenames = os.listdir(search_path) + except FileNotFoundError: + continue + + for filename in filenames: if filename.endswith(".typelib"): namespace, version = filename.removesuffix(".typelib").rsplit("-", 1) _available_namespaces.append((namespace, version)) diff --git a/blueprintcompiler/language/__init__.py b/blueprintcompiler/language/__init__.py index 39d8336..1e183c9 100644 --- a/blueprintcompiler/language/__init__.py +++ b/blueprintcompiler/language/__init__.py @@ -3,7 +3,7 @@ from .adw_breakpoint import ( AdwBreakpointSetter, AdwBreakpointSetters, ) -from .adw_message_dialog import ExtAdwMessageDialog +from .adw_response_dialog import ExtAdwResponseDialog from .attributes import BaseAttribute from .binding import Binding from .common import * @@ -60,7 +60,7 @@ OBJECT_CONTENT_HOOKS.children = [ AdwBreakpointCondition, AdwBreakpointSetters, ExtAccessibility, - ExtAdwMessageDialog, + ExtAdwResponseDialog, ExtComboBoxItems, ext_file_filter_mime_types, ext_file_filter_patterns, diff --git a/blueprintcompiler/language/adw_message_dialog.py b/blueprintcompiler/language/adw_response_dialog.py similarity index 81% rename from blueprintcompiler/language/adw_message_dialog.py rename to blueprintcompiler/language/adw_response_dialog.py index 9b3d41c..46172f2 100644 --- a/blueprintcompiler/language/adw_message_dialog.py +++ b/blueprintcompiler/language/adw_response_dialog.py @@ -1,4 +1,4 @@ -# adw_message_dialog.py +# adw_response_dialog.py # # Copyright 2023 James Westman # @@ -25,7 +25,7 @@ from .gobject_object import ObjectContent, validate_parent_type from .values import StringValue -class ExtAdwMessageDialogFlag(AstNode): +class ExtAdwResponseDialogFlag(AstNode): grammar = AnyOf( UseExact("flag", "destructive"), UseExact("flag", "suggested"), @@ -51,12 +51,12 @@ class ExtAdwMessageDialogFlag(AstNode): ) -class ExtAdwMessageDialogResponse(AstNode): +class ExtAdwResponseDialogResponse(AstNode): grammar = [ UseIdent("id"), Match(":").expected(), to_parse_node(StringValue).expected("a string or translatable string"), - ZeroOrMore(ExtAdwMessageDialogFlag), + ZeroOrMore(ExtAdwResponseDialogFlag), ] @property @@ -64,8 +64,8 @@ class ExtAdwMessageDialogResponse(AstNode): return self.tokens["id"] @property - def flags(self) -> T.List[ExtAdwMessageDialogFlag]: - return self.children[ExtAdwMessageDialogFlag] + def flags(self) -> T.List[ExtAdwResponseDialogFlag]: + return self.children[ExtAdwResponseDialogFlag] @property def appearance(self) -> T.Optional[str]: @@ -106,16 +106,16 @@ class ExtAdwMessageDialogResponse(AstNode): ) -class ExtAdwMessageDialog(AstNode): +class ExtAdwResponseDialog(AstNode): grammar = [ Keyword("responses"), Match("[").expected(), - Delimited(ExtAdwMessageDialogResponse, ","), + Delimited(ExtAdwResponseDialogResponse, ","), "]", ] @property - def responses(self) -> T.List[ExtAdwMessageDialogResponse]: + def responses(self) -> T.List[ExtAdwResponseDialogResponse]: return self.children @property @@ -128,8 +128,11 @@ class ExtAdwMessageDialog(AstNode): ) @validate("responses") - def container_is_message_dialog(self): - validate_parent_type(self, "Adw", "MessageDialog", "responses") + def container_is_message_dialog_or_alert_dialog(self): + try: + validate_parent_type(self, "Adw", "MessageDialog", "responses") + except: + validate_parent_type(self, "Adw", "AlertDialog", "responses") @validate("responses") def unique_in_parent(self): @@ -141,7 +144,18 @@ class ExtAdwMessageDialog(AstNode): applies_in_subclass=("Adw", "MessageDialog"), matches=new_statement_patterns, ) -def style_completer(lsp, ast_node, match_variables): +def complete_adw_message_dialog(lsp, ast_node, match_variables): + yield Completion( + "responses", CompletionItemKind.Keyword, snippet="responses [\n\t$0\n]" + ) + + +@completer( + applies_in=[ObjectContent], + applies_in_subclass=("Adw", "AlertDialog"), + matches=new_statement_patterns, +) +def complete_adw_alert_dialog(lsp, ast_node, match_variables): yield Completion( "responses", CompletionItemKind.Keyword, snippet="responses [\n\t$0\n]" ) diff --git a/blueprintcompiler/outputs/xml/__init__.py b/blueprintcompiler/outputs/xml/__init__.py index b84c5cf..8707ee6 100644 --- a/blueprintcompiler/outputs/xml/__init__.py +++ b/blueprintcompiler/outputs/xml/__init__.py @@ -337,7 +337,7 @@ class XmlOutput(OutputFormat): self._emit_attribute("property", "name", prop.name, prop.value, xml) xml.end_tag() - elif isinstance(extension, ExtAdwMessageDialog): + elif isinstance(extension, ExtAdwResponseDialog): xml.start_tag("responses") for response in extension.responses: xml.start_tag( diff --git a/docs/reference/extensions.rst b/docs/reference/extensions.rst index fd42bad..86cfe62 100644 --- a/docs/reference/extensions.rst +++ b/docs/reference/extensions.rst @@ -15,7 +15,7 @@ Properties are the main way to set values on objects, but they are limited by th .. rst-class:: grammar-block Extension = :ref:`ExtAccessibility` - | :ref:`ExtAdwMessageDialog` + | :ref:`ExtAdwResponseDialog` | :ref:`ExtAdwBreakpoint` | :ref:`ExtComboBoxItems` | :ref:`ExtFileFilterMimeTypes` @@ -63,16 +63,16 @@ Defines the condition for a breakpoint and the properties that will be set at th The `Adw.Breakpoint:condition `_ property has type `Adw.BreakpointCondition `_, which GtkBuilder doesn't know how to parse from a string. Therefore, the ``condition`` syntax is used instead. -.. _Syntax ExtAdwMessageDialog: +.. _Syntax ExtAdwResponseDialog: Adw.MessageDialog Responses ---------------------------- .. rst-class:: grammar-block - ExtAdwMessageDialog = 'responses' '[' (ExtAdwMessageDialogResponse),* ']' - ExtAdwMessageDialogResponse = `> ':' :ref:`StringValue` ExtAdwMessageDialogFlag* - ExtAdwMessageDialogFlag = 'destructive' | 'suggested' | 'disabled' + ExtAdwResponseDialog = 'responses' '[' (ExtAdwResponseDialogResponse),* ']' + ExtAdwResponseDialogResponse = `> ':' :ref:`StringValue` ExtAdwResponseDialogFlag* + ExtAdwResponseDialogFlag = 'destructive' | 'suggested' | 'disabled' Valid in `Adw.MessageDialog `_. @@ -92,6 +92,33 @@ The ``responses`` block defines the buttons that will be added to the dialog. Th } +Adw.AlertDialog Responses +---------------------------- + +.. rst-class:: grammar-block + + ExtAdwAlertDialog = 'responses' '[' (ExtAdwAlertDialogResponse),* ']' + ExtAdwAlertDialogResponse = `> ':' :ref:`StringValue` ExtAdwAlertDialogFlag* + ExtAdwAlertDialogFlag = 'destructive' | 'suggested' | 'disabled' + +Valid in `Adw.AlertDialog `_. + +The ``responses`` block defines the buttons that will be added to the dialog. The ``destructive`` or ``suggested`` flag sets the appearance of the button, and the ``disabled`` flag can be used to disable the button. + +.. code-block:: blueprint + + using Adw 1; + + Adw.AlertDialog { + responses [ + cancel: _("Cancel"), + delete: _("Delete") destructive, + save: "Save" suggested, + wipeHardDrive: "Wipe Hard Drive" destructive disabled, + ] + } + + .. _Syntax ExtComboBoxItems: Gtk.ComboBoxText Items diff --git a/tests/sample_errors/adw_alert_dialog_duplicate_flags.blp b/tests/sample_errors/adw_alert_dialog_duplicate_flags.blp new file mode 100644 index 0000000..65fae22 --- /dev/null +++ b/tests/sample_errors/adw_alert_dialog_duplicate_flags.blp @@ -0,0 +1,9 @@ +using Gtk 4.0; +using Adw 1; + +Adw.AlertDialog { + responses [ + cancel: _("Cancel") disabled disabled, + ok: _("Ok") destructive suggested, + ] +} \ No newline at end of file diff --git a/tests/sample_errors/adw_alert_dialog_duplicate_flags.err b/tests/sample_errors/adw_alert_dialog_duplicate_flags.err new file mode 100644 index 0000000..91f0a3d --- /dev/null +++ b/tests/sample_errors/adw_alert_dialog_duplicate_flags.err @@ -0,0 +1,2 @@ +6,34,8,Duplicate 'disabled' flag +7,29,9,'suggested' and 'destructive' are exclusive \ No newline at end of file diff --git a/tests/samples/adw_alertdialog_responses.blp b/tests/samples/adw_alertdialog_responses.blp new file mode 100644 index 0000000..6680aa6 --- /dev/null +++ b/tests/samples/adw_alertdialog_responses.blp @@ -0,0 +1,10 @@ +using Gtk 4.0; +using Adw 1; + +Adw.AlertDialog { + responses [ + cancel: _('Cancel'), + discard: _('Discard') destructive, + save: 'Save' suggested disabled, + ] +} \ No newline at end of file diff --git a/tests/samples/adw_alertdialog_responses.ui b/tests/samples/adw_alertdialog_responses.ui new file mode 100644 index 0000000..0418751 --- /dev/null +++ b/tests/samples/adw_alertdialog_responses.ui @@ -0,0 +1,16 @@ + + + + + + + Cancel + Discard + Save + + + \ No newline at end of file diff --git a/tests/samples/responses.blp b/tests/samples/adw_messagedialog_responses.blp similarity index 100% rename from tests/samples/responses.blp rename to tests/samples/adw_messagedialog_responses.blp diff --git a/tests/samples/responses.ui b/tests/samples/adw_messagedialog_responses.ui similarity index 100% rename from tests/samples/responses.ui rename to tests/samples/adw_messagedialog_responses.ui diff --git a/tests/test_samples.py b/tests/test_samples.py index 38b72d6..0a525ca 100644 --- a/tests/test_samples.py +++ b/tests/test_samples.py @@ -44,6 +44,23 @@ class TestSamples(unittest.TestCase): super().__init__(*args, **kwargs) self.maxDiff = None + self.have_adw_1_4 = False + self.have_adw_1_5 = False + + try: + import gi + + gi.require_version("Adw", "1") + from gi.repository import Adw + + Adw.init() + if Adw.MINOR_VERSION >= 4: + self.have_adw_1_4 = True + if Adw.MINOR_VERSION >= 5: + self.have_adw_1_5 = True + except: + pass + def assert_ast_doesnt_crash(self, text, tokens, ast): for i in range(len(text)): ast.get_docs(i) @@ -147,22 +164,6 @@ class TestSamples(unittest.TestCase): raise AssertionError() def test_samples(self): - have_adw = False - have_adw_1_4 = False - - try: - import gi - - gi.require_version("Adw", "1") - from gi.repository import Adw - - have_adw = True - Adw.init() - if Adw.MINOR_VERSION >= 4: - have_adw_1_4 = True - except: - pass - # list the samples directory samples = [ f.stem @@ -172,6 +173,7 @@ class TestSamples(unittest.TestCase): samples.sort() for sample in samples: REQUIRE_ADW_1_4 = ["adw_breakpoint"] + REQUIRE_ADW_1_5 = ["adw_alertdialog_responses"] SKIP_RUN = [ "expr_closure", @@ -189,7 +191,9 @@ class TestSamples(unittest.TestCase): "unchecked_class", ] - if sample in REQUIRE_ADW_1_4 and not have_adw_1_4: + if sample in REQUIRE_ADW_1_4 and not self.have_adw_1_4: + continue + if sample in REQUIRE_ADW_1_5 and not self.have_adw_1_5: continue with self.subTest(sample): @@ -202,8 +206,11 @@ class TestSamples(unittest.TestCase): sample_errors.sort() for sample_error in sample_errors: REQUIRE_ADW_1_4 = ["adw_breakpoint"] + REQUIRE_ADW_1_5 = ["adw_alert_dialog_duplicate_flags"] - if sample_error in REQUIRE_ADW_1_4 and not have_adw_1_4: + if sample_error in REQUIRE_ADW_1_4 and not self.have_adw_1_4: + continue + if sample_error in REQUIRE_ADW_1_5 and not self.have_adw_1_5: continue with self.subTest(sample_error): @@ -211,6 +218,9 @@ class TestSamples(unittest.TestCase): def test_decompiler(self): self.assert_decompile("accessibility_dec") + if self.have_adw_1_5: + self.assert_decompile("adw_alertdialog_responses") + self.assert_decompile("adw_messagedialog_responses") self.assert_decompile("child_type") self.assert_decompile("file_filter") self.assert_decompile("flags") @@ -220,7 +230,6 @@ class TestSamples(unittest.TestCase): self.assert_decompile("property") self.assert_decompile("property_binding_dec") self.assert_decompile("placeholder_dec") - self.assert_decompile("responses") self.assert_decompile("scale_marks") self.assert_decompile("signal") self.assert_decompile("strings_dec")