Blame platform-demos/C/image-viewer.js.page

Packit 1470ea
Packit 1470ea
Packit 1470ea
      xmlns:its="http://www.w3.org/2005/11/its"
Packit 1470ea
      type="topic"
Packit 1470ea
      id="image-viewer.js">
Packit 1470ea
Packit 1470ea
  <info>
Packit 1470ea
  <title type="text">Image viewer (JavaScript)</title>
Packit 1470ea
    <link type="guide" xref="js#examples"/>
Packit 1470ea
Packit 1470ea
    <desc>A little bit more than a simple "Hello world" application - write an image viewer in GTK+. Includes an introduction to the JavaScript language.</desc>
Packit 1470ea
Packit 1470ea
    <revision pkgversion="0.1" version="0.1" date="2011-03-19" status="review"/>
Packit 1470ea
    <credit type="author">
Packit 1470ea
      <name>Jonh Wendell</name>
Packit 1470ea
      <email its:translate="no">jwendell@gnome.org</email>
Packit 1470ea
    </credit>
Packit 1470ea
    <credit type="author">
Packit 1470ea
      <name>Johannes Schmid</name>
Packit 1470ea
      <email its:translate="no">jhs@gnome.org</email>
Packit 1470ea
    </credit>
Packit 1470ea
    <credit type="editor">
Packit 1470ea
      <name>Marta Maria Casetti</name>
Packit 1470ea
      <email its:translate="no">mmcasettii@gmail.com</email>
Packit 1470ea
      <years>2013</years>
Packit 1470ea
    </credit>
Packit 1470ea
  </info>
Packit 1470ea
Packit 1470ea
<title>Image viewer</title>
Packit 1470ea
Packit 1470ea
<synopsis>
Packit 1470ea
  

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:

Packit 1470ea
  <list>
Packit 1470ea
    <item>

Write a basic GTK user interface in JavaScript

</item>
Packit 1470ea
    <item>

Deal with events by connecting signals to signal handlers

</item>
Packit 1470ea
    <item>

Lay out GTK user interfaces using containers

</item>
Packit 1470ea
    <item>

Load and display image files

</item>
Packit 1470ea
  </list>
Packit 1470ea
  

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

Packit 1470ea
  <list>
Packit 1470ea
    <item>

An installed copy of the <link xref="getting-ready">Anjuta IDE</link>

</item>
Packit 1470ea
    <item>

An installed copy of the gjs interpreter

</item>
Packit 1470ea
    <item>

Basic knowledge of any object-orientated programming language

</item>
Packit 1470ea
  </list>
Packit 1470ea
</synopsis>
Packit 1470ea
Packit 1470ea
<media type="image" mime="image/png" src="media/image-viewer.png"/>
Packit 1470ea
Packit 1470ea
<section id="anjuta">
Packit 1470ea
  <title>Create a project in Anjuta</title>
Packit 1470ea
  

Before you start coding, you'll need to set up a new project in Anjuta. This will create all of the files you need to build and run the code later on. It's also useful for keeping everything together.

Packit 1470ea
  <steps>
Packit 1470ea
    <item>
Packit 1470ea
    

Start Anjuta and click <guiseq><gui>File</gui><gui>New</gui><gui>Project</gui></guiseq> to open the project wizard.

Packit 1470ea
    </item>
Packit 1470ea
    <item>
Packit 1470ea
    

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

Packit 1470ea
   	</item>
Packit 1470ea
    <item>
Packit 1470ea
    

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

Packit 1470ea
    </item>
Packit 1470ea
  </steps>
Packit 1470ea
</section>
Packit 1470ea
Packit 1470ea
Packit 1470ea
<section id="js">
Packit 1470ea
  <title>JavaScript basics: Hello World</title>
Packit 1470ea
  

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 <file>main.js</file>:

Packit 1470ea
  print ("Hello world!");
Packit 1470ea
  

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.

Packit 1470ea
</section>
Packit 1470ea
Packit 1470ea
<section id="classes">
Packit 1470ea
  <title>Classes in JavaScript</title>
Packit 1470ea
  

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

Packit 1470ea
  
