A little bit more than a simple "Hello world" application - write an image viewer in GTK. Includes an introduction to the JavaScript language.
Jonh Wendell
jwendell@gnome.org
Johannes Schmid
jhs@gnome.org

# Visor de imágenes

      In this tutorial, we're going to write a very simple GTK application that loads and displays an image file. You will learn how to:

        Write a basic GTK user interface in JavaScript
        Deal with events by connecting signals to signal handlers
        Lay out GTK user interfaces using containers
        Cargar y mostrar archivos de imagen

      You'll need the following to be able to follow this tutorial:

        An installed copy of the Anjuta IDE
        An installed copy of the gjs interpreter
        Basic knowledge of any object-orientated programming language

# Crear un proyecto en Anjuta

Antes de empezar a programar, deberá configurar un proyecto nuevo en Anjuta. Esto creará todos los archivos que necesite para construir y ejecutar el código más adelante. También es útil para mantener todo ordenado.

Inicie Anjuta y pulse Archivo Nuevo Proyecto para abrir el asistente de proyectos.

Choose Generic Javascript from the JS tab, click Forward, and fill out your details on the next few pages. Use image-viewer as project name and directory.

Click Apply and the project will be created for you. Open src/main.js from the Project or File tabs. It contains very basic example code.

# JavaScript basics: Hello World

Before we start writing the image viewer, let's find out more about the way JavaScript is used in GNOME. Of course, your very first contact with any programming language should be the Hello World program which can already be found in main.js:

print ("¡Hola mundo!");

This should look quite natural if you're familiar with almost any other programming language. The function `print` is called with the argument `"Hello world!"`, which will be printed on the screen. Note that each line of code ends with a semicolon.

# Clases en JavaScript

This is the standard way to define a class in JavaScript:

function MyClass () { this._init (); }

MyClass.prototype = {

_init: function () { this.propertyA = "This is an object's field"; this.propertyB = 10; },

aMethod: function (arg1, arg2) { print ("inside aMethod: " + arg1 + " " + arg2); },

dumpProperties: function () { print (this.propertyA); print (this.propertyB); }

}

This defines a class called `MyClass`. Let's go through each part of the class definition:

`function MyClass` es el constructor de la clase; su nombre debe coincidir con el de la clase. Puede acceder a cualquier miembro de la clase usando el objeto `this`; aquí, el constructor llama al método `_init` de la clase.

The `MyClass.prototype` block is where you define the structure of the class. Each class is made up of methods (functions) and fields (variables); there are three methods and two fields in this example.

The first method defined here is called `_init`, and we specify that it is a function with no arguments:

_init: function ()

We write the function inside some curly braces. Two fields are defined here, `propertyA` and `propertyB`. The first is set to a string and the second is set to an integer (10). The function doesn't return any value.

The next method is called `aMethod` and has two arguments, which it prints out when you call it. The final method is `dumpProperties`, and prints the fields `propertyA` and `propertyB`.

Note how the class definition (prototype) is arranged; each function definition is separated by a comma.

Now that MyClass has been defined, we can play with it:

var o = new MyClass (); o.aMethod ("Hello", "world"); o.propertyA = "Just changed its value!"; o.dumpProperties ();

Este código crear una instancia nueva de la clase llamada `o`, ejecuta `aMethod`, cambia `propertyA` a una cadena diferente y entonces llama a `dumpProperties` (que muestra los campos).

Save the code in the main.js and then run it by using Run Execute from the menu or using the toolbar.

# Una primera aplicación en GTK

Let's see what a very basic Gtk application looks like in JavaScript:

const Gtk = imports.gi.Gtk;

Gtk.init (0, null);

var w = new Gtk.Window ({title: "Image Viewer Demo"}); w.show ();

Gtk.main ();

Let's take a look at what's happening:

The first line imports the Gtk namespace (that is, it includes the Gtk library). The libraries are provided by GObject Introspection (gi), which provides language bindings for many GNOME libraries.

`Gtk.init` inicializa la biblioteca GTK; esta sentencia es obligatoria para todos los programas GTK.

La siguiente línea crea la ventana principal creando un objeto `Gtk.Window`. Puede pasar varias propiedades al constructor de la ventana usando la sintaxis `{property: value, property: value, ...}`. En este caso, se está estableciendo el título de la ventana.

The next line explicitly shows the window. In Gtk, every widget is hidden by default.

Finalmente, `Gtk.main` ejecuta el bucle principal; en otras palabras, ejecuta el programa. El bucle principal escucha los eventos (señales) de la interfaz del usuario y llama a un manejador de la señal que hará algo útil. En breve aprenderá más cosas sobre las señales.

Save the code in main.js and run it. You will notice that the application does not quit when you close the window. This is because we haven't set up a signal handler to deal with the window's `destroy` (close) signal yet. We'll do this shortly, but for now you can just hit Ctrl C in the terminal window to quit the program.

# Añadir clases

