Add support for Adw.AlertDialog

This commit is contained in:
Sonny Piers 2024-01-28 02:03:29 +01:00
parent 6522421251
commit ba8b492134
13 changed files with 136 additions and 40 deletions

View file

@ -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 # v0.10.0
## Added ## Added

View file

@ -79,7 +79,12 @@ def get_available_namespaces() -> T.List[T.Tuple[str, str]]:
] ]
for search_path in search_paths: 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"): if filename.endswith(".typelib"):
namespace, version = filename.removesuffix(".typelib").rsplit("-", 1) namespace, version = filename.removesuffix(".typelib").rsplit("-", 1)
_available_namespaces.append((namespace, version)) _available_namespaces.append((namespace, version))

View file

@ -3,7 +3,7 @@ from .adw_breakpoint import (
AdwBreakpointSetter, AdwBreakpointSetter,
AdwBreakpointSetters, AdwBreakpointSetters,
) )
from .adw_message_dialog import ExtAdwMessageDialog from .adw_response_dialog import ExtAdwResponseDialog
from .attributes import BaseAttribute from .attributes import BaseAttribute
from .binding import Binding from .binding import Binding
from .common import * from .common import *
@ -60,7 +60,7 @@ OBJECT_CONTENT_HOOKS.children = [
AdwBreakpointCondition, AdwBreakpointCondition,
AdwBreakpointSetters, AdwBreakpointSetters,
ExtAccessibility, ExtAccessibility,
ExtAdwMessageDialog, ExtAdwResponseDialog,
ExtComboBoxItems, ExtComboBoxItems,
ext_file_filter_mime_types, ext_file_filter_mime_types,
ext_file_filter_patterns, ext_file_filter_patterns,

View file

@ -1,4 +1,4 @@
# adw_message_dialog.py # adw_response_dialog.py
# #
# Copyright 2023 James Westman <james@jwestman.net> # Copyright 2023 James Westman <james@jwestman.net>
# #
@ -25,7 +25,7 @@ from .gobject_object import ObjectContent, validate_parent_type
from .values import StringValue from .values import StringValue
class ExtAdwMessageDialogFlag(AstNode): class ExtAdwResponseDialogFlag(AstNode):
grammar = AnyOf( grammar = AnyOf(
UseExact("flag", "destructive"), UseExact("flag", "destructive"),
UseExact("flag", "suggested"), UseExact("flag", "suggested"),
@ -51,12 +51,12 @@ class ExtAdwMessageDialogFlag(AstNode):
) )
class ExtAdwMessageDialogResponse(AstNode): class ExtAdwResponseDialogResponse(AstNode):
grammar = [ grammar = [
UseIdent("id"), UseIdent("id"),
Match(":").expected(), Match(":").expected(),
to_parse_node(StringValue).expected("a string or translatable string"), to_parse_node(StringValue).expected("a string or translatable string"),
ZeroOrMore(ExtAdwMessageDialogFlag), ZeroOrMore(ExtAdwResponseDialogFlag),
] ]
@property @property
@ -64,8 +64,8 @@ class ExtAdwMessageDialogResponse(AstNode):
return self.tokens["id"] return self.tokens["id"]
@property @property
def flags(self) -> T.List[ExtAdwMessageDialogFlag]: def flags(self) -> T.List[ExtAdwResponseDialogFlag]:
return self.children[ExtAdwMessageDialogFlag] return self.children[ExtAdwResponseDialogFlag]
@property @property
def appearance(self) -> T.Optional[str]: def appearance(self) -> T.Optional[str]:
@ -106,16 +106,16 @@ class ExtAdwMessageDialogResponse(AstNode):
) )
class ExtAdwMessageDialog(AstNode): class ExtAdwResponseDialog(AstNode):
grammar = [ grammar = [
Keyword("responses"), Keyword("responses"),
Match("[").expected(), Match("[").expected(),
Delimited(ExtAdwMessageDialogResponse, ","), Delimited(ExtAdwResponseDialogResponse, ","),
"]", "]",
] ]
@property @property
def responses(self) -> T.List[ExtAdwMessageDialogResponse]: def responses(self) -> T.List[ExtAdwResponseDialogResponse]:
return self.children return self.children
@property @property
@ -128,8 +128,11 @@ class ExtAdwMessageDialog(AstNode):
) )
@validate("responses") @validate("responses")
def container_is_message_dialog(self): def container_is_message_dialog_or_alert_dialog(self):
try:
validate_parent_type(self, "Adw", "MessageDialog", "responses") validate_parent_type(self, "Adw", "MessageDialog", "responses")
except:
validate_parent_type(self, "Adw", "AlertDialog", "responses")
@validate("responses") @validate("responses")
def unique_in_parent(self): def unique_in_parent(self):
@ -141,7 +144,18 @@ class ExtAdwMessageDialog(AstNode):
applies_in_subclass=("Adw", "MessageDialog"), applies_in_subclass=("Adw", "MessageDialog"),
matches=new_statement_patterns, 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( yield Completion(
"responses", CompletionItemKind.Keyword, snippet="responses [\n\t$0\n]" "responses", CompletionItemKind.Keyword, snippet="responses [\n\t$0\n]"
) )