Packit 1470ea
function MyClass () {
Packit 1470ea
  this._init ();
Packit 1470ea
}
Packit 1470ea
Packit 1470ea
MyClass.prototype = {
Packit 1470ea
Packit 1470ea
  _init: function () {
Packit 1470ea
    this.propertyA = "This is an object's field";
Packit 1470ea
    this.propertyB = 10;
Packit 1470ea
  },
Packit 1470ea
Packit 1470ea
  aMethod: function (arg1, arg2) {
Packit 1470ea
    print ("inside aMethod: " + arg1 + " " + arg2);
Packit 1470ea
  },
Packit 1470ea
Packit 1470ea
  dumpProperties: function () {
Packit 1470ea
    print (this.propertyA);
Packit 1470ea
    print (this.propertyB);
Packit 1470ea
  }
Packit 1470ea
Packit 1470ea
}]]>
Packit 1470ea
  

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

Packit 1470ea
  <steps>
Packit 1470ea
    <item>
Packit 1470ea
    

function MyClass is the constructor of the class — its name must match the class's name. You can access any member of the class by using the this object; here, the constructor calls the class's _init method.

Packit 1470ea
    </item>
Packit 1470ea
    <item>
Packit 1470ea
    

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.

Packit 1470ea
    </item>
Packit 1470ea
    <item>
Packit 1470ea
    

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

Packit 1470ea
    _init: function ()
Packit 1470ea
    

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.

Packit 1470ea
    </item>
Packit 1470ea
    <item>
Packit 1470ea
    

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.

Packit 1470ea
    </item>
Packit 1470ea
    <item>
Packit 1470ea
    

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

Packit 1470ea
    </item>
Packit 1470ea
  </steps>
Packit 1470ea
Packit 1470ea
  

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

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

This code creates a new instance of the class called o, runs aMethod, changes propertyA to a different string, and then calls dumpProperties (which outputs the fields).

Packit 1470ea
  

Save the code in the <file>main.js</file> and then run it by using

Packit 1470ea
  <guiseq><gui>Run</gui><gui>Execute</gui></guiseq> from the menu or using the toolbar.

Packit 1470ea
</section>
Packit 1470ea
Packit 1470ea
<section id="gtk">
Packit 1470ea
  <title>A first Gtk application</title>
Packit 1470ea
  

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

Packit 1470ea
  
Packit 1470ea
const Gtk = imports.gi.Gtk;
Packit 1470ea
Packit 1470ea
Gtk.init (null, null);
Packit 1470ea
Packit 1470ea
var w = new Gtk.Window ({title: "Image Viewer Demo"});
Packit 1470ea
w.show ();
Packit 1470ea
Packit 1470ea
Gtk.main ();]]>
Packit 1470ea
  

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

Packit 1470ea
  <list>
Packit 1470ea
    <item>
Packit 1470ea
    

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.

Packit 1470ea
    </item>
Packit 1470ea
    <item>
Packit 1470ea
    

Gtk.init initializes the Gtk library; this statement is mandatory for all Gtk programs.

Packit 1470ea
    </item>
Packit 1470ea
    <item>
Packit 1470ea
    

The next line creates the main window by creating a new Gtk.Window object. You can pass several properties to the window's constructor by using the syntax {property: value, property: value, ...}. In this case we are setting the title of the window.

</item>
Packit 1470ea
    <item>

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

</item>
Packit 1470ea
    <item>

Finally, Gtk.main runs the main loop — in other words, it executes the program. The main loop listens for events (signals) from the user interface and then calls a signal handler which will do something useful. We'll learn more about signals shortly.

</item>
Packit 1470ea
  </list>
Packit 1470ea
Packit 1470ea
  

Save the code in <file>main.js</file> 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 <keyseq><key>Ctrl</key><key>C</key></keyseq> in the terminal window to quit the program.

Packit 1470ea
Packit 1470ea
</section>
Packit 1470ea
Packit 1470ea
<section id="classes2">
Packit 1470ea
  <title>Adding classes</title>
Packit 1470ea
  

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

Packit 1470ea
  