The proper way of doing Gtk programming is by using classes. Let's rewrite the simple code you just wrote using classes:

const Gtk = imports.gi.Gtk;

function ImageViewer () { this._init (); }

ImageViewer.prototype = { _init: function () { this.window = new Gtk.Window ({title: "Image Viewer Demo"}); this.window.show (); } }

Gtk.init (0, null); var iv = new ImageViewer (); Gtk.main ();

Notice that the program is the same; we just moved the window creation code to our own `ImageViewer` class. The class's constructor calls the `_init` method, which creates and shows the window. We then create an instance of the class before running the main loop (`Gtk.main`).

This code is modular and can be split into multiple files easily. This makes it cleaner and easier to read.

# Señales

Signals are one of the key concepts in Gtk programming. Whenever something happens to an object, it emits a signal; for example, when a button is clicked it gives off the `clicked` signal. If you want your program to do something when that event occurs, you must connect a function (a "signal handler") to that signal. Here's an example:

function button_clicked () { print ("you clicked me!"); } var b = new Gtk.Button ({label:"Click me"}); b.connect ("clicked", button_clicked);

The last two lines create a `Gtk.Button` called `b` and connect its `clicked` signal to the `button_clicked` function, which is defined above. Every time the button is clicked, the code in the `button_clicked` function will be executed. It just prints a message here.

The syntax for connecting any signal to a function is:

object.connect (<signal_name>, <function_to_be_called>);

You can find signal definitions for any object in the GTK class reference.

Puede simplificar el código usando una definición de función en línea:

`b.connect ("clicked", function () { print ("you clicked me!"); });`

# Cerrar la ventana

Cuando cierra una ventana de GTK, realmente no se cierra, se oculta. Esto le permite mantener la ventana (lo que es útil si quiere preguntar al usuario si realmente quiere cerrar la ventana, por ejemplo).

In our case, we really do just want to close the window. The simplest way of doing this is by connecting the `hide` signal of the GtkWindow object to a function that closes the application. Go back to the image-viewer.js file and add the following code to the `_init` method, on the line above `this.window.show`:

this.window.connect ("hide", Gtk.main_quit);

Esto conecta la señal `hide` de la ventana con la función `main_quit` de GTK, que termina la ejecución del bucle principal de GTK. Una vez que el bucle principal termina, la función `Gtk.main` retorna. El programa puede continuar ejecutando cualquier código que haya después de la línea `Gtk.main ();`, pero como no hay ningún código después de ese punto, el programa simplemente termina.

# Contenedores: diseñar la interfaz de usuario

Los widgets (controles, como botones y etiquetas) se pueden organizar en la ventana usando contenedores. Puede organizar el diseño mezclando diferentes tipos de contenedores, como cajas y rejillas.

A `Gtk.Window` is itself a type of container, but you can only put one widget directly into it. We would like to have two widgets, an image and a button, so we must put a "higher-capacity" container inside the window to hold the other widgets. A number of container types are available, but we will use a `Gtk.Box` here. A `Gtk.Box` can hold several widgets, organized horizontally or vertically. You can do more complicated layouts by putting several boxes inside another box and so on.

There is a graphical user interface designer called Glade integrated in Anjuta which makes UI design really easy. For this simple example, however, we will code everything manually.

Let's add the box and widgets to the window. Insert the following code into the `_init` method, immediately above the `this.window.show` line:

var main_box = new Gtk.Box ({orientation: Gtk.Orientation.VERTICAL, spacing: 0}); this.window.add (main_box);

The first line creates a `Gtk.Box` called `main_box` and sets two of its properties: the `orientation` is set to vertical (so widgets are arranged in a column), and the `spacing` between the widgets is set to 0 pixels. The next line then adds the newly-created `Gtk.Box` to the window.

So far the window only contains an empty `Gtk.Box`, and if you run the program now you will see no changes at all (the `Gtk.Box` is a transparent container, so you can't see that it's there).

# Empaquetado: añadir widgets al contenedor

Para añadir algunos widgets a la `Gtk.Box`, inserte el siguiente código justo debajo de la línea `this.window.add (main_box)`:

this.image = new Gtk.Image (); main_box.pack_start (this.image, true, true, 0);

The first line creates a new `Gtk.Image` called `image`, which will be used to display an image file. Then, the image widget is added (packed) into the `main_box` container using `Gtk.Box`'s `pack_start` method.

`pack_start` takes 4 arguments: the widget that is to be added to the `Gtk.Box` (`child`); whether the `Gtk.Box` should grow larger when the new widget is added (`expand`); whether the new widget should take up all of the extra space created if the `Gtk.Box` gets bigger (`fill`); and how much space there should be, in pixels, between the widget and its neighbors inside the `Gtk.Box` (`padding`).

Gtk containers (and widgets) dynamically expand to fill the available space, if you let them. You don't position widgets by giving them a precise x,y-coordinate location in the window; rather, they are positioned relative to one another. This makes handling window resizing much easier, and widgets should automatically take a sensible size in most situations.

