Moving On
Here, we shall look at the various types of data types supported by Vala, and extend Hello World a bit more.
Vala supports three kinds of data types: value types, reference types,z
and meta types. Value types include simple types (e.g. char
, int
, and
float
), enum types, and struct types. Reference types include object types,
array types, delegate types, and error types. Meta types are created from other
types, and so may have either reference or value type semantics. They include
parametrized types, nullable types, and pointer types
Value Types
Value types differ from reference types in that instances of value types are stored directly in variables or fields that represent them. Whenever a value type instance is assigned to another variable or field, the default action is to duplicate the value, such that each identifier refers to a unique copy of the data, over which it has ownership. When a value type is instantiated in a method, the instance is created on the stack.
Value types include the boolean type, integral types, the floating-point types, and enumerated types.
The boolean type, bool
, can have values of true
or false
.
Integral types can contain only integers. They are either signed or unsigned,
each of which is considered a different type, though it is possible to cast
between them when needed. Some types define exactly how many bits of storage are
used to represent the integer e.g. uint8
, int64
, etc. Others depend on the
environment, for example long
, int
, short
map to C data types and therefore
depend on the machine architecture. A char
is 1 byte wide and can represent
one of 256 values. A unichar
is 4 bytes wide, i.e. large enough to store any
UTF-8 character.
Floating point types are used to represent contain irrational floating point
numbers in a fixed number of bits. There are two floating point types: float
and double
.
An enumerated type is one in which all possible values that instances of the type can hold are declared with the type.
Reference Types
Variables of reference types contain references to the instances, rather than the instances themselves. Assinging an instance of a reference type to a variable or field will not make a copy of the data, instead only the reference to the data is copied. This means that both variables will refer to the same data, and so changes made to that data using one of the references will be visible when using the other. Instances of reference types are always stored on the heap (the part of memory that is dynamically allocated during a program's run time).
When a variable that is an instance of a reference variable goes out of scope, the fact that a reference to the instance has been removed is also recorded. This means that a reference variable can be automatically removed from memory when it is no longer needed.
Reference types include classes, arrays, delegates, errors and strings.
A class definition introduces a new reference type - this is the most common way of creating a new type in Vala. A class is definition of a new data type. A class can contain fields, constants, methods, properties, and signals. Class types support inheritance, a mechanism whereby a derived class can extend and specialize a base class. Vala supports three different types of classes, namely:
-
GObject subclasses, which inherit directly from
GLib.Object
, and are the most powerful type of class. -
Fundamental GType classes are those either without any superclass or that don't inherit at any level from
GLib.Object
. These classes support inheritence, interfaces, virtual methods, reference counting, unmanaged properties, and private fields. They are instantiated faster than GObject subclasses but are less powerful. -
Compact classes, so called because they use less memory per instance, are the least featured of all class types. They are not registered with the GType system and do not support reference counting, virtual methods, or private fields. Such classes are very fast to instantiate but not massively useful except when dealing with existing libraries. They are declared using the
Compact
attribute on the class.
An array is a data structure that can contains zero or more elements of the same type, up to a limit defined by the type.
A delegate is a data structure that refers to a method. A method executes in a given scope which is also stored, meaning that for instance methods a delegate will contain also a reference to the instance.
Instances of error types represent recoverable runtime errors. All errors are described using error domains, a type of enumerated value, but errors themselves are not enumerated types.
Vala has built in support for Unicode strings, via the fundamental string
type. This is the only fundamental type that is a reference type. Like other
fundamental types, it can be instantiated with a literal expression. Strings are
UTF-8 encoded which means that they cannot be accessed like character arrays in
C since it is not guaranteed that each Unicode character will be stored in just
one byte. Instead, the string fundamental struct type (which all strings are
instances of) provides access methods along with other tools.
Meta Types
-
Parameterized Types
Vala allows definitions of types that can be customised at runtime with type parameters. For example, a list can be defined so that it can be instantiated as a list of ints, a list of Objects, etc. This is achieved using generic declarations.
-
Pointer types
The name of a type can be used to implicitly create a pointer type related to that type. The value of a variable declared as being of type
T*
represents the memory address of an instance of typeT
. The instance is never made aware that its address has been recorded, and so cannot record the fact that it is referred to in this way.Instances of any type can be assigned to a variable that is declared to be a pointer to an instance of that type. For referenced types, direct assignment is allowed in either direction. For value types the pointer-to operator
&
is required to assign to a pointer, and the pointer-indirection operator*
is used to access the instance pointed to.The
void*
type represents a pointer to an unknown type. As the referred type is unknown, the indirection operator cannot be applied to a pointer of typevoid*
, nor can any arithmetic be performed on such a pointer. However, a pointer of typevoid*
can be cast to any other pointer type (and vice-versa) and compared to values of other pointer types. -
Nullable Types
There is another characterization of types, nullable types. The name of a type can be used to implicitly create a nullable type related to that type. An instance of a nullable type
T?
can either be a value of typeT
ornull
. A nullable type will have either value or reference type semantics, depending on the type it is based on.
An Upgraded Hello World
Let us now take a look at a slightly improved helloworld
with better examples
of callbacks. This will also introduce us to our next topic, packing widgets.
class HelloWorld : Gtk.Window {
private Gtk.Button button1;
private Gtk.Button button2;
private Gtk.Box box;
/* Our new improved callback. The data passed to this function
* is printed to stdout. */
void callback(string data) {
stdout.printf("Hello! - %s was pressed\n", data);
}
/* another callback */
bool on_delete_event() {
Gtk.main_quit();
return false;
}
public HelloWorld () {
/* This is a new call, which just sets the title of our
* new window to "Hello Buttons!" */
this.set_title("Hello Buttons!");
/* Here we just set a handler for delete_event that immediately
* exits GTK. */
this.delete_event.connect(this.on_delete_event);
/* Sets the border width of the window. */
this.set_border_width(10);
/* We create a box to pack widgets into. This is described
* in detail in the "packing" section. The box is not really
* visible, it is just used as a tool to arrange widgets. */
box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 0);
/* Put the box into the main window. */
this.add(box);
/* Creates a new button with the label "Button 1". */
this.button1 = new Gtk.Button.with_label("Button 1");
/* Now when the button is clicked, we call the "callback" function
* with a pointer to "button 1" as its argument */
this.button1.clicked.connect (() => { this.callback("Button 1"); });
/* Instead of gtk_container_add, we pack this button into the
* invisible box, which has been packed into the window. */
box.pack_start(button1, true, true, 0);
/* Always remember this step, this tells GTK that our preparation
* for this button is complete, and it can now be displayed. */
button1.show();
/* Do these same steps again to create a second button */
this.button2 = new Gtk.Button.with_label("Button 2");
/* Call the same callback function with a different argument,
passing a pointer to "button 2" instead. */
this.button2.clicked.connect (() => { this.callback("Button 2"); });
box.pack_start(button2, true, true, 0);
/* The order in which we show the buttons is not really important,
* but we recommend showing the window last, so it all pops up at
* once. */
button2.show();
box.show();
}
public static int main (string[] args) {
/* This is called in all GTK applications. Arguments are parsed
* from the command line and are returned to the application. */
Gtk.init (ref args);
var hello = new HelloWorld();
hello.show();
/* Rest in gtk_main and wait for the fun to begin! */
Gtk.main();
return 0;
}
}
Compiling and running the code produces the window below, "Upgraded Hello World Example".
You'll notice this time there is no way to exit the program except to use your
window manager or command line to kill it. A good exercise for the reader would
be to insert a third "Quit" button that will exit the program. You may also wish
to play with the options to pack_start()
while reading the next section. Try
resizing the window, and observe the behavior.
A short commentary on the code differences from the first Hello World program is in order.
As noted above there is no "destroy" event handler in the upgraded Hello World.
The lines
void callback(string data) {
stdout.printf("Hello! - %s was pressed\n", data);
}
define a callback method which is similar to the hello()
callback
in the first helloworld. The difference is that the callback prints a message
including data passed in.
The line
this.set_title("Hello Buttons!");
sets a title string to be used on the titlebar of the window, as seen in the screenshot above.
The line
box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 0);
creates a horizontal box (Gtk.Box
) to hold the two buttons that are
created in the lines
this.button1 = new Gtk.Button.with_label("Button 1");
this.button2 = new Gtk.Button.with_label("Button 2");
The line
this.window.add(box);
adds the horizontal box to the window container.
The lines
this.button1.clicked.connect (() => { this.callback("Button 1"); });
this.button2.clicked.connect (() => { this.callback("Button 2"); });
connect the callback()
method to the "clicked" signal of the buttons. Each
button sets up a different string to be passed to the callback()
method when
invoked.
The lines
box.pack_start(button1, true, true, 0);
box.pack_start(button2, true, true, 0);
pack the buttons into the horizontal box. The lines
button1.show();
button2.show();
ask GTK to display the buttons.
The lines
box.show();
ask GTK to display the box and the window respectively.
The window is shown by the line
hello.show();
in main()
.
References and Further Reading
-
The GTK+ Tutorial: Getting Started. [Online] Available from: https://developer.gnome.org/gtk-tutorial/2.90/c39.html [Accessed 16 September 2014]
-
The GTK 3 Reference Manual. [Online] Available from: https://developer.gnome.org/gtk3/stable/ [Accessed 9 November 2014]
-
The Vala Manual (draft) [Online] Available from: http://www.vala-project.org/doc/vala-draft/types.html [Accessed 9 November 2014]
-
Vala Documentation: Signals and Callbacks. [Online] Available from: https://wiki.gnome.org/Projects/Vala/SignalsAndCallbacks [Accessed 16 September 2014]
-
Valadoc (Vala online package binding reference documentation) [Online] Available from: http://valadoc.org/#!api=gobject-2.0/GLib.SignalHandler [Accessed 16 September 2014]