From 764ad1b815ac0dcf9ec41337c5b10d45f2636d38 Mon Sep 17 00:00:00 2001 From: Jan-Willem Harmannij Date: Sat, 11 Jan 2025 17:05:18 +0100 Subject: [PATCH] Documentation updates --- docs/advanced.md | 42 ++++++++++++++++++----- docs/register.md | 86 +++++++++++++++++++++++++++++------------------- docs/usage.md | 6 ++-- 3 files changed, 89 insertions(+), 45 deletions(-) diff --git a/docs/advanced.md b/docs/advanced.md index 8a663089..fc32dad9 100644 --- a/docs/advanced.md +++ b/docs/advanced.md @@ -5,13 +5,13 @@ In most cases, memory management of native resources is automatically taken care of. Java-GI uses GObject toggle references to dispose the native object when the Java instance is garbage-collected, and manages all memory allocations for marshaling of string, array and struct parameters. ### Allocating structs with an Arena -Struct definitions (in contrast to GObjects) in native code are mapped to Java classes. +Struct and union definitions (in contrast to GObjects) in native code are mapped to Java classes. -Because structs don't necessarily have a constructor method, the Java classes offer two constructor to allocate a new struct: +Because structs don't necessarily have a constructor method, the Java classes offer two constructors to allocate a new struct: - A constructor without parameters, that will allocate an uninitialized structs - A constructor with parameters to set all struct members to an initial value -The constructors optionally take an `Arena` to allocate memory for the new instances in native memory. Users can choose between different arenas in OpenJDK, depending on the use case. All constructors generated by Java-GI for record (struct) and union types take an optional Arena parameter, defaulting to `Arena.ofAuto()`. Some examples: +The constructors optionally take an `Arena` to specify how to allocate memory for the new instances in native memory. Users can choose between different arenas in OpenJDK, depending on the use case. The constructors generated by Java-GI default to `Arena.ofAuto()`. Some examples: === "Java" @@ -52,7 +52,7 @@ The constructors optionally take an `Arena` to allocate memory for the new insta ``` !!! warning - The Java garbage collector does not know about the native resources, and might wait an indefinite amount of time before the objects are effectively disposed. Therefore, the default `Arena.ofAuto()` can lead to excessive memory usage. When allocating and discarding many struct instances in a tight loop, use `try-with-resources` to explicitly manage the memory allocations. + The Java garbage collector does not know about the native resources, and might wait an indefinite amount of time before the objects are effectively disposed. Therefore, the default `Arena.ofAuto()` can lead to excessive memory usage. When allocating and discarding many struct instances in a tight loop, use `Arena.ofConfined()` or `Arena.ofShared()` with a `try {}` block to explicitly manage the memory allocations. Read the [OpenJDK Arena class documentation](https://docs.oracle.com/en/java/javase/22/docs/api/java.base/java/lang/foreign/Arena.html) to learn how to use arenas to manage the lifecycle of native memory segments. ## Builder pattern @@ -83,6 +83,28 @@ You can construct an object with GObject properties using a Builder pattern. For With a `Builder` you can set the properties of the class, its parents, and all implemented interfaces. Behind the scenes, this will call `g_object_new_with_properties()`. +Builders also offer `on...()` methods to connect signals: + +=== "Java" + + ```java + var button = Button.builder() + .setLabel("Close window") + .onClicked(window::close) + .build() + ``` + +=== "Kotlin" + + ```kotlin + val button = Button.builder() + .setLabel("Close window") + .onClicked(window::close) + .build() + ``` + +This can result in cleaner-looking code, but the signals cannot be managed and disconnected. To properly connect and disconnect signals, use the `SignalHandler` object that is returned by the `on...()` methods on the created instances. You should only connect signals from a builder when they don't need to be disconnected later. + ## Exceptions `GError**` parameters are mapped to Java `GErrorException`s. @@ -117,7 +139,7 @@ Nullability of parameters (as defined in the GObject-introspection attributes) i Java-GI copies all array parameters into a newly allocated array in native memory. When an array is returned, the contents are copied back into a Java array. This impacts performance, so keep this in mind when working with arrays in a tight loop. -C functions that work with arrays, often expect the array length as an additional parameter. In the corresponding Java methods, that parameter is omitted, because Java-GI will set it automatically. +C functions that work with arrays, often expect the length as an additional parameter. In the corresponding Java methods, that parameter is omitted, because Java-GI will set it automatically. ## Out-parameters @@ -157,18 +179,18 @@ Enumerations and flags (bitfields) are available as Java enums. To combine multi entry.inputHints = setOf(InputHints.WORD_COMPLETION, InputHints.NO_SPELLCHECK) ``` -When you just want to set a single flag, you can omit `Set.of()` and pass the flag directly. All methods with flag parameters are overloaded for this purpose: +When the flags are the last parameter, you can omit `Set.of()` and pass the flags directly als variadic arguments. All methods with flags as the last parameter are overloaded for this purpose: === "Java" ```java - entry.setInputHints(InputHints.NO_EMOJI); + entry.setInputHints(InputHints.WORD_COMPLETION, InputHints.NO_EMOJI); ``` === "Kotlin" ```kotlin - entry.setInputHints(InputHints.NO_EMOJI) + entry.setInputHints(InputHints.WORD_COMPLETION, InputHints.NO_EMOJI) ``` @@ -238,6 +260,8 @@ For every signal, a method to connect (e.g. `onClicked`) and emit the signal (`e Functions with callback parameters are supported too. The generated Java bindings contain `@FunctionalInterface` definitions for all callback functions to ensure type safety. +To learn about creating your own signals, read [Registering new types](register.md). + ## Closures [Closures](https://docs.gtk.org/gobject/struct.Closure.html) can be marshaled to Java methods. Similar to the `CClosure` type in C code, Java-GI offers a [JavaClosure](https://jwharm.github.io/java-gi/javadoc/io/github/jwharm/javagi/gobject/JavaClosure.html). You can create a JavaClosure for a lambda fuction, functional interface or `java.lang.reflect.Method`, and then pass it to native code (for example, the last two parameters of [`GObject.bindPropertyFull()`](https://jwharm.github.io/java-gi/glib/org.gnome.glib/org/gnome/gobject/GObject.html#bindPropertyFull(java.lang.String,org.gnome.gobject.GObject,java.lang.String,org.gnome.gobject.BindingFlags,org.gnome.gobject.Closure,org.gnome.gobject.Closure))). @@ -260,7 +284,7 @@ To create a Gtk composite template class (coupled to a ui definition in XML or B A great tool for developing GNOME applications is GNOME Builder. Builder primarily supports C, JavaScript, Rust, Python and Vala applications, but you can use it to develop Java applications as well, if you have the Eclipse JDT language server (JDTLS) installed. !!! warning - In my limited experience this is still very unpolished and fragile. For any serieus work, by all means use a well-supported Java IDE. + In my limited experience this is still very unpolished and fragile. For any serious work, by all means use a well-supported Java IDE. 1. Install GNOME Builder. Preferably the newest release from Flathub, but at least version 44. 2. Download JDTLS [here](https://download.eclipse.org/jdtls/milestones/). Pick the latest milestone version and download the file `jdt-language-server-...-tar.gz`. diff --git a/docs/register.md b/docs/register.md index 224a2d41..8866ad5c 100644 --- a/docs/register.md +++ b/docs/register.md @@ -15,17 +15,15 @@ When you extend a Java class from an existing GObject-derived class, Java will t ``` -However, the GObject type system itself will not recognize it as its own class. Therefore, you need to register your class as a new GType. To do this, Java-GI offers an easy-to-use wrapper function: `Types.register(classname)`. This will use reflection to determine the name, parent class, implemented interfaces and overridden methods, and will register it as a new GType. +However, the GObject type system itself will not recognize it as its own class. To do that, you need to register your class as a new GType. Java-GI offers an easy-to-use function to achieve this: `Types.register(classname)`. This will use reflection to determine the name, parent class, implemented interfaces and overridden methods, and will register it as a new GType. -It is recommended to register the new gtype in a static field `gtype` like this: +It is recommended to register the new gtype in a static block like this: === "Java" ```java - private static final Type gtype = Types.register(MyObject.class); - - public static Type getType() { - return gtype; + { + Types.register(MyObject.class); } ``` @@ -33,27 +31,28 @@ It is recommended to register the new gtype in a static field `gtype` like this: ```kotlin companion object { - val gtype: Type = Types.register(MyObject::class.java) + init { + Types.register(MyObject::class.java) + } } ``` +By using a static initializer, the GObject type will be registered immediately when the JVM classloader initializes the Java class. Similarly, you can register all your classes in your `main()` method. -By declaring the `gtype` as a static field in this way, it will be registered immediately when the JVM classloader initializes the Java class. - -When instantiating a new instance of the object, pass the `gtype` to `GObject::new()`: +When instantiating a new instance of the object, pass the class to `GObject::new()`: === "Java" ```java public MyObject() { - super(gtype, null); + super(MyObject.class); } ``` === "Kotlin" ```kotlin - constructor() : super(gtype, null) + constructor() : super(MyObject::class.java) ``` Alternatively, create a static factory method with a descriptive name like `create` or `newInstance` that calls `GObject::newInstance()`: @@ -62,7 +61,7 @@ Alternatively, create a static factory method with a descriptive name like `crea ```java public static MyObject create() { - return GObject.newInstance(gtype); + return GObject.newInstance(MyObject.class); } ``` @@ -73,14 +72,14 @@ Alternatively, create a static factory method with a descriptive name like `crea val gtype: Type = ... fun create(): MyObject { - return newInstance(gtype) + return newInstance(MyObject::class.java) } } ``` Now, when you call `MyObject.create()`, you will have a Java object that is also instantiated as a native GObject instance. -If your class contains GObject class or instance initializer method (see below), the constructor **must** be a static factory method; a regular constructor that calls `super(gtype, null)` **will not work** correctly with GObject initializers. +If your class contains GObject class or instance initializer method (see below), the constructor **must** be a static factory method; a regular constructor that calls `super(MyObject.class)` **will not work** correctly with GObject initializers. Finally, add the default memory-address-constructor for Java-GI Proxy objects: @@ -100,7 +99,7 @@ Finally, add the default memory-address-constructor for Java-GI Proxy objects: } ``` -Ignore warnings that the constructor appears unused: This constructor should exist in all Java-GI proxy classes. It enables a Java class to be instantiated automatically for new instances returned from native function calls. +Ignore warnings that the constructor appears unused: This constructor should exist in all Java-GI proxy classes. It enables a Java object to be instantiated automatically for GObject instances returned from native function calls. If your Java application is module-based, you must export your package to the `org.gnome.gobject` module in your `module-info.java` file, to allow the reflection to work: @@ -130,7 +129,9 @@ A GType has a unique name, like 'GtkLabel', 'GstObject' or 'GListModel'. (You ca ... ``` -If you don't intend to override the name of the GType, you can safely omit the `@RegisteredType` annotation. +To prefix all type names in a package with a shared namespace identifier, use the `@Namespace(name="...")` annotation in your `package-info.java` file. + +If you don't intend to override the name of the GType, you can safely omit the `@RegisteredType` and `@Namespace` annotations. ## Method overrides @@ -144,7 +145,9 @@ When a virtual method is not available as a regular instance method, you can saf ## Properties -You can define GObject properties with the `@Property` annotation on the getter and setter methods. You must annotate both the getter and setter methods (if applicable). All `@Property` annotation parameters are optional. +When your class contains getter and setter method pairs, Java-GI will register them as GObject properties. For boolean properties, you can also use `isFoo()`/`setFoo()` pairs. Kotlin creates get- and set-methods for Kotlin properties automatically, so in practice a Kotlin property will be registered as a GObject property. + +You can define GObject properties with the `@Property` annotation on other methods besides getter/setter pairs, or when you want to change the property name or change other parameters. When you use `@Property`, you must annotate both the getter and setter methods (if applicable). Example definition of an `int` property with name `n-items`: @@ -153,12 +156,10 @@ Example definition of an `int` property with name `n-items`: ```java private int size = 0; - @Property public int getNItems() { return size; } - @Property public void setNItems(int nItems) { size = nItems; } @@ -167,28 +168,45 @@ Example definition of an `int` property with name `n-items`: === "Kotlin" ```kotlin - var size: Int = 0 - @Property get - @Property set + var nItems: Int = 0 ``` +### Property annotation parameters + +The `@Property` annotation can be used to set a property name and other attributes. It can also be used to mark methods as properties that don't conform to the getter/setter convention. Finally, a `@Property(skip=true)` annotation can be used to prevent getter/setter methods getting registered as a GObject property. + +For properties with a getter and setter method, either both or neither methods must be annotated with `@Property` (or else they will not be recognized by Java-GI as a pair). The annotation parameters must be specified on the getter. They can be specified on the setter too, but only the parameters on the getter are actually used. + The `@Property` annotation accepts the following parameters: -| Parameter | Type | Default value | -|----------------|-----------|---------------| -| name | String | inferred | -| type | ParamSpec | inferred | -| readable | Boolean | true | -| writable | Boolean | true | -| construct | Boolean | false | -| constructOnly | Boolean | false | -| explicitNotify | Boolean | false | -| deprecated | Boolean | false | +| Parameter | Type | Default value | +|----------------|-----------|-----------------| +| name | String | inferred | +| type | ParamSpec | inferred | +| readable | Boolean | true | +| writable | Boolean | true | +| construct | Boolean | false | +| constructOnly | Boolean | false | +| explicitNotify | Boolean | false | +| deprecated | Boolean | false | +| minimumValue | String | type-dependent | +| maximumValue | String | type-dependent | +| defaultValue | String | type-dependent | +| skip | Boolean | false | + +All `@Property` annotation parameters are optional. When the name is not specified, it will be inferred from the name of the method (provided that the method names follow the `getX()`/`setX(...)` pattern), stripping the "get" or "set" prefix and converting CamelCase to kebab-case. If you do specify a name, it must be present on **both** the getter and setter methods (otherwise Java-GI will create two properties, with different names). When the type is not specified, it will be inferred from the parameter or return-type of the method. When the type is specified, it must be one of the subclasses of `GParamSpec`. The boolean parameters are `GParamFlags` arguments, and are documented [here](https://docs.gtk.org/gobject/flags.ParamFlags.html). +The `@Property` parameters `minimumValue`, `maximumValue` and `defaultValue` expect String values. They are transformed by Java-GI to the proper type using `Boolean.parseBoolean()`, `Integer.parseInt()` etcetera. Using these three parameters, you can set the minimum, maximum and default values on the `GParamSpec` of a property that was defined in Java. + +* The minimum and maximum values are not enforced by Java-GI, so on the Java side the benefits are negligible. In most cases it is advisable to implement minimum and maximum property values in Java itself. +* The default value is returned by Java-GI for properties that have only a setter method in Java. + +When the `skip` parameter is set, the method will *not* be registered as a GObject property. + ## Class and instance init functions To implement a custom class initializer or instance initializer function, use the `@ClassInit` and `@InstanceInit` attributes: @@ -229,6 +247,8 @@ To implement a custom class initializer or instance initializer function, use th } ``` +Be aware that the instance initializer will only run for objects constructed with a static factory method. A regular constructor that calls `super(MyObject.class)` will not work. + ## Signals You can define custom signals in Java classes that extend GObject. For example: diff --git a/docs/usage.md b/docs/usage.md index 76cf2c68..0cf2909f 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -91,7 +91,7 @@ An example Gtk application with a "Hello world" button can be created as follows } public HelloWorld(String[] args) { - var app = new Application("my.example.HelloApp", ApplicationFlags.DEFAULT_FLAGS); + var app = new Application("my.example.HelloApp"); app.onActivate(() -> activate(app)); app.run(args); } @@ -126,7 +126,7 @@ An example Gtk application with a "Hello world" button can be created as follows import org.gnome.gtk.* fun main(args: Array) { - val app = Application("my.example.HelloApp", ApplicationFlags.DEFAULT_FLAGS) + val app = Application("my.example.HelloApp") app.onActivate { activate(app) } app.run(args) } @@ -180,7 +180,7 @@ An example Gtk application with a "Hello world" button can be created as follows object HelloWorld { def main(args: Array[String]) = { - val app = Application("my.example.HelloApp", ApplicationFlags.DEFAULT_FLAGS) + val app = Application("my.example.HelloApp") app.onActivate(() => HelloWorld().activate(app)) app.run(args); ()