Packit 1470ea
const Gtk = imports.gi.Gtk;
Packit 1470ea
Packit 1470ea
function ImageViewer () {
Packit 1470ea
  this._init ();
Packit 1470ea
}
Packit 1470ea
Packit 1470ea
ImageViewer.prototype = {
Packit 1470ea
  _init: function () {
Packit 1470ea
    this.window = new Gtk.Window ({title: "Image Viewer Demo"});
Packit 1470ea
    this.window.show ();
Packit 1470ea
  }
Packit 1470ea
}
Packit 1470ea
Packit 1470ea
Gtk.init (null, null);
Packit 1470ea
var iv = new ImageViewer ();
Packit 1470ea
Gtk.main ();]]>
Packit 1470ea
  
Packit 1470ea
  

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).

Packit 1470ea
  

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

Packit 1470ea
</section>
Packit 1470ea
Packit 1470ea
<section id="signals">
Packit 1470ea
  <title>Signals</title>
Packit 1470ea
  

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:

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

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.

Packit 1470ea
  

The syntax for connecting any signal to a function is:

Packit 1470ea
  
Packit 1470ea
object.connect (<signal_name>, <function_to_be_called>);]]>
Packit 1470ea
  

You can find signal definitions for any object in the <link href="https://developer.gnome.org/gtk3/stable/gtkobjects.html">GTK class reference</link>.

Packit 1470ea
Packit 1470ea
  <note>
Packit 1470ea
    

You can simplify the code by making use of an inline function definition:

Packit 1470ea
    
Packit 1470ea
b.connect ("clicked", function () { print ("you clicked me!"); });]]>
Packit 1470ea
  </note>
Packit 1470ea
Packit 1470ea
</section>
Packit 1470ea
Packit 1470ea
<section id="close">
Packit 1470ea
  <title>Closing the window</title>
Packit 1470ea
  

When you close a Gtk window it's not really closed, it's hidden. This allows you to keep the window around (which is useful if you want to ask the user if they really want to close the window, for example).

Packit 1470ea
  

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 <file>image-viewer.js</file> file and add the following code to the _init method, on the line above this.window.show:

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

This connects the hide signal of the window to Gtk's main_quit function, which ends the execution of the Gtk main loop. Once the main loop finishes, the function Gtk.main returns. Our program would continue to run any code written after the Gtk.main (); line, but since we don't have any code after that point, the program just ends.

Packit 1470ea
</section>
Packit 1470ea
Packit 1470ea
<section id="containers2">
Packit 1470ea
  <title>Containers: Laying-out the user interface</title>
Packit 1470ea
  

Widgets (controls, such as buttons and labels) can be arranged in the window by making use of containers. You can organize the layout by mixing different types of containers, like boxes and grids.

Packit 1470ea
  

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 <link href="http://library.gnome.org/devel/gtk/stable/GtkContainer.html">container types</link> 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.

Packit 1470ea
  <note>
Packit 1470ea
  

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

Packit 1470ea
  </note>
Packit 1470ea
  

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:

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

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.

Packit 1470ea
  

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).

Packit 1470ea
</section>
Packit 1470ea
Packit 1470ea
<section id="packing2">
Packit 1470ea
  <title>Packing: Adding widgets to the container</title>
Packit 1470ea
  

To add some widgets to the Gtk.Box, insert the following code directly below the this.window.add (main_box) line:

Packit 1470ea
  
Packit 1470ea
this.image = new Gtk.Image ();
Packit 1470ea
main_box.pack_start (this.image, true, true, 0);]]>
Packit 1470ea
  

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 <link href="http://library.gnome.org/devel/gtk/stable/GtkBox.html#gtk-box-pack-start">pack_start</link> method.

Packit 1470ea
  

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).

Packit 1470ea
  

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.

Packit 1470ea
  

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.

Packit 1470ea
  

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

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

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.

Packit 1470ea
  

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

Packit 1470ea
  this.window.show_all ();
Packit 1470ea
  

This will show the child of the Gtk window, and all of its children, and its children's children, and so on. (Remember that Gtk widgets are all hidden by default.)

Packit 1470ea
</section>
Packit 1470ea
Packit 1470ea
<section id="loading2">
Packit 1470ea
  <title>Loading the image: Connecting to the button's clicked signal</title>
Packit 1470ea
  

When the user clicks on the <gui>Open</gui> 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.

Packit 1470ea
  

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:

Packit 1470ea
  