View file

@ -337,7 +337,7 @@ class XmlOutput(OutputFormat):
self._emit_attribute("property", "name", prop.name, prop.value, xml) self._emit_attribute("property", "name", prop.name, prop.value, xml)
xml.end_tag() xml.end_tag()
elif isinstance(extension, ExtAdwMessageDialog): elif isinstance(extension, ExtAdwResponseDialog):
xml.start_tag("responses") xml.start_tag("responses")
for response in extension.responses: for response in extension.responses:
xml.start_tag( xml.start_tag(

View file

@ -15,7 +15,7 @@ Properties are the main way to set values on objects, but they are limited by th
.. rst-class:: grammar-block .. rst-class:: grammar-block
Extension = :ref:`ExtAccessibility<Syntax ExtAccessibility>` Extension = :ref:`ExtAccessibility<Syntax ExtAccessibility>`
| :ref:`ExtAdwMessageDialog<Syntax ExtAdwMessageDialog>` | :ref:`ExtAdwResponseDialog<Syntax ExtAdwResponseDialog>`
| :ref:`ExtAdwBreakpoint<Syntax ExtAdwBreakpoint>` | :ref:`ExtAdwBreakpoint<Syntax ExtAdwBreakpoint>`
| :ref:`ExtComboBoxItems<Syntax ExtComboBoxItems>` | :ref:`ExtComboBoxItems<Syntax ExtComboBoxItems>`
| :ref:`ExtFileFilterMimeTypes<Syntax ExtFileFilter>` | :ref:`ExtFileFilterMimeTypes<Syntax ExtFileFilter>`
@ -63,16 +63,16 @@ Defines the condition for a breakpoint and the properties that will be set at th
The `Adw.Breakpoint:condition <https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/property.Breakpoint.condition.html>`_ property has type `Adw.BreakpointCondition <https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/struct.BreakpointCondition.html>`_, which GtkBuilder doesn't know how to parse from a string. Therefore, the ``condition`` syntax is used instead. The `Adw.Breakpoint:condition <https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/property.Breakpoint.condition.html>`_ property has type `Adw.BreakpointCondition <https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/struct.BreakpointCondition.html>`_, 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 Adw.MessageDialog Responses
---------------------------- ----------------------------
.. rst-class:: grammar-block .. rst-class:: grammar-block
ExtAdwMessageDialog = 'responses' '[' (ExtAdwMessageDialogResponse),* ']' ExtAdwResponseDialog = 'responses' '[' (ExtAdwResponseDialogResponse),* ']'
ExtAdwMessageDialogResponse = <id::ref:`IDENT<Syntax IDENT>`> ':' :ref:`StringValue<Syntax StringValue>` ExtAdwMessageDialogFlag* ExtAdwResponseDialogResponse = <id::ref:`IDENT<Syntax IDENT>`> ':' :ref:`StringValue<Syntax StringValue>` ExtAdwResponseDialogFlag*
ExtAdwMessageDialogFlag = 'destructive' | 'suggested' | 'disabled' ExtAdwResponseDialogFlag = 'destructive' | 'suggested' | 'disabled'
Valid in `Adw.MessageDialog <https://gnome.pages.gitlab.gnome.org/libadwaita/doc/1-latest/class.MessageDialog.html>`_. Valid in `Adw.MessageDialog <https://gnome.pages.gitlab.gnome.org/libadwaita/doc/1-latest/class.MessageDialog.html>`_.
@ -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 = <id::ref:`IDENT<Syntax IDENT>`> ':' :ref:`StringValue<Syntax StringValue>` ExtAdwAlertDialogFlag*
ExtAdwAlertDialogFlag = 'destructive' | 'suggested' | 'disabled'
Valid in `Adw.AlertDialog <https://gnome.pages.gitlab.gnome.org/libadwaita/doc/1-latest/class.AlertDialog.html>`_.
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: .. _Syntax ExtComboBoxItems:
Gtk.ComboBoxText Items Gtk.ComboBoxText Items

View file

@ -0,0 +1,9 @@
using Gtk 4.0;
using Adw 1;
Adw.AlertDialog {
responses [
cancel: _("Cancel") disabled disabled,
ok: _("Ok") destructive suggested,
]
}

View file

@ -0,0 +1,2 @@
6,34,8,Duplicate 'disabled' flag
7,29,9,'suggested' and 'destructive' are exclusive

View file

@ -0,0 +1,10 @@
using Gtk 4.0;
using Adw 1;
Adw.AlertDialog {
responses [
cancel: _('Cancel'),
discard: _('Discard') destructive,
save: 'Save' suggested disabled,
]
}

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
DO NOT EDIT!
This file was @generated by blueprint-compiler. Instead, edit the
corresponding .blp file and regenerate this file with blueprint-compiler.
-->
<interface>
<requires lib="gtk" version="4.0"/>
<object class="AdwAlertDialog">
<responses>
<response id="cancel" translatable="true">Cancel</response>
<response id="discard" translatable="true" appearance="destructive">Discard</response>
<response id="save" enabled="false" appearance="suggested">Save</response>
</responses>
</object>
</interface>

View file

@ -44,6 +44,23 @@ class TestSamples(unittest.TestCase):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.maxDiff = None 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): def assert_ast_doesnt_crash(self, text, tokens, ast):
for i in range(len(text)): for i in range(len(text)):
ast.get_docs(i) ast.get_docs(i)
@ -147,22 +164,6 @@ class TestSamples(unittest.TestCase):
raise AssertionError() raise AssertionError()
def test_samples(self): 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 # list the samples directory
samples = [ samples = [
f.stem f.stem
@ -172,6 +173,7 @@ class TestSamples(unittest.TestCase):
samples.sort() samples.sort()
for sample in samples: for sample in samples:
REQUIRE_ADW_1_4 = ["adw_breakpoint"] REQUIRE_ADW_1_4 = ["adw_breakpoint"]
REQUIRE_ADW_1_5 = ["adw_alertdialog_responses"]
SKIP_RUN = [ SKIP_RUN = [
"expr_closure", "expr_closure",
@ -189,7 +191,9 @@ class TestSamples(unittest.TestCase):
"unchecked_class", "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 continue
with self.subTest(sample): with self.subTest(sample):
@ -202,8 +206,11 @@ class TestSamples(unittest.TestCase):
sample_errors.sort() sample_errors.sort()
for sample_error in sample_errors: for sample_error in sample_errors:
REQUIRE_ADW_1_4 = ["adw_breakpoint"] 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 continue
with self.subTest(sample_error): with self.subTest(sample_error):
@ -211,6 +218,9 @@ class TestSamples(unittest.TestCase):
def test_decompiler(self): def test_decompiler(self):
self.assert_decompile("accessibility_dec") 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("child_type")
self.assert_decompile("file_filter") self.assert_decompile("file_filter")
self.assert_decompile("flags") self.assert_decompile("flags")
@ -220,7 +230,6 @@ class TestSamples(unittest.TestCase):
self.assert_decompile("property") self.assert_decompile("property")
self.assert_decompile("property_binding_dec") self.assert_decompile("property_binding_dec")
self.assert_decompile("placeholder_dec") self.assert_decompile("placeholder_dec")
self.assert_decompile("responses")
self.assert_decompile("scale_marks") self.assert_decompile("scale_marks")
self.assert_decompile("signal") self.assert_decompile("signal")
self.assert_decompile("strings_dec") self.assert_decompile("strings_dec")