forked from AuroraMiddleware/gtk
1004 lines
39 KiB
XML
1004 lines
39 KiB
XML
<?xml version="1.0"?>
|
||
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
|
||
"http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd" [
|
||
]>
|
||
<chapter id="gtk-getting-started" xmlns:xi="http://www.w3.org/2003/XInclude">
|
||
<title>Getting Started with GTK+</title>
|
||
|
||
<para>This chapter contains some tutorial information to get you
|
||
started with GTK+ programming. It assumes that you have GTK+, its
|
||
dependencies and a C compiler installed and ready to use. If you
|
||
need to build GTK+ itself first, refer to the
|
||
<link linkend="gtk-compiling">Compiling the GTK+ libraries</link>
|
||
section in this reference.</para>
|
||
|
||
<section>
|
||
<title>Basics</title>
|
||
|
||
<para>To begin our introduction to GTK, we'll start with the simplest
|
||
program possible. This program will create an empty 200 × 200 pixel
|
||
window.</para>
|
||
|
||
<informalfigure>
|
||
<mediaobject>
|
||
<imageobject>
|
||
<imagedata fileref="window-default.png" format="PNG"/>
|
||
</imageobject>
|
||
</mediaobject>
|
||
</informalfigure>
|
||
|
||
<informalexample>
|
||
<para>Create a new file with the following content named example-0.c.</para>
|
||
<programlisting><xi:include href="../../../../examples/window-default.c" parse="text"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include></programlisting>
|
||
</informalexample>
|
||
|
||
<para>
|
||
You can compile the program above with GCC using:
|
||
<literallayout>
|
||
<literal>gcc `pkg-config --cflags gtk+-3.0` -o example-0 example-0.c `pkg-config --libs gtk+-3.0`</literal>
|
||
</literallayout>
|
||
</para>
|
||
|
||
<note><para>For more information on how to compile a GTK+ application, please
|
||
refer to the <link linkend="gtk-compiling">Compiling GTK+ Applications</link>
|
||
section in this reference.</para></note>
|
||
|
||
<para>All GTK+ applications will, of course, include
|
||
<filename>gtk/gtk.h</filename>, which declares functions, types and
|
||
macros required by GTK+ applications.</para>
|
||
|
||
<warning><para>Even if GTK+ installs multiple header files, only the
|
||
top-level <filename>gtk/gtk.h</filename> header can be directly included
|
||
by third party code. The compiler will abort with an error if any other
|
||
header is directly included.</para></warning>
|
||
|
||
<para>We then proceed into the <function>main</function>() function of the
|
||
application, and we declare a <varname>window</varname> variable as a pointer
|
||
of type #GtkWidget.</para>
|
||
|
||
<para>The following line will call gtk_init(), which
|
||
is the initialization function for GTK+; this function will set up GTK+,
|
||
the type system, the connection to the windowing environment, etc. The
|
||
gtk_init() takes as arguments the pointers to the command line arguments
|
||
counter and string array; this allows GTK+ to parse specific command line
|
||
arguments that control the behavior of GTK+ itself. The parsed arguments
|
||
will be removed from the array, leaving the unrecognized ones for your
|
||
application to parse.</para>
|
||
|
||
<note><para>For more information on which command line arguments GTK+
|
||
recognizes, please refer to the <link linkend="gtk-running">Running GTK+
|
||
Applications</link> section in this reference.</para></note>
|
||
|
||
<para>The call to gtk_window_new() will create a new #GtkWindow and store
|
||
it inside the <varname>window</varname> variable. The type of the window
|
||
is %GTK_WINDOW_TOPLEVEL, which means that the #GtkWindow will be managed
|
||
by the windowing system: it will have a frame, a title bar and window
|
||
controls, depending on the platform.</para>
|
||
|
||
<para>In order to terminate the application when the #GtkWindow is
|
||
destroyed, we connect the #GtkWidget::destroy signal to the gtk_main_quit()
|
||
function. This function will terminate the GTK+ main loop started by calling
|
||
gtk_main() later. The #GtkWidget::destroy signal is emitted when a widget is
|
||
destroyed, either by explicitly calling gtk_widget_destroy() or when the
|
||
widget is unparented. Top-level #GtkWindow<!-- -->s are also destroyed when
|
||
the Close window control button is clicked.</para>
|
||
|
||
<para>#GtkWidget<!-- -->s are hidden by default. By calling gtk_widget_show()
|
||
on a #GtkWidget we are asking GTK+ to set the visibility attribute so that it
|
||
can be displayed. All this work is done after the main loop has been
|
||
started.</para>
|
||
|
||
<para>The last line of interest is the call to gtk_main(). This function will
|
||
start the GTK+ main loop and will block the control flow of the
|
||
main() until the gtk_main_quit() function is called.</para>
|
||
|
||
<para>While the program is running, GTK+ is receiving
|
||
<firstterm>events</firstterm>. These are typically input events caused by
|
||
the user interacting with your program, but also things like messages from
|
||
the window manager or other applications. GTK+ processes these and as a
|
||
result, <firstterm>signals</firstterm> may be emitted on your widgets.
|
||
Connecting handlers for these signals is how you normally make your
|
||
program do something in response to user input.</para>
|
||
|
||
<para>The following example is slightly more complex, and tries to
|
||
showcase some of the capabilities of GTK+.</para>
|
||
|
||
<para>In the long tradition of programming languages and libraries,
|
||
it is called <emphasis>Hello, World</emphasis>.</para>
|
||
|
||
<informalfigure>
|
||
<mediaobject>
|
||
<imageobject>
|
||
<imagedata fileref="hello-world.png" format="PNG"/>
|
||
</imageobject>
|
||
</mediaobject>
|
||
</informalfigure>
|
||
|
||
<example id="gtk-getting-started-hello-world">
|
||
<title>Hello World in GTK+</title>
|
||
<para>Create a new file with the following content named example-1.c.</para>
|
||
<programlisting><xi:include href="../../../../examples/hello-world.c" parse="text">
|
||
<xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include></programlisting>
|
||
</example>
|
||
|
||
<para>
|
||
You can compile the program above with GCC using:
|
||
<literallayout>
|
||
<literal>gcc `pkg-config --cflags gtk+-3.0` -o example-1 example-1.c `pkg-config --libs gtk+-3.0`</literal>
|
||
</literallayout>
|
||
</para>
|
||
</section>
|
||
|
||
<section>
|
||
<title>Packing</title>
|
||
|
||
<para>When creating an application, you'll want to put more than one widget
|
||
inside a window. Our first helloworld example only used one widget so we
|
||
could simply use a gtk_container_add() call to "pack" the widget into the
|
||
window. But when you want to put more than one widget into a window, it
|
||
it becomes important to control how each widget is positioned and sized.
|
||
This is where packing comes in.</para>
|
||
|
||
<para>GTK+ comes with a large variety of <firstterm>layout containers</firstterm>
|
||
whose purpose it is to control the layout of the child widgets that are
|
||
added to them. See <xref linkend="LayoutContainers"/> for an overview.</para>
|
||
|
||
<para>The following example shows how the GtkGrid container lets you
|
||
arrange several buttons:</para>
|
||
|
||
<informalfigure>
|
||
<mediaobject>
|
||
<imageobject>
|
||
<imagedata fileref="grid-packing.png" format="PNG"/>
|
||
</imageobject>
|
||
</mediaobject>
|
||
</informalfigure>
|
||
|
||
<example id="gtk-getting-started-grid-packing">
|
||
<title>Packing buttons</title>
|
||
<para>Create a new file with the following content named example-2.c.</para>
|
||
<programlisting><xi:include href="../../../../examples/grid-packing.c" parse="text"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include></programlisting>
|
||
</example>
|
||
<para>
|
||
You can compile the program above with GCC using:
|
||
<literallayout>
|
||
<literal>gcc `pkg-config --cflags gtk+-3.0` -o example-2 example-2.c `pkg-config --libs gtk+-3.0`</literal>
|
||
</literallayout>
|
||
</para>
|
||
</section>
|
||
|
||
<section>
|
||
<title>Drawing</title>
|
||
|
||
<para>Many widgets, like buttons, do all their drawing themselves. You
|
||
just tell them the label you want to see, and they figure out what font
|
||
to use, draw the button outline and focus rectangle, etc. Sometimes, it
|
||
is necessary to do some custom drawing. In that case, a #GtkDrawingArea
|
||
might be the right widget to use. It offers a canvas on which you can
|
||
draw by connecting to the #GtkWidget::draw signal.
|
||
</para>
|
||
|
||
<para>The contents of a widget often need to be partially or fully redrawn,
|
||
e.g. when another window is moved and uncovers part of the widget, or
|
||
when tie window containing it is resized. It is also possible to explicitly
|
||
cause part or all of the widget to be redrawn, by calling
|
||
gtk_widget_queue_draw() or its variants. GTK+ takes care of most of the
|
||
details by providing a ready-to-use cairo context to the ::draw signal
|
||
handler.</para>
|
||
|
||
<para>The following example shows a ::draw signal handler. It is a bit
|
||
more complicated than the previous examples, since it also demonstrates
|
||
input event handling by means of ::button-press and ::motion-notify
|
||
handlers.</para>
|
||
|
||
<informalfigure>
|
||
<mediaobject>
|
||
<imageobject>
|
||
<imagedata fileref="drawing.png" format="PNG"/>
|
||
</imageobject>
|
||
</mediaobject>
|
||
</informalfigure>
|
||
|
||
<example id="gtk-getting-started-drawing">
|
||
<title>Drawing in response to input</title>
|
||
<para>Create a new file with the following content named example-3.c.</para>
|
||
<programlisting><xi:include href="../../../../examples/drawing.c" parse="text"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include></programlisting>
|
||
</example>
|
||
<para>
|
||
You can compile the program above with GCC using:
|
||
<literallayout>
|
||
<literal>gcc `pkg-config --cflags gtk+-3.0` -o example-3 example-3.c `pkg-config --libs gtk+-3.0`</literal>
|
||
</literallayout>
|
||
</para>
|
||
</section>
|
||
|
||
<section>
|
||
<title>Building user interfaces</title>
|
||
|
||
<para>When construcing a more complicated user interface, with dozens
|
||
or hundreds of widgets, doing all the setup work in C code is
|
||
cumbersome, and making changes becomes next to impossible.</para>
|
||
|
||
<para>Thankfully, GTK+ supports the separation of user interface
|
||
layout from your business logic, by using UI descriptions in an
|
||
XML format that can be parsed by the #GtkBuilder class.</para>
|
||
|
||
<example>
|
||
<title>Packing buttons with GtkBuilder</title>
|
||
<para>Create a new file with the following content named example-4.c.</para>
|
||
<programlisting><xi:include href="../../../../examples/builder.c" parse="text"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include></programlisting>
|
||
<para>Create a new file with the following content named builder.ui.</para>
|
||
<programlisting><xi:include href="../../../../examples/builder.ui" parse="text"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include></programlisting>
|
||
</example>
|
||
<para>
|
||
You can compile the program above with GCC using:
|
||
<literallayout>
|
||
<literal>gcc `pkg-config --cflags gtk+-3.0` -o example-4 example-4.c `pkg-config --libs gtk+-3.0`</literal>
|
||
</literallayout>
|
||
</para>
|
||
|
||
<para>Note that GtkBuilder can also be used to construct objects
|
||
that are not widgets, such as tree models, adjustments, etc.
|
||
That is the reason the method we use here is called
|
||
gtk_builder_get_object() and returns a GObject* instead of a
|
||
GtkWidget*.</para>
|
||
|
||
<para>Normally, you would pass a full path to
|
||
gtk_builder_add_from_file() to make the execution of your program
|
||
independent of the current directory. A common location to install
|
||
UI descriptions and similar data is
|
||
<filename>/usr/share/<replaceable>appname</replaceable></filename>.
|
||
</para>
|
||
|
||
<para>It is also possible to embed the UI description in the source
|
||
code as a string and use gtk_builder_add_from_string() to load it.
|
||
But keeping the UI description in a separate file has several
|
||
advantages: It is then possible to make minor adjustments to the UI
|
||
without recompiling your program, and, more importantly, graphical
|
||
UI editors such as <ulink url="http://glade.gnome.org">glade</ulink>
|
||
can load the file and allow you to create and modify your UI by
|
||
point-and-click.</para>
|
||
</section>
|
||
|
||
<section>
|
||
<title>Building applications</title>
|
||
|
||
<para>An application consists of a number of files:
|
||
<variablelist>
|
||
<varlistentry>
|
||
<term>The binary</term>
|
||
<listitem>This gets installed in <filename>/usr/bin</filename>.</listitem>
|
||
</varlistentry>
|
||
<varlistentry>
|
||
<term>A desktop file</term>
|
||
<listitem>The desktop file provides important information about the application to the desktop shell, such as its name, icon, D-Bus name, commandline to launch it, etc. It is installed in <filename>/usr/share/applications</filename>.</listitem>
|
||
</varlistentry>
|
||
<varlistentry>
|
||
<term>An icon</term>
|
||
<listitem>The icon gets installed in <filename>/usr/share/icons/hicolor/48x48/apps</filename>, where it will be found regardless of the current theme.</listitem>
|
||
</varlistentry>
|
||
<varlistentry>
|
||
<term>A settings schema</term>
|
||
<listitem>If the application uses GSettings, it will install its schema
|
||
in <filename>/usr/share/glib-2.0/schemas</filename>, so that tools
|
||
like dconf-editor can find it.</listitem>
|
||
</varlistentry>
|
||
<varlistentry>
|
||
<term>Other resources</term>
|
||
<listitem>Other files, such as GtkBuilder ui files, are best loaded from
|
||
resources stored in the application binary itself. This eliminates the
|
||
need for most of the files that would traditionally be installed in
|
||
an application-specific location in <filename>/usr/share</filename>.</listitem>
|
||
</varlistentry>
|
||
</variablelist>
|
||
</para>
|
||
|
||
<para>GTK+ includes application support that is built on top of
|
||
#GApplication. In this tutorial we'll build a simple application by
|
||
starting from scratch, adding more and more pieces over time. Along
|
||
the way, we'll learn about #GtkApplication, templates, resources,
|
||
application menus, settings, #GtkHeaderBar, #GtkStack, #GtkSearchBar,
|
||
#GtkListBox, and more.</para>
|
||
|
||
<para>The full, buildable sources for these examples can be found
|
||
in the examples/ directory of the GTK+ source distribution, or
|
||
<ulink url="https://git.gnome.org/browse/gtk+/tree/examples">online</ulink> in the GTK+ git repository.</para>
|
||
|
||
<section>
|
||
<title>A trivial application</title>
|
||
|
||
<para>When using #GtkApplication, the main() function can be very
|
||
simple. We just call g_application_run() and give it an instance
|
||
of our application class.</para>
|
||
|
||
<informalexample>
|
||
<programlisting><xi:include href="../../../../examples/application1/main.c" parse="text"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include></programlisting>
|
||
</informalexample>
|
||
|
||
<para>All the application logic is in the application class, which
|
||
is a subclass of #GtkApplication. Our example does not yet have any
|
||
interesting functionality. All it does is open a window when it is
|
||
activated without arguments, and open the files it is given, if it
|
||
is started with arguments.</para>
|
||
|
||
<para>To handle these two cases, we override the activate() vfunc,
|
||
which gets called when the application is launched without commandline
|
||
arguments, and the open() vfunc, which gets called when the application
|
||
is launched with commandline arguments.</para>
|
||
|
||
<para>To learn more about GApplication entry points, consult the
|
||
GIO <ulink url="https://developer.gnome.org/gio/2.36/GApplication.html#GApplication.description">documentation</ulink>.</para>
|
||
|
||
<informalexample>
|
||
<programlisting><xi:include href="../../../../examples/application1/exampleapp.c" parse="text"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include></programlisting>
|
||
</informalexample>
|
||
|
||
<para>Another important class that is part of the application support
|
||
in GTK+ is #GtkApplicationWindow. It is typically subclassed as well.
|
||
Our subclass does not do anything yet, so we will just get an empty
|
||
window.</para>
|
||
|
||
<informalexample>
|
||
<programlisting><xi:include href="../../../../examples/application1/exampleappwin.c" parse="text"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include></programlisting>
|
||
</informalexample>
|
||
|
||
<para>As part of the initial setup of our application, we also
|
||
create an icon and a desktop file.</para>
|
||
|
||
<informalfigure>
|
||
<mediaobject>
|
||
<imageobject>
|
||
<imagedata fileref="exampleapp.png" format="PNG"/>
|
||
</imageobject>
|
||
</mediaobject>
|
||
</informalfigure>
|
||
|
||
<informalexample>
|
||
<programlisting><xi:include href="../../../../examples/application1/exampleapp.desktop" parse="text"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include></programlisting>
|
||
</informalexample>
|
||
|
||
<para>Note that <replaceable>@<!-- -->bindir@</replaceable> needs to be replaced
|
||
with the actual path to the binary before this desktop file can be used.</para>
|
||
|
||
<para>Here is what we've achieved so far:</para>
|
||
|
||
<informalfigure>
|
||
<mediaobject>
|
||
<imageobject>
|
||
<imagedata fileref="getting-started-app1.png" format="PNG"/>
|
||
</imageobject>
|
||
</mediaobject>
|
||
</informalfigure>
|
||
|
||
<para>This does not look very impressive yet, but our application
|
||
is already presenting itself on the session bus, it has single-instance
|
||
semantics, and it accepts files as commandline arguments.</para>
|
||
</section>
|
||
|
||
<section>
|
||
<title>Populating the window</title>
|
||
|
||
<para>In this step, we use a #GtkBuilder template to associate a
|
||
#GtkBuilder ui file with our application window class.</para>
|
||
<para>Our simple ui file puts a #GtkHeaderBar on top of a #GtkStack
|
||
widget. The header bar contains a #GtkStackSwitcher, which is a
|
||
standalone widget to show a row of 'tabs' for the pages of a #GtkStack.
|
||
</para>
|
||
|
||
<informalexample>
|
||
<programlisting><xi:include href="../../../../examples/application2/window.ui" parse="text"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include></programlisting>
|
||
</informalexample>
|
||
|
||
<para>To make use of this file in our application, we revisit
|
||
our #GtkApplicationWindow subclass, and call
|
||
gtk_widget_class_set_template_from_resource() from the class init
|
||
function to set the ui file as template for this class. We also
|
||
add a call to gtk_widget_init_template() in the instance init
|
||
function to instantiate the template for each instance of our
|
||
class.</para>
|
||
|
||
<informalexample>
|
||
<programlisting><![CDATA[
|
||
...
|
||
|
||
static void
|
||
example_app_window_init (ExampleAppWindow *win)
|
||
{
|
||
gtk_widget_init_template (GTK_WIDGET (win));
|
||
}
|
||
|
||
static void
|
||
example_app_window_class_init (ExampleAppWindowClass *class)
|
||
{
|
||
gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (class),
|
||
"/org/gtk/exampleapp/window.ui");
|
||
}
|
||
|
||
...
|
||
]]></programlisting>
|
||
<para>(<ulink url="https://git.gnome.org/browse/gtk+/tree/examples/application2/exampleappwin.c">full source</ulink>)</para>
|
||
</informalexample>
|
||
|
||
<para>You may have noticed that we used the _from_resource() variant
|
||
of the function that sets a template. Now we need to use GLib's resource
|
||
functionality to include the ui file in the binary. This is commonly
|
||
done by listing all resources in a .gresource.xml file, such as this:
|
||
</para>
|
||
|
||
<informalexample>
|
||
<programlisting><xi:include href="../../../../examples/application2/exampleapp.gresource.xml" parse="text"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include></programlisting>
|
||
</informalexample>
|
||
|
||
<para>This file has to be converted into a C source file that will be
|
||
compiled and linked into the application together with the other source
|
||
files. To do so, we use the glib-compile-resources utility:</para>
|
||
|
||
<screen>
|
||
glib-compile-resources exampleapp.gresource.xml --target=resources.c --generate-source
|
||
</screen>
|
||
|
||
<para>Our application now looks like this:</para>
|
||
|
||
<informalfigure>
|
||
<mediaobject>
|
||
<imageobject>
|
||
<imagedata fileref="getting-started-app2.png" format="PNG"/>
|
||
</imageobject>
|
||
</mediaobject>
|
||
</informalfigure>
|
||
</section>
|
||
|
||
<section>
|
||
<title>Opening files</title>
|
||
|
||
<para>In this step, we make our application show the content of
|
||
all the files that it is given on the commandline.</para>
|
||
|
||
<para>To this end, we add a private struct to our application
|
||
window subclass and keep a reference to the #GtkStack there.
|
||
The gtk_widget_class_bind_child() function arranges things so
|
||
that after instantiating the template, the @stack member of
|
||
the private struct will point to the widget of the same name
|
||
from the template.</para>
|
||
|
||
<informalexample>
|
||
<programlisting><![CDATA[
|
||
...
|
||
|
||
struct _ExampleAppWindowPrivate
|
||
{
|
||
GtkWidget *stack;
|
||
};
|
||
|
||
G_DEFINE_TYPE_WITH_PRIVATE(ExampleAppWindow, example_app_window, GTK_TYPE_APPLICATION_WINDOW);
|
||
|
||
...
|
||
|
||
static void
|
||
example_app_window_class_init (ExampleAppWindowClass *class)
|
||
{
|
||
gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (class),
|
||
"/org/gtk/exampleapp/window.ui");
|
||
gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (class), ExampleAppWindow, stack);
|
||
}
|
||
|
||
...
|
||
]]></programlisting>
|
||
<para>(<ulink url="https://git.gnome.org/browse/gtk+/tree/examples/application3/exampleappwin.c">full source</ulink>)</para>
|
||
</informalexample>
|
||
|
||
<para>Now we revisit the example_app_window_open() function that
|
||
is called for each commandline argument, and construct a GtkTextView
|
||
that we then add as a page to the stack:</para>
|
||
|
||
<informalexample>
|
||
<programlisting><![CDATA[
|
||
...
|
||
|
||
void
|
||
example_app_window_open (ExampleAppWindow *win,
|
||
GFile *file)
|
||
{
|
||
ExampleAppWindowPrivate *priv;
|
||
gchar *basename;
|
||
GtkWidget *scrolled, *view;
|
||
gchar *contents;
|
||
gsize length;
|
||
|
||
priv = example_app_window_get_instance_private (win);
|
||
basename = g_file_get_basename (file);
|
||
|
||
scrolled = gtk_scrolled_window_new (NULL, NULL);
|
||
gtk_widget_show (scrolled);
|
||
gtk_widget_set_hexpand (scrolled, TRUE);
|
||
gtk_widget_set_vexpand (scrolled, TRUE);
|
||
view = gtk_text_view_new ();
|
||
gtk_text_view_set_editable (GTK_TEXT_VIEW (view), FALSE);
|
||
gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (view), FALSE);
|
||
gtk_widget_show (view);
|
||
gtk_container_add (GTK_CONTAINER (scrolled), view);
|
||
gtk_stack_add_titled (GTK_STACK (priv->stack), scrolled, basename, basename);
|
||
|
||
if (g_file_load_contents (file, NULL, &contents, &length, NULL, NULL))
|
||
{
|
||
GtkTextBuffer *buffer;
|
||
|
||
buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
|
||
gtk_text_buffer_set_text (buffer, contents, length);
|
||
g_free (contents);
|
||
}
|
||
|
||
g_free (basename);
|
||
}
|
||
|
||
...
|
||
]]></programlisting>
|
||
<para>(<ulink url="https://git.gnome.org/browse/gtk+/tree/examples/application3/exampleappwin.c">full source</ulink>)</para>
|
||
</informalexample>
|
||
|
||
<para>Note that we did not have to touch the stack switcher
|
||
at all. It gets all its information from the stack that it
|
||
belongs to. Here, we are passing the label to show for each
|
||
file as the last argument to the gtk_stack_add_titled()
|
||
function.</para>
|
||
|
||
<para>Our application is beginning to take shape:</para>
|
||
|
||
<informalfigure>
|
||
<mediaobject>
|
||
<imageobject>
|
||
<imagedata fileref="getting-started-app3.png" format="PNG"/>
|
||
</imageobject>
|
||
</mediaobject>
|
||
</informalfigure>
|
||
</section>
|
||
|
||
<section>
|
||
<title>An application menu</title>
|
||
|
||
<para>An application menu is shown by GNOME shell at the top of the
|
||
screen. It is meant to collect infrequently used actions that affect
|
||
the whole application.</para>
|
||
|
||
<para>Just like the window template, we specify our application menu
|
||
in a ui file, and add it as a resource to our binary.</para>
|
||
|
||
<informalexample>
|
||
<programlisting><xi:include href="../../../../examples/application4/app-menu.ui" parse="text"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include></programlisting>
|
||
</informalexample>
|
||
|
||
<para>To associate the app menu with the application, we have to call
|
||
gtk_application_set_app_menu(). Since app menus work by activating
|
||
#GActions, we also have to add a suitable set of actions to our
|
||
application.</para>
|
||
|
||
<para>Both of these tasks are best done in the startup() vfunc,
|
||
which is guaranteed to be called once for each primary application
|
||
instance:</para>
|
||
<informalexample>
|
||
<programlisting>
|
||
...
|
||
|
||
static void
|
||
preferences_activated (GSimpleAction *action,
|
||
GVariant *parameter,
|
||
gpointer app)
|
||
{
|
||
}
|
||
|
||
static void
|
||
quit_activated (GSimpleAction *action,
|
||
GVariant *parameter,
|
||
gpointer app)
|
||
{
|
||
g_application_quit (G_APPLICATION (app));
|
||
}
|
||
|
||
static GActionEntry app_entries[] =
|
||
{
|
||
{ "preferences", preferences_activated, NULL, NULL, NULL },
|
||
{ "quit", quit_activated, NULL, NULL, NULL }
|
||
};
|
||
|
||
static void
|
||
example_app_startup (GApplication *app)
|
||
{
|
||
GtkBuilder *builder;
|
||
GMenuModel *app_menu;
|
||
|
||
G_APPLICATION_CLASS (example_app_parent_class)->startup (app);
|
||
|
||
g_action_map_add_action_entries (G_ACTION_MAP (app),
|
||
app_entries, G_N_ELEMENTS (app_entries),
|
||
app);
|
||
|
||
builder = gtk_builder_new_from_resource ("/org/gtk/exampleapp/app-menu.ui");
|
||
app_menu = G_MENU_MODEL (gtk_builder_get_object (builder, "appmenu"));
|
||
gtk_application_set_app_menu (GTK_APPLICATION (app), app_menu);
|
||
g_object_unref (builder);
|
||
}
|
||
|
||
static void
|
||
example_app_class_init (ExampleAppClass *class)
|
||
{
|
||
G_APPLICATION_CLASS (class)->startup = example_app_startup;
|
||
...
|
||
}
|
||
|
||
...
|
||
</programlisting>
|
||
<para>(<ulink url="https://git.gnome.org/browse/gtk+/tree/examples/application4/exampleapp.c">full source</ulink>)</para>
|
||
</informalexample>
|
||
|
||
<para>Our preferences menu item does not do anything yet,
|
||
but the Quit menu item is fully functional. Note that it
|
||
can also be activated by the usual Ctrl-Q shortcut. The
|
||
shortcut was specified in the ui file.
|
||
</para>
|
||
|
||
<para>The application menu looks like this:</para>
|
||
|
||
<informalfigure>
|
||
<mediaobject>
|
||
<imageobject>
|
||
<imagedata fileref="getting-started-app4.png" format="PNG"/>
|
||
</imageobject>
|
||
</mediaobject>
|
||
</informalfigure>
|
||
</section>
|
||
|
||
<section>
|
||
<title>A preference dialog</title>
|
||
|
||
<para>A typical application will have a some preferences that
|
||
should be remembered from one run to the next. Even for our
|
||
simple example application, we may want to change the font
|
||
that is used for the content.</para>
|
||
|
||
<para>We are going to use GSettings to store our preferences.
|
||
GSettings requires a schema that describes our settings:</para>
|
||
|
||
<informalexample>
|
||
<programlisting><xi:include href="../../../../examples/application5/org.gtk.exampleapp.gschema.xml" parse="text"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include></programlisting>
|
||
</informalexample>
|
||
|
||
<para>Before we can make use of this schema in our application,
|
||
we need to compile it into the binary form that GSettings
|
||
expects. GIO provides <ulink url="https://developer.gnome.org/gio/2.36/ch31s06.html">macros</ulink>
|
||
to do this in autotools-based projects.</para>
|
||
|
||
<para>Next, we need to connect our settings to the widgets
|
||
that they are supposed to control. One convenient way to do
|
||
this is to use GSettings bind functionality to bind settings
|
||
keys to object properties, as we do here for the transition
|
||
setting.</para>
|
||
|
||
<informalexample>
|
||
<programlisting><![CDATA[
|
||
...
|
||
|
||
static void
|
||
example_app_window_init (ExampleAppWindow *win)
|
||
{
|
||
ExampleAppWindowPrivate *priv;
|
||
|
||
priv = example_app_window_get_instance_private (win);
|
||
gtk_widget_init_template (GTK_WIDGET (win));
|
||
priv->settings = g_settings_new ("org.gtk.exampleapp");
|
||
|
||
g_settings_bind (priv->settings, "transition",
|
||
priv->stack, "transition-type",
|
||
G_SETTINGS_BIND_DEFAULT);
|
||
}
|
||
|
||
...
|
||
]]></programlisting>
|
||
<para>(<ulink url="https://git.gnome.org/browse/gtk+/tree/examples/application5/exampleappwin.c">full source</ulink>)</para>
|
||
</informalexample>
|
||
|
||
<para>The code to connect the font setting is a little more involved,
|
||
since there is no simple object property that it corresponds to, so
|
||
we are not going to go into that here.</para>
|
||
|
||
<para>At this point, the application will already react if you
|
||
change one of the settings, e.g. using the gsettings commandline
|
||
tool. Of course, we expect the application to provide a preference
|
||
dialog for these. So lets do that now. Our preference dialog will
|
||
be a subclass of GtkDialog, and we'll use the same techniques that
|
||
we've already seen: templates, private structs, settings
|
||
bindings.</para>
|
||
|
||
<para>Lets start with the template.</para>
|
||
|
||
<informalexample>
|
||
<programlisting><xi:include href="../../../../examples/application6/prefs.ui" parse="text"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include></programlisting>
|
||
</informalexample>
|
||
|
||
<para>Next comes the dialog subclass.</para>
|
||
|
||
<informalexample>
|
||
<programlisting><xi:include href="../../../../examples/application6/exampleappprefs.c" parse="text"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include></programlisting>
|
||
</informalexample>
|
||
|
||
<para>Now we revisit the preferences_activated() function in our
|
||
application class, and make it open a new preference dialog.</para>
|
||
|
||
<informalexample>
|
||
<programlisting><![CDATA[
|
||
...
|
||
|
||
static void
|
||
preferences_activated (GSimpleAction *action,
|
||
GVariant *parameter,
|
||
gpointer app)
|
||
{
|
||
ExampleAppPrefs *prefs;
|
||
GtkWindow *win;
|
||
|
||
win = gtk_application_get_active_window (GTK_APPLICATION (app));
|
||
prefs = example_app_prefs_new (EXAMPLE_APP_WINDOW (win));
|
||
gtk_window_present (GTK_WINDOW (prefs));
|
||
}
|
||
|
||
...
|
||
]]></programlisting>
|
||
<para>(<ulink url="https://git.gnome.org/browse/gtk+/tree/examples/application6/exampleapp.c">full source</ulink>)</para>
|
||
</informalexample>
|
||
|
||
<para>After all this work, our application can now show
|
||
a preference dialog like this:</para>
|
||
|
||
<informalfigure>
|
||
<mediaobject>
|
||
<imageobject>
|
||
<imagedata fileref="getting-started-app6.png" format="PNG"/>
|
||
</imageobject>
|
||
</mediaobject>
|
||
</informalfigure>
|
||
</section>
|
||
|
||
<section>
|
||
<title>Adding a search bar</title>
|
||
|
||
<para>We continue to flesh out the functionality of our application.
|
||
For now, we add search. GTK+ supports this with #GtkSearchEntry and
|
||
#GtkSearchBar. The search bar is a widget that can slide in from the
|
||
top to present a search entry.</para>
|
||
|
||
<para>We add a toggle button to the header bar, which can be used
|
||
to slide out the search bar below the header bar.</para>
|
||
|
||
<informalexample>
|
||
<programlisting><xi:include href="../../../../examples/application7/window.ui" parse="text"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include></programlisting>
|
||
</informalexample>
|
||
|
||
<para>Implementing the search needs quite a few code changes that
|
||
we are not going to completely go over here. The central piece of
|
||
the search implementation is a signal handler that listens for
|
||
text changes in the search entry.</para>
|
||
|
||
<informalexample>
|
||
<programlisting><![CDATA[
|
||
...
|
||
|
||
static void
|
||
search_text_changed (GtkEntry *entry,
|
||
ExampleAppWindow *win)
|
||
{
|
||
ExampleAppWindowPrivate *priv;
|
||
const gchar *text;
|
||
GtkWidget *tab;
|
||
GtkWidget *view;
|
||
GtkTextBuffer *buffer;
|
||
GtkTextIter start, match_start, match_end;
|
||
|
||
text = gtk_entry_get_text (entry);
|
||
|
||
if (text[0] == '\0')
|
||
return;
|
||
|
||
priv = example_app_window_get_instance_private (win);
|
||
|
||
tab = gtk_stack_get_visible_child (GTK_STACK (priv->stack));
|
||
view = gtk_bin_get_child (GTK_BIN (tab));
|
||
buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
|
||
|
||
/* Very simple-minded search implementation */
|
||
gtk_text_buffer_get_start_iter (buffer, &start);
|
||
if (gtk_text_iter_forward_search (&start, text, GTK_TEXT_SEARCH_CASE_INSENSITIVE,
|
||
&match_start, &match_end, NULL))
|
||
{
|
||
gtk_text_buffer_select_range (buffer, &match_start, &match_end);
|
||
gtk_text_view_scroll_to_iter (GTK_TEXT_VIEW (view), &match_start,
|
||
0.0, FALSE, 0.0, 0.0);
|
||
}
|
||
}
|
||
|
||
static void
|
||
example_app_window_init (ExampleAppWindow *win)
|
||
{
|
||
|
||
...
|
||
|
||
gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), search_text_changed);
|
||
|
||
...
|
||
|
||
}
|
||
|
||
...
|
||
]]></programlisting>
|
||
<para>(<ulink url="https://git.gnome.org/browse/gtk+/tree/examples/application7/exampleappwin.c">full source</ulink>)</para>
|
||
</informalexample>
|
||
|
||
<para>With the search bar, our application now looks like this:</para>
|
||
|
||
<informalfigure>
|
||
<mediaobject>
|
||
<imageobject>
|
||
<imagedata fileref="getting-started-app7.png" format="PNG"/>
|
||
</imageobject>
|
||
</mediaobject>
|
||
</informalfigure>
|
||
</section>
|
||
|
||
<section>
|
||
<title>Adding a side bar</title>
|
||
|
||
<para>As another piece of functionality, we are adding a sidebar,
|
||
which demonstrates #GtkMenuButton, #GtkRevealer and #GtkListBox.</para>
|
||
|
||
<informalexample>
|
||
<programlisting><xi:include href="../../../../examples/application8/window.ui" parse="text"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include></programlisting>
|
||
</informalexample>
|
||
|
||
<para>The code to populate the sidebar with buttons for the words
|
||
found in each file is a little too involved to go into here. But we'll
|
||
look at the code to add the gears menu.</para>
|
||
|
||
<para>As expected by now, the gears menu is specified in a GtkBuilder
|
||
ui file.</para>
|
||
|
||
<informalexample>
|
||
<programlisting><xi:include href="../../../../examples/application8/gears-menu.ui" parse="text"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include></programlisting>
|
||
</informalexample>
|
||
|
||
<para>To connect the menuitem to the show-words setting,
|
||
we use a #GSettingsAction.</para>
|
||
|
||
<informalexample>
|
||
<programlisting><![CDATA[
|
||
...
|
||
|
||
static void
|
||
example_app_window_init (ExampleAppWindow *win)
|
||
{
|
||
|
||
...
|
||
|
||
builder = gtk_builder_new_from_resource ("/org/gtk/exampleapp/gears-menu.ui");
|
||
menu = G_MENU_MODEL (gtk_builder_get_object (builder, "menu"));
|
||
gtk_menu_button_set_menu_model (GTK_MENU_BUTTON (priv->gears), menu);
|
||
g_object_unref (builder);
|
||
|
||
action = g_settings_create_action (priv->settings, "show-words");
|
||
g_action_map_add_action (G_ACTION_MAP (win), action);
|
||
g_object_unref (action);
|
||
}
|
||
|
||
...
|
||
]]></programlisting>
|
||
<para>(<ulink url="https://git.gnome.org/browse/gtk+/tree/examples/application8/exampleappwin.c">full source</ulink>)</para>
|
||
</informalexample>
|
||
|
||
<para>What our application looks like now:</para>
|
||
|
||
<informalfigure>
|
||
<mediaobject>
|
||
<imageobject>
|
||
<imagedata fileref="getting-started-app8.png" format="PNG"/>
|
||
</imageobject>
|
||
</mediaobject>
|
||
</informalfigure>
|
||
</section>
|
||
<section>
|
||
<title>Properties</title>
|
||
|
||
<para>Widgets and other objects have many useful properties.</para>
|
||
|
||
<para>Here we show some ways to use them in new and flexible ways,
|
||
by wrapping them in actions with #GPropertyAction or by binding them
|
||
with #GBinding.</para>
|
||
|
||
<para>To set this up, we add two labels to the header bar in our
|
||
window template, named @lines_label and @lines, and bind them to
|
||
struct members in the private struct, as we've seen a couple of times
|
||
by now.</para>
|
||
|
||
<para>We add a new "Lines" menu item to the gears menu, which
|
||
triggers the show-lines action:</para>
|
||
|
||
<informalexample>
|
||
<programlisting><xi:include href="../../../../examples/application9/gears-menu.ui" parse="text"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include></programlisting>
|
||
</informalexample>
|
||
|
||
<para>To make this menu item do something, we create a property
|
||
action for the visible property of the @lines label, and add it to the
|
||
actions of the window. The effect of this is that the visibility
|
||
of the label gets toggled every time the action is activated.</para>
|
||
|
||
<para>Since we want both labels to appear and disappear together,
|
||
we bind the visible property of the @lines_label widget to the
|
||
same property of the @lines widget.</para>
|
||
|
||
<informalexample>
|
||
<programlisting>
|
||
...
|
||
|
||
static void
|
||
example_app_window_init (ExampleAppWindow *win)
|
||
{
|
||
...
|
||
|
||
action = (GAction*) g_property_action_new ("show-lines", priv->lines, "visible");
|
||
g_action_map_add_action (G_ACTION_MAP (win), action);
|
||
g_object_unref (action);
|
||
|
||
g_object_bind_property (priv->lines, "visible",
|
||
priv->lines_label, "visible",
|
||
G_BINDING_DEFAULT);
|
||
}
|
||
|
||
...
|
||
</programlisting>
|
||
<para>(<ulink url="https://git.gnome.org/browse/gtk+/tree/examples/application9/exampleappwin.c">full source</ulink>)</para>
|
||
</informalexample>
|
||
|
||
<para>We also need a function that counts the lines of the currently
|
||
active tab, and updates the @lines label. See the
|
||
<ulink url="https://git.gnome.org/browse/gtk+/tree/examples/application9/exampleappwin.c">full source</ulink>
|
||
if you are interested in the details.</para>
|
||
|
||
<para>This brings our example application to this appearance:</para>
|
||
|
||
<informalfigure>
|
||
<mediaobject>
|
||
<imageobject>
|
||
<imagedata fileref="getting-started-app9.png" format="PNG"/>
|
||
</imageobject>
|
||
</mediaobject>
|
||
</informalfigure>
|
||
</section>
|
||
<section>
|
||
<title>Header bar</title>
|
||
|
||
<para>Our application already uses a GtkHeaderBar, but so far it
|
||
still gets a 'normal' window titlebar on top of that. This is a
|
||
bit redundant, and we will now tell GTK+ to use the header bar
|
||
as replacement for the titlebar. To do so, we move it around to
|
||
be a direct child of the window, and set its type to be titlebar.</para>
|
||
|
||
<informalexample>
|
||
<programlisting><xi:include href="../../../../examples/application10/window.ui" parse="text"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include></programlisting>
|
||
</informalexample>
|
||
|
||
<para>A small extra refinement that we have added here is to allow
|
||
the header bar to provide a fallback application menu, by setting
|
||
the show-fallback-app-menu property to TRUE. Here is how the
|
||
application now looks, if this fallback is used.</para>
|
||
|
||
<informalfigure>
|
||
<mediaobject>
|
||
<imageobject>
|
||
<imagedata fileref="getting-started-app10.png" format="PNG"/>
|
||
</imageobject>
|
||
</mediaobject>
|
||
</informalfigure>
|
||
|
||
<para>If we set up the window icon for our window, the menu button
|
||
will use that instead of the generic placeholder icon you see
|
||
here.
|
||
</section>
|
||
</section>
|
||
</chapter>
|