Also note how the widgets are organized in a hierarchy. Once packed in the `Gtk.Box`, the `Gtk.Image` is considered a child of the `Gtk.Box`. This allows you to treat all of the children of a widget as a group; for example, you could hide the `Gtk.Box`, which would also hide all of its children at the same time.

Now insert these two lines, below the two you just added:

var open_button = new Gtk.Button ({label: "Open a picture..."}); main_box.pack_start (open_button, false, false, 0);

These lines are similar to the first two, but this time they create a `Gtk.Button` and add it to `main_box`. Notice that we are setting the `expand` argument (the second one) to `false` here, whereas it was set to `true` for the `Gtk.Image`. This will cause the image to take up all available space and the button to take only the space it needs. When you maximize the window, the button size will remain the same, but the image size will increase, taking up all of the rest of the window.

Finally, we must change the `this.window.show ();` line to read:

this.window.show_all ();

Esto mostrará el hijo de la ventana de GTK, y todos sus hijos, y los hijos de sus hijos, etcétera. (Recuerde que los widgets de GTK están ocultos de manera predeterminada.)

# Loading the image: Connecting to the button's `clicked` signal

When the user clicks on the Open button, a dialog should appear so that the user can choose a picture. Once chosen, the picture should be loaded and shown in the image widget.

The first step is to connect the `clicked` signal of the button to a signal handler function, which we call `_openClicked`. Put this code immediately after the `var open_button = new Gtk.Button` line where the button was created:

open_button.connect ("clicked", Lang.bind (this, this._openClicked));

We are using the Lang JavaScript helper here. It allows us to connect a class method to the signal, rather than a plain function (without a class) which we had used before for the window's `hide` signal. Don't worry about this for now, it's just a technical detail. For it to work, you also need to put the following line at the top of the file:

const Lang = imports.lang;

# Loading the image: Writing the signal's callback

Now we can create the `_openClicked()` method. Insert the following into the `ImageViewer.prototype` code block, after the `_init` method (and not forgetting the comma):

_openClicked: function () { var chooser = new Gtk.FileChooserDialog ({title: "Select an image", action: Gtk.FileChooserAction.OPEN, transient_for: this.window, modal: true}); chooser.add_button (Gtk.STOCK_CANCEL, 0); chooser.add_button (Gtk.STOCK_OPEN, 1); chooser.set_default_response (1);

var filter = new Gtk.FileFilter (); filter.add_pixbuf_formats (); chooser.filter = filter;

if (chooser.run () == 1) this.image.file = chooser.get_filename ();

chooser.destroy (); }

This is a bit more complicated than anything we've attempted so far, so let's break it down:

The line beginning with `var chooser` creates an Open dialog, which the user can use to choose files. We set four properties: the title of the dialog; the action (type) of the dialog (it's an "open" dialog, but we could have used `SAVE` if the intention was to save a file; `transient_for`, which sets the parent window of the dialog; and `modal` which, if set to `true`, prevents the user from clicking on another area of the application until the dialog is closed.

The next two lines add Cancel and Open buttons to the dialog. The second argument of the `add_button` method is the (integer) value that is returned when the button is pressed: 0 for Cancel and 1 for Open.

Notice that we are using stock button names from Gtk, instead of manually typing "Cancel" or "Open". The advantage of using stock names is that the button labels will already be translated into the user's language.

`set_default_response` determines the button that will be activated if the user double-clicks a file or presses Enter. In our case, we are using the Open button as default (which has the value 1).

The next three lines restrict the Open dialog to only display files which can be opened by `Gtk.Image`. A filter object is created first; we then add all kinds of files supported by `Gdk.Pixbuf` (which includes most image formats like PNG and JPEG) to the filter. Finally, we set this filter to be the Open dialog's filter.

`chooser.run` displays the Open dialog. The dialog will wait for the user to choose an image; when they do, `chooser.run` will return the value 1 (it would return 0 if the user clicked Cancel). The `if` statement tests for this.

Assuming that the user did click Open, the next line sets the `file` property of the `Gtk.Image` to the filename of the image selected by the user. The `Gtk.Image` will then load and display the chosen image.

In the final line of this method, we destroy the Open dialog because we don't need it any more.

# Ejecutar la aplicación

All of the code you need should now be in place, so try running the code. That should be it; a fully-functioning image viewer (and a whistlestop tour of JavaScript and Gtk) in not much time at all!

# Implementación de referencia

Si tiene problemas con este tutorial, compare su código con este código de referencia.

# Siguientes pasos

Aquí hay algunas ideas sobre cómo puede extender esta sencilla demostración:

Have the user select a directory rather than a file, and provide controls to cycle through all of the images in a directory.

Apply random filters and effects to the image when it is loaded and allow the user to save the modified image.

GEGL provides powerful image manipulation capabilities.

Allow the user to load images from network shares, scanners, and other more complicated sources.

You can use GIO to handle network file tranfers and the like, and GNOME Scan to handle scanning.