Packit 1470ea
open_button.connect ("clicked", Lang.bind (this, this._openClicked));]]>
Packit 1470ea
  

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:

Packit 1470ea
  const Lang = imports.lang;
Packit 1470ea
</section>
Packit 1470ea
Packit 1470ea
<section id="loading3">
Packit 1470ea
  <title>Loading the image: Writing the signal's callback</title>
Packit 1470ea
  

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):

Packit 1470ea
    
Packit 1470ea
  _openClicked: function () {
Packit 1470ea
    var chooser = new Gtk.FileChooserDialog ({title: "Select an image",
Packit 1470ea
                                              action: Gtk.FileChooserAction.OPEN,
Packit 1470ea
                                              transient_for: this.window,
Packit 1470ea
                                              modal: true});
Packit 1470ea
    chooser.add_button (Gtk.STOCK_CANCEL, 0);
Packit 1470ea
    chooser.add_button (Gtk.STOCK_OPEN, 1);
Packit 1470ea
    chooser.set_default_response (1);
Packit 1470ea
Packit 1470ea
    var filter = new Gtk.FileFilter ();
Packit 1470ea
    filter.add_pixbuf_formats ();
Packit 1470ea
    chooser.filter = filter;
Packit 1470ea
Packit 1470ea
    if (chooser.run () == 1)
Packit 1470ea
      this.image.file = chooser.get_filename ();
Packit 1470ea
Packit 1470ea
    chooser.destroy ();
Packit 1470ea
  }]]>
Packit 1470ea
  

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

Packit 1470ea
  <list>
Packit 1470ea
    <item>
Packit 1470ea
      

The line beginning with var chooser creates an <gui>Open</gui> 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.

Packit 1470ea
    </item>
Packit 1470ea
    <item>
Packit 1470ea
    

The next two lines add <gui>Cancel</gui> and <gui>Open</gui> 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 <gui>Cancel</gui> and 1 for <gui>Open</gui>.

Packit 1470ea
    

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.

Packit 1470ea
    </item>
Packit 1470ea
    <item>
Packit 1470ea
    

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

Packit 1470ea
    </item>
Packit 1470ea
    <item>
Packit 1470ea
    

The next three lines restrict the <gui>Open</gui> 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 <gui>Open</gui> dialog's filter.

Packit 1470ea
    </item>
Packit 1470ea
    <item>
Packit 1470ea
    

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

Packit 1470ea
    </item>
Packit 1470ea
    <item>

Assuming that the user did click <gui>Open</gui>, 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.

Packit 1470ea
    </item>
Packit 1470ea
    <item>
Packit 1470ea
    

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

Packit 1470ea
    </item>
Packit 1470ea
  </list>
Packit 1470ea
Packit 1470ea
  </section>
Packit 1470ea
Packit 1470ea
<section id="run">
Packit 1470ea
  <title>Run the application</title>
Packit 1470ea
  

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!

Packit 1470ea
</section>
Packit 1470ea
Packit 1470ea
<section id="impl">
Packit 1470ea
 <title>Reference Implementation</title>
Packit 1470ea
 

If you run into problems with the tutorial, compare your code with this <link href="image-viewer/image-viewer.js">reference code</link>.

Packit 1470ea
</section>
Packit 1470ea
Packit 1470ea
<section id="next">
Packit 1470ea
  <title>Next steps</title>
Packit 1470ea
  

Here are some ideas for how you can extend this simple demonstration:

Packit 1470ea
  <list>
Packit 1470ea
   <item>
Packit 1470ea
   

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

Packit 1470ea
   </item>
Packit 1470ea
   <item>
Packit 1470ea
   

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

Packit 1470ea
   

<link href="http://www.gegl.org/api.html">GEGL</link> provides powerful image manipulation capabilities.

Packit 1470ea
   </item>
Packit 1470ea
   <item>
Packit 1470ea
   

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

Packit 1470ea
   

You can use <link href="http://library.gnome.org/devel/gio/unstable/">GIO</link> to handle network file transfers and the like, and <link href="http://library.gnome.org/devel/gnome-scan/unstable/">GNOME Scan</link> to handle scanning.

Packit 1470ea
   </item>
Packit 1470ea
  </list>
Packit 1470ea
</section>
Packit 1470ea
Packit 1470ea
</page>