<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE book [
<!ENTITY url_refdocs_base_glib_html "https://gnome.pages.gitlab.gnome.org/glibmm/">
<!ENTITY url_refdocs_base_glib "&url_refdocs_base_glib_html;classGlib_1_1">
<!ENTITY url_refdocs_base_gio "&url_refdocs_base_glib_html;classGio_1_1">
<!ENTITY url_refdocs_base_gtk_html "https://gnome.pages.gitlab.gnome.org/gtkmm/">
<!ENTITY url_refdocs_base_gdk "&url_refdocs_base_gtk_html;classGdk_1_1">
<!ENTITY url_refdocs_base_gtk "&url_refdocs_base_gtk_html;classGtk_1_1">
<!ENTITY url_figures_base "figures/">
<!ENTITY url_examples_branch "master">
<!ENTITY url_examples_base "https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/&url_examples_branch;/examples/book/">
<!ENTITY url_gtkmm_base "https://gitlab.gnome.org/GNOME/gtkmm/tree/&url_examples_branch;/">
<!ENTITY gtkmm "<application xmlns='http://docbook.org/ns/docbook'>gtkmm</application>">
<!ENTITY uuml "ü">
<!ENTITY szlig "ß">
<!ENTITY verbar "|">
<!ENTITY copy "©">
<!ENTITY nbsp " ">
]>
<!--
NOTE TO TUTORIAL DOCUMENTATION AUTHORS:
When referring to the gtkmm project in this document, please use the form
>kmm; so that the name is consistent throughout the document. This will wrap
gtkmm with <application></application> tags which can then be styled by CSS if
desired (e.g. boldface, monospace, etc) to make it stand out as the project
name
-->
<!--
Avoid <programlisting> elements within <para> elements.
In some situations (not quite clear exactly which situations) the translation
tools (itstool and friends) can't create translated index.docbook files if
a <programlisting> element occurs in a <para> element.
<programlisting> can be a direct child of e.g. <chapter>, <listitem>,
<sect1>, <sect2>, <sect3>, <sect4>, <sect5>, <section>.
See https://gitlab.gnome.org/GNOME/gtkmm-documentation/-/merge_requests/11
-->
<book xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.0" xml:id="index" xml:lang="es">
<info>
<title xml:lang="en">Programming with <application>gtkmm</application> 4</title>
<authorgroup>
<author xml:lang="en">
<personname><firstname>Murray</firstname><surname>Cumming</surname></personname>
</author>
<author xml:lang="en">
<personname><firstname>Bernhard</firstname><surname>Rieder</surname></personname>
<contrib>Chapter on "Timeouts".</contrib>
</author>
<author xml:lang="en">
<personname><firstname>Jonathon</firstname><surname>Jongsma</surname></personname>
<contrib>Chapter on "Drawing with Cairo".</contrib>
<contrib>Chapter on "Working with gtkmm's Source Code".</contrib>
<contrib>Chapter on "Recent Files".</contrib>
</author>
<author xml:lang="en">
<personname><firstname>Ole</firstname><surname>Laursen</surname></personname>
<contrib>Parts of chapter on "Internationalization".</contrib>
</author>
<author xml:lang="en">
<personname><firstname>Marko</firstname><surname>Anastasov</surname></personname>
<contrib>Chapter on "Printing".</contrib>
<contrib>Parts of chapter on "Internationalization".</contrib>
</author>
<author xml:lang="en">
<personname><firstname>Daniel</firstname><surname>Elstner</surname></personname>
<contrib>Section "Build Structure" of chapter on "Wrapping C Libraries with gmmproc".</contrib>
</author>
<author xml:lang="en">
<personname><firstname>Chris</firstname><surname>Vine</surname></personname>
<contrib>Chapter on "Multi-threaded programs".</contrib>
</author>
<author xml:lang="en">
<personname><firstname>David</firstname><surname>King</surname></personname>
<contrib>Section on Gtk::Grid.</contrib>
</author>
<author xml:lang="en">
<personname><firstname>Pedro</firstname><surname>Ferreira</surname></personname>
<contrib>Chapter on "Keyboard Events".</contrib>
</author>
<author xml:lang="en">
<personname><firstname>Kjell</firstname><surname>Ahlstedt</surname></personname>
<contrib>Update from gtkmm 3 to gtkmm 4.</contrib>
<contrib>Chapter on "Building applications".</contrib>
<contrib>Chapter on "The DropDown Widget".</contrib>
<contrib>Chapter on "ListView, GridView, ColumnView".</contrib>
</author>
<author xml:lang="en">
<personname><firstname>Daniel</firstname><surname>Boles</surname></personname>
<contrib>Notes on need to remove widgets in non-managed wrappers from parents to dispose, other tweaks.</contrib>
</author>
</authorgroup>
<abstract>
<!-- This text is copied from the introduction. -->
<para>Este libro explica conceptos clave sobre la API <application>gtkmm</application> de C++ para la creación de interfaces de usuario. También introduce los elementos principales de la interfaz de usuario («widgets»).</para>
</abstract>
<copyright><year>2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010</year> <holder>Murray Cumming</holder></copyright>
<legalnotice>
<para>Se otorga permiso para copiar, distribuir y/o modificar este documento en virtud de los términos de la Versión 1.2 de la Licencia de Documentación Libre de GNU o de cualquier versión posterior publicada por la Fundación para el Software Libre; sin Secciones Invariantes, sin Textos de Portada y sin Textos de Contraportada. Puede conseguir una copia de la Licencia de Documentación Libre de GNU, visitando la página oficial o escribiendo a: Free Software Fundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, EE. UU.</para>
</legalnotice>
</info>
<chapter xml:id="chapter-introduction">
<title>Introducción</title>
<section xml:id="sec-this-book">
<title>Este libro</title>
<para>Este libro explica conceptos clave sobre la API <application>gtkmm</application> de C++ para la creación de interfaces de usuario. También introduce los elementos principales de la interfaz de usuario («widgets»). A pesar de que se hace mención a las clases, constructores y métodos, estas no se explican en gran detalle. Por lo tanto, para obtener información completa sobre la API, diríjase a los enlaces en la documentación de referencia.</para>
<para>Este libro asume un buen entendimiento sobre C++, y cómo crear programas en C++.</para>
<para>Nos encantaría que nos informara sobre cualquier problema que encuentre aprendiendo <application>gtkmm</application> con este documento, y apreciaríamos cualquier información sobre mejoras en el mismo. Consulte la sección <link linkend="chapter-contributing">contribuir</link> para obtener más información.</para>
</section>
<section xml:id="sec-gtkmm">
<title>gtkmm</title>
<para xml:lang="en">
<application>gtkmm</application> is a C++ wrapper for
<link xlink:href="http://www.gtk.org/">GTK</link>,
a library used to create graphical user
interfaces. It is licensed using the LGPL license, so you can develop
open software, free software, or even commercial non-free software
using <application>gtkmm</application> without purchasing licenses.
</para>
<para xml:lang="en"><application>gtkmm</application> was originally named gtk-- because GTK was originally named GTK+
and had a + in the name. However, as -- is not easily indexed by search engines,
the package generally went by the name <application>gtkmm</application>, and that's what we stuck with.</para>
<section xml:id="why-use-gtkmm">
<title xml:lang="en">Why use <application>gtkmm</application> instead of GTK?</title>
<para xml:lang="en"><application>gtkmm</application> allows you to write code using normal C++ techniques such as encapsulation, derivation, and polymorphism. As a C++ programmer you probably already realize that this leads to clearer and better organized code.</para>
<para><application>gtkmm</application> es más seguro, por lo que el compilador puede detectar errores que sólo pudieran detectarse durante ejecución al usar C. Este uso de tipos específicos también hace la API más limpia debido a que puede ver qué tipos deberían usarse con sólo mirar la declaración de un método.</para>
<para xml:lang="en">Inheritance can be used to derive new widgets. The derivation of new widgets in GTK C code is so complicated and error prone that almost no C coders do it. As a C++ developer you know that derivation is an essential Object Orientated technique.</para>
<para xml:lang="en">Member instances can be used, simplifying memory management. All GTK C widgets are dealt with by use of pointers. As a C++ coder you know that pointers should be avoided where possible.</para>
<para xml:lang="en"><application>gtkmm</application> involves less code compared to GTK, which uses prefixed function names and lots of cast macros.</para>
</section>
<section xml:id="gtkmm-vs-qt">
<title><application>gtkmm</application> comparado con Qt</title>
<para>Qt de Trolltech es el competidor más cercano de <application>gtkmm</application>, por lo que merece discusión.</para>
<para xml:lang="en"><application>gtkmm</application> developers tend to prefer <application>gtkmm</application> to Qt because <application>gtkmm</application> does things in a more C++ way. Qt originates from a time when C++ and the standard library were not standardized or well supported by compilers. It therefore duplicates a lot of stuff that is now in the standard library, such as containers and type information. Most significantly, Trolltech modified the C++ language to provide signals, so that Qt classes cannot be used easily with non-Qt classes. <application>gtkmm</application> was able to use standard C++ to provide signals without changing the C++ language.
See the <link xlink:href="https://wiki.gnome.org/Projects/gtkmm/FAQ">FAQ</link> for more detailed differences.</para>
</section>
<section xml:id="gtkmm-is-a-wrapper">
<title><application>gtkmm</application> es un envoltorio</title>
<para xml:lang="en">
<application>gtkmm</application> is not a native C++ toolkit, but a C++ wrapper of a C toolkit. This separation of interface and implementation has advantages. The <application>gtkmm</application> developers spend most of their time talking about how <application>gtkmm</application> can present the clearest API, without awkward compromises due to obscure technical details. We contribute a little to the underlying GTK code base, but so do the C coders, and the Perl coders and the Python coders, etc. Therefore GTK benefits from a broader user base than language-specific toolkits - there are more implementers, more developers, more testers, and more users.</para>
</section>
</section>
</chapter>
<chapter xml:id="chapter-installation">
<title>Instalación</title>
<section xml:id="sec-installation-dependencies">
<title>Dependecias</title>
<para xml:lang="en">
Before attempting to install <application>gtkmm</application><application>-4.0</application>,
you might first need to install these other packages.
</para>
<itemizedlist>
<listitem><para><application>sigc++-3.0</application></para></listitem>
<listitem><para><application>gtk4</application></para></listitem>
<listitem><para><application>glibmm-2.68</application></para></listitem>
<listitem><para><application>cairomm-1.16</application></para></listitem>
<listitem><para><application>pangomm-2.48</application></para></listitem>
</itemizedlist>
<para>Estas dependencias tienen sus propias dependencias, incluyendo las siguientes aplicaciones y bibliotecas:</para>
<itemizedlist>
<listitem><para><application>pkg-config</application></para></listitem>
<listitem><para><application>glib-2.0</application></para></listitem>
<listitem><para><application>pango</application></para></listitem>
<listitem><para><application>cairo</application></para></listitem>
<listitem><para><application>gdk-pixbuf-2.0</application></para></listitem>
<listitem><para><application>graphene-1.0</application></para></listitem>
</itemizedlist>
</section>
<section xml:id="sec-install-unix-and-linux">
<title>Unix y Linux</title>
<section xml:id="sec-linux-install-from-packages">
<title>Paquetes preconstruidos</title>
<para>Las versiones recientes de <application>gtkmm</application> se empaquetan en casi todas las distribuciones principales de Linux actualmente. Si usted utiliza Linux, es probable que pueda empezar a trabajar con <application> gtkmm </application> instalando el paquete desde el repositorio oficial para su distribución Linux. Las distribuciones que cuentan con <application>gtkmm</application> en sus repositorios son Debian, Ubuntu, Red Hat, Fedora, Mandriva, Suse, y muchas otras.</para>
<para xml:lang="en">
The names of the <application>gtkmm</application> packages vary from distribution to distribution
(e.g. <application>libgtkmm-4.0-dev</application> on Debian and Ubuntu or
<application>gtkmm4.0-devel</application> on Red Hat and Fedora), so check
with your distribution's package management program for the correct package
name and install it like you would any other package.
</para>
<note>
<para xml:lang="en">
The package names will not change when new API/ABI-compatible versions of <application>gtkmm</application>
are released. Otherwise they would not be API/ABI-compatible. So don't be
surprised, for instance, to find <application>gtkmm</application> 4.8 supplied by Debian's
<application>libgtkmm-4.0-dev</application> package.
</para>
</note>
</section>
<section xml:id="sec-install-from-source">
<title>Instalar desde las fuentes</title>
<para xml:lang="en">
If your distribution does not provide a pre-built <application>gtkmm</application> package, or if you
want to install a different version than the one provided by your distribution,
you can also install <application>gtkmm</application> from source. The source code for <application>gtkmm</application> can
be downloaded from <link xlink:href="https://download.gnome.org/sources/gtkmm/"/>.
</para>
<para xml:lang="en">
After you've installed all of the dependencies, download the <application>gtkmm</application> source
code, unpack it, and change to the newly created directory. <application>gtkmm</application> can be
built with Meson. See the <filename>README</filename> file in the <application>gtkmm</application> version
you've downloaded.
</para>
<note>
<para>Recuerde que en un sistema Unix o Linux, probablemente tendrá que ser <literal>root</literal> para instalar el software. Los comandos <command>su</command> o <command>sudo</command> le permitirán introducir la contraseña de <literal>root</literal>para tener el acceso de <literal>root</literal> temporalmente.</para>
</note>
<para xml:lang="en">
The <filename>configure</filename> script or <command>meson</command> will check
to make sure all of the required dependencies are already installed. If you are
missing any dependencies, it will exit and display an error.
</para>
<para xml:lang="en">
By default, <application>gtkmm</application> if built with Meson or Autotools, will be installed under the
<filename>/usr/local</filename> directory. On some systems you may need to
install to a different location. For instance, on Red Hat Linux systems
you might use the <literal>--prefix</literal> option with configure, like
one of:
<screen xml:lang="en">
# meson setup --prefix=/usr <builddir> <srcdir>
# meson configure --prefix=/usr
# ./configure --prefix=/usr
</screen>
</para>
<warning>
<para>Debe tener mucho cuidado al instalar en prefijos estándar del sistema, como <filename>/usr</filename>. Las distribuciones de Linux instalan paquetes de software en <filename>/usr</filename>, por lo que instalar un paquete de fuentes en este prefijo puede corromper o crear un conflicto con el software instalado usando el sistema de gestión de paquetes de su distribución. De manera ideal, debería usar un prefijo separado para todo el software que instale desde las fuentes.</para>
</warning>
<para>Si quiere ayudar al desarrollo de <application>gtkmm</application> o experimentar con nuevas características, puede instalar <application>gtkmm</application> desde git. La mayoría de los usuarios nunca tendrán que hacer esto, pero si está interesado en involucrarse directamente con el desarrollo de <application>gtkmm</application>, consulte el apéndice <link linkend="chapter-working-with-source">Trabajando con el código fuente de gtkmm</link>.</para>
</section>
</section>
<section xml:id="sec-packages-windows">
<title>Microsoft Windows</title>
<para xml:lang="en">GTK and <application>gtkmm</application> were designed to work well with Microsoft Windows, and the
developers encourage its use on the win32 platform. However, Windows has no standard
installation system for development libraries. Please see the
<link xlink:href="https://wiki.gnome.org/Projects/gtkmm/MSWindows">Windows Installation</link>
page or the <link linkend="sec-windows-installation"><application>gtkmm</application> and Win32</link> appendix
for Windows-specific installation instructions and notes.</para>
</section>
</chapter>
<chapter xml:id="chapter-basics">
<title>Conceptos básicos</title>
<para>En este capítulo se presentan algunos de los aspectos más importantes de la codificación <application>gtkmm</application>. Que se validarán con un ejemplo de trabajo de código. Sin embargo, esta es sólo una pequeña muestra, por lo que es necesario leer otros capítulos para obtener más información importante.</para>
<para>Conocer C++ le ayudará con <application>gtkmm</application> como lo haría con cualquier otra biblioteca. A menos que se indique lo contrario, el comportamiento de las clases de <application>gtkmm</application> será como el de cualquier otra clase C++, de manera que podrá utilizar sus actuales técnicas de C++ con las clases de <application>gtkmm</application>.</para>
<section xml:id="sec-basics-simple-example">
<title>Ejemplo simple</title>
<para>Para iniciar nuestra introducción a <application>gtkmm</application>, vamos a empezar con el programa más simple posible. Este programa va a crear una ventana vacía de 200 x 200 píxeles.</para>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/base">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>base.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include <gtkmm.h>
class MyWindow : public Gtk::Window
{
public:
MyWindow();
};
MyWindow::MyWindow()
{
set_title("Basic application");
set_default_size(200, 200);
}
int main(int argc, char* argv[])
{
auto app = Gtk::Application::create("org.gtkmm.examples.base");
return app->make_window_and_run<MyWindow>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
<para xml:lang="en">We will now explain each part of the example</para>
<programlisting xml:lang="en"><code>#include <gtkmm.h></code></programlisting>
<para>Todos los programas <application>gtkmm</application> deben incluir ciertas cabeceras <application>gtkmm</application>: <literal>gtkmm.h</literal> incluye el kit completo de <application>gtkmm</application>. Esto no suele ser una buena idea, ya que incluye casi un megabyte de cabeceras, pero para programas sencillos, basta.</para>
<para xml:lang="en">The next part of the program:</para>
<programlisting xml:lang="en"><code>class MyWindow : public Gtk::Window
{
public:
MyWindow();
};
MyWindow::MyWindow()
{
set_title("Basic application");
set_default_size(200, 200);
}</code></programlisting>
<para xml:lang="en">
defines the <classname>MyWindow</classname> class. Its default constructor sets the
window's title and default (initial) size.
</para>
<para xml:lang="en">The <function>main()</function> function's first statement:</para>
<programlisting xml:lang="en"><code>auto app = Gtk::Application::create("org.gtkmm.examples.base");</code></programlisting>
<para xml:lang="en">
creates a <classname>Gtk::Application</classname> object, stored in a <classname>Glib::RefPtr</classname> smartpointer.
This is needed in all <application>gtkmm</application> applications. The <methodname>create()</methodname> method for this object initializes <application>gtkmm</application>.
</para>
<para xml:lang="en">
The last line creates and shows a window and enters the <application>gtkmm</application> main processing loop, which will finish when the window is closed.
Your <function>main()</function> function will then return with an appropriate success or error code.
The <parameter>argc</parameter> and <parameter>argv</parameter> arguments, passed to your application on the command line,
can be checked when <methodname>make_window_and_run()</methodname> is called, but this simple application does not use those arguments.
</para>
<programlisting xml:lang="en"><code>return app->make_window_and_run<MyWindow>(argc, argv);</code></programlisting>
<para xml:lang="en">
After putting the source code in <literal>simple.cc</literal> you can compile
the above program with <application>gcc</application> using:
</para>
<programlisting xml:lang="en"><code>g++ simple.cc -o simple `pkg-config --cflags --libs gtkmm-4.0` -std=c++17</code></programlisting>
<para xml:lang="en">
Note that you must surround the <literal>pkg-config</literal> invocation with backquotes.
Backquotes cause the shell to execute the command inside them, and to use
the command's output as part of the command line.
Note also that <literal>simple.cc</literal> must come before the <literal>pkg-config</literal>
invocation on the command line. <literal>-std=c++17</literal> is necessary only if
your compiler is not C++17 compliant by default.
</para>
</section>
<section xml:id="sec-headers-and-linking">
<title>Cabeceras y enlazado</title>
<para xml:lang="en">
Although we have shown the compilation command for the simple example, you really
should use the <link xlink:href="https://mesonbuild.com/">Meson build system</link>.
The examples used in this book are included in the <application>gtkmm-documentation</application>
package, with appropriate build files, so we won't show the build commands in future.
The <filename>README</filename> file in <application>gtkmm-documentation</application>
describes how to build the examples.
</para>
<para>Para simplificar la compilación, se usa <literal>pkg-config</literal>, que está presente en todas las instalaciones correctas de <application>gtkmm</application>. Este programa «sabe» qué opciones de compilación son necesarias para compilar los programas que usan <application>gtkmm</application>. La opción <literal>--cflags</literal> hace que <literal>pkg-config</literal> devuelva la lista de carpetas en las que el compilador buscará los archivos de cabecera; la opción <literal>--libs</literal> solicita la lista de bibliotecas a las que el compilador enlazará y las carpetas en las que encontrarlas. Intente ejecutarlo desde su intérprete de comandos para ver los resultados en su sistema.</para>
<para xml:lang="en">
However, this is even simpler when using the <function>dependency()</function> function
in a <filename>meson.build</filename> file with Meson. For instance:
</para>
<programlisting xml:lang="en"><code>gtkmm_dep = dependency('gtkmm-4.0', version: '>= 4.6.0')</code></programlisting>
<para xml:lang="en">
This checks for the presence of gtkmm and defines <literal>gtkmm_dep</literal> for use
in your <filename>meson.build</filename> files. For instance:
</para>
<programlisting xml:lang="en"><code>exe_file = executable('my_program', 'my_source1.cc', 'my_source2.cc',
dependencies: gtkmm_dep,
win_subsystem: 'windows',
)</code></programlisting>
<para xml:lang="en">gtkmm-4.0 is the name of the current stable API. There are older APIs called gtkmm-2.4
and gtkmm-3.0 which install in parallel when they are available. There are several
versions of gtkmm-2.4, such as gtkmm 2.10 and there are several versions of the gtkmm-3.0 API.
Note that the API name does not change for every version because that would be an incompatible
API and ABI break. There might be a future gtkmm-5.0 API which would install in parallel
with gtkmm-4.0 without affecting existing applications.
</para>
<para xml:lang="en">If you start by experimenting with a small application that you plan to use just for yourself,
it's easier to start with a <filename>meson.build</filename> similar to the <filename>meson.build</filename> files
in the <link linkend="chapter-building-applications">Building applications</link> chapter.
</para>
<para xml:lang="en">If you use the older Autotools build system, see also the GNU site. It has more
information about <link xlink:href="https://www.gnu.org/software/autoconf/">autoconf</link>
and <link xlink:href="https://www.gnu.org/software/automake/">automake</link>.
There are also some books describing Autotools: "GNU Autoconf, Automake, and Libtool"
by Gary Vaughan et al. and "Autotools, A Practitioner's Guide to GNU Autoconf,
Automake, and Libtool" by John Calcote.
</para>
</section>
<section xml:id="sec-widgets-overview">
<title>Widgets</title>
<para>Las aplicaciones <application>gtkmm</application> están compuestas por ventanas, estas a su vez contienen widgets, tales como botones y cuadros de texto. En algunos otros sistemas, los widgets se llaman «controles». Hay un objeto C++ en el código de la aplicación para cada widget contenido en las ventanas de una aplicación. Sólo debe llamar a un método de la clase widget para afectar al widget visible.</para>
<para xml:lang="en">Widgets are arranged inside container widgets such as frames and notebooks, in a hierarchy of widgets within widgets. Some of these container widgets, such as <classname>Gtk::Grid</classname>, are not visible - they exist only to arrange other widgets. Here is some example code that adds 2 <classname>Gtk::Button</classname> widgets to a <classname>Gtk::Box</classname> container widget:
</para>
<programlisting xml:lang="en"><code>m_box.append(m_Button1);
m_box.append(m_Button2);</code></programlisting>
<para xml:lang="en">and here is how to add the <classname>Gtk::Box</classname>, containing those buttons, to a <classname>Gtk::Frame</classname>, which has a visible frame and title:
</para>
<programlisting xml:lang="en"><code>m_frame.set_child(m_box);</code></programlisting>
<para>La mayoría de los capítulos de este libro tratan de widgets específicos. Consulte la sección <link linkend="chapter-container-widgets">Widgets contenedores</link> para obtener más detalles sobre de cómo agregar widgets a widgets contenedores.</para>
<para xml:lang="en">Although you can specify the layout and appearance of windows and widgets with C++ code,
you will probably find it more convenient to design your user interfaces with
<filename class="extension">.ui</filename> XML files and load them at runtime with
<classname>Gtk::Builder</classname>. See the <link linkend="chapter-builder">Gtk::Builder</link> chapter.
</para>
<para xml:lang="en">Although <application>gtkmm</application> widget instances have lifetimes and scopes just like
those of other C++ classes, <application>gtkmm</application> has an optional time-saving feature that you
will see in some of the examples. The <function>Gtk::make_managed()</function>
allows you to create a new widget and state that it will become owned by the
container into which you place it. This allows you to create the widget, add it
to the container and not be concerned about deleting it, since that will occur
when the parent container (which may itself be managed) is deleted. You can
learn more about <application>gtkmm</application> memory management techniques in the
<link linkend="chapter-memory">Memory Management chapter</link>.
</para>
</section>
<section xml:id="sec-signals-overview">
<title>Señales</title>
<para><application>gtkmm</application>, como la mayoría de kits de herramientas de la IGU, está <emphasis>dirigido por eventos</emphasis>. Cuando ocurre un evento, como la pulsación de un botón del ratón sobre un widget, éste emitirá la señal apropiada. Cada widget puede emitir un conjunto de señales diferente. Para hacer que la pulsación de un botón resulte en una acción, establecemos un <emphasis>gestor de señales</emphasis> para atrapar la señal «clicked» del botón.</para>
<para xml:lang="en"><application>gtkmm</application> uses the libsigc++ library to implement signals. Here is an example line of code that connects a Gtk::Button's "clicked" signal with a signal handler called "on_button_clicked":
</para>
<programlisting xml:lang="en"><code>m_button1.signal_clicked().connect( sigc::mem_fun(*this,
&HelloWorld::on_button_clicked) );</code></programlisting>
<para>Para obtener información más detallada acerca de señales, consulte el <link linkend="chapter-signals">apéndice</link>.</para>
<para>Para obtener información acerca de la implementación de sus propias señales en vez de sólo conectar a las señales existentes de <application>gtkmm</application>, consulte el <link linkend="chapter-custom-signals">apéndice</link>.</para>
</section>
<section xml:id="sec-basics-ustring">
<title>Glib::ustring</title>
<para>Puede sorprenderle aprender que <application>gtkmm</application> no utiliza <classname>std::string</classname> en sus interfaces. En cambio, usa <classname>Glib::ustring</classname>, que es tan similar y discreto que en realidad puede pretender que cada <classname>Glib::ustring</classname> es un <classname>std::string</classname> e ignorar el resto de esta sección. Pero siga leyendo si quiere aplicar otros lenguajes además del inglés en su aplicación.</para>
<para xml:lang="en">std::string uses 8 bits per character, but 8 bits aren't enough to encode languages such as Arabic, Chinese, and Japanese.
Although the encodings for these languages have been specified by the <link xlink:href="http://www.unicode.org/">Unicode Consortium</link>,
the C and C++ languages do not yet provide any standardized Unicode support for UTF-8 encoding.
GTK and GNOME chose to implement Unicode using UTF-8, and that's what is wrapped by Glib::ustring.
It provides almost exactly the same interface as std::string, along with automatic conversions to and from std::string.</para>
<para>Uno de los beneficios de UTF-8 es que no necesita usarlo a menos que quiera, por lo que no necesita reconvertir todo su código de una vez. <classname>std::string</classname> todavía funcionará para cadenas de 7 bits ASCII. Pero cuando intente localizar su aplicación para lenguajes como chino, por ejemplo, empezará a ver errores extraños y posibles fallos. Entonces, todo lo que necesitará hacer es empezar a utilizar <classname>Glib::ustring</classname> en su lugar.</para>
<para>tenga en cuenta que UTF-8 no es compatible con codificaciones de 8 bits como ISO-8859-1. Por ejemplo, las diéresis alemanas no están en el rango ASCII y necesitan más de un byte en la codificación UTF-8. Si su código contiene cadenas literales de 8 bits, debe convertirlas a UTF-8 (por ejemplo, el saludo bávaro «Grüß Gott» sería «Gr\xC3\xBC\xC3\x9F Gott»).</para>
<para>Evite la aritmética de punteros al estilo C, y funciones como strlen(). En UTF-8, cada carácter podría necesitar entre 1 y 6 bytes, por lo que no es posible asumir que el próximo byte es otro carácter. <classname>Glib::ustring</classname> se ocupa de estos detalles, por lo que puede usar métodos como Glib::ustring::substr() y seguir pensando en términos de caracteres en vez de bytes.</para>
<para>A diferencia de la solución Unicode de Windows UCS-2, esto no requiere ninguna opción especial de compilación para procesar cadenas literales, y no resulta en que los ejecutables y bibliotecas Unicode sean incompatibles con las ASCII.</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/glibmm/classGlib_1_1ustring.html">Reference</link></para>
<para>Para obtener más información acerca de cómo proporcionar cadenas literales UTF-8, consulte la sección <link linkend="chapter-internationalization">Internacionalización</link>.</para>
</section>
<section xml:id="sec-basics-gobj-and-wrap">
<title>Mezclando las API de C y C++</title>
<para xml:lang="en">You can use C APIs which do not yet have convenient C++ interfaces.
It is generally not a problem to use C APIs from C++, and <application>gtkmm</application> helps by
providing access to the underlying C object, and providing an easy way to create
a C++ wrapper object from a C object, provided that the C API is also based on
the <classname>GObject</classname> system.</para>
<para xml:lang="en">
To use a <application>gtkmm</application> instance with a C function that requires a C
<classname>GObject</classname> instance, use the C++ instance’s
<function>gobj()</function> function to obtain a pointer to the underlying C
instance. For example:</para>
<programlisting xml:lang="en"><code>Gtk::Button button("example");
gtk_button_do_something_that_gtkmm_cannot(button.gobj());
</code></programlisting>
<para xml:lang="en">
To obtain a <application>gtkmm</application> instance from a C <classname>GObject</classname> instance,
use one of the many overloaded <function>Glib::wrap()</function> functions.
The C instance’s reference count is not incremented, unless you set the optional
<parameter>take_copy</parameter> argument to <literal>true</literal>. For
example:</para>
<programlisting xml:lang="en"><code>GtkButton* cbutton = get_a_button();
Gtk::Button* button = Glib::wrap(cbutton);
button->set_label("Now I speak C++ too!");
</code></programlisting>
<para xml:lang="en">The C++ wrapper shall be explicitly deleted if
<itemizedlist>
<listitem><para xml:lang="en">it's a widget or other class that inherits from <classname>Gtk::Object</classname>, and</para></listitem>
<listitem><para xml:lang="en">the C instance has a floating reference when the wrapper is created, and</para></listitem>
<listitem><para xml:lang="en"><function>Gtk::manage()</function> has not been called on it (which includes if it was created with <function>Gtk::make_managed()</function>), or</para></listitem>
<listitem><para xml:lang="en"><function>Gtk::manage()</function> was called on it, but it was never added to a parent.</para></listitem>
</itemizedlist>
<function>Glib::wrap()</function> binds the C and C++ instances to each other.
Don't delete the C++ instance before you want the C instance to die.
</para>
<para xml:lang="en">In all other cases the C++ instance is automatically deleted when the last reference
to the C instance is dropped. This includes all <function>Glib::wrap()</function>
overloads that return a <classname>Glib::RefPtr</classname>.</para>
</section>
<section xml:id="sec-helloworld">
<title>«Hola mundo» en <application>gtkmm</application></title>
<para>Ahora ha aprendido lo suficiente para ver un ejemplo real. De acuerdo con una antigua tradición de la informática, se introducirá la aplicación Hola Mundo en <application>gtkmm</application>:</para>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/helloworld">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>helloworld.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLE_HELLOWORLD_H
#define GTKMM_EXAMPLE_HELLOWORLD_H
#include <gtkmm/button.h>
#include <gtkmm/window.h>
class HelloWorld : public Gtk::Window
{
public:
HelloWorld();
~HelloWorld() override;
protected:
//Signal handlers:
void on_button_clicked();
//Member widgets:
Gtk::Button m_button;
};
#endif // GTKMM_EXAMPLE_HELLOWORLD_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>helloworld.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "helloworld.h"
#include <iostream>
HelloWorld::HelloWorld()
: m_button("Hello World") // creates a new button with label "Hello World".
{
// Sets the margin around the button.
m_button.set_margin(10);
// When the button receives the "clicked" signal, it will call the
// on_button_clicked() method defined below.
m_button.signal_clicked().connect(sigc::mem_fun(*this,
&HelloWorld::on_button_clicked));
// This packs the button into the Window (a container).
set_child(m_button);
}
HelloWorld::~HelloWorld()
{
}
void HelloWorld::on_button_clicked()
{
std::cout << "Hello World" << std::endl;
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "helloworld.h"
#include <gtkmm/application.h>
int main(int argc, char* argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
//Shows the window and returns when it is closed.
return app->make_window_and_run<HelloWorld>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
<para>Intente compilarla y ejecutarla antes de continuar. Debería ver algo así:</para>
<figure xml:id="figure-helloworld">
<title>Hello World</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/helloworld.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para>Fascinante, ¿verdad? Examinemos el código. Primero, la clase <classname>HelloWorld</classname>:</para>
<programlisting xml:lang="en"><code>class HelloWorld : public Gtk::Window
{
public:
HelloWorld();
~HelloWorld() override;
protected:
//Signal handlers:
void on_button_clicked();
//Member widgets:
Gtk::Button m_button;
};</code></programlisting>
<para xml:lang="en">
This class implements the "Hello World" window. It's derived from
<classname>Gtk::Window</classname>, and has a single <classname>Gtk::Button</classname> as a member.
We've chosen to use the
constructor to do all of the initialization work for the window,
including setting up the signals. Here it is, with the comments
omitted:
</para>
<programlisting xml:lang="en"><code>HelloWorld::HelloWorld()
: m_button("Hello World")
{
m_button.set_margin(10);
m_button.signal_clicked().connect(sigc::mem_fun(*this,
&HelloWorld::on_button_clicked));
set_child(m_button);
}</code></programlisting>
<para xml:lang="en">
Notice that we've used an initializer statement to give the <literal>m_button</literal>
object the label "Hello World".
</para>
<para xml:lang="en">
Next we call the Button's <methodname>set_margin()</methodname> method. This sets
the amount of space around the button.
</para>
<para>Luego, se le engancha un gestor de señales a la señal <literal>clicked</literal> de <literal>m_button</literal>. Esto imprime nuestro amigable saludo a <literal>stdout</literal>.</para>
<para xml:lang="en">
Next, we use the Window's <methodname>set_child()</methodname> method to put
<literal>m_button</literal> in the Window. The <methodname>set_child()</methodname>
method places the Widget in the Window.
</para>
<para>Ahora eche un vistazo a la función <function>main()</function> del programa. Aquí está, sin comentarios:</para>
<programlisting xml:lang="en"><code>int main(int argc, char* argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
return app->make_window_and_run<HelloWorld>(argc, argv);
}</code></programlisting>
<para xml:lang="en">
First we instantiate an object stored in a <classname>Glib::RefPtr</classname> smartpointer called <literal>app</literal>. This is of type
<classname>Gtk::Application</classname>. Every <application>gtkmm</application> program must have one of these.
</para>
<para xml:lang="en">
Next we call <methodname>make_window_and_run()</methodname> which creates an object
of our <classname>HelloWorld</classname> class, shows that Window and starts the <application>gtkmm</application>
<emphasis>event loop</emphasis>. During the event loop <application>gtkmm</application> idles, waiting for actions
from the user, and responding appropriately.
When the user closes the Window, <methodname>make_window_and_run()</methodname> will return,
causing our <function>main()</function> function to return. The application will then finish.
</para>
<para xml:lang="en">
Like the simple example we showed earlier, this Hello World program does not use
the command-line parameters.
</para>
</section>
</chapter>
<chapter xml:id="changes-gtkmm3">
<title>Cambios en <application>gtkmm</application> 3</title>
<para xml:lang="en"><application>gtkmm</application>-3.0 is an old version of the <application>gtkmm</application> API that installs in parallel with the still older <application>gtkmm</application>-2.4 API and the new <application>gtkmm</application>-4.0 API. The last version of the <application>gtkmm</application>-2.4 API was <application>gtkmm</application> 2.24. <application>gtkmm</application> 3 has no major fundamental differences to <application>gtkmm</application> 2 but does make several small changes that were not possible while maintaining binary compatibility. If you never used the <application>gtkmm</application>-2.4 API then you can safely ignore this chapter.</para>
<para>La biblioteca de <application>gtkmm</application> 3 se llama <literal>libgtkmm-3.0</literal>, en vez de <literal>libgtkmm-2.4</literal> e instala sus archivos de cabecera en una carpeta versionada similarmente, por lo que su verificación pkg-config debería buscar <literal>gtkmm-3.0</literal> en vez de <literal>gtkmm-2.4</literal>.</para>
<para><application>gtkmm</application> 3 añadió algunas clases nuevas:</para>
<orderedlist inheritnum="ignore" continuation="restarts">
<listitem><simpara><classname>Gtk::AppChooser</classname>, <classname>Gtk::AppChooserButton</classname>, <classname>Gtk::AppChooserDialog</classname> permiten al usuario seleccionar una aplicación instalada para abrir un determinado tipo de contenido.</simpara></listitem>
<listitem><simpara><classname>Gtk::Grid</classname> es un widget contenedor nuevo que eventualmente reemplazará a <classname>Gtk::Box</classname> y a <classname>Gtk::Table</classname>. Ordena a sus hijos de acuerdo a sus propiedades en lugar de sus propios detalles de disposición.</simpara></listitem>
<listitem><simpara xml:lang="en"><classname>Gtk::Switch</classname> displays On/Off states more explicitly than <classname>Gtk::CheckButton</classname>. It may be useful, for instance, when allowing users to activate hardware.</simpara></listitem>
</orderedlist>
<para><application>gtkmm</application> 3 también hizo varios cambios pequeños a la API, que probablemente encontrará cuando porte código que usaba <application>gtkmm</application>-2.4. Aquí hay una lista corta:</para>
<para>
<orderedlist inheritnum="ignore" continuation="restarts">
<listitem><simpara><classname>Gtk::CellLayout</classname>, usada por <classname>Gtk::IconView</classname>, <classname>Gtk::TreeView::Column</classname> y <classname>Gtk::ComboBox</classname> ahora tiene una <classname>Gtk::CellArea</classname>, que puede usarse para especificar más detalles acerca de cómo las <classname>CellRenderer</classname> se ordenan y alinean.</simpara></listitem>
<listitem><simpara>Gtk::ComboBox ahora deriva de CellLayout, permitiendo una disposición y alineación más fácil de sus <classname>Gtk::CellRenderer</classname>.</simpara></listitem>
<listitem><simpara><classname>Gtk::Adjustment</classname>, <classname>IconSet</classname> y <classname>Gdk::Cursor</classname> se usan ahora a través de <classname>Glib::RefPtr</classname>.</simpara></listitem>
<listitem><simpara><classname>Gtk::Box</classname>, <classname>Gtk::ButtonBox</classname>, <classname>Gtk::IconView</classname>, <classname>Gtk::Paned</classname>, <classname>Gtk::ProgressBar</classname>, <classname>Gtk::ScaleButton</classname>, <classname>Gtk::Scrollbar</classname> y <classname>Gtk::Separator</classname> ahora derivan de <classname>Gtk::Orientable</classname>, permitiendo especificar su orientación (vertical u horizontal) sin requerir el uso de una clase derivada como <classname>Gtk::HBox</classname>.</simpara></listitem>
<listitem><simpara><classname>Gtk::IconView</classname>, <classname>Gtk::TextView</classname>, <classname>Gtk::TreeView</classname> y otros widgets derivan de Scrollable en vez de tener sus propios métodos como <methodname>get_vadjustment()</methodname> y su propia señal set_scroll_adjustments.</simpara></listitem>
<listitem><simpara><classname>Gtk::Style</classname> y <classname>Gtk::Rc</classname> se quitaron y se reemplazaron por <classname>Gtk::StyleContext</classname>, y <classname>Gtk::StyleProvider</classname>, así como <classname>Gtk::CssProvider</classname>.</simpara></listitem>
<listitem><simpara>Widget::on_expose_event() se reemplazó por Widget::on_draw(), que asume que cairomm se usa para dibujar, a través del <classname>Cairo::Context</classname> provisto y no requiere que llame a <methodname>Cairo::Context::clip()</methodname>.</simpara></listitem>
<listitem><simpara><classname>Gdk::RGBA</classname> reemplaza a <classname>Color</classname>, añadiendo un componente alfa para la opacidad. <classname>Colormap</classname> se eliminó, junto con su molesto uso para asignar colores.</simpara></listitem>
<listitem><simpara xml:lang="en"><classname>Gdk::Pixmap</classname> and <classname>Gdk::Bitmap</classname> were removed in favor of <classname>Gdk::Pixbuf</classname>.</simpara></listitem>
<listitem><simpara><classname>Gdk::Drawable</classname> se quitó, y sus métodos se movieron a <classname>Gdk::Window</classname>.</simpara></listitem>
<listitem><simpara>Ahora se usa std::vector en muchos métodos en vez de los tipos intermedios *Handle para hacer a la API más clara.</simpara></listitem>
</orderedlist>
</para>
<para xml:lang="en">All deprecated API was removed in <application>gtkmm</application> 3.0, though there have been new deprecations in later <application>gtkmm</application> 3.x versions.</para>
<para xml:lang="en">As a first step to porting your source code to <application>gtkmm</application>-3.0 you should probably ensure that your application builds with the deprecated <application>gtkmm</application>-2.4 API disabled, by defining macro such as GTKMM_DISABLE_DEPRECATED. There are some autotools macros that can help with this by defining them optionally at build time. See the <link xlink:href="https://wiki.gnome.org/Projects/gtkmm/PortingToGtkmm3">gtkmm 3 porting wiki page</link> for more details.</para>
</chapter>
<chapter xml:id="changes-gtkmm4">
<title xml:lang="en">Changes in <application>gtkmm</application>-4.0 and <application>glibmm-2.68</application></title>
<para xml:lang="en"><application>gtkmm</application>-4.0 is a new version of the <application>gtkmm</application> API that installs in parallel with the
older <application>gtkmm</application>-2.4 and <application>gtkmm</application>-3.0 APIs. The last version of the <application>gtkmm</application>-3.0 API
is <application>gtkmm</application> 3.24. <application>gtkmm</application> 4 has no major fundamental differences to <application>gtkmm</application> 3 but
does make several changes (both small and large ones) that were not possible while
maintaining binary compatibility. If you never used the <application>gtkmm</application>-3.0 API then you
can safely ignore this chapter.
</para>
<para xml:lang="en"><application>gtkmm</application> 4's library is called <literal>libgtkmm-4.0</literal> rather than
<literal>libgtkmm-3.0</literal> and installs its headers in a similarly-versioned
directory, so your <application>pkg-config</application> check should ask for
<literal>gtkmm-4.0</literal> rather than <literal>gtkmm-3.0</literal>.
</para>
<para xml:lang="en"><application>gtkmm</application>-4.0 is used in combination with <application>glibmm-2.68</application>,
which sets the global locale for your program. The older <application>glibmm-2.4</application>
does not do that, and <application>gtkmm</application>-3.0 does it only to some extent. What this means is
briefly that if your <application>gtkmm</application>-3.0 program contains a call to
<function>std::locale::global(std::locale(""))</function>, you can probably remove it.
If you don't want <application>glibmm</application> or <application>gtkmm</application>
to set the global locale for you, you should add a call to
<function>Glib::set_init_to_users_preferred_locale(false)</function> before any call to
<function>Glib::init()</function> or <methodname>Gtk::Application::create()</methodname>.
See the <application>glibmm</application> <link xlink:href="https://gnome.pages.gitlab.gnome.org/glibmm/namespaceGlib.html">
reference</link>.</para>
<para xml:lang="en">There are lots and lots of differences between <application>gtkmm</application>-3.0 and <application>gtkmm</application>-4.0.
The following lists are not complete.</para>
<para xml:lang="en">There are some important behavioural changes, to which you must adapt when migrating:</para>
<para>
<itemizedlist>
<listitem><simpara xml:lang="en">
Whereas in <application>gtkmm</application>-3.0, destruction of a non-managed C++ widget wrapper caused the wrapped GtkWidget
to be destroyed, changes in GTK4 mean that in <application>gtkmm</application>-4.0 this canʼt always be the case. GTK4 has no
uniform way to remove a widget from a parent, as GTK3 did with <methodname>Gtk::Container::remove()</methodname>,
so <application>gtkmm</application>-4.0 canʼt do the removal for you as <application>gtkmm</application>-3.0 did. Hence, if a non-managed C++ widget
instance is destructed while the widget is a child of another, <application>gtkmm</application>-4.0 wonʼt remove it from the
parent, the parent retains a ref that stops the child being disposed, and the GtkWidget stays alive.
The end result is that to destroy a non-managed widget with its C++ wrapper you must first remove it
from its parent (however that parent allows) so the C++ wrapperʼs dtor can drop the final reference.
</simpara></listitem>
</itemizedlist>
</para>
<para xml:lang="en">Some new classes were added in <application>gtkmm</application> 4 and <application>glibmm</application> 2.68:</para>
<orderedlist inheritnum="ignore" continuation="restarts">
<listitem><simpara xml:lang="en"><classname>Glib::ExtraClassInit</classname> and <classname>Gtk::Snapshot</classname>:
These classes are needed only for writing custom widgets. See the
<link linkend="sec-custom-widgets">Custom Widgets</link> section.</simpara></listitem>
<listitem><simpara xml:lang="en"><classname>Gtk::EventControllerKey</classname>,
<classname>Gtk::EventControllerMotion</classname>, <classname>Gtk::EventControllerScroll</classname>
and <classname>Gtk::GestureStylus</classname></simpara></listitem>
<listitem><simpara xml:lang="en"><classname>Gdk::Paintable</classname>, <classname>Gdk::Texture</classname>,
<classname>Gtk::Picture</classname> and <classname>Gtk::WidgetPaintable</classname>
</simpara></listitem>
<listitem><simpara xml:lang="en"><classname>Gdk::Window</classname> has been renamed to <classname>Gdk::Surface</classname>.
(<classname>Gtk::Window</classname> keeps its name.)</simpara></listitem>
<listitem><simpara xml:lang="en"><classname>Gdk::DrawContext</classname> and <classname>Gdk::CairoContext</classname>
are new. <classname>Gdk::DrawingContext</classname> has been removed.</simpara></listitem>
<listitem><simpara xml:lang="en"><classname>Gtk::Clipboard</classname> has been replaced by the new
<classname>Gdk::Clipboard</classname>.</simpara></listitem>
<listitem><simpara xml:lang="en"><classname>Gdk::DragContext</classname> has been split into
<classname>Gdk::Drag</classname> and <classname>Gdk::Drop</classname>.</simpara></listitem>
</orderedlist>
<para xml:lang="en">There have also been several changes to the API, which you will probably encounter
when porting code that used <application>gtkmm</application>-3.0 and <application>glibmm</application>-2.4. Here is a short list:</para>
<para>
<orderedlist inheritnum="ignore" continuation="restarts">
<listitem><simpara xml:lang="en">A C++17 compiler is required.</simpara></listitem>
<listitem><simpara xml:lang="en"><classname>Gtk::Button</classname>, <classname>Gtk::ToolButton</classname>,
<classname>Gtk::MenuItem</classname> and <classname>Gtk::Switch</classname>
implement the <classname>Gtk::Actionable</classname> interface instead of the removed
<classname>Gtk::Activatable</classname> interface.</simpara></listitem>
<listitem><simpara xml:lang="en"><classname>Gtk::FontButton</classname> implements the <classname>Gtk::FontChooser</classname> interface.</simpara></listitem>
<listitem><simpara xml:lang="en"><classname>Gtk::Widget</classname>: The <methodname>get_preferred_*_vfunc()</methodname>s
have been replaced by <methodname>measure_vfunc()</methodname>. This change only affects
custom widgets.</simpara></listitem>
<listitem><simpara xml:lang="en"><classname>sigc::slot</classname>s use the <classname>sigc::slot<R(Args...)></classname> syntax.
Example: <classname>sigc::slot<void(int, int)></classname> instead of <classname>sigc::slot<void, int, int></classname>.</simpara></listitem>
<listitem><simpara xml:lang="en"><classname>Gtk::DrawingArea</classname> uses a draw function instead of the draw signal.</simpara></listitem>
<listitem><simpara xml:lang="en"><classname>Glib::ArrayHandle</classname>, <classname>Glib::StringArrayHandle</classname>,
<classname>Glib::ListHandle</classname> and <classname>Glib::SListHandle</classname> have been removed.
They were used in <application>glibmm</application>-2.4, but not used in <application>gtkmm</application>-3.0.
If you've ever used these classes, replace them with a standard C++ container, such as <classname>std::vector</classname>.</simpara></listitem>
<listitem><simpara xml:lang="en"><classname>Gtk::Container</classname> has been removed.</simpara></listitem>
<listitem><simpara xml:lang="en"><methodname>Gtk::Widget::show_all()</methodname> has been removed. The default value
of <methodname>Gtk::Widget::property_visible()</methodname> has been changed from
<literal>false</literal> to <literal>true</literal>.</simpara></listitem>
<listitem><simpara xml:lang="en">All event signals have been removed from <classname>Gtk::Widget</classname>.
In most cases you can use one of the subclasses of <classname>Gtk::EventController</classname>
as a replacement. For instance, use <classname>Gtk::GestureMultiPress</classname>
instead of <methodname>signal_button_press_event()</methodname> and
<methodname>signal_button_release_event()</methodname>, and <classname>Gtk::EventControllerKey</classname>
instead of <methodname>signal_key_press_event()</methodname> and
<methodname>signal_key_release_event()</methodname>.</simpara></listitem>
<listitem><simpara xml:lang="en"><classname>Glib::RefPtr</classname> is an alias for <classname>std::shared_ptr</classname>.
If you make your own <classname>Glib::ObjectBase</classname>-derived classes with
<methodname>create()</methodname> methods that return a <classname>Glib::RefPtr</classname>,
you must use <methodname>Glib::make_refptr_for_instance()</methodname> in your
<methodname>create()</methodname> methods.</simpara></listitem>
<listitem><simpara xml:lang="en"><methodname>Gtk::Box::pack_start()</methodname> and <methodname>Gtk::Box::pack_end()</methodname>
have been removed. Use the new <classname>Gtk::Box</classname> methods
<methodname>append()</methodname>, <methodname>prepend()</methodname>,
<methodname>insert_child_after()</methodname> and <methodname>insert_child_at_start()</methodname>.
</simpara></listitem>
<listitem><simpara xml:lang="en"><classname>Gtk::ButtonBox</classname> has been removed.</simpara></listitem>
<listitem><simpara xml:lang="en"><classname>Gtk::RadioButton</classname> and <classname>Gtk::RadioButtonGroup</classname>
have been removed. Use <classname>Gtk::CheckButton</classname> or <classname>Gtk::ToggleButton</classname>
with <methodname>set_group()</methodname>.</simpara></listitem>
</orderedlist>
</para>
<para xml:lang="en">All deprecated API was removed in <application>gtkmm</application> 4.0 and <application>glibmm</application> 2.68,
though there will be new deprecations in future versions.</para>
<para xml:lang="en">As a first step to porting your source code to <application>gtkmm</application>-4.0 you should probably ensure
that your application builds with the deprecated <application>gtkmm</application>-3.0 and <application>glibmm-2.4</application>
API disabled, by defining the macros GTKMM_DISABLE_DEPRECATED, GDKMM_DISABLE_DEPRECATED,
GLIBMM_DISABLE_DEPRECATED and GIOMM_DISABLE_DEPRECATED. There are some autotools macros
that can help with this by defining them optionally at build time. See the
<link xlink:href="https://wiki.gnome.org/Projects/gtkmm/PortingToGtkmm3">Porting from
gtkmm-2.4 to gtkmm-3.0</link> wiki page for more details.</para>
<para xml:lang="en">See also <link xlink:href="https://docs.gtk.org/gtk4/migrating-3to4.html">
Migrating from GTK 3.x to GTK 4</link>.</para>
<section xml:id="sec-deprecations-4-10">
<title xml:lang="en">Deprecations in <application>gtkmm</application> 4.10</title>
<para xml:lang="en">Many classes are deprecated since <application>gtkmm</application> 4.10. They can still be used in
<application>gtkmm</application>4 applications, provided GTKMM_DISABLE_DEPRECATED and GDKMM_DISABLE_DEPRECATED
are not defined. There are also many new classes in <application>gtkmm</application> 4.10, which replace
some of the deprecated classes. Some example programs in this tutorial use classes
deprecated since <application>gtkmm</application> 4.10. Some other programs use classes available since <application>gtkmm</application> 4.10.
</para>
<para xml:lang="en">Deprecated classes:
AppChooser, AppChooserButton, AppChooserDialog,
AppChooserWidget, CellArea, CellAreaBox, CellAreaContext,
CellLayout, CellRenderer, CellRendererAccel, CellRendererCombo,
CellRendererPixbuf, CellRendererProgress, CellRendererSpin,
CellRendererSpinner, CellRendererText, CellRendererToggle, CellView,
ComboBox, ComboBoxText, EntryCompletion, IconView, ListStore,
ListViewText, StyleContext, TreeDragDest, TreeDragSource,
TreeIter and other classes in treeiter.h,
TreeModel, TreeModelFilter, TreeModelSort, TreePath, TreeRowReference,
TreeSelection, TreeSortable, TreeStore, TreeView, TreeViewColumn,
namespace CellRenderer_Generation, namespace TreeView_Private,
ColorButton, ColorChooser, ColorChooserDialog,
FileChooser, FileChooserDialog, FileChooserNative, FileChooserWidget,
FontButton, FontChooser, FontChooserDialog, FontChooserWidget,
MessageDialog, TreeModelColumn, TreeModelColumnRecord, InfoBar,
Assistant, AssistantPage, LockButton, Statusbar, VolumeButton.
</para>
<para xml:lang="en">New classes and enums:
AlertDialog, ColorDialog, ColorDialogButton, ColumnViewSorter, FileDialog,
FontDialog, FontDialogButton, FileLauncher, UriLauncher, ATContext,
enums DialogError, FontLevel, Collation.
</para>
<para xml:lang="en">In most cases there are replacements for the deprecated classes.
See the reference documentation.
</para>
</section>
</chapter>
<chapter xml:id="chapter-button-widget">
<title>Botones</title>
<para><application>gtkmm</application> proporciona cuatro tipos básicos de botones:</para>
<variablelist>
<varlistentry>
<term xml:lang="en">Push buttons</term>
<listitem>
<para xml:lang="en">
<link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1Button.html"><classname>Gtk::Button</classname></link>. Standard buttons, usually
marked with a label or picture. Pushing one triggers an action. See the <link linkend="sec-pushbuttons">Button</link> section.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>Botones conmutables</term>
<listitem>
<para xml:lang="en">
<link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1ToggleButton.html"><classname>Gtk::ToggleButton</classname></link>.
Unlike a normal Button, which springs back up, a ToggleButton stays down until you
press it again. It might be useful as an on/off switch. See the <link linkend="sec-toggle-buttons">ToggleButton</link> section.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en">Check buttons</term>
<listitem>
<para xml:lang="en">
<link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1CheckButton.html"><classname>Gtk::CheckButton</classname></link>.
These act like ToggleButtons, but show their state in small squares,
with their label at the side. They should be used in most situations
which require an on/off setting.
See the <link linkend="sec-checkbuttons">CheckButton</link> section.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>Botones de radio</term>
<listitem>
<para xml:lang="en">
Named after the station selectors on old car
radios, these buttons are used in groups for options which are
mutually exclusive. Pressing one causes all the
others in its group to turn off. They are similar to ToggleButtons or
CheckButtons (a small widget with a label at the side), but usually
look different. There is no separate radio button class. Check buttons
and toggle buttons can act as radio buttons.
See the <link linkend="sec-radio-buttons">Radio Button</link> section.
</para>
</listitem>
</varlistentry>
</variablelist>
<para xml:lang="en">
Note that, due to GTK's theming system, the appearance of these
widgets will vary. In the case of check buttons and radio buttons, they
may vary considerably.
</para>
<section xml:id="sec-pushbuttons">
<title>Botón</title>
<para>Hay dos maneras de crear un botón. Puede especificar una etiqueta en el constructor de <classname>Gtk::Button</classname>, o establecerla más tarde con <methodname>set_label()</methodname>.</para>
<para>Para definir un atajo para la navegación por teclado, ponga un guión bajo antes de uno de los caracteres y especifique <literal>true</literal> para el parámetro opcional <literal>mnemonic</literal>. Por ejemplo:</para>
<programlisting xml:lang="en"><code>Gtk::Button* pButton = new Gtk::Button("_Something", true);</code></programlisting>
<para><classname>Gtk::Button</classname> también es un contenedor, por lo que puede poner otro widget, como un <classname>Gtk::Image</classname> dentro de él.</para>
<para xml:lang="en">
The <classname>Gtk::Button</classname> widget has the <literal>clicked</literal> signal
which is emitted when the button is pressed and released.
</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1Button.html">Reference</link></para>
<section xml:id="pushbutton-example">
<title>Ejemplo</title>
<para>Este ejemplo crea un botón con una imagen y una etiqueta.</para>
<figure xml:id="figure-buttons">
<title>ejemplo de botones</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/buttons.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/buttons/button">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>buttons.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLE_BUTTONS_H
#define GTKMM_EXAMPLE_BUTTONS_H
#include <gtkmm/window.h>
#include <gtkmm/button.h>
class Buttons : public Gtk::Window
{
public:
Buttons();
virtual ~Buttons();
protected:
//Signal handlers:
void on_button_clicked();
//Child widgets:
Gtk::Button m_button;
};
#endif //GTKMM_EXAMPLE_BUTTONS_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>buttons.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "buttons.h"
#include <gtkmm/box.h>
#include <gtkmm/image.h>
#include <gtkmm/label.h>
#include <iostream>
Buttons::Buttons()
{
// This corresponds to Gtk::Bin::add_pixlabel("info.xpm", "cool button") in gtkmm3.
//Create Image and Label widgets:
auto pmap = Gtk::make_managed<Gtk::Image>("info.xpm");
auto label = Gtk::make_managed<Gtk::Label>("cool button");
label->set_expand(true);
//Put them in a Box:
auto hbox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 5);
hbox->append(*pmap);
hbox->append(*label);
//And put that Box in the button:
m_button.set_child(*hbox);
set_title("Pixmap'd buttons!");
m_button.signal_clicked().connect( sigc::mem_fun(*this,
&Buttons::on_button_clicked) );
m_button.set_margin(10);
set_child(m_button);
}
Buttons::~Buttons()
{
}
void Buttons::on_button_clicked()
{
std::cout << "The Button was clicked." << std::endl;
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "buttons.h"
#include <gtkmm/application.h>
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
//Shows the window and returns when it is closed.
return app->make_window_and_run<Buttons>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</section>
<section xml:id="sec-toggle-buttons">
<title>Botón Conmutable</title>
<para>Los <classname>ToggleButton</classname> son como los <classname>Button</classname> normales, pero cuando se pulsan quedan activados, o presionados, hasta que se vuelvan a pulsar.</para>
<para>Para recuperar el estado de un <classname>ToggleButton</classname>, puede usar el método <methodname>get_active()</methodname>. Esto devuelve <literal>true</literal> si el botón está «abajo». También puede establecer el estado del botón conmutador con <methodname>set_active()</methodname>. Tenga en cuenta que, si hace esto, y el estado efectivamente cambia, hace que se emita la señal «clicked». Esto normalmente será lo que quiera.</para>
<para>Puede usar el método <methodname>toggled()</methodname> para conmutar el botón, en vez de forzarlo a estar arriba o abajo: esto conmuta el estado del botón, y hace que se emita la señal <literal>toggled</literal>.</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1ToggleButton.html">Reference</link></para>
</section>
<section xml:id="sec-checkbuttons">
<title>Casilla de verificación</title>
<para xml:lang="en">
<classname>Gtk::CheckButton</classname> inherits directly from
<classname>Gtk::Widget</classname>. It is similar to <classname>Gtk::ToggleButton</classname>.
The only real difference between the two is <classname>Gtk::CheckButton</classname>'s
appearance. You can check and set a check button using the same
member methods as for <classname>Gtk::ToggleButton</classname>.
</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1CheckButton.html">Reference</link></para>
<section xml:id="checkbutton-example">
<title>Ejemplo</title>
<figure xml:id="figure-checkbutton">
<title>Casilla de verificación</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/checkbutton.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/buttons/checkbutton">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLE_BUTTONS_H
#define GTKMM_EXAMPLE_BUTTONS_H
#include <gtkmm/window.h>
#include <gtkmm/checkbutton.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
virtual ~ExampleWindow();
protected:
//Signal handlers:
void on_button_toggled();
//Child widgets:
Gtk::CheckButton m_button;
};
#endif //GTKMM_EXAMPLE_BUTTONS_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <iostream>
ExampleWindow::ExampleWindow()
: m_button("something")
{
set_title("checkbutton example");
m_button.signal_toggled().connect(sigc::mem_fun(*this,
&ExampleWindow::on_button_toggled) );
m_button.set_margin(10);
set_child(m_button);
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::on_button_toggled()
{
std::cout << "The Button was toggled: state="
<< (m_button.get_active() ? "true" : "false")
<< std::endl;
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
//Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</section>
<section xml:id="sec-radio-buttons">
<title xml:lang="en">Radio Button</title>
<para xml:lang="en">
There is no separate class for radio buttons. Check buttons and toggle buttons
act as radio buttons when they form a group. Only one button in a group can be
selected at any one time.
</para>
<section xml:id="radiobutton-groups">
<title>Grupos</title>
<para xml:lang="en">
You create the buttons, and set up their group afterwards. In the following example,
we put 3 radio buttons in a group:
</para>
<programlisting xml:lang="en"><code>auto rb1 = Gtk::make_managed<Gtk::CheckButton>("button1");
auto rb2 = Gtk::make_managed<Gtk::CheckButton>("button2");
auto rb3 = Gtk::make_managed<Gtk::CheckButton>("button3");
rb2->set_group(*rb1);
rb3->set_group(*rb1);
</code></programlisting>
<para xml:lang="en">
We told <application>gtkmm</application> to put all three <classname>CheckButton</classname>s in the
same group by using <methodname>set_group()</methodname> to tell the other
<classname>CheckButton</classname>s to share group with the first
<classname>CheckButton</classname>.
</para>
</section>
<section xml:id="radiobutton-methods">
<title>Métodos</title>
<para xml:lang="en">
<classname>CheckButton</classname>s and <classname>ToggleButton</classname>s are "off"
when created; this means that when you first make a group of them, they will all be off.
Don't forget to turn one of them on using <methodname>set_active()</methodname>.
</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1RadioButton.html">Reference</link></para>
</section>
<section xml:id="radiobutton-example">
<title>Ejemplo</title>
<para xml:lang="en">
The following example demonstrates the use of grouped
<classname>CheckButton</classname>s:
</para>
<figure xml:id="figure-radiobutton">
<title>Botón de radio</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/radiobuttons.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/buttons/radiobutton">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>radiobuttons.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLE_RADIOBUTTONS_H
#define GTKMM_EXAMPLE_RADIOBUTTONS_H
#include <gtkmm/box.h>
#include <gtkmm/window.h>
#include <gtkmm/button.h>
#include <gtkmm/checkbutton.h>
#include <gtkmm/separator.h>
class RadioButtons : public Gtk::Window
{
public:
RadioButtons();
virtual ~RadioButtons();
protected:
//Signal handlers:
void on_button_clicked();
//Child widgets:
Gtk::Box m_Box_Top, m_Box1, m_Box2;
Gtk::CheckButton m_RadioButton1, m_RadioButton2, m_RadioButton3;
Gtk::Separator m_Separator;
Gtk::Button m_Button_Close;
};
#endif //GTKMM_EXAMPLE_RADIOBUTTONS_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "radiobuttons.h"
#include <gtkmm/application.h>
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
//Shows the window and returns when it is closed.
return app->make_window_and_run<RadioButtons>(argc, argv);
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>radiobuttons.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "radiobuttons.h"
RadioButtons::RadioButtons() :
m_Box_Top(Gtk::Orientation::VERTICAL),
m_Box1(Gtk::Orientation::VERTICAL, 10),
m_Box2(Gtk::Orientation::VERTICAL, 10),
m_RadioButton1("button1"),
m_RadioButton2("button2"),
m_RadioButton3("button3"),
m_Button_Close("close")
{
// Set title and border of the window
set_title("radio buttons");
// Gtk::CheckButton and Gtk::ToggleButton have set_group() methods.
// They act as radio buttons, if they are included in a group.
// Put radio buttons 2 and 3 in the same group as 1:
m_RadioButton2.set_group(m_RadioButton1);
m_RadioButton3.set_group(m_RadioButton1);
// Add outer box to the window (because the window
// can only contain a single widget)
set_child(m_Box_Top);
//Put the inner boxes and the separator in the outer box:
m_Box_Top.append(m_Box1);
m_Box_Top.append(m_Separator);
m_Box_Top.append(m_Box2);
m_Separator.set_expand();
// Set the inner boxes' margins
m_Box1.set_margin(10);
m_Box2.set_margin(10);
// Put the radio buttons in Box1:
m_Box1.append(m_RadioButton1);
m_Box1.append(m_RadioButton2);
m_Box1.append(m_RadioButton3);
m_RadioButton1.set_expand();
m_RadioButton2.set_expand();
m_RadioButton3.set_expand();
// Set the second button active
m_RadioButton2.set_active(true);
// Put Close button in Box2:
m_Box2.append(m_Button_Close);
m_Button_Close.set_expand();
// Make the button the default widget
set_default_widget(m_Button_Close);
// Connect the toggled signal of the button to
// RadioButtons::on_button_toggled()
m_Button_Close.signal_clicked().connect(sigc::mem_fun(*this,
&RadioButtons::on_button_clicked) );
}
RadioButtons::~RadioButtons()
{
}
void RadioButtons::on_button_clicked()
{
set_visible(false); //to close the application.
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</section>
</chapter>
<chapter xml:id="chapter-range-widgets">
<title>Widgets de Rango</title>
<para xml:lang="en">
<classname>Gtk::Scale</classname> inherits from <classname>Gtk::Range</classname>.
<classname>Gtk::Scrollbar</classname> does not inherit from <classname>Gtk::Range</classname>,
but it shares much functionality with <classname>Gtk::Scale</classname>.
They both contain a "trough" and a "slider" (sometimes called a
"thumbwheel" in other GUI environments). Dragging the slider with the pointer
moves it within the trough, while clicking in the trough advances the slider
towards the location of the click, either completely, or by a designated
amount, depending on which mouse button is used. This should be familiar
scrollbar behavior.
</para>
<para xml:lang="en">
As will be explained in the <link linkend="chapter-adjustment">Adjustments</link>
section, all range widgets are associated with an
<classname>Adjustment</classname> object. To change the lower, upper, and
current values used by the widget you need to use the methods of its
<classname>Adjustment</classname>, which you can get with the
<methodname>get_adjustment()</methodname> method. The range
widgets' default constructors create an <classname>Adjustment</classname>
automatically, or you can specify an existing
<classname>Adjustment</classname>, maybe to share it with another widget. See
the <link linkend="chapter-adjustment">Adjustments</link> section for further
details.
</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1Range.html">Reference</link></para>
<section xml:id="sec-scrollbar-widgets">
<title>Widgets de barras de desplazamiento</title>
<para xml:lang="en">
These are standard scrollbars. They should be used only to scroll another
widget, such as a <classname>Gtk::Entry</classname> or a
<classname>Gtk::Viewport</classname>, though it's usually easier to use the
<classname>Gtk::ScrolledWindow</classname> widget in most cases.
</para>
<para>La orientación de una <classname>Gtk::Scrollbar</classname> puede ser horizontal o vertical.</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1Scrollbar.html">Reference</link></para>
</section>
<section xml:id="sec-scale-widgets">
<title>Widgets de Escala</title>
<para>Los widgets <classname>Gtk::Scale</classname> (o «deslizadores») le permiten al usuario seleccionar y manipular visualmente un valor dentro de un rango específico. Puede usar uno, por ejemplo, para ajustar el nivel de ampliación en una previsualización de una imagen, o para controlar el brillo de un color, o especificar la cantidad de minutos de inactividad antes de que aparezca un salvapantallas.</para>
<para xml:lang="en">
As with <classname>Scrollbar</classname>s, the orientation can be either
horizontal or vertical. The default constructor creates an
<classname>Adjustment</classname> with all of its values set to
<literal>0.0</literal>. This isn't useful so you will need to set some
<classname>Adjustment</classname> details to get meaningful behavior.
</para>
<section xml:id="scale-useful-methods">
<title>Métodos útiles</title>
<para>Los widgets <classname>Scale</classname> pueden mostrar su valor actual como un número junto al canal. De manera predeterminada muestran el valor, pero puede cambiar esto con el método <methodname>set_draw_value()</methodname>.</para>
<para>El valor mostrado en un widget de escala se redondea a un dígito decimal de manera predeterminada, como es el campo <literal>value</literal> en su <classname>Gtk::Adjustment</classname>. Puede cambiar esto con el método <methodname>set_digits()</methodname>.</para>
<para>Además, pueden dibujar el valor en diferentes posiciones relativas al canal, especificadas por el método <methodname>set_value_pos()</methodname>.</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1Scale.html">Reference</link></para>
</section>
</section>
<section xml:id="sec-range-example">
<title>Ejemplo</title>
<para>Este ejemplo muestra una ventana con tres widgets de rango, todos conectados al mismo ajuste, junto con un par de controles para establecer algunos de los parámetros mencionados anteriormente y en la sección de ajustes, para que pueda ver cómo afectan la manera en la que estos widgets funcionan para el usuario.</para>
<figure xml:id="figure-range-widgets">
<title>Widgets de Rango</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/range_widgets.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/range_widgets">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLE_RANGEWIDGETS_H
#define GTKMM_EXAMPLE_RANGEWIDGETS_H
#include <gtkmm.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
~ExampleWindow() override;
protected:
//Signal handlers:
void on_checkbutton_toggled();
void on_dropdown_position();
void on_adjustment1_value_changed();
void on_adjustment2_value_changed();
void on_button_quit();
//Child widgets:
Gtk::Box m_VBox_Top, m_VBox2, m_VBox_HScale;
Gtk::Box m_HBox_Scales, m_HBox_DropDown, m_HBox_Digits, m_HBox_PageSize;
Glib::RefPtr<Gtk::Adjustment> m_adjustment, m_adjustment_digits, m_adjustment_pagesize;
Gtk::Scale m_VScale;
Gtk::Scale m_HScale, m_Scale_Digits, m_Scale_PageSize;
Gtk::Separator m_Separator;
Gtk::CheckButton m_CheckButton;
Gtk::Scrollbar m_Scrollbar;
Gtk::DropDown m_DropDown_Position;
Gtk::Button m_Button_Quit;
};
#endif //GTKMM_EXAMPLE_RANGEWIDGETS_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <array>
#include <iostream>
namespace
{
struct PositionTypeStruct
{
Gtk::PositionType position;
Glib::ustring text;
};
const std::array<PositionTypeStruct, 4> positionTypes =
{
PositionTypeStruct{Gtk::PositionType::TOP, "Top"},
PositionTypeStruct{Gtk::PositionType::BOTTOM, "Bottom"},
PositionTypeStruct{Gtk::PositionType::LEFT, "Left"},
PositionTypeStruct{Gtk::PositionType::RIGHT, "Right"}
};
} // anonymous namespace
ExampleWindow::ExampleWindow()
:
m_VBox_Top(Gtk::Orientation::VERTICAL, 0),
m_VBox2(Gtk::Orientation::VERTICAL, 20),
m_VBox_HScale(Gtk::Orientation::VERTICAL, 10),
m_HBox_Scales(Gtk::Orientation::HORIZONTAL, 10),
m_HBox_DropDown(Gtk::Orientation::HORIZONTAL, 10),
m_HBox_Digits(Gtk::Orientation::HORIZONTAL, 10),
m_HBox_PageSize(Gtk::Orientation::HORIZONTAL, 10),
// Value, lower, upper, step_increment, page_increment, page_size:
// Note that the page_size value only makes a difference for
// scrollbar widgets, and the highest value you'll get is actually
// (upper - page_size).
m_adjustment( Gtk::Adjustment::create(0.0, 0.0, 101.0, 0.1, 1.0, 1.0) ),
m_adjustment_digits( Gtk::Adjustment::create(1.0, 0.0, 5.0, 1.0, 2.0) ),
m_adjustment_pagesize( Gtk::Adjustment::create(1.0, 1.0, 101.0) ),
m_VScale(m_adjustment, Gtk::Orientation::VERTICAL),
m_HScale(m_adjustment, Gtk::Orientation::HORIZONTAL),
m_Scale_Digits(m_adjustment_digits),
m_Scale_PageSize(m_adjustment_pagesize),
// A checkbutton to control whether the value is displayed or not:
m_CheckButton("Display value on scale widgets", 0),
// Reuse the same adjustment again.
// Notice how this causes the scales to always be updated
// continuously when the scrollbar is moved.
m_Scrollbar(m_adjustment),
m_Button_Quit("Quit")
{
set_title("range controls");
set_default_size(300, 350);
//VScale:
m_VScale.set_digits(1);
m_VScale.set_value_pos(Gtk::PositionType::TOP);
m_VScale.set_draw_value();
m_VScale.set_inverted(); // highest value at top
//HScale:
m_HScale.set_digits(1);
m_HScale.set_value_pos(Gtk::PositionType::TOP);
m_HScale.set_draw_value();
set_child(m_VBox_Top);
m_VBox_Top.append(m_VBox2);
m_VBox2.set_expand(true);
m_VBox2.set_margin(10);
m_VBox2.append(m_HBox_Scales);
m_HBox_Scales.set_expand(true);
//Put VScale and HScale (above scrollbar) side-by-side.
m_HBox_Scales.append(m_VScale);
m_VScale.set_expand(true);
m_HBox_Scales.append(m_VBox_HScale);
m_VBox_HScale.set_expand(true);
m_VBox_HScale.append(m_HScale);
m_HScale.set_expand(true);
//Scrollbar:
m_VBox_HScale.append(m_Scrollbar);
m_Scrollbar.set_expand(true);
//CheckButton:
m_CheckButton.set_active();
m_CheckButton.signal_toggled().connect( sigc::mem_fun(*this,
&ExampleWindow::on_checkbutton_toggled) );
m_VBox2.append(m_CheckButton);
//Position DropDown:
//Create the StringList:
auto string_list = Gtk::StringList::create({});
m_DropDown_Position.set_model(string_list);
// Fill the DropDown's list model:
for (const auto& positionType : positionTypes)
string_list->append(positionType.text);
m_VBox2.append(m_HBox_DropDown);
m_HBox_DropDown.append(*Gtk::make_managed<Gtk::Label>("Scale Value Position:", 0));
m_HBox_DropDown.append(m_DropDown_Position);
m_DropDown_Position.property_selected().signal_changed().connect(
sigc::mem_fun(*this, &ExampleWindow::on_dropdown_position));
m_DropDown_Position.set_selected(0); // Top
m_DropDown_Position.set_expand(true);
//Digits:
m_HBox_Digits.append(*Gtk::make_managed<Gtk::Label>("Scale Digits:", 0));
m_Scale_Digits.set_digits(0);
m_Scale_Digits.set_expand(true);
m_adjustment_digits->signal_value_changed().connect(sigc::mem_fun(*this,
&ExampleWindow::on_adjustment1_value_changed));
m_HBox_Digits.append(m_Scale_Digits);
//Page Size:
m_HBox_PageSize.append(*Gtk::make_managed<Gtk::Label>("Scrollbar Page Size:", 0));
m_Scale_PageSize.set_digits(0);
m_Scale_PageSize.set_expand(true);
m_adjustment_pagesize->signal_value_changed().connect(sigc::mem_fun(*this,
&ExampleWindow::on_adjustment2_value_changed));
m_HBox_PageSize.append(m_Scale_PageSize);
m_VBox2.append(m_HBox_Digits);
m_VBox2.append(m_HBox_PageSize);
m_VBox_Top.append(m_Separator);
m_VBox_Top.append(m_Button_Quit);
set_default_widget(m_Button_Quit);
m_Button_Quit.signal_clicked().connect(sigc::mem_fun(*this,
&ExampleWindow::on_button_quit));
m_Button_Quit.set_margin(10);
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::on_checkbutton_toggled()
{
m_VScale.set_draw_value(m_CheckButton.get_active());
m_HScale.set_draw_value(m_CheckButton.get_active());
}
void ExampleWindow::on_dropdown_position()
{
const auto selected = m_DropDown_Position.get_selected();
if (selected == GTK_INVALID_LIST_POSITION)
return;
const auto postype = positionTypes[selected].position;
m_VScale.set_value_pos(postype);
m_HScale.set_value_pos(postype);
}
void ExampleWindow::on_adjustment1_value_changed()
{
const double val = m_adjustment_digits->get_value();
m_VScale.set_digits((int)val);
m_HScale.set_digits((int)val);
}
void ExampleWindow::on_adjustment2_value_changed()
{
const double val = m_adjustment_pagesize->get_value();
m_adjustment->set_page_size(val);
m_adjustment->set_page_increment(val);
// Note that we don't have to emit the "changed" signal
// because gtkmm does this for us.
}
void ExampleWindow::on_button_quit()
{
set_visible(false);
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
//Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</chapter>
<chapter xml:id="chapter-misc-widgets">
<title>Widgets varios</title>
<section xml:id="sec-labels">
<title>Etiqueta</title>
<para xml:lang="en">
Labels are the main method of placing non-editable text in windows, for
instance to place a title next to an <classname>Entry</classname> widget. You
can specify the text in the constructor, or later with the
<methodname>set_text()</methodname> or <methodname>set_markup()</methodname> methods.
</para>
<para>El ancho de la etiqueta se ajusta automáticamente. Puede producir etiquetas con varias líneas poniendo saltos de línea («\n») en la cadena de la etiqueta.</para>
<para xml:lang="en">
The label text can be justified using the <methodname>set_justify()</methodname>
method. The widget is also capable of word-wrapping, which can be activated
with <methodname>set_wrap()</methodname>.
</para>
<para xml:lang="en">
Gtk::Label supports some simple formatting, for instance allowing you to make some
text bold, colored, or larger. You can do this by providing a string to
<methodname>set_markup()</methodname>, using the <link xlink:href="https://docs.gtk.org/Pango/pango_markup.html">Pango Markup syntax</link>. For instance,
<code>
<b>bold text</b> and <s>strikethrough text</s>
</code>
.</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1Label.html">Reference</link></para>
<section xml:id="label-example">
<title>Ejemplo</title>
<para>Abajo se muestra un ejemplo corto que ilustra estas funciones. Este ejemplo hace uso del widget «Marco» para demostrar mejor los estilos de etiquetas. (El widget «Marco» se explica en la sección <link linkend="sec-frame">Marco</link>). Esto es posible gracias que el primer carácter de <literal>m_Label_Normal</literal> aparece subrayado cuando pulsa la tecla <keycap>Alt</keycap>.</para>
<figure xml:id="figure-label">
<title>Etiqueta</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/label.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/label">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
virtual ~ExampleWindow();
protected:
//Child widgets:
Gtk::Box m_HBox;
Gtk::Box m_VBox, m_VBox2;
Gtk::Frame m_Frame_Normal, m_Frame_Multi, m_Frame_Left, m_Frame_Right,
m_Frame_LineWrapped, m_Frame_FilledWrapped, m_Frame_Underlined;
Gtk::Label m_Label_Normal, m_Label_Multi, m_Label_Left, m_Label_Right,
m_Label_LineWrapped, m_Label_FilledWrapped, m_Label_Underlined;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <iostream>
ExampleWindow::ExampleWindow()
:
m_HBox(Gtk::Orientation::HORIZONTAL, 5),
m_VBox(Gtk::Orientation::VERTICAL, 5),
m_VBox2(Gtk::Orientation::VERTICAL, 5),
m_Frame_Normal("Normal Label"),
m_Frame_Multi("Multi-line Label"),
m_Frame_Left("Left Justified Label"),
m_Frame_Right("Right Justified Label"),
m_Frame_LineWrapped("Line wrapped label"),
m_Frame_FilledWrapped("Filled, wrapped label"),
m_Frame_Underlined("Underlined label"),
m_Label_Normal("_This is a Normal label", true),
m_Label_Multi("This is a Multi-line label.\nSecond line\nThird line"),
m_Label_Left("This is a Left-Justified\nMulti-line label.\nThird line"),
m_Label_Right("This is a Right-Justified\nMulti-line label.\nThird line"),
m_Label_Underlined("<u>This label is underlined!</u>\n"
"<u>T</u>h<u>is one is</u> <u>u</u>n<u>derlin</u>ed "
"in<u> q</u>u<u>ite a f</u>u<u>nky</u> fashion")
{
set_title("Label");
m_HBox.set_margin(5);
set_child(m_HBox);
m_HBox.append(m_VBox);
m_Frame_Normal.set_child(m_Label_Normal);
m_VBox.append(m_Frame_Normal);
m_Frame_Multi.set_child(m_Label_Multi);
m_VBox.append(m_Frame_Multi);
m_Label_Left.set_justify(Gtk::Justification::LEFT);
m_Frame_Left.set_child(m_Label_Left);
m_VBox.append(m_Frame_Left);
m_Label_Right.set_justify(Gtk::Justification::RIGHT);
m_Frame_Right.set_child(m_Label_Right);
m_VBox.append(m_Frame_Right);
m_HBox.append(m_VBox2);
m_Label_LineWrapped.set_text(
"This is an example of a line-wrapped label. It "
/* add a big space to the next line to test spacing */
"should not be taking up the entire "
"width allocated to it, but automatically "
"wraps the words to fit. "
"The time has come, for all good men, to come to "
"the aid of their party. "
"The sixth sheik's six sheep's sick.\n"
" It supports multiple paragraphs correctly, "
"and correctly adds "
"many extra spaces. ");
m_Label_LineWrapped.set_wrap();
m_Frame_LineWrapped.set_child(m_Label_LineWrapped);
m_VBox2.append(m_Frame_LineWrapped);
m_Label_FilledWrapped.set_text(
"This is an example of a line-wrapped, filled label. "
"It should be taking "
"up the entire width allocated to it. "
"Here is a sentence to prove "
"my point. Here is another sentence. "
"Here comes the sun, do de do de do.\n"
" This is a new paragraph.\n"
" This is another newer, longer, better "
"paragraph. It is coming to an end, "
"unfortunately.");
m_Label_FilledWrapped.set_justify(Gtk::Justification::FILL);
m_Label_FilledWrapped.set_wrap();
m_Frame_FilledWrapped.set_child(m_Label_FilledWrapped);
m_VBox2.append(m_Frame_FilledWrapped);
m_Label_Underlined.set_justify(Gtk::Justification::LEFT);
m_Label_Underlined.set_use_markup(true);
m_Frame_Underlined.set_child(m_Label_Underlined);
m_VBox2.append(m_Frame_Underlined);
}
ExampleWindow::~ExampleWindow()
{
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
//Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</section>
<section xml:id="sec-text-entry">
<title>Entry</title>
<section xml:id="sec-text-entry-simple">
<title>Uso simple</title>
<para>Los widgets de «entry» le permiten al usuario introducir texto. Puede cambiar el contenido con el método <methodname>set_text()</methodname>, y leer el contenido actual con el método <methodname>get_text()</methodname>.</para>
<para>Ocasionalmente querrá definir un widget <classname>Entry</classname> como sólo lectura. Esto se puede hacer pasándole <literal>false</literal> al método <methodname>set_editable()</methodname>.</para>
<para>Para introducir contraseñas, frases de paso, y otra información que no quiera que aparezca en la pantalla, llamar a <methodname>set_visibility</methodname> con <literal>false</literal> hará que el texto permanezca oculto.</para>
<para xml:lang="en">
You might want to be notified whenever the user types in a text entry widget.
<classname>Gtk::Entry</classname> provides two signals,
<literal>activate</literal> (since <application>gtkmm</application> 4.8) and <literal>changed</literal>, for this purpose.
<literal>activate</literal> is emitted when the user presses the <keycap>Enter</keycap> key in
a text-entry widget; <literal>changed</literal> is emitted when the text in
the widget changes. You can use these, for instance, to validate or filter
the text the user types. Moving the keyboard focus to another widget may also
signal that the user has finished entering text. The <literal>leave</literal>
signal in a <classname>Gtk::EventControllerFocus</classname> can notify you when
that happens. The <link linkend="sec-comboboxentry">ComboBox with an Entry</link>
section contains example programs that use these signals.
</para>
<para xml:lang="en">
If you pass <literal>true</literal> to the <methodname>set_activates_default()</methodname>
method, pressing <keycap>Enter</keycap> in the <classname>Gtk::Entry</classname> will activate
the default widget for the window containing the <classname>Gtk::Entry</classname>.
This is especially useful in dialog boxes. The default widget is usually one of
the dialog buttons, which e.g. will close the dialog box. To set a widget as the
default widget, use <methodname>Gtk::Window::set_default_widget()</methodname>.
</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1Entry.html">Reference</link></para>
<section xml:id="entry-example">
<title>Ejemplo simple de «Entry»</title>
<para>Este ejemplo usa <classname>Gtk::Entry</classname>. También tiene dos <classname>CheckButton</classname>, con los que puede conmutar las opciones «editable» y «visible».</para>
<figure xml:id="figure-entry-simple">
<title>Entry</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/entry.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/entry/simple">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
virtual ~ExampleWindow();
protected:
//Signal handlers:
void on_checkbox_editable_toggled();
void on_checkbox_visibility_toggled();
void on_button_close();
//Child widgets:
Gtk::Box m_HBox;
Gtk::Box m_VBox;
Gtk::Entry m_Entry;
Gtk::Button m_Button_Close;
Gtk::CheckButton m_CheckButton_Editable, m_CheckButton_Visible;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <iostream>
ExampleWindow::ExampleWindow()
: m_VBox(Gtk::Orientation::VERTICAL),
m_Button_Close("Close"),
m_CheckButton_Editable("Editable"),
m_CheckButton_Visible("Visible")
{
set_size_request(200, 100);
set_title("Gtk::Entry");
set_child(m_VBox);
m_Entry.set_max_length(50);
m_Entry.set_text("hello");
m_Entry.set_text(m_Entry.get_text() + " world");
m_Entry.select_region(0, m_Entry.get_text_length());
m_Entry.set_expand(true);
m_VBox.append(m_Entry);
m_VBox.append(m_HBox);
m_HBox.append(m_CheckButton_Editable);
m_CheckButton_Editable.set_expand(true);
m_CheckButton_Editable.signal_toggled().connect( sigc::mem_fun(*this,
&ExampleWindow::on_checkbox_editable_toggled) );
m_CheckButton_Editable.set_active(true);
m_HBox.append(m_CheckButton_Visible);
m_CheckButton_Visible.set_expand(true);
m_CheckButton_Visible.signal_toggled().connect( sigc::mem_fun(*this,
&ExampleWindow::on_checkbox_visibility_toggled) );
m_CheckButton_Visible.set_active(true);
m_Button_Close.signal_clicked().connect( sigc::mem_fun(*this,
&ExampleWindow::on_button_close) );
m_VBox.append(m_Button_Close);
m_Button_Close.set_expand();
set_default_widget(m_Button_Close);
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::on_checkbox_editable_toggled()
{
m_Entry.set_editable(m_CheckButton_Editable.get_active());
}
void ExampleWindow::on_checkbox_visibility_toggled()
{
m_Entry.set_visibility(m_CheckButton_Visible.get_active());
}
void ExampleWindow::on_button_close()
{
set_visible(false);
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
//Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</section>
<section xml:id="sec-text-entry-completion">
<title>Completado de «Entry»</title>
<note><para xml:lang="en"><classname>Gtk::EntryCompletion</classname> is deprecated since <application>gtkmm</application> 4.10.
There is no replacement in <application>gtkmm</application>.
</para></note>
<para xml:lang="en">An <classname>Entry</classname> widget can offer a drop-down list of
pre-existing choices based on the first few characters typed by the user. For
instance, a search dialog could suggest text from previous searches.
</para>
<para xml:lang="en">To enable this functionality, you must create an
<classname>EntryCompletion</classname> object, and provide it to the
<classname>Entry</classname> widget via the
<methodname>set_completion()</methodname> method.</para>
<para>El <classname>EntryCompletion</classname> puede usar un <classname>TreeModel</classname> que contenga las entradas posibles, especificadas por <methodname>set_model()</methodname>. Luego llame a <methodname>set_text_column()</methodname> para especificar cuál de las columnas de su modelo debe usarse para las posibles entradas de texto.</para>
<para>Alternativamente, si una lista completa de entradas posibles fuera muy larga o demasiado incómoda para generar, puede especificar en su lugar un espacio para una retrollamada con <methodname>set_match_func()</methodname>. Esto también es útil si no desea usar el principio de la cadena.</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1EntryCompletion.html">Reference</link></para>
<section xml:id="entry-completion-example">
<title>Ejemplo de completado de «Entry»</title>
<para>Este ejemplo crea un <classname>Gtk::EntryCompletion</classname> y lo asocia a un widget <classname>Gtk::Entry</classname>. Se usan un <classname>Gtk::TreeModel</classname> de entradas posibles para el completado y algunas acciones adicionales.</para>
<figure xml:id="figure-entry-completion">
<title>Completado de «Entry»</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/entry_completion.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/entry/completion">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
virtual ~ExampleWindow();
protected:
//Signal handlers:
void on_button_close();
//See the comment in the implementation:
//bool on_completion_match(const Glib::ustring& key, const Gtk::TreeModel::const_iterator& iter);
//Tree model columns, for the EntryCompletion's filter model:
class ModelColumns : public Gtk::TreeModel::ColumnRecord
{
public:
ModelColumns()
{ add(m_col_id); add(m_col_name); }
Gtk::TreeModelColumn<unsigned int> m_col_id;
Gtk::TreeModelColumn<Glib::ustring> m_col_name;
};
ModelColumns m_Columns;
//Child widgets:
Gtk::Box m_HBox;
Gtk::Box m_VBox;
Gtk::Entry m_Entry;
Gtk::Label m_Label;
Gtk::Button m_Button_Close;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <iostream>
ExampleWindow::ExampleWindow()
: m_VBox(Gtk::Orientation::VERTICAL),
m_Label("Press a or b to see a list of possible completions."),
m_Button_Close("Close")
{
//set_size_request(200, 100);
set_title("Gtk::EntryCompletion");
set_child(m_VBox);
m_VBox.append(m_Entry);
m_VBox.append(m_Label);
m_Label.set_expand(true);
m_Button_Close.signal_clicked().connect( sigc::mem_fun(*this,
&ExampleWindow::on_button_close) );
m_VBox.append(m_Button_Close);
set_default_widget(m_Button_Close);
//Add an EntryCompletion:
auto completion =
Gtk::EntryCompletion::create();
m_Entry.set_completion(completion);
//Create and fill the completion's filter model
auto refCompletionModel =
Gtk::ListStore::create(m_Columns);
completion->set_model(refCompletionModel);
// For more complex comparisons, use a filter match callback, like this.
// See the comment below for more details:
//completion->set_match_func( sigc::mem_fun(*this,
//&ExampleWindow::on_completion_match) );
//Fill the TreeView's model
auto row = *(refCompletionModel->append());
row[m_Columns.m_col_id] = 1;
row[m_Columns.m_col_name] = "Alan Zebedee";
row = *(refCompletionModel->append());
row[m_Columns.m_col_id] = 2;
row[m_Columns.m_col_name] = "Adrian Boo";
row = *(refCompletionModel->append());
row[m_Columns.m_col_id] = 3;
row[m_Columns.m_col_name] = "Bob McRoberts";
row = *(refCompletionModel->append());
row[m_Columns.m_col_id] = 4;
row[m_Columns.m_col_name] = "Bob McBob";
//Tell the completion what model column to use to
//- look for a match (when we use the default matching, instead of
// set_match_func().
//- display text in the entry when a match is found.
completion->set_text_column(m_Columns.m_col_name);
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::on_button_close()
{
set_visible(false);
}
/* You can do more complex matching with a handler like this.
* For instance, you could check for substrings inside the string instead of the start,
* or you could look for the key in extra model columns as well as the model column that will be displayed.
* The code here is not actually more complex - it's a reimplementation of the default behaviour.
*
bool ExampleWindow::on_completion_match(const Glib::ustring& key, const
Gtk::TreeModel::const_iterator& iter)
{
if(iter)
{
const auto row = *iter;
const auto key_length = key.size();
auto filter_string = row[m_Columns.m_col_name];
auto filter_string_start = filter_string.substr(0, key_length);
//The key is lower-case, even if the user input is not.
filter_string_start = filter_string_start.lowercase();
if(key == filter_string_start)
return true; //A match was found.
}
return false; //No match.
}
*/
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
//Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</section>
<section xml:id="sec-text-entry-icons">
<title>Iconos de «Entry»</title>
<para xml:lang="en">An <classname>Entry</classname> widget can show an icon at the start or
end of the text area. The icon can be specified by methods such as
<methodname>set_icon_from_paintable()</methodname> or
<methodname>set_icon_from_icon_name()</methodname>. An application can respond to the
user pressing the icon by handling the
<methodname>signal_icon_press</methodname> signal.</para>
<section xml:id="entry-icon-example">
<title>Ejemplo de icono de «Entry»</title>
<para>Este ejemplo muestra un widget <classname>Gtk::Entry</classname> con un icono de búsqueda del almacén, e imprime texto en la terminal cuando se pulsa el icono.</para>
<figure xml:id="figure-entry-icon">
<title>«Entry» con icono</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/entry_icon.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/entry/icon">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
virtual ~ExampleWindow();
protected:
//Signal handlers:
void on_icon_pressed(Gtk::Entry::IconPosition icon_pos);
void on_button_close();
//Child widgets:
Gtk::Box m_VBox;
Gtk::Entry m_Entry;
Gtk::Button m_Button_Close;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <iostream>
ExampleWindow::ExampleWindow()
: m_VBox(Gtk::Orientation::VERTICAL),
m_Button_Close("Close")
{
set_title("Gtk::Entry");
set_child(m_VBox);
m_Entry.set_max_length(50);
m_Entry.set_text("Hello world");
m_VBox.append(m_Entry);
m_Entry.set_icon_from_icon_name("edit-find");
m_Entry.signal_icon_press().connect( sigc::mem_fun(*this, &ExampleWindow::on_icon_pressed) );
m_Button_Close.signal_clicked().connect( sigc::mem_fun(*this,
&ExampleWindow::on_button_close) );
m_VBox.append(m_Button_Close);
set_default_widget(m_Button_Close);
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::on_icon_pressed(Gtk::Entry::IconPosition /* icon_pos */)
{
std::cout << "Icon pressed." << std::endl;
}
void ExampleWindow::on_button_close()
{
set_visible(false);
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
//Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</section>
<section xml:id="sec-text-entry-progress">
<title>«Entry» de progreso</title>
<para>Un widget <classname>Entry</classname> puede mostrar una barra de progreso dentro del área del texto, bajo el texto introducido. La barra de progreso se mostrará si se llama a los métodos <methodname>set_progress_fraction()</methodname> o <methodname>set_progress_pulse_step()</methodname>.</para>
<section xml:id="entry-progress-example">
<title>Ejemplo de «Entry» de progreso</title>
<para>Este ejemplo muestra un widget <classname>Gtk::Entry</classname> con una barra de progreso.</para>
<figure xml:id="figure-entry-progress">
<title>«Entry» con barra de progreso</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/entry_progress.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/entry/progress">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
virtual ~ExampleWindow();
protected:
//Signal handlers:
bool on_timeout();
void on_button_close();
//Child widgets:
Gtk::Box m_VBox;
Gtk::Entry m_Entry;
Gtk::Button m_Button_Close;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <iostream>
ExampleWindow::ExampleWindow()
: m_VBox(Gtk::Orientation::VERTICAL),
m_Button_Close("Close")
{
set_title("Gtk::Entry");
set_child(m_VBox);
m_Entry.set_max_length(50);
m_Entry.set_text("Hello world");
m_VBox.append(m_Entry);
//Change the progress fraction every 0.1 second:
Glib::signal_timeout().connect(
sigc::mem_fun(*this, &ExampleWindow::on_timeout),
100
);
m_Button_Close.signal_clicked().connect( sigc::mem_fun(*this,
&ExampleWindow::on_button_close) );
m_VBox.append(m_Button_Close);
set_default_widget(m_Button_Close);
}
ExampleWindow::~ExampleWindow()
{
}
bool ExampleWindow::on_timeout()
{
static double fraction = 0;
m_Entry.set_progress_fraction(fraction);
fraction += 0.01;
if(fraction > 1)
fraction = 0;
return true;
}
void ExampleWindow::on_button_close()
{
set_visible(false);
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
//Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</section>
</section>
<section xml:id="sec-spinbutton">
<title>SpinButton</title>
<para xml:lang="en">
A <classname>SpinButton</classname> allows the user to select a value from a
range of numeric values. It has an <classname>Entry</classname> widget with increment and decrement buttons
at the side. Clicking the buttons causes the value to 'spin' up and down across
the range of possible values. The <classname>Entry</classname> widget may also
be used to enter a value directly.
</para>
<para>El valor puede tener un número ajustable de decimales, y el tamaño del paso es configurable. Los <classname>SpinButton</classname> también tienen una característica de «auto-repetición»: mantener pulsada una de las flechas puede, opcionalmente, causar que el valor cambie más rápidamente cuanto más tiempo se mantenga pulsada la flecha.</para>
<para xml:lang="en">
<classname>SpinButton</classname>s use an <link linkend="chapter-adjustment">Adjustment</link> object to hold information about
the range of values. These Adjustment attributes are used by the Spin Button
like so:
<itemizedlist>
<listitem>
<para xml:lang="en">
<literal>value</literal>: value for the Spin Button
</para>
</listitem>
<listitem>
<para xml:lang="en">
<literal>lower</literal>: lower range value
</para>
</listitem>
<listitem>
<para xml:lang="en">
<literal>upper</literal>: upper range value
</para>
</listitem>
<listitem>
<para xml:lang="en">
<literal>step_increment</literal>: value to increment/decrement when pressing
mouse button 1
</para>
</listitem>
<listitem>
<para xml:lang="en">
<literal>page_increment</literal>: value to increment/decrement when pressing
mouse button 2
</para>
</listitem>
<listitem>
<para xml:lang="en">
<literal>page_size</literal>: unused
</para>
</listitem>
</itemizedlist>
</para>
<para>Además, el botón 3 del ratón se puede usar para saltar directamente a los valores <literal>upper</literal> o <literal>lower</literal>.</para>
<para>El <classname>SpinButton</classname> puede crear un <classname>Adjustment</classname> predeterminado, al que puede acceder mediante el método <methodname>get_adjustment()</methodname>, o puede especificar un <classname>Adjustment</classname> existente en el constructor.</para>
<section xml:id="spinbutton-methods">
<title>Métodos</title>
<para>La cantidad de lugares decimales se puede alterar usando el método <methodname>set_digits()</methodname>.</para>
<para>Puede establecer el valor del «spinbutton» usando el método <methodname>set_value()</methodname>, y obtenerlo con <methodname>get_value()</methodname>.</para>
<para>El método <methodname>spin()</methodname> «gira» el <classname>SpinButton</classname>, como si se hubiera presionado una de sus flechas. Debe especificar un <classname>Gtk::SpinType</classname> para especificar la dirección de su posición nueva.</para>
<para>Para que el usuario no pueda introducir caracteres no numéricos al cuadro de entrada, pásele <literal>true</literal> al método <methodname>set_numeric()</methodname>.</para>
<para>Para que el <classname>SpinButton</classname> «salte» entre sus límites superior e inferior, use el método <methodname>set_wrap()</methodname>.</para>
<para>Para forzarlo a encajar en el <literal>step_increment</literal> más cercano, use <methodname>set_snap_to_ticks()</methodname>.</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1SpinButton.html">Reference</link></para>
</section>
<section xml:id="spinbutton-example">
<title>Ejemplo</title>
<para>Aquí hay un ejemplo de un <classname>SpinButton</classname> en acción:</para>
<figure xml:id="figure-spinbutton">
<title>SpinButton</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/spinbutton.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/spinbutton">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
virtual ~ExampleWindow();
protected:
//Signal handlers:
void on_checkbutton_snap();
void on_checkbutton_numeric();
void on_spinbutton_digits_changed();
void on_button_close();
enum enumValueFormats
{
VALUE_FORMAT_INT,
VALUE_FORMAT_FLOAT
};
void on_button_getvalue(enumValueFormats display);
//Child widgets:
Gtk::Frame m_Frame_NotAccelerated, m_Frame_Accelerated;
Gtk::Box m_HBox_NotAccelerated, m_HBox_Accelerated,
m_HBox_Buttons;
Gtk::Box m_VBox_Main, m_VBox, m_VBox_Day, m_VBox_Month, m_VBox_Year,
m_VBox_Accelerated, m_VBox_Value, m_VBox_Digits;
Gtk::Label m_Label_Day, m_Label_Month, m_Label_Year,
m_Label_Value, m_Label_Digits,
m_Label_ShowValue;
Glib::RefPtr<Gtk::Adjustment> m_adjustment_day, m_adjustment_month, m_adjustment_year,
m_adjustment_value, m_adjustment_digits;
Gtk::SpinButton m_SpinButton_Day, m_SpinButton_Month, m_SpinButton_Year,
m_SpinButton_Value, m_SpinButton_Digits;
Gtk::CheckButton m_CheckButton_Snap, m_CheckButton_Numeric;
Gtk::Button m_Button_Int, m_Button_Float, m_Button_Close;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <iostream>
#include <cstdio>
ExampleWindow::ExampleWindow()
:
m_Frame_NotAccelerated("Not accelerated"),
m_Frame_Accelerated("Accelerated"),
m_VBox_Main(Gtk::Orientation::VERTICAL, 5),
m_VBox(Gtk::Orientation::VERTICAL),
m_VBox_Day(Gtk::Orientation::VERTICAL),
m_VBox_Month(Gtk::Orientation::VERTICAL),
m_VBox_Year(Gtk::Orientation::VERTICAL),
m_VBox_Accelerated(Gtk::Orientation::VERTICAL),
m_VBox_Value(Gtk::Orientation::VERTICAL),
m_VBox_Digits(Gtk::Orientation::VERTICAL),
m_Label_Day("Day: ", Gtk::Align::START),
m_Label_Month("Month: ", Gtk::Align::START),
m_Label_Year("Year: ", Gtk::Align::START),
m_Label_Value("Value: ", Gtk::Align::START),
m_Label_Digits("Digits: ", Gtk::Align::START),
m_adjustment_day( Gtk::Adjustment::create(1.0, 1.0, 31.0, 1.0, 5.0, 0.0) ),
m_adjustment_month( Gtk::Adjustment::create(1.0, 1.0, 12.0, 1.0, 5.0, 0.0) ),
m_adjustment_year( Gtk::Adjustment::create(2012.0, 1.0, 2200.0, 1.0, 100.0, 0.0) ),
m_adjustment_value( Gtk::Adjustment::create(0.0, -10000.0, 10000.0, 0.5, 100.0, 0.0) ),
m_adjustment_digits( Gtk::Adjustment::create(2.0, 1.0, 5.0, 1.0, 1.0, 0.0) ),
m_SpinButton_Day(m_adjustment_day),
m_SpinButton_Month(m_adjustment_month),
m_SpinButton_Year(m_adjustment_year),
m_SpinButton_Value(m_adjustment_value, 1.0, 2),
m_SpinButton_Digits(m_adjustment_digits),
m_CheckButton_Snap("Snap to 0.5-ticks"),
m_CheckButton_Numeric("Numeric only input mode"),
m_Button_Int("Value as Int"),
m_Button_Float("Value as Float"),
m_Button_Close("Close")
{
set_title("SpinButton");
m_VBox_Main.set_margin(10);
set_child(m_VBox_Main);
m_VBox_Main.append(m_Frame_NotAccelerated);
m_VBox.set_margin(5);
m_Frame_NotAccelerated.set_child(m_VBox);
/* Day, month, year spinners */
m_VBox.set_spacing(5);
m_VBox.append(m_HBox_NotAccelerated);
m_Label_Day.set_expand();
m_VBox_Day.append(m_Label_Day);
m_SpinButton_Day.set_wrap();
m_SpinButton_Day.set_expand();
m_VBox_Day.append(m_SpinButton_Day);
m_HBox_NotAccelerated.set_spacing(5);
m_HBox_NotAccelerated.append(m_VBox_Day);
m_Label_Month.set_expand();
m_VBox_Month.append(m_Label_Month);
m_SpinButton_Month.set_wrap();
m_SpinButton_Month.set_expand();
m_VBox_Month.append(m_SpinButton_Month);
m_HBox_NotAccelerated.append(m_VBox_Month);
m_Label_Year.set_expand();
m_VBox_Year.append(m_Label_Year);
m_SpinButton_Year.set_wrap();
m_SpinButton_Year.set_expand();
m_SpinButton_Year.set_size_request(55, -1);
m_VBox_Year.append(m_SpinButton_Year);
m_HBox_NotAccelerated.append(m_VBox_Year);
//Accelerated:
m_VBox_Main.append(m_Frame_Accelerated);
m_VBox_Accelerated.set_margin(5);
m_Frame_Accelerated.set_child(m_VBox_Accelerated);
m_VBox_Accelerated.set_spacing(5);
m_VBox_Accelerated.append(m_HBox_Accelerated);
m_HBox_Accelerated.append(m_VBox_Value);
m_Label_Value.set_expand();
m_VBox_Value.append(m_Label_Value);
m_SpinButton_Value.set_wrap();
m_SpinButton_Value.set_expand();
m_SpinButton_Value.set_size_request(100, -1);
m_VBox_Value.append(m_SpinButton_Value);
m_HBox_Accelerated.append(m_VBox_Digits);
m_VBox_Digits.append(m_Label_Digits);
m_Label_Digits.set_expand();
m_SpinButton_Digits.set_wrap();
m_adjustment_digits->signal_value_changed().connect( sigc::mem_fun(*this,
&ExampleWindow::on_spinbutton_digits_changed) );
m_VBox_Digits.append(m_SpinButton_Digits);
m_SpinButton_Digits.set_expand();
//CheckButtons:
m_VBox_Accelerated.append(m_CheckButton_Snap);
m_CheckButton_Snap.set_expand();
m_CheckButton_Snap.set_active();
m_CheckButton_Snap.signal_toggled().connect( sigc::mem_fun(*this,
&ExampleWindow::on_checkbutton_snap) );
m_VBox_Accelerated.append(m_CheckButton_Numeric);
m_CheckButton_Numeric.set_expand();
m_CheckButton_Numeric.set_active();
m_CheckButton_Numeric.signal_toggled().connect( sigc::mem_fun(*this,
&ExampleWindow::on_checkbutton_numeric) );
//Buttons:
m_VBox_Accelerated.append(m_HBox_Buttons);
m_Button_Int.signal_clicked().connect( sigc::bind( sigc::mem_fun(*this,
&ExampleWindow::on_button_getvalue), VALUE_FORMAT_INT) );
m_HBox_Buttons.set_spacing(5);
m_HBox_Buttons.append(m_Button_Int);
m_Button_Int.set_expand();
m_Button_Float.signal_clicked().connect( sigc::bind( sigc::mem_fun(*this,
&ExampleWindow::on_button_getvalue), VALUE_FORMAT_FLOAT) );
m_HBox_Buttons.append(m_Button_Float);
m_Button_Float.set_expand();
m_VBox_Accelerated.append(m_Label_ShowValue);
m_Label_ShowValue.set_expand();
m_Label_ShowValue.set_text("0");
//Close button:
m_Button_Close.signal_clicked().connect( sigc::mem_fun(*this,
&ExampleWindow::on_button_close) );
m_VBox_Main.append(m_Button_Close);
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::on_button_close()
{
set_visible(false);
}
void ExampleWindow::on_checkbutton_snap()
{
m_SpinButton_Value.set_snap_to_ticks( m_CheckButton_Snap.get_active() );
}
void ExampleWindow::on_checkbutton_numeric()
{
m_SpinButton_Value.set_numeric( m_CheckButton_Numeric.get_active() );
}
void ExampleWindow::on_spinbutton_digits_changed()
{
m_SpinButton_Value.set_digits( m_SpinButton_Digits.get_value_as_int() );
}
void ExampleWindow::on_button_getvalue(enumValueFormats display)
{
gchar buf[32];
if (display == VALUE_FORMAT_INT)
sprintf (buf, "%d", m_SpinButton_Value.get_value_as_int());
else
sprintf (buf, "%0.*f", m_SpinButton_Value.get_digits(),
m_SpinButton_Value.get_value());
m_Label_ShowValue.set_text(buf);
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
//Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</section>
<section xml:id="sec-progressbar">
<title>ProgressBar</title>
<para>Las barras de progreso se usan para mostrar el estado de una operación en curso. Por ejemplo, una <classname>ProgressBar</classname> puede mostrar cuánto se ha completado de una tarea.</para>
<para xml:lang="en">
To change the value shown, use the <methodname>set_fraction()</methodname> method,
passing a <type>double</type> between 0.0 and 1.0 to provide the new fraction.
</para>
<para>Una <classname>ProgressBar</classname> es horizontal y de izquierda a derecha de manera predeterminada, pero se puede cambiar a una barra de progreso vertical mediante el uso del método <methodname>set_orientation()</methodname>.</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1ProgressBar.html">Reference</link></para>
<section xml:id="progressbar-activity-mode">
<title>Modo de actividad</title>
<para xml:lang="en">
Besides indicating the amount of progress that has occurred, the
progress bar can also be used to indicate that there is some activity;
this is done by placing the progress bar in <emphasis>activity mode</emphasis>. In
this mode, the progress bar displays a small rectangle which moves
back and forth. Activity mode is useful in situations where the
progress of an operation cannot be calculated as a value range (e.g.,
receiving a file of unknown length).
</para>
<para>Para hacer esto, debe llamar al método <methodname>pulse()</methodname> a intervalos regulares. También puede elegir el tamaño del paso con el método <methodname>set_pulse_step()</methodname>.</para>
<para xml:lang="en">
The progress bar can also display a configurable text
string next to the bar, using the <methodname>set_text()</methodname> method.
</para>
</section>
<section xml:id="progressbar-example">
<title>Ejemplo</title>
<figure xml:id="figure-progressbar">
<title>ProgressBar</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/progressbar.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/progressbar">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
virtual ~ExampleWindow();
protected:
//Signal handlers:
void on_checkbutton_text();
void on_checkbutton_activity();
void on_checkbutton_inverted();
bool on_timeout();
void on_button_close();
//Child widgets:
Gtk::Box m_VBox;
Gtk::Grid m_Grid;
Gtk::ProgressBar m_ProgressBar;
Gtk::Separator m_Separator;
Gtk::CheckButton m_CheckButton_Text, m_CheckButton_Activity, m_CheckButton_Inverted;
Gtk::Button m_Button_Close;
sigc::connection m_connection_timeout;
bool m_bActivityMode;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <iostream>
ExampleWindow::ExampleWindow()
: m_VBox(Gtk::Orientation::VERTICAL, 5),
m_CheckButton_Text("Show text"),
m_CheckButton_Activity("Activity mode"),
m_CheckButton_Inverted("Right to Left"),
m_Button_Close("Close"),
m_bActivityMode(false)
{
set_resizable();
set_title("Gtk::ProgressBar");
m_VBox.set_margin(10);
set_child(m_VBox);
m_VBox.append(m_ProgressBar);
m_ProgressBar.set_margin_end(5);
m_ProgressBar.set_halign(Gtk::Align::CENTER);
m_ProgressBar.set_valign(Gtk::Align::CENTER);
m_ProgressBar.set_size_request(100, -1);
m_ProgressBar.set_text("some text");
m_ProgressBar.set_show_text(false);
//Add a timer callback to update the value of the progress bar:
m_connection_timeout = Glib::signal_timeout().connect(sigc::mem_fun(*this,
&ExampleWindow::on_timeout), 50 );
m_VBox.append(m_Separator);
m_VBox.append(m_Grid);
m_Grid.set_expand(true);
m_Grid.set_row_homogeneous(true);
//Add a check button to select displaying of the trough text:
m_Grid.attach(m_CheckButton_Text, 0, 0);
m_CheckButton_Text.set_margin(5);
m_CheckButton_Text.signal_toggled().connect(sigc::mem_fun(*this,
&ExampleWindow::on_checkbutton_text) );
//Add a check button to toggle activity mode:
m_Grid.attach(m_CheckButton_Activity, 0, 1);
m_CheckButton_Activity.set_margin(5);
m_CheckButton_Activity.signal_toggled().connect(sigc::mem_fun(*this,
&ExampleWindow::on_checkbutton_activity) );
//Add a check button to select growth from left to right or from right to left:
m_Grid.attach(m_CheckButton_Inverted, 0, 2);
m_CheckButton_Inverted.set_margin(5);
m_CheckButton_Inverted.signal_toggled().connect(sigc::mem_fun(*this,
&ExampleWindow::on_checkbutton_inverted) );
//Add a button to exit the program.
m_VBox.append(m_Button_Close);
m_Button_Close.signal_clicked().connect(sigc::mem_fun(*this,
&ExampleWindow::on_button_close) );
set_default_widget(m_Button_Close);
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::on_checkbutton_text()
{
const bool show_text = m_CheckButton_Text.get_active();
m_ProgressBar.set_show_text(show_text);
}
void ExampleWindow::on_checkbutton_activity()
{
m_bActivityMode = m_CheckButton_Activity.get_active();
if(m_bActivityMode)
m_ProgressBar.pulse();
else
m_ProgressBar.set_fraction(0.0);
}
void ExampleWindow::on_checkbutton_inverted()
{
const bool inverted = m_CheckButton_Inverted.get_active();
m_ProgressBar.set_inverted(inverted);
}
void ExampleWindow::on_button_close()
{
set_visible(false);
}
/* Update the value of the progress bar so that we get
* some movement */
bool ExampleWindow::on_timeout()
{
if(m_bActivityMode)
m_ProgressBar.pulse();
else
{
double new_val = m_ProgressBar.get_fraction() + 0.01;
if(new_val > 1.0)
new_val = 0.0;
//Set the new value:
m_ProgressBar.set_fraction(new_val);
}
//As this is a timeout function, return true so that it
//continues to get called
return true;
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
//Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</section>
<section xml:id="sec-infobar">
<title>InfoBar</title>
<para>Una <classname>InfoBar</classname> puede mostrar pequeños elementos de información o hacer preguntas breves. A diferencia de un <classname>Dialog</classname>, aparece encima de la ventana actual en vez de en una ventana nueva. Su API es muy similar a la del <link linkend="chapter-dialogs">Gtk::Dialog</link>.</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1InfoBar.html">Reference</link></para>
<section xml:id="infobar-example">
<title>Ejemplo</title>
<para xml:lang="en">The <classname>InfoBar</classname> widget is deprecated since <application>gtkmm</application> 4.10.
The example shows an info bar consisting of a <classname>Box</classname> with
a <classname>Label</classname> and a <classname>Button</classname>.</para>
<figure xml:id="figure-infobar">
<title>InfoBar</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/infobar.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/infobar">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
~ExampleWindow() override;
protected:
//Signal handlers:
void on_infobar_ok();
void on_button_quit();
void on_button_clear();
void on_textbuffer_changed();
void on_parsing_error(const Glib::RefPtr<const Gtk::CssSection>& section,
const Glib::Error& error);
void set_infobar_background();
//Child widgets:
Gtk::Box m_VBox;
Gtk::ScrolledWindow m_ScrolledWindow;
Gtk::TextView m_TextView;
Glib::RefPtr<Gtk::TextBuffer> m_refTextBuffer;
Gtk::Box m_InfoBar;
Gtk::Label m_Message_Label;
Gtk::Button m_Button_OK;
Gtk::Box m_ButtonBox;
Gtk::Button m_Button_Quit;
Gtk::Button m_Button_Clear;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <iostream>
// The Gtk::InfoBar widget is deprecated since gtk/gtkmm 4.10.
// A Box with a Label and Button is often a good replacement.
// If you don't care about its background color, it's even easier
// than the InfoBar replacement in this example program.
ExampleWindow::ExampleWindow()
: m_VBox(Gtk::Orientation::VERTICAL, 6),
m_InfoBar(Gtk::Orientation::HORIZONTAL, 6),
m_Button_OK("_OK", true),
m_ButtonBox(Gtk::Orientation::HORIZONTAL, 6),
m_Button_Quit("_Quit", true),
m_Button_Clear("_Clear", true)
{
set_title("Infobar example");
set_default_size(400, 200);
m_VBox.set_margin(6);
set_child(m_VBox);
// Add the message label to the InfoBar:
m_InfoBar.append(m_Message_Label);
m_Message_Label.set_margin_start(5);
m_Message_Label.set_hexpand(true);
m_Message_Label.set_halign(Gtk::Align::START);
// Add an ok button to the InfoBar:
m_InfoBar.append(m_Button_OK);
m_Button_OK.set_margin(5);
// Add the InfoBar to the vbox:
m_VBox.append(m_InfoBar);
// Create the buffer and set it for the TextView:
m_refTextBuffer = Gtk::TextBuffer::create();
m_TextView.set_buffer(m_refTextBuffer);
// Add the TextView, inside a ScrolledWindow:
m_ScrolledWindow.set_child(m_TextView);
// Show the scrollbars only when they are necessary:
m_ScrolledWindow.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
m_ScrolledWindow.set_expand();
m_VBox.append(m_ScrolledWindow);
// Add button box:
m_VBox.append(m_ButtonBox);
m_ButtonBox.append(m_Button_Clear);
m_ButtonBox.append(m_Button_Quit);
m_Button_Clear.set_hexpand(true);
m_Button_Clear.set_halign(Gtk::Align::END);
// Connect signals:
m_Button_OK.signal_clicked().connect(sigc::mem_fun(*this,
&ExampleWindow::on_infobar_ok) );
m_Button_Quit.signal_clicked().connect(sigc::mem_fun(*this,
&ExampleWindow::on_button_quit) );
m_Button_Clear.signal_clicked().connect(sigc::mem_fun(*this,
&ExampleWindow::on_button_clear) );
m_refTextBuffer->signal_changed().connect(sigc::mem_fun(*this,
&ExampleWindow::on_textbuffer_changed) );
// Keep the InfoBar hidden until a message needs to be shown:
m_InfoBar.set_visible(false);
set_infobar_background();
// Make the clear button insensitive until text is typed in the buffer. When
// the button is sensitive and it is pressed, the InfoBar is displayed with a
// message.
m_Button_Clear.set_sensitive(false);
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::on_infobar_ok()
{
// Clear the message and hide the info bar:
m_Message_Label.set_text("");
m_InfoBar.set_visible(false);
}
void ExampleWindow::on_button_quit()
{
set_visible(false);
}
void ExampleWindow::on_button_clear()
{
m_refTextBuffer->set_text("");
m_Message_Label.set_text("Cleared the text.");
m_InfoBar.set_visible(true);
}
void ExampleWindow::on_textbuffer_changed()
{
m_Button_Clear.set_sensitive(m_refTextBuffer->size() > 0);
}
void ExampleWindow::set_infobar_background()
{
// Concerning color in CSS files, see https://www.w3.org/TR/css-color-3.
const std::string css = "#infobarbox { background-color: beige; }";
m_InfoBar.set_name("infobarbox");
auto css_provider = Gtk::CssProvider::create();
Gtk::StyleProvider::add_provider_for_display(
m_InfoBar.get_display(), css_provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
css_provider->load_from_data(css);
css_provider->signal_parsing_error().connect(
sigc::mem_fun(*this, &ExampleWindow::on_parsing_error));
}
void ExampleWindow::on_parsing_error(const Glib::RefPtr<const Gtk::CssSection>& section,
const Glib::Error& error)
{
std::cerr << "on_parsing_error(): " << error.what() << std::endl;
if (section)
{
const auto file = section->get_file();
if (file)
{
std::cerr << " URI = " << file->get_uri() << std::endl;
}
auto start_location = section->get_start_location();
auto end_location = section->get_end_location();
std::cerr << " start_line = " << start_location.get_lines()+1
<< ", end_line = " << end_location.get_lines()+1 << std::endl;
std::cerr << " start_position = " << start_location.get_line_chars()
<< ", end_position = " << end_location.get_line_chars() << std::endl;
}
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
//Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</section>
<section xml:id="sec-tooltips">
<title>Consejos</title>
<para xml:lang="en">
Tooltips are the little information windows that pop up when you leave your
pointer over a widget for a few seconds. Use
<methodname>set_tooltip_text()</methodname> to set a text string as a tooltip
on any <classname>Widget</classname>.
<classname>Gtk::Tooltip</classname> is used for more advanced tooltip usage,
such as showing an image as well as text.
</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1Widget.html">Widget Reference</link></para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1Tooltip.html">Tooltip Reference</link></para>
<section xml:id="tooltip-example">
<title>Ejemplo</title>
<figure xml:id="figure-tooltip">
<title>Consejo</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/tooltip.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/tooltips">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
virtual ~ExampleWindow();
protected:
//Methods:
void prepare_textview();
void connect_signals();
//Signal handlers:
void on_markup_checkbutton_toggled();
bool on_textview_query_tooltip(int x, int y, bool keyboard_tooltip, const Glib::RefPtr<Gtk::Tooltip>& tooltip);
bool on_button_query_tooltip(int x, int y, bool keyboard_tooltip, const Glib::RefPtr<Gtk::Tooltip>& tooltip);
//Child widgets:
Gtk::Box m_vbox;
Gtk::CheckButton m_checkbutton;
Gtk::Label m_label;
Gtk::ScrolledWindow m_scrolled_window;
Gtk::TextView m_text_view;
Glib::RefPtr<Gtk::TextBuffer> m_ref_text_buffer;
Glib::RefPtr<Gtk::TextTag> m_ref_bold_tag;
Gtk::Button m_button;
Gtk::Box m_button_tooltip_widget;
};
#endif // GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <vector>
const Glib::ustring app_title = "gtkmm tooltips example";
const Glib::ustring non_markedup_tip = "A tooltip without markup.";
const Glib::ustring markedup_tip = "<i>Markup</i> in a tooltip.";
ExampleWindow::ExampleWindow()
:
m_vbox(Gtk::Orientation::VERTICAL, 3),
m_checkbutton("Click to alternate markup in tooltip"),
m_label("A label"),
m_button("Button with a custom tooltip widget")
{
//Set up window and the top-level container:
set_title(app_title);
m_vbox.set_margin(10);
set_child(m_vbox);
//Check button with markup in tooltip:
m_checkbutton.set_tooltip_text(non_markedup_tip);
m_vbox.append(m_checkbutton);
//Label:
m_label.set_tooltip_text("Another tooltip");
m_vbox.append(m_label);
//Textview:
prepare_textview();
//Button:
//When only connecting to the query-tooltip signal, and not using any
//of set_tooltip_text() or set_tooltip_markup(), we need to explicitly
//tell GTK that the widget has a tooltip which we'll show.
m_button.set_has_tooltip();
m_vbox.append(m_button);
//Button's custom tooltip widget:
auto label = Gtk::make_managed<Gtk::Label>("A label in a custom tooltip widget");
m_button_tooltip_widget.append(*label);
connect_signals();
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::prepare_textview()
{
Gtk::TextIter iter;
std::vector<Glib::RefPtr<Gtk::TextTag>> tags;
//Set up a scrolled window:
m_scrolled_window.set_child(m_text_view);
m_scrolled_window.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
m_scrolled_window.set_expand();
m_vbox.append(m_scrolled_window);
//Create a text buffer with some text:
m_ref_text_buffer = Gtk::TextBuffer::create();
iter = m_ref_text_buffer->end();
m_ref_text_buffer->insert(iter, "Hover over the text ");
//Insert some text with a tag.
//In the tooltip signal handler below, we will show a tooltip
//when mouse pointer is above this tagged text.
m_ref_bold_tag = m_ref_text_buffer->create_tag("bold");
m_ref_bold_tag->set_property("weight", Pango::Weight::BOLD);
tags.push_back(m_ref_bold_tag);
iter = m_ref_text_buffer->end();
m_ref_text_buffer->insert_with_tags(iter, "in bold", tags);
iter = m_ref_text_buffer->end();
m_ref_text_buffer->insert(iter, " to see its tooltip");
m_text_view.set_buffer(m_ref_text_buffer);
m_text_view.set_size_request(320, 50);
//When only connecting to the query-tooltip signal, and not using any
//of set_tooltip_text() or set_tooltip_markup(), we need to explicitly
//tell GTK that the widget has a tooltip which we'll show.
m_text_view.set_has_tooltip();
}
void ExampleWindow::connect_signals()
{
m_checkbutton.signal_toggled().connect(
sigc::mem_fun(*this, &ExampleWindow::on_markup_checkbutton_toggled));
m_text_view.signal_query_tooltip().connect(
sigc::mem_fun(*this, &ExampleWindow::on_textview_query_tooltip), true);
m_button.signal_query_tooltip().connect(
sigc::mem_fun(*this, &ExampleWindow::on_button_query_tooltip), true);
}
void ExampleWindow::on_markup_checkbutton_toggled()
{
if (m_checkbutton.get_active())
{
m_checkbutton.set_tooltip_markup(markedup_tip);
}
else
{
m_checkbutton.set_tooltip_markup(non_markedup_tip);
}
}
bool ExampleWindow::on_textview_query_tooltip(int x, int y, bool keyboard_tooltip, const Glib::RefPtr<Gtk::Tooltip>& tooltip)
{
Gtk::TextIter iter;
if (keyboard_tooltip)
{
int offset = m_ref_text_buffer->property_cursor_position().get_value();
iter = m_ref_text_buffer->get_iter_at_offset(offset);
}
else
{
int mouse_x, mouse_y, trailing;
m_text_view.window_to_buffer_coords(Gtk::TextWindowType::TEXT,
x, y, mouse_x, mouse_y);
m_text_view.get_iter_at_position(iter, trailing, mouse_x, mouse_y);
}
if (!iter.has_tag(m_ref_bold_tag))
return false;
//Show a tooltip if the cursor or mouse pointer is over the text
//with the specific tag:
tooltip->set_markup("<b>Information</b> attached to a text tag");
tooltip->set_icon("dialog-information");
return true;
}
bool ExampleWindow::on_button_query_tooltip(int, int, bool, const Glib::RefPtr<Gtk::Tooltip>& tooltip)
{
tooltip->set_custom(m_button_tooltip_widget);
return true;
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
//Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</section>
</chapter>
<chapter xml:id="chapter-container-widgets">
<title>Widgets contenedores</title>
<para xml:lang="en">
Container widgets, like other widgets, derive from <classname>Gtk::Widget</classname>.
Some container widgets, such as <classname>Gtk::Grid</classname> can hold many
child widgets, so these typically have more complex interfaces. Others, such as
<classname>Gtk::Frame</classname> contain only one child widget.
</para>
<section xml:id="sec-single-item-containers">
<title>Contenedor de un sólo elemento</title>
<para xml:lang="en">
Most single-item container widgets have <methodname>set_child()</methodname>
and <methodname>unset_child()</methodname> methods for the child widget.
<classname>Gtk::Button</classname> and <classname>Gtk::Window</classname> are
technically single-item containers, but we have discussed them already elsewhere.
</para>
<para>También se habla del widget <classname>Gtk::Paned</classname>, que le permite dividir una ventana en dos «paneles» separados. El widget, en realidad, contiene dos widgets hijos, pero el número es fijo, por lo que parece apropiado.</para>
<section xml:id="sec-frame">
<title>Marco</title>
<para xml:lang="en">
Frames can enclose one or a group of widgets within a box, optionally with a
title. For instance, you might place a group of
<classname>ToggleButton</classname>s or <classname>CheckButton</classname>s in a
<classname>Frame</classname>.
</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1Frame.html">Reference</link></para>
<section xml:id="frame-example">
<title>Ejemplo</title>
<figure xml:id="figure-frame">
<title>Marco</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/frame.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/frame">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
virtual ~ExampleWindow();
protected:
//Child widgets:
Gtk::Frame m_Frame;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
ExampleWindow::ExampleWindow()
{
/* Set some window properties */
set_title("Frame Example");
set_size_request(300, 300);
/* Sets the margin around the frame. */
m_Frame.set_margin(10);
set_child(m_Frame);
/* Set the frames label */
m_Frame.set_label("Gtk::Frame Widget");
/* Align the label at the right of the frame */
m_Frame.set_label_align(Gtk::Align::END);
}
ExampleWindow::~ExampleWindow()
{
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
//Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</section>
<section xml:id="sec-paned">
<title>Con paneles</title>
<para>Se pueden usar paneles para dividir un widget en dos mitades, separadas por un divisor móvil. Las dos mitades (paneles) pueden orientarse tanto horizontal (lado a lado) como verticalmente (uno encima de otro).</para>
<para xml:lang="en">
Unlike the other widgets in this section, pane widgets contain not one but two
child widgets, one in each pane. Therefore, you should use
<methodname>set_start_child()</methodname> and <methodname>set_end_child()</methodname>
instead of a <methodname>set_child()</methodname> method.
</para>
<para>Puede ajustar la posición del divisor usando el método <methodname>set_position()</methodname>, y probablemente lo necesite.</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1Paned.html">Reference</link></para>
<section xml:id="paned-example">
<title>Ejemplo</title>
<figure xml:id="figure-paned">
<title>Con paneles</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/paned.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/paned">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include "messageslist.h"
#include "messagetext.h"
#include <gtkmm.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
virtual ~ExampleWindow();
protected:
//Child widgets:
Gtk::Paned m_VPaned;
MessagesList m_MessagesList;
MessageText m_MessageText;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>messageslist.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLE_MESSAGESLIST_H
#define GTKMM_EXAMPLE_MESSAGESLIST_H
#include <gtkmm.h>
class MessagesList: public Gtk::ScrolledWindow
{
public:
MessagesList();
~MessagesList() override;
protected:
// Signal handlers:
void on_setup_message(const Glib::RefPtr<Gtk::ListItem>& list_item);
void on_bind_message(const Glib::RefPtr<Gtk::ListItem>& list_item);
Glib::RefPtr<Gtk::StringList> m_refStringList; // The List Model.
Gtk::ListView m_ListView; // The List View.
};
#endif //GTKMM_EXAMPLE_MESSAGESLIST_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>messagetext.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLE_MESSAGETEXT_H
#define GTKMM_EXAMPLE_MESSAGETEXT_H
#include <gtkmm.h>
class MessageText : public Gtk::ScrolledWindow
{
public:
MessageText();
virtual ~MessageText();
void insert_text();
protected:
Gtk::TextView m_TextView;
};
#endif //GTKMM_EXAMPLE_MESSAGETEXT_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
ExampleWindow::ExampleWindow()
: m_VPaned(Gtk::Orientation::VERTICAL)
{
set_title ("Paned Windows");
set_default_size(450, 400);
m_VPaned.set_margin(10);
/* Add a vpaned widget to our toplevel window */
set_child(m_VPaned);
/* Now add the contents of the two halves of the window */
m_VPaned.set_start_child(m_MessagesList);
m_VPaned.set_end_child(m_MessageText);
}
ExampleWindow::~ExampleWindow()
{
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
//Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>messageslist.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "messageslist.h"
MessagesList::MessagesList()
{
/* Create a new scrolled window, with scrollbars only if needed */
set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
set_child(m_ListView);
/* create list store */
m_refStringList = Gtk::StringList::create({});
auto selection_model = Gtk::SingleSelection::create(m_refStringList);
m_ListView.set_model(selection_model);
/* Add some messages to the window */
for (int i = 1; i <= 10; ++i)
m_refStringList->append(Glib::ustring::format("message #", i));
// Create a ListItemFactory to use for populating list items.
auto factory = Gtk::SignalListItemFactory::create();
factory->signal_setup().connect(sigc::mem_fun(*this, &MessagesList::on_setup_message));
factory->signal_bind().connect(sigc::mem_fun(*this, &MessagesList::on_bind_message));
m_ListView.set_factory(factory);
}
MessagesList::~MessagesList()
{
}
void MessagesList::on_setup_message(const Glib::RefPtr<Gtk::ListItem>& list_item)
{
auto label = Gtk::make_managed<Gtk::Label>();
label->set_halign(Gtk::Align::START);
list_item->set_child(*label);
}
void MessagesList::on_bind_message(const Glib::RefPtr<Gtk::ListItem>& list_item)
{
auto pos = list_item->get_position();
if (pos == GTK_INVALID_LIST_POSITION)
return;
auto label = dynamic_cast<Gtk::Label*>(list_item->get_child());
if (!label)
return;
label->set_text(m_refStringList->get_string(pos));
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>messagetext.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "messagetext.h"
MessageText::MessageText()
{
set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
set_child(m_TextView);
insert_text();
}
MessageText::~MessageText()
{
}
void MessageText::insert_text()
{
auto refTextBuffer = m_TextView.get_buffer();
auto iter = refTextBuffer->get_iter_at_offset(0);
refTextBuffer->insert(iter,
"From: [email protected]\n"
"To: [email protected]\n"
"Subject: Made it!\n"
"\n"
"We just got in this morning. The weather has been\n"
"great - clear but cold, and there are lots of fun sights.\n"
"Sojourner says hi. See you soon.\n"
" -Path\n");
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</section>
<section xml:id="sec-scrolledwindow">
<title>ScrolledWindow</title>
<para xml:lang="en">
<classname>ScrolledWindow</classname> widgets create a scrollable
area. You can insert any type of widget into a
<classname>ScrolledWindow</classname>, and it will be accessible
regardless of its size by using the scrollbars. Note that
<classname>ScrolledWindow</classname> is not a
<classname>Gtk::Window</classname> despite the slightly misleading name.
</para>
<para xml:lang="en">
Scrolled windows have <emphasis>scrollbar policies</emphasis> which determine
whether the <classname>Scrollbar</classname>s will be displayed. The policies
can be set with the <methodname>set_policy()</methodname> method. The policy may be
for instance <literal>Gtk::PolicyType::AUTOMATIC</literal> or
<literal>Gtk::PolicyType::ALWAYS</literal>.
<literal>Gtk::PolicyType::AUTOMATIC</literal> will cause the scrolled window
to display the scrollbar only if the contained widget is larger than the
visible area. <literal>Gtk::PolicyType::ALWAYS</literal> will cause the
scrollbar to be displayed always.
</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1ScrolledWindow.html">Reference</link></para>
<section xml:id="scrolledwindow-example">
<title>Ejemplo</title>
<para>Aquí hay un ejemplo simple que incluye 100 botones conmutadores en una ventana ScrolledWindow. Intente redimensionar la ventana para ver reaccionar a las barras de desplazamiento.</para>
<figure xml:id="figure-scrolledwindow">
<title>ScrolledWindow</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/scrolledwindow.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/scrolledwindow">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
~ExampleWindow() override;
protected:
//Signal handlers:
void on_button_close();
//Child widgets:
Gtk::Box m_VBox;
Gtk::ScrolledWindow m_ScrolledWindow;
Gtk::Grid m_Grid;
Gtk::Button m_ButtonClose;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <iostream>
ExampleWindow::ExampleWindow()
: m_VBox(Gtk::Orientation::VERTICAL, 10),
m_ButtonClose("_Close", true)
{
set_title("Gtk::ScrolledWindow example");
set_size_request(300, 300);
set_child(m_VBox);
m_VBox.set_margin(10);
/* The policy is one of Gtk::PolicyType::AUTOMATIC, or Gtk::PolicyType::ALWAYS.
* Gtk::PolicyType::AUTOMATIC will automatically decide whether you need
* scrollbars, whereas Gtk::PolicyType::ALWAYS will always leave the scrollbars
* there. The first one is the horizontal scrollbar, the second, the vertical. */
m_ScrolledWindow.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::ALWAYS);
m_ScrolledWindow.set_expand();
m_VBox.append(m_ScrolledWindow);
/* set the spacing to 10 on x and 10 on y */
m_Grid.set_row_spacing(10);
m_Grid.set_column_spacing(10);
/* pack the grid into the scrolled window */
m_ScrolledWindow.set_child(m_Grid);
/* this simply creates a grid of toggle buttons
* to demonstrate the scrolled window. */
for (int i = 0; i < 10; i++)
{
for (int j = 0; j < 10; j++)
{
auto s = Glib::ustring::compose("button (%1,%2)", i, j);
auto pButton = Gtk::make_managed<Gtk::ToggleButton>(s);
m_Grid.attach(*pButton, i, j);
}
}
/* Add a "close" button to the bottom of the dialog */
m_VBox.append(m_ButtonClose);
m_ButtonClose.set_halign(Gtk::Align::END);
m_ButtonClose.signal_clicked().connect(
sigc::mem_fun(*this, &ExampleWindow::on_button_close));
/* Hitting the "Enter" key will cause this button to activate. */
m_ButtonClose.grab_focus();
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::on_button_close()
{
set_visible(false);
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
//Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</section>
<section xml:id="sec-aspectframe">
<title>AspectFrame</title>
<para>El widget <classname>AspectFrame</classname> se ve como un widget <classname>Frame</classname>, pero fuerza la <emphasis>relación de aspecto</emphasis> (la razón entre la altura y el ancho) del widget hijo, añadiendo espacio adicional de ser necesario. Por ejemplo, esto le permitiría mostrar una fotografía sin permitirle al usuario distorsionarla horizontal o verticalmente cuando la redimensione.</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1AspectFrame.html">Reference</link></para>
<section xml:id="aspectframe-example">
<title>Ejemplo</title>
<para>El siguiente programa usa un <classname>Gtk::AspectFrame</classname> para presentar un área de dibujo cuya relación de aspecto siempre es 2:1, sin importar cómo el usuario redimensiona la ventana superior.</para>
<figure xml:id="figure-aspectframe">
<title>AspectFrame</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/aspectframe.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/aspectframe">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
virtual ~ExampleWindow();
protected:
//Child widgets:
Gtk::AspectFrame m_AspectFrame;
Gtk::Frame m_Frame;
Gtk::DrawingArea m_DrawingArea;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
ExampleWindow::ExampleWindow()
: m_AspectFrame(
Gtk::Align::CENTER, /* center x */
Gtk::Align::CENTER, /* center y */
2.0, /* xsize/ysize = 2 */
false /* ignore child's aspect */),
m_Frame("2x1" /* label */)
{
set_title("Aspect Frame");
// Set a child widget to the aspect frame */
// Ask for a 200x200 window, but the AspectFrame will give us a 200x100
// window since we are forcing a 2x1 aspect ratio */
m_DrawingArea.set_content_width(200);
m_DrawingArea.set_content_height(200);
m_Frame.set_child(m_DrawingArea);
m_AspectFrame.set_child(m_Frame);
m_AspectFrame.set_margin(10);
// Add the aspect frame to our toplevel window:
set_child(m_AspectFrame);
}
ExampleWindow::~ExampleWindow()
{
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
//Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</section>
<section xml:id="sec-other-single-item-containers">
<title xml:lang="en">Other Single-item Containers</title>
<para xml:lang="en">
There are other single-item containers. See the reference documentation for a
complete list. Here are links to some example programs that show containers,
which are not mentioned elsewhere in this tutorial.
</para>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/expander">Source Code, Expander</link></para>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/popover">Source Code, Popover</link></para>
</section>
</section>
<section xml:id="sec-multi-item-containers">
<title xml:lang="en">Multiple-item Containers </title>
<para xml:lang="en">
Multiple-item container widgets have other methods than <methodname>set_child()</methodname>
and <methodname>unset_child()</methodname>. Different containers can have different
methods for adding and removing child widgets. For instance, <classname>Gtk::Box</classname>
has <methodname>append()</methodname> and <methodname>remove()</methodname> as
well as other methods. The <methodname>remove()</methodname> method for multiple-item
containers takes an argument, specifying which widget to remove.
</para>
<section xml:id="container-packing">
<title>Empaquetado</title>
<para>Probablemente ya haya notado que las ventanas de <application>gtkmm</application> parecen «elásticas»: normalmente pueden estirarse de muchas maneras diferentes. Esto es así por el sistema de <emphasis>empaquetado de widgets</emphasis>.</para>
<para>Muchos conjuntos de herramientas de la IGU le requieren ubicar precisamente widgets en una ventana, utilizando posicionamiento absoluto, a menudo usando un editor visual. Esto lleva a muchos problemas:</para>
<itemizedlist>
<listitem>
<para>Los widgets no se reordenar cuando la ventana se redimensiona. Algunos se esconden cuando las ventanas se hacen más pequeñas, y aparece un montón de espacio sin utilizar cuando la ventana se agranda.</para>
</listitem>
<listitem>
<para>Es imposible predecir la cantidad de espacio necesaria para texto después de que se ha traducido a otros idiomas, o se ha mostrado en otra tipografía. En Unix, también es imposible anticipar los efectos de cada tema y gestor de ventanas.</para>
</listitem>
<listitem>
<para>Cambiar la disposición de una ventana «al vuelo» para, por ejemplo, hacer que algunos widgets adicionales aparezcan, es complejo. Requiere un cálculo tedioso de la posición de cada widget.</para>
</listitem>
</itemizedlist>
<para xml:lang="en">
<application>gtkmm</application> uses the packing system to solve these problems. Rather than specifying the position and size of each widget in the window,
you can arrange your widgets in rows, columns,
and/or grids. <application>gtkmm</application> can size your window automatically, based on the
sizes of the widgets it contains. And the sizes of the widgets are, in turn, determined by the amount of text they contain, or the minimum and maximum sizes that you specify, and/or how you have requested that the available space should be shared between sets of widgets.
You can perfect your layout by
specifying margins and centering values for each of your widgets. <application>gtkmm</application> then uses
all this information to resize and reposition everything sensibly and smoothly when the user manipulates the window. </para>
<para xml:lang="en">
<application>gtkmm</application> arranges widgets hierarchically, using <emphasis>containers</emphasis>.
A container widget contains other widgets. Most <application>gtkmm</application> widgets are
containers. Windows, Notebook tabs, and Buttons are all container widgets.
There are two flavors of containers: single-child containers and multiple-child
containers. Most container widgets in <application>gtkmm</application> are single-child containers,
including <classname>Gtk::Window</classname>.
</para>
<para>Sí, es correcto: una ventana sólo puede contener un widget. Entonces, ¿cómo puede usar una ventana para algo útil? Poniendo un contenedor de múltiples hijos en la ventana. Los widgets contenedores más útiles son <classname>Gtk::Grid</classname> y <classname>Gtk::Box</classname>.</para>
<itemizedlist>
<listitem>
<para xml:lang="en">
<classname>Gtk::Grid</classname> arranges its child widgets in rows and
columns. Use <methodname>attach()</methodname> and
<methodname>attach_next_to()</methodname> to insert child widgets.
</para>
</listitem>
<listitem>
<para xml:lang="en">
<classname>Gtk::Box</classname> arranges its child widgets vertically or horizontally.
Use <methodname>append()</methodname> to insert child widgets.
</para>
</listitem>
</itemizedlist>
<para>Hay muchos más contenedores, de los que también se hablará.</para>
<para>Si nunca ha usado un kit de herramientas de empaquetado antes, puede llevar algo de tiempo acostumbrarse a él. Probablemente encuentre, sin embargo, que no necesita editores de formularios visuales tanto como con otros kits de herramientas.</para>
</section>
<section xml:id="sec-helloworld2">
<title>Un «Hola mundo» mejorado</title>
<para>Eche un vistazo a un <literal>helloworld</literal> ligeramente mejorado, mostrando lo que ha aprendido.</para>
<figure xml:id="figure-helloworld2">
<title>Hola mundo 2</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/helloworld2.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/helloworld2">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>helloworld.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLE_HELLOWORLD_H
#define GTKMM_EXAMPLE_HELLOWORLD_H
#include <gtkmm/box.h>
#include <gtkmm/button.h>
#include <gtkmm/window.h>
class HelloWorld : public Gtk::Window
{
public:
HelloWorld();
~HelloWorld() override;
protected:
// Signal handlers:
// Our new improved on_button_clicked().
void on_button_clicked(const Glib::ustring& data);
// Child widgets:
Gtk::Box m_box1;
Gtk::Button m_button1, m_button2;
};
#endif // GTKMM_EXAMPLE_HELLOWORLD_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>helloworld.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "helloworld.h"
#include <iostream>
HelloWorld::HelloWorld()
: m_button1("Button 1"),
m_button2("Button 2")
{
// This just sets the title of our new window.
set_title("Hello Buttons!");
// Sets the margin around the box.
m_box1.set_margin(10);
// put the box into the main window.
set_child(m_box1);
// Now when the button is clicked, we call the on_button_clicked() function
// with a pointer to "button 1" as its argument.
m_button1.signal_clicked().connect(sigc::bind(
sigc::mem_fun(*this, &HelloWorld::on_button_clicked), "button 1"));
// We use Gtk::Box::append() to pack this button into the box,
// which has been packed into the window.
// A widget's default behaviour is not to expand if extra space is available.
// A container widget by default expands if any of the contained widgets
// wants to expand.
m_box1.append(m_button1);
m_button1.set_expand();
// call the same signal handler with a different argument,
// passing a pointer to "button 2" instead.
m_button2.signal_clicked().connect(sigc::bind(
sigc::mem_fun(*this, &HelloWorld::on_button_clicked), "button 2"));
m_box1.append(m_button2);
m_button2.set_expand();
// Gtk::Widget::set_visible(true) is seldom needed. All widgets are visible by default.
}
HelloWorld::~HelloWorld()
{
}
// Our new improved signal handler. The data passed to this method is
// printed to stdout.
void HelloWorld::on_button_clicked(const Glib::ustring& data)
{
std::cout << "Hello World - " << data << " was pressed" << std::endl;
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "helloworld.h"
#include <gtkmm/application.h>
int main(int argc, char* argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
//Shows the window and returns when it is closed.
return app->make_window_and_run<HelloWorld>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
<para xml:lang="en">
After building and running this program, try resizing the window to see the
behavior. Also, try playing with <methodname>set_expand()</methodname>,
<methodname>set_hexpand()</methodname>, <methodname>set_vexpand()</methodname>,
<methodname>set_halign()</methodname> and <methodname>set_valign()</methodname>
while reading the <link linkend="sec-boxes">Boxes</link> section.
</para>
</section>
<section xml:id="sec-boxes">
<title>Cajas</title>
<para xml:lang="en">
Most packing uses boxes as in the above example. These
are invisible containers into which we can pack our widgets. When
packing widgets into a horizontal box, the objects are inserted
horizontally from left to right. In a vertical box, widgets are packed from
top to bottom. You may use any combination of boxes inside or beside other
boxes to create the desired effect.
</para>
<section xml:id="boxes-adding-widgets">
<title>Añadir widgets</title>
<section xml:id="per-child-packing-options">
<title>Opciones de empaquetado por hijo</title>
<para xml:lang="en">
The <methodname>append()</methodname> method places widgets inside these
containers. It will start at the top and work its way down in a
<classname>Box</classname> with vertical orientation, or pack left to right in
a <classname>Box</classname> with horizontal orientation. If it's inconvenient
to add widgets in this order, use <methodname>insert_child_after()</methodname>
or <methodname>insert_child_at_start()</methodname>. We will use
<methodname>append()</methodname> in our examples.
</para>
<para xml:lang="en">
There are several options governing how widgets are to be packed, and this can
be confusing at first. You can modify the packing by using <methodname>set_expand()</methodname>,
<methodname>set_hexpand()</methodname>, <methodname>set_vexpand()</methodname>,
<methodname>set_halign()</methodname>, <methodname>set_valign()</methodname>
and/or <methodname>set_margin()</methodname> on the child widgets. If you have
difficulties, then it is sometimes a good idea to play with the
<application>Cambalache</application> GUI designer to see what is possible.
You can then use the <classname>Gtk::Builder</classname> API to load your GUI at runtime.
</para>
<para>Básicamente, hay cinco estilos diferentes, como se muestra en esta imagen.</para>
<figure xml:id="figure-box-packing1">
<title>Caja de empaquetado 1</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/box_packing1.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en">
Each line contains one horizontal <classname>Box</classname> with
several buttons. Each of the buttons on a line is packed into the
<classname>Box</classname> with the same arguments to the
<methodname>set_hexpand()</methodname>, <methodname>set_halign()</methodname>,
<methodname>set_margin_start()</methodname> and <methodname>set_margin_end()</methodname>
methods.
</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1Box.html">Reference</link></para>
</section>
<section xml:id="per-container-packing-options">
<title>Opciones de empaquetado por contenedor</title>
<para xml:lang="en">
Here's the constructor for the <classname>Box</classname> widget,
and methods that set per-container packing options:</para>
<programlisting xml:lang="en"><code>Gtk::Box(Gtk::Orientation orientation = Gtk::Orientation::HORIZONTAL, int spacing = 0);
void set_orientation(Gtk::Orientation orientation);
void set_spacing(int spacing);
void set_homogeneous(bool homogeneous = true);</code></programlisting>
<para xml:lang="en">Passing <literal>true</literal> to <methodname>set_homogeneous()</methodname> will
cause all of the contained widgets to be the same size.
<parameter>spacing</parameter> is a (minimum) number of pixels to leave between
each widget.
</para>
<para xml:lang="en">
What's the difference between spacing (set when the box is created)
and margins (set separately for each child widget)? Spacing is added between
objects, and margins are added on one or more sides of a widget. The following
figure should make it clearer. The shown margins are the left and right margins
of each button in the row.
</para>
<figure xml:id="figure-box-packing2">
<title>Caja de empaquetado 2</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/box_packing2.png"/></imageobject></mediaobject>
</screenshot>
</figure>
</section>
</section>
<section xml:id="boxes-command-line-options">
<title>Gtk::Application y opciones de línea de comandos</title>
<para>El siguiente programa de ejemplo requiere una opción de línea de comandos. El código fuente muestra dos maneras de manejarla, en combinación con <classname>Gtk::Application</classname>.</para>
<itemizedlist>
<listitem><para xml:lang="en">
Handle the options in <function>main()</function> and hide them from
<classname>Gtk::Application</classname> by setting <literal>argc = 1</literal>
in the call to <methodname>Gtk::Application::run()</methodname>.
</para></listitem>
<listitem><para xml:lang="en">
Give all command-line options to <methodname>Gtk::Application::run()</methodname>
and add the flag <literal>Gio::Application::Flags::HANDLES_COMMAND_LINE</literal>
to <methodname>Gtk::Application::create()</methodname>.
Connect a signal handler to the <literal>command_line</literal> signal, and
handle the command-line options in the signal handler.</para>
<para>Debe establecer el parámetro opcional <literal>after = false</literal> en la llamada a <literal>signal_command_line().connect()</literal>, porque su gestor de señales debe llamarse antes que el gestor predeterminado. También debe llamar a <methodname>Gio::Application::activate()</methodname> en este, a menos que quiere que su aplicación se cierre sin mostrar su ventana principal. (<classname>Gio::Application</classname> es una clase base de <classname>Gtk::Application</classname>).</para></listitem>
</itemizedlist>
</section>
<section xml:id="box-packing-example">
<title>Ejemplo</title>
<para>Aquí está el código fuente del ejemplo que produjo las capturas de pantalla anteriores. Cuando ejecute este ejemplo, proporcione un número entre 1 y 3 como opción de línea de comandos, para ver las diferentes opciones de empaquetado en acción.</para>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/box">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow(int which);
virtual ~ExampleWindow();
protected:
//Signal handlers:
void on_button_quit_clicked();
//Child widgets:
Gtk::Button m_button;
Gtk::Box m_box1;
Gtk::Box m_boxQuit;
Gtk::Button m_buttonQuit;
Gtk::Label m_Label1, m_Label2;
Gtk::Separator m_separator1, m_separator2;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>packbox.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLE_PACKBOX_H
#define GTKMM_EXAMPLE_PACKBOX_H
#include <gtkmm.h>
class PackBox : public Gtk::Box
{
public:
PackBox(bool homogeneous = false, int spacing = 0, bool expand = false,
Gtk::Align align = Gtk::Align::FILL, int margin = 0);
protected:
Gtk::Button m_buttons[4];
};
#endif //GTKMM_EXAMPLE_PACKBOX_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include <iostream>
#include "examplewindow.h"
#include "packbox.h"
ExampleWindow::ExampleWindow(int which)
: m_box1(Gtk::Orientation::VERTICAL),
m_buttonQuit("Quit")
{
set_title("Gtk::Box example");
m_separator1.set_margin_top(5);
m_separator1.set_margin_bottom(5);
m_separator2.set_margin_top(5);
m_separator2.set_margin_bottom(5);
switch(which)
{
case 1:
{
m_Label1.set_text("Gtk::Box(Gtk::Orientation::HORIZONTAL, 0); set_homogeneous(false);");
// Align the label to the left side.
m_Label1.set_halign(Gtk::Align::START);
m_Label1.set_valign(Gtk::Align::START);
// Pack the label into the vertical box (vbox box1). Remember that
// widgets added to a vbox will be packed one on top of the other in
// order.
m_box1.append(m_Label1);
// Create a PackBox - homogeneous = false, spacing = 0,
// expand = false, Gtk::Align::FILL, margin = 0
// These are the default values.
auto pPackBox = Gtk::make_managed<PackBox>();
m_box1.append(*pPackBox);
// Create a PackBox - homogeneous = false, spacing = 0,
// expand = true, Gtk::Align::CENTER, margin = 0
pPackBox = Gtk::make_managed<PackBox>(false, 0, true, Gtk::Align::CENTER);
m_box1.append(*pPackBox);
// Create a PackBox - homogeneous = false, spacing = 0,
// expand = true, Gtk::Align::FILL, margin = 0
pPackBox = Gtk::make_managed<PackBox>(false, 0, true);
m_box1.append(*pPackBox);
// pack the separator into the vbox. Remember each of these
// widgets are being packed into a vbox, so they'll be stacked
// vertically.
m_box1.append(m_separator1);
// create another new label, and show it.
m_Label2.set_text("Gtk::Box(Gtk::Orientation::HORIZONTAL, 0); set_homogeneous(true);");
m_Label2.set_halign(Gtk::Align::START);
m_Label2.set_valign(Gtk::Align::START);
m_box1.append(m_Label2);
// Args are: homogeneous, spacing, expand, align, margin
pPackBox = Gtk::make_managed<PackBox>(true, 0, true, Gtk::Align::CENTER);
m_box1.append(*pPackBox);
// Args are: homogeneous, spacing, expand, align, margin
pPackBox = Gtk::make_managed<PackBox>(true, 0, true);
m_box1.append(*pPackBox);
m_box1.append(m_separator2);
break;
}
case 2:
{
m_Label1.set_text("Gtk::Box(Gtk::Orientation::HORIZONTAL, 10); set_homogeneous(false);");
m_Label1.set_halign(Gtk::Align::START);
m_Label1.set_valign(Gtk::Align::START);
m_box1.append(m_Label1);
auto pPackBox = Gtk::make_managed<PackBox>(false, 10, true, Gtk::Align::CENTER);
m_box1.append(*pPackBox);
pPackBox = Gtk::make_managed<PackBox>(false, 10, true);
m_box1.append(*pPackBox);
m_box1.append(m_separator1);
m_Label2.set_text("Gtk::Box(Gtk::Orientation::HORIZONTAL, 0); set_homogeneous(false);");
m_Label2.set_halign(Gtk::Align::START);
m_Label2.set_valign(Gtk::Align::START);
m_box1.append(m_Label2);
pPackBox = Gtk::make_managed<PackBox>(false, 0, false, Gtk::Align::FILL, 10);
m_box1.append(*pPackBox);
pPackBox = Gtk::make_managed<PackBox>(false, 0, true, Gtk::Align::FILL, 10);
m_box1.append(*pPackBox);
m_box1.append(m_separator2);
break;
}
case 3:
{
// This demonstrates the ability to use Gtk::Align::END to
// right justify widgets. First, we create a new box as before.
auto pPackBox = Gtk::make_managed<PackBox>();
// create the label that will be put at the end.
m_Label1.set_text("end");
// pack it using Gtk::Align::END, so it is put on the right side
// of the PackBox.
m_Label1.set_halign(Gtk::Align::END);
m_Label1.set_hexpand(true);
pPackBox->append(m_Label1);
m_box1.append(*pPackBox);
// This explicitly sets the separator to 700 pixels wide by 5 pixels
// high. This is so the hbox we created will also be 700 pixels wide,
// and the "end" label will be separated from the other labels in the
// hbox. Otherwise, all the widgets in the hbox would be packed as
// close together as possible.
m_separator1.set_size_request(700, 5);
// pack the separator into the vbox.
m_box1.append(m_separator1);
break;
}
default:
{
std::cerr << "Unexpected command-line option." << std::endl;
break;
}
}
// Connect the signal to hide the window:
m_buttonQuit.signal_clicked().connect( sigc::mem_fun(*this,
&ExampleWindow::on_button_quit_clicked) );
// pack the button into the quitbox.
m_boxQuit.append(m_buttonQuit);
m_buttonQuit.set_hexpand(true);
m_buttonQuit.set_halign(Gtk::Align::CENTER);
m_box1.append(m_boxQuit);
// pack the vbox (box1) which now contains all our widgets, into the
// main window.
set_child(m_box1);
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::on_button_quit_clicked()
{
set_visible(false);
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
#include <iostream>
#include <cstdlib>
#define GTK_APPLICATION_RECEIVES_COMMAND_LINE_ARGUMENTS 0
#if GTK_APPLICATION_RECEIVES_COMMAND_LINE_ARGUMENTS
namespace
{
int on_command_line(const Glib::RefPtr<Gio::ApplicationCommandLine>& command_line,
Glib::RefPtr<Gtk::Application>& app)
{
int argc = 0;
char** argv = command_line->get_arguments(argc);
for (int i = 0; i < argc; ++i)
std::cout << "argv[" << i << "] = " << argv[i] << std::endl;
app->activate(); // Without activate() the window won't be shown.
return EXIT_SUCCESS;
}
} // anonymous namespace
#endif
int main(int argc, char *argv[])
{
if (argc != 2)
{
std::cerr << "Usage: example <num>, where <num> is 1, 2, or 3." << std::endl;
return EXIT_FAILURE;
}
int argc1 = argc;
#if GTK_APPLICATION_RECEIVES_COMMAND_LINE_ARGUMENTS
// The Gio::Application::Flags::HANDLES_COMMAND_LINE flag and the
// on_command_line() signal handler are not necessary. This program is simpler
// without them, and with argc = 1 in the call to Gtk::Application::make_window_and_run().
// They are included to show a program with Gio::Application::Flags::HANDLES_COMMAND_LINE.
// Gio::Application::Flags::NON_UNIQUE makes it possible to run several instances of
// this application simultaneously.
auto app = Gtk::Application::create(
"org.gtkmm.example", Gio::Application::Flags::HANDLES_COMMAND_LINE | Gio::Application::Flags::NON_UNIQUE);
// Note after = false.
// Only one signal handler is invoked. This signal handler must run before
// the default signal handler, or else it won't run at all.
app->signal_command_line().connect(
[&app](const auto& command_line)
{ return on_command_line(command_line, app); }, false);
#else
// Gio::Application::Flags::NON_UNIQUE makes it possible to run several instances of
// this application simultaneously.
argc1 = 1; // Don't give the command line arguments to Gtk::Application.
auto app = Gtk::Application::create("org.gtkmm.example", Gio::Application::Flags::NON_UNIQUE);
#endif
// Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc1, argv, std::atoi(argv[1]));
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>packbox.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "packbox.h"
#include <map>
namespace
{
const std::map<Gtk::Align, Glib::ustring> align_string = {
{Gtk::Align::FILL, "Gtk::Align::FILL"},
{Gtk::Align::START, "Gtk::Align::START"},
{Gtk::Align::END, "Gtk::Align::END"},
{Gtk::Align::CENTER, "Gtk::Align::CENTER"},
{Gtk::Align::BASELINE, "Gtk::Align::BASELINE"},
};
}
PackBox::PackBox(bool homogeneous, int spacing, bool expand, Gtk::Align align, int margin)
: Gtk::Box(Gtk::Orientation::HORIZONTAL, spacing)
{
set_homogeneous(homogeneous);
m_buttons[0].set_label("box.append(button);");
m_buttons[1].set_label("expand=" + Glib::ustring(expand ? "true" : "false"));
m_buttons[2].set_label(align_string.at(align));
m_buttons[3].set_label("margin=" + Glib::ustring::format(margin));
for (auto& button : m_buttons)
{
append(button);
button.set_hexpand(expand);
button.set_halign(align);
button.set_margin_start(margin);
button.set_margin_end(margin);
}
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</section>
<section xml:id="sec-grid">
<title>Grid</title>
<para>Un <classname>Grid</classname> establece dinámicamente widgets hijos en filas y columnas. No es necesario especificar las dimensiones de la red en el constructor.</para>
<para>Los widgets hijos pueden abarcar múltiples filas o columnas, usando <methodname>attach()</methodname>, o añadirse a un widget existente dentro de la cuadrícula con <methodname>attach_next_to()</methodname>. Se puede establecer que las filas o columnas individuales de la cuadrícula tengan una altura o ancho uniforme con <methodname>set_row_homogeneous()</methodname> y <methodname>set_column_homogeneous()</methodname>.</para>
<para xml:lang="en">You can set the <emphasis>margin</emphasis> and <emphasis>expand</emphasis> properties of the
child <classname>Widget</classname>s to control their spacing and their behavior when the Grid is resized.
</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1Grid.html">Reference</link></para>
<section xml:id="grid-example">
<title>Ejemplo</title>
<para>Este ejemplo crea una ventana con tres botones en una cuadrícula. Los dos primeros botones están en la fila superior, de izquierda a derecha. Se ha añadido un tercer botón bajo el primer botón, en una nueva fila más abajo, abarcando dos columnas.</para>
<figure xml:id="figure-grid">
<title>Grid</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/grid.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/grid">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
virtual ~ExampleWindow();
private:
// Signal handlers:
void on_button_quit();
void on_button_numbered(const Glib::ustring& data);
// Child widgets:
Gtk::Grid m_grid;
Gtk::Button m_button_1, m_button_2, m_button_quit;
};
#endif /* GTKMM_EXAMPLEWINDOW_H */
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include <iostream>
#include "examplewindow.h"
ExampleWindow::ExampleWindow()
: m_button_1("button 1"),
m_button_2("button 2"),
m_button_quit("Quit")
{
set_title("Gtk::Grid");
m_grid.set_margin(12);
set_child(m_grid);
m_grid.attach(m_button_1, 0, 0);
m_grid.attach(m_button_2, 1, 0);
m_grid.attach_next_to(m_button_quit, m_button_1, Gtk::PositionType::BOTTOM, 2, 1);
m_button_1.signal_clicked().connect(
sigc::bind( sigc::mem_fun(*this, &ExampleWindow::on_button_numbered), "button 1") );
m_button_2.signal_clicked().connect(
sigc::bind( sigc::mem_fun(*this, &ExampleWindow::on_button_numbered), "button 2") );
m_button_quit.signal_clicked().connect(sigc::mem_fun(*this,
&ExampleWindow::on_button_quit) );
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::on_button_quit()
{
set_visible(false);
}
void
ExampleWindow::on_button_numbered(const Glib::ustring& data)
{
std::cout << data << " was pressed" << std::endl;
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
// Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</section>
<section xml:id="sec-notebook">
<title>Cuaderno</title>
<para>Un <classname>Notebook</classname> tiene un conjunto de <literal>páginas</literal> apiladas, cada una de ellas contiene widgets. Las <literal>pestañas</literal> etiquetadas permiten al usuario seleccionar las páginas. Los <classname>Notebook</classname> permiten colocar varios conjuntos de widgets en un espacio reducido, mostrando sólo una página a la vez. Por ejemplo, se utilizan a menudo en los diálogos de preferencias.</para>
<para>Use los métodos <methodname>append_page()</methodname>, <methodname>prepend_page()</methodname> e <methodname>insert_page()</methodname> para añadir páginas con pestañas al <literal>Notebook</literal>, proporcionándoles el widget hijo y el nombre de la pestaña.</para>
<para>Para descubrir la página visible actual, use el método <methodname>get_current_page()</methodname>. Esto devuelve el número de página. Después llame a <methodname>get_nth_page()</methodname> con ese número le dará un puntero al widget hijo en sí.</para>
<para>Para cambiar la página seleccionada mediante programación, use el método <methodname>set_current_page()</methodname>.</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1Notebook.html">Reference</link></para>
<section xml:id="notebook-example">
<title>Ejemplo</title>
<figure xml:id="figure-notebook">
<title>Cuaderno</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/notebook.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/notebook/">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
virtual ~ExampleWindow();
protected:
//Signal handlers:
void on_button_quit();
void on_notebook_switch_page(Gtk::Widget* page, guint page_num);
//Child widgets:
Gtk::Box m_VBox;
Gtk::Notebook m_Notebook;
Gtk::Label m_Label1, m_Label2;
Gtk::Box m_ButtonBox;
Gtk::Button m_Button_Quit;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include <iostream>
#include "examplewindow.h"
ExampleWindow::ExampleWindow()
: m_VBox(Gtk::Orientation::VERTICAL),
m_Label1("Contents of tab 1"),
m_Label2("Contents of tab 2"),
m_Button_Quit("Quit")
{
set_title("Gtk::Notebook example");
set_default_size(400, 200);
m_VBox.set_margin(10);
set_child(m_VBox);
//Add the Notebook, with the button underneath:
m_Notebook.set_margin(10);
m_Notebook.set_expand();
m_VBox.append(m_Notebook);
m_VBox.append(m_ButtonBox);
m_ButtonBox.append(m_Button_Quit);
m_Button_Quit.set_hexpand(true);
m_Button_Quit.set_halign(Gtk::Align::CENTER);
m_Button_Quit.signal_clicked().connect(sigc::mem_fun(*this,
&ExampleWindow::on_button_quit) );
//Add the Notebook pages:
m_Notebook.append_page(m_Label1, "First");
m_Notebook.append_page(m_Label2, "Second");
m_Notebook.signal_switch_page().connect(sigc::mem_fun(*this,
&ExampleWindow::on_notebook_switch_page) );
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::on_button_quit()
{
set_visible(false);
}
void ExampleWindow::on_notebook_switch_page(Gtk::Widget* /* page */, guint page_num)
{
std::cout << "Switched to tab with index " << page_num << std::endl;
//You can also use m_Notebook.get_current_page() to get this index.
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
//Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</section>
<section xml:id="sec-assistant">
<title>Asistente</title>
<note><para xml:lang="en"><classname>Gtk::Assistant</classname> is deprecated since <application>gtkmm</application> 4.10.
There is no replacement in <application>gtkmm</application>. libadwaita (a C library) has replacement parts
(like AdwCarousel). In some cases, a <classname>Gtk::Notebook</classname> might
be an acceptable replacement.
</para></note>
<para>Un <classname>Assistant</classname> divide una operación compleja en pasos. Cada paso es una página, conteniendo una cabecera, un widget hijo, y un área de acción. El área de acción del asistente tiene botones de navegación que se actualizan automáticamente dependiendo del tipo de la página, establecido con <methodname>set_page_type()</methodname>.</para>
<para>Use los métodos <methodname>append_page()</methodname>, <methodname>prepend_page</methodname> e <methodname>insert_page()</methodname> para añadirle páginas al <classname>Assistant</classname>, proporcionándole el widget hijo por cada página.</para>
<para>Para determinar la página actualmente visible, use el método <methodname>get_current_page()</methodname> y pásele el resultado a <methodname>get_nth_page()</methodname>, que le devuelve un puntero al widget en sí. Para cambiar mediante programación la página actual, use el método <methodname>set_current_page()</methodname>.</para>
<para xml:lang="en">
To set the title of a page, use the <methodname>set_page_title()</methodname> method.
</para>
<para>Para añadir widgets al área de acción, use el método <methodname>add_action_widget()</methodname>. Serán empaquetados junto a los botones predeterminados. Use el método <methodname>remove_action_widget()</methodname> para borrar los widgets.</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1Assistant.html">Reference</link></para>
<section xml:id="assistant-example">
<title>Ejemplo</title>
<figure xml:id="figure-assistant">
<title>Asistente</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/assistant.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/assistant/">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>exampleassistant.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEASSISTANT_H
#define GTKMM_EXAMPLEASSISTANT_H
#include <gtkmm.h>
class ExampleAssistant : public Gtk::Assistant
{
public:
ExampleAssistant();
virtual ~ExampleAssistant();
void get_result(bool& check_state, Glib::ustring& entry_text);
private:
// Signal handlers:
void on_assistant_apply();
void on_assistant_cancel();
void on_assistant_close();
void on_assistant_prepare(Gtk::Widget* widget);
void on_entry_changed();
// Member functions:
void print_status();
// Child widgets:
Gtk::Box m_box;
Gtk::Label m_label1, m_label2;
Gtk::CheckButton m_check;
Gtk::Entry m_entry;
};
#endif /* GTKMM_EXAMPLEASSISTANT_H */
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include "exampleassistant.h"
#include <gtkmm.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
virtual ~ExampleWindow();
private:
// Signal handlers:
void on_button_clicked();
void on_assistant_apply();
// Child widgets:
Gtk::Grid m_grid;
Gtk::Button m_button;
Gtk::Label m_label1, m_label2;
Gtk::CheckButton m_check;
Gtk::Entry m_entry;
ExampleAssistant m_assistant;
};
#endif /* GTKMM_EXAMPLEWINDOW_H */
]]></code></programlisting>
<para xml:lang="en">File: <filename>exampleassistant.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include <iostream>
#include "exampleassistant.h"
ExampleAssistant::ExampleAssistant()
: m_box(Gtk::Orientation::HORIZONTAL, 12),
m_label1("Type text to allow the assistant to continue:"),
m_label2("Confirmation page"),
m_check("Optional extra information")
{
set_title("Gtk::Assistant example");
set_default_size(400, 200);
m_box.append(m_label1);
m_box.append(m_entry);
m_label1.set_wrap();
m_label1.set_valign(Gtk::Align::CENTER);
m_entry.set_valign(Gtk::Align::CENTER);
append_page(m_box);
append_page(m_check);
append_page(m_label2);
set_page_title(*get_nth_page(0), "Page 1");
set_page_title(*get_nth_page(1), "Page 2");
set_page_title(*get_nth_page(2), "Confirmation");
set_page_complete(m_check, true);
set_page_complete(m_label2, true);
set_page_type(m_box, Gtk::AssistantPage::Type::INTRO);
set_page_type(m_label2, Gtk::AssistantPage::Type::CONFIRM);
signal_apply().connect(sigc::mem_fun(*this,
&ExampleAssistant::on_assistant_apply));
signal_cancel().connect(sigc::mem_fun(*this,
&ExampleAssistant::on_assistant_cancel));
signal_close().connect(sigc::mem_fun(*this,
&ExampleAssistant::on_assistant_close));
signal_prepare().connect(sigc::mem_fun(*this,
&ExampleAssistant::on_assistant_prepare));
m_entry.signal_changed().connect(sigc::mem_fun(*this,
&ExampleAssistant::on_entry_changed));
}
ExampleAssistant::~ExampleAssistant()
{
}
void ExampleAssistant::get_result(bool& check_state, Glib::ustring& entry_text)
{
check_state = m_check.get_active();
entry_text = m_entry.get_text();
}
void ExampleAssistant::on_assistant_apply()
{
std::cout << "Apply was clicked";
print_status();
}
void ExampleAssistant::on_assistant_cancel()
{
std::cout << "Cancel was clicked";
print_status();
set_visible(false);
}
void ExampleAssistant::on_assistant_close()
{
std::cout << "Assistant was closed";
print_status();
set_visible(false);
}
void ExampleAssistant::on_assistant_prepare(Gtk::Widget* /* widget */)
{
set_title(Glib::ustring::compose("Gtk::Assistant example (Page %1 of %2)",
get_current_page() + 1, get_n_pages()));
}
void ExampleAssistant::on_entry_changed()
{
// The page is only complete if the entry contains text.
if(m_entry.get_text_length())
set_page_complete(m_box, true);
else
set_page_complete(m_box, false);
}
void ExampleAssistant::print_status()
{
std::cout << ", entry contents: \"" << m_entry.get_text()
<< "\", checkbutton status: " << m_check.get_active() << std::endl;
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include "exampleassistant.h"
ExampleWindow::ExampleWindow()
: m_button("Show the assistant"),
m_label1("State of assistant checkbutton:", Gtk::Align::START, Gtk::Align::CENTER),
m_label2("Contents of assistant entry:", Gtk::Align::START, Gtk::Align::CENTER)
{
set_title("Gtk::Assistant example");
m_grid.set_row_homogeneous(true);
m_grid.set_column_spacing(5);
m_grid.set_margin(12);
m_grid.attach(m_button, 0, 0, 2, 1);
m_button.set_hexpand(true);
m_button.set_valign(Gtk::Align::CENTER);
m_grid.attach(m_label1, 0, 1, 1, 1);
m_grid.attach(m_label2, 0, 2, 1, 1);
m_grid.attach(m_check, 1, 1, 1, 1);
m_check.set_halign(Gtk::Align::START);
m_grid.attach(m_entry, 1, 2, 1, 1);
m_entry.set_hexpand(true);
set_child(m_grid);
m_button.signal_clicked().connect(sigc::mem_fun(*this,
&ExampleWindow::on_button_clicked));
m_assistant.signal_apply().connect(sigc::mem_fun(*this,
&ExampleWindow::on_assistant_apply));
m_check.set_sensitive(false);
m_entry.set_sensitive(false);
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::on_assistant_apply()
{
bool check_state;
Glib::ustring entry_text;
m_assistant.get_result(check_state, entry_text);
m_check.set_active(check_state);
m_entry.set_text(entry_text);
}
void ExampleWindow::on_button_clicked()
{
m_assistant.set_visible(true);
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
// Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</section>
<section xml:id="sec-other-multi-item-containers">
<title xml:lang="en">Other Multi-item Containers</title>
<para xml:lang="en">
There are other multi-item containers. See the reference documentation for a
complete list. Here are links to some example programs that show containers,
which are not mentioned elsewhere in this tutorial.
</para>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/actionbar">Source Code, ActionBar</link></para>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/listbox_flowbox/flowbox">Source Code, FlowBox</link></para>
</section>
</section>
</chapter>
<chapter xml:id="chapter-listmodel">
<title xml:lang="en">ListView, GridView, ColumnView</title>
<para xml:lang="en">
Lists are intended to be used whenever developers want to display many objects
in roughly the same way. They are perfectly fine to be used for very short lists
of only 2 or 3 items, but generally scale fine to thousands of items.
</para>
<para xml:lang="en">
Lists are meant to be used with changing data, both with the items themselves
changing as well as the list adding and removing items. Of course, they work just
as well with static data.
</para>
<para xml:lang="en">
The <link xlink:href="https://docs.gtk.org/gtk4/section-list-widget.html">List Widget Overview</link>
chapter in the GTK documentation contains more information about list widgets.
</para>
<para xml:lang="en">
Some examples are shown in this chapter. There are more examples in the
<link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/listmodelviews/">listmodelviews directory</link>
in <application>gtkmm-documentation</application>'s examples.</para>
<section xml:id="sec-listmodel-datamodel">
<title xml:lang="en">The Data Model</title>
<para xml:lang="en">
The data model is a class that implements the <classname>Gio::ListModel</classname>
interface. Examples of such classes are <classname>Gio::ListStore</classname>
(not to be confused with the deprecated <classname>Gtk::ListStore</classname>),
<classname>Gtk:StringList</classname>, <classname>Gtk:DirectoryList</classname>
and <classname>Pango::FontMap</classname>.
</para>
<para xml:lang="en">
The elements in a model are called <emphasis>items</emphasis>.
All items are instances of a subclass of <classname>Glib::Object</classname>.
For instance, you might have a <classname>ColumnView</classname> with one integer
and one text column, like so:
</para>
<programlisting xml:lang="en"><code><![CDATA[class ModelColumns : public Glib::Object
{
public:
int m_col_id;
Glib::ustring m_col_name;
static Glib::RefPtr<ModelColumns> create(
int col_id, const Glib::ustring& col_name)
{
return Glib::make_refptr_for_instance<ModelColumns>(
new ModelColumns(col_id, col_name));
}
protected:
ModelColumns(int col_id, const Glib::ustring& col_name)
: m_col_id(col_id), m_col_name(col_name)
{}
};
Glib::RefPtr<Gio::ListStore<ModelColumns>> m_ListStore;
]]></code></programlisting>
<para xml:lang="en">
Every item in a model has a position which is the unsigned integer that describes
where in the model the item is located. The first item in a model is at position 0.
The position of an item can of course change as other items are added to or removed
from the model.
</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/glibmm/classGio_1_1ListStore.html">Gio::ListStore Reference</link></para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1StringList.html">StringList Reference</link></para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1DirectoryList.html">DirectoryList Reference</link></para>
</section>
<section xml:id="sec-listmodel-selectionmodel">
<title xml:lang="en">The Selection Model</title>
<para xml:lang="en">
The selection model is a class that implements the <classname>Gtk::SelectionModel</classname>
interface. You can choose between <classname>NoSelection</classname>,
<classname>SingleSelection</classname> and <classname>MultiSelection</classname>.
</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1NoSelection.html">NoSelection Reference</link></para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1SingleSelection.html">SingleSelection Reference</link></para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1MultiSelection.html">MultiSelection Reference</link></para>
</section>
<section xml:id="sec-listmodel-factory">
<title xml:lang="en">The Factory</title>
<para xml:lang="en">
Data from the data model is added to the view by a factory, which is a subclass of
<classname>ListItemFactory</classname>. There is only one such subclass in <application>gtkmm</application>,
<classname>SignalListItemFactory</classname>. Data from the model is added to the
view with signal handlers connected to a <classname>SignalListItemFactory</classname>.
</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1SignalListItemFactory.html">SignalListItemFactory Reference</link></para>
</section>
<section xml:id="sec-listmodel-view">
<title>La vista</title>
<para xml:lang="en">
The View is the widget that displays the model data and allows the user to interact
with it. The View can show all of the model's columns, or just some, and it can show
them in various ways.
</para>
<para xml:lang="en">
An important requirement for views (especially views of long lists) is that they need
to know which items are not visible so they can be recycled. Views achieve that by
implementing the <classname>Scrollable</classname> interface and expecting
to be placed directly into a <classname>ScrolledWindow</classname>.
</para>
<para xml:lang="en">
There are different view widgets to choose from.
</para>
<section xml:id="sec-listmodel-listview">
<title xml:lang="en">ListView</title>
<para xml:lang="en">
The <classname>ListView</classname> shows a 1-dimensional list with one column.
</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1ListView.html">Reference</link></para>
<section xml:id="listmodel-listview-example">
<title>Ejemplo</title>
<figure xml:id="figure-listmodel-listview">
<title xml:lang="en">ListView</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/listmodel_listview.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/listmodelviews/list_listview">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
~ExampleWindow() override;
protected:
// Signal handlers:
void on_button_quit();
void on_setup_label(const Glib::RefPtr<Gtk::ListItem>& list_item);
void on_bind_name(const Glib::RefPtr<Gtk::ListItem>& list_item);
// Child widgets:
Gtk::Box m_VBox;
Gtk::Label m_Heading;
Gtk::ScrolledWindow m_ScrolledWindow;
Gtk::ListView m_ListView;
Gtk::Box m_ButtonBox;
Gtk::Button m_Button_Quit;
Glib::RefPtr<Gtk::StringList> m_StringList;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
ExampleWindow::ExampleWindow()
: m_VBox(Gtk::Orientation::VERTICAL),
m_Heading("<b>Name</b>", Gtk::Align::START),
m_Button_Quit("Quit")
{
set_title("Gtk::ListView (Gtk::StringList) example");
set_default_size(300, 200);
m_VBox.set_margin(5);
set_child(m_VBox);
// Add the ListView, inside a ScrolledWindow, with the heading above
// and the button underneath.
m_Heading.set_use_markup();
m_VBox.append(m_Heading);
m_ScrolledWindow.set_child(m_ListView);
// Only show the scrollbars when they are necessary:
m_ScrolledWindow.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
m_ScrolledWindow.set_expand();
m_VBox.append(m_ScrolledWindow);
m_VBox.append(m_ButtonBox);
m_ButtonBox.append(m_Button_Quit);
m_ButtonBox.set_margin(5);
m_Button_Quit.set_hexpand(true);
m_Button_Quit.set_halign(Gtk::Align::END);
m_Button_Quit.signal_clicked().connect(
sigc::mem_fun(*this, &ExampleWindow::on_button_quit));
// Create the list model:
m_StringList = Gtk::StringList::create({"Billy Bob", "Joey Jojo", "Rob McRoberts"});
// Set list model and selection model.
auto selection_model = Gtk::SingleSelection::create(m_StringList);
selection_model->set_autoselect(false);
selection_model->set_can_unselect(true);
m_ListView.set_model(selection_model);
m_ListView.add_css_class("data-table"); // high density table
// Add the factory for the ListView's single column.
auto factory = Gtk::SignalListItemFactory::create();
factory->signal_setup().connect(
sigc::mem_fun(*this, &ExampleWindow::on_setup_label));
factory->signal_bind().connect(
sigc::mem_fun(*this, &ExampleWindow::on_bind_name));
m_ListView.set_factory(factory);
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::on_button_quit()
{
set_visible(false);
}
void ExampleWindow::on_setup_label(const Glib::RefPtr<Gtk::ListItem>& list_item)
{
list_item->set_child(*Gtk::make_managed<Gtk::Label>("", Gtk::Align::START));
}
void ExampleWindow::on_bind_name(const Glib::RefPtr<Gtk::ListItem>& list_item)
{
auto pos = list_item->get_position();
if (pos == GTK_INVALID_LIST_POSITION)
return;
auto label = dynamic_cast<Gtk::Label*>(list_item->get_child());
if (!label)
return;
label->set_text(m_StringList->get_string(pos));
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char* argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
// Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</section>
<section xml:id="sec-listmodel-gridview">
<title xml:lang="en">GridView</title>
<para xml:lang="en">
The <classname>GridView</classname> shows a 2-dimensional grid.
</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1GridView.html">Reference</link></para>
<section xml:id="listmodel-gridview-example">
<title>Ejemplo</title>
<figure xml:id="figure-listmodel-gridview">
<title xml:lang="en">GridView</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/listmodel_gridview.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/listmodelviews/gridview">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm.h>
#include <gdkmm.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
~ExampleWindow() override;
protected:
class ModelColumns;
// Signal handlers:
void on_button_quit();
void on_item_activated(unsigned int position);
void on_selection_changed();
void on_setup_listitem(const Glib::RefPtr<Gtk::ListItem>& list_item);
void on_bind_listitem(const Glib::RefPtr<Gtk::ListItem>& list_item);
int on_model_sort(const Glib::RefPtr<const ModelColumns>& a, const Glib::RefPtr<const ModelColumns>& b);
void add_entry(const std::string& filename, const Glib::ustring& description);
// A Gio::ListStore stores filename, description and texture.
class ModelColumns : public Glib::Object
{
public:
std::string m_filename;
Glib::ustring m_description;
Glib::RefPtr<Gdk::Texture> m_texture;
static Glib::RefPtr<ModelColumns> create(const std::string& filename,
const Glib::ustring& description, const Glib::RefPtr<Gdk::Texture>& texture)
{
return Glib::make_refptr_for_instance<ModelColumns>(
new ModelColumns(filename, description, texture));
}
protected:
ModelColumns(const std::string& filename, const Glib::ustring& description,
const Glib::RefPtr<Gdk::Texture>& texture)
: m_filename(filename), m_description(description), m_texture(texture)
{ }
}; // ModelColumns
Glib::RefPtr<Gio::ListStore<ModelColumns>> m_data_model;
Glib::RefPtr<Gtk::SingleSelection> m_selection_model;
Glib::RefPtr<Gtk::SignalListItemFactory> m_factory;
// Child widgets:
Gtk::Box m_VBox;
Gtk::ScrolledWindow m_ScrolledWindow;
Gtk::GridView m_GridView;
Gtk::Box m_ButtonBox;
Gtk::Button m_Button_Quit;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <array>
#include <iostream>
namespace
{
struct GridEntry
{
std::string m_filename;
Glib::ustring m_description;
};
std::array<GridEntry, 8> entries =
{
GridEntry{"mozilla-firefox.png", "<b>Mozilla Firefox</b> Logo"},
GridEntry{"xmms.xpm", "<b>XMMS</b> Logo"},
GridEntry{"gnome-dice-1.svg", "<b>Gnome Dice 1</b> Logo"},
GridEntry{"gnome-dice-2.svg", "<b>Gnome Dice 2</b> Logo"},
GridEntry{"gnome-dice-3.svg", "<b>Gnome Dice 3</b> Logo"},
GridEntry{"gnome-dice-4.svg", "<b>Gnome Dice 4</b> Logo"},
GridEntry{"gnome-dice-5.svg", "<b>Gnome Dice 5</b> Logo"},
GridEntry{"gnome-dice-6.svg", "<b>Gnome Dice 6</b> Logo"}
};
} // anonymous namespace
ExampleWindow::ExampleWindow()
: m_VBox(Gtk::Orientation::VERTICAL),
m_Button_Quit("Quit")
{
set_title("Gtk::GridView (Gio::ListStore) example");
set_default_size(400, 400);
m_VBox.set_margin(5);
set_child(m_VBox);
// Add the GridView inside a ScrolledWindow, with the button underneath:
m_ScrolledWindow.set_child(m_GridView);
// Only show the scrollbars when they are necessary:
m_ScrolledWindow.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
m_ScrolledWindow.set_expand();
m_VBox.append(m_ScrolledWindow);
m_VBox.append(m_ButtonBox);
m_ButtonBox.append(m_Button_Quit);
m_ButtonBox.set_margin(6);
m_Button_Quit.set_hexpand(true);
m_Button_Quit.set_halign(Gtk::Align::END);
m_Button_Quit.signal_clicked().connect(
sigc::mem_fun(*this, &ExampleWindow::on_button_quit));
// Create the data model:
m_data_model = Gio::ListStore<ModelColumns>::create();
m_selection_model = Gtk::SingleSelection::create(m_data_model);
m_selection_model->set_autoselect(false);
m_factory = Gtk::SignalListItemFactory::create();
m_factory->signal_setup().connect(
sigc::mem_fun(*this, &ExampleWindow::on_setup_listitem));
m_factory->signal_bind().connect(
sigc::mem_fun(*this, &ExampleWindow::on_bind_listitem));
// Fill the Gio::ListStore's data model and sort it.
for (const auto& entry : entries)
add_entry(entry.m_filename, entry.m_description);
m_data_model->sort(sigc::mem_fun(*this, &ExampleWindow::on_model_sort));
m_GridView.set_model(m_selection_model);
m_GridView.set_factory(m_factory);
m_GridView.signal_activate().connect(
sigc::mem_fun(*this, &ExampleWindow::on_item_activated));
m_selection_model->property_selected().signal_changed().connect(
sigc::mem_fun(*this, &ExampleWindow::on_selection_changed));
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::on_button_quit()
{
set_visible(false);
}
void ExampleWindow::on_setup_listitem(const Glib::RefPtr<Gtk::ListItem>& list_item)
{
// Each ListItem contains a vertical Box with an Image and a Label.
auto vBox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL);
auto picture = Gtk::make_managed<Gtk::Picture>();
picture->set_can_shrink(false);
picture->set_halign(Gtk::Align::CENTER);
picture->set_valign(Gtk::Align::END);
vBox->append(*picture);
vBox->append(*Gtk::make_managed<Gtk::Label>());
list_item->set_child(*vBox);
}
void ExampleWindow::on_bind_listitem(const Glib::RefPtr<Gtk::ListItem>& list_item)
{
auto col = std::dynamic_pointer_cast<ModelColumns>(list_item->get_item());
if (!col)
return;
auto vBox = dynamic_cast<Gtk::Box*>(list_item->get_child());
if (!vBox)
return;
auto picture = dynamic_cast<Gtk::Picture*>(vBox->get_first_child());
if (!picture)
return;
auto label = dynamic_cast<Gtk::Label*>(picture->get_next_sibling());
if (!label)
return;
picture->set_paintable(col->m_texture);
label->set_markup(col->m_description);
}
void ExampleWindow::on_item_activated(unsigned int position)
{
auto col = m_data_model->get_item(position);
if (!col)
return;
const std::string filename = col->m_filename;
const Glib::ustring description = col->m_description;
std::cout << "Item activated: filename=" << filename
<< ", description=" << description << std::endl;
}
void ExampleWindow::on_selection_changed()
{
auto position = m_selection_model->get_selected();
auto col = m_data_model->get_item(position);
if (!col)
return;
const std::string filename = col->m_filename;
const Glib::ustring description = col->m_description;
std::cout << "Selection Changed to: filename=" << filename
<< ", description=" << description << std::endl;
}
int ExampleWindow::on_model_sort(const Glib::RefPtr<const ModelColumns>& a,
const Glib::RefPtr<const ModelColumns>& b)
{
return (a->m_description < b->m_description) ? -1 : ((a->m_description > b->m_description) ? 1 : 0);
}
void ExampleWindow::add_entry(const std::string& filename,
const Glib::ustring& description )
{
try
{
auto pixbuf = Gdk::Pixbuf::create_from_file(filename);
auto texture = Gdk::Texture::create_for_pixbuf(pixbuf);
m_data_model->append(ModelColumns::create(filename, description, texture));
}
catch (const Gdk::PixbufError& ex)
{
std::cerr << "Gdk::PixbufError: " << ex.what() << std::endl;
}
catch (const Glib::FileError& ex)
{
std::cerr << "Glib::FileError: " << ex.what() << std::endl;
}
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
//Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</section>
<section xml:id="sec-listmodel-columnview">
<title xml:lang="en">ColumnView</title>
<para xml:lang="en">
The <classname>ColumnView</classname> shows a 1-dimensional list with one or more columns.
</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1ColumnView.html">Reference</link></para>
<section xml:id="listmodel-columnview-example">
<title>Ejemplo</title>
<figure xml:id="figure-listmodel-columnview">
<title xml:lang="en">ColumnView</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/listmodel_columnview.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/listmodelviews/list_columnview">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
~ExampleWindow() override;
protected:
// Signal handlers:
void on_button_quit();
void on_setup_label(const Glib::RefPtr<Gtk::ListItem>& list_item, Gtk::Align halign);
void on_setup_progressbar(const Glib::RefPtr<Gtk::ListItem>& list_item);
void on_bind_id(const Glib::RefPtr<Gtk::ListItem>& list_item);
void on_bind_name(const Glib::RefPtr<Gtk::ListItem>& list_item);
void on_bind_number(const Glib::RefPtr<Gtk::ListItem>& list_item);
void on_bind_percentage(const Glib::RefPtr<Gtk::ListItem>& list_item);
// A Gio::ListStore item.
class ModelColumns : public Glib::Object
{
public:
unsigned int m_col_id;
Glib::ustring m_col_name;
short m_col_number;
int m_col_percentage;
static Glib::RefPtr<ModelColumns> create(unsigned int col_id,
const Glib::ustring& col_name, short col_number, int col_percentage)
{
return Glib::make_refptr_for_instance<ModelColumns>(
new ModelColumns(col_id, col_name, col_number, col_percentage));
}
protected:
ModelColumns(unsigned int col_id, const Glib::ustring& col_name,
short col_number, int col_percentage)
: m_col_id(col_id), m_col_name(col_name), m_col_number(col_number),
m_col_percentage(col_percentage)
{}
}; // ModelColumns
// Child widgets:
Gtk::Box m_VBox;
Gtk::ScrolledWindow m_ScrolledWindow;
Gtk::ColumnView m_ColumnView;
Gtk::Box m_ButtonBox;
Gtk::Button m_Button_Quit;
Glib::RefPtr<Gio::ListStore<ModelColumns>> m_ListStore;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
ExampleWindow::ExampleWindow()
: m_VBox(Gtk::Orientation::VERTICAL),
m_Button_Quit("Quit")
{
set_title("Gtk::ColumnView (Gio::ListStore) example");
set_default_size(500, 250);
m_VBox.set_margin(5);
set_child(m_VBox);
// Add the ColumnView, inside a ScrolledWindow, with the button underneath:
m_ScrolledWindow.set_child(m_ColumnView);
// Only show the scrollbars when they are necessary:
m_ScrolledWindow.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
m_ScrolledWindow.set_expand();
m_VBox.append(m_ScrolledWindow);
m_VBox.append(m_ButtonBox);
m_ButtonBox.append(m_Button_Quit);
m_ButtonBox.set_margin(5);
m_Button_Quit.set_hexpand(true);
m_Button_Quit.set_halign(Gtk::Align::END);
m_Button_Quit.signal_clicked().connect(
sigc::mem_fun(*this, &ExampleWindow::on_button_quit));
// Create the List model:
m_ListStore = Gio::ListStore<ModelColumns>::create();
m_ListStore->append(ModelColumns::create(1, "Billy Bob", 10, 15));
m_ListStore->append(ModelColumns::create(2, "Joey Jojo", 20, 40));
m_ListStore->append(ModelColumns::create(3, "Rob McRoberts", 30, 70));
// Set list model and selection model.
auto selection_model = Gtk::SingleSelection::create(m_ListStore);
selection_model->set_autoselect(false);
selection_model->set_can_unselect(true);
m_ColumnView.set_model(selection_model);
m_ColumnView.add_css_class("data-table"); // high density table
// Make the columns reorderable.
// This is not necessary, but it's nice to show the feature.
m_ColumnView.set_reorderable(true);
// Add the ColumnView's columns:
// Id column
auto factory = Gtk::SignalListItemFactory::create();
factory->signal_setup().connect(sigc::bind(sigc::mem_fun(*this,
&ExampleWindow::on_setup_label), Gtk::Align::END));
factory->signal_bind().connect(
sigc::mem_fun(*this, &ExampleWindow::on_bind_id));
auto column = Gtk::ColumnViewColumn::create("ID", factory);
m_ColumnView.append_column(column);
// Name column
factory = Gtk::SignalListItemFactory::create();
factory->signal_setup().connect(sigc::bind(sigc::mem_fun(*this,
&ExampleWindow::on_setup_label), Gtk::Align::START));
factory->signal_bind().connect(
sigc::mem_fun(*this, &ExampleWindow::on_bind_name));
column = Gtk::ColumnViewColumn::create("Name", factory);
m_ColumnView.append_column(column);
// Number column
factory = Gtk::SignalListItemFactory::create();
factory->signal_setup().connect(sigc::bind(sigc::mem_fun(*this,
&ExampleWindow::on_setup_label), Gtk::Align::END));
factory->signal_bind().connect(
sigc::mem_fun(*this, &ExampleWindow::on_bind_number));
column = Gtk::ColumnViewColumn::create("Formatted number", factory);
m_ColumnView.append_column(column);
// Percentage column
factory = Gtk::SignalListItemFactory::create();
factory->signal_setup().connect(
sigc::mem_fun(*this, &ExampleWindow::on_setup_progressbar));
factory->signal_bind().connect(
sigc::mem_fun(*this, &ExampleWindow::on_bind_percentage));
column = Gtk::ColumnViewColumn::create("Some percentage", factory);
m_ColumnView.append_column(column);
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::on_button_quit()
{
set_visible(false);
}
void ExampleWindow::on_setup_label(
const Glib::RefPtr<Gtk::ListItem>& list_item, Gtk::Align halign)
{
list_item->set_child(*Gtk::make_managed<Gtk::Label>("", halign));
}
void ExampleWindow::on_setup_progressbar(
const Glib::RefPtr<Gtk::ListItem>& list_item)
{
auto progressbar = Gtk::make_managed<Gtk::ProgressBar>();
progressbar->set_show_text(true);
list_item->set_child(*progressbar);
}
void ExampleWindow::on_bind_id(const Glib::RefPtr<Gtk::ListItem>& list_item)
{
auto col = std::dynamic_pointer_cast<ModelColumns>(list_item->get_item());
if (!col)
return;
auto label = dynamic_cast<Gtk::Label*>(list_item->get_child());
if (!label)
return;
label->set_text(Glib::ustring::format(col->m_col_id));
}
void ExampleWindow::on_bind_name(const Glib::RefPtr<Gtk::ListItem>& list_item)
{
auto col = std::dynamic_pointer_cast<ModelColumns>(list_item->get_item());
if (!col)
return;
auto label = dynamic_cast<Gtk::Label*>(list_item->get_child());
if (!label)
return;
label->set_text(col->m_col_name);
}
void ExampleWindow::on_bind_number(const Glib::RefPtr<Gtk::ListItem>& list_item)
{
auto col = std::dynamic_pointer_cast<ModelColumns>(list_item->get_item());
if (!col)
return;
auto label = dynamic_cast<Gtk::Label*>(list_item->get_child());
if (!label)
return;
// 10 digits, using leading zeroes.
label->set_text(Glib::ustring::sprintf("%010d", col->m_col_number));
}
void ExampleWindow::on_bind_percentage(const Glib::RefPtr<Gtk::ListItem>& list_item)
{
auto col = std::dynamic_pointer_cast<ModelColumns>(list_item->get_item());
if (!col)
return;
auto progressbar = dynamic_cast<Gtk::ProgressBar*>(list_item->get_child());
if (!progressbar)
return;
progressbar->set_fraction(col->m_col_percentage * 0.01);
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char* argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
// Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</section>
</section>
<section xml:id="sec-listmodel-sorting">
<title>Ordenar</title>
<para xml:lang="en">
The list can be sorted by wrapping it in a <classname>SortListModel</classname>.
There are two ways to do this.
</para>
<itemizedlist>
<listitem><para xml:lang="en">In a <classname>ColumnView</classname>, get the
<classname>ColumnViewSorter</classname> from the <classname>ColumnView</classname>
and set it to the <classname>SortListModel</classname>. Set a <classname>Sorter</classname>
to each <classname>ColumnViewColumn</classname>. Then the user of your app can
sort the items by clicking on a column heading.</para></listitem>
<listitem><para xml:lang="en">In any view, set a <classname>Sorter</classname> such as a
<classname>StringSorter</classname> or a <classname>NumericSorter</classname>
to the <classname>SortListModel</classname>.</para></listitem>
</itemizedlist>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1SortListModel.html">SortListModel Reference</link></para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1StringSorter.html">StringSorter Reference</link></para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1NumericSorter.html">NumericSorter Reference</link></para>
<section xml:id="listmodel-sorting-example">
<title>Ejemplo</title>
<figure xml:id="figure-listmodel-sorting">
<title xml:lang="en">SortListModel</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/listmodel_sort.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/listmodelviews/sort">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
~ExampleWindow() override;
protected:
// Signal handlers:
void on_button_nosort();
void on_button_sortheadings();
void on_button_sortlength();
void on_button_quit();
void on_setup_label(const Glib::RefPtr<Gtk::ListItem>& list_item, Gtk::Align halign);
void on_bind_id(const Glib::RefPtr<Gtk::ListItem>& list_item);
void on_bind_name(const Glib::RefPtr<Gtk::ListItem>& list_item);
// A Gio::ListStore item.
class ModelColumns : public Glib::Object
{
public:
unsigned int m_col_id;
Glib::ustring m_col_name;
static Glib::RefPtr<ModelColumns> create(int col_id, const Glib::ustring& col_name)
{
return Glib::make_refptr_for_instance<ModelColumns>(
new ModelColumns(col_id, col_name));
}
protected:
ModelColumns(int col_id, const Glib::ustring& col_name)
: m_col_id(col_id), m_col_name(col_name)
{}
}; // ModelColumns
// Child widgets:
Gtk::Box m_VBox;
Gtk::ScrolledWindow m_ScrolledWindow;
Gtk::ColumnView m_ColumnView;
Gtk::Box m_ButtonBox;
Gtk::ToggleButton m_Button_NoSort;
Gtk::ToggleButton m_Button_SortHeadings;
Gtk::ToggleButton m_Button_SortLength;
Gtk::Button m_Button_Quit;
Glib::RefPtr<Gtk::SingleSelection> m_SelectionModel;
Glib::RefPtr<Gio::ListStore<ModelColumns>> m_ListStore;
Glib::RefPtr<Gtk::SortListModel> m_SortListModel;
Glib::RefPtr<Gtk::ColumnViewColumn> m_IdColumn;
Glib::RefPtr<Gtk::ColumnViewColumn> m_NameColumn;
Glib::RefPtr<Gtk::NumericSorter<Glib::ustring::size_type>> m_LengthSorter;
Glib::RefPtr<Gtk::NumericSorter<unsigned int>> m_IdSorter;
Glib::RefPtr<Gtk::StringSorter> m_NameSorter;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><->Glib::ustring::size_type
{
const auto col = std::dynamic_pointer_cast<ModelColumns>(item);
return col ? col->m_col_name.size() : 0;
});
m_LengthSorter = Gtk::NumericSorter<Glib::ustring::size_type>::create(size_expression);
m_LengthSorter->set_sort_order(Gtk::SortType::ASCENDING);
m_SortListModel = Gtk::SortListModel::create(m_ListStore, m_LengthSorter);
// Set list model and selection model.
m_SelectionModel = Gtk::SingleSelection::create(m_ListStore);
m_SelectionModel->set_autoselect(false);
m_SelectionModel->set_can_unselect(true);
m_ColumnView.set_model(m_SelectionModel);
m_ColumnView.add_css_class("data-table"); // high density table
// Make the columns reorderable.
// This is not necessary, but it's nice to show the feature.
m_ColumnView.set_reorderable(true);
// Add the ColumnView's columns:
// Id column
auto factory = Gtk::SignalListItemFactory::create();
factory->signal_setup().connect(sigc::bind(sigc::mem_fun(*this,
&ExampleWindow::on_setup_label), Gtk::Align::END));
factory->signal_bind().connect(
sigc::mem_fun(*this, &ExampleWindow::on_bind_id));
m_IdColumn = Gtk::ColumnViewColumn::create("ID", factory);
m_ColumnView.append_column(m_IdColumn);
auto uint_expression = Gtk::ClosureExpression<unsigned int>::create(
[](const Glib::RefPtr<Glib::ObjectBase>& item)->unsigned int
{
const auto col = std::dynamic_pointer_cast<ModelColumns>(item);
return col ? col->m_col_id : 0;
});
m_IdSorter = Gtk::NumericSorter<unsigned int>::create(uint_expression);
// Name column
factory = Gtk::SignalListItemFactory::create();
factory->signal_setup().connect(sigc::bind(sigc::mem_fun(*this,
&ExampleWindow::on_setup_label), Gtk::Align::START));
factory->signal_bind().connect(
sigc::mem_fun(*this, &ExampleWindow::on_bind_name));
m_NameColumn = Gtk::ColumnViewColumn::create("Name", factory);
m_ColumnView.append_column(m_NameColumn);
auto ustring_expression = Gtk::ClosureExpression<Glib::ustring>::create(
[](const Glib::RefPtr<Glib::ObjectBase>& item)->Glib::ustring
{
const auto col = std::dynamic_pointer_cast<ModelColumns>(item);
return col ? col->m_col_name : "";
});
m_NameSorter = Gtk::StringSorter::create(ustring_expression);
// Show the list model without sorting.
m_Button_NoSort.set_active(true);
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::on_button_nosort()
{
if (m_Button_NoSort.get_active())
{
m_SelectionModel->set_model(m_ListStore);
m_IdColumn->set_sorter({});
m_NameColumn->set_sorter({});
}
}
void ExampleWindow::on_button_sortheadings()
{
if (m_Button_SortHeadings.get_active())
{
m_SortListModel->set_sorter(m_ColumnView.get_sorter());
m_SelectionModel->set_model(m_SortListModel);
m_IdColumn->set_sorter(m_IdSorter);
m_NameColumn->set_sorter(m_NameSorter);
}
}
void ExampleWindow::on_button_sortlength()
{
if (m_Button_SortLength.get_active())
{
m_SortListModel->set_sorter(m_LengthSorter);
m_SelectionModel->set_model(m_SortListModel);
m_IdColumn->set_sorter({});
m_NameColumn->set_sorter({});
}
}
void ExampleWindow::on_button_quit()
{
set_visible(false);
}
void ExampleWindow::on_setup_label(
const Glib::RefPtr<Gtk::ListItem>& list_item, Gtk::Align halign)
{
list_item->set_child(*Gtk::make_managed<Gtk::Label>("", halign));
}
void ExampleWindow::on_bind_id(const Glib::RefPtr<Gtk::ListItem>& list_item)
{
auto col = std::dynamic_pointer_cast<ModelColumns>(list_item->get_item());
if (!col)
return;
auto label = dynamic_cast<Gtk::Label*>(list_item->get_child());
if (!label)
return;
label->set_text(Glib::ustring::format(col->m_col_id));
}
void ExampleWindow::on_bind_name(const Glib::RefPtr<Gtk::ListItem>& list_item)
{
auto col = std::dynamic_pointer_cast<ModelColumns>(list_item->get_item());
if (!col)
return;
auto label = dynamic_cast<Gtk::Label*>(list_item->get_child());
if (!label)
return;
label->set_text(col->m_col_name);
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char* argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
// Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</section>
<section xml:id="sec-listmodel-filtering">
<title xml:lang="en">Filtering</title>
<para xml:lang="en">
The list can be filtered by wrapping it in a <classname>FilterListModel</classname>.
Set a <classname>Filter</classname> such as a <classname>StringFilter</classname>
or a <classname>BoolFilter</classname> to the <classname>FilterListModel</classname>.
</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1FilterListModel.html">FilterListModel Reference</link></para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1StringFilter.html">StringFilter Reference</link></para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1BoolFilter.html">BoolFilter Reference</link></para>
<section xml:id="listmodel-filtering-example">
<title>Ejemplo</title>
<figure xml:id="figure-listmodel-filtering">
<title xml:lang="en">FilterListModel</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/listmodel_filter.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/listmodelviews/filter">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
~ExampleWindow() override;
protected:
// Signal handlers:
void on_button_showall();
void on_button_showj();
void on_button_showm();
void on_button_quit();
void on_setup_label(const Glib::RefPtr<Gtk::ListItem>& list_item, Gtk::Align halign);
void on_bind_id(const Glib::RefPtr<Gtk::ListItem>& list_item);
void on_bind_name(const Glib::RefPtr<Gtk::ListItem>& list_item);
// A Gio::ListStore item.
class ModelColumns : public Glib::Object
{
public:
unsigned int m_col_id;
Glib::ustring m_col_name;
static Glib::RefPtr<ModelColumns> create(int col_id, const Glib::ustring& col_name)
{
return Glib::make_refptr_for_instance<ModelColumns>(
new ModelColumns(col_id, col_name));
}
protected:
ModelColumns(int col_id, const Glib::ustring& col_name)
: m_col_id(col_id), m_col_name(col_name)
{}
}; // ModelColumns
// Child widgets:
Gtk::Box m_VBox;
Gtk::ScrolledWindow m_ScrolledWindow;
Gtk::ColumnView m_ColumnView;
Gtk::Box m_ButtonBox;
Gtk::ToggleButton m_Button_ShowAll;
Gtk::ToggleButton m_Button_ShowJ;
Gtk::ToggleButton m_Button_ShowM;
Gtk::Button m_Button_Quit;
Glib::RefPtr<Gtk::SingleSelection> m_SelectionModel;
Glib::RefPtr<Gio::ListStore<ModelColumns>> m_ListStore;
Glib::RefPtr<Gtk::FilterListModel> m_FilterListModel;
Glib::RefPtr<Gtk::StringFilter> m_StringFilter;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><->Glib::ustring
{
const auto col = std::dynamic_pointer_cast<ModelColumns>(item);
return col ? col->m_col_name : "";
});
m_StringFilter = Gtk::StringFilter::create(expression);
m_StringFilter->set_ignore_case(false);
m_StringFilter->set_match_mode(Gtk::StringFilter::MatchMode::SUBSTRING);
m_FilterListModel = Gtk::FilterListModel::create(m_ListStore, m_StringFilter);
// Set list model and selection model.
m_SelectionModel = Gtk::SingleSelection::create(m_ListStore);
m_SelectionModel->set_autoselect(false);
m_SelectionModel->set_can_unselect(true);
m_ColumnView.set_model(m_SelectionModel);
m_ColumnView.add_css_class("data-table"); // high density table
// Make the columns reorderable.
// This is not necessary, but it's nice to show the feature.
m_ColumnView.set_reorderable(true);
// Add the ColumnView's columns:
// Id column
auto factory = Gtk::SignalListItemFactory::create();
factory->signal_setup().connect(sigc::bind(sigc::mem_fun(*this,
&ExampleWindow::on_setup_label), Gtk::Align::END));
factory->signal_bind().connect(
sigc::mem_fun(*this, &ExampleWindow::on_bind_id));
auto column = Gtk::ColumnViewColumn::create("ID", factory);
m_ColumnView.append_column(column);
// Name column
factory = Gtk::SignalListItemFactory::create();
factory->signal_setup().connect(sigc::bind(sigc::mem_fun(*this,
&ExampleWindow::on_setup_label), Gtk::Align::START));
factory->signal_bind().connect(
sigc::mem_fun(*this, &ExampleWindow::on_bind_name));
column = Gtk::ColumnViewColumn::create("Name", factory);
m_ColumnView.append_column(column);
// Show the list model without filtering.
m_Button_ShowAll.set_active(true);
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::on_button_showall()
{
if (m_Button_ShowAll.get_active())
m_SelectionModel->set_model(m_ListStore);
}
void ExampleWindow::on_button_showj()
{
if (m_Button_ShowJ.get_active())
{
m_StringFilter->set_search("J");
m_SelectionModel->set_model(m_FilterListModel);
}
}
void ExampleWindow::on_button_showm()
{
if (m_Button_ShowM.get_active())
{
m_StringFilter->set_search("M");
m_SelectionModel->set_model(m_FilterListModel);
}
}
void ExampleWindow::on_button_quit()
{
set_visible(false);
}
void ExampleWindow::on_setup_label(
const Glib::RefPtr<Gtk::ListItem>& list_item, Gtk::Align halign)
{
list_item->set_child(*Gtk::make_managed<Gtk::Label>("", halign));
}
void ExampleWindow::on_bind_id(const Glib::RefPtr<Gtk::ListItem>& list_item)
{
auto col = std::dynamic_pointer_cast<ModelColumns>(list_item->get_item());
if (!col)
return;
auto label = dynamic_cast<Gtk::Label*>(list_item->get_child());
if (!label)
return;
label->set_text(Glib::ustring::format(col->m_col_id));
}
void ExampleWindow::on_bind_name(const Glib::RefPtr<Gtk::ListItem>& list_item)
{
auto col = std::dynamic_pointer_cast<ModelColumns>(list_item->get_item());
if (!col)
return;
auto label = dynamic_cast<Gtk::Label*>(list_item->get_child());
if (!label)
return;
label->set_text(col->m_col_name);
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char* argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
// Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</section>
<section xml:id="sec-listmodel-trees">
<title xml:lang="en">Displaying Trees</title>
<para xml:lang="en">
While the deprecated <classname>TreeView</classname> provided built-in support for trees,
the list widgets, and in particular <classname>Gio::ListModel</classname>, do not.
However, <application>gtkmm</application> provides functionality to make trees look and behave like lists for
the people who still want to display lists. This is achieved by using the
<classname>TreeListModel</classname> to flatten a tree into a list.
The <classname>TreeExpander</classname> widget can then be used inside a listitem
to allow users to expand and collapse rows.
</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1TreeListModel.html">TreeListModel Reference</link></para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1TreeExpander.html">TreeExpander Reference</link></para>
<section xml:id="listmodel-tree-example">
<title>Ejemplo</title>
<figure xml:id="figure-listmodel-tree">
<title xml:lang="en">TreeListModel</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/listmodel_tree.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/listmodelviews/tree_columnview">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
~ExampleWindow() override;
protected:
// Signal handlers:
void on_button_quit();
void on_setup_id(const Glib::RefPtr<Gtk::ListItem>& list_item);
void on_setup_name(const Glib::RefPtr<Gtk::ListItem>& list_item);
void on_bind_id(const Glib::RefPtr<Gtk::ListItem>& list_item);
void on_bind_name(const Glib::RefPtr<Gtk::ListItem>& list_item);
Glib::RefPtr<Gio::ListModel> create_model(
const Glib::RefPtr<Glib::ObjectBase>& item = {});
// A Gio::ListStore item.
class ModelColumns : public Glib::Object
{
public:
int m_col_id;
Glib::ustring m_col_name;
static Glib::RefPtr<ModelColumns> create(int col_id, const Glib::ustring& col_name)
{
return Glib::make_refptr_for_instance<ModelColumns>(
new ModelColumns(col_id, col_name));
}
protected:
ModelColumns(int col_id, const Glib::ustring& col_name)
: m_col_id(col_id), m_col_name(col_name)
{}
}; // ModelColumns
// Child widgets:
Gtk::Box m_VBox;
Gtk::ScrolledWindow m_ScrolledWindow;
Gtk::ColumnView m_ColumnView;
Gtk::Box m_ButtonBox;
Gtk::Button m_Button_Quit;
Glib::RefPtr<Gtk::TreeListModel> m_TreeListModel;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
ExampleWindow::ExampleWindow()
: m_VBox(Gtk::Orientation::VERTICAL),
m_Button_Quit("Quit")
{
set_title("Gtk::ColumnView (Gtk::TreeListModel) example");
set_default_size(300, 350);
m_VBox.set_margin(5);
set_child(m_VBox);
// Add the ColumnView, inside a ScrolledWindow, with the button underneath:
m_ScrolledWindow.set_child(m_ColumnView);
// Only show the scrollbars when they are necessary:
m_ScrolledWindow.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
m_ScrolledWindow.set_expand();
m_VBox.append(m_ScrolledWindow);
m_VBox.append(m_ButtonBox);
m_ButtonBox.append(m_Button_Quit);
m_ButtonBox.set_margin(5);
m_Button_Quit.set_hexpand(true);
m_Button_Quit.set_halign(Gtk::Align::END);
m_Button_Quit.signal_clicked().connect(
sigc::mem_fun(*this, &ExampleWindow::on_button_quit));
// Create the root model:
auto root = create_model();
// Set list model and selection model.
// passthrough must be false when Gtk::TreeExpander is used in the view.
m_TreeListModel = Gtk::TreeListModel::create(root,
sigc::mem_fun(*this, &ExampleWindow::create_model),
/* passthrough */ false, /* autoexpand */ false);
auto selection_model = Gtk::MultiSelection::create(m_TreeListModel);
m_ColumnView.set_model(selection_model);
m_ColumnView.add_css_class("data-table"); // high density table
// Make the columns reorderable.
// This is not necessary, but it's nice to show the feature.
m_ColumnView.set_reorderable(true);
// Add the ColumnView's columns:
// Id column
auto factory = Gtk::SignalListItemFactory::create();
factory->signal_setup().connect(
sigc::mem_fun(*this, &ExampleWindow::on_setup_id));
factory->signal_bind().connect(
sigc::mem_fun(*this, &ExampleWindow::on_bind_id));
auto column = Gtk::ColumnViewColumn::create("ID", factory);
m_ColumnView.append_column(column);
// Name column
factory = Gtk::SignalListItemFactory::create();
factory->signal_setup().connect(
sigc::mem_fun(*this, &ExampleWindow::on_setup_name));
factory->signal_bind().connect(
sigc::mem_fun(*this, &ExampleWindow::on_bind_name));
column = Gtk::ColumnViewColumn::create("Name", factory);
m_ColumnView.append_column(column);
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::on_button_quit()
{
set_visible(false);
}
Glib::RefPtr<Gio::ListModel> ExampleWindow::create_model(
const Glib::RefPtr<Glib::ObjectBase>& item)
{
auto col = std::dynamic_pointer_cast<ModelColumns>(item);
auto result = Gio::ListStore<ModelColumns>::create();
if (!col)
{
// Top names
result->append(ModelColumns::create(1, "Billy Bob"));
result->append(ModelColumns::create(2, "Joey Jojo"));
result->append(ModelColumns::create(3, "Rob McRoberts"));
}
else
{
switch (col->m_col_id)
{
case 1:
result->append(ModelColumns::create(11, "Billy Bob Junior"));
result->append(ModelColumns::create(12, "Sue Bob"));
break;
case 3:
result->append(ModelColumns::create(31, "Xavier McRoberts"));
break;
}
}
// If result is empty, it's a leaf in the tree, i.e. an item without children.
// Returning an empty RefPtr (not a RefPtr with an empty Gio::ListModel)
// signals that the item is not expandable.
return (result->get_n_items() > 0) ? result : Glib::RefPtr<Gio::ListModel>();
}
void ExampleWindow::on_setup_id(
const Glib::RefPtr<Gtk::ListItem>& list_item)
{
// Each ListItem contains a TreeExpander, which contains a Label.
// The Label shows the ModelColumns::m_col_id. That's done in on_bind_id().
auto expander = Gtk::make_managed<Gtk::TreeExpander>();
auto label = Gtk::make_managed<Gtk::Label>();
label->set_halign(Gtk::Align::END);
expander->set_child(*label);
list_item->set_child(*expander);
}
void ExampleWindow::on_setup_name(
const Glib::RefPtr<Gtk::ListItem>& list_item)
{
list_item->set_child(*Gtk::make_managed<Gtk::Label>("", Gtk::Align::START));
}
void ExampleWindow::on_bind_id(const Glib::RefPtr<Gtk::ListItem>& list_item)
{
// When TreeListModel::property_passthrough() is false, ListItem::get_item()
// is a TreeListRow. TreeExpander needs the TreeListRow.
// The ModelColumns item is returned by TreeListRow::get_item().
auto row = std::dynamic_pointer_cast<Gtk::TreeListRow>(list_item->get_item());
if (!row)
return;
auto col = std::dynamic_pointer_cast<ModelColumns>(row->get_item());
if (!col)
return;
auto expander = dynamic_cast<Gtk::TreeExpander*>(list_item->get_child());
if (!expander)
return;
expander->set_list_row(row);
auto label = dynamic_cast<Gtk::Label*>(expander->get_child());
if (!label)
return;
label->set_text(Glib::ustring::format(col->m_col_id));
}
void ExampleWindow::on_bind_name(const Glib::RefPtr<Gtk::ListItem>& list_item)
{
auto row = std::dynamic_pointer_cast<Gtk::TreeListRow>(list_item->get_item());
if (!row)
return;
auto col = std::dynamic_pointer_cast<ModelColumns>(row->get_item());
if (!col)
return;
auto label = dynamic_cast<Gtk::Label*>(list_item->get_child());
if (!label)
return;
label->set_text(col->m_col_name);
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char* argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
// Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</section>
</chapter>
<chapter xml:id="chapter-treeview">
<title>El widget TreeView</title>
<note><para xml:lang="en"><classname>Gtk::TreeView</classname> is deprecated since <application>gtkmm</application> 4.10.
In new code, use <classname>Gtk::ListView</classname> for lists and
<classname>Gtk::ColumnView</classname> for tabular lists.
</para></note>
<para>El widget <classname>Gtk::TreeView</classname> puede contener listas o árboles de datos, en columnas.</para>
<section xml:id="sec-treeview-model">
<title>El modelo</title>
<para>Cada <classname>Gtk::TreeView</classname> tiene un <classname>Gtk::Model</classname> asociado que contiene los datos mostrados por el <classname>TreeView</classname> Varios <classname>Gtk::TreeView</classname> puede usar el mismo <classname>Gtk::TreeModel</classname>. Por ejemplo, esto le permite mostrar y editar los mismos datos de 2 formas diferentes al mismo tiempo. O las 2 vistas pueden mostrar diferentes columnas de los mismos modelos de datos, de la misma manera que dos consultas SQL (o «vistas») pueden mostrar diferentes campos de la misma tabla de la base de datos.</para>
<para>A pesar de que, teóricamente, puede implementar su propio modelo, normalmente usará las clases de los modelos <classname>ListStore</classname> o <classname>TreeStore</classname>.</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1TreeModel.html">Reference</link></para>
<section xml:id="treeview-model-liststore">
<title>ListStore, para filas</title>
<para>El <classname>ListStore</classname> contiene filas simples de datos, y ninguna fila tiene hijos.</para>
<figure xml:id="figure-treeview-liststore-model">
<title>TreeView - ListStore</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/treeview_list.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1ListStore.html">Reference</link></para>
</section>
<section xml:id="treeview-model-treestore">
<title>TreeStore, para una jerarquía</title>
<para>El <classname>TreeStore</classname> contiene filas de datos, y cada fila puede tener filas hijas.</para>
<figure xml:id="figure-treeview-treestore-model">
<title>TreeView - TreeStore</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/treeview_tree.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1TreeStore.html">Reference</link></para>
</section>
<section xml:id="treeview-model-columns">
<title>Columnas del modelo</title>
<para xml:lang="en">
The <classname>TreeModelColumnRecord</classname> class is used to keep track
of the columns and their data types. You add
<classname>TreeModelColumn</classname> instances to the
<classname>ColumnRecord</classname> and then use those
<classname>TreeModelColumn</classname>s when getting and setting the data in
model rows. You will probably find it convenient to derive a new
<classname>TreeModelColumnRecord</classname> which has your
<classname>TreeModelColumn</classname> instances as member data.
</para>
<programlisting xml:lang="en"><code>class ModelColumns : public Gtk::TreeModelColumnRecord
{
public:
ModelColumns()
{ add(m_col_text); add(m_col_number); }
Gtk::TreeModelColumn<Glib::ustring> m_col_text;
Gtk::TreeModelColumn<int> m_col_number;
};
ModelColumns m_Columns;</code></programlisting>
<para>Especifique el <classname>ColumnRecord</classname> cuando cree el modelo, así:</para>
<programlisting xml:lang="en"><code>Glib::RefPtr<Gtk::ListStore> refListStore =
Gtk::ListStore::create(m_Columns);</code></programlisting>
<para xml:lang="en">
As a <classname>TreeModelColumnRecord</classname> describes structure, not data,
it can be shared among multiple models, and this is preferable for efficiency.
However, the instance (such as <varname>m_Columns</varname> here) should usually
not be static, because it often needs to be instantiated after
<application>glibmm</application> has been initialized. The best solution is
to make it a lazily instantiated singleton, so that it will be constructed
on-demand, whenever the first model accesses it.
</para>
</section>
<section xml:id="treeview-adding-rows">
<title>Añadir filas</title>
<para>Añadir filas al modelo con los métodos <methodname>append()</methodname>, <methodname>prepend()</methodname>, o <methodname>insert()</methodname>.</para>
<programlisting xml:lang="en"><code>auto iter = m_refListStore->append();</code></programlisting>
<para>Puede desreferenciar al iterador para obtener la fila:</para>
<programlisting xml:lang="en"><code>auto row = *iter;</code></programlisting>
<section xml:id="treeview-adding-child-rows">
<title>Añadiendo filas secundarias</title>
<para>Los modelos <classname>Gtk::TreeStore</classname> pueden tener elementos hijos. Añádalos con los métodos <methodname>append()</methodname>, <methodname>prepend()</methodname>, o <methodname>insert()</methodname>, así:</para>
<programlisting xml:lang="en"><code>auto iter_child =
m_refTreeStore->append(row.children());</code></programlisting>
</section>
</section>
<section xml:id="treeview-setting-values">
<title>Configurar los valores</title>
<para xml:lang="en">
You can use the <methodname>operator[]</methodname> overload to set the data for a
particular column in the row, specifying the
<classname>TreeModelColumn</classname> used to create the model.
</para>
<programlisting xml:lang="en"><code>row[m_Columns.m_col_text] = "sometext";</code></programlisting>
</section>
<section xml:id="treeview-getting-values">
<title>Obtener los valores</title>
<para xml:lang="en">
You can use the <methodname>operator[]</methodname> overload to get the data in a
particular column in a row, specifying the
<classname>TreeModelColumn</classname> used to create the model.
</para>
<programlisting xml:lang="en"><code>auto strText = row[m_Columns.m_col_text];
auto number = row[m_Columns.m_col_number];</code></programlisting>
<para>El compilador le hará saber si usa un tipo inapropiado. Por ejemplo, esto generaría un error de compilación:</para>
<programlisting xml:lang="en"><code>//compiler error - no conversion from ustring to int.
int number = row[m_Columns.m_col_text];</code></programlisting>
</section>
<section xml:id="treeview-hidden-columns">
<title>Columnas «ocultas»</title>
<para>Puede querer asociar datos adicionales a cada fila. Si es así, sólo añádalos como una columa del modelo, pero no se los añada a la vista.</para>
</section>
</section>
<section xml:id="sec-treeview">
<title>La vista</title>
<para>La vista es el widget en sí (<classname>Gtk::TreeView</classname>) que muestra los datos del modelo (<classname>Gtk::TreeModel</classname>) y le permite al usuario interactuar con él. La vista puede mostrar todas las columnas del modelo, o sólo algunas, y puede mostrarlas de varias maneras.</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1TreeView.html">Reference</link></para>
<section xml:id="sec-treeview-using-a-model">
<title>Usar un modelo</title>
<para>Puede especificar un <classname>Gtk::TreeModel</classname> cuando construye la <classname>Gtk::TreeView</classname>, o puede usar el método <methodname>set_model()</methodname>, así:</para>
<programlisting xml:lang="en"><code>m_TreeView.set_model(m_refListStore);</code></programlisting>
</section>
<section xml:id="treeview-adding-view-columns">
<title>Añadir columnas a la vista</title>
<para>Puede usar el método <methodname>append_column()</methodname> para decirle a la vista que debe mostrar ciertas columnas del modelo, en cierto orden, con un cierto título de columna.</para>
<programlisting xml:lang="en"><code>m_TreeView.append_column("Messages", m_Columns.m_col_text);</code></programlisting>
<para xml:lang="en">
When using this simple <methodname>append_column()</methodname> overload, the
<classname>TreeView</classname> will display the model data with an appropriate
<classname>CellRenderer</classname>. For instance, strings and numbers are
shown in a simple <classname>Gtk::Entry</classname> widget, and booleans are
shown in a <classname>Gtk::CheckButton</classname>. This is usually what you
need. For other column types you must either connect a callback that converts
your type into a string representation, with
<methodname>TreeViewColumn::set_cell_data_func()</methodname>, or derive a custom
<classname>CellRenderer</classname>. Note that (unsigned) short is not
supported by default - You could use (unsigned) int or (unsigned) long as the
column type instead.
</para>
</section>
<section xml:id="treeview-multiple-model-columns-per-view-column">
<title>Más de una columna del modelo por columna de la vista</title>
<para>Para reproducir más de una columna del modelo en una columna de vista, necesita crear el widget <classname>TreeView::Column</classname> manualmente, y usar <methodname>pack_start()</methodname> para añadirle las columnas del modelo.</para>
<para xml:lang="en">
Then use <methodname>append_column()</methodname> to add the view Column to the
View. Notice that <methodname>Gtk::TreeView::append_column()</methodname> is overloaded
to accept either a prebuilt <classname>Gtk::TreeView::Column</classname> widget, or
just the <classname>TreeModelColumn</classname> from which it generates an
appropriate <classname>Gtk::TreeView::Column</classname> widget.
</para>
<para xml:lang="en">
Here is some example code, which has a pixbuf icon and a text name in the same column:
</para>
<programlisting xml:lang="en"><code>auto pColumn = Gtk::make_managed<Gtk::TreeView::Column>("Icon Name");
// m_columns.icon and m_columns.iconname are columns in the model.
// pColumn is the column in the TreeView:
pColumn->pack_start(m_columns.icon, /* expand= */ false);
pColumn->pack_start(m_columns.iconname);
m_TreeView.append_column(*pColumn);</code></programlisting>
</section>
<section xml:id="treeview-cellrenderer-details">
<title>Especificar los detalles del CellRenderer</title>
<para xml:lang="en">
The default <classname>CellRenderer</classname>s and their default behavior
will normally suffice, but you might occasionally need finer control. For
instance, this example code from
<filename>gtkmm/demos/gtk-demo/example_treeview_treestore.cc</filename>, appends a
<classname>Gtk::CellRenderer</classname> widget and instructs it to render the
data from various model columns through various aspects of its appearance.
</para>
<programlisting xml:lang="en"><code>auto cols_count = m_TreeView.append_column_editable("Alex", m_columns.alex);
auto pColumn = m_TreeView.get_column(cols_count-1);
if(pColumn)
{
auto pRenderer = static_cast<Gtk::CellRendererToggle*>(pColumn->get_first_cell());
pColumn->add_attribute(pRenderer->property_visible(), m_columns.visible);
pColumn->add_attribute(pRenderer->property_activatable(), m_columns.world);</code></programlisting>
<para>También puede conectarle señales a un <classname>CellRenderer</classname> para detectar las acciones del usuario. Por ejemplo:</para>
<programlisting xml:lang="en"><code>auto pRenderer = Gtk::make_managed<Gtk::CellRendererToggle>();
pRenderer->signal_toggled().connect(
sigc::bind( sigc::mem_fun(*this,
&Example_TreeView_TreeStore::on_cell_toggled), m_columns.dave)
);</code></programlisting>
</section>
<section xml:id="treeview-editable-cells">
<title>Celdas editables</title>
<section xml:id="treeview-editable-cells-automatic">
<title>Celdas editables guardadas automáticamente.</title>
<para xml:lang="en">
Cells in a <classname>TreeView</classname> can be edited in-place by the user.
To allow this, use the <classname>Gtk::TreeView</classname>
<methodname>insert_column_editable()</methodname> and
<methodname>append_column_editable()</methodname> methods instead of
<methodname>insert_column()</methodname> and <methodname>append_column()</methodname>.
When these cells are edited the new values will be stored immediately in the
Model. Note that these methods are templates which can only be instantiated for
simple column types such as <classname>Glib::ustring</classname>, int, and
long.
</para>
</section>
<section xml:id="treeview-editable-cells-custom">
<title>Implementación de la lógica personalizada para celdas editables.</title>
<para>Sin embargo, tal vez no quiera que se almacenen los valores nuevos inmediatamente. Por ejemplo, tal vez quiera restringir la entrada a ciertos caracteres o rangos de valores.</para>
<para xml:lang="en">
To achieve this, you should use the normal <classname>Gtk::TreeView</classname>
<methodname>insert_column()</methodname> and <methodname>append_column()</methodname>
methods, then use <methodname>get_column_cell_renderer()</methodname> to get the
<classname>Gtk::CellRenderer</classname> used by that column.
</para>
<para>Entonces, convierta ese <classname>Gtk::CellRenderer*</classname> al <classname>CellRenderer</classname> específico que espera, para que pueda usar la API específica.</para>
<para>Por ejemplo, para un CellRendererText, establecería la propiedad <emphasis>editable</emphasis> de la celda a «true», así:</para>
<programlisting xml:lang="en"><code>cell->property_editable() = true;</code></programlisting>
<para>Para un CellRendererToggle, establecería la propiedad <emphasis>activable</emphasis> en su lugar.</para>
<para xml:lang="en">You can then connect
to the appropriate "edited" signal. For instance, connect to
<methodname>Gtk::CellRendererText::signal_edited()</methodname>, or
<methodname>Gtk::CellRendererToggle::signal_toggled()</methodname>. If the column
contains more than one <classname>CellRenderer</classname> then you will need
to use <methodname>Gtk::TreeView::get_column()</methodname> and then call
<methodname>get_cells()</methodname> on that view Column.
</para>
<para>En su gestor de señales, debe examinar el valor nuevo y luego almacenarlo en el modelo, si eso es lo apropiado para su aplicación.</para>
</section>
</section>
</section>
<section xml:id="sec-iterating-over-model-rows">
<title>Iterar sobre las filas del modelo</title>
<para><classname>Gtk::TreeModel</classname> proporciona un contenedor de sus hijos al estilo de las bibliotecas C++ estándar, a través del método <methodname>children()</methodname>. Puede usar los incrementos del iterador familiares de los métodos <methodname>begin()</methodname> y <methodname>end()</methodname>.</para>
<programlisting xml:lang="en"><code>auto children = refModel->children();
for (auto iter = children.begin(), end = children.end(); iter != end; ++iter)
{
auto row = *iter;
//Do something with the row - see above for set/get.
}</code></programlisting>
<para xml:lang="en">
If you always want to iterate across the entire range, much more succinct syntax
is possible using C++'s range-based <literal>for</literal> loop:
</para>
<programlisting xml:lang="en"><code>for (auto row: refModel->children())
{
//Do something with the row - see above for set/get.
}</code></programlisting>
<section xml:id="treeview-row-children">
<title>Fila hija</title>
<para xml:lang="en">
When using a <classname>Gtk::TreeStore</classname>, the rows can have child
rows, which can have their own children in turn. Use
<methodname>Gtk::TreeModel::Row::children()</methodname> to get the container of child <classname>Row</classname>s:
</para>
<programlisting xml:lang="en"><code>Gtk::TreeModel::Children children = row.children();</code></programlisting>
</section>
</section>
<section xml:id="sec-treeview-selection">
<title>La selección</title>
<para>Para descubrir qué filas ha seleccionado el usuario, obtenga el objeto <classname>Gtk::TreeView::Selection</classname> del <classname>TreeView</classname>, así:</para>
<programlisting xml:lang="en"><code>auto refTreeSelection = m_TreeView.get_selection();</code></programlisting>
<section xml:id="treeview-selection-mode">
<title>Selección única o múltiple</title>
<para xml:lang="en">
By default, only single rows can be selected, but you can allow
multiple selection by setting the mode, like so:
</para>
<programlisting xml:lang="en"><code>refTreeSelection->set_mode(Gtk::SelectionMode::MULTIPLE);</code></programlisting>
</section>
<section xml:id="treeview-selected-rows">
<title>Las filas seleccionadas</title>
<para>Para la selección simple, puede simplemente llamar <methodname>get_selected()</methodname>, así:</para>
<programlisting xml:lang="en"><code>auto iter = refTreeSelection->get_selected();
if(iter) //If anything is selected
{
auto row = *iter;
//Do something with the row.
}</code></programlisting>
<para xml:lang="en">
For multiple-selection, you need to call <methodname>get_selected_rows()</methodname>
or define a callback, and give it to
<methodname>selected_foreach()</methodname>,
<methodname>selected_foreach_path()</methodname>, or
<methodname>selected_foreach_iter()</methodname>, like so:
</para>
<programlisting xml:lang="en"><code>refTreeSelection->selected_foreach_iter(
sigc::mem_fun(*this, &TheClass::selected_row_callback) );
void TheClass::selected_row_callback(
const Gtk::TreeModel::const_iterator& iter)
{
auto row = *iter;
//Do something with the row.
}</code></programlisting>
</section>
<section xml:id="treeview-selection-changed-signal">
<title>La señal «changed»</title>
<para>Para responder a la pulsación del usuario en una fila o un rango de filas, conéctese a la señal así:</para>
<programlisting xml:lang="en"><code>refTreeSelection->signal_changed().connect(
sigc::mem_fun(*this, &Example_IconTheme::on_selection_changed)
);</code></programlisting>
</section>
<section xml:id="treeview-selection-preventing">
<title>Evitar la selección de la fila</title>
<para>Tal vez, el usuario no deba ser capaz de seleccionar todos los elementos en su lista o árbol. Por ejemplo, en gtk-demo, puede seleccionar una demostración para ver su código fuente, pero no tiene ningún sentido seleccionar una categoría de demostraciones.</para>
<para>Para controlar qué filas pueden seleccionarse, use el método <methodname>set_select_function()</methodname>, proporcionándole un retorno de llamada <classname>sigc::slot</classname>. Por ejemplo:</para>
<programlisting xml:lang="en"><code>m_refTreeSelection->set_select_function( sigc::mem_fun(*this,
&DemoWindow::select_function) );</code></programlisting>
<para>Y luego</para>
<programlisting xml:lang="en"><code>bool DemoWindow::select_function(
const Glib::RefPtr<Gtk::TreeModel>& model,
const Gtk::TreeModel::Path& path, bool)
{
const auto iter = model->get_iter(path);
return iter->children().empty(); // only allow leaf nodes to be selected
}</code></programlisting>
</section>
<section xml:id="treeview-selection-changing">
<title>Cambiar la selección</title>
<para>Para cambiar la selección, especifique un <classname>Gtk::TreeModel::iterator</classname> o un <classname>Gtk::TreeModel::Row</classname>, así:</para>
<programlisting xml:lang="en"><code>auto row = m_refModel->children()[5]; //The sixth row.
if(row)
refTreeSelection->select(row.get_iter());</code></programlisting>
<para>o</para>
<programlisting xml:lang="en"><code>auto iter = m_refModel->children().begin()
if(iter)
refTreeSelection->select(iter);</code></programlisting>
</section>
</section>
<section xml:id="sec-treeview-sort">
<title>Ordenar</title>
<para>Los modelos de árbol estándar (<classname>TreeStore</classname> y <classname>ListStore</classname>) derivan de <classname>TreeSortable</classname>, por lo que ofrecen funciones de ordenación. Por ejemplo, llame a <methodname>set_sort_column()</methodname> para ordenar el modelo por la columna especificada. O, proporcione una función de retorno de llamada a <methodname>set_sort_func()</methodname> para implementar un algoritmo de ordenación más complejo.</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1TreeSortable.html">TreeSortable Reference</link></para>
<section xml:id="treeview-sort-headers">
<title>Ordenación al pulsar en columnas</title>
<para xml:lang="en">
So that a user can click on a <classname>TreeView</classname>'s column header to sort the <classname>TreeView</classname>'s contents, call <methodname>Gtk::TreeView::Column::set_sort_column()</methodname>, supplying the model column on which model should be sorted when the header is clicked. For instance:
</para>
<programlisting xml:lang="en"><code>auto pColumn = treeview.get_column(0);
if(pColumn)
pColumn->set_sort_column(m_columns.m_col_id);</code></programlisting>
</section>
<section xml:id="treeview-sort-independent-views">
<title>Vistas ordenadas independientemente del mismo modelo</title>
<para xml:lang="en">
The <classname>TreeView</classname> already allows you to show the same <classname>TreeModel</classname>
in two <classname>TreeView</classname> widgets. If you need one of these TreeViews to sort the model
differently than the other then you should use a <classname>TreeModelSort</classname> instead of just,
for instance, <methodname>Gtk::TreeViewColumn::set_sort_column()</methodname>.
<classname>TreeModelSort</classname> is a model that contains another model, presenting a sorted version
of that model. For instance, you might add a sorted version of a model to a <classname>TreeView</classname> like so:
</para>
<programlisting xml:lang="en"><code>auto sorted_model = Gtk::TreeModelSort::create(model);
sorted_model->set_sort_column(columns.m_col_name, Gtk::SortType::ASCENDING);
treeview.set_model(sorted_model);</code></programlisting>
<para>Tenga en cuenta, sin embargo, que el «TreeView» le proporcionará iteradores al modelo ordenado. Debe convertirlos a iteradores del modelo hijo subyacente para llevar a cabo acciones en ese modelo. Por ejemplo:</para>
<programlisting xml:lang="en"><code>void ExampleWindow::on_button_delete()
{
auto refTreeSelection = m_treeview.get_selection();
if(refTreeSelection)
{
auto sorted_iter = m_refTreeSelection->get_selected();
if(sorted_iter)
{
auto iter = m_refModelSort->convert_iter_to_child_iter(sorted_iter);
m_refModel->erase(iter);
}
}
}</code></programlisting>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1TreeModelSort.html">TreeModelSort Reference</link></para>
</section>
</section>
<section xml:id="sec-treeview-draganddrop">
<title>Arrastrar y soltar</title>
<para xml:lang="en">
<classname>Gtk::TreeView</classname> already implements simple drag-and-drop
when used with the <classname>Gtk::ListStore</classname> or
<classname>Gtk::TreeStore</classname> models (since gtk 4.8). If necessary, it also allows you
to implement more complex behavior when items are dragged and dropped, using
the normal <link linkend="chapter-draganddrop">Drag and Drop</link> API.
</para>
<section xml:id="treeview-reorderable-rows">
<title>Filas reordenables</title>
<para>Si llama a <methodname>Gtk::TreeView::set:reorderable</methodname>, entonces se podrán mover los elementos de su «TreeView» dentro del «TreeView» en sí. Esto se demuestra en el ejemplo <classname>TreeStore</classname>.</para>
<para xml:lang="en">However, this does not allow you any control of which items can be dragged, and where they can be dropped.
If you need that extra control then you might create a derived <literal>Gtk::TreeModel</literal> from
<literal>Gtk::TreeStore</literal> or <literal>Gtk::ListStore</literal> and override the
<literal>Gtk::TreeDragSource::row_draggable_vfunc()</literal> and
<literal>Gtk::TreeDragDest::row_drop_possible_vfunc()</literal> virtual methods.
You can examine the <literal>Gtk::TreeModel::Path</literal>s provided and allow or disallow dragging
or dropping by returning <literal>true</literal> or <literal>false</literal>.</para>
<para>Esto se demuestra en el ejemplo «drag_and_drop».</para>
</section>
</section>
<section xml:id="sec-treeview-contextmenu">
<title>Menú contextual emergente</title>
<para xml:lang="en">
Lots of people need to implement right-click context menus for
<classname>TreeView</classname>s so we will explain how to do that here to
save you some time. It's much the same as a normal context menu, as described
in the <link linkend="sec-menus-popup">menus chapter</link>. You use a
<classname>Gtk::GestureClick</classname> to detect the mouse click.
</para>
<para xml:lang="en">This is demonstrated in the Popup Context Menu example. In that example
a derived <classname>TreeView</classname> is used, but that's not necessary.
</para>
</section>
<section xml:id="sec-treeview-examples">
<title>Ejemplos</title>
<para xml:lang="en">Some <classname>TreeView</classname> examples are shown here. There are
more examples in the <link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/treeview/">treeview directory</link>
in <application>gtkmm-documentation</application>'s examples.</para>
<para xml:lang="en">If neither <classname>ListStore</classname> nor <classname>TreeStore</classname>
is suitable for your application, look at the
<link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/treeview/custom_treemodel">custom TreeModel</link>
example. It shows how you can make your own implementation of the <classname>TreeModel</classname>
interface.</para>
<section xml:id="liststore-example">
<title>ListStore</title>
<para>Este ejemplo tiene un widget <classname>Gtk::TreeView</classname>, con un modelo <classname>Gtk::ListStore</classname>.</para>
<figure xml:id="figure-treeview-liststore">
<title>TreeView - ListStore</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/treeview_list.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/treeview/list/">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
virtual ~ExampleWindow();
protected:
//Signal handlers:
void on_button_quit();
//Tree model columns:
class ModelColumns : public Gtk::TreeModel::ColumnRecord
{
public:
ModelColumns()
{ add(m_col_id); add(m_col_name); add(m_col_number); add(m_col_percentage);}
Gtk::TreeModelColumn<unsigned int> m_col_id;
Gtk::TreeModelColumn<Glib::ustring> m_col_name;
Gtk::TreeModelColumn<short> m_col_number;
Gtk::TreeModelColumn<int> m_col_percentage;
};
ModelColumns m_Columns;
//Child widgets:
Gtk::Box m_VBox;
Gtk::ScrolledWindow m_ScrolledWindow;
Gtk::TreeView m_TreeView;
Glib::RefPtr<Gtk::ListStore> m_refTreeModel;
Gtk::Box m_ButtonBox;
Gtk::Button m_Button_Quit;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include <iostream>
#include "examplewindow.h"
ExampleWindow::ExampleWindow()
: m_VBox(Gtk::Orientation::VERTICAL),
m_Button_Quit("Quit")
{
set_title("Gtk::TreeView (ListStore) example");
set_default_size(400, 200);
m_VBox.set_margin(5);
set_child(m_VBox);
//Add the TreeView, inside a ScrolledWindow, with the button underneath:
m_ScrolledWindow.set_child(m_TreeView);
//Only show the scrollbars when they are necessary:
m_ScrolledWindow.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
m_ScrolledWindow.set_expand();
m_VBox.append(m_ScrolledWindow);
m_VBox.append(m_ButtonBox);
m_ButtonBox.append(m_Button_Quit);
m_ButtonBox.set_margin(5);
m_Button_Quit.set_hexpand(true);
m_Button_Quit.set_halign(Gtk::Align::END);
m_Button_Quit.signal_clicked().connect( sigc::mem_fun(*this,
&ExampleWindow::on_button_quit) );
//Create the Tree model:
m_refTreeModel = Gtk::ListStore::create(m_Columns);
m_TreeView.set_model(m_refTreeModel);
//Fill the TreeView's model
auto row = *(m_refTreeModel->append());
row[m_Columns.m_col_id] = 1;
row[m_Columns.m_col_name] = "Billy Bob";
row[m_Columns.m_col_number] = 10;
row[m_Columns.m_col_percentage] = 15;
row = *(m_refTreeModel->append());
row[m_Columns.m_col_id] = 2;
row[m_Columns.m_col_name] = "Joey Jojo";
row[m_Columns.m_col_number] = 20;
row[m_Columns.m_col_percentage] = 40;
row = *(m_refTreeModel->append());
row[m_Columns.m_col_id] = 3;
row[m_Columns.m_col_name] = "Rob McRoberts";
row[m_Columns.m_col_number] = 30;
row[m_Columns.m_col_percentage] = 70;
//Add the TreeView's view columns:
//This number will be shown with the default numeric formatting.
m_TreeView.append_column("ID", m_Columns.m_col_id);
m_TreeView.append_column("Name", m_Columns.m_col_name);
m_TreeView.append_column_numeric("Formatted number", m_Columns.m_col_number,
"%010d" /* 10 digits, using leading zeroes. */);
//Display a progress bar instead of a decimal number:
auto cell = Gtk::make_managed<Gtk::CellRendererProgress>();
int cols_count = m_TreeView.append_column("Some percentage", *cell);
auto pColumn = m_TreeView.get_column(cols_count - 1);
if(pColumn)
{
pColumn->add_attribute(cell->property_value(), m_Columns.m_col_percentage);
}
//Make all the columns reorderable:
//This is not necessary, but it's nice to show the feature.
//You can use TreeView::set_column_drag_function() to more
//finely control column drag and drop.
for(guint i = 0; i < 2; i++)
{
auto column = m_TreeView.get_column(i);
column->set_reorderable();
}
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::on_button_quit()
{
set_visible(false);
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
//Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
<section xml:id="treestore-example">
<title>TreeStore</title>
<para>Este ejemplo es muy similar al ejemplo del <classname>ListStore</classname>, pero usa el modelo <classname>Gtk::TreeStore</classname> en su lugar, y le añade hijos a las filas.</para>
<figure xml:id="figure-treeview-treestore">
<title>TreeView - TreeStore</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/treeview_tree.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/treeview/tree/">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
virtual ~ExampleWindow();
protected:
//Signal handlers:
void on_button_quit();
void on_treeview_row_activated(const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn* column);
//Tree model columns:
class ModelColumns : public Gtk::TreeModel::ColumnRecord
{
public:
ModelColumns()
{ add(m_col_id); add(m_col_name); }
Gtk::TreeModelColumn<int> m_col_id;
Gtk::TreeModelColumn<Glib::ustring> m_col_name;
};
ModelColumns m_Columns;
//Child widgets:
Gtk::Box m_VBox;
Gtk::ScrolledWindow m_ScrolledWindow;
Gtk::TreeView m_TreeView;
Glib::RefPtr<Gtk::TreeStore> m_refTreeModel;
Gtk::Box m_ButtonBox;
Gtk::Button m_Button_Quit;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include <iostream>
#include "examplewindow.h"
ExampleWindow::ExampleWindow()
: m_VBox(Gtk::Orientation::VERTICAL),
m_Button_Quit("Quit")
{
set_title("Gtk::TreeView (TreeStore) example");
set_default_size(400, 200);
m_VBox.set_margin(5);
set_child(m_VBox);
//Add the TreeView, inside a ScrolledWindow, with the button underneath:
m_ScrolledWindow.set_child(m_TreeView);
//Only show the scrollbars when they are necessary:
m_ScrolledWindow.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
m_ScrolledWindow.set_expand();
m_VBox.append(m_ScrolledWindow);
m_VBox.append(m_ButtonBox);
m_ButtonBox.append(m_Button_Quit);
m_ButtonBox.set_margin(5);
m_Button_Quit.set_hexpand(true);
m_Button_Quit.set_halign(Gtk::Align::END);
m_Button_Quit.signal_clicked().connect(sigc::mem_fun(*this,
&ExampleWindow::on_button_quit) );
//Create the Tree model:
m_refTreeModel = Gtk::TreeStore::create(m_Columns);
m_TreeView.set_model(m_refTreeModel);
//All the items to be reordered with drag-and-drop:
m_TreeView.set_reorderable();
//Fill the TreeView's model
auto row = *(m_refTreeModel->append());
row[m_Columns.m_col_id] = 1;
row[m_Columns.m_col_name] = "Billy Bob";
auto childrow = *(m_refTreeModel->append(row.children()));
childrow[m_Columns.m_col_id] = 11;
childrow[m_Columns.m_col_name] = "Billy Bob Junior";
childrow = *(m_refTreeModel->append(row.children()));
childrow[m_Columns.m_col_id] = 12;
childrow[m_Columns.m_col_name] = "Sue Bob";
row = *(m_refTreeModel->append());
row[m_Columns.m_col_id] = 2;
row[m_Columns.m_col_name] = "Joey Jojo";
row = *(m_refTreeModel->append());
row[m_Columns.m_col_id] = 3;
row[m_Columns.m_col_name] = "Rob McRoberts";
childrow = *(m_refTreeModel->append(row.children()));
childrow[m_Columns.m_col_id] = 31;
childrow[m_Columns.m_col_name] = "Xavier McRoberts";
//Add the TreeView's view columns:
m_TreeView.append_column("ID", m_Columns.m_col_id);
m_TreeView.append_column("Name", m_Columns.m_col_name);
//Connect signal:
m_TreeView.signal_row_activated().connect(sigc::mem_fun(*this,
&ExampleWindow::on_treeview_row_activated) );
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::on_button_quit()
{
set_visible(false);
}
void ExampleWindow::on_treeview_row_activated(const Gtk::TreeModel::Path& path,
Gtk::TreeViewColumn* /* column */)
{
const auto iter = m_refTreeModel->get_iter(path);
if(iter)
{
const auto row = *iter;
std::cout << "Row activated: ID=" << row[m_Columns.m_col_id] << ", Name="
<< row[m_Columns.m_col_name] << std::endl;
}
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
//Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
<section xml:id="sec-editable-cells-example">
<title>Celdas editables</title>
<para>Este ejemplo es idéntico al del <classname>ListStore</classname>, pero usa <methodname>TreeView::append_column_editable()</methodname> en lugar de <methodname>TreeView::append_column()</methodname>.</para>
<figure xml:id="figure-treeview-editablecells">
<title>TreeView: celdas editables</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/treeview_editablecells.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/treeview/editable_cells/">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
virtual ~ExampleWindow();
protected:
//Signal handlers:
void on_button_quit();
void treeviewcolumn_validated_on_cell_data(Gtk::CellRenderer* renderer, const Gtk::TreeModel::const_iterator& iter);
void cellrenderer_validated_on_editing_started(Gtk::CellEditable* cell_editable, const Glib::ustring& path);
void cellrenderer_validated_on_edited(const Glib::ustring& path_string, const Glib::ustring& new_text);
void on_message_response(int response_id, Gtk::MessageDialog* dialog);
//Tree model columns:
class ModelColumns : public Gtk::TreeModel::ColumnRecord
{
public:
ModelColumns()
{ add(m_col_id); add(m_col_name); add(m_col_foo); add(m_col_number); add(m_col_number_validated); }
Gtk::TreeModelColumn<unsigned int> m_col_id;
Gtk::TreeModelColumn<Glib::ustring> m_col_name;
Gtk::TreeModelColumn<bool> m_col_foo;
Gtk::TreeModelColumn<int> m_col_number;
Gtk::TreeModelColumn<int> m_col_number_validated;
};
ModelColumns m_Columns;
//Child widgets:
Gtk::Box m_VBox;
Gtk::ScrolledWindow m_ScrolledWindow;
Gtk::TreeView m_TreeView;
Glib::RefPtr<Gtk::ListStore> m_refTreeModel;
Gtk::Box m_ButtonBox;
Gtk::Button m_Button_Quit;
//For the validated column:
//You could also use a CellRendererSpin or a CellRendererProgress:
Gtk::CellRendererText m_cellrenderer_validated;
Gtk::TreeView::Column m_treeviewcolumn_validated;
bool m_validate_retry;
Glib::ustring m_invalid_text_for_retry;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include <iostream>
#include <cstdio>
#include <cstdlib>
#include "examplewindow.h"
using std::sprintf;
using std::strtol;
ExampleWindow::ExampleWindow()
: m_VBox(Gtk::Orientation::VERTICAL),
m_Button_Quit("Quit"),
m_validate_retry(false)
{
set_title("Gtk::TreeView Editable Cells example");
set_default_size(400, 200);
m_VBox.set_margin(5);
set_child(m_VBox);
//Add the TreeView, inside a ScrolledWindow, with the button underneath:
m_ScrolledWindow.set_child(m_TreeView);
//Only show the scrollbars when they are necessary:
m_ScrolledWindow.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
m_ScrolledWindow.set_expand();
m_VBox.append(m_ScrolledWindow);
m_VBox.append(m_ButtonBox);
m_ButtonBox.append(m_Button_Quit);
m_ButtonBox.set_margin(5);
m_Button_Quit.set_hexpand(true);
m_Button_Quit.set_halign(Gtk::Align::END);
m_Button_Quit.signal_clicked().connect( sigc::mem_fun(*this,
&ExampleWindow::on_button_quit) );
//Create the Tree model:
m_refTreeModel = Gtk::ListStore::create(m_Columns);
m_TreeView.set_model(m_refTreeModel);
//Fill the TreeView's model
auto row = *(m_refTreeModel->append());
row[m_Columns.m_col_id] = 1;
row[m_Columns.m_col_name] = "Billy Bob";
row[m_Columns.m_col_foo] = true;
row[m_Columns.m_col_number] = 10;
row = *(m_refTreeModel->append());
row[m_Columns.m_col_id] = 2;
row[m_Columns.m_col_name] = "Joey Jojo";
row[m_Columns.m_col_foo] = true;
row[m_Columns.m_col_number] = 20;
row = *(m_refTreeModel->append());
row[m_Columns.m_col_id] = 3;
row[m_Columns.m_col_name] = "Rob McRoberts";
row[m_Columns.m_col_foo] = false;
row[m_Columns.m_col_number] = 30;
//Add the TreeView's view columns:
//We use the *_editable convenience methods for most of these,
//because the default functionality is enough:
m_TreeView.append_column_editable("ID", m_Columns.m_col_id);
m_TreeView.append_column_editable("Name", m_Columns.m_col_name);
m_TreeView.append_column_editable("foo", m_Columns.m_col_foo);
m_TreeView.append_column_numeric_editable("Number", m_Columns.m_col_number,
"%010d");
//For this column, we create the CellRenderer ourselves, and connect our own
//signal handlers, so that we can validate the data that the user enters, and
//control how it is displayed.
m_treeviewcolumn_validated.set_title("validated (<10)");
m_treeviewcolumn_validated.pack_start(m_cellrenderer_validated);
m_TreeView.append_column(m_treeviewcolumn_validated);
//Tell the view column how to render the model values:
m_treeviewcolumn_validated.set_cell_data_func(m_cellrenderer_validated,
sigc::mem_fun(*this,
&ExampleWindow::treeviewcolumn_validated_on_cell_data) );
//Make the CellRenderer editable, and handle its editing signals:
m_cellrenderer_validated.property_editable() = true;
m_cellrenderer_validated.signal_editing_started().connect(
sigc::mem_fun(*this,
&ExampleWindow::cellrenderer_validated_on_editing_started) );
m_cellrenderer_validated.signal_edited().connect( sigc::mem_fun(*this,
&ExampleWindow::cellrenderer_validated_on_edited) );
//If this was a CellRendererSpin then you would have to set the adjustment:
//m_cellrenderer_validated.property_adjustment() = m_spin_adjustment;
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::on_button_quit()
{
set_visible(false);
}
void ExampleWindow::treeviewcolumn_validated_on_cell_data(
Gtk::CellRenderer* /* renderer */,
const Gtk::TreeModel::const_iterator& iter)
{
//Get the value from the model and show it appropriately in the view:
if(iter)
{
const auto row = *iter;
int model_value = row[m_Columns.m_col_number_validated];
//This is just an example.
//In this case, it would be easier to use append_column_editable() or
//append_column_numeric_editable()
char buffer[32];
sprintf(buffer, "%d", model_value);
auto view_text = buffer;
m_cellrenderer_validated.property_text() = view_text;
}
}
void ExampleWindow::cellrenderer_validated_on_editing_started(
Gtk::CellEditable* cell_editable, const Glib::ustring& /* path */)
{
//Start editing with previously-entered (but invalid) text,
//if we are allowing the user to correct some invalid data.
if(m_validate_retry)
{
//This is the CellEditable inside the CellRenderer.
auto celleditable_validated = cell_editable;
//It's usually an Entry, at least for a CellRendererText:
auto pEntry = dynamic_cast<Gtk::Entry*>(celleditable_validated);
if(pEntry)
{
pEntry->set_text(m_invalid_text_for_retry);
m_validate_retry = false;
m_invalid_text_for_retry.clear();
}
}
}
void ExampleWindow::cellrenderer_validated_on_edited(
const Glib::ustring& path_string,
const Glib::ustring& new_text)
{
Gtk::TreePath path(path_string);
//Convert the inputed text to an integer, as needed by our model column:
char* pchEnd = nullptr;
int new_value = strtol(new_text.c_str(), &pchEnd, 10);
if(new_value >= 10)
{
//Prevent entry of numbers higher than 10.
//Tell the user:
auto dialog = new Gtk::MessageDialog(*this,
"The number must be less than 10. Please try again.",
false, Gtk::MessageType::ERROR, Gtk::ButtonsType::OK, true /* modal */);
dialog->signal_response().connect(sigc::bind(
sigc::mem_fun(*this, &ExampleWindow::on_message_response), dialog));
dialog->set_visible(true);
//Start editing again, with the bad text, so that the user can correct it.
//A real application should probably allow the user to revert to the
//previous text.
//Set the text to be used in the start_editing signal handler:
m_invalid_text_for_retry = new_text;
m_validate_retry = true;
//Start editing again, when the message dialog has been closed:
m_TreeView.set_cursor(path, m_treeviewcolumn_validated,
m_cellrenderer_validated, true /* start_editing */);
}
else
{
//Get the row from the path:
auto iter = m_refTreeModel->get_iter(path);
if(iter)
{
auto row = *iter;
//Put the new value in the model:
row[m_Columns.m_col_number_validated] = new_value;
}
}
}
void ExampleWindow::on_message_response(int /* response_id */, Gtk::MessageDialog* dialog)
{
delete dialog;
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
//Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
<section xml:id="treeview-dnd-example">
<title>Arrastrar y soltar</title>
<para>Este ejemplo es muy parecido al ejemplo del <classname>TreeStore</classname>, pero tiene dos columnas adicionales que indican si la fila se puede arrastrar, y si puede recibir filas arrastradas y soltadas. Usa un <classname>Gtk::TreeStore</classname> derivado, que sobrecarga las funciones virtuales como se describe en la sección <link linkend="sec-treeview-draganddrop">TreeView: arrastrar y soltar</link>.</para>
<figure xml:id="figure-treeview-draganddrop">
<title>TreeView: arrastrar y soltar</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/treeview_draganddrop.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/treeview/drag_and_drop/">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm.h>
#include "treemodel_dnd.h"
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
virtual ~ExampleWindow();
protected:
//Signal handlers:
void on_button_quit();
//Child widgets:
Gtk::Box m_VBox;
Gtk::ScrolledWindow m_ScrolledWindow;
Gtk::TreeView m_TreeView;
Glib::RefPtr<TreeModel_Dnd> m_refTreeModel;
Gtk::Box m_ButtonBox;
Gtk::Button m_Button_Quit;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>treemodel_dnd.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLE_TREEMODEL_DND_H
#define GTKMM_EXAMPLE_TREEMODEL_DND_H
#include <gtkmm.h>
class TreeModel_Dnd : public Gtk::TreeStore
{
protected:
TreeModel_Dnd();
public:
//Tree model columns:
class ModelColumns : public Gtk::TreeModel::ColumnRecord
{
public:
ModelColumns()
{ add(m_col_id); add(m_col_name); add(m_col_draggable); add(m_col_receivesdrags); }
Gtk::TreeModelColumn<int> m_col_id;
Gtk::TreeModelColumn<Glib::ustring> m_col_name;
Gtk::TreeModelColumn<bool> m_col_draggable;
Gtk::TreeModelColumn<bool> m_col_receivesdrags;
};
ModelColumns m_Columns;
static Glib::RefPtr<TreeModel_Dnd> create();
protected:
//Overridden virtual functions:
bool row_draggable_vfunc(const Gtk::TreeModel::Path& path) const override;
bool row_drop_possible_vfunc(const Gtk::TreeModel::Path& dest, const Glib::ValueBase& value) const override;
};
#endif //GTKMM_EXAMPLE_TREEMODEL_DND_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include <iostream>
#include "examplewindow.h"
ExampleWindow::ExampleWindow()
: m_VBox(Gtk::Orientation::VERTICAL),
m_Button_Quit("_Quit", true)
{
set_title("Gtk::TreeView (Drag and Drop) example");
set_default_size(400, 200);
m_VBox.set_margin(5);
set_child(m_VBox);
//Add the TreeView, inside a ScrolledWindow, with the button underneath:
m_ScrolledWindow.set_child(m_TreeView);
//Only show the scrollbars when they are necessary:
m_ScrolledWindow.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
m_ScrolledWindow.set_expand();
m_VBox.append(m_ScrolledWindow);
m_VBox.append(m_ButtonBox);
m_ButtonBox.append(m_Button_Quit);
m_ButtonBox.set_margin(5);
m_Button_Quit.set_hexpand(true);
m_Button_Quit.set_halign(Gtk::Align::END);
m_Button_Quit.signal_clicked().connect(sigc::mem_fun(*this,
&ExampleWindow::on_button_quit) );
//Create the Tree model:
//Use our derived model, which overrides some Gtk::TreeDragDest and
//Gtk::TreeDragSource virtual functions:
//The columns are declared in the overridden TreeModel.
m_refTreeModel = TreeModel_Dnd::create();
m_TreeView.set_model(m_refTreeModel);
//Enable Drag-and-Drop of TreeView rows:
//See also the derived TreeModel's *_vfunc overrides.
m_TreeView.enable_model_drag_source();
m_TreeView.enable_model_drag_dest();
//Fill the TreeView's model
auto row = *(m_refTreeModel->append());
row[m_refTreeModel->m_Columns.m_col_id] = 1;
row[m_refTreeModel->m_Columns.m_col_name] = "Billy Bob";
row[m_refTreeModel->m_Columns.m_col_draggable] = true;
row[m_refTreeModel->m_Columns.m_col_receivesdrags] = true;
auto childrow = *(m_refTreeModel->append(row.children()));
childrow[m_refTreeModel->m_Columns.m_col_id] = 11;
childrow[m_refTreeModel->m_Columns.m_col_name] = "Billy Bob Junior";
childrow[m_refTreeModel->m_Columns.m_col_draggable] = true;
childrow[m_refTreeModel->m_Columns.m_col_receivesdrags] = true;
childrow = *(m_refTreeModel->append(row.children()));
childrow[m_refTreeModel->m_Columns.m_col_id] = 12;
childrow[m_refTreeModel->m_Columns.m_col_name] = "Sue Bob";
childrow[m_refTreeModel->m_Columns.m_col_draggable] = true;
childrow[m_refTreeModel->m_Columns.m_col_receivesdrags] = true;
row = *(m_refTreeModel->append());
row[m_refTreeModel->m_Columns.m_col_id] = 2;
row[m_refTreeModel->m_Columns.m_col_name] = "Joey Jojo";
row[m_refTreeModel->m_Columns.m_col_draggable] = true;
row[m_refTreeModel->m_Columns.m_col_receivesdrags] = true;
row = *(m_refTreeModel->append());
row[m_refTreeModel->m_Columns.m_col_id] = 3;
row[m_refTreeModel->m_Columns.m_col_name] = "Rob McRoberts";
row[m_refTreeModel->m_Columns.m_col_draggable] = true;
row[m_refTreeModel->m_Columns.m_col_receivesdrags] = true;
childrow = *(m_refTreeModel->append(row.children()));
childrow[m_refTreeModel->m_Columns.m_col_id] = 31;
childrow[m_refTreeModel->m_Columns.m_col_name] = "Xavier McRoberts";
childrow[m_refTreeModel->m_Columns.m_col_draggable] = true;
childrow[m_refTreeModel->m_Columns.m_col_receivesdrags] = true;
//Add the TreeView's view columns:
m_TreeView.append_column("ID", m_refTreeModel->m_Columns.m_col_id);
m_TreeView.append_column("Name", m_refTreeModel->m_Columns.m_col_name);
m_TreeView.append_column_editable("Draggable",
m_refTreeModel->m_Columns.m_col_draggable);
m_TreeView.append_column_editable("Receives Drags",
m_refTreeModel->m_Columns.m_col_receivesdrags);
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::on_button_quit()
{
set_visible(false);
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
//Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>treemodel_dnd.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "treemodel_dnd.h"
#include <iostream>
TreeModel_Dnd::TreeModel_Dnd()
{
//We can't just call Gtk::TreeModel(m_Columns) in the initializer list
//because m_Columns does not exist when the base class constructor runs.
//And we can't have a static m_Columns instance, because that would be
//instantiated before the gtkmm type system.
//So, we use this method, which should only be used just after creation:
set_column_types(m_Columns);
}
Glib::RefPtr<TreeModel_Dnd> TreeModel_Dnd::create()
{
return Glib::make_refptr_for_instance<TreeModel_Dnd>( new TreeModel_Dnd() );
}
bool
TreeModel_Dnd::row_draggable_vfunc(const Gtk::TreeModel::Path& path) const
{
// Make the value of the "draggable" column determine whether this row can
// be dragged:
const const_iterator iter = get_iter(path);
if(iter)
{
ConstRow row = *iter;
bool is_draggable = row[m_Columns.m_col_draggable];
return is_draggable;
}
return Gtk::TreeStore::row_draggable_vfunc(path);
}
bool
TreeModel_Dnd::row_drop_possible_vfunc(const Gtk::TreeModel::Path& dest,
const Glib::ValueBase& value) const
{
//Make the value of the "receives drags" column determine whether a row can be
//dragged into it:
//dest is the path that the row would have after it has been dropped:
//But in this case we are more interested in the parent row:
auto dest_parent = dest;
bool dest_is_not_top_level = dest_parent.up();
if(!dest_is_not_top_level || dest_parent.empty())
{
//The user wants to move something to the top-level.
//Let's always allow that.
}
else
{
//Get an iterator for the row at this path:
const const_iterator iter_dest_parent = get_iter(dest_parent);
if(iter_dest_parent)
{
ConstRow row = *iter_dest_parent;
bool receives_drags = row[m_Columns.m_col_receivesdrags];
return receives_drags;
}
}
// You could also examine the row being dragged (via value)
// if you must look at both rows to see whether a drop should be allowed.
// You could use
// Glib::RefPtr<const Gtk::TreeModel> model_dragged_row;
// Gtk::TreeModel::Path path_dragged_row;
// Gtk::TreeModel::Path::get_row_drag_data(value,
// model_dragged_row, path_dragged_row);
return Gtk::TreeStore::row_drop_possible_vfunc(dest, value);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
<section xml:id="treeview-popup-menu-example">
<title>Menú contextual emergente</title>
<para xml:lang="en">
This example is much like the <classname>ListStore</classname> example, but
derives a custom <classname>TreeView</classname> to encapsulate the tree model
code and popup menu code in our derived class. See the
<link linkend="sec-treeview-contextmenu">TreeView Popup Context Menu</link> section.
</para>
<figure xml:id="figure-treeview-popup">
<title>TreeView: menú de contexto emergente</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/treeview_popup.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/treeview/popup/">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm.h>
#include "treeview_withpopup.h"
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
virtual ~ExampleWindow();
protected:
//Signal handlers:
void on_button_quit();
//Child widgets:
Gtk::Box m_VBox;
Gtk::ScrolledWindow m_ScrolledWindow;
TreeView_WithPopup m_TreeView;
Gtk::Box m_ButtonBox;
Gtk::Button m_Button_Quit;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>treeview_withpopup.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLE_TREEVIEW_WITHPOPUP_H
#define GTKMM_EXAMPLE_TREEVIEW_WITHPOPUP_H
#include <gtkmm.h>
class TreeView_WithPopup : public Gtk::TreeView
{
public:
TreeView_WithPopup();
virtual ~TreeView_WithPopup();
protected:
// Signal handler for showing popup menu:
void on_popup_button_pressed(int n_press, double x, double y);
//Signal handler for popup menu items:
void on_menu_file_popup_generic();
//Tree model columns:
class ModelColumns : public Gtk::TreeModel::ColumnRecord
{
public:
ModelColumns()
{ add(m_col_id); add(m_col_name); }
Gtk::TreeModelColumn<unsigned int> m_col_id;
Gtk::TreeModelColumn<Glib::ustring> m_col_name;
};
ModelColumns m_Columns;
//The Tree model:
Glib::RefPtr<Gtk::ListStore> m_refTreeModel;
Gtk::PopoverMenu m_MenuPopup;
};
#endif //GTKMM_EXAMPLE_TREEVIEW_WITHPOPUP_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include <iostream>
#include "examplewindow.h"
ExampleWindow::ExampleWindow()
: m_VBox(Gtk::Orientation::VERTICAL),
m_Button_Quit("Quit")
{
set_title("Gtk::TreeView (ListStore) example");
set_default_size(400, 200);
m_VBox.set_margin(5);
set_child(m_VBox);
//Add the TreeView, inside a ScrolledWindow, with the button underneath:
m_ScrolledWindow.set_child(m_TreeView);
//Only show the scrollbars when they are necessary:
m_ScrolledWindow.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
m_ScrolledWindow.set_expand();
m_VBox.append(m_ScrolledWindow);
m_VBox.append(m_ButtonBox);
m_ButtonBox.append(m_Button_Quit);
m_ButtonBox.set_margin(5);
m_Button_Quit.set_hexpand(true);
m_Button_Quit.set_halign(Gtk::Align::END);
m_Button_Quit.signal_clicked().connect( sigc::mem_fun(*this,
&ExampleWindow::on_button_quit) );
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::on_button_quit()
{
set_visible(false);
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
//Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>treeview_withpopup.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "treeview_withpopup.h"
#include <iostream>
TreeView_WithPopup::TreeView_WithPopup()
{
//Create the Tree model:
m_refTreeModel = Gtk::ListStore::create(m_Columns);
set_model(m_refTreeModel);
//Fill the TreeView's model
auto row = *(m_refTreeModel->append());
row[m_Columns.m_col_id] = 1;
row[m_Columns.m_col_name] = "right-click on this";
row = *(m_refTreeModel->append());
row[m_Columns.m_col_id] = 2;
row[m_Columns.m_col_name] = "or this";
row = *(m_refTreeModel->append());
row[m_Columns.m_col_id] = 3;
row[m_Columns.m_col_name] = "or this, for a popup context menu";
//Add the TreeView's view columns:
append_column("ID", m_Columns.m_col_id);
append_column("Name", m_Columns.m_col_name);
// Catch button press events:
auto refGesture = Gtk::GestureClick::create();
refGesture->set_button(GDK_BUTTON_SECONDARY);
refGesture->signal_pressed().connect(
sigc::mem_fun(*this, &TreeView_WithPopup::on_popup_button_pressed));
add_controller(refGesture);
// Fill popup menu:
auto gmenu = Gio::Menu::create();
gmenu->append("_Edit", "popup.edit");
gmenu->append("_Process", "popup.process");
gmenu->append("_Remove", "popup.remove");
m_MenuPopup.set_parent(*this);
m_MenuPopup.set_menu_model(gmenu);
m_MenuPopup.set_has_arrow(false);
// Create actions:
auto refActionGroup = Gio::SimpleActionGroup::create();
refActionGroup->add_action("edit",
sigc::mem_fun(*this, &TreeView_WithPopup::on_menu_file_popup_generic));
refActionGroup->add_action("process",
sigc::mem_fun(*this, &TreeView_WithPopup::on_menu_file_popup_generic));
refActionGroup->add_action("remove",
sigc::mem_fun(*this, &TreeView_WithPopup::on_menu_file_popup_generic));
insert_action_group("popup", refActionGroup);
}
TreeView_WithPopup::~TreeView_WithPopup()
{
}
void TreeView_WithPopup::on_popup_button_pressed(int /* n_press */, double x, double y)
{
const Gdk::Rectangle rect(x, y, 1, 1);
m_MenuPopup.set_pointing_to(rect);
m_MenuPopup.popup();
}
void TreeView_WithPopup::on_menu_file_popup_generic()
{
std::cout << "A popup menu item was selected." << std::endl;
auto refSelection = get_selection();
if(refSelection)
{
auto iter = refSelection->get_selected();
if(iter)
{
int id = (*iter)[m_Columns.m_col_id];
std::cout << " Selected ID=" << id << std::endl;
}
}
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</section>
</chapter>
<chapter xml:id="chapter-dropdown">
<title xml:lang="en">The DropDown Widget</title>
<para xml:lang="en">The <classname>DropDown</classname> widget is an alternative to the deprecated
<classname>ComboBox</classname>. It uses list models instead of tree models,
and the content is displayed using widgets instead of cell renderers.
</para>
<para xml:lang="en">The <classname>DropDown</classname> widget offers a list of choices in a
dropdown menu. If appropriate, it can show extra information about each item,
such as text, a picture, or a check button. The <classname>DropDown</classname> widget
can optionally have an <classname>Entry</classname> in the dropdown menu, allowing
the user to search in a long list.
</para>
<para xml:lang="en">The list is provided via a <classname>Gio::ListModel</classname>, and data
from this model is added to the <classname>DropDown</classname>'s view with
signal handlers connected to a <classname>SignalListItemFactory</classname>.
This provides flexibility, but the <classname>StringList</classname> class provides
a simpler text-based specialization in case that flexibility is not required.
</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1DropDown.html">Reference</link></para>
<section xml:id="sec-dropdown-model">
<title>El modelo</title>
<para xml:lang="en">The model for a <classname>DropDown</classname> can be defined and filled
exactly as for a <classname>ListView</classname> or a <classname>ColumnView</classname>.
It must be a subclass of <classname>Glib::Object</classname>.
For instance, you might have a <classname>DropDown</classname> with one integer
and one text column, like so:
</para>
<programlisting xml:lang="en"><code><![CDATA[class ModelColumns : public Glib::Object
{
public:
int m_col_id;
Glib::ustring m_col_name;
static Glib::RefPtr<ModelColumns> create(
int col_id, const Glib::ustring& col_name)
{
return Glib::make_refptr_for_instance<ModelColumns>(
new ModelColumns(col_id, col_name));
}
protected:
ModelColumns(int col_id, const Glib::ustring& col_name)
: m_col_id(col_id), m_col_name(col_name)
{}
};
Glib::RefPtr<Gio::ListStore<ModelColumns>> m_ListStore;
]]></code></programlisting>
<para xml:lang="en">After appending rows to this model, you should provide the model to the
<classname>DropDown</classname> with the <methodname>set_model()</methodname> method.
Unless you use the <classname>StringList</classname> model, you also need to set
a <classname>ListItemFactory</classname> with <methodname>set_factory()</methodname>.
If you want the items in the dropdown menu to look different from the item
in the <classname>DropDown</classname> widget, you also need to set a separate
<classname>ListItemFactory</classname> with <methodname>set_list_factory()</methodname>.
</para>
</section>
<section xml:id="sec-dropdown-get">
<title xml:lang="en">The selected item</title>
<para xml:lang="en">To discover what item, if any, the user has selected from the <classname>DropDown</classname>,
call <methodname>DropDown::get_selected()</methodname>. This returns an
<type>unsigned int</type> that you can use to get the selected data from the model.
For instance, you might read an integer ID value from the model, even though you
have chosen only to show the human-readable description in the
<classname>DropDown</classname>. For instance:
</para>
<programlisting xml:lang="en"><code><![CDATA[unsigned int sel = m_DropDown.get_selected();
if (sel != GTK_INVALID_LIST_POSITION)
{
// Get the data for the selected row, using our knowledge of the list model:
auto id = m_ListStore->get_item(sel).m_col_id;
set_some_id_chosen(id); // Your own function.
}
else
set_nothing_chosen(); // Your own function.
]]></code></programlisting>
</section>
<section xml:id="sec-dropdown-changes">
<title>Responder a los cambios</title>
<para xml:lang="en">You might need to react to every change of selection in the <classname>DropDown</classname>,
for instance to update other widgets. To do so, you should connect to
<methodname>property_selected().signal_changed()</methodname>. For instance:
</para>
<programlisting xml:lang="en"><code><![CDATA[m_DropDown.property_selected().signal_changed().connect(
sigc::mem_fun(*this, &ExampleWindow::on_dropdown_changed));
]]></code></programlisting>
</section>
<section xml:id="sec-dropdown-simple">
<title xml:lang="en">Simple String Example</title>
<figure xml:id="figure-dropdown-string">
<title xml:lang="en">Simple DropDown</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/dropdown_string.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/dropdown/string">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm/window.h>
#include <gtkmm/dropdown.h>
#include <gtkmm/stringlist.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
~ExampleWindow() override;
protected:
// Signal handler:
void on_dropdown_changed();
// Child widget:
Gtk::DropDown m_DropDown;
Glib::RefPtr<Gtk::StringList> m_StringList;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <iostream>
ExampleWindow::ExampleWindow()
{
set_title("DropDown example");
set_child(m_DropDown);
// Fill the dropdown:
const std::vector<Glib::ustring> strings{
"1 minute", "2 minutes", "5 minutes", "20 minutes"
};
m_StringList = Gtk::StringList::create(strings);
m_DropDown.set_model(m_StringList);
m_DropDown.set_selected(0);
// Connect signal handler:
m_DropDown.property_selected().signal_changed().connect(
sigc::mem_fun(*this, &ExampleWindow::on_dropdown_changed));
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::on_dropdown_changed()
{
const auto selected = m_DropDown.get_selected();
std::cout << "DropDown changed: Row=" << selected
<< ", String=" << m_StringList->get_string(selected) << std::endl;
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char* argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
// Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
<section xml:id="sec-dropdown-search">
<title xml:lang="en">Examples with a Search Entry</title>
<para xml:lang="en">The dropdown menu may contain an <classname>Entry</classname> that allows
to search for items in the list. Call <methodname>set_enable_search()</methodname>
and <methodname>set_expression()</methodname>. For instance:
</para>
<programlisting xml:lang="en"><code><![CDATA[m_DropDown.set_enable_search(true);
auto expression = Gtk::ClosureExpression<Glib::ustring>::create(
sigc::mem_fun(*this, &ExampleWindow::get_col_name));
m_DropDown.set_expression(expression);
//-------
Glib::ustring ExampleWindow::get_col_name(const Glib::RefPtr<Glib::ObjectBase>& item)
{
const auto col = std::dynamic_pointer_cast<ModelColumns>(item);
return col ? col->m_col_name : "";
}
]]></code></programlisting>
<section xml:id="sec-dropdown-search-string">
<title xml:lang="en">String Example</title>
<figure xml:id="figure-dropdown-search-string">
<title xml:lang="en">Search String</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/dropdown_search_string.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/dropdown/search_string">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm/window.h>
#include <gtkmm/dropdown.h>
#include <gtkmm/stringlist.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
~ExampleWindow() override;
protected:
// Signal handler:
void on_dropdown_changed();
// Child widget:
Gtk::DropDown m_DropDown;
Glib::RefPtr<Gtk::StringList> m_StringList;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><->Glib::ustring
{
// An item in a StringList is a StringObject.
const auto string_object = std::dynamic_pointer_cast<Gtk::StringObject>(item);
return string_object ? string_object->get_string() : "";
});
m_DropDown.set_expression(expression);
// Connect signal handler:
m_DropDown.property_selected().signal_changed().connect(
sigc::mem_fun(*this, &ExampleWindow::on_dropdown_changed));
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::on_dropdown_changed()
{
const auto selected = m_DropDown.get_selected();
std::cout << "DropDown changed: Row=" << selected
<< ", String=" << m_StringList->get_string(selected) << std::endl;
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char* argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
// Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
<section xml:id="sec-dropdown-search-font">
<title xml:lang="en">Font Example</title>
<para xml:lang="en">This example uses a <classname>Pango::FontMap</classname> as its model.
This is possible because <classname>Pango::FontMap</classname> implements
the <classname>Gio::ListModel</classname> interface. Of course you can use a
<classname>FontDialogButton</classname> instead.
</para>
<figure xml:id="figure-dropdown-search-font">
<title xml:lang="en">Search Font</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/dropdown_search_font.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/dropdown/search_font">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
~ExampleWindow() override;
protected:
// Signal handler:
void on_dropdown_changed();
Glib::ustring get_family_name(const Glib::RefPtr<Glib::ObjectBase>& item);
// Child widget:
Gtk::DropDown m_DropDown;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <pangomm/cairofontmap.h>
#include <iostream>
//#include <typeinfo>
ExampleWindow::ExampleWindow()
{
set_title("Searchable DropDown example");
// Add the DropDown to the window.
set_child(m_DropDown);
// Show a search entry.
m_DropDown.set_enable_search(true);
auto expression = Gtk::ClosureExpression<Glib::ustring>::create(
sigc::mem_fun(*this, &ExampleWindow::get_family_name));
m_DropDown.set_expression(expression);
// Pango::FontMap is a Gio::ListModel.
auto model = Pango::CairoFontMap::get_default();
m_DropDown.set_model(model);
m_DropDown.set_selected(0);
// Connect signal handler.
m_DropDown.property_selected().signal_changed().connect(
sigc::mem_fun(*this, &ExampleWindow::on_dropdown_changed));
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::on_dropdown_changed()
{
const auto selected = m_DropDown.get_selected();
const auto item = m_DropDown.get_selected_item();
std::cout << "DropDown changed: Row=" << selected
<< ", Font=" << get_family_name(item) << std::endl;
// Pango::FontMap is often a list of items of type Pango::FontFamily.
// From the description of Pango::CairoFontMap: "The actual type of the font map
// will depend on the particular font technology Cairo was compiled to use."
// If get_family_name() does not return a family name, you can add
// #include <typeinfo> and query the type of the items with:
// std::cout << typeid(*item.get()).name() << std::endl;
}
Glib::ustring ExampleWindow::get_family_name(const Glib::RefPtr<Glib::ObjectBase>& item)
{
auto family = std::dynamic_pointer_cast<Pango::FontFamily>(item);
return family ? family->get_name() : "";
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char* argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
// Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</section>
<section xml:id="sec-dropdown-complex">
<title xml:lang="en">Complex Example</title>
<para xml:lang="en">This is a more complex example with two <classname>SignalListItemFactory</classname>
objects and their signal handlers. This example would be simpler without the
checkmark in the dropdown menu.
</para>
<figure xml:id="figure-dropdown-complex">
<title xml:lang="en">Search Font</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/dropdown_complex.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/dropdown/complex">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
~ExampleWindow() override;
protected:
// Signal handlers:
void on_setup_selected_item(const Glib::RefPtr<Gtk::ListItem>& list_item);
void on_bind_selected_item(const Glib::RefPtr<Gtk::ListItem>& list_item);
void on_setup_list_item(const Glib::RefPtr<Gtk::ListItem>& list_item);
void on_bind_list_item(const Glib::RefPtr<Gtk::ListItem>& list_item);
void on_unbind_list_item(const Glib::RefPtr<Gtk::ListItem>& list_item);
void on_selected_item_changed(const Glib::RefPtr<Gtk::ListItem>& list_item);
void on_dropdown_changed();
void create_model();
void liststore_add_item(const Glib::ustring& title, const Glib::ustring& icon,
const Glib::ustring& description);
// A Gio::ListStore item.
class ModelColumns : public Glib::Object
{
public:
Glib::ustring m_title;
Glib::ustring m_icon;
Glib::ustring m_description;
static Glib::RefPtr<ModelColumns> create(const Glib::ustring& title,
const Glib::ustring& icon, const Glib::ustring& description)
{
return Glib::make_refptr_for_instance<ModelColumns>(
new ModelColumns(title, icon, description));
}
protected:
ModelColumns(const Glib::ustring& title, const Glib::ustring& icon,
const Glib::ustring& description)
: m_title(title), m_icon(icon), m_description(description)
{}
}; // ModelColumns
// Child widget:
Gtk::DropDown m_DropDown;
Glib::RefPtr<Gio::ListStore<ModelColumns>> m_ListStore;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <iostream>
ExampleWindow::ExampleWindow()
{
set_title("DropDown example");
// Add the DropDown to the window.
set_child(m_DropDown);
// Factory for presenting the selected item.
auto factory = Gtk::SignalListItemFactory::create();
factory->signal_setup().connect(
sigc::mem_fun(*this, &ExampleWindow::on_setup_selected_item));
factory->signal_bind().connect(
sigc::mem_fun(*this, &ExampleWindow::on_bind_selected_item));
m_DropDown.set_factory(factory);
// Factory for presenting the items in the dropdown list.
factory = Gtk::SignalListItemFactory::create();
factory->signal_setup().connect(
sigc::mem_fun(*this, &ExampleWindow::on_setup_list_item));
factory->signal_bind().connect(
sigc::mem_fun(*this, &ExampleWindow::on_bind_list_item));
factory->signal_unbind().connect(
sigc::mem_fun(*this, &ExampleWindow::on_unbind_list_item));
m_DropDown.set_list_factory(factory);
// Create the model and fill it.
create_model();
m_DropDown.set_model(m_ListStore);
m_DropDown.set_selected(0);
// Connect signal handler:
m_DropDown.property_selected().signal_changed().connect(
sigc::mem_fun(*this, &ExampleWindow::on_dropdown_changed));
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::on_dropdown_changed()
{
const auto selected = m_DropDown.get_selected();
std::cout << "DropDown changed: Row=" << selected
<< ", Title=" << m_ListStore->get_item(selected)->m_title << std::endl;
}
void ExampleWindow::create_model()
{
m_ListStore = Gio::ListStore<ModelColumns>::create();
liststore_add_item("Digital Output", "audio-card-symbolic", "Built-in Audio");
liststore_add_item("Headphones", "audio-headphones-symbolic", "Built-in Audio");
liststore_add_item("Digital Output", "audio-card-symbolic", "Thinkpad Tunderbolt 3 Dock USB Audio");
liststore_add_item("Analog Output", "audio-card-symbolic", "Thinkpad Tunderbolt 3 Dock USB Audio");
}
void ExampleWindow::liststore_add_item(const Glib::ustring& title,
const Glib::ustring& icon, const Glib::ustring& description)
{
m_ListStore->append(ModelColumns::create(title, icon, description));
}
void ExampleWindow::on_setup_selected_item(const Glib::RefPtr<Gtk::ListItem>& list_item)
{
auto box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 10);
box->append(*Gtk::make_managed<Gtk::Image>());
box->append(*Gtk::make_managed<Gtk::Label>());
list_item->set_child(*box);
}
void ExampleWindow::on_bind_selected_item(const Glib::RefPtr<Gtk::ListItem>& list_item)
{
auto col = std::dynamic_pointer_cast<ModelColumns>(list_item->get_item());
if (!col)
return;
auto box = dynamic_cast<Gtk::Box*>(list_item->get_child());
if (!box)
return;
auto image = dynamic_cast<Gtk::Image*>(box->get_first_child());
if (!image)
return;
image->set_from_icon_name(col->m_icon);
auto label = dynamic_cast<Gtk::Label*>(image->get_next_sibling());
if (!label)
return;
label->set_text(col->m_title);
}
void ExampleWindow::on_setup_list_item(const Glib::RefPtr<Gtk::ListItem>& list_item)
{
auto hbox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 10);
auto vbox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 2);
hbox->append(*Gtk::make_managed<Gtk::Image>());
hbox->append(*vbox);
auto title = Gtk::make_managed<Gtk::Label>();
title->set_xalign(0.0);
vbox->append(*title);
auto description = Gtk::make_managed<Gtk::Label>();
description->set_xalign(0.0);
description->add_css_class("dim-label");
vbox->append(*description);
auto checkmark = Gtk::make_managed<Gtk::Image>();
checkmark->set_from_icon_name("object-select-symbolic");
checkmark->set_visible(false);
hbox->append(*checkmark);
list_item->set_child(*hbox);
}
void ExampleWindow::on_bind_list_item(const Glib::RefPtr<Gtk::ListItem>& list_item)
{
auto col = std::dynamic_pointer_cast<ModelColumns>(list_item->get_item());
if (!col)
return;
auto hbox = dynamic_cast<Gtk::Box*>(list_item->get_child());
if (!hbox)
return;
auto image = dynamic_cast<Gtk::Image*>(hbox->get_first_child());
if (!image)
return;
image->set_from_icon_name(col->m_icon);
auto vbox = dynamic_cast<Gtk::Box*>(image->get_next_sibling());
if (!vbox)
return;
auto title = dynamic_cast<Gtk::Label*>(vbox->get_first_child());
if (!title)
return;
title->set_text(col->m_title);
auto description = dynamic_cast<Gtk::Label*>(title->get_next_sibling());
if (!description)
return;
description->set_text(col->m_description);
auto connection = m_DropDown.property_selected_item().signal_changed().connect(
sigc::bind(sigc::mem_fun(*this, &ExampleWindow::on_selected_item_changed),
list_item));
list_item->set_data("connection", new sigc::connection(connection),
Glib::destroy_notify_delete<sigc::connection>);
on_selected_item_changed(list_item);
}
void ExampleWindow::on_unbind_list_item(const Glib::RefPtr<Gtk::ListItem>& list_item)
{
if (auto connection = static_cast<sigc::connection*>(list_item->get_data("connection")))
{
connection->disconnect();
list_item->set_data("connection", nullptr);
}
}
void ExampleWindow::on_selected_item_changed(const Glib::RefPtr<Gtk::ListItem>& list_item)
{
auto hbox = dynamic_cast<Gtk::Box*>(list_item->get_child());
if (!hbox)
return;
auto checkmark = dynamic_cast<Gtk::Image*>(hbox->get_last_child());
if (!checkmark)
return;
checkmark->set_visible(m_DropDown.get_selected_item() == list_item->get_item());
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char* argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
// Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</chapter>
<chapter xml:id="chapter-combobox">
<title>Cajas combinadas</title>
<note><para xml:lang="en"><classname>Gtk::ComboBox</classname> and <classname>Gtk::ComboBoxText</classname>
are deprecated since <application>gtkmm</application> 4.10. Use <classname>Gtk::DropDown</classname> in new code.
</para></note>
<para xml:lang="en">The <classname>ComboBox</classname> widget offers a list (or tree) of choices in a dropdown menu. If appropriate, it can show extra information about each item, such as text, a picture, a check button, or a progress bar. The <classname>ComboBox</classname> widget usually restricts the user to the available choices, but it can optionally have an <classname>Entry</classname>, allowing the user to enter arbitrary text if none of the available choices are suitable.
</para>
<para>Un <classname>TreeModel</classname> proporciona la lista, y las columnas de este modelo se añaden a la vista del «ComboBox» con el método <methodname>ComboBox::pack_start()</methodname>. Esto proporciona flexibilidad y seguridad de tipos en tiempo de compilación, pero la clase <classname>ComboBoxText</classname> proporciona una especialización más simple basada en texto en caso de que no se requiera la flexibilidad.</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1ComboBox.html">Reference</link></para>
<section xml:id="sec-combobox-model">
<title>El modelo</title>
<para>El modelo de una «ComboBox» puede definirse y llenarse exactamente como un <classname>TreeView</classname>. Por ejemplo, puede derivar una clase «ComboBox» con una columna de números enteros y otra de texto, así:</para>
<programlisting xml:lang="en"><code>class ModelColumns : public Gtk::TreeModel::ColumnRecord
{
public:
ModelColumns()
{ add(m_col_id); add(m_col_name); }
Gtk::TreeModelColumn<int> m_col_id;
Gtk::TreeModelColumn<Glib::ustring> m_col_name;
};
ModelColumns m_columns;</code></programlisting>
<para>Después de añadirle las filas a este modelo, deberá proporcionárselo al <classname>ComboBox</classname> con el método <methodname>set_model()</methodname>. Luego, use los métodos <methodname>pack_start()</methodname> o <methodname>pack_end()</methodname> para especificar qué columnas se mostrarán en el «ComboBox». Al igual que con el «TreeView», podrá usar el «CellRenderer» predeterminado pasándole la <classname>TreeModelColumn</classname> a los métodos de empaquetado, o puede instanciar un <classname>CellRenderer</classname> específico y definir un mapeado particular con <methodname>add_attribute()</methodname> o <methodname>set_cell_data_func()</methodname>. Tenga en cuenta que estos métodos están en la clase base <classname>CellLayout</classname>.</para>
</section>
<section xml:id="sec-combobox-get">
<title>El elemento elegido</title>
<para xml:lang="en">To discover what item, if any, the user has chosen from the ComboBox, call <methodname>ComboBox::get_active()</methodname>. This returns a <classname>TreeModel::iterator</classname> that you can dereference to a <classname>Row</classname> in order to read the values in your columns. For instance, you might read an integer ID value from the model, even though you have chosen only to show the human-readable description in the ComboBox. For instance:
</para>
<programlisting xml:lang="en"><code>Gtk::TreeModel::iterator iter = m_Combo.get_active();
if(iter)
{
auto row = *iter;
//Get the data for the selected row, using our knowledge
//of the tree model:
auto id = row[m_Columns.m_col_id];
set_something_id_chosen(id); //Your own function.
}
else
set_nothing_chosen(); //Your own function.</code></programlisting>
</section>
<section xml:id="sec-combobox-changes">
<title>Responder a los cambios</title>
<para>Tal vez necesite reaccionar a cada cambio de la selección en el «ComboBox», por ejemplo, para actualizar otros widgets. Para hacer esto, debe manejar la señal <literal>changed</literal>. Por ejemplo:</para>
<programlisting xml:lang="en"><code>m_combo.signal_changed().connect( sigc::mem_fun(*this,
&ExampleWindow::on_combo_changed) );</code></programlisting>
</section>
<section xml:id="combobox-example-full">
<title>Ejemplo completo</title>
<figure xml:id="figure-combobox-complex">
<title>ComboBox</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/combobox_complex.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/combobox/complex">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm/window.h>
#include <gtkmm/comboboxtext.h>
#include <gtkmm/liststore.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
virtual ~ExampleWindow();
protected:
void on_cell_data_extra(const Gtk::TreeModel::const_iterator& iter);
//Signal handlers:
void on_combo_changed();
//Tree model columns:
class ModelColumns : public Gtk::TreeModel::ColumnRecord
{
public:
ModelColumns()
{ add(m_col_id); add(m_col_name); add(m_col_extra);}
Gtk::TreeModelColumn<int> m_col_id;
Gtk::TreeModelColumn<Glib::ustring> m_col_name;
Gtk::TreeModelColumn<Glib::ustring> m_col_extra;
};
ModelColumns m_Columns;
//Child widgets:
Gtk::ComboBox m_Combo;
Gtk::CellRendererText m_cell;
Glib::RefPtr<Gtk::ListStore> m_refTreeModel;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <iostream>
ExampleWindow::ExampleWindow()
{
set_title("ComboBox example");
//Create the Tree model:
//m_refTreeModel = Gtk::TreeStore::create(m_Columns);
m_refTreeModel = Gtk::ListStore::create(m_Columns);
m_Combo.set_model(m_refTreeModel);
//Fill the ComboBox's Tree Model:
auto iter = m_refTreeModel->append();
auto row = *iter;
row[m_Columns.m_col_id] = 1;
row[m_Columns.m_col_name] = "Billy Bob";
row[m_Columns.m_col_extra] = "something";
m_Combo.set_active(iter);
/*
auto childrow = *(m_refTreeModel->append(row.children()));
childrow[m_Columns.m_col_id] = 11;
childrow[m_Columns.m_col_name] = "Billy Bob Junior";
childrow = *(m_refTreeModel->append(row.children()));
childrow[m_Columns.m_col_id] = 12;
childrow[m_Columns.m_col_name] = "Sue Bob";
*/
row = *(m_refTreeModel->append());
row[m_Columns.m_col_id] = 2;
row[m_Columns.m_col_name] = "Joey Jojo";
row[m_Columns.m_col_extra] = "yadda";
row = *(m_refTreeModel->append());
row[m_Columns.m_col_id] = 3;
row[m_Columns.m_col_name] = "Rob McRoberts";
row[m_Columns.m_col_extra] = "";
/*
childrow = *(m_refTreeModel->append(row.children()));
childrow[m_Columns.m_col_id] = 31;
childrow[m_Columns.m_col_name] = "Xavier McRoberts";
*/
//Add the model columns to the Combo (which is a kind of view),
//rendering them in the default way:
m_Combo.pack_start(m_Columns.m_col_id);
m_Combo.pack_start(m_Columns.m_col_name);
//An example of adding a cell renderer manually,
//instead of using pack_start(model_column, Gtk::PackOptions::EXPAND_WIDGET)
//so we have more control:
m_Combo.set_cell_data_func(m_cell,
sigc::mem_fun(*this, &ExampleWindow::on_cell_data_extra));
m_Combo.pack_start(m_cell);
//Add the ComboBox to the window.
set_child(m_Combo);
//Connect signal handler:
m_Combo.signal_changed().connect( sigc::mem_fun(*this, &ExampleWindow::on_combo_changed) );
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::on_cell_data_extra(const Gtk::TreeModel::const_iterator& iter)
{
auto row = *iter;
const Glib::ustring extra = row[m_Columns.m_col_extra];
//Some arbitrary logic just to show that this is where you can do such things:
//Transform the value, deciding how to represent it as text:
if(extra.empty())
m_cell.property_text() = "(none)";
else
m_cell.property_text() = "-" + extra + "-";
//Change other cell renderer properties too:
m_cell.property_foreground() = (extra == "yadda" ? "red" : "green");
}
void ExampleWindow::on_combo_changed()
{
const auto iter = m_Combo.get_active();
if(iter)
{
const auto row = *iter;
if(row)
{
//Get the data for the selected row, using our knowledge of the tree
//model:
int id = row[m_Columns.m_col_id];
Glib::ustring name = row[m_Columns.m_col_name];
std::cout << " ID=" << id << ", name=" << name << std::endl;
}
}
else
std::cout << "invalid iter" << std::endl;
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
//Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
<section xml:id="combobox-example-simple">
<title>Ejemplo de texto simple</title>
<figure xml:id="figure-combobox-text">
<title>ComboBoxText</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/combobox_text.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/combobox/text">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm/window.h>
#include <gtkmm/comboboxtext.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
virtual ~ExampleWindow();
protected:
//Signal handlers:
void on_combo_changed();
//Child widgets:
Gtk::ComboBoxText m_Combo;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <iostream>
ExampleWindow::ExampleWindow()
{
set_title("ComboBoxText example");
//Fill the combo:
m_Combo.append("something");
m_Combo.append("something else");
m_Combo.append("something or other");
m_Combo.set_active(1);
set_child(m_Combo);
//Connect signal handler:
m_Combo.signal_changed().connect(sigc::mem_fun(*this,
&ExampleWindow::on_combo_changed) );
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::on_combo_changed()
{
Glib::ustring text = m_Combo.get_active_text();
if(!(text.empty()))
std::cout << "Combo changed: " << text << std::endl;
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
//Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
<section xml:id="sec-comboboxentry">
<title>«ComboBox» con una entrada</title>
<para>Un <classname>ComboBox</classname> puede contener un widget <classname>Entry</classname> para la entrada de texto arbitrario, mediante la especificación de <literal>true</literal> al parámetro <literal>has_entry</literal> del constructor.</para>
<section xml:id="sec-comboboxentry-text-column">
<title>La columna de texto</title>
<para xml:lang="en">So that the <classname>Entry</classname> can interact with the drop-down list of choices, you must specify which of your model columns is the text column, with <methodname>set_entry_text_column()</methodname>. For instance:
</para>
<programlisting xml:lang="en"><code>m_combo.set_entry_text_column(m_columns.m_col_name);</code></programlisting>
<para>Cuando seleccione una opción de la lista desplegable, el valor de esta columna se pondrá en el widget <classname>Entry</classname>.</para>
</section>
<section xml:id="sec-comboboxentry-model">
<title>La entrada</title>
<para>Dado que el usuario puede introducir texto arbitrario, una fila del modelo activa es suficiente para indicar qué texto ha introducido el usuario. Por lo tanto, debe obtener el widget <classname>Entry</classname> con el método <methodname>ComboBoxEntry::get_entry()</methodname> y llamar a <methodname>get_text()</methodname> sobre él.</para>
</section>
<section xml:id="sec-comboboxentry-changes">
<title>Responder a los cambios</title>
<para xml:lang="en">
When the user enters arbitrary text, it may not be enough to connect to the
<literal>changed</literal> signal, which is emitted for every typed character.
It is not emitted when the user presses the <keycap>Enter</keycap> key.
Pressing the <keycap>Enter</keycap> key or moving the keyboard focus to another
widget may signal that the user has finished entering text. To be notified of
these events, connect to the <classname>Entry</classname>'s <literal>activate</literal>
signal (available since <application>gtkmm</application> 4.8), and add a <classname>Gtk::EventControllerFocus</classname>
and connect to its <literal>leave</literal> signal, like so
</para>
<programlisting xml:lang="en"><code><![CDATA[auto entry = m_Combo.get_entry();
if (entry)
{
// Alternatively you can connect to m_Combo.signal_changed().
entry->signal_changed().connect(sigc::mem_fun(*this,
&ExampleWindow::on_entry_changed));
entry->signal_activate().connect(sigc::mem_fun(*this,
&ExampleWindow::on_entry_activate));
// The Entry shall receive focus-leave events.
auto controller = Gtk::EventControllerFocus::create();
m_ConnectionFocusLeave = controller->signal_leave().connect(
sigc::mem_fun(*this, &ExampleWindow::on_entry_focus_leave));
entry->add_controller(controller);
}]]></code></programlisting>
<para xml:lang="en">
The <literal>changed</literal> signals of <classname>ComboBox</classname> and
<classname>Entry</classname> are both emitted for every change. It doesn't matter
which one you connect to. But the <classname>EventControllerFocus</classname>
must be added to the <classname>Entry</classname>.
</para>
</section>
<section xml:id="comboboxentry-example-full">
<title>Ejemplo completo</title>
<figure xml:id="figure-comboboxentry-complex">
<title>«ComboBox» con una entrada</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/comboboxentry_complex.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/combobox/entry_complex">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm/window.h>
#include <gtkmm/combobox.h>
#include <gtkmm/liststore.h>
#include <gtkmm/version.h>
#define HAS_SIGNAL_ACTIVATE GTKMM_CHECK_VERSION(4,7,1)
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
~ExampleWindow() override;
protected:
//Signal handlers:
void on_entry_changed();
#if HAS_SIGNAL_ACTIVATE
void on_entry_activate();
#endif
void on_entry_focus_leave();
//Signal connection:
sigc::connection m_ConnectionFocusLeave;
//Tree model columns:
class ModelColumns : public Gtk::TreeModel::ColumnRecord
{
public:
ModelColumns()
{ add(m_col_id); add(m_col_name); }
Gtk::TreeModelColumn<Glib::ustring> m_col_id; //The data to choose - this must be text.
Gtk::TreeModelColumn<Glib::ustring> m_col_name;
};
ModelColumns m_Columns;
//Child widgets:
Gtk::ComboBox m_Combo;
Glib::RefPtr<Gtk::ListStore> m_refTreeModel;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/eventcontrollerfocus.h>
#include <iostream>
ExampleWindow::ExampleWindow()
: m_Combo(true /* has_entry */)
{
set_title("ComboBox example");
//Create the Tree model:
//m_refTreeModel = Gtk::TreeStore::create(m_Columns);
m_refTreeModel = Gtk::ListStore::create(m_Columns);
m_Combo.set_model(m_refTreeModel);
//Fill the ComboBox's Tree Model:
auto row = *(m_refTreeModel->append());
row[m_Columns.m_col_id] = "1";
row[m_Columns.m_col_name] = "Billy Bob";
/*
auto childrow = *(m_refTreeModel->append(row.children()));
childrow[m_Columns.m_col_id] = 11;
childrow[m_Columns.m_col_name] = "Billy Bob Junior";
childrow = *(m_refTreeModel->append(row.children()));
childrow[m_Columns.m_col_id] = 12;
childrow[m_Columns.m_col_name] = "Sue Bob";
*/
row = *(m_refTreeModel->append());
row[m_Columns.m_col_id] = "2";
row[m_Columns.m_col_name] = "Joey Jojo";
row = *(m_refTreeModel->append());
row[m_Columns.m_col_id] = "3";
row[m_Columns.m_col_name] = "Rob McRoberts";
/*
childrow = *(m_refTreeModel->append(row.children()));
childrow[m_Columns.m_col_id] = 31;
childrow[m_Columns.m_col_name] = "Xavier McRoberts";
*/
//Add the model columns to the Combo (which is a kind of view),
//rendering them in the default way:
//This is automatically rendered when we use set_entry_text_column().
//m_Combo.pack_start(m_Columns.m_col_id, Gtk::PackOptions::EXPAND_WIDGET);
m_Combo.pack_start(m_Columns.m_col_name);
m_Combo.set_entry_text_column(m_Columns.m_col_id);
m_Combo.set_active(1);
//Add the ComboBox to the window.
set_child(m_Combo);
//Connect signal handlers:
auto entry = m_Combo.get_entry();
if (entry)
{
// Alternatively you can connect to m_Combo.signal_changed().
entry->signal_changed().connect(sigc::mem_fun(*this,
&ExampleWindow::on_entry_changed));
#if HAS_SIGNAL_ACTIVATE
entry->signal_activate().connect(sigc::mem_fun(*this,
&ExampleWindow::on_entry_activate));
#endif
// The Entry shall receive focus-leave events.
auto controller = Gtk::EventControllerFocus::create();
m_ConnectionFocusLeave = controller->signal_leave().connect(
sigc::mem_fun(*this, &ExampleWindow::on_entry_focus_leave));
entry->add_controller(controller);
}
else
std::cout << "No Entry ???" << std::endl;
}
ExampleWindow::~ExampleWindow()
{
// The focus-leave signal may be emitted while m_Combo is being destructed.
// The signal handler can generate critical messages, if it's called when
// m_Combo has been partly destructed.
m_ConnectionFocusLeave.disconnect();
}
void ExampleWindow::on_entry_changed()
{
auto entry = m_Combo.get_entry();
if (entry)
{
std::cout << "on_entry_changed(): Row=" << m_Combo.get_active_row_number()
<< ", ID=" << entry->get_text() << std::endl;
}
}
#if HAS_SIGNAL_ACTIVATE
void ExampleWindow::on_entry_activate()
{
auto entry = m_Combo.get_entry();
if (entry)
{
std::cout << "on_entry_activate(): Row=" << m_Combo.get_active_row_number()
<< ", ID=" << entry->get_text() << std::endl;
}
}
#endif
void ExampleWindow::on_entry_focus_leave()
{
auto entry = m_Combo.get_entry();
if (entry)
{
std::cout << "on_entry_focus_leave(): Row=" << m_Combo.get_active_row_number()
<< ", ID=" << entry->get_text() << std::endl;
}
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
//Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
<section xml:id="comboboxentry-example-simple">
<title>Ejemplo de texto simple</title>
<figure xml:id="figure-comboboxentry-text">
<title>«ComboBoxText» con una entrada</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/comboboxentry_text.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/combobox/entry_text">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm/window.h>
#include <gtkmm/comboboxtext.h>
#include <gtkmm/version.h>
#define HAS_SIGNAL_ACTIVATE GTKMM_CHECK_VERSION(4,7,1)
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
~ExampleWindow() override;
protected:
//Signal handlers:
void on_combo_changed();
#if HAS_SIGNAL_ACTIVATE
void on_entry_activate();
#endif
void on_entry_focus_leave();
//Signal connection:
sigc::connection m_ConnectionFocusLeave;
//Child widgets:
Gtk::ComboBoxText m_Combo;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/eventcontrollerfocus.h>
#include <iostream>
ExampleWindow::ExampleWindow()
: m_Combo(true /* has_entry */)
{
set_title("ComboBoxText example");
//Fill the combo:
m_Combo.append("something");
m_Combo.append("something else");
m_Combo.append("something or other");
m_Combo.set_active(0);
set_child(m_Combo);
//Connect signal handlers:
auto entry = m_Combo.get_entry();
// Alternatively you can connect to entry->signal_changed().
m_Combo.signal_changed().connect(sigc::mem_fun(*this,
&ExampleWindow::on_combo_changed) );
if (entry)
{
#if HAS_SIGNAL_ACTIVATE
entry->signal_activate().connect(sigc::mem_fun(*this,
&ExampleWindow::on_entry_activate));
#endif
// The Entry shall receive focus-leave events.
auto controller = Gtk::EventControllerFocus::create();
m_ConnectionFocusLeave = controller->signal_leave().connect(
sigc::mem_fun(*this, &ExampleWindow::on_entry_focus_leave));
entry->add_controller(controller);
}
else
std::cout << "No Entry ???" << std::endl;
m_Combo.property_has_frame() = false;
}
ExampleWindow::~ExampleWindow()
{
// The focus-leave signal may be emitted while m_Combo is being destructed.
// The signal handler can generate critical messages, if it's called when
// m_Combo has been partly destructed.
m_ConnectionFocusLeave.disconnect();
}
void ExampleWindow::on_combo_changed()
{
std::cout << "on_combo_changed(): Row=" << m_Combo.get_active_row_number()
<< ", Text=" << m_Combo.get_active_text() << std::endl;
}
#if HAS_SIGNAL_ACTIVATE
void ExampleWindow::on_entry_activate()
{
std::cout << "on_entry_activate(): Row=" << m_Combo.get_active_row_number()
<< ", Text=" << m_Combo.get_active_text() << std::endl;
}
#endif
void ExampleWindow::on_entry_focus_leave()
{
std::cout << "on_entry_focus_leave(): Row=" << m_Combo.get_active_row_number()
<< ", Text=" << m_Combo.get_active_text() << std::endl;
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
//Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</section>
</chapter>
<chapter xml:id="chapter-textview">
<title>TextView</title>
<para>El widget <classname>TextView</classname> puede usarse para mostrar y editar grandes cantidades de texto formateado. Al igual que el <classname>TreeView</classname>, tiene un diseño modelo/vista. En este caso, el <classname>TextBuffer</classname> es el modelo.</para>
<section xml:id="sec-textview-buffer">
<title>El búfer</title>
<para><classname>Gtk::TextBuffer</classname> es el modelo que contiene los datos del <classname>Gtk::TextView</classname>, al igual que el <classname>Gtk::TreeModel</classname> usado por <classname>Gtk::TreeView</classname>. Esto permite a dos o más <classname>Gtk::TreeView</classname> compartir el mismo <classname>TextBuffer</classname>, y permite mostrar esos búferes de texto de una manera ligeramente diferente. O bien, puede mantener varios <classname>Gtk::TextBuffer</classname> y elegir mostrar cada uno en distintas ocasiones en el mismo widget <classname>GtK::TextView</classname>.</para>
<para>El <classname>TextView</classname> crea su propio <classname>TextBuffer</classname> predeterminado, al que puede acceder mediante el método <methodname>get_buffer()</methodname>.</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1TextBuffer.html">Reference</link></para>
<section xml:id="textview-iterators">
<title>Iteradores</title>
<para xml:lang="en">
A <classname>Gtk::TextBuffer::iterator</classname> and a <classname>Gtk::TextBuffer::const_iterator</classname>
represent a position between two characters in the text buffer. Whenever the buffer
is modified in a way that affects the number of characters in the buffer, all outstanding
iterators become invalid. Because of this, iterators can't be used to preserve positions
across buffer modifications. To preserve a position, use <classname>Gtk::TextBuffer::Mark</classname>.
</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1TextIter.html">Reference</link></para>
</section>
<section xml:id="textview-formatting">
<title>Etiquetas y formateado</title>
<section xml:id="textview-formatting-tags">
<title>Etiquetas</title>
<para>Para especificar que algún texto en el búfer deba tener un formato especial, defina una etiqueta que contenta esa información de formato, y luego aplíquela a la región de texto. Por ejemplo, para definir la etiqueta y sus propiedades:</para>
<programlisting xml:lang="en"><code>auto refTagMatch = Gtk::TextBuffer::Tag::create();
refTagMatch->property_background() = "orange";</code></programlisting>
<para>Puede especificar un nombre para la clase <classname>Tag</classname> cuando use el método <methodname>create()</methodname>, pero no es necesario.</para>
<para>La clase <classname>Tag</classname> tiene muchas otras propiedades.</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1TextTag.html">Reference</link></para>
</section>
<section xml:id="textview-formatting-tagtable">
<title>TagTable</title>
<para>Cada <classname>Gtk::TextBuffer</classname> usa una <classname>Gtk::TextBuffer::TagTable</classname>, que contiene las <classname>Tag</classname> para ese búfer. Dos o más <classname>TextBuffer</classname> pueden compartir la misma <classname>TagTable</classname>. Cuando cree las <classname>Tag</classname>, añádalas a la <classname>TagTable</classname>. Por ejemplo:</para>
<programlisting xml:lang="en"><code>auto refTagTable = Gtk::TextBuffer::TagTable::create();
refTagTable->add(refTagMatch);
//Hopefully a future version of gtkmm will have a set_tag_table() method,
//for use after creation of the buffer.
auto refBuffer = Gtk::TextBuffer::create(refTagTable);</code></programlisting>
<para>También puede usar <methodname>get_tag_table()</methodname> para obtener, y tal vez modificar, la <classname>TagTable</classname> predeterminada del <classname>TextBuffer</classname> en lugar de crear una explícitamente.</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1TextTagTable.html">Reference</link></para>
</section>
<section xml:id="textview-formatting-applying-tags">
<title>Aplicar etiquetas</title>
<para>Si ha creado una <classname>Tag</classname> y la ha añadido a la <classname>TagTable</classname>, podrá aplicarle esa etiqueta a parte del <classname>TextBuffer</classname>, para que una parte del texto se muestre con ese formato. Puede definir el inicio y el fin del rango de texto especificando <classname>Gtk::TextBuffer::iterator</classname>. Por ejemplo:</para>
<programlisting xml:lang="en"><code>refBuffer->apply_tag(refTagMatch, iterRangeStart, iterRangeStop);</code></programlisting>
<para xml:lang="en">
Or you could specify the tag when first inserting the text:
</para>
<programlisting xml:lang="en"><code>refBuffer->insert_with_tag(iter, "Some text", refTagMatch);</code></programlisting>
<para>Puede aplicar más de una <classname>Tag</classname> al mismo texto, usando <methodname>apply_tag()</methodname> más de una vez, o usando <methodname>insert_with_tags()</methodname>. Las <classname>Tag</classname> podrían especificar valores diferentes para las mismas propiedades, pero puede resolver estos conflictos usando <methodname>Tag::set_priority()</methodname>.</para>
</section>
</section>
<section xml:id="textview-marks">
<title>Marcas</title>
<para>Generalmente se invalidan los iteradores del <classname>TextBuffer</classname> cuando el texto cambia, pero puede usar una <classname>Gtk::TextBuffer::Mark</classname> para recordar una posición en estas situaciones. Por ejemplo,</para>
<programlisting xml:lang="en"><code>auto refMark = refBuffer->create_mark(iter);</code></programlisting>
<para>Puede entonces usar el método <methodname>get_iter()</methodname> más tarde para crear un iterador para la posición nueva de la <classname>Mark</classname>.</para>
<para>Hay dos clases <classname>Mark</classname> incorporadas: <literal>insert</literal> y <literal>select_bound</literal>, a las que puede acceder con los métodos <methodname>get_insert()</methodname> and <methodname>get_selection_bound()</methodname> de <classname>TextBuffer</classname>.</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1TextMark.html">Reference</link></para>
</section>
<section xml:id="textview-view">
<title>La vista</title>
<para>Como se mencionó anteriormente, cada <classname>TextView</classname> tiene un <classname>TextBuffer</classname>, y uno o más <classname>TextView</classname> pueden compartir el mismo <classname>TextBuffer</classname>.</para>
<para>Al igual que con el <classname>TreeView</classname>, probablemente deba poner su <classname>TextView</classname> dentro de una <classname>ScrolledWindow</classname> para permitirle al usuario ver y mover toda el área de texto con barras de desplazamiento.</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1TextView.html">Reference</link></para>
<section xml:id="textview-default-formatting">
<title>Formato predeterminado</title>
<para>Los <classname>TextView</classname> tienen varios métodos que le permiten cambiar la presentación del búfer de esta vista particular. Algunos de estos pueden anularse por los <classname>Gtk::TextTag</classname> en el búfer, si especifican las mismas cosas. Por ejemplo, <methodname>set_left_margin()</methodname>, <methodname>set_right_margin()</methodname>, <methodname>set_indent()</methodname>, etc.</para>
</section>
<section xml:id="textview-scrolling">
<title>Desplazamiento</title>
<para xml:lang="en">
<classname>Gtk::TextView</classname> has various
<methodname>scroll_to()</methodname> methods. These allow you to ensure that a
particular part of the text buffer is visible. For instance, your application's
Find feature might use <methodname>Gtk::TextView::scroll_to()</methodname> to
show the found text.
</para>
</section>
</section>
</section>
<section xml:id="sec-widgets-and-childanchors">
<title>Widgets y ChildAnchors</title>
<para xml:lang="en">
You can embed widgets, such as <classname>Gtk::Button</classname>s, in the
text. Each such child widget needs a <classname>ChildAnchor</classname>.
ChildAnchors are associated with <classname>iterator</classname>s. For
instance, to create a child anchor at a particular position, use
<methodname>Gtk::TextBuffer::create_child_anchor()</methodname>:
</para>
<programlisting xml:lang="en"><code>auto refAnchor = refBuffer->create_child_anchor(iter);</code></programlisting>
<para>Luego, para añadir un widget en esa posición, use <methodname>Gtk::TextView::add_child_at_anchor()</methodname>:</para>
<programlisting xml:lang="en"><code>m_TextView.add_child_at_anchor(m_Button, refAnchor);</code></programlisting>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1TextChildAnchor.html">Reference</link></para>
</section>
<section xml:id="sec-textview-examples">
<title>Ejemplos</title>
<section xml:id="textview-example-simple">
<title>Ejemplo simple</title>
<figure xml:id="figure-textview">
<title>TextView</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/textview.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/textview/">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
virtual ~ExampleWindow();
protected:
void fill_buffers();
//Signal handlers:
void on_button_quit();
void on_button_buffer1();
void on_button_buffer2();
//Child widgets:
Gtk::Box m_VBox;
Gtk::ScrolledWindow m_ScrolledWindow;
Gtk::TextView m_TextView;
Glib::RefPtr<Gtk::TextBuffer> m_refTextBuffer1, m_refTextBuffer2;
Gtk::Box m_ButtonBox;
Gtk::Button m_Button_Quit, m_Button_Buffer1, m_Button_Buffer2;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
ExampleWindow::ExampleWindow()
: m_VBox(Gtk::Orientation::VERTICAL),
m_Button_Quit("_Quit", true),
m_Button_Buffer1("Use buffer 1"),
m_Button_Buffer2("Use buffer 2")
{
set_title("Gtk::TextView example");
set_default_size(400, 200);
m_VBox.set_margin(5);
set_child(m_VBox);
//Add the TreeView, inside a ScrolledWindow, with the button underneath:
m_ScrolledWindow.set_child(m_TextView);
//Only show the scrollbars when they are necessary:
m_ScrolledWindow.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
m_ScrolledWindow.set_expand();
m_VBox.append(m_ScrolledWindow);
//append buttons:
m_VBox.append(m_ButtonBox);
m_Button_Buffer1.set_hexpand(true);
m_Button_Buffer1.set_halign(Gtk::Align::END);
m_ButtonBox.append(m_Button_Buffer1);
m_ButtonBox.append(m_Button_Buffer2);
m_ButtonBox.append(m_Button_Quit);
m_ButtonBox.set_margin(5);
m_ButtonBox.set_spacing(5);
//Connect signals:
m_Button_Quit.signal_clicked().connect(sigc::mem_fun(*this,
&ExampleWindow::on_button_quit) );
m_Button_Buffer1.signal_clicked().connect(sigc::mem_fun(*this,
&ExampleWindow::on_button_buffer1) );
m_Button_Buffer2.signal_clicked().connect(sigc::mem_fun(*this,
&ExampleWindow::on_button_buffer2) );
fill_buffers();
on_button_buffer1();
}
void ExampleWindow::fill_buffers()
{
m_refTextBuffer1 = Gtk::TextBuffer::create();
m_refTextBuffer1->set_text("This is the text from TextBuffer #1.");
m_refTextBuffer2 = Gtk::TextBuffer::create();
m_refTextBuffer2->set_text(
"This is some alternative text, from TextBuffer #2.");
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::on_button_quit()
{
set_visible(false);
}
void ExampleWindow::on_button_buffer1()
{
m_TextView.set_buffer(m_refTextBuffer1);
}
void ExampleWindow::on_button_buffer2()
{
m_TextView.set_buffer(m_refTextBuffer2);
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
//Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</section>
</chapter>
<chapter xml:id="chapter-menus-and-toolbars">
<title>Menús y barras de herramientas</title>
<para xml:lang="en">
There are specific APIs for menus and toolbars, but you should usually deal
with them together, creating <classname>Gio::SimpleAction</classname>s that
you can refer to in both menus and toolbars. In this way you can handle
activation of the action instead of responding to the menu and toolbar items
separately. And you can enable or disable both the menu and toolbar item via
the action. <classname>Gtk::Builder</classname> can create menus and toolbars.
</para>
<para xml:lang="en">
This involves the use of the <classname>Gio::SimpleActionGroup</classname>,
<classname>Gio::SimpleAction</classname> and <classname>Gtk::Builder</classname>
classes, all of which should be instantiated via their <methodname>create()</methodname>
methods, which return <classname>RefPtr</classname>s.
</para>
<section xml:id="sec-actions">
<title>Acciones</title>
<para xml:lang="en">
First create the <classname>Gio::SimpleAction</classname>s and add them to a
<classname>Gio::SimpleActionGroup</classname>, with
<methodname>Gio::ActionMap::add_action()</methodname>.
(<classname>Gio::ActionMap</classname> is a base class of
<classname>Gio::SimpleActionGroup</classname>.) Then add the action group to
your window with <methodname>Gtk::Widget::insert_action_group()</methodname>.
</para>
<para xml:lang="en">
The arguments to <methodname>add_action()</methodname> specify the action's
name, which is used in the menu items and toolbar buttons. You can also specify
a signal handler when calling <methodname>add_action()</methodname>. This signal
handler will be called when the action is activated via either a menu item or
a toolbar button.
</para>
<para>Por ejemplo:</para>
<programlisting xml:lang="en"><code>m_refActionGroup = Gio::SimpleActionGroup::create();
m_refActionGroup->add_action("new", sigc::mem_fun(*this, &ExampleWindow::on_action_file_new));
m_refActionGroup->add_action("open", sigc::mem_fun(*this, &ExampleWindow::on_action_file_open));
m_refActionGroup->add_action("quit", sigc::mem_fun(*this, &ExampleWindow::on_action_file_quit));
insert_action_group("example", m_refActionGroup);
</code></programlisting>
<para xml:lang="en">
If you use an <classname>Gtk::ApplicationWindow</classname>, you don't have to
create your own action group. <classname>Gio::ActionGroup</classname> and
<classname>Gio::ActionMap</classname> are base classes of
<classname>Gtk::ApplicationWindow</classname>.
</para>
</section>
<section xml:id="sec-menubar-and-toolbar">
<title xml:lang="en">Menubar and Toolbar</title>
<para xml:lang="en">
Next you should create a <classname>Gtk::Builder</classname>. At this point is
also a good idea to tell the application to respond to keyboard shortcuts,
by using <methodname>Gtk::Application::set_accel_for_action()</methodname>.
</para>
<para>Por ejemplo,</para>
<programlisting xml:lang="en"><code>m_refBuilder = Gtk::Builder::create();
app->set_accel_for_action("example.new", "<Primary>n");
app->set_accel_for_action("example.quit", "<Primary>q");
app->set_accel_for_action("example.copy", "<Primary>c");
app->set_accel_for_action("example.paste", "<Primary>v");
</code></programlisting>
<!-- Not true in gtkmm4 (July 2022). Will it become true in the future?
<para>
If your main window is derived from <classname>ApplicationWindow</classname> and
you instantiate your menubar with <methodname>Gtk::Application::set_menubar()</methodname>,
then you don't have to call <methodname>set_accel_for_action()</methodname>.
See <link linkend="menu-example-main">Application Menu and Main Menu example</link>
for an example.
</para>
-->
<para xml:lang="en">
Then, you can define the actual visible layout of the menus and toolbars, and
add the UI layout to the <classname>Builder</classname>. This "ui
string" uses an XML format, in which you should mention the names of the
actions that you have already created. For instance:
</para>
<programlisting xml:lang="en"><code>Glib::ustring ui_info =
"<interface>"
" <menu id='menubar'>"
" <submenu>"
" <attribute name='label' translatable='yes'>_File</attribute>"
" <section>"
" <item>"
" <attribute name='label' translatable='yes'>_New</attribute>"
" <attribute name='action'>example.new</attribute>"
" </item>"
" </section>"
" <section>"
" <item>"
" <attribute name='label' translatable='yes'>_Quit</attribute>"
" <attribute name='action'>example.quit</attribute>"
" </item>"
" </section>"
" </submenu>"
" <submenu>"
" <attribute name='label' translatable='yes'>_Edit</attribute>"
" <item>"
" <attribute name='label' translatable='yes'>_Copy</attribute>"
" <attribute name='action'>example.copy</attribute>"
" </item>"
" <item>"
" <attribute name='label' translatable='yes'>_Paste</attribute>"
" <attribute name='action'>example.paste</attribute>"
" </item>"
" </submenu>"
" </menu>"
"</interface>";
m_refBuilder->add_from_string(ui_info);
m_refBuilder->add_from_resource("/toolbar/toolbar.ui");
</code></programlisting>
<para xml:lang="en">This is where we specify the names of the menu items as they will be seen
by users in the menu. Therefore, this is where you should make strings
translatable, by adding <literal>translatable='yes'</literal>.
</para>
<para xml:lang="en">
To instantiate a <classname>Gtk::PopoverMenuBar</classname> and toolbar (a horizontal
<classname>Gtk::Box</classname>) which you can actually show, you should use
the <methodname>Builder::get_object()</methodname> and
<methodname>Builder::get_widget()</methodname> methods, and then add the widgets
to a container. For instance:
</para>
<programlisting xml:lang="en"><code>auto gmenu = m_refBuilder->get_object<Gio::Menu>("menubar");
auto pMenuBar = Gtk::make_managed<Gtk::PopoverMenuBar>(gmenu);
m_Box.append(*pMenuBar);
auto toolbar = m_refBuilder->get_widget<Gtk::Box>("toolbar");
m_Box.append(*toolbar);
</code></programlisting>
</section>
<section xml:id="sec-menus-popup">
<title>Menús emergentes</title>
<para xml:lang="en">
<classname>Menu</classname>s are normally just added to a window, but they can
also be displayed temporarily as the result of a mouse button click. For
instance, a context menu might be displayed when the user clicks their right
mouse button.
</para>
<para>Por ejemplo:</para>
<programlisting xml:lang="en"><code>Glib::ustring ui_info =
"<interface>"
" <menu id='menu-examplepopup'>"
" <section>"
" <item>"
" <attribute name='label' translatable='yes'>Edit</attribute>"
" <attribute name='action'>examplepopup.edit</attribute>"
" </item>"
" <item>"
" <attribute name='label' translatable='yes'>Process</attribute>"
" <attribute name='action'>examplepopup.process</attribute>"
" </item>"
" <item>"
" <attribute name='label' translatable='yes'>Remove</attribute>"
" <attribute name='action'>examplepopup.remove</attribute>"
" </item>"
" </section>"
" </menu>"
"</interface>";
m_refBuilder->add_from_string(ui_info);
auto gmenu = m_refBuilder->get_object<Gio::Menu>("menu-examplepopup");
m_MenuPopup.set_menu_model(gmenu);
</code></programlisting>
<para xml:lang="en">
To show the popup menu, use a <classname>Gtk::GestureClick</classname>
and connect to its <literal>pressed</literal> signal. In the signal handler,
use <classname>Gtk::PopoverMenu</classname>'s
<methodname>popup()</methodname> method. For instance:
</para>
<programlisting xml:lang="en"><code>void ExampleWindow::on_label_pressed(int /* n_press */, double x, double y)
{
const Gdk::Rectangle rect(x, y, 1, 1);
m_MenuPopup.set_pointing_to(rect);
m_MenuPopup.popup();
}</code></programlisting>
</section>
<section xml:id="sec-gio-resource">
<title xml:lang="en">Gio::Resource and glib-compile-resources</title>
<para xml:lang="en">
Applications and libraries often contain binary or textual data that is
really part of the application, rather than user data. For instance
<classname>Gtk::Builder</classname> <filename class="extension">.ui</filename> files,
splashscreen images, <classname>Gio::Menu</classname> markup xml, CSS files,
icons, etc. These are often shipped as files in <filename class="directory">$datadir/appname</filename>,
or manually included as literal strings in the code.
</para>
<para xml:lang="en">
The <classname>Gio::Resource</classname> API and the <application>glib-compile-resources</application>
program provide a convenient and efficient alternative to this, which has some nice properties. You
maintain the files as normal files, so it's easy to edit them, but during the build the files
are combined into a binary bundle that is linked into the executable. This means that loading
the resource files is efficient (as they are already in memory, shared with other instances) and
simple (no need to check for things like I/O errors or locate the files in the filesystem). It
also makes it easier to create relocatable applications.
</para>
<!-- TODO: Add a link to the description of glib-compile-resources, if it will
be described at https://docs.gtk.org/gio/glib-compile-resources.html or elsewhere.
-->
<para xml:lang="en">
Resource bundles are created by the <application>glib-compile-resources</application>
program which takes an xml file that describes the bundle, and a set of files that the xml references.
These are combined into a binary resource bundle.
</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/glibmm/classGio_1_1Resource.html">Gio::Resource Reference</link></para>
<para xml:lang="en">An example:</para>
<programlisting xml:lang="en"><code><?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/toolbar">
<file preprocess="xml-stripblanks">toolbar.ui</file>
<file>rain.png</file>
</gresource>
</gresources>
</code></programlisting>
<para xml:lang="en">This will create a resource bundle with the files
<itemizedlist>
<listitem><para xml:lang="en"><filename>/toolbar/toolbar.ui</filename></para></listitem>
<listitem><para xml:lang="en"><filename>/toolbar/rain.png</filename></para></listitem>
</itemizedlist>
</para>
<para xml:lang="en">
You can then use <application>glib-compile-resources</application> to compile the xml to a binary bundle
that you can load with <methodname>Gio::Resource::create_from_file()</methodname>.
However, it's more common to use the <parameter class="command">--generate-source</parameter>
argument to create a C source file to link directly into your application. E.g.
<screen xml:lang="en">$ glib-compile-resources --target=resources.c --generate-source toolbar.gresource.xml</screen>
</para>
<para xml:lang="en">
Once a <classname>Gio::Resource</classname> has been created and registered all the data
in it can be accessed globally in the process by using API calls like
<methodname>Gio::Resource::open_stream_from_global_resources()</methodname>
to stream the data or <methodname>Gio::Resource::lookup_data_in_global_resources()</methodname>
to get a direct pointer to the data. You can also use URIs like <uri>resource:///toolbar/rain.png</uri>
with <classname>Gio::File</classname> to access the resource data.
</para>
<para xml:lang="en">
Often you don't need a <classname>Gio::Resource</classname> instance,
because resource data can be loaded with methods such as
<methodname>Gdk::Pixbuf::create_from_resource()</methodname>,
<methodname>Gtk::Builder::add_from_resource()</methodname> and
<methodname>Gtk::Image::set_from_resource()</methodname>.
</para>
</section>
<section xml:id="sec-menus-examples">
<title>Ejemplos</title>
<section xml:id="menu-example-main">
<title xml:lang="en">Application Menu and Main Menu example</title>
<para xml:lang="en">
This program contains an application menu, a menubar and a toolbar.
Classes are derived from <classname>Gtk::Application</classname> and
<classname>Gtk::ApplicationWindow</classname>.
</para>
<figure xml:id="figure-menus-mainmenu">
<title xml:lang="en">App and Main Menu</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/main_menu.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/menus/main_menu/">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>exampleapplication.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEAPPLICATION_H
#define GTKMM_EXAMPLEAPPLICATION_H
#include <gtkmm.h>
class ExampleApplication : public Gtk::Application
{
protected:
ExampleApplication();
public:
static Glib::RefPtr<ExampleApplication> create();
protected:
//Overrides of default signal handlers:
void on_startup() override;
void on_activate() override;
private:
void create_window();
void on_menu_file_new_generic();
void on_menu_file_quit();
void on_menu_help_about();
Glib::RefPtr<Gtk::Builder> m_refBuilder;
};
#endif /* GTKMM_EXAMPLEAPPLICATION_H */
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm.h>
class ExampleWindow : public Gtk::ApplicationWindow
{
public:
ExampleWindow();
virtual ~ExampleWindow();
protected:
//Signal handlers:
void on_menu_others();
void on_menu_choices(const Glib::ustring& parameter);
void on_menu_choices_other(int parameter);
void on_menu_toggle();
//Child widgets:
Gtk::Box m_Box;
Glib::RefPtr<Gtk::Builder> m_refBuilder;
//Two sets of choices:
Glib::RefPtr<Gio::SimpleAction> m_refChoice;
Glib::RefPtr<Gio::SimpleAction> m_refChoiceOther;
Glib::RefPtr<Gio::SimpleAction> m_refToggle;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>exampleapplication.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "exampleapplication.h"
#include "examplewindow.h"
#include <iostream>
ExampleApplication::ExampleApplication()
: Gtk::Application("org.gtkmm.example.main_menu")
{
Glib::set_application_name("Main Menu Example");
}
Glib::RefPtr<ExampleApplication> ExampleApplication::create()
{
return Glib::make_refptr_for_instance<ExampleApplication>(new ExampleApplication());
}
void ExampleApplication::on_startup()
{
//Call the base class's implementation:
Gtk::Application::on_startup();
//Create actions for menus and toolbars.
//We can use add_action() because Gtk::Application derives from Gio::ActionMap.
//File|New sub menu:
add_action("newstandard",
sigc::mem_fun(*this, &ExampleApplication::on_menu_file_new_generic));
add_action("newfoo",
sigc::mem_fun(*this, &ExampleApplication::on_menu_file_new_generic));
add_action("newgoo",
sigc::mem_fun(*this, &ExampleApplication::on_menu_file_new_generic));
//File menu:
add_action("quit", sigc::mem_fun(*this, &ExampleApplication::on_menu_file_quit));
//Help menu:
add_action("about", sigc::mem_fun(*this, &ExampleApplication::on_menu_help_about));
// Set accelerator keys:
set_accel_for_action("app.newstandard", "<Primary>n");
set_accel_for_action("app.quit", "<Primary>q");
set_accel_for_action("win.copy", "<Primary>c");
set_accel_for_action("win.paste", "<Primary>v");
m_refBuilder = Gtk::Builder::create();
//Layout the actions in a menubar and a menu:
Glib::ustring ui_info =
"<interface>"
" <!-- menubar -->"
" <menu id='menu-example'>"
" <submenu>"
" <attribute name='label' translatable='yes'>_File</attribute>"
" <section>"
" <item>"
" <attribute name='label' translatable='yes'>New _Standard</attribute>"
" <attribute name='action'>app.newstandard</attribute>"
" <attribute name='accel'><Primary>n</attribute>"
" </item>"
" <item>"
" <attribute name='label' translatable='yes'>New _Foo</attribute>"
" <attribute name='action'>app.newfoo</attribute>"
" </item>"
" <item>"
" <attribute name='label' translatable='yes'>New _Goo</attribute>"
" <attribute name='action'>app.newgoo</attribute>"
" </item>"
" </section>"
" <section>"
" <item>"
" <attribute name='label' translatable='yes'>_Quit</attribute>"
" <attribute name='action'>app.quit</attribute>"
" <attribute name='accel'><Primary>q</attribute>"
" </item>"
" </section>"
" </submenu>"
" <submenu>"
" <attribute name='label' translatable='yes'>_Edit</attribute>"
" <section>"
" <item>"
" <attribute name='label' translatable='yes'>_Copy</attribute>"
" <attribute name='action'>win.copy</attribute>"
" <attribute name='accel'><Primary>c</attribute>"
" </item>"
" <item>"
" <attribute name='label' translatable='yes'>_Paste</attribute>"
" <attribute name='action'>win.paste</attribute>"
" <attribute name='accel'><Primary>v</attribute>"
" </item>"
" <item>"
" <attribute name='label' translatable='yes'>_Something</attribute>"
" <attribute name='action'>win.something</attribute>"
" </item>"
" </section>"
" </submenu>"
" <submenu>"
" <attribute name='label' translatable='yes'>_Choices</attribute>"
" <section>"
" <item>"
" <attribute name='label' translatable='yes'>Choice _A</attribute>"
" <attribute name='action'>win.choice</attribute>"
" <attribute name='target'>a</attribute>"
" </item>"
" <item>"
" <attribute name='label' translatable='yes'>Choice _B</attribute>"
" <attribute name='action'>win.choice</attribute>"
" <attribute name='target'>b</attribute>"
" </item>"
" </section>"
" </submenu>"
" <submenu>"
" <attribute name='label' translatable='yes'>_Other Choices</attribute>"
" <section>"
" <item>"
" <attribute name='label' translatable='yes'>Choice 1</attribute>"
" <attribute name='action'>win.choiceother</attribute>"
" <attribute name='target' type='i'>1</attribute>"
" </item>"
" <item>"
" <attribute name='label' translatable='yes'>Choice 2</attribute>"
" <attribute name='action'>win.choiceother</attribute>"
" <attribute name='target' type='i'>2</attribute>"
" </item>"
" </section>"
" <section>"
" <item>"
" <attribute name='label' translatable='yes'>Some Toggle</attribute>"
" <attribute name='action'>win.sometoggle</attribute>"
" </item>"
" </section>"
" </submenu>"
" <submenu>"
" <attribute name='label' translatable='yes'>_Help</attribute>"
" <section>"
" <item>"
" <attribute name='label' translatable='yes'>_About Window</attribute>"
" <attribute name='action'>win.about</attribute>"
" </item>"
" <item>"
" <attribute name='label' translatable='yes'>_About App</attribute>"
" <attribute name='action'>app.about</attribute>"
" </item>"
" </section>"
" </submenu>"
" </menu>"
"</interface>";
try
{
m_refBuilder->add_from_string(ui_info);
}
catch (const Glib::Error& ex)
{
std::cerr << "Building menus failed: " << ex.what();
}
//Get the menubar and the app menu, and add them to the application:
auto gmenu = m_refBuilder->get_object<Gio::Menu>("menu-example");
if (!gmenu)
{
g_warning("GMenu not found");
}
else
{
set_menubar(gmenu);
}
}
void ExampleApplication::on_activate()
{
//std::cout << "debug1: " << G_STRFUNC << std::endl;
// The application has been started, so let's show a window.
// A real application might want to reuse this window in on_open(),
// when asked to open a file, if no changes have been made yet.
create_window();
}
void ExampleApplication::create_window()
{
auto win = new ExampleWindow();
//Make sure that the application runs for as long this window is still open:
add_window(*win);
//Delete the window when it is hidden.
//That's enough for this simple example.
win->signal_hide().connect([win]() { delete win; });
win->set_show_menubar();
win->set_visible(true);
}
void ExampleApplication::on_menu_file_new_generic()
{
std::cout << "A File|New menu item was selected." << std::endl;
}
void ExampleApplication::on_menu_file_quit()
{
std::cout << G_STRFUNC << std::endl;
quit(); // Not really necessary, when Gtk::Widget::set_visible(false) is called.
// Gio::Application::quit() will make Gio::Application::run() return,
// but it's a crude way of ending the program. The window is not removed
// from the application. Neither the window's nor the application's
// destructors will be called, because there will be remaining reference
// counts in both of them. If we want the destructors to be called, we
// must remove the window from the application. One way of doing this
// is to hide the window.
std::vector<Gtk::Window*> windows = get_windows();
if (windows.size() > 0)
windows[0]->set_visible(false); // In this simple case, we know there is only one window.
}
void ExampleApplication::on_menu_help_about()
{
std::cout << "Help|About App was selected." << std::endl;
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <iostream>
ExampleWindow::ExampleWindow()
: Gtk::ApplicationWindow(),
m_Box(Gtk::Orientation::VERTICAL)
{
set_title("Main menu example");
set_default_size(300, 100);
// ExampleApplication displays the menubar. Other stuff, such as a toolbar,
// is put into the box.
set_child(m_Box);
// Create actions for menus and toolbars.
// We can use add_action() because Gtk::ApplicationWindow derives from Gio::ActionMap.
// This Action Map uses a "win." prefix for the actions.
// Therefore, for instance, "win.copy", is used in ExampleApplication::on_startup()
// to layout the menu.
//Edit menu:
add_action("copy", sigc::mem_fun(*this, &ExampleWindow::on_menu_others));
add_action("paste", sigc::mem_fun(*this, &ExampleWindow::on_menu_others));
add_action("something", sigc::mem_fun(*this, &ExampleWindow::on_menu_others));
//Choices menus, to demonstrate Radio items,
//using our convenience methods for string and int radio values:
m_refChoice = add_action_radio_string("choice",
sigc::mem_fun(*this, &ExampleWindow::on_menu_choices), "a");
m_refChoiceOther = add_action_radio_integer("choiceother",
sigc::mem_fun(*this, &ExampleWindow::on_menu_choices_other), 1);
m_refToggle = add_action_bool("sometoggle",
sigc::mem_fun(*this, &ExampleWindow::on_menu_toggle), false);
//Help menu:
add_action("about", sigc::mem_fun(*this, &ExampleWindow::on_menu_others));
//Create the toolbar and add it to a container widget:
m_refBuilder = Gtk::Builder::create();
Glib::ustring ui_info =
"<!-- Generated with glade 3.18.3 and then changed manually -->"
"<interface>"
" <object class='GtkBox' id='toolbar'>"
" <property name='can_focus'>False</property>"
" <child>"
" <object class='GtkButton' id='toolbutton_new'>"
" <property name='can_focus'>False</property>"
" <property name='tooltip_text' translatable='yes'>New Standard</property>"
" <property name='action_name'>app.newstandard</property>"
" <property name='icon_name'>document-new</property>"
" <property name='hexpand'>False</property>"
" <property name='vexpand'>False</property>"
" </object>"
" </child>"
" <child>"
" <object class='GtkButton' id='toolbutton_quit'>"
" <property name='can_focus'>False</property>"
" <property name='tooltip_text' translatable='yes'>Quit</property>"
" <property name='action_name'>app.quit</property>"
" <property name='icon_name'>application-exit</property>"
" <property name='hexpand'>False</property>"
" <property name='vexpand'>False</property>"
" </object>"
" </child>"
" </object>"
"</interface>";
try
{
m_refBuilder->add_from_string(ui_info);
}
catch (const Glib::Error& ex)
{
std::cerr << "Building toolbar failed: " << ex.what();
}
auto toolbar = m_refBuilder->get_widget<Gtk::Box>("toolbar");
if (!toolbar)
g_warning("toolbar not found");
else
m_Box.append(*toolbar);
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::on_menu_others()
{
std::cout << "A menu item was selected." << std::endl;
}
void ExampleWindow::on_menu_choices(const Glib::ustring& parameter)
{
//The radio action's state does not change automatically:
m_refChoice->change_state(parameter);
Glib::ustring message;
if (parameter == "a")
message = "Choice a was selected.";
else
message = "Choice b was selected.";
std::cout << message << std::endl;
}
void ExampleWindow::on_menu_choices_other(int parameter)
{
//The radio action's state does not change automatically:
m_refChoiceOther->change_state(parameter);
Glib::ustring message;
if (parameter == 1)
message = "Choice 1 was selected.";
else
message = "Choice 2 was selected.";
std::cout << message << std::endl;
}
void ExampleWindow::on_menu_toggle()
{
bool active = false;
m_refToggle->get_state(active);
//The toggle action's state does not change automatically:
active = !active;
m_refToggle->change_state(active);
Glib::ustring message;
if (active)
message = "Toggle is active.";
else
message = "Toggle is not active.";
std::cout << message << std::endl;
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "exampleapplication.h"
int main(int argc, char* argv[])
{
auto application = ExampleApplication::create();
// Start the application, showing the initial window,
// and opening extra windows for any files that it is asked to open,
// for instance as a command-line parameter.
// run() will return when the last window has been closed by the user.
const int status = application->run(argc, argv);
return status;
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
<section xml:id="menu-example-main2">
<title>Ejemplo de menú principal</title>
<para xml:lang="en">
This program contains a menubar and a toolbar.
A class is derived from <classname>Gtk::Window</classname>.
</para>
<figure xml:id="figure-menus-mainmenu2">
<title>Menú principal</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/menus_and_toolbars.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/menus_and_toolbars">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow(const Glib::RefPtr<Gtk::Application>& app);
virtual ~ExampleWindow();
private:
//Signal handlers:
//void on_action_file_new();
void on_action_file_quit();
void on_action_others();
void on_action_toggle();
//Child widgets:
Gtk::Box m_Box;
Glib::RefPtr<Gtk::Builder> m_refBuilder;
Glib::RefPtr<Gio::SimpleActionGroup> m_refActionGroup;
Glib::RefPtr<Gio::SimpleAction> m_refActionRain;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm.h>
#include <iostream>
ExampleWindow::ExampleWindow(const Glib::RefPtr<Gtk::Application>& app)
: m_Box(Gtk::Orientation::VERTICAL)
{
set_title("main_menu example");
set_default_size(200, 200);
set_child(m_Box); //We can put a MenuBar at the top of the box and other stuff below it.
//Define the actions:
m_refActionGroup = Gio::SimpleActionGroup::create();
// There are several ways of calling a function that takes a sigc::slot.
// If the slot function is very short, it might be easy to skip the on_xxx()
// method and put its contents directly in a lambda expression.
m_refActionGroup->add_action("new",
[] { std::cout << "A File|New menu item was selected.\n"; /* on_action_file_new() */});
// With sigc::mem_fun() or (for non-member functions and static member functions)
// sigc::ptr_fun(). The only way before C++11 introduced lambda expressions.
m_refActionGroup->add_action("open",
sigc::mem_fun(*this, &ExampleWindow::on_action_others) );
// With a lambda expression. Does not disconnect automatically when ExampleWindow
// is deleted, like sigc::mem_fun() does.
m_refActionRain = m_refActionGroup->add_action_bool("rain",
[this] { on_action_toggle(); }, false);
m_refActionGroup->add_action("quit",
sigc::mem_fun(*this, &ExampleWindow::on_action_file_quit) );
// With a lambda expression and sigc::track_obj() or sigc::track_object().
// Disconnects automatically like sigc::mem_fun().
#if SIGCXX_MINOR_VERSION >= 4
m_refActionGroup->add_action("cut",
sigc::track_object([this] { on_action_others(); }, *this));
#else
m_refActionGroup->add_action("cut",
sigc::track_obj([this] { on_action_others(); }, *this));
#endif
m_refActionGroup->add_action("copy",
sigc::mem_fun(*this, &ExampleWindow::on_action_others) );
m_refActionGroup->add_action("paste",
sigc::mem_fun(*this, &ExampleWindow::on_action_others) );
insert_action_group("example", m_refActionGroup);
//Define how the actions are presented in the menus and toolbars:
m_refBuilder = Gtk::Builder::create();
//Layout the actions in a menubar and toolbar:
const Glib::ustring ui_info =
"<interface>"
" <menu id='menubar'>"
" <submenu>"
" <attribute name='label' translatable='yes'>_File</attribute>"
" <section>"
" <item>"
" <attribute name='label' translatable='yes'>_New</attribute>"
" <attribute name='action'>example.new</attribute>"
" </item>"
" <item>"
" <attribute name='label' translatable='yes'>_Open</attribute>"
" <attribute name='action'>example.open</attribute>"
" </item>"
" </section>"
" <section>"
" <item>"
" <attribute name='label' translatable='yes'>Rain</attribute>"
" <attribute name='action'>example.rain</attribute>"
" </item>"
" </section>"
" <section>"
" <item>"
" <attribute name='label' translatable='yes'>_Quit</attribute>"
" <attribute name='action'>example.quit</attribute>"
" </item>"
" </section>"
" </submenu>"
" <submenu>"
" <attribute name='label' translatable='yes'>_Edit</attribute>"
" <item>"
" <attribute name='label' translatable='yes'>_Cut</attribute>"
" <attribute name='action'>example.cut</attribute>"
" </item>"
" <item>"
" <attribute name='label' translatable='yes'>_Copy</attribute>"
" <attribute name='action'>example.copy</attribute>"
" </item>"
" <item>"
" <attribute name='label' translatable='yes'>_Paste</attribute>"
" <attribute name='action'>example.paste</attribute>"
" </item>"
" </submenu>"
" </menu>"
"</interface>";
// Set accelerator keys:
app->set_accel_for_action("example.new", "<Primary>n");
app->set_accel_for_action("example.open", "<Primary>o");
app->set_accel_for_action("example.quit", "<Primary>q");
app->set_accel_for_action("example.cut", "<Primary>x");
app->set_accel_for_action("example.copy", "<Primary>c");
app->set_accel_for_action("example.paste", "<Primary>v");
try
{
m_refBuilder->add_from_string(ui_info);
m_refBuilder->add_from_resource("/toolbar/toolbar.ui");
}
catch(const Glib::Error& ex)
{
std::cerr << "Building menus and toolbar failed: " << ex.what();
}
//Get the menubar:
auto gmenu = m_refBuilder->get_object<Gio::Menu>("menubar");
if (!gmenu)
g_warning("GMenu not found");
else
{
auto pMenuBar = Gtk::make_managed<Gtk::PopoverMenuBar>(gmenu);
//Add the PopoverMenuBar to the window:
m_Box.append(*pMenuBar);
}
//Get the toolbar and add it to a container widget:
auto toolbar = m_refBuilder->get_widget<Gtk::Box>("toolbar");
if (!toolbar)
g_warning("toolbar not found");
else
m_Box.append(*toolbar);
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::on_action_file_quit()
{
set_visible(false); //Closes the main window to stop the app->make_window_and_run().
}
//void ExampleWindow::on_action_file_new()
//{
// std::cout << "A File|New menu item was selected." << std::endl;
//}
void ExampleWindow::on_action_others()
{
std::cout << "A menu item was selected." << std::endl;
}
void ExampleWindow::on_action_toggle()
{
std::cout << "The toggle menu item was selected." << std::endl;
bool active = false;
m_refActionRain->get_state(active);
//The toggle action's state does not change automatically:
active = !active;
m_refActionRain->change_state(active);
Glib::ustring message;
if(active)
message = "Toggle is active.";
else
message = "Toggle is not active";
std::cout << message << std::endl;
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include <gtkmm.h>
#include "examplewindow.h"
int main(int argc, char* argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
//Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv, app);
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>toolbar.gresource.xml</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/toolbar">
<file preprocess="xml-stripblanks">toolbar.ui</file>
<file>rain.png</file>
</gresource>
</gresources>
]]></code></programlisting>
<!-- end inserted example code -->
</section>
<section xml:id="menu-example-popup">
<title>Ejemplo de menú emergente</title>
<figure xml:id="figure-menus-popup">
<title>Menú emergente</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/menu_popup.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/menus/popup/">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm.h>
#include <memory>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow(const Glib::RefPtr<Gtk::Application>& app);
virtual ~ExampleWindow();
protected:
//Signal handlers:
void on_label_pressed(int n_press, double x, double y);
void on_menu_file_popup_generic();
//Child widgets:
Gtk::Box m_Box;
Gtk::Label m_Label;
Gtk::PopoverMenu m_MenuPopup;
Glib::RefPtr<Gtk::Builder> m_refBuilder;
Glib::RefPtr<Gtk::GestureClick> m_refGesture;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <iostream>
ExampleWindow::ExampleWindow(const Glib::RefPtr<Gtk::Application>& app)
: m_Box(Gtk::Orientation::VERTICAL),
m_Label("Right-click to see the popup menu.")
{
set_title("popup example");
set_default_size(200, 200);
set_child(m_Box);
// Catch button_press events:
m_Box.append(m_Label);
m_Label.set_expand();
m_refGesture = Gtk::GestureClick::create();
m_refGesture->set_button(GDK_BUTTON_SECONDARY);
m_refGesture->signal_pressed().connect(
sigc::mem_fun(*this, &ExampleWindow::on_label_pressed));
m_Label.add_controller(m_refGesture);
//Create actions:
auto refActionGroup = Gio::SimpleActionGroup::create();
//File|New sub menu:
//These menu actions would normally already exist for a main menu, because a
//context menu should not normally contain menu items that are only available
//via a context menu.
refActionGroup->add_action("edit",
sigc::mem_fun(*this, &ExampleWindow::on_menu_file_popup_generic));
refActionGroup->add_action("process",
sigc::mem_fun(*this, &ExampleWindow::on_menu_file_popup_generic));
refActionGroup->add_action("remove",
sigc::mem_fun(*this, &ExampleWindow::on_menu_file_popup_generic));
insert_action_group("examplepopup", refActionGroup);
// Set accelerator keys:
app->set_accel_for_action("examplepopup.edit", "<Primary>e");
app->set_accel_for_action("examplepopup.process", "<Primary>p");
app->set_accel_for_action("examplepopup.remove", "<Primary>r");
m_refBuilder = Gtk::Builder::create();
//Layout the actions in a menubar and toolbar:
Glib::ustring ui_info =
"<interface>"
" <menu id='menu-examplepopup'>"
" <section>"
" <item>"
" <attribute name='label' translatable='yes'>Edit</attribute>"
" <attribute name='action'>examplepopup.edit</attribute>"
" </item>"
" <item>"
" <attribute name='label' translatable='yes'>Process</attribute>"
" <attribute name='action'>examplepopup.process</attribute>"
" </item>"
" <item>"
" <attribute name='label' translatable='yes'>Remove</attribute>"
" <attribute name='action'>examplepopup.remove</attribute>"
" </item>"
" </section>"
" </menu>"
"</interface>";
try
{
m_refBuilder->add_from_string(ui_info);
}
catch(const Glib::Error& ex)
{
std::cerr << "building menus failed: " << ex.what();
}
//Get the menu:
auto gmenu = m_refBuilder->get_object<Gio::Menu>("menu-examplepopup");
if(!gmenu)
g_warning("GMenu not found");
m_MenuPopup.set_parent(m_Label);
m_MenuPopup.set_menu_model(gmenu);
m_MenuPopup.set_has_arrow(false);
}
ExampleWindow::~ExampleWindow()
{
m_MenuPopup.unparent();
}
void ExampleWindow::on_menu_file_popup_generic()
{
std::cout << "A popup menu item was selected." << std::endl;
}
void ExampleWindow::on_label_pressed(int /* n_press */, double x, double y)
{
const Gdk::Rectangle rect(x, y, 1, 1);
m_MenuPopup.set_pointing_to(rect);
m_MenuPopup.popup();
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
//Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv, app);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</section>
</chapter>
<chapter xml:id="chapter-adjustment">
<title>Ajustes</title>
<para><application>gtkmm</application> tiene varios widgets que pueden ajustarse visualmente usando el ratón o el teclado, como los widgets <classname>Range</classname> (descritos en la sección <link linkend="chapter-range-widgets">Widgets de rango</link>). También hay algunos widgets que muestran una parte ajustable de un área más grande, como el widget <classname>Viewport</classname>. Estos widgets tienen objetos <classname>Gtk::Adjustment</classname> que expresan esta parte común de sus API.</para>
<para>Para que las aplicaciones puedan reaccionar a los cambios, por ejemplo, cuando un usuario mueve una barra de desplazamiento, <classname>Gtk::Adjustment</classname> tiene una señal <literal>value_changed</literal>.Entonces, usar el método <methodname>get_value()</methodname> para descubrir el valor nuevo.</para>
<section xml:id="sec-creating-adjustment">
<title>Crear un ajuste</title>
<para xml:lang="en">
The <classname>Gtk::Adjustment</classname> is created by its
<methodname>create()</methodname> method which is as follows:
</para>
<programlisting xml:lang="en"><code>Glib::RefPtr<Gtk::Adjustment> Gtk::Adjustment::create(
double value,
double lower,
double upper,
double step_increment = 1,
double page_increment = 10,
double page_size = 0);</code></programlisting>
<para xml:lang="en">
The <parameter>value</parameter> argument is the initial value of the
adjustment, usually corresponding to the topmost or leftmost position of an
adjustable widget. The <parameter>lower</parameter> and
<parameter>upper</parameter> arguments specify the possible range of values
which the adjustment can hold. The
<parameter>step_increment</parameter> argument specifies the smaller of
the two increments by which the user can change the value, while the
<parameter>page_increment</parameter> is the larger one. The
<parameter>page_size</parameter> argument usually corresponds somehow to
the visible area of a panning widget. The <parameter>upper</parameter> argument
is used to represent the bottommost or rightmost coordinate in a panning
widget's child.
<!-- TODO: Investigate the upper argument properly. There was some unclear stuff about it not always being the upper value. -->
</para>
</section>
<section xml:id="sec-adjustments-easy">
<title>Usar ajustes de la manera fácil</title>
<para>Los widgets ajustables pueden dividirse en aquellos que usan y requieren unidades específicas para estos valores, y aquellos que los tratan como números arbitrarios.</para>
<para>El grupo que trata los valores como números arbitrarios incluye los widgets <classname>Range</classname> (<classname>Scrollbar</classname> y <classname>Scale</classname>), el widget <classname>ScaleButton</classname> y el widget <classname>SpinButton</classname>. Típicamente, el usuario «ajusta» directamente a estos widgets mediante el teclado o el ratón. Tratarán a los valores <parameter>lower</parameter> y <parameter>upper</parameter> de un ajuste como un rango dentro del cual el usuario podrá manipular el <parameter>value</parameter> del ajuste. De manera predeterminada, sólo modificarán el <parameter>value</parameter> de un ajuste.</para>
<para>El otro grupo incluye al widget <classname>Viewport</classname> y al widget <classname>ScrolledWindow</classname>. Todos estos widgets usan valores de píxel para sus ajustes. Típicamente, estos también se ajustan indirectamente usando barras de desplazamiento. A pesar de que todos los widgets que usan ajustes pueden crear los suyos o usar los que se les proporcionan, generalmente querrá dejarle a esta categoría particular de widgets crear sus propios ajustes.</para>
<para>Si comparte un objeto de ajuste entre una barra de desplazamiento y un widget «TextView», manipular la barra de desplazamiento ajustará automágicamente al widget «TextView». Puede establecerlo así:</para>
<programlisting xml:lang="en"><code>// creates its own adjustments
Gtk::TextView textview;
// uses the newly-created adjustment for the scrollbar as well
Gtk::Scrollbar vscrollbar(textview.get_vadjustment(), Gtk::Orientation::VERTICAL);</code></programlisting>
</section>
<section xml:id="sec-adjustment-internals">
<title>Interioridades del ajuste</title>
<para>Hasta aquí está todo bien, pero ¿y si quiere crear sus propios gestores para responder cuando el usuario ajusta un widget <classname>Range</classname> o un <classname>SpinButton</classname>? Para acceder al valor de un <classname>Gtk::Adjustment</classname>, puede usar los métodos <methodname>get_value()</methodname> y <methodname>set_value()</methodname>:</para>
<para>Como se mencionó anteriormente, <classname>Gtk::Adjustment</classname> puede emitir señales. Así es, por supuesto, cómo suceden las actualizaciones automáticamente cuando comparte un objeto <classname>Adjustment</classname> entre una <classname>Scrollbar</classname> y otro widget ajustable; todos los widgets ajustables conectan gestores de señales a la señal <literal>value_changed</literal> de sus ajustes, como también puede su programa.</para>
<para>Entonces, por ejemplo, si tiene un widget <classname>Scale</classname>, y quiere cambiar la rotación de una imagen cuando su valor cambia, podría crear un gestor de señales de esta forma:</para>
<programlisting xml:lang="en"><code>void cb_rotate_picture(MyPicture* picture)
{
picture->set_rotation(adj->get_value());
...</code></programlisting>
<para>y conectarlo al ajuste del widget de escala así:</para>
<programlisting xml:lang="en"><code>adj->signal_value_changed().connect(sigc::bind<MyPicture*>(sigc::mem_fun(*this,
&cb_rotate_picture), picture));</code></programlisting>
<para>¿Qué pasa si un widget reconfigura los campos <parameter>upper</parameter> o <parameter>lower</parameter> de su <classname>Adjustment</classname>, como cuando un usuario le añade más texto a un widget de texto? En este caso, emite la señal <literal>changed</literal>.</para>
<para>Los widgets <classname>Range</classname> típicamente conectan un gestor a esta señal, que cambia su apariencia para reflejar el cambio: por ejemplo, el tamaño de un control deslizante en una barra de desplazamiento crecerá o se encogerá de manera inversamente proporcional a la diferencia entre los valores <parameter>lower</parameter> y <parameter>upper</parameter> de su <classname>Adjustment</classname>.</para>
<para>Probablemente nunca necesite adjuntarle un gestor a esta señal, a menos que esté escribiendo un nuevo tipo de widget de rango.</para>
<programlisting xml:lang="en"><code>adjustment->signal_changed();</code></programlisting>
</section>
</chapter>
<chapter xml:id="chapter-dialogs">
<title>Diálogos</title>
<note><para xml:lang="en"><classname>Gtk::Dialog</classname> and the classes derived from it,
are deprecated since <application>gtkmm</application> 4.10. They can still be used in <application>gtkmm</application>4 applications,
provided GTKMM_DISABLE_DEPRECATED and GDKMM_DISABLE_DEPRECATED are not defined.
Some of the dialog classes are replaced by classes that are available since <application>gtkmm</application> 4.10.
</para>
<para xml:lang="en">The examples in this chapter use classes that are available since <application>gtkmm</application> 4.10.
Similar examples with the deprecated classes are available in the
<link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/gtkmm-4-0/examples/book/dialogs/">
gtkmm-4-0 branch</link> in the git repository.
</para></note>
<para xml:lang="en">
Dialogs are used as secondary windows, to provide specific information or to
ask questions. <classname>Gtk::Dialog</classname> windows contain a few pre-packed
widgets to ensure consistency, and a <literal>response</literal> signal which
is emitted when the user dismisses the dialog.
</para>
<para>Hay varias clases <classname>Dialog</classname> derivadas que podría encontrar útiles. <classname>Gtk::MessageDialog</classname> se usa para las notificaciones más simples. Pero en otras ocasiones, tal vez necesite derivar su propia clase de diálogo para proporcionar una funcionalidad más compleja.</para>
<para>Para empaquetar widgets en un diálogo personalizado, debe empaquetarlos en la <classname>Gtk::Box</classname>, disponible a través de <methodname>get_content_area()</methodname>. Para simplemente añadirle un <classname>Button</classname> a la parte inferior del <classname>Dialog</classname>, puede usar el método <methodname>add_button()</methodname>.</para>
<para xml:lang="en">
The <literal>response</literal> signal handler receives an <literal>int</literal>. This
may be a value from the <type>Gtk::ResponseType</type> if the user
closed the dialog by clicking a standard button, or it could be the custom
response value that you specified when using <methodname>add_button()</methodname>.
</para>
<para xml:lang="en">
To show the dialog, call <methodname>set_visible(true)</methodname>. If the same dialog instance
will be shown several times, you must also call <methodname>set_hide_on_close()</methodname>,
or else the dialog will be destroyed when it's closed.
Connect to the <literal>response</literal> signal, if you want to know which button
was pressed. The <literal>response</literal> signal handler is also where you
should hide the dialog.
</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1Dialog.html">Reference</link></para>
<section xml:id="sec-dialogs-alertdialog">
<title xml:lang="en">AlertDialog and MessageDialog</title>
<para xml:lang="en">
<classname>MessageDialog</classname> (deprecated since <application>gtkmm</application> 4.10) and
<classname>AlertDialog</classname> (available since <application>gtkmm</application> 4.10) are convenience
classes, used to create simple, standard message dialogs, with a message and buttons
for user response.
</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1AlertDialog.html">AlertDialog Reference</link></para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1MessageDialog.html">MessageDialog Reference</link></para>
<section xml:id="alertdialog-example">
<title>Ejemplo</title>
<figure xml:id="figure-dialogs-alertdialog">
<title xml:lang="en">AlertDialog</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/dialogs_alertdialog.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/dialogs/alertdialog">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
~ExampleWindow() override;
protected:
//Signal handlers:
void on_button_info_clicked();
void on_button_question_clicked();
void on_question_dialog_finish(const Glib::RefPtr<Gio::AsyncResult>& result);
//Child widgets:
Gtk::Box m_ButtonBox;
Gtk::Button m_Button_Info;
Gtk::Button m_Button_Question;
Glib::RefPtr<Gtk::AlertDialog> m_pDialog;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/messagedialog.h>
#include <iostream>
ExampleWindow::ExampleWindow()
: m_ButtonBox(Gtk::Orientation::VERTICAL),
m_Button_Info("Show Info AlertDialog"),
m_Button_Question("Show Question AlertDialog")
{
set_title("Gtk::AlertDialog example");
set_child(m_ButtonBox);
m_ButtonBox.append(m_Button_Info);
m_Button_Info.set_expand(true);
m_Button_Info.signal_clicked().connect(sigc::mem_fun(*this,
&ExampleWindow::on_button_info_clicked) );
m_ButtonBox.append(m_Button_Question);
m_Button_Question.set_expand(true);
m_Button_Question.signal_clicked().connect(sigc::mem_fun(*this,
&ExampleWindow::on_button_question_clicked) );
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::on_button_info_clicked()
{
if (!m_pDialog)
m_pDialog = Gtk::AlertDialog::create();
else
{
// Reset values that may have been set by on_button_question_clicked().
m_pDialog->set_buttons({});
m_pDialog->set_default_button(-1);
m_pDialog->set_cancel_button(-1);
}
m_pDialog->set_message("This is an INFO AlertDialog.");
m_pDialog->set_detail("And this is the secondary text that explains things.");
m_pDialog->show(*this);
}
void ExampleWindow::on_button_question_clicked()
{
if (!m_pDialog)
m_pDialog = Gtk::AlertDialog::create();
m_pDialog->set_message("This is a QUESTION AlertDialog.");
m_pDialog->set_detail("And this is the secondary text that explains things.");
m_pDialog->set_buttons({"Cancel", "Retry", "OK"});
m_pDialog->set_default_button(2); // OK button or Return key
m_pDialog->set_cancel_button(0); // Cancel button or Escape key
m_pDialog->choose(*this,
sigc::mem_fun(*this, &ExampleWindow::on_question_dialog_finish));
}
void ExampleWindow::on_question_dialog_finish(const Glib::RefPtr<Gio::AsyncResult>& result)
{
try
{
const int response_id = m_pDialog->choose_finish(result);
switch (response_id)
{
case 0:
std::cout << "Cancel clicked." << std::endl;
break;
case 1:
std::cout << "Retry clicked." << std::endl;
break;
case 2:
std::cout << "OK clicked." << std::endl;
break;
default:
std::cout << "Unexpected response: " << response_id << std::endl;
break;
}
}
catch (const Gtk::DialogError& err)
{
// Can be thrown by m_pDialog->choose_finish(result).
std::cout << "DialogError, " << err.what() << std::endl;
}
catch (const Glib::Error& err)
{
std::cout << "Unexpected exception. " << err.what() << std::endl;
}
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
//Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</section>
<section xml:id="sec-dialogs-filedialog">
<title xml:lang="en">FileDialog and FileChooserDialog</title>
<para xml:lang="en">
The <classname>FileChooserDialog</classname> (deprecated since <application>gtkmm</application> 4.10) and
<classname>FileDialog</classname> (available since <application>gtkmm</application> 4.10) are suitable
for use with "Open" or "Save" menu items.
</para>
<para xml:lang="en">
Most of the useful member methods for <classname>FileChooserDialog</classname>
are actually in the <classname>Gtk::FileChooser</classname> base class.
</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1FileDialog.html">FileDialog Reference</link></para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1FileChooserDialog.html">FileChooserDialog Reference</link></para>
<section xml:id="filedialog-example">
<title>Ejemplo</title>
<figure xml:id="figure-dialogs-filedialog">
<title xml:lang="en">FileDialog</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/dialogs_filedialog.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/dialogs/filedialog">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
~ExampleWindow() override;
protected:
//Signal handlers:
void on_button_file_clicked();
void on_button_folder_clicked();
void on_file_dialog_finish(const Glib::RefPtr<Gio::AsyncResult>& result,
const Glib::RefPtr<Gtk::FileDialog>& dialog);
void on_folder_dialog_finish(const Glib::RefPtr<Gio::AsyncResult>& result,
const Glib::RefPtr<Gtk::FileDialog>& dialog);
//Child widgets:
Gtk::Box m_ButtonBox;
Gtk::Button m_Button_File;
Gtk::Button m_Button_Folder;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <iostream>
ExampleWindow::ExampleWindow()
: m_ButtonBox(Gtk::Orientation::VERTICAL),
m_Button_File("Choose File"),
m_Button_Folder("Choose Folder")
{
set_title("Gtk::FileDialog example");
set_child(m_ButtonBox);
m_ButtonBox.append(m_Button_File);
m_Button_File.set_expand(true);
m_Button_File.signal_clicked().connect(sigc::mem_fun(*this,
&ExampleWindow::on_button_file_clicked) );
m_ButtonBox.append(m_Button_Folder);
m_Button_Folder.set_expand(true);
m_Button_Folder.signal_clicked().connect(sigc::mem_fun(*this,
&ExampleWindow::on_button_folder_clicked) );
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::on_button_file_clicked()
{
auto dialog = Gtk::FileDialog::create();
// Add filters, so that only certain file types can be selected:
auto filters = Gio::ListStore<Gtk::FileFilter>::create();
auto filter_text = Gtk::FileFilter::create();
filter_text->set_name("Text files");
filter_text->add_mime_type("text/plain");
filters->append(filter_text);
auto filter_cpp = Gtk::FileFilter::create();
filter_cpp->set_name("C/C++ files");
filter_cpp->add_mime_type("text/x-c");
filter_cpp->add_mime_type("text/x-c++");
filter_cpp->add_mime_type("text/x-c-header");
filters->append(filter_cpp);
auto filter_any = Gtk::FileFilter::create();
filter_any->set_name("Any files");
filter_any->add_pattern("*");
filters->append(filter_any);
dialog->set_filters(filters);
// Show the dialog and wait for a user response:
dialog->open(sigc::bind(sigc::mem_fun(
*this, &ExampleWindow::on_file_dialog_finish), dialog));
}
void ExampleWindow::on_file_dialog_finish(const Glib::RefPtr<Gio::AsyncResult>& result,
const Glib::RefPtr<Gtk::FileDialog>& dialog)
{
// Handle the response:
try
{
auto file = dialog->open_finish(result);
// Notice that this is a std::string, not a Glib::ustring.
auto filename = file->get_path();
std::cout << "File selected: " << filename << std::endl;
}
catch (const Gtk::DialogError& err)
{
// Can be thrown by dialog->open_finish(result).
std::cout << "No file selected. " << err.what() << std::endl;
}
catch (const Glib::Error& err)
{
std::cout << "Unexpected exception. " << err.what() << std::endl;
}
}
void ExampleWindow::on_button_folder_clicked()
{
auto dialog = Gtk::FileDialog::create();
// Show the dialog and wait for a user response:
dialog->select_folder(sigc::bind(sigc::mem_fun(
*this, &ExampleWindow::on_folder_dialog_finish), dialog));
}
void ExampleWindow::on_folder_dialog_finish(const Glib::RefPtr<Gio::AsyncResult>& result,
const Glib::RefPtr<Gtk::FileDialog>& dialog)
{
// Handle the response:
try
{
auto folder = dialog->select_folder_finish(result);
std::cout << "Folder selected: " << folder->get_path() << std::endl;
}
catch (const Gtk::DialogError& err)
{
// Can be thrown by dialog->select_folder_finish(result).
std::cout << "No folder selected. " << err.what() << std::endl;
}
catch (const Glib::Error& err)
{
std::cout << "Unexpected exception. " << err.what() << std::endl;
}
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
//Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</section>
<section xml:id="sec-dialogs-colordialog">
<title xml:lang="en">ColorDialog and ColorChooserDialog</title>
<para xml:lang="en">
The <classname>ColorChooserDialog</classname> (deprecated since <application>gtkmm</application> 4.10) and
<classname>ColorDialog</classname> (available since <application>gtkmm</application> 4.10) allow the user
to choose a color. The <classname>ColorButton</classname> (deprecated since <application>gtkmm</application> 4.10)
and <classname>ColorDialogButton</classname> (available since <application>gtkmm</application> 4.10) open
a color selection dialog when it is clicked.
</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1ColorDialog.html">ColorDialog Reference</link></para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1ColorChooserDialog.html">ColorChooserDialog Reference</link></para>
<section xml:id="colordialog-example">
<title>Ejemplo</title>
<figure xml:id="figure-dialogs-colordialog">
<title xml:lang="en">ColorDialog</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/dialogs_colordialog.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/dialogs/colordialog">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
~ExampleWindow() override;
protected:
//Signal handlers:
void on_color_button_color_set();
void on_button_dialog_clicked();
void on_dialog_finish(const Glib::RefPtr<Gio::AsyncResult>& result);
//Draw function:
void on_drawing_area_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height);
//Child widgets:
Gtk::Box m_VBox;
Gtk::ColorDialogButton m_ColorDialogButton;
Gtk::Button m_Button_Dialog;
Gtk::DrawingArea m_DrawingArea; //To show the color.
Glib::RefPtr<Gtk::ColorDialog> m_pDialog;
Gdk::RGBA m_Color;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <iostream>
ExampleWindow::ExampleWindow()
: m_VBox(Gtk::Orientation::VERTICAL, 5),
m_Button_Dialog("Choose Color")
{
set_title("Gtk::ColorDialog example");
set_default_size(200, 200);
m_pDialog = Gtk::ColorDialog::create();
m_ColorDialogButton.set_dialog(m_pDialog);
set_child(m_VBox);
m_VBox.append(m_ColorDialogButton);
m_ColorDialogButton.property_rgba().signal_changed().connect(
sigc::mem_fun(*this, &ExampleWindow::on_color_button_color_set));
m_VBox.append(m_Button_Dialog);
m_Button_Dialog.signal_clicked().connect(sigc::mem_fun(*this,
&ExampleWindow::on_button_dialog_clicked) );
//Set start color:
m_Color.set_red(0.0);
m_Color.set_green(0.0);
m_Color.set_blue(1.0);
m_Color.set_alpha(1.0); //opaque
m_ColorDialogButton.set_rgba(m_Color);
m_VBox.append(m_DrawingArea);
m_DrawingArea.set_expand(true);
m_DrawingArea.set_draw_func(sigc::mem_fun(*this, &ExampleWindow::on_drawing_area_draw));
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::on_color_button_color_set()
{
//Store the chosen color:
m_Color = m_ColorDialogButton.get_rgba();
m_DrawingArea.queue_draw();
}
void ExampleWindow::on_button_dialog_clicked()
{
m_pDialog->choose_rgba(*this, m_Color,
sigc::mem_fun(*this, &ExampleWindow::on_dialog_finish));
}
void ExampleWindow::on_dialog_finish(const Glib::RefPtr<Gio::AsyncResult>& result)
{
try
{
// If this call changes the color, it will trigger a call to
// on_color_button_color_set().
m_ColorDialogButton.set_rgba(m_pDialog->choose_rgba_finish(result));
// This is what you should do if m_ColorDialogButton and
// on_color_button_color_set() did not exist:
//m_Color = m_pDialog->choose_rgba_finish(result);
//m_DrawingArea.queue_draw();
}
catch (const Gtk::DialogError& err)
{
// Can be thrown by m_pDialog->choose_rgba_finish(result).
std::cout << "No color selected. " << err.what() << std::endl;
}
catch (const Glib::Error& err)
{
std::cout << "Unexpected exception. " << err.what() << std::endl;
}
}
void ExampleWindow::on_drawing_area_draw(const Cairo::RefPtr<Cairo::Context>& cr, int, int)
{
Gdk::Cairo::set_source_rgba(cr, m_Color);
cr->paint();
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
//Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</section>
<section xml:id="sec-dialogs-fontdialog">
<title xml:lang="en">FontDialog and FontChooserDialog</title>
<para xml:lang="en">
The <classname>FontChooserDialog</classname> (deprecated since <application>gtkmm</application> 4.10) and
<classname>FontDialog</classname> (available since <application>gtkmm</application> 4.10) allow the user
to choose a font. The <classname>FontButton</classname> (deprecated since <application>gtkmm</application> 4.10)
and <classname>FontDialogButton</classname> (available since <application>gtkmm</application> 4.10) open
a font chooser dialog when it is clicked.
</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1FontDialog.html">FontDialog Reference</link></para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1FontChooserDialog.html">FontChooserDialog Reference</link></para>
<section xml:id="fontdialog-example">
<title>Ejemplo</title>
<figure xml:id="figure-dialogs-fontdialog">
<title xml:lang="en">FontDialog</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/dialogs_fontdialog.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/dialogs/fontdialog">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
~ExampleWindow() override;
protected:
//Signal handlers:
void on_font_button_font_set();
void on_button_dialog_clicked();
void on_dialog_finish(const Glib::RefPtr<Gio::AsyncResult>& result);
//Child widgets:
Gtk::Box m_ButtonBox;
Gtk::FontDialogButton m_FontDialogButton;
Gtk::Button m_Button_Dialog;
Glib::RefPtr<Gtk::FontDialog> m_pDialog;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <iostream>
ExampleWindow::ExampleWindow()
: m_ButtonBox(Gtk::Orientation::VERTICAL),
m_Button_Dialog("Choose Font")
{
set_title("Gtk::FontDialog example");
set_child(m_ButtonBox);
m_pDialog = Gtk::FontDialog::create();
m_ButtonBox.append(m_FontDialogButton);
m_FontDialogButton.set_dialog(m_pDialog);
m_FontDialogButton.set_font_desc(Pango::FontDescription("Sans 10"));
m_FontDialogButton.set_expand(true);
m_FontDialogButton.set_use_font(true);
m_FontDialogButton.set_use_size(true);
m_FontDialogButton.property_font_desc().signal_changed().connect(
sigc::mem_fun(*this, &ExampleWindow::on_font_button_font_set));
m_ButtonBox.append(m_Button_Dialog);
m_Button_Dialog.set_expand(true);
m_Button_Dialog.signal_clicked().connect(
sigc::mem_fun(*this, &ExampleWindow::on_button_dialog_clicked));
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::on_font_button_font_set()
{
auto font_name = m_FontDialogButton.get_font_desc().to_string();
std::cout << "Font chosen: " << font_name << std::endl;
}
void ExampleWindow::on_button_dialog_clicked()
{
// Get the previously selected font description from the FontDialogButton.
m_pDialog->choose_font(*this,
sigc::mem_fun(*this, &ExampleWindow::on_dialog_finish),
m_FontDialogButton.get_font_desc());
}
void ExampleWindow::on_dialog_finish(const Glib::RefPtr<Gio::AsyncResult>& result)
{
try
{
// If this call changes the font, it will trigger a call to
// on_font_button_font_set().
m_FontDialogButton.set_font_desc(m_pDialog->choose_font_finish(result));
}
catch (const Gtk::DialogError& err)
{
// Can be thrown by m_pDialog->choose_font_finish(result).
std::cout << "No font selected. " << err.what() << std::endl;
}
catch (const Glib::Error& err)
{
std::cout << "Unexpected exception. " << err.what() << std::endl;
}
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
//Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</section>
<section xml:id="sec-about-dialog">
<title>AboutDialog no modal</title>
<para>La clase <classname>AboutDialog</classname> ofrece una manera sencilla de mostrar información sobre el programa, como su logo, nombre, copyright, página web y licencia.</para>
<para xml:lang="en">
Most dialogs in this chapter are modal, that is, they freeze the rest of
the application while they are shown. It's also possible to create a non-modal
dialog, which does not freeze other windows in the application.
The following example shows a non-modal <classname>AboutDialog</classname>. This is
perhaps not the kind of dialog you would normally make non-modal, but non-modal
dialogs can be useful in other cases. E.g. <application>gedit</application>'s
search-and-replace dialog is non-modal.
</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1AboutDialog.html">Reference</link></para>
<section xml:id="aboutdialog-example">
<title>Ejemplo</title>
<figure xml:id="figure-dialogs-about">
<title>AboutDialog</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/dialogs_about.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/dialogs/aboutdialog">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
virtual ~ExampleWindow();
protected:
//Signal handlers:
void on_button_clicked();
//Child widgets:
Gtk::Box m_VBox;
Gtk::Label m_Label;
Gtk::Box m_ButtonBox;
Gtk::Button m_Button;
Gtk::AboutDialog m_Dialog;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <iostream>
ExampleWindow::ExampleWindow()
: m_VBox(Gtk::Orientation::VERTICAL),
m_Label("The AboutDialog is non-modal. "
"You can select parts of this text while the AboutDialog is shown."),
m_ButtonBox(Gtk::Orientation::VERTICAL),
m_Button("Show AboutDialog")
{
set_title("Gtk::AboutDialog example");
set_default_size(400, 150);
set_child(m_VBox);
m_VBox.append(m_Label);
m_Label.set_expand(true);
m_Label.set_wrap(true);
m_Label.set_selectable(true);
m_VBox.append(m_ButtonBox);
m_ButtonBox.append(m_Button);
m_Button.set_expand(true);
m_Button.set_halign(Gtk::Align::CENTER);
m_Button.set_valign(Gtk::Align::CENTER);
m_Button.signal_clicked().connect(sigc::mem_fun(*this,
&ExampleWindow::on_button_clicked) );
m_Dialog.set_transient_for(*this);
m_Dialog.set_hide_on_close();
m_Dialog.set_logo(Gdk::Texture::create_from_resource("/about/gtkmm_logo.gif"));
m_Dialog.set_program_name("Example application");
m_Dialog.set_version("1.0.0");
m_Dialog.set_copyright("Murray Cumming");
m_Dialog.set_comments("This is just an example application.");
m_Dialog.set_license("LGPL");
m_Dialog.set_website("http://www.gtkmm.org");
m_Dialog.set_website_label("gtkmm website");
std::vector<Glib::ustring> list_authors;
list_authors.push_back("Murray Cumming");
list_authors.push_back("Somebody Else");
list_authors.push_back("AN Other");
m_Dialog.set_authors(list_authors);
m_Button.grab_focus();
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::on_button_clicked()
{
m_Dialog.set_visible(true);
//Bring it to the front, in case it was already shown:
m_Dialog.present();
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
//Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>aboutdialog.gresource.xml</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/about">
<file>gtkmm_logo.gif</file>
</gresource>
</gresources>
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</section>
<section xml:id="sec-dialogs-windowdialog">
<title xml:lang="en">Custom Dialog</title>
<para xml:lang="en">
When none of the predefined dialog classes suit your needs, you can make your own
dialog by deriving a class from <classname>Window</classname> and fill it with
the widgets you need.
</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1Window.html">Window Reference</link></para>
<section xml:id="windowdialog-example">
<title>Ejemplo</title>
<figure xml:id="figure-dialogs-windowdialog">
<title xml:lang="en">Window Dialog</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/dialogs_windowdialog.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/dialogs/windowdialog">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm.h>
#include "namedialog.h"
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
~ExampleWindow() override;
protected:
//Signal handlers:
void on_button_clicked();
void on_dialog_response(const Glib::ustring& response);
void on_dialog_hidden();
//Child widgets:
Gtk::Button m_Button;
NameDialog m_Dialog;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>namedialog.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_NAME_DIALOG_H_
#define GTKMM_NAME_DIALOG_H_
#include <gtkmm.h>
class NameDialog : public Gtk::Window
{
public:
NameDialog();
~NameDialog() override;
void buttons_clicked_connect(const sigc::slot<void(const Glib::ustring&)>& slot);
Glib::ustring get_entry1() const;
Glib::ustring get_entry2() const;
protected:
//Member widgets:
Gtk::Grid m_Grid;
Gtk::Image m_Image;
Gtk::Label m_Label1;
Gtk::Label m_Label2;
Gtk::Entry m_Entry1;
Gtk::Entry m_Entry2;
Gtk::Box m_ButtonBox;
Gtk::Button m_Button_OK;
Gtk::Button m_Button_Cancel;
};
#endif /* GTKMM_NAME_DIALOG_H_ */
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <iostream>
ExampleWindow::ExampleWindow()
: m_Button("Show Dialog"),
m_Dialog()
{
set_title("Custom Dialog example");
set_child(m_Button);
m_Button.signal_clicked().connect(
sigc::mem_fun(*this, &ExampleWindow::on_button_clicked));
m_Dialog.set_default_size(250, 100);
m_Dialog.set_transient_for(*this);
m_Dialog.set_modal();
m_Dialog.set_hide_on_close();
m_Dialog.buttons_clicked_connect(
sigc::mem_fun(*this, &ExampleWindow::on_dialog_response));
m_Dialog.signal_hide().connect(
sigc::mem_fun(*this, &ExampleWindow::on_dialog_hidden));
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::on_button_clicked()
{
m_Dialog.set_visible(true);
}
void ExampleWindow::on_dialog_response(const Glib::ustring& response)
{
m_Dialog.set_visible(false);
if (response == "OK")
std::cout << "Name: " << m_Dialog.get_entry1() << " "
<< m_Dialog.get_entry2() << std::endl;
else
std::cout << response << " button clicked" << std::endl;
}
void ExampleWindow::on_dialog_hidden()
{
std::cout << "Dialog hidden" << std::endl;
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
//Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>namedialog.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[NameDialog::NameDialog()
: m_Label1("_First name", true),
m_Label2("_Second name", true),
m_ButtonBox(Gtk::Orientation::HORIZONTAL, 5),
m_Button_OK("_OK", true),
m_Button_Cancel("_Cancel", true)
{
set_destroy_with_parent(true);
set_title("Name Dialog");
set_child(m_Grid);
m_Grid.set_row_spacing(4);
m_Grid.set_column_spacing(4);
m_Grid.set_expand(true);
m_Image.set_from_icon_name("dialog-question");
m_Image.set_icon_size(Gtk::IconSize::LARGE);
m_Grid.attach(m_Image, 0, 0, 1, 2);
m_Grid.attach(m_Label1, 1, 0);
m_Grid.attach(m_Entry1, 2, 0);
m_Label1.set_mnemonic_widget(m_Entry1);
m_Grid.attach(m_Label2, 1, 1);
m_Grid.attach(m_Entry2, 2, 1);
m_Label2.set_mnemonic_widget(m_Entry2);
m_Grid.attach(m_ButtonBox, 0, 2, 3, 1);
m_ButtonBox.set_halign(Gtk::Align::END);
m_ButtonBox.append(m_Button_OK);
m_ButtonBox.append(m_Button_Cancel);
}
void NameDialog::buttons_clicked_connect(
const sigc::slot<void(const Glib::ustring&)>& slot)
{
m_Button_OK.signal_clicked().connect(sigc::bind(slot, "OK"));
m_Button_Cancel.signal_clicked().connect(sigc::bind(slot, "Cancel"));
}
Glib::ustring NameDialog::get_entry1() const
{
return m_Entry1.get_text();
}
Glib::ustring NameDialog::get_entry2() const
{
return m_Entry2.get_text();
}
NameDialog::~NameDialog()
{
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</section>
</chapter>
<chapter xml:id="chapter-drawingarea">
<title xml:lang="en">The DrawingArea Widget</title>
<para xml:lang="en">
The <classname>DrawingArea</classname> widget is a blank window that gives
you the freedom to create any graphic you desire. Along with that freedom
comes the responsibility to draw on the widget. When a
widget is first shown, or when it is covered and then uncovered again it
needs to redraw itself. Most widgets have code to do this, but the
<classname>DrawingArea</classname> does not, allowing you to write your own
draw function to determine how the contents of the widget will be drawn.
This is done by setting a draw function with a call to the
<methodname>set_draw_func()</methodname> member function.
</para>
<para xml:lang="en">
GTK uses the <link xlink:href="http://cairographics.org">Cairo</link> drawing API.
With <application>gtkmm</application>, you may use the <link xlink:href="http://www.cairographics.org/cairomm/">cairomm</link> C++ API for cairo.
</para>
<para>Puede dibujar formas muy sofisticadas usando Cairo, pero los métodos para hacerlo son bastante básicos. Cairo proporciona métodos para dibujar líneas rectas, curvas, y arcos (incluyendo círculos). Estas formas básicas pueden combinarse para crear formas más complejas y caminos que pueden llenarse con colores sólidos, gradientes, patrones, y otras cosas. Además, Cairo puede realizar transformaciones complejas, componer imágenes, y generar texto con «antialiasing».</para>
<note>
<title>Cairo y Pango</title>
<para>A pesar de que Cairo puede generar texto, no está pensado para reemplazar a Pango. Pango es una mejor elección si necesita generar texto más avanzado, por ejemplo, con ajuste de línea o elipses. Sólo debe dibujar texto con Cairo si éste es parte de un gráfico.</para>
</note>
<para>En esta sección del tutorial, se cubrirá el modelo básico de dibujo con Cairo, describiendo cada uno de los elementos básicos de dibujo (con ejemplos), y luego se prsentará una aplicación simple que usa Cairo para dibujar un widget de reloj personalizado.</para>
<section xml:id="sec-cairo-drawing-model">
<title>El modelo de dibujo de Cairo</title>
<para>El concepto básico de dibujar con Cairo implica definir caminos «invisibles» y luego tacharlos o rellenarlos para hacerlos visibles.</para>
<para xml:lang="en">
To do any drawing in <application>gtkmm</application> with Cairo, you must first get a
<classname>Cairo::Context</classname> object. This class holds all of the graphics state parameters that
describe how drawing is to be done. This includes information such as
line width, color, the surface to draw to, and many other things. This
allows the actual drawing functions to take fewer arguments to simplify
the interface. Usually, you use the <classname>Cairo::Context</classname>
that you get as input data to the draw function that you set with the call to
<methodname>set_draw_func()</methodname>. It's also possible to create
a <classname>Cairo::Context</classname> by calling the
<methodname>Gdk::Surface::create_cairo_context()</methodname> and
<methodname>Gdk::CairoContext::cairo_create()</methodname> functions.
Since Cairo contexts are reference-counted objects, <methodname>cairo_create()</methodname>
returns a <classname>Cairo::RefPtr<Cairo::Context></classname> object.
(Note the difference between <classname>Gdk::CairoContext</classname>
and <classname>Cairo::Context</classname>.)
</para>
<para>El siguiente ejemplo muestra cómo crear un contexto Cairo con un color de frente rojo y un ancho de 2. Cualquier función de dibujo que use este contexto usará esta configuración</para>
<programlisting xml:lang="en"><code>Gtk::DrawingArea myArea;
auto gdkCairoContext = myArea.get_surface()->create_cairo_context();
auto myContext = gdkCairoContext->cairo_create();
myContext->set_source_rgb(1.0, 0.0, 0.0);
myContext->set_line_width(2.0);
</code></programlisting>
<para xml:lang="en">
Each <classname>Cairo::Context</classname> is associated with a
particular <classname>Gdk::Surface</classname>, so the first line of the
above example creates a <classname>Gtk::DrawingArea</classname> widget
and the next two lines use its associated <classname>Gdk::Surface</classname>
to create a <classname>Cairo::Context</classname> object. The final
two lines change the graphics state of the context.
</para>
<para xml:lang="en">
There are a number of graphics state variables that can be set for a
Cairo context. The most common context attributes are color (using
<methodname>set_source_rgb()</methodname> or
<methodname>set_source_rgba()</methodname> for translucent colors), line
width (using <methodname>set_line_width()</methodname>), line dash pattern
(using <methodname>set_dash()</methodname>), line cap style (using
<methodname>set_line_cap()</methodname>), and line join style (using
<methodname>set_line_join()</methodname>), and font styles (using
<methodname>set_font_size()</methodname>,
<methodname>set_font_face()</methodname> and others).
There are many other settings as well, such as transformation matrices,
fill rules, whether to perform antialiasing, and others. For further
information, see the <link xlink:href="http://www.cairographics.org/cairomm/">cairomm</link> API documentation.
</para>
<para xml:lang="en">
The current state of a <classname>Cairo::Context</classname> can be
saved to an internal stack of saved states and later be restored to the
state it was in when you saved it. To do this, use the
<methodname>save()</methodname>
method and the <methodname>restore()</methodname> method. This can be
useful if you need to temporarily change the line width and color (or
any other graphics setting) in order to draw something and then return
to the previous settings. In this situation, you could call
<methodname>Cairo::Context::save()</methodname>, change the graphics
settings, draw the lines, and then call
<methodname>Cairo::Context::restore()</methodname> to restore the original
graphics state. Multiple calls to <methodname>save()</methodname> and
<methodname>restore()</methodname> can be nested; each call to
<methodname>restore()</methodname> restores the state from the
matching paired <methodname>save()</methodname>.
<tip>
<para xml:lang="en">It is good practice to put all modifications to the graphics state
between <methodname>save()</methodname>/<methodname>restore()</methodname>
function calls. For example, if you have a function that takes a
<classname>Cairo::Context</classname> reference as an argument, you
might implement it as follows:
</para>
<programlisting xml:lang="en"><code>void doSomething(const Cairo::RefPtr<Cairo::Context>& context, int x)
{
context->save();
// change graphics state
// perform drawing operations
context->restore();
}</code></programlisting>
</tip>
</para>
<para xml:lang="en">
The draw function that you set with a call to <methodname>set_draw_func()</methodname>
is called with a Cairo context that you shall use for drawing in the
<classname>Gtk::DrawingArea</classname> widget. It is not necessary to
save and restore this Cairo context in the draw function.
</para>
</section>
<section xml:id="sec-cairo-drawing-lines">
<title>Dibujar Lineas Rectas</title>
<para xml:lang="en">
Now that we understand the basics of the Cairo graphics library, we're
almost ready to start drawing. We'll start with the simplest of
drawing elements: the straight line. But first you need to know a
little bit about Cairo's coordinate system. The origin of the Cairo
coordinate system is located in the upper-left corner of the window
with positive x values to the right and positive y values going down.
<tip>
<para xml:lang="en">Since the Cairo graphics library was written with support for
multiple output targets (the X window system, PNG images, OpenGL,
etc), there is a distinction between user-space and device-space
coordinates. The mapping between these two coordinate systems
defaults to one-to-one so that integer values map roughly to pixels
on the screen, but this setting can be adjusted if desired.
Sometimes it may be useful to scale the coordinates so that the
full width and height of a window both range from 0 to 1 (the 'unit
square') or some other mapping that works for your application.
This can be done with the
<methodname>Cairo::Context::scale()</methodname> function.</para>
</tip>
</para>
<section xml:id="cairo-example-lines">
<title>Ejemplo</title>
<para>En este ejemplo, se construirá un programa <application>gtkmm</application> pequeño pero funcional y se dibujarán algunas líneas en la ventana. Las líneas se dibujan creando un camino y luego rellenándolo. Un camino se crea usando las funciones <methodname>Cairo::Context::move_to()</methodname> y <methodname>Cairo::Context::line_to()</methodname>. La función <methodname>move_to()</methodname> es similar al acto de levantar el bolígrafo del papel y ponerlo en algún otro lado: no se dibuja ninguna línea entre el punto en el que estaba y el punto al que se movió. Para dibujar una línea entre dos puntos, use la función <methodname>line_to()</methodname>.</para>
<para>Después de terminar de crear su camino, todavía no ha dibujado nada visible. Para hacer el camino visible, debe usar la función <methodname>stroke()</methodname> que rellenará el camino actual con la anchura y estilo de línea que se ha especificado en su objeto <classname>Cairo::Context</classname>. Después de rellenar, el camino actual se despejará para que pueda comenzar el próximo.</para>
<tip>
<para>Muchas funciones de dibujo de Cairo tienen una variante <methodname>_preserve()</methodname>. Normalmente, las funciones de dibujo como <methodname>clip()</methodname>, <methodname>fill()</methodname>, o <methodname>stroke()</methodname> despejarán el camino actual. Si usa la variante <methodname>_preserve()</methodname>, el camino actual se retendrá, por lo que podrá usar el mismo camino con la próxima función de dibujo.</para>
</tip>
<figure xml:id="figure-drawingarea-lines">
<title>Área de dibujo: líneas</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/drawingarea_lines.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/drawingarea/simple">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>myarea.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLE_MYAREA_H
#define GTKMM_EXAMPLE_MYAREA_H
#include <gtkmm/drawingarea.h>
class MyArea : public Gtk::DrawingArea
{
public:
MyArea();
virtual ~MyArea();
protected:
void on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height);
};
#endif // GTKMM_EXAMPLE_MYAREA_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "myarea.h"
#include <gtkmm/application.h>
#include <gtkmm/window.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
protected:
MyArea m_area;
};
ExampleWindow::ExampleWindow()
{
set_title("DrawingArea");
set_child(m_area);
}
int main(int argc, char** argv)
{
auto app = Gtk::Application::create("org.gtkmm.example");
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>myarea.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "myarea.h"
#include <cairomm/context.h>
MyArea::MyArea()
{
set_draw_func(sigc::mem_fun(*this, &MyArea::on_draw));
}
MyArea::~MyArea()
{
}
void MyArea::on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height)
{
// coordinates for the center of the window
int xc, yc;
xc = width / 2;
yc = height / 2;
cr->set_line_width(10.0);
// draw red lines out from the center of the window
cr->set_source_rgb(0.8, 0.0, 0.0);
cr->move_to(0, 0);
cr->line_to(xc, yc);
cr->line_to(0, height);
cr->move_to(xc, yc);
cr->line_to(width, yc);
cr->stroke();
}
]]></code></programlisting>
<!-- end inserted example code -->
<para xml:lang="en">
This program contains a single class, <classname>MyArea</classname>,
which is a subclass of <classname>Gtk::DrawingArea</classname> and
contains an <methodname>on_draw()</methodname> member function.
This function becomes the draw function by a call to <methodname>set_draw_func()</methodname>
in <classname>MyArea</classname>'s constructor. <methodname>on_draw()</methodname>
is then called whenever the image in the drawing area needs to
be redrawn. It is passed a <classname>Cairo::RefPtr</classname>
pointer to a <classname>Cairo::Context</classname> that we use
for the drawing.
The actual drawing code sets the color we want to use for drawing by
using <methodname>set_source_rgb()</methodname> which takes arguments
defining the Red, Green, and Blue components of the desired color
(valid values are between 0 and 1). After setting the color, we
created a new path using the functions <methodname>move_to()</methodname>
and <methodname>line_to()</methodname>, and then stroked this path with
<methodname>stroke()</methodname>.
</para>
<tip>
<title>Dibujar con coordenadas relativas</title>
<para>En el ejemplo anterior, se ha dibujado todo usando coordenadas absolutas. También puede dibujar usando coordenadas relativas. Para una línea recta, esto se hace con la función <methodname>Cairo::Context::rel_line_to()</methodname>.</para>
</tip>
</section>
<section xml:id="cairo-line-styles">
<title>Estilos de línea</title>
<para>Además de dibujar líneas rectas básicas, también puede personalizar algunas cosas de las líneas. Ya ha visto ejemplos de cómo establecer el color y anchura de una línea, pero también hay otras cosas.</para>
<para>Si ha dibujado una serie de líneas que forman un camino, tal vez quiera que se junten de alguna manera. Cairo le ofrece tres maneras distintas de juntar líneas: «Miter», «Bevel», y «Round». Se muestran a continuación:</para>
<figure xml:id="figure-cairo-joins">
<title>Distintos tipos de uniones en Cairo</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/cairo_joins.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para>El estilo de unión de línea se establece usando la función <methodname>Cairo::Context::set_line_join()</methodname>.</para>
<para>Las puntas de las líneas pueden también tener distintos estilos. El estilo predeterminado consiste en que la línea comience y se detenga exactamente en sus puntos de destino. Esto se llama terminación «Butt». Las otras opciones son «Round» (usa una terminación redondeada, con el centro del círculo en el último punto) o «Square» (usa una terminación cuadrada, con el centro del cuadrado en el último punto). Esta opción se establece usando la función <methodname>Cairo::Context::set_line_cap()</methodname>.</para>
<para>Además, hay otras cosas que puede personalizar, incluyendo la creación de líneas punteadas y otras cosas. Para obtener más información, consulte la documentación de la API de Cairo.</para>
</section>
<section xml:id="sec-cairo-thin-lines">
<title>Dibujar líneas estrechas</title>
<para>Si intenta dibujar líneas de un píxel de anchura, notará que a veces la línea sale más borrosa y ancha de lo que debería. Esto sucede porque Cairo intentará dibujar desde la posición seleccionada, a ambos lados (mitad a cada uno), por que lo que si está posicionado justo en la intersección de los píxeles, y quiere líneas de un píxel de anchura, Cairo intentará usar la mitad de cada píxel adyacente, lo que no es posible (un píxel es la menor unidad posible). Esto sucede cuando la anchura de la línea es un número impar de píxeles (no sólo uno).</para>
<para xml:lang="en">
The trick is to position in the middle of the pixel where you want the
line to be drawn, and thus guaranteeing you get the desired results.
See <link xlink:href="http://cairographics.org/FAQ/#sharp_lines">Cairo FAQ</link>.
</para>
<figure xml:id="figure-drawingarea-thin-lines">
<title>Área de dibujo: líneas estrechas</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/drawingarea_thin_lines.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/drawingarea/thin_lines">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm/window.h>
#include <gtkmm/box.h>
#include <gtkmm/checkbutton.h>
#include "myarea.h"
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
virtual ~ExampleWindow();
protected:
//Signal handlers:
void on_button_toggled();
private:
Gtk::Box m_HBox;
MyArea m_Area_Lines;
Gtk::CheckButton m_Button_FixLines;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>myarea.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLE_MYAREA_H
#define GTKMM_EXAMPLE_MYAREA_H
#include <gtkmm/drawingarea.h>
class MyArea : public Gtk::DrawingArea
{
public:
MyArea();
virtual ~MyArea();
void fix_lines(bool fix = true);
protected:
void on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height);
private:
double m_fix;
};
#endif // GTKMM_EXAMPLE_MYAREA_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
ExampleWindow::ExampleWindow()
: m_HBox(Gtk::Orientation::HORIZONTAL),
m_Button_FixLines("Fix lines")
{
set_title("Thin lines example");
m_HBox.append(m_Area_Lines);
m_HBox.append(m_Button_FixLines);
set_child(m_HBox);
m_Button_FixLines.signal_toggled().connect(
sigc::mem_fun(*this, &ExampleWindow::on_button_toggled));
// Synchonize the drawing in m_Area_Lines with the state of the toggle button.
on_button_toggled();
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::on_button_toggled()
{
m_Area_Lines.fix_lines(m_Button_FixLines.get_active());
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char* argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
//Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>myarea.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "myarea.h"
MyArea::MyArea()
: m_fix (0)
{
set_content_width(200);
set_content_height(100);
set_draw_func(sigc::mem_fun(*this, &MyArea::on_draw));
}
MyArea::~MyArea()
{
}
void MyArea::on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height)
{
cr->set_line_width(1.0);
// draw one line, every two pixels
// without the 'fix', you won't notice any space between the lines,
// since each one will occupy two pixels (width)
for (int i = 0; i < width; i += 2)
{
cr->move_to(i + m_fix, 0);
cr->line_to(i + m_fix, height);
}
cr->stroke();
}
// Toogle between both values (0 or 0.5)
void MyArea::fix_lines(bool fix)
{
// to get the width right, we have to draw in the middle of the pixel
m_fix = fix ? 0.5 : 0.0;
// force the redraw of the image
queue_draw();
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</section>
<section xml:id="sec-cairo-curved-lines">
<title>Dibujar líneas curvas</title>
<para>Además de dibujar líneas rectas, Cairo le permite dibujar líneas curvas fácilmente. (técnicamente, una spline cúbica de Bézier) usando las funciones <methodname>Cairo::Context::curve_to()</methodname> y <methodname>Cairo::Context::rel_curve_to()</methodname>. Estas funciones toman las coordenadas de un punto de destino y de dos puntos de «control». Esto se entiende mejor con un ejemplo, así que analícelo en profundidad.</para>
<section xml:id="cairo-example-curves">
<title>Ejemplo</title>
<para>Esta aplicación simple dibuja una curva con Cairo y muestra los puntos de control para cada punta de la curva.</para>
<figure xml:id="figure-drawingarea-curve">
<title>Área de dibujo: líneas</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/drawingarea_curve.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/drawingarea/curve">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>myarea.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLE_MYAREA_H
#define GTKMM_EXAMPLE_MYAREA_H
#include <gtkmm/drawingarea.h>
class MyArea : public Gtk::DrawingArea
{
public:
MyArea();
virtual ~MyArea();
protected:
void on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height);
};
#endif // GTKMM_EXAMPLE_MYAREA_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "myarea.h"
#include <gtkmm/application.h>
#include <gtkmm/window.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
protected:
MyArea m_area;
};
ExampleWindow::ExampleWindow()
{
set_title("DrawingArea");
set_child(m_area);
}
int main(int argc, char** argv)
{
auto app = Gtk::Application::create("org.gtkmm.example");
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>myarea.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "myarea.h"
#include <cairomm/context.h>
MyArea::MyArea()
{
set_draw_func(sigc::mem_fun(*this, &MyArea::on_draw));
}
MyArea::~MyArea()
{
}
void MyArea::on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height)
{
double x0=0.1, y0=0.5, // start point
x1=0.4, y1=0.9, // control point #1
x2=0.6, y2=0.1, // control point #2
x3=0.9, y3=0.5; // end point
// scale to unit square (0 to 1 width and height)
cr->scale(width, height);
cr->set_line_width(0.05);
// draw curve
cr->move_to(x0, y0);
cr->curve_to(x1, y1, x2, y2, x3, y3);
cr->stroke();
// show control points
cr->set_source_rgba(1, 0.2, 0.2, 0.6);
cr->move_to(x0, y0);
cr->line_to (x1, y1);
cr->move_to(x2, y2);
cr->line_to (x3, y3);
cr->stroke();
}
]]></code></programlisting>
<!-- end inserted example code -->
<para>La única diferencia entre este ejemplo y el de la línea recta está en la función <methodname>on_draw()</methodname>, pero hay unos conceptos y funciones nuevas presentadas aquí, así que se examinarán brevemente.</para>
<para>Se hace una llamada a <methodname>Cairo::Context::scale()</methodname>, pasándole la anchura y altura del área de dibujo. Esto escala el sistema de coordenadas de espacio del usuario de tal manera que la anchura y altura del widget correspondan ambas a 1.0 «unidades». No hay una razón en particular para escalar el sistema de coordenadas en este caso, pero a veces, puede hacer las operaciones de dibujo más fáciles.</para>
<para xml:lang="en">
The call to <methodname>Cairo::Context::curve_to()</methodname> should
be fairly self-explanatory. The first pair of coordinates define
the control point for the beginning of the curve. The second set
of coordinates define the control point for the end of the curve,
and the last set of coordinates define the destination point. To
make the concept of control points a bit easier to visualize, a
line has been drawn from each control point to the end-point on the
curve that it is associated with. Note that these control point
lines are both translucent. This is achieved with a variant of
<methodname>set_source_rgb()</methodname> called
<methodname>set_source_rgba()</methodname>. This function takes a
fourth argument specifying the alpha value of the color (valid
values are between 0 and 1).
</para>
</section>
</section>
<section xml:id="sec-cairo-drawing-arcs">
<title>Dibujar arcos y círculos</title>
<para>Con Cairo, la misma función se usa para dibujar arcos, círculos, o elipses: <methodname>Cairo::Context::arc()</methodname>. Esta función toma cinco argumentos. Los dos primeros son las coordenadas del punto central del arco, el tercer argumento es el radio del arco, y los últimos dos argumentos definen el ángulo de inicio y fin del arco. Todos los ángulos se definen en radianes, por lo que dibujar un círculo es lo mismo que dibujar un arco de 0 a 2 * M_PI radianes. Un ángulo de 0 está en la dirección del eje positivo de X (en espacio de usuario). Un ángulo de M_PI/2 radianes (90 grados) está en la dirección del eje positivo de Y (en espacio de usuario). Los ángulos se incrementan en la dirección del eje positivo de X hacia el eje positivo de Y, por lo que con la matriz de transformación predeterminada, los ángulos incrementan en la dirección de las agujas del reloj (recuerde que el eje positivo de Y apunta hacia abajo).</para>
<para xml:lang="en">
To draw an ellipse, you can scale the current transformation matrix
by different amounts in the X and Y directions. For example, to draw
an ellipse with center at <varname>x</varname>, <varname>y</varname>
and size <varname>width</varname>, <varname>height</varname>:
</para>
<programlisting xml:lang="en"><code>context->save();
context->translate(x, y);
context->scale(width / 2.0, height / 2.0);
context->arc(0.0, 0.0, 1.0, 0.0, 2 * M_PI);
context->restore();</code></programlisting>
<section xml:id="cairo-example-arcs">
<title>Ejemplo</title>
<para>Aquí hay un programa simple de ejemplo que dibuja un arco, un círculo, y una elipse en un área de dibujo.</para>
<figure xml:id="figure-drawingarea-arc">
<title>Área de dibujo: arcos</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/drawingarea_arcs.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/drawingarea/arcs">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>myarea.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLE_MYAREA_H
#define GTKMM_EXAMPLE_MYAREA_H
#include <gtkmm/drawingarea.h>
class MyArea : public Gtk::DrawingArea
{
public:
MyArea();
virtual ~MyArea();
protected:
void on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height);
};
#endif // GTKMM_EXAMPLE_MYAREA_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "myarea.h"
#include <gtkmm/application.h>
#include <gtkmm/window.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
protected:
MyArea m_area;
};
ExampleWindow::ExampleWindow()
{
set_title("DrawingArea");
set_child(m_area);
}
int main(int argc, char** argv)
{
auto app = Gtk::Application::create("org.gtkmm.example");
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>myarea.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "myarea.h"
#include <cairomm/context.h>
#include <cmath>
MyArea::MyArea()
{
set_draw_func(sigc::mem_fun(*this, &MyArea::on_draw));
}
MyArea::~MyArea()
{
}
void MyArea::on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height)
{
const int lesser = std::min(width, height);
// coordinates for the center of the window
const int xc = width / 2;
const int yc = height / 2;
cr->set_line_width(lesser * 0.02); // outline thickness changes
// with window size
// first draw a simple unclosed arc
cr->save();
cr->arc(width / 3.0, height / 4.0, lesser / 4.0, -(M_PI / 5.0), M_PI);
cr->close_path(); // line back to start point
cr->set_source_rgb(0.0, 0.8, 0.0);
cr->fill_preserve();
cr->restore(); // back to opaque black
cr->stroke(); // outline it
// now draw a circle
cr->save();
cr->arc(xc, yc, lesser / 4.0, 0.0, 2.0 * M_PI); // full circle
cr->set_source_rgba(0.0, 0.0, 0.8, 0.6); // partially translucent
cr->fill_preserve();
cr->restore(); // back to opaque black
cr->stroke();
// and finally an ellipse
double ex, ey, ew, eh;
// center of ellipse
ex = xc;
ey = 3.0 * height / 4.0;
// ellipse dimensions
ew = 3.0 * width / 4.0;
eh = height / 3.0;
cr->save();
cr->translate(ex, ey); // make (ex, ey) == (0, 0)
cr->scale(ew / 2.0, eh / 2.0); // for width: ew / 2.0 == 1.0
// for height: eh / 2.0 == 1.0
cr->arc(0.0, 0.0, 1.0, 0.0, 2 * M_PI); // 'circle' centered at (0, 0)
// with 'radius' of 1.0
cr->set_source_rgba(0.8, 0.0, 0.0, 0.7);
cr->fill_preserve();
cr->restore(); // back to opaque black
cr->stroke();
}
]]></code></programlisting>
<!-- end inserted example code -->
<para>Hay un par de cosas que debe tener en cuenta acerca de este código de ejemplo. Nuevamente, la única diferencia real entre este ejemplo y los anteriores es la función <methodname>on_draw()</methodname>, por lo que nos limitaremos a trabajar esa función. Además, la primera parte de la función es casi idéntica a la de los ejemplos previos, por lo que se omitirá ese fragmento.</para>
<para>Tenga en cuenta que, en este caso, se ha expresado casi todo en términos de anchura y altura de la ventana, incluyendo la anchura de las líneas. Es por esto que, cuando cambie el tamaño de la ventana, todo se escalará a ella. Además, tenga en cuenta que hay tres secciones de dibujo en la función, y cada una está envuelta en un par <methodname>save()</methodname>/<methodname>restore()</methodname> para volver a un estado conocido después de cada dibujo.</para>
<para>La sección para dibujar un arco presenta una nueva función, <methodname>close_path()</methodname>. Esta función, en efecto, dibujará una línea recta desde el punto actual de vuelta al primer punto en el camino. Sin embargo, hay una diferencia significativa entre llamar a <methodname>close_path()</methodname> y dibujar una línea manualmente al punto de inicio. Si usa <methodname>close_path()</methodname>, las líneas se juntarán suavemente. Si usa <methodname>line_to()</methodname> en su lugar, las líneas terminarán en el mismo lugar, pero Cairo no las juntará de manera especial.</para>
<note>
<title>Dibujar en sentido anti-horario</title>
<para>La función <methodname>Cairo::Context::arc_negative()</methodname> es exactamente la misma que <methodname>Cairo::Context::arc()</methodname>, pero los ángulos van en la dirección opuesta.</para>
</note>
</section>
</section>
<section xml:id="sec-drawing-text">
<title>Dibujar texto</title>
<section xml:id="drawing-text-pango">
<title>Dibujar texto con Pango</title>
<para>El texto se dibuja a través de disposiciones de Pango. La manera más fácil de crear una <classname>Pango::Layout</classname> es usar <methodname>Gtk::Widget::create_pango_layout()</methodname>. Una vez creada, la disposición puede manipularse de varias maneras, incluyendo cambiar el texto, la tipografía, etc. Finalmente, la disposición puede mostrarse usando el método <methodname>Pango::Layout::show_in_cairo_context()</methodname>.</para>
</section>
<section xml:id="pango-text-example">
<title>Ejemplo</title>
<para xml:lang="en">
Here is an example of a program that draws some text, some of it
upside-down. The Printing chapter contains another
<link linkend="sec-printing-examples">example</link> of drawing text.
</para>
<figure xml:id="figure-drawingarea-pango-text">
<title>Área de dibujo: texto</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/drawingarea_pango_text.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/drawingarea/pango_text">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>myarea.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLE_MYAREA_H
#define GTKMM_EXAMPLE_MYAREA_H
#include <gtkmm/drawingarea.h>
class MyArea : public Gtk::DrawingArea
{
public:
MyArea();
virtual ~MyArea();
protected:
void on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height);
private:
void draw_rectangle(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height);
void draw_text(const Cairo::RefPtr<Cairo::Context>& cr, int rectangle_width, int rectangle_height);
};
#endif // GTKMM_EXAMPLE_MYAREA_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "myarea.h"
#include <gtkmm/application.h>
#include <gtkmm/window.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
protected:
MyArea m_area;
};
ExampleWindow::ExampleWindow()
{
set_title("Drawing text example");
set_child(m_area);
}
int main(int argc, char* argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>myarea.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "myarea.h"
MyArea::MyArea()
{
set_draw_func(sigc::mem_fun(*this, &MyArea::on_draw));
}
MyArea::~MyArea()
{
}
void MyArea::on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height)
{
const int rectangle_width = width;
const int rectangle_height = height / 2;
// Draw a black rectangle
cr->set_source_rgb(0.0, 0.0, 0.0);
draw_rectangle(cr, rectangle_width, rectangle_height);
// and some white text
cr->set_source_rgb(1.0, 1.0, 1.0);
draw_text(cr, rectangle_width, rectangle_height);
// flip the image vertically
// see http://www.cairographics.org/documentation/cairomm/reference/classCairo_1_1Matrix.html
// the -1 corresponds to the yy part (the flipping part)
// the height part is a translation (we could have just called cr->translate(0, height) instead)
// it's height and not height / 2, since we want this to be on the second part of our drawing
// (otherwise, it would draw over the previous part)
Cairo::Matrix matrix(1.0, 0.0, 0.0, -1.0, 0.0, height);
// apply the matrix
cr->transform(matrix);
// white rectangle
cr->set_source_rgb(1.0, 1.0, 1.0);
draw_rectangle(cr, rectangle_width, rectangle_height);
// black text
cr->set_source_rgb(0.0, 0.0, 0.0);
draw_text(cr, rectangle_width, rectangle_height);
}
void MyArea::draw_rectangle(const Cairo::RefPtr<Cairo::Context>& cr,
int width, int height)
{
cr->rectangle(0, 0, width, height);
cr->fill();
}
void MyArea::draw_text(const Cairo::RefPtr<Cairo::Context>& cr,
int rectangle_width, int rectangle_height)
{
// https://gnome.pages.gitlab.gnome.org/pangomm/classPango_1_1FontDescription.html
Pango::FontDescription font;
font.set_family("Monospace");
font.set_weight(Pango::Weight::BOLD);
// https://gnome.pages.gitlab.gnome.org/pangomm/classPango_1_1Layout.html
auto layout = create_pango_layout("Hi there!");
layout->set_font_description(font);
int text_width;
int text_height;
//get the text dimensions (it updates the variables -- by reference)
layout->get_pixel_size(text_width, text_height);
// Position the text in the middle
cr->move_to((rectangle_width-text_width)/2, (rectangle_height-text_height)/2);
layout->show_in_cairo_context(cr);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
<!--
<sect2 id="drawing-text-cairo">
<title>Drawing Text with Cairo</title>
<warning>TODO: Add Cairo content.</warning>
</sect2>
-->
</section>
<section xml:id="sec-draw-images">
<title>Dibujar imágenes</title>
<para>Hay un método para dibujar desde un <classname>Gdk::Pixbuf</classname> a un <classname>Cairo::Context</classname>. Un búfer <classname>Gdk::Pixbuf</classname> es un envoltorio útil alrededor de un grupo de píxeles, que puede leerse desde archivos y manipularse de varias maneras.</para>
<para xml:lang="en">
Probably the most common way of creating
<classname>Gdk::Pixbuf</classname>s is to use
<methodname>Gdk::Pixbuf::create_from_file()</methodname> or
<methodname>Gdk::Pixbuf::create_from_resource()</methodname>,
which can read an image file, such as a png file into a pixbuf
ready for rendering.
</para>
<para>El <classname>Gdk::Pixbuf</classname> puede procesarse estableciéndolo como el patrón fuente del contexto de Cairo con <methodname>Gdk::Cairo::set_source_pixbuf()</methodname>. Después, dibuje la imagen con <methodname>Cairo::Context::paint()</methodname> (para dibujar la imagen entera), o <methodname>Cairo::Context::rectangle()</methodname> y <methodname>Cairo::Context::fill()</methodname> (para rellenar el rectángulo especificado). <methodname>set_source_pixbuf()</methodname> no es un miembro de <classname>Cairo::Context</classname>. Toma un <classname>Cairo::Context</classname> como primer parámetro.</para>
<para>Aquí hay un poco de código que junta todo: (tenga en cuenta que normalmente no cargaría la imagen cada vez en el gestor de señal de dibujo. Se muestra aquí sólo para mantener a todo junto).</para>
<programlisting xml:lang="en"><code>void MyArea::on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height)
{
auto image = Gdk::Pixbuf::create_from_file("myimage.png");
// Draw the image at 110, 90, except for the outermost 10 pixels.
Gdk::Cairo::set_source_pixbuf(cr, image, 100, 80);
cr->rectangle(110, 90, image->get_width()-20, image->get_height()-20);
cr->fill();
}</code></programlisting>
<section xml:id="cairo-example-image">
<title>Ejemplo</title>
<para xml:lang="en">
Here is an example of a simple program that draws an image.
The program loads the image from a resource file. See the <link linkend="sec-gio-resource">Gio::Resource and glib-compile-resources</link>
section. Use <application>glib-compile-resources</application> to compile
the resources into a C source file that can be compiled and
linked with the C++ code. E.g.
<screen xml:lang="en">$ glib-compile-resources --target=resources.c --generate-source image.gresource.xml</screen>
</para>
<figure xml:id="figure-drawingarea-image">
<title>Área de dibujo: imagen</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/drawingarea_image.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/drawingarea/image">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>myarea.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLE_MYAREA_H
#define GTKMM_EXAMPLE_MYAREA_H
#include <gtkmm/drawingarea.h>
#include <gdkmm/pixbuf.h>
class MyArea : public Gtk::DrawingArea
{
public:
MyArea();
virtual ~MyArea();
protected:
void on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height);
Glib::RefPtr<Gdk::Pixbuf> m_image;
};
#endif // GTKMM_EXAMPLE_MYAREA_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "myarea.h"
#include <gtkmm/application.h>
#include <gtkmm/window.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
protected:
MyArea m_area;
};
ExampleWindow::ExampleWindow()
{
set_title("DrawingArea");
set_default_size(300, 200);
set_child(m_area);
}
int main(int argc, char** argv)
{
auto app = Gtk::Application::create("org.gtkmm.example");
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>myarea.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "myarea.h"
#include <cairomm/context.h>
#include <giomm/resource.h>
#include <gdkmm/general.h> // set_source_pixbuf()
#include <glibmm/fileutils.h>
#include <iostream>
MyArea::MyArea()
{
try
{
// The fractal image has been created by the XaoS program.
// http://xaos.sourceforge.net
m_image = Gdk::Pixbuf::create_from_resource("/image/fractal_image.png");
}
catch(const Gio::ResourceError& ex)
{
std::cerr << "ResourceError: " << ex.what() << std::endl;
}
catch(const Gdk::PixbufError& ex)
{
std::cerr << "PixbufError: " << ex.what() << std::endl;
}
// Show at least a quarter of the image.
if (m_image)
{
set_content_width(m_image->get_width()/2);
set_content_height(m_image->get_height()/2);
}
// Set the draw function.
set_draw_func(sigc::mem_fun(*this, &MyArea::on_draw));
}
MyArea::~MyArea()
{
}
void MyArea::on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height)
{
if (!m_image)
return;
// Draw the image in the middle of the drawing area, or (if the image is
// larger than the drawing area) draw the middle part of the image.
Gdk::Cairo::set_source_pixbuf(cr, m_image,
(width - m_image->get_width())/2, (height - m_image->get_height())/2);
cr->paint();
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>image.gresource.xml</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/image">
<file>fractal_image.png</file>
</gresource>
</gresources>
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</section>
<!--
<sect1 id="sec-drawing-fill">
<title>Gradients and other fill techniques</title>
<warning>TODO: Add content.</warning>
</sect1>
<sect1 id="sec-drawing-transformations">
<title>Transformations with Cairo</title>
<warning>TODO: Add content.</warning>
</sect1>
-->
<section xml:id="sec-drawing-clock-example">
<title>Aplicación de ejemplo: crear un reloj con Cairo</title>
<para>Ahora que se ha cubierto lo básico acerca del dibujo con Cairo, junte todo y cree una aplicación simple que haga algo de verdad. El siguiente ejemplo usa Cairo para crear un widget <classname>Clock</classname> personalizado. El reloj tiene una manecilla de segundos, una de minutos, y una de horas; y se actualiza cada segundo.</para>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/cairo_clock.png"/></imageobject></mediaobject>
</screenshot>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/drawingarea/clock">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>clock.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLE_CLOCK_H
#define GTKMM_EXAMPLE_CLOCK_H
#include <gtkmm/drawingarea.h>
class Clock : public Gtk::DrawingArea
{
public:
Clock();
virtual ~Clock();
protected:
void on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height);
bool on_timeout();
double m_radius;
double m_line_width;
};
#endif // GTKMM_EXAMPLE_CLOCK_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>clock.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include <ctime>
#include <cmath>
#include <cairomm/context.h>
#include <glibmm/main.h>
#include "clock.h"
Clock::Clock()
: m_radius(0.42), m_line_width(0.05)
{
Glib::signal_timeout().connect( sigc::mem_fun(*this, &Clock::on_timeout), 1000 );
set_draw_func(sigc::mem_fun(*this, &Clock::on_draw));
}
Clock::~Clock()
{
}
void Clock::on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height)
{
// scale to unit square and translate (0, 0) to be (0.5, 0.5), i.e.
// the center of the window
cr->scale(width, height);
cr->translate(0.5, 0.5);
cr->set_line_width(m_line_width);
cr->save();
cr->set_source_rgba(0.337, 0.612, 0.117, 0.9); // green
cr->paint();
cr->restore();
cr->arc(0, 0, m_radius, 0, 2 * M_PI);
cr->save();
cr->set_source_rgba(1.0, 1.0, 1.0, 0.8);
cr->fill_preserve();
cr->restore();
cr->stroke_preserve();
cr->clip();
//clock ticks
for (int i = 0; i < 12; i++)
{
double inset = 0.05;
cr->save();
cr->set_line_cap(Cairo::Context::LineCap::ROUND);
if(i % 3 != 0)
{
inset *= 0.8;
cr->set_line_width(0.03);
}
cr->move_to(
(m_radius - inset) * cos (i * M_PI / 6),
(m_radius - inset) * sin (i * M_PI / 6));
cr->line_to (
m_radius * cos (i * M_PI / 6),
m_radius * sin (i * M_PI / 6));
cr->stroke();
cr->restore(); /* stack-pen-size */
}
// store the current time
time_t rawtime;
time(&rawtime);
struct tm * timeinfo = localtime (&rawtime);
// compute the angles of the indicators of our clock
double minutes = timeinfo->tm_min * M_PI / 30;
double hours = timeinfo->tm_hour * M_PI / 6;
double seconds= timeinfo->tm_sec * M_PI / 30;
cr->save();
cr->set_line_cap(Cairo::Context::LineCap::ROUND);
// draw the seconds hand
cr->save();
cr->set_line_width(m_line_width / 3);
cr->set_source_rgba(0.7, 0.7, 0.7, 0.8); // gray
cr->move_to(0, 0);
cr->line_to(sin(seconds) * (m_radius * 0.9),
-cos(seconds) * (m_radius * 0.9));
cr->stroke();
cr->restore();
// draw the minutes hand
cr->set_source_rgba(0.117, 0.337, 0.612, 0.9); // blue
cr->move_to(0, 0);
cr->line_to(sin(minutes + seconds / 60) * (m_radius * 0.8),
-cos(minutes + seconds / 60) * (m_radius * 0.8));
cr->stroke();
// draw the hours hand
cr->set_source_rgba(0.337, 0.612, 0.117, 0.9); // green
cr->move_to(0, 0);
cr->line_to(sin(hours + minutes / 12.0) * (m_radius * 0.5),
-cos(hours + minutes / 12.0) * (m_radius * 0.5));
cr->stroke();
cr->restore();
// draw a little dot in the middle
cr->arc(0, 0, m_line_width / 3.0, 0, 2 * M_PI);
cr->fill();
}
bool Clock::on_timeout()
{
// force our program to redraw the entire clock.
queue_draw();
return true;
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "clock.h"
#include <gtkmm/application.h>
#include <gtkmm/window.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
protected:
Clock m_clock;
};
ExampleWindow::ExampleWindow()
{
set_title("Cairomm Clock");
set_child(m_clock);
}
int main(int argc, char** argv)
{
auto app = Gtk::Application::create("org.gtkmm.example");
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
<para xml:lang="en">
As before, almost all of the interesting stuff is done in the draw
function <methodname>on_draw()</methodname>. Before we dig
into the draw function, notice that the constructor for the
<classname>Clock</classname> widget connects a handler function
<methodname>on_timeout()</methodname> to a timer with a timeout
period of 1000 milliseconds (1 second). This means that
<methodname>on_timeout()</methodname> will get called once per
second. The sole responsibility of this function is to invalidate
the window so that <application>gtkmm</application> will be forced to redraw it.
</para>
<para>Ahora, eche un vistazo al código que hace el dibujo en sí. La primera sección del <methodname>on_draw()</methodname> ya le debería resultar bastante familiar. Este ejemplo, otra vez, escala el sistema de coordenadas para ser un cuadrado unitario, así es más fácil dibujar el reloj como un porcentaje del tamaño de la ventana para que se escale automáticamente cuando el tamaño de la ventana se ajuste. Además, el sistema de coordenadas se escala de tal manera que la coordinada (0, 0) esté justo en el centro de la ventana.</para>
<para xml:lang="en">
The function <methodname>Cairo::Context::paint()</methodname> is used here
to set the background color of the window. This function takes no
arguments and fills the current surface (or the clipped portion of
the surface) with the source color currently active. After setting
the background color of the window, we draw a circle for the clock
outline, fill it with white, and then stroke the outline in black.
Notice that both of these actions use the
<methodname>_preserve</methodname> variant to preserve the current path,
and then this same path is clipped to make sure that our next lines
don't go outside the outline of the clock.
</para>
<para>Después de haber dibujado el contorno, se recorre el reloj dibujando marcas por cada hora, con una marca más grande en la posición de las 12, 3, 6, y 9. Ahora, está finalmente listo para implementar la función del reloj, mostrar la hora, lo que simplemente implica obtener los valores actuales de la hora, minutos, y segundos, y dibujar las manecillas en los ángulos correctos.</para>
</section>
</chapter>
<chapter xml:id="chapter-draganddrop">
<title>Arrastrar y soltar</title>
<para xml:lang="en">
The <classname>Gtk::DragSource</classname> and <classname>Gtk::DropTarget</classname>
event controllers have methods and signals which are used for Drag and Drop.
</para>
<section xml:id="sec-dnd-sources-targets">
<title xml:lang="en">Sources and Targets</title>
<para xml:lang="en">
Things are dragged from <literal>sources</literal> to be dropped on
<literal>targets</literal>. Each source and target has information
about the data formats that it can send or receive, provided by
<classname>Gdk::ContentFormats</classname>. A drop target will only
accept a dragged item if they both share a compatible format. Appropriate
signals will then be emitted, telling the signal handlers which format was used.
</para>
<para xml:lang="en">
<classname>Gdk::ContentFormats</classname> objects contain information about
available <type>GType</type>s and mime types (media types).
</para>
</section>
<section xml:id="sec-dnd-methods">
<title>Métodos</title>
<para xml:lang="en">
<classname>Widget</classname>s can be identified as sources or targets using
<classname>Gtk::DragSource</classname> and <classname>Gtk::DropTarget</classname>
event controllers.
</para>
<programlisting xml:lang="en"><code>auto source = Gtk::DragSource::create();
m_source_widget.add_controller(source);</code></programlisting>
<para xml:lang="en">
Some <classname>DragSource</classname> methods:
<itemizedlist>
<listitem>
<para xml:lang="en">
<literal>void set_content(const Glib::RefPtr<Gdk::ContentProvider>& content)</literal>:
Sets a content provider on the drag source.
</para>
</listitem>
<listitem>
<para xml:lang="en">
<literal>void set_actions(Gdk::DragAction actions)</literal>:
Sets the actions on the drag source. For instance <literal>Gdk::DragAction::COPY
| Gdk::DragAction::MOVE</literal>.
</para>
</listitem>
<listitem>
<para xml:lang="en">
<literal>void set_icon(const Glib::RefPtr<const Gdk::Paintable>& paintable, int hot_x, int hot_y)</literal>:
Sets a paintable to use as icon during DND operations.
</para>
</listitem>
</itemizedlist>
</para>
<programlisting xml:lang="en"><code>auto target = Gtk::DropTarget::create(gtype, actions);
m_target_widget.add_controller(target);</code></programlisting>
<para xml:lang="en">
Some <classname>DropTarget</classname> methods:
<itemizedlist>
<listitem>
<para xml:lang="en">
<literal>void set_gtypes(const std::vector<GType>& types)</literal>:
Sets the supported types for this drop target.
</para>
</listitem>
<listitem>
<para xml:lang="en">
<literal>void set_actions(Gdk::DragAction actions)</literal>:
Sets the actions that this drop target supports.
</para>
</listitem>
<listitem>
<para xml:lang="en">
<literal>Glib::ValueBase get_value() const</literal>:
Gets the current drop data, as a <classname>Glib::Value</classname>.
</para>
</listitem>
<listitem>
<para xml:lang="en">
<literal>void reject()</literal>:
Rejects the ongoing drop operation. This function should be used when
delaying the decision on whether to accept a drag or not until after
reading the data.
</para>
</listitem>
</itemizedlist>
</para>
</section>
<section xml:id="sec-dnd-signals">
<title>Señales</title>
<para xml:lang="en">
When a drop target has accepted a dragged item, certain signals will be
emitted, depending on what action has been selected. For instance, the user
might have held down the <keycap>Shift</keycap> key to specify a
<literal>move</literal> rather than a <literal>copy</literal>. Remember that
the user can only select the actions which you have specified in your calls to
<methodname>Gtk::DragSource::set_actions()</methodname> and
<methodname>Gtk::DropTarget::set_actions()</methodname>.
</para>
<para xml:lang="en">
The source widget will emit these <classname>DragSource</classname> signals:
<itemizedlist>
<listitem><para xml:lang="en"><literal>drag_begin</literal>: Provides a <classname>Gdk::Drag</classname>.</para></listitem>
<listitem><para xml:lang="en"><literal>prepare</literal>: Shall return a <classname>Gdk::ContentProvider</classname>,
with the data to use for the drag that is about to start.</para></listitem>
<listitem><para xml:lang="en"><literal>drag_end</literal>: Provides a <classname>Gdk::Drag</classname>,
and a <type>bool</type> that tells if the drag was performing a <literal>move</literal>
and the data should be deleted.</para></listitem>
<listitem><para xml:lang="en"><literal>drag_cancel</literal>: Emitted on the drag source when a drag has failed.</para></listitem>
</itemizedlist>
</para>
<para xml:lang="en">
The target widget will emit these <classname>DropTarget</classname> signals:
<itemizedlist>
<listitem><para xml:lang="en"><literal>enter</literal>: Provides coordinates.
Shall return the preferred <type>Gdk::DragAction</type>.</para></listitem>
<listitem><para xml:lang="en"><literal>motion</literal>: Provides coordinates.
Shall return the preferred <type>Gdk::DragAction</type>.</para></listitem>
<listitem><para xml:lang="en"><literal>leave</literal>: Emitted on the drop site when the pointer
leaves the widget.</para></listitem>
<listitem><para xml:lang="en"><literal>accept</literal>: Provides a <classname>Gdk::Drop</classname>.
You can call the <methodname>status()</methodname> method of the
<classname>Gdk::Drop</classname> to indicate which actions will be accepted.</para></listitem>
<listitem><para xml:lang="en"><literal>drop</literal>: Provides the data being dropped and coordinates.
Shall return a <type>bool</type> indicating whether the drop was accepted.</para></listitem>
</itemizedlist>
</para>
<para xml:lang="en">
The following signals call only one signal handler when emitted. When you connect
a handler to such a signal, your signal handler must be called before (instead of)
the default handler, otherwise it won't be called. Set the <literal>after</literal>
parameter in <methodname>connect()</methodname> to <literal>false</literal>.
<itemizedlist>
<listitem><para xml:lang="en"><methodname>Gtk::DragSource::signal_prepare()</methodname></para></listitem>
<listitem><para xml:lang="en"><methodname>Gtk::DropTarget::signal_enter()</methodname></para></listitem>
<listitem><para xml:lang="en"><methodname>Gtk::DropTarget::signal_motion()</methodname></para></listitem>
<listitem><para xml:lang="en"><methodname>Gtk::DropTarget::signal_accept()</methodname></para></listitem>
<listitem><para xml:lang="en"><methodname>Gtk::DropTarget::signal_drop()</methodname></para></listitem>
</itemizedlist>
</para>
</section>
<section xml:id="sec-dnd-example">
<title>Ejemplo</title>
<para>Aquí hay un ejemplo muy simple, demostrando arrastrar y soltar en una operación de <literal>copia</literal>:</para>
<figure xml:id="figure-drag-and-drop">
<title>Arrastrar y soltar</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/drag_and_drop.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/drag_and_drop">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>dndwindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLE_DNDWINDOW_H
#define GTKMM_EXAMPLE_DNDWINDOW_H
#include <gdkmm/drop.h>
#include <gtkmm/box.h>
#include <gtkmm/label.h>
#include <gtkmm/window.h>
#include <gtkmm/button.h>
class DnDWindow : public Gtk::Window
{
public:
DnDWindow();
virtual ~DnDWindow();
protected:
//Signal handlers:
Glib::RefPtr<Gdk::ContentProvider> on_label_drag_prepare_data(double x, double y);
bool on_button_drop_drop_data(const Glib::ValueBase& value, double x, double y);
//Member widgets:
Gtk::Box m_HBox;
Gtk::Label m_Label_Drag;
Gtk::Button m_Button_Drop;
};
#endif // GTKMM_EXAMPLE_DNDWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>dndwindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "dndwindow.h"
#include <gdkmm/contentprovider.h>
#include <gtkmm/dragsource.h>
#include <gtkmm/droptarget.h>
#include <iostream>
DnDWindow::DnDWindow()
: m_Label_Drag("Drag Here\n"),
m_Button_Drop("Drop here\n")
{
set_title("DnD example");
set_child(m_HBox);
//Drag site:
//Make m_Label_Drag a DnD drag source:
auto source = Gtk::DragSource::create();
source->set_actions(Gdk::DragAction::COPY);
source->signal_prepare().connect(
sigc::mem_fun(*this, &DnDWindow::on_label_drag_prepare_data), false);
m_Label_Drag.add_controller(source);
m_HBox.append(m_Label_Drag);
m_Label_Drag.set_expand(true);
//Drop site:
//Make m_Button_Drop a DnD drop destination:
const GType ustring_type = Glib::Value<Glib::ustring>::value_type();
auto target = Gtk::DropTarget::create(ustring_type, Gdk::DragAction::COPY);
target->signal_drop().connect(
sigc::mem_fun(*this, &DnDWindow::on_button_drop_drop_data), false);
m_Button_Drop.add_controller(target);
m_HBox.append(m_Button_Drop);
m_Button_Drop.set_expand(true);
}
DnDWindow::~DnDWindow()
{
}
// In this simple example where just a small amount of data is copied,
// it would be reasonable to store the ContentProvider in the DragSource.
// Then this signal handler would be unnecessary.
Glib::RefPtr<Gdk::ContentProvider> DnDWindow::on_label_drag_prepare_data(double, double)
{
Glib::Value<Glib::ustring> ustring_value;
ustring_value.init(ustring_value.value_type());
ustring_value.set("I'm Data!");
return Gdk::ContentProvider::create(ustring_value);
}
bool DnDWindow::on_button_drop_drop_data(const Glib::ValueBase& value, double, double)
{
if (G_VALUE_HOLDS(value.gobj(), Glib::Value<Glib::ustring>::value_type()))
{
// We got the value type that we expected.
Glib::Value<Glib::ustring> ustring_value;
ustring_value.init(value.gobj());
const Glib::ustring dropped_string = ustring_value.get();
std::cout << "Received \"" << dropped_string << "\" in button " << std::endl;
return true;
}
else
{
std::cout << "Received unexpected data type \""
<< G_VALUE_TYPE_NAME(value.gobj()) << "\" in button " << std::endl;
return false;
}
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "dndwindow.h"
#include <gtkmm/application.h>
int main(int argc, char* argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
//Shows the window and returns when it is closed.
return app->make_window_and_run<DnDWindow>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
<para>Hay un ejemplo más complejo en examples/others/dnd.</para>
</section>
</chapter>
<chapter xml:id="chapter-clipboard">
<title>El portapapeles</title>
<para>Widgets libres como <classname>Gtk::Entry</classname> y <classname>Gtk::TextView</classname>proporcionan una función de copiar y pegar texto simple, pero tal vez necesite código especial para manejar sus propios formatos de datos. Por ejemplo, un programa de dibujo necesitaría código especial para permitir copiar y pegar dentro de una vista, o entre documentos.</para>
<para xml:lang="en">
You can get a clipboard instance with <methodname>Gtk::Widget::get_clipboard()</methodname>
or <methodname>Gdk::Display::get_clipboard()</methodname>.
</para>
<para xml:lang="en">
Your application doesn't need to wait for clipboard operations, particularly
between the time when the user chooses Copy and then later chooses Paste. Many
<classname>Gdk::Clipboard</classname> methods take <classname>sigc::slot</classname>s
which specify callback methods. When <classname>Gdk::Clipboard</classname> is ready,
it will call these methods, providing the requested data.
</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGdk_1_1Clipboard.html">Reference</link></para>
<section xml:id="sec-clipboard-formats">
<title xml:lang="en">Formats</title>
<para xml:lang="en">
Different applications contain different types of data, and they might make that data available in
a variety of formats. <application>gtkmm</application> calls these data types <literal>format</literal>s.</para>
<para xml:lang="en">
For instance, <application>gedit</application> can supply and receive the <literal>text/plain</literal> mime type,
so you can paste data into <application>gedit</application> from any application that supplies that format.
Or two different image editing applications might supply and receive a variety of image formats.
As long as one application can receive one of the formats that the other supplies
then you will be able to copy data from one to the other.
</para>
<para xml:lang="en">
Clipboard data can be in a variety of binary formats. This chapter, and the examples,
assume that the data is 8-bit text. This would allow us to use an XML format
for the clipboard data. However this would probably not be appropriate for
binary data such as images.
</para>
<para xml:lang="en">The <link linkend="chapter-draganddrop">Drag and Drop</link> API uses the same mechanism.
You should probably use the same data formats for both Clipboard and Drag and Drop operations.</para>
</section>
<section xml:id="sec-clipboard-copy">
<title>Copiar</title>
<para xml:lang="en">
When the user asks to copy some data, you should copy the data to the
<classname>Clipboard</classname>. For instance,
</para>
<programlisting xml:lang="en"><code>void ExampleWindow::on_button_copy()
{
get_clipboard()->set_text("example_custom_target");
}</code></programlisting>
</section>
<section xml:id="sec-clipboard-paste">
<title>Pegar</title>
<para>Cuando el usuario pide pegar datos desde el <classname>Clipboard</classname>, debe pedir un formato específico y proporcionar un método de retorno de llamada que se llamará con los datos en sí. Por ejemplo:</para>
<programlisting xml:lang="en"><code>void ExampleWindow::on_button_paste()
{
get_clipboard()->read_text_async(sigc::mem_fun(*this,
&ExampleWindow::on_clipboard_received));
}</code></programlisting>
<para>Aquí hay un ejemplo de un método de retorno de llamada:</para>
<programlisting xml:lang="en"><code>void ExampleWindow::on_clipboard_received(Glib::RefPtr<Gio::AsyncResult>& result)
{
auto text = get_clipboard()->read_text_finish(result);
//Do something with the pasted data.
}</code></programlisting>
<section xml:id="sec-clipboard-discovering-formats">
<title xml:lang="en">Discovering the available formats</title>
<para xml:lang="en">
To find out what formats are currently available on the <classname>Clipboard</classname>
for pasting, call the <methodname>get_formats()</methodname> method. Then call a
<classname>Gdk::ContentFormats</classname> method to find out if a format that
your application supports is available.
</para>
</section>
</section>
<section xml:id="sec-clipboard-examples">
<title>Ejemplos</title>
<section xml:id="sec-clipboard-example-simple">
<title>Simple</title>
<para xml:lang="en">
This example allows copy and pasting of application-specific data, using the
standard text format. Although this is simple, it's not ideal because it does
not identify the <classname>Clipboard</classname> data as being of a particular
type.
</para>
<figure xml:id="figure-clipboard-simple">
<title>Portapapeles: simple</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/clipboard_simple.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/clipboard/simple/">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
virtual ~ExampleWindow();
protected:
//Signal handlers:
void on_button_copy();
void on_button_paste();
void on_clipboard_text_received(Glib::RefPtr<Gio::AsyncResult>& result);
Glib::ustring m_strData;
//Child widgets:
Gtk::Box m_VBox;
Gtk::Label m_Label;
Gtk::Grid m_Grid;
Gtk::ToggleButton m_ButtonA1, m_ButtonA2, m_ButtonB1, m_ButtonB2;
Gtk::Box m_ButtonBox;
Gtk::Button m_Button_Copy, m_Button_Paste;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <iostream>
ExampleWindow::ExampleWindow()
: m_VBox(Gtk::Orientation::VERTICAL),
m_Label("Select cells in the grid, click Copy, then open a second "
"instance of this example to try pasting the copied data."),
m_ButtonA1("A1"), m_ButtonA2("A2"), m_ButtonB1("B1"), m_ButtonB2("B2"),
m_Button_Copy("_Copy", /* mnemonic= */ true), m_Button_Paste("_Paste", true)
{
set_title("Gtk::Clipboard example");
m_VBox.set_margin(12);
set_child(m_VBox);
m_VBox.append(m_Label);
//Fill Grid:
m_VBox.append(m_Grid);
m_Grid.set_expand(true);
m_Grid.set_row_homogeneous(true);
m_Grid.set_column_homogeneous(true);
m_Grid.attach(m_ButtonA1, 0, 0);
m_Grid.attach(m_ButtonA2, 1, 0);
m_Grid.attach(m_ButtonB1, 0, 1);
m_Grid.attach(m_ButtonB2, 1, 1);
//Add ButtonBox to bottom:
m_VBox.append(m_ButtonBox);
m_VBox.set_spacing(6);
//Fill ButtonBox:
m_ButtonBox.append(m_Button_Copy);
m_Button_Copy.set_hexpand(true);
m_Button_Copy.set_halign(Gtk::Align::END);
m_Button_Copy.signal_clicked().connect(sigc::mem_fun(*this,
&ExampleWindow::on_button_copy) );
m_ButtonBox.append(m_Button_Paste);
m_Button_Paste.signal_clicked().connect(sigc::mem_fun(*this,
&ExampleWindow::on_button_paste) );
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::on_button_copy()
{
//Build a string representation of the stuff to be copied:
//Ideally you would use XML, with an XML parser here:
m_strData = m_ButtonA1.get_active() ? "1" : "0";
m_strData += m_ButtonA2.get_active() ? "1" : "0";
m_strData += m_ButtonB1.get_active() ? "1" : "0";
m_strData += m_ButtonB2.get_active() ? "1" : "0";
// Gdk::Clipboard::set_text() does not take a copy of the text.
// The text can only be pasted (in this program or in some other program)
// for as long as it exists.
get_clipboard()->set_text(m_strData);
}
void ExampleWindow::on_button_paste()
{
//Tell the clipboard to call our method when it is ready:
get_clipboard()->read_text_async(sigc::mem_fun(*this,
&ExampleWindow::on_clipboard_text_received));
}
void ExampleWindow::on_clipboard_text_received(Glib::RefPtr<Gio::AsyncResult>& result)
{
Glib::ustring text;
try
{
text = get_clipboard()->read_text_finish(result);
}
catch (const Glib::Error& err)
{
// Print an error about why pasting failed.
// Usually you probably want to ignore such failures,
// but for demonstration purposes, we show the error.
std::cout << "Pasting failed: " << err.what() << std::endl;
}
//See comment in on_button_copy() about this silly clipboard format.
if(text.size() >= 4)
{
m_ButtonA1.set_active( text[0] == '1' );
m_ButtonA2.set_active( text[1] == '1' );
m_ButtonB1.set_active( text[2] == '1' );
m_ButtonB2.set_active( text[3] == '1' );
}
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char *argv[])
{
// Gio::Application::Flags::NON_UNIQUE because it shall be possible to run
// several instances of this application simultaneously.
auto app = Gtk::Application::create(
"org.gtkmm.example", Gio::Application::Flags::NON_UNIQUE);
//Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
<section xml:id="sec-clipboard-example-ideal">
<title>Ideal</title>
<para xml:lang="en">This is like the simple example, but it
<orderedlist inheritnum="ignore" continuation="restarts">
<listitem><simpara xml:lang="en">Defines a custom clipboard target, though the format is still text.</simpara></listitem>
<listitem><simpara xml:lang="en">It uses the <methodname>Gdk::ContentFormats::signal_changed()</methodname>
signal and disables the Paste button if it can't use anything on the clipboard.</simpara></listitem>
</orderedlist>
</para>
<figure xml:id="figure-clipboard-ideal">
<title>Portapapeles: ideal</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/clipboard_ideal.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/clipboard/ideal/">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
virtual ~ExampleWindow();
protected:
//Signal handlers:
void on_button_copy();
void on_button_paste();
void on_clipboard_content_changed();
void on_clipboard_received(Glib::RefPtr<Gio::AsyncResult>& result);
void on_clipboard_received_status(Glib::RefPtr<Gio::AsyncResult>& result);
void update_paste_status(); //Disable the paste button if there is nothing to paste.
Glib::ustring m_strData;
//Child widgets:
Gtk::Box m_VBox;
Gtk::Label m_Label;
Gtk::Grid m_Grid;
Gtk::ToggleButton m_ButtonA1, m_ButtonA2, m_ButtonB1, m_ButtonB2;
Gtk::Box m_ButtonBox;
Gtk::Button m_Button_Copy, m_Button_Paste;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <iostream>
#include <string>
namespace
{
const char example_format_custom[] = "gtkmmclipboardexample";
} // anonymous namespace
ExampleWindow::ExampleWindow()
: m_VBox(Gtk::Orientation::VERTICAL),
m_Label("Select cells in the grid, click Copy, then open a second instance "
"of this example to try pasting the copied data.\nOr try pasting the "
"text representation into gedit."),
m_ButtonA1("A1"), m_ButtonA2("A2"), m_ButtonB1("B1"), m_ButtonB2("B2"),
m_Button_Copy("_Copy", /* mnemonic= */ true), m_Button_Paste("_Paste", true)
{
set_title("Gtk::Clipboard example");
m_VBox.set_margin(12);
set_child(m_VBox);
m_VBox.append(m_Label);
//Fill Grid:
m_VBox.append(m_Grid);
m_Grid.set_expand(true);
m_Grid.set_row_homogeneous(true);
m_Grid.set_column_homogeneous(true);
m_Grid.attach(m_ButtonA1, 0, 0);
m_Grid.attach(m_ButtonA2, 1, 0);
m_Grid.attach(m_ButtonB1, 0, 1);
m_Grid.attach(m_ButtonB2, 1, 1);
//Add ButtonBox to bottom:
m_VBox.append(m_ButtonBox);
m_VBox.set_spacing(6);
//Fill ButtonBox:
m_ButtonBox.append(m_Button_Copy);
m_Button_Copy.set_hexpand(true);
m_Button_Copy.set_halign(Gtk::Align::END);
m_Button_Copy.signal_clicked().connect(sigc::mem_fun(*this,
&ExampleWindow::on_button_copy) );
m_ButtonBox.append(m_Button_Paste);
m_Button_Paste.signal_clicked().connect(sigc::mem_fun(*this,
&ExampleWindow::on_button_paste) );
//Connect a signal handler that will be called when the contents of
//the clipboard change.
get_clipboard()->signal_changed().connect(sigc::mem_fun(*this,
&ExampleWindow::on_clipboard_content_changed));
update_paste_status();
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::on_button_copy()
{
//Build a string representation of the stuff to be copied:
//Ideally you would use XML, with an XML parser here:
m_strData = example_format_custom;
m_strData += m_ButtonA1.get_active() ? " A1" : "";
m_strData += m_ButtonA2.get_active() ? " A2" : "";
m_strData += m_ButtonB1.get_active() ? " B1" : "";
m_strData += m_ButtonB2.get_active() ? " B2" : "";
// Gdk::Clipboard::set_text() does not take a copy of the text.
// The text can only be pasted (in this program or in some other program)
// for as long as it exists.
get_clipboard()->set_text(m_strData);
}
void ExampleWindow::on_button_paste()
{
//Tell the clipboard to call our method when it is ready:
get_clipboard()->read_text_async(sigc::mem_fun(*this,
&ExampleWindow::on_clipboard_received));
}
void ExampleWindow::on_clipboard_content_changed()
{
update_paste_status();
}
void ExampleWindow::on_clipboard_received(Glib::RefPtr<Gio::AsyncResult>& result)
{
Glib::ustring text;
try
{
text = get_clipboard()->read_text_finish(result);
}
catch (const Glib::Error& err)
{
// Print an error about why pasting failed.
// Usually you probably want to ignore such failures,
// but for demonstration purposes, we show the error.
std::cout << "Pasting failed: " << err.what() << std::endl;
}
if (text.find(example_format_custom) == 0)
{
// It's the expected format.
m_ButtonA1.set_active(text.find("A1") != std::string::npos);
m_ButtonA2.set_active(text.find("A2") != std::string::npos);
m_ButtonB1.set_active(text.find("B1") != std::string::npos);
m_ButtonB2.set_active(text.find("B2") != std::string::npos);
}
else
{
// Unexpected format. Disable the Paste button.
std::cout << "Unexpected pasted text: \"" << text << "\"" << std::endl;
m_Button_Paste.set_sensitive(false);
}
}
void ExampleWindow::update_paste_status()
{
// Disable the paste button if there is nothing to paste.
get_clipboard()->read_text_async(sigc::mem_fun(*this,
&ExampleWindow::on_clipboard_received_status));
}
void ExampleWindow::on_clipboard_received_status(Glib::RefPtr<Gio::AsyncResult>& result)
{
Glib::ustring text;
try
{
text = get_clipboard()->read_text_finish(result);
}
catch (const Glib::Error&)
{
}
const bool bPasteIsPossible = text.find(example_format_custom) == 0;
// Enable/Disable the Paste button appropriately:
m_Button_Paste.set_sensitive(bPasteIsPossible);
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char *argv[])
{
// Gio::Application::Flags::NON_UNIQUE because it shall be possible to run
// several instances of this application simultaneously.
auto app = Gtk::Application::create(
"org.gtkmm.example", Gio::Application::Flags::NON_UNIQUE);
//Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</section>
</chapter>
<chapter xml:id="chapter-printing">
<title>Impresión</title>
<para xml:lang="en">
At the application development level, <application>gtkmm</application>'s printing API
provides dialogs that are consistent across applications and allows use of Cairo's common drawing API, with Pango-driven text rendering. In the implementation of this common API, platform-specific backends and printer-specific drivers are used.
</para>
<section xml:id="sec-printoperation">
<title>PrintOperation</title>
<para>El objeto primario es <classname>Gtk::PrintOperation</classname>, reservado por cada operación de impresión. Para manejar el dibujo de una página, conecte sus señales, o herede de él y sobrecargue los gestores de señales virtuales predeterminados. <classname>PrintOperation</classname> maneja automáticamente todas las opciones que afectan al bucle de impresión.</para>
<section xml:id="sec-printoperation-signals">
<title>Señales</title>
<para xml:lang="en">
The <methodname>PrintOperation::run()</methodname> method starts the print loop,
during which various signals are emitted:
<itemizedlist>
<listitem>
<para xml:lang="en">
<literal>begin_print</literal>:
You must handle this signal, because this is where you
create and set up a <classname>Pango::Layout</classname> using the
provided <classname>Gtk::PrintContext</classname>, and break up your
printing output into pages.
</para>
</listitem>
<listitem>
<para xml:lang="en">
<literal>paginate</literal>: Pagination is potentially slow so if you
need to monitor it you can call the
<methodname>PrintOperation::set_show_progress()</methodname> method and
handle this signal.
</para>
</listitem>
<listitem>
<para xml:lang="en">
For each page that needs to be rendered, the following signals
are emitted:
<itemizedlist>
<listitem>
<para xml:lang="en">
<literal>request_page_setup</literal>: Provides a
<classname>PrintContext</classname>, page number and
<classname>Gtk::PageSetup</classname>. Handle this signal if you
need to modify page setup on a per-page basis.
</para>
</listitem>
<listitem>
<para xml:lang="en">
<literal>draw_page</literal>: You must handle this signal, which provides a
<classname>PrintContext</classname> and a page number.
The <classname>PrintContext</classname> should be used
to create a <classname>Cairo::Context</classname> into which
the provided page should be drawn. To render text, iterate over
the <classname>Pango::Layout</classname> you created in the
<literal>begin_print</literal> handler.
</para>
</listitem>
</itemizedlist>
</para>
</listitem>
<listitem>
<para xml:lang="en">
<literal>end_print</literal>: A handler for it is a safe place to free
any resources related to a <classname>PrintOperation</classname>.
If you have your custom class that inherits from
<classname>PrintOperation</classname>, it is naturally simpler to do it
in the destructor.
</para>
</listitem>
<listitem>
<para xml:lang="en">
<literal>done</literal>: This signal is emitted when printing is finished, meaning when the
print data is spooled. Note that the provided
<literal>Gtk::PrintOperation::Result</literal> may indicate that
an error occurred. In any case you probably want to notify the user
about the final status.
</para>
</listitem>
<listitem>
<para xml:lang="en">
<literal>status_changed</literal>: Emitted whenever a print job's
status changes, until it is finished. Call the
<methodname>PrintOperation::set_track_print_status()</methodname> method to
monitor the job status after spooling. To see the status, use
<methodname>get_status()</methodname> or
<methodname>get_status_string()</methodname>.
</para>
</listitem>
</itemizedlist>
</para>
<para xml:lang="en">
<link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1PrintOperation.html">Reference</link>
</para>
</section>
</section>
<section xml:id="sec-page-setup">
<title>Configuración de página</title>
<para>La clase <classname>PrintOperation</classname> tiene un método llamado <methodname>set_default_page_setup()</methodname> que selecciona el tamaño de papel, orientación y márgenes predeterminados. Para mostrar un diálogo de configuración de página desde su aplicación, use el método <methodname>Gtk::run_page_setup_dialog()</methodname>, que devuelve un objeto <classname>Gtk::PageSetup()</classname> con la configuración elegida. Use este objeto para actualizar una <classname>PrintOperation</classname> y acceder a <classname>Gtk::PaperSize</classname>, <literal>Gtk::PageOrientation</literal> y los márgenes específicos de la impresora.</para>
<para>Debe guardar la <classname>Gtk::PageSetup</classname> elegida para poder usarla luego si se vuelve a mostrar el diálogo de configuración de la página.</para>
<para>Por ejemplo,</para>
<programlisting xml:lang="en"><code>// Within a class that inherits from Gtk::Window and keeps m_refPageSetup
// and m_refSettings as members...
auto new_page_setup = Gtk::run_page_setup_dialog(*this, m_refPageSetup, m_refSettings);
m_refPageSetup = new_page_setup;
</code></programlisting>
<para xml:lang="en">
<link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1PageSetup.html">Reference</link>
</para>
<para>El sistema de coordenadas de Cairo, en el gestor <literal>draw_page</literal>, rota automáticamente a la orientación de la página actual. Normalmente está dentro de los márgenes de la impresora, pero puede cambiar esto mediante el método <methodname>PrintOperation::set_use_full_page()</methodname>. La unidad de medida predeterminada es el píxel del dispositivo. Para seleccionar otras unidades, use el método <methodname>PrintOperation::set_unit()</methodname>.</para>
</section>
<section xml:id="sec-printing-rendering-text">
<title>Renderizar texto</title>
<para>El texto se renderiza usando Pango. El objeto <classname>Pango::Layout</classname> para la impresión debe crearse llamando al método <methodname>PrintContext::create_pango_layout()</methodname>. El objeto <classname>PrintContext</classname> también proporciona las medidas de la página, mediante <methodname>get_width()</methodname> y <methodname>get_height()</methodname>. El número de páginas puede establecerse con <methodname>PrintOperation::set_n_pages()</methodname>. Para renderizar el texto de Pango en <literal>on_draw_page</literal>, obtenga un <classname>Cairo::Context</classname> con <methodname>PrintContext::get_cairo_context()</methodname> y haga visibles las <classname>Pango::LayoutLine</classname> que aparecen dentro del número de página pedido.</para>
<para xml:lang="en">
See <link linkend="sec-printing-examples-simple">an example</link>
of exactly how this can be done.
</para>
</section>
<section xml:id="sec-async-printing-ops">
<title>Operaciones asíncronas</title>
<para>De manera predeterminada, <methodname>PrintOperation::run()</methodname> retorna cuando una operación de impresión ha terminado. Si necesite ejecutar una operación no modal de impresión, llame a <methodname>PrintOperation::set_allow_async()</methodname>. Tenga en cuenta que, a pesar de que no todas las plataformas soportan <methodname>set_allow_async()</methodname>, la señal <literal>done</literal> se emitirá de todos modos.</para>
<para xml:lang="en">
<methodname>run()</methodname> may return
<literal>PrintOperation::Result::IN_PROGRESS</literal>. To track status
and handle the result or error you need to implement signal handlers for
the <literal>done</literal> and <literal>status_changed</literal> signals:
</para>
<para>Por ejemplo,</para>
<programlisting xml:lang="en"><code>// in class ExampleWindow's method...
auto op = PrintOperation::create();
// ...set up op...
op->signal_done().connect(sigc::bind(sigc::mem_fun(
*this, &ExampleWindow::on_printoperation_done), op));
// run the op
</code></programlisting>
<para xml:lang="en">Second, check for an error and connect to the <literal>status_changed</literal> signal. For instance:
</para>
<programlisting xml:lang="en"><code>void ExampleWindow::on_printoperation_done(Gtk::PrintOperation::Result result,
const Glib::RefPtr<PrintOperation>& op)
{
if (result == Gtk::PrintOperation::Result::ERROR)
//notify user
else if (result == Gtk::PrintOperation::Result::APPLY)
//Update PrintSettings with the ones used in this PrintOperation
if (! op->is_finished())
op->signal_status_changed().connect(sigc::bind(sigc::mem_fun(
*this, &ExampleWindow::on_printoperation_status_changed), op));
}
</code></programlisting>
<para xml:lang="en">Finally, check the status. For instance,</para>
<programlisting xml:lang="en"><code>void ExampleWindow::on_printoperation_status_changed(const Glib::RefPtr<PrintOperation>& op)
{
if (op->is_finished())
//the print job is finished
else
//get the status with get_status() or get_status_string()
//update UI
}
</code></programlisting>
</section>
<section xml:id="sec-printing-export-to-pdf">
<title>Exportar a PDF</title>
<para xml:lang="en">
The 'Print to file' option is available in the print dialog, without the need for extra implementation. However, it is sometimes useful to generate a pdf file directly from code. For instance,
</para>
<programlisting xml:lang="en"><code>auto op = Gtk::PrintOperation::create();
// ...set up op...
op->set_export_filename("test.pdf");
auto res = op->run(Gtk::PrintOperation::Action::EXPORT);
</code></programlisting>
</section>
<section xml:id="sec-extending-print-dialog">
<title>Extender el diálogo de impresión</title>
<para xml:lang="en">
You may add a custom tab to the print dialog:
<itemizedlist>
<listitem>
<para xml:lang="en">
Set the title of the tab via
<methodname>PrintOperation::set_custom_tab_label()</methodname>,
create a new widget and return it from the
<literal>create_custom_widget</literal> signal handler. You'll probably
want this to be a container widget, packed with some others.
</para>
</listitem>
<listitem>
<para xml:lang="en">
Get the data from the widgets in the
<literal>custom_widget_apply</literal> signal handler.
</para>
</listitem>
</itemizedlist>
</para>
<para xml:lang="en">
Although the <literal>custom_widget_apply</literal> signal provides the widget you
previously created, to simplify things you can keep the widgets you expect
to contain some user input as class members. For example, let's say you have
a <classname>Gtk::Entry</classname> called <literal>m_Entry</literal> as
a member of your <classname>CustomPrintOperation</classname> class:
</para>
<programlisting xml:lang="en"><code>Gtk::Widget* CustomPrintOperation::on_create_custom_widget()
{
set_custom_tab_label("My custom tab");
auto hbox = new Gtk::Box(Gtk::Orientation::HORIZONTAL, 8);
hbox->set_margin(6);
auto label = Gtk::make_managed<Gtk::Label>("Enter some text: ");
hbox->append(*label);
hbox->append(m_Entry);
return hbox;
}
void CustomPrintOperation::on_custom_widget_apply(Gtk::Widget* /* widget */)
{
auto user_input = m_Entry.get_text();
//...
}
</code></programlisting>
<para>El ejemplo en examples/book/printing/advanced demuestra esto.</para>
</section>
<section xml:id="sec-printing-preview">
<title>Vista previa</title>
<para xml:lang="en">
The native GTK print dialog has a preview button, but you may also start
a preview directly from an application:</para>
<programlisting xml:lang="en"><code>// in a class that inherits from Gtk::Window...
auto op = PrintOperation::create();
// ...set up op...
op->run(Gtk::PrintOperation::Action::PREVIEW, *this);
</code></programlisting>
<para xml:lang="en">
On Unix, the default preview handler uses an external viewer program.
On Windows, the native preview dialog will be shown. If necessary you may
override this behavior and provide a custom preview dialog. See the example
located in /examples/book/printing/advanced.
</para>
</section>
<section xml:id="sec-printing-printdialog">
<title xml:lang="en">PrintDialog</title>
<para xml:lang="en">
Since <application>gtkmm</application> 4.14 <classname>Gtk::PrintDialog</classname> is an alternative
to <classname>Gtk::PrintOperation</classname>. <classname>PrintDialog</classname>
uses the same <classname>PageSetup</classname> and <classname>PrintSettings</classname>
classes as <classname>PrintOperation</classname>. The rendering with Cairo and
Pango is also similar.
</para>
</section>
<section xml:id="sec-printing-examples">
<title>Ejemplos</title>
<section xml:id="sec-printing-examples-simple">
<title>Simple</title>
<para xml:lang="en">
The following example demonstrates how to print some input from a user interface
using <classname>PrintOperation</classname>. It shows how to implement
<literal>on_begin_print</literal> and <literal>on_draw_page</literal>,
as well as how to track print status and update the print settings.
</para>
<figure xml:id="figure-printing-simple">
<title>Impresión simple</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/printing_simple.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/printing/simple/">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
// This file is part of the printing/simple and printing/advanced examples
#include <gtkmm.h>
class PrintFormOperation;
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow(const Glib::RefPtr<Gtk::Application>& app);
virtual ~ExampleWindow();
protected:
void build_main_menu(const Glib::RefPtr<Gtk::Application>& app);
void print_or_preview(Gtk::PrintOperation::Action print_action);
//PrintOperation signal handlers.
//We handle these so can get necessary information to update the UI or print settings.
//Our derived PrintOperation class also overrides some default signal handlers.
void on_printoperation_status_changed();
void on_printoperation_done(Gtk::PrintOperation::Result result);
//Action signal handlers:
void on_menu_file_new();
void on_menu_file_page_setup();
void on_menu_file_print_preview();
void on_menu_file_print();
void on_menu_file_quit();
//Printing-related objects:
Glib::RefPtr<Gtk::PageSetup> m_refPageSetup;
Glib::RefPtr<Gtk::PrintSettings> m_refSettings;
Glib::RefPtr<PrintFormOperation> m_refPrintFormOperation;
//Child widgets:
Gtk::Box m_VBox;
Gtk::Grid m_Grid;
Gtk::Label m_NameLabel;
Gtk::Entry m_NameEntry;
Gtk::Label m_SurnameLabel;
Gtk::Entry m_SurnameEntry;
Gtk::Label m_CommentsLabel;
Gtk::ScrolledWindow m_ScrolledWindow;
Gtk::TextView m_TextView;
Glib::RefPtr<Gtk::TextBuffer> m_refTextBuffer;
Gtk::Label m_Statusbar;
Glib::RefPtr<Gtk::Builder> m_refBuilder;
Glib::RefPtr<Gtk::AlertDialog> m_refDialog;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>printformoperation.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_PRINT_FORM_OPERATION_H
#define GTKMM_PRINT_FORM_OPERATION_H
#include <gtkmm.h>
#include <pangomm.h>
#include <vector>
//We derive our own class from PrintOperation,
//so we can put the actual print implementation here.
class PrintFormOperation : public Gtk::PrintOperation
{
public:
static Glib::RefPtr<PrintFormOperation> create();
virtual ~PrintFormOperation();
void set_name(const Glib::ustring& name) { m_Name = name; }
void set_comments(const Glib::ustring& comments) { m_Comments = comments; }
protected:
PrintFormOperation();
//PrintOperation default signal handler overrides:
void on_begin_print(const Glib::RefPtr<Gtk::PrintContext>& context) override;
void on_draw_page(const Glib::RefPtr<Gtk::PrintContext>& context, int page_nr) override;
Glib::ustring m_Name;
Glib::ustring m_Comments;
Glib::RefPtr<Pango::Layout> m_refLayout;
std::vector<int> m_PageBreaks; // line numbers where a page break occurs
};
#endif // GTKMM_PRINT_FORM_OPERATION_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include "printformoperation.h"
#include <iostream>
ExampleWindow::ExampleWindow(const Glib::RefPtr<Gtk::Application>& app)
: m_VBox(Gtk::Orientation::VERTICAL),
m_NameLabel("Name"),
m_SurnameLabel("Surname"),
m_CommentsLabel("Comments")
{
m_refPageSetup = Gtk::PageSetup::create();
m_refSettings = Gtk::PrintSettings::create();
set_title("gtkmm Printing Example");
set_default_size(400, 300);
set_child(m_VBox);
build_main_menu(app);
m_VBox.append(m_Grid);
//Arrange the widgets inside the grid:
m_Grid.set_expand(true);
m_Grid.set_row_spacing(5);
m_Grid.set_column_spacing(5);
m_Grid.attach(m_NameLabel, 0, 0);
m_Grid.attach(m_NameEntry, 1, 0);
m_Grid.attach(m_SurnameLabel, 0, 1);
m_Grid.attach(m_SurnameEntry, 1, 1);
//Add the TextView, inside a ScrolledWindow:
m_ScrolledWindow.set_child(m_TextView);
//Only show the scrollbars when they are necessary:
m_ScrolledWindow.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
m_Grid.attach(m_CommentsLabel, 0, 2);
m_Grid.attach(m_ScrolledWindow, 1, 2);
m_ScrolledWindow.set_expand(true);
m_refTextBuffer = Gtk::TextBuffer::create();
m_TextView.set_buffer(m_refTextBuffer);
m_Statusbar.set_hexpand(true);
m_VBox.append(m_Statusbar);
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::build_main_menu(const Glib::RefPtr<Gtk::Application>& app)
{
//Create actions for menus and toolbars:
auto refActionGroup = Gio::SimpleActionGroup::create();
//File menu:
refActionGroup->add_action("new",
sigc::mem_fun(*this, &ExampleWindow::on_menu_file_new));
refActionGroup->add_action("pagesetup",
sigc::mem_fun(*this, &ExampleWindow::on_menu_file_page_setup));
refActionGroup->add_action("printpreview",
sigc::mem_fun(*this, &ExampleWindow::on_menu_file_print_preview));
refActionGroup->add_action("print",
sigc::mem_fun(*this, &ExampleWindow::on_menu_file_print));
refActionGroup->add_action("quit",
sigc::mem_fun(*this, &ExampleWindow::on_menu_file_quit));
insert_action_group("example", refActionGroup);
// When the menubar is a child of a Gtk::Window, keyboard accelerators are not
// automatically fetched from the Gio::Menu.
// See the examples/book/menus/main_menu example for an alternative way of
// adding the menubar when using Gtk::ApplicationWindow.
app->set_accel_for_action("example.new", "<Primary>n");
app->set_accel_for_action("example.print", "<Primary>p");
app->set_accel_for_action("example.quit", "<Primary>q");
m_refBuilder = Gtk::Builder::create();
// Layout the actions in a menubar:
Glib::ustring ui_menu_info =
"<interface>"
" <menu id='menu-example'>"
" <submenu>"
" <attribute name='label' translatable='yes'>_File</attribute>"
" <section>"
" <item>"
" <attribute name='label' translatable='yes'>_New</attribute>"
" <attribute name='action'>example.new</attribute>"
" </item>"
" </section>"
" <section>"
" <item>"
" <attribute name='label' translatable='yes'>Page _Setup...</attribute>"
" <attribute name='action'>example.pagesetup</attribute>"
" </item>"
" <item>"
" <attribute name='label' translatable='yes'>Print Preview</attribute>"
" <attribute name='action'>example.printpreview</attribute>"
" </item>"
" <item>"
" <attribute name='label' translatable='yes'>_Print...</attribute>"
" <attribute name='action'>example.print</attribute>"
" </item>"
" </section>"
" <section>"
" <item>"
" <attribute name='label' translatable='yes'>_Quit</attribute>"
" <attribute name='action'>example.quit</attribute>"
" </item>"
" </section>"
" </submenu>"
" </menu>"
"</interface>";
try
{
m_refBuilder->add_from_string(ui_menu_info);
}
catch(const Glib::Error& ex)
{
std::cerr << "building menus failed: " << ex.what();
}
// Layout the actions in a toolbar:
Glib::ustring ui_toolbar_info =
"<!-- Generated with glade 3.18.3 and then changed manually -->"
"<interface>"
"<object class='GtkBox' id='toolbar'>"
"<property name='can_focus'>False</property>"
"<property name='spacing'>3</property>"
"<child>"
"<object class='GtkButton' id='toolbutton_new'>"
"<property name='can_focus'>False</property>"
"<property name='tooltip_text' translatable='yes'>New</property>"
"<property name='action_name'>example.new</property>"
"<property name='icon_name'>document-new</property>"
"<property name='hexpand'>False</property>"
"<property name='vexpand'>False</property>"
"</object>"
"</child>"
"<child>"
"<object class='GtkButton' id='toolbutton_print'>"
"<property name='can_focus'>False</property>"
"<property name='tooltip_text' translatable='yes'>Print</property>"
"<property name='action_name'>example.print</property>"
"<property name='icon_name'>document-print</property>"
"<property name='hexpand'>False</property>"
"<property name='vexpand'>False</property>"
"</object>"
"</child>"
"<child>"
"<object class='GtkSeparator' id='separator1'>"
"<property name='can_focus'>False</property>"
"<property name='hexpand'>False</property>"
"<property name='vexpand'>False</property>"
"</object>"
"</child>"
"<child>"
"<object class='GtkButton' id='toolbutton_quit'>"
"<property name='can_focus'>False</property>"
"<property name='tooltip_text' translatable='yes'>Quit</property>"
"<property name='action_name'>example.quit</property>"
"<property name='icon_name'>application-exit</property>"
"<property name='hexpand'>False</property>"
"<property name='vexpand'>False</property>"
"</object>"
"</child>"
"</object>"
"</interface>";
try
{
m_refBuilder->add_from_string(ui_toolbar_info);
}
catch(const Glib::Error& ex)
{
std::cerr << "building toolbar failed: " << ex.what();
}
// Get the menubar and add it to a container widget:
auto object = m_refBuilder->get_object("menu-example");
auto gmenu = std::dynamic_pointer_cast<Gio::Menu>(object);
if (!gmenu)
g_warning("GMenu not found");
else
{
auto pMenuBar = Gtk::make_managed<Gtk::PopoverMenuBar>(gmenu);
// Add the PopoverMenuBar to the window:
m_VBox.append(*pMenuBar);
}
// Get the toolbar and add it to a container widget:
auto toolbar = m_refBuilder->get_widget<Gtk::Box>("toolbar");
if (!toolbar)
g_warning("toolbar not found");
else
m_VBox.append(*toolbar);
}
void ExampleWindow::on_printoperation_status_changed()
{
Glib::ustring status_msg;
if (m_refPrintFormOperation->is_finished())
{
status_msg = "Print job completed.";
}
else
{
//You could also use get_status().
status_msg = m_refPrintFormOperation->get_status_string();
}
m_Statusbar.set_text(status_msg);
}
void ExampleWindow::on_printoperation_done(Gtk::PrintOperation::Result result)
{
//Printing is "done" when the print data is spooled.
if (result == Gtk::PrintOperation::Result::ERROR)
{
if (!m_refDialog)
m_refDialog = Gtk::AlertDialog::create("Error printing form");
m_refDialog->show(*this);
}
else if (result == Gtk::PrintOperation::Result::APPLY)
{
//Update PrintSettings with the ones used in this PrintOperation:
m_refSettings = m_refPrintFormOperation->get_print_settings();
}
if (!m_refPrintFormOperation->is_finished())
{
//We will connect to the status-changed signal to track status
//and update a status bar. In addition, you can, for example,
//keep a list of active print operations, or provide a progress dialog.
m_refPrintFormOperation->signal_status_changed().connect(sigc::mem_fun(*this,
&ExampleWindow::on_printoperation_status_changed));
}
}
void ExampleWindow::print_or_preview(Gtk::PrintOperation::Action print_action)
{
//Create a new PrintOperation with our PageSetup and PrintSettings:
//(We use our derived PrintOperation class)
m_refPrintFormOperation = PrintFormOperation::create();
m_refPrintFormOperation->set_name(m_NameEntry.get_text() + " " + m_SurnameEntry.get_text());
m_refPrintFormOperation->set_comments(m_refTextBuffer->get_text(false /*Don't include hidden*/));
// In the printing/advanced example, the font will be set through a custom tab
// in the print dialog.
m_refPrintFormOperation->set_track_print_status();
m_refPrintFormOperation->set_default_page_setup(m_refPageSetup);
m_refPrintFormOperation->set_print_settings(m_refSettings);
m_refPrintFormOperation->signal_done().connect(sigc::mem_fun(*this,
&ExampleWindow::on_printoperation_done));
try
{
m_refPrintFormOperation->run(print_action /* print or preview */, *this);
}
catch (const Gtk::PrintError& ex)
{
//See documentation for exact Gtk::PrintError error codes.
std::cerr << "An error occurred while trying to run a print operation:"
<< ex.what() << std::endl;
}
}
void ExampleWindow::on_menu_file_new()
{
//Clear entries and textview:
m_NameEntry.set_text("");
m_SurnameEntry.set_text("");
m_refTextBuffer->set_text("");
m_TextView.set_buffer(m_refTextBuffer);
}
void ExampleWindow::on_menu_file_page_setup()
{
//Show the page setup dialog, asking it to start with the existing settings:
auto new_page_setup =
Gtk::run_page_setup_dialog(*this, m_refPageSetup, m_refSettings);
//Save the chosen page setup dialog for use when printing, previewing, or
//showing the page setup dialog again:
m_refPageSetup = new_page_setup;
}
void ExampleWindow::on_menu_file_print_preview()
{
print_or_preview(Gtk::PrintOperation::Action::PREVIEW);
}
void ExampleWindow::on_menu_file_print()
{
print_or_preview(Gtk::PrintOperation::Action::PRINT_DIALOG);
}
void ExampleWindow::on_menu_file_quit()
{
set_visible(false);
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char* argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
// Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv, app);
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>printformoperation.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "printformoperation.h"
PrintFormOperation::PrintFormOperation()
{
}
PrintFormOperation::~PrintFormOperation()
{
}
Glib::RefPtr<PrintFormOperation> PrintFormOperation::create()
{
return Glib::make_refptr_for_instance<PrintFormOperation>(new PrintFormOperation());
}
void PrintFormOperation::on_begin_print(
const Glib::RefPtr<Gtk::PrintContext>& print_context)
{
//Create and set up a Pango layout for PrintData based on the passed
//PrintContext: We then use this to calculate the number of pages needed, and
//the lines that are on each page.
m_refLayout = print_context->create_pango_layout();
Pango::FontDescription font_desc("sans 12");
m_refLayout->set_font_description(font_desc);
const double width = print_context->get_width();
const double height = print_context->get_height();
m_refLayout->set_width(static_cast<int>(width * Pango::SCALE));
//Set and mark up the text to print:
Glib::ustring marked_up_form_text;
marked_up_form_text += "<b>Name</b>: " + m_Name + "\n\n";
marked_up_form_text += "<b>Comments</b>: " + m_Comments;
m_refLayout->set_markup(marked_up_form_text);
//Set the number of pages to print by determining the line numbers
//where page breaks occur:
const int line_count = m_refLayout->get_line_count();
Glib::RefPtr<Pango::LayoutLine> layout_line;
double page_height = 0;
for (int line = 0; line < line_count; ++line)
{
Pango::Rectangle ink_rect, logical_rect;
layout_line = m_refLayout->get_line(line);
layout_line->get_extents(ink_rect, logical_rect);
const double line_height = logical_rect.get_height() / 1024.0;
if (page_height + line_height > height)
{
m_PageBreaks.push_back(line);
page_height = 0;
}
page_height += line_height;
}
set_n_pages(m_PageBreaks.size() + 1);
}
void PrintFormOperation::on_draw_page(
const Glib::RefPtr<Gtk::PrintContext>& print_context, int page_nr)
{
//Decide which lines we need to print in order to print the specified page:
int start_page_line = 0;
int end_page_line = 0;
if(page_nr == 0)
{
start_page_line = 0;
}
else
{
start_page_line = m_PageBreaks[page_nr - 1];
}
if(page_nr < static_cast<int>(m_PageBreaks.size()))
{
end_page_line = m_PageBreaks[page_nr];
}
else
{
end_page_line = m_refLayout->get_line_count();
}
//Get a Cairo Context, which is used as a drawing board:
Cairo::RefPtr<Cairo::Context> cairo_ctx = print_context->get_cairo_context();
//We'll use black letters:
cairo_ctx->set_source_rgb(0, 0, 0);
//Render Pango LayoutLines over the Cairo context:
auto iter = m_refLayout->get_iter();
double start_pos = 0;
int line_index = 0;
do
{
if(line_index >= start_page_line)
{
auto layout_line = iter.get_line();
auto logical_rect = iter.get_line_logical_extents();
int baseline = iter.get_baseline();
if (line_index == start_page_line)
{
start_pos = logical_rect.get_y() / 1024.0;
}
cairo_ctx->move_to(logical_rect.get_x() / 1024.0,
baseline / 1024.0 - start_pos);
layout_line->show_in_cairo_context(cairo_ctx);
}
line_index++;
}
while(line_index < end_page_line && iter.next_line());
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
<section xml:id="sec-printing-examples-printdialog">
<title xml:lang="en">PrintDialog</title>
<para xml:lang="en">
The following example demonstrates how to print some input from a user
interface using <classname>PrintDialog</classname>. The user interface is
similar to the previous example.
</para>
<figure xml:id="figure-printing-printdialog">
<title xml:lang="en">Printing - PrintDialog</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/printing_printdialog.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/printing/print_dialog/">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm.h>
class PrintFormDialog;
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow(const Glib::RefPtr<Gtk::Application>& app);
~ExampleWindow() override;
protected:
void build_main_menu(const Glib::RefPtr<Gtk::Application>& app);
// Action signal handlers:
void on_menu_file_new();
void on_menu_file_print_setup();
void on_menu_file_print();
void on_menu_file_quit();
// Printing-related objects:
Glib::RefPtr<PrintFormDialog> m_refPrintFormDialog;
// Child widgets:
Gtk::Box m_VBox;
Gtk::Grid m_Grid;
Gtk::Label m_NameLabel;
Gtk::Entry m_NameEntry;
Gtk::Label m_SurnameLabel;
Gtk::Entry m_SurnameEntry;
Gtk::Label m_CommentsLabel;
Gtk::ScrolledWindow m_ScrolledWindow;
Gtk::TextView m_TextView;
Glib::RefPtr<Gtk::TextBuffer> m_refTextBuffer;
Glib::RefPtr<Gtk::Builder> m_refBuilder;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>printformdialog.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_PRINT_FORM_DIALOG_H
#define GTKMM_PRINT_FORM_DIALOG_H
#include <gtkmm.h>
#include <pangomm.h>
#include <vector>
#define HAS_PRINT_DIALOG GTKMM_CHECK_VERSION(4,13,1)
#if HAS_PRINT_DIALOG
// We derive our own class from PrintDialog,
// so we can put the actual print implementation here.
class PrintFormDialog : public Gtk::PrintDialog
{
public:
static Glib::RefPtr<PrintFormDialog> create();
virtual ~PrintFormDialog();
void set_name(const Glib::ustring& name) { m_Name = name; }
void set_comments(const Glib::ustring& comments) { m_Comments = comments; }
void do_setup(Gtk::Window* parent);
void do_print(Gtk::Window* parent);
protected:
PrintFormDialog();
// PrintDialog callback slots:
void on_setup_finish(const Glib::RefPtr<Gio::AsyncResult>& result);
void on_print_finish(const Glib::RefPtr<Gio::AsyncResult>& result);
Cairo::ErrorStatus write_func(const unsigned char* data, unsigned int length,
Glib::RefPtr<Gio::OutputStream>& stream);
void do_printing(const Cairo::RefPtr<Cairo::Context>& cairo_ctx);
Glib::ustring m_Name;
Glib::ustring m_Comments;
Glib::RefPtr<Pango::Layout> m_refLayout;
Glib::RefPtr<Gtk::PrintSetup> m_refPrintSetup;
};
#endif // HAS_PRINT_DIALOG
#endif // GTKMM_PRINT_FORM_DIALOG_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include "printformdialog.h"
#include <iostream>
ExampleWindow::ExampleWindow(const Glib::RefPtr<Gtk::Application>& app)
: m_VBox(Gtk::Orientation::VERTICAL),
m_NameLabel("Name"),
m_SurnameLabel("Surname"),
m_CommentsLabel("Comments")
{
set_title("gtkmm PrintDialog Example");
set_default_size(400, 300);
#if HAS_PRINT_DIALOG
// Create a PrintDialog. (We use our derived PrintDialog class.)
m_refPrintFormDialog = PrintFormDialog::create();
#endif // HAS_PRINT_DIALOG
set_child(m_VBox);
build_main_menu(app);
m_VBox.append(m_Grid);
// Arrange the widgets inside the grid:
m_Grid.set_expand(true);
m_Grid.set_row_spacing(5);
m_Grid.set_column_spacing(5);
m_Grid.attach(m_NameLabel, 0, 0);
m_Grid.attach(m_NameEntry, 1, 0);
m_Grid.attach(m_SurnameLabel, 0, 1);
m_Grid.attach(m_SurnameEntry, 1, 1);
// Add the TextView, inside a ScrolledWindow:
m_ScrolledWindow.set_child(m_TextView);
// Only show the scrollbars when they are necessary:
m_ScrolledWindow.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
m_Grid.attach(m_CommentsLabel, 0, 2);
m_Grid.attach(m_ScrolledWindow, 1, 2);
m_ScrolledWindow.set_expand(true);
m_refTextBuffer = Gtk::TextBuffer::create();
m_TextView.set_buffer(m_refTextBuffer);
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::build_main_menu(const Glib::RefPtr<Gtk::Application>& app)
{
// Create actions for menus and toolbars:
auto refActionGroup = Gio::SimpleActionGroup::create();
// File menu:
refActionGroup->add_action("new",
sigc::mem_fun(*this, &ExampleWindow::on_menu_file_new));
refActionGroup->add_action("printsetup",
sigc::mem_fun(*this, &ExampleWindow::on_menu_file_print_setup));
refActionGroup->add_action("print",
sigc::mem_fun(*this, &ExampleWindow::on_menu_file_print));
refActionGroup->add_action("quit",
sigc::mem_fun(*this, &ExampleWindow::on_menu_file_quit));
insert_action_group("example", refActionGroup);
// When the menubar is a child of a Gtk::Window, keyboard accelerators are not
// automatically fetched from the Gio::Menu.
// See the examples/book/menus/main_menu example for an alternative way of
// adding the menubar when using Gtk::ApplicationWindow.
app->set_accel_for_action("example.new", "<Primary>n");
app->set_accel_for_action("example.print", "<Primary>p");
app->set_accel_for_action("example.quit", "<Primary>q");
m_refBuilder = Gtk::Builder::create();
// Layout the actions in a menubar:
Glib::ustring ui_menu_info =
"<interface>"
" <menu id='menu-example'>"
" <submenu>"
" <attribute name='label' translatable='yes'>_File</attribute>"
" <section>"
" <item>"
" <attribute name='label' translatable='yes'>_New</attribute>"
" <attribute name='action'>example.new</attribute>"
" </item>"
" </section>"
" <section>"
" <item>"
" <attribute name='label' translatable='yes'>Print _Setup...</attribute>"
" <attribute name='action'>example.printsetup</attribute>"
" </item>"
" <item>"
" <attribute name='label' translatable='yes'>_Print...</attribute>"
" <attribute name='action'>example.print</attribute>"
" </item>"
" </section>"
" <section>"
" <item>"
" <attribute name='label' translatable='yes'>_Quit</attribute>"
" <attribute name='action'>example.quit</attribute>"
" </item>"
" </section>"
" </submenu>"
" </menu>"
"</interface>";
try
{
m_refBuilder->add_from_string(ui_menu_info);
}
catch(const Glib::Error& ex)
{
std::cerr << "building menus failed: " << ex.what();
}
// Layout the actions in a toolbar:
Glib::ustring ui_toolbar_info =
"<!-- Generated with glade 3.18.3 and then changed manually -->"
"<interface>"
"<object class='GtkBox' id='toolbar'>"
"<property name='can_focus'>False</property>"
"<property name='spacing'>3</property>"
"<child>"
"<object class='GtkButton' id='toolbutton_new'>"
"<property name='can_focus'>False</property>"
"<property name='tooltip_text' translatable='yes'>New</property>"
"<property name='action_name'>example.new</property>"
"<property name='icon_name'>document-new</property>"
"<property name='hexpand'>False</property>"
"<property name='vexpand'>False</property>"
"</object>"
"</child>"
"<child>"
"<object class='GtkButton' id='toolbutton_print'>"
"<property name='can_focus'>False</property>"
"<property name='tooltip_text' translatable='yes'>Print</property>"
"<property name='action_name'>example.print</property>"
"<property name='icon_name'>document-print</property>"
"<property name='hexpand'>False</property>"
"<property name='vexpand'>False</property>"
"</object>"
"</child>"
"<child>"
"<object class='GtkSeparator' id='separator1'>"
"<property name='can_focus'>False</property>"
"<property name='hexpand'>False</property>"
"<property name='vexpand'>False</property>"
"</object>"
"</child>"
"<child>"
"<object class='GtkButton' id='toolbutton_quit'>"
"<property name='can_focus'>False</property>"
"<property name='tooltip_text' translatable='yes'>Quit</property>"
"<property name='action_name'>example.quit</property>"
"<property name='icon_name'>application-exit</property>"
"<property name='hexpand'>False</property>"
"<property name='vexpand'>False</property>"
"</object>"
"</child>"
"</object>"
"</interface>";
try
{
m_refBuilder->add_from_string(ui_toolbar_info);
}
catch(const Glib::Error& ex)
{
std::cerr << "building toolbar failed: " << ex.what();
}
// Get the menubar and add it to a container widget:
auto object = m_refBuilder->get_object("menu-example");
auto gmenu = std::dynamic_pointer_cast<Gio::Menu>(object);
if (!gmenu)
g_warning("GMenu not found");
else
{
auto pMenuBar = Gtk::make_managed<Gtk::PopoverMenuBar>(gmenu);
// Add the PopoverMenuBar to the window:
m_VBox.append(*pMenuBar);
}
// Get the toolbar and add it to a container widget:
auto toolbar = m_refBuilder->get_widget<Gtk::Box>("toolbar");
if (!toolbar)
g_warning("toolbar not found");
else
m_VBox.append(*toolbar);
}
void ExampleWindow::on_menu_file_new()
{
// Clear entries and textview:
m_NameEntry.set_text("");
m_SurnameEntry.set_text("");
m_refTextBuffer->set_text("");
m_TextView.set_buffer(m_refTextBuffer);
}
void ExampleWindow::on_menu_file_print_setup()
{
#if HAS_PRINT_DIALOG
m_refPrintFormDialog->do_setup(this);
#else
std::cout << "Gtk::PrintDialog is not available.\n";
#endif // HAS_PRINT_DIALOG
}
void ExampleWindow::on_menu_file_print()
{
#if HAS_PRINT_DIALOG
m_refPrintFormDialog->set_name(m_NameEntry.get_text() + " " + m_SurnameEntry.get_text());
m_refPrintFormDialog->set_comments(m_refTextBuffer->get_text(false /*Don't include hidden*/));
m_refPrintFormDialog->do_print(this);
#else
std::cout << "Gtk::PrintDialog is not available.\n";
#endif // HAS_PRINT_DIALOG
}
void ExampleWindow::on_menu_file_quit()
{
set_visible(false);
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char* argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
// Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv, app);
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>printformdialog.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "printformdialog.h"
#include <iostream>
#if HAS_PRINT_DIALOG
PrintFormDialog::PrintFormDialog()
{
set_page_setup(Gtk::PageSetup::create());
set_print_settings(Gtk::PrintSettings::create());
}
PrintFormDialog::~PrintFormDialog()
{
}
Glib::RefPtr<PrintFormDialog> PrintFormDialog::create()
{
return Glib::make_refptr_for_instance<PrintFormDialog>(new PrintFormDialog());
}
void PrintFormDialog::do_setup(Gtk::Window* parent)
{
set_title("Print setup");
if (parent)
setup(*parent, sigc::mem_fun(*this, &PrintFormDialog::on_setup_finish));
else
setup(sigc::mem_fun(*this, &PrintFormDialog::on_setup_finish));
}
void PrintFormDialog::do_print(Gtk::Window* parent)
{
set_title("Printing");
if (parent)
print(*parent, sigc::mem_fun(*this, &PrintFormDialog::on_print_finish));
else
print(sigc::mem_fun(*this, &PrintFormDialog::on_print_finish));
}
void PrintFormDialog::on_setup_finish(const Glib::RefPtr<Gio::AsyncResult>& result)
{
try
{
m_refPrintSetup = setup_finish(result);
if (m_refPrintSetup)
{
set_page_setup(m_refPrintSetup->get_page_setup());
set_print_settings(m_refPrintSetup->get_print_settings());
}
}
catch (const Gtk::DialogError& err)
{
// Can be thrown by setup_finish(result).
std::cout << "No setup selected. " << err.what() << std::endl;
}
catch (const Glib::Error& err)
{
std::cout << "Unexpected exception. " << err.what() << std::endl;
}
}
void PrintFormDialog::on_print_finish(const Glib::RefPtr<Gio::AsyncResult>& result)
{
Glib::RefPtr<Gio::OutputStream> outputStream;
try
{
outputStream = print_finish(result);
}
catch (const Gtk::DialogError& err)
{
// Can be thrown by print_finish(result).
std::cout << "No output stream. " << err.what() << std::endl;
return;
}
catch (const Glib::Error& err)
{
std::cout << "Unexpected exception. " << err.what() << std::endl;
return;
}
const double paper_width = get_page_setup()->get_paper_width(Gtk::Unit::POINTS);
const double paper_height = get_page_setup()->get_paper_height(Gtk::Unit::POINTS);
auto cairo_surface = Cairo::PdfSurface::create_for_stream(
sigc::bind(sigc::mem_fun(*this, &PrintFormDialog::write_func), outputStream),
paper_width, paper_height);
auto cairo_ctx = Cairo::Context::create(cairo_surface);
do_printing(cairo_ctx);
// cairo_surface->finish() calls write_func(),
// which must be done before the stream is closed.
cairo_surface->finish();
try
{
outputStream->close();
}
catch (const Glib::Error& err)
{
std::cout << "Error closing stream. " << err.what() << std::endl;
}
}
Cairo::ErrorStatus PrintFormDialog::write_func(const unsigned char* data,
unsigned int length, Glib::RefPtr<Gio::OutputStream>& stream)
{
try
{
gsize bytes_written = 0;
stream->write_all(data, length, bytes_written);
}
catch (const Glib::Error& err)
{
std::cout << "Error writing to stream. " << err.what() << std::endl;
return CAIRO_STATUS_WRITE_ERROR;
}
return CAIRO_STATUS_SUCCESS;
}
void PrintFormDialog::do_printing(const Cairo::RefPtr<Cairo::Context>& cairo_ctx)
{
m_refLayout = Pango::Layout::create(cairo_ctx);
m_refLayout->set_font_description(Pango::FontDescription("sans 12"));
const auto page_setup = get_page_setup();
const double page_width = page_setup->get_page_width(Gtk::Unit::POINTS);
const double page_height = page_setup->get_page_height(Gtk::Unit::POINTS);
const double left_margin = page_setup->get_left_margin(Gtk::Unit::POINTS);
const double top_margin = page_setup->get_top_margin(Gtk::Unit::POINTS);
m_refLayout->set_width(static_cast<int>(page_width * Pango::SCALE));
m_refLayout->set_wrap(Pango::WrapMode::WORD_CHAR);
cairo_ctx->translate(left_margin, top_margin);
// Set and mark up the text to print:
Glib::ustring marked_up_form_text =
"<b>Name</b>: " + m_Name + "\n\n" +
"<b>Comments</b>: " + m_Comments;
m_refLayout->set_markup(marked_up_form_text);
// We'll use black letters:
cairo_ctx->set_source_rgb(0, 0, 0);
// Render Pango LayoutLines over the Cairo context:
const double inverse_pango_scale = 1.0 / Pango::SCALE;
double page_y = 0.0;
auto iter = m_refLayout->get_iter();
double start_y_pos = iter.get_line_logical_extents().get_y() * inverse_pango_scale;
do
{
const auto layout_line = iter.get_line();
const auto logical_rect = iter.get_line_logical_extents();
const double line_height = logical_rect.get_height() * inverse_pango_scale;
if (page_y + line_height > page_height)
{
// Start a new page.
cairo_ctx->show_page();
page_y = 0.0;
start_y_pos = logical_rect.get_y() * inverse_pango_scale;
}
page_y += line_height;
cairo_ctx->move_to(logical_rect.get_x() * inverse_pango_scale,
iter.get_baseline() * inverse_pango_scale - start_y_pos);
layout_line->show_in_cairo_context(cairo_ctx);
}
while (iter.next_line());
cairo_ctx->show_page();
}
#endif // HAS_PRINT_DIALOG
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</section>
</chapter>
<chapter xml:id="chapter-recent-documents">
<title>Documentos usados recientemente</title>
<para xml:lang="en">
<application>gtkmm</application> provides an easy way to manage recently used documents. This functionality
is implemented in the <classname>Gtk::RecentManager</classname> class.
</para>
<para>Cada elemento de la lista de archivos usados recientemente se identifica mediante su URI, y puede tener metadatos asociados. Los metadatos pueden usarse para especificar cómo se mostrará el archivo, su descripción, su tipo MIME, qué aplicación lo registró, si es privado de esta aplicación y muchas otras cosas.</para>
<section xml:id="sec-recentmanager">
<title>RecentManager</title>
<para><classname>RecentManager</classname> actúa como una base de datos de archivos usados recientemente. Use esta clase para registrar archivos nuevos, eliminarlos o buscarlos. Hay una lista de archivos usados recientemente por cada usuario.</para>
<para>Puede crear un <classname>RecentManager</classname> nuevo, pero es muy probable que prefiera usar el predeterminado. Puede obtener una referencia del <classname>RecentManager</classname> predeterminado con <methodname>get_default()</methodname>.</para>
<section xml:id="recent-files-adding">
<title>Agregar elementos a la lista de archivos recientes</title>
<para>Para añadir un archivo nuevo a la lista de documentos recientes, en el caso más simple, sólo necesita proporcionar su URI. Por ejemplo:</para>
<programlisting xml:lang="en"><code>auto recent_manager = Gtk::RecentManager::get_default();
recent_manager->add_item(uri);</code></programlisting>
<para>Si quiere registrar un archivo con metadatos, puede pasar un parámetro <classname>RecentManager::Data</classname> al <methodname>add_item()</methodname>. Los metadatos que pueden establecerse en un elemento archivo particular son los siguientes:</para>
<itemizedlist xml:id="list-file-metadata">
<listitem>
<para><varname>app_exec</varname>: la línea de comandos que se usará para lanzar este recurso. La cadena puede contener los caracteres de escape «f» y «u» que se expandirán en la ruta del archivo de recursos y el URI respectivamente</para>
</listitem>
<listitem>
<para><varname>app_name</varname>: el nombre de la aplicación que registró el recurso</para>
</listitem>
<listitem>
<para><varname>description</varname>: una descripción corta del recurso en una cadena codificada en UTF-8</para>
</listitem>
<listitem>
<para><varname>display_name</varname>: el nombre del recurso que se mostrará como una cadena codificada en UTF-8</para>
</listitem>
<listitem>
<para><varname>groups</varname>: una lista de grupos asociada con este elemento. Los grupos son esencialmente cadenas arbitrarias asociadas con un recurso en particular. Puede pensar en ellos como «categorías» (como «correo-e», «gráficos», etc), o etiquetas del recurso</para>
</listitem>
<listitem>
<para><varname>is_private</varname>: determina si el recurso debe ser visible sólo a las aplicaciones que lo han registrado o no</para>
</listitem>
<listitem>
<para><varname>mime_type</varname>: el tipo MIME del recurso</para>
</listitem>
</itemizedlist>
<para>Además de añadir elementos a la lista, puede buscarlos y modificarlos o eliminarlos.</para>
</section>
<section xml:id="recent-files-lookup">
<title>Buscar elementos en la lista de archivos recientes</title>
<para>Para buscar archivos usados recientemente, <classname>RecentManager</classname> proporciona varias funciones. Para buscar un elemento específico por su URI, puede usar la función <methodname>lookup_item()</methodname>, que devolverá una clase <classname>RecentInfo</classname>. Si el URI especificado no existe en la lista de archivos recientes, <methodname>lookup_item()</methodname> arroja una excepción <classname>RecentManagerError</classname>. Por ejemplo:</para>
<programlisting xml:lang="en"><code>Glib::RefPtr<Gtk::RecentInfo> info;
try
{
info = recent_manager->lookup_item(uri);
}
catch(const Gtk::RecentManagerError& ex)
{
std::cerr << "RecentManagerError: " << ex.what() << std::endl;
}
if (info)
{
// item was found
}</code></programlisting>
<para>Un objeto <classname>RecentInfo</classname> es esencialmente un objeto que contiene todos los metadatos de un solo archivo utilizado recientemente. Puede utilizar este objeto para buscar cualquiera de las propiedades enumeradas <link linkend="list-file-metadata">anteriormente</link>.</para>
<para>Si no quiere buscar un URI específico, sino obtener una lista de todos los elementos usados recientemente, <classname>RecentManager</classname> proporciona la función <methodname>get_items()</methodname>. El valor que devuelve esta función es un <classname>std::vector</classname> de todos los archivos usados recientemente. El siguiente código demuestra cómo puede obtener una lista de todos los archivos usados recientemente:</para>
<programlisting xml:lang="en"><code>auto info_list = recent_manager->get_items();</code></programlisting>
<para>La edad máxima de los elementos de la lista de archivos usados recientemente puede establecerse con <methodname>Gtk::Settings::property_gtk_recent_files_max_age()</methodname>. El valor predeterminado es 30 días.</para>
</section>
<section xml:id="recent-files-modifying">
<title>Modificar la lista de archivos recientes</title>
<para>Podría existir alguna ocasión en la que necesite modificar la lista de archivos usados recientemente. Por ejemplo, si un archivo se borra o renombra, necesitará actualizar la localización del archivo en la lista de archivos recientes para que no apunte a un lugar incorrecto. Puede actualizar la localización de un elemento usando <methodname>move_item()</methodname>.</para>
<para>Además de cambiar el URI de un archivo, también puede eliminar archivos de la lista uno a uno o todos juntos. Realice lo primero con <methodname>remove_item()</methodname>, y lo último con <methodname>purge_items()</methodname>.</para>
<note>
<para>Las funciones <methodname>move_item()</methodname>, <methodname>remove_item()</methodname> y <methodname>purge_items()</methodname> no tienen efecto en los archivos en sí a los que se refieren los URI, sólo modifican la lista de archivos recientes.</para>
</note>
</section>
</section>
<section xml:id="sec-filechooser-filedialog">
<title xml:lang="en">FileChooser and FileDialog</title>
<para xml:lang="en">
<classname>FileChooser</classname> is an interface that can be
implemented by widgets displaying a list of files.
<application>gtkmm</application> provides three built-in implementations for choosing recent files
or other files:
<classname>FileChooserWidget</classname>,
<classname>FileChooserDialog</classname>, and
<classname>FileChooserNative</classname>.
</para>
<para xml:lang="en">
<classname>FileChooserWidget</classname> is a simple widget for
displaying a list of recently used files or other files.
<classname>FileChooserWidget</classname> is the basic building block for
<classname>FileChooserDialog</classname>, but you can embed it into your
user interface if you want to.
</para>
<note><para xml:lang="en"><classname>FileChooser</classname> and the classes that
implement it are deprecated since <application>gtkmm</application> 4.10. They have been replaced
by <classname>FileDialog</classname>, which is available since <application>gtkmm</application> 4.10.
</para></note>
<section xml:id="recentfiles-example">
<title xml:lang="en">Simple FileDialog example</title>
<para xml:lang="en">
Shown below is a simple example of how to use the
<classname>FileDialog</classname> class in a program.
This simple program has a menubar with a
<guimenuitem>File Dialog</guimenuitem> menu item.
When you select this menu item, a dialog pops up showing a list of files.
If you select <guimenuitem>Recent</guimenuitem> in the sidebar,
the list of recently used files is shown.
</para>
<note>
<para>Si esta es la primera vez que usa un programa que utiliza el sistema de archivos recientes, el diálogo podría estar vacío. De lo contrario, debería mostrar la lista de documentos recientemente usados registrados por otras aplicaciones.</para>
</note>
<para xml:lang="en">
After selecting the <guimenuitem>File Dialog</guimenuitem> menu
item and the <guimenuitem>Recent</guimenuitem> sidebar item, you should
see something similar to the following window.
</para>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/recentfiles.png"/></imageobject></mediaobject>
</screenshot>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/recent_files">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow(const Glib::RefPtr<Gtk::Application>& app);
~ExampleWindow() override;
protected:
//Signal handlers:
void on_menu_file_files_dialog();
void on_menu_file_quit();
void on_menu_file_new();
void on_dialog_finish(Glib::RefPtr<Gio::AsyncResult>& result);
//Child widgets:
Gtk::Box m_Box;
Glib::RefPtr<Gtk::Builder> m_refBuilder;
Glib::RefPtr<Gio::SimpleActionGroup> m_refActionGroup;
Glib::RefPtr<Gtk::RecentManager> m_refRecentManager;
Glib::RefPtr<Gtk::FileDialog> m_refFileDialog;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <iostream>
ExampleWindow::ExampleWindow(const Glib::RefPtr<Gtk::Application>& app)
: m_Box(Gtk::Orientation::VERTICAL),
m_refRecentManager(Gtk::RecentManager::get_default())
{
set_title("Recent files example");
set_default_size(300, 150);
//We can put a PopoverMenuBar at the top of the box and other stuff below it.
set_child(m_Box);
//Create actions for menus and toolbars:
m_refActionGroup = Gio::SimpleActionGroup::create();
//File menu:
m_refActionGroup->add_action("new",
sigc::mem_fun(*this, &ExampleWindow::on_menu_file_new));
//A menu item to open the file dialog:
m_refActionGroup->add_action("files-dialog",
sigc::mem_fun(*this, &ExampleWindow::on_menu_file_files_dialog));
m_refActionGroup->add_action("quit",
sigc::mem_fun(*this, &ExampleWindow::on_menu_file_quit) );
insert_action_group("example", m_refActionGroup);
m_refBuilder = Gtk::Builder::create();
// When the menubar is a child of a Gtk::Window, keyboard accelerators are not
// automatically fetched from the Gio::Menu.
// See the examples/book/menus/main_menu example for an alternative way of
// adding the menubar when using Gtk::ApplicationWindow.
app->set_accel_for_action("example.new", "<Primary>n");
app->set_accel_for_action("example.files-dialog", "<Primary>o");
app->set_accel_for_action("example.quit", "<Primary>q");
//Layout the actions in a menubar and a toolbar:
const char* ui_info =
"<interface>"
" <menu id='menubar'>"
" <submenu>"
" <attribute name='label' translatable='yes'>_File</attribute>"
" <item>"
" <attribute name='label' translatable='yes'>_New</attribute>"
" <attribute name='action'>example.new</attribute>"
" <attribute name='accel'><Primary>n</attribute>"
" </item>"
" <item>"
" <attribute name='label' translatable='yes'>File _Dialog</attribute>"
" <attribute name='action'>example.files-dialog</attribute>"
" <attribute name='accel'><Primary>o</attribute>"
" </item>"
" <item>"
" <attribute name='label' translatable='yes'>_Quit</attribute>"
" <attribute name='action'>example.quit</attribute>"
" <attribute name='accel'><Primary>q</attribute>"
" </item>"
" </submenu>"
" </menu>"
" <object class='GtkBox' id='toolbar'>"
" <property name='can_focus'>False</property>"
" <child>"
" <object class='GtkButton' id='toolbutton_new'>"
" <property name='can_focus'>False</property>"
" <property name='tooltip_text' translatable='yes'>New</property>"
" <property name='action_name'>example.new</property>"
" <property name='icon_name'>document-new</property>"
" <property name='hexpand'>False</property>"
" <property name='vexpand'>False</property>"
" </object>"
" </child>"
" <child>"
" <object class='GtkButton' id='toolbutton_quit'>"
" <property name='can_focus'>False</property>"
" <property name='tooltip_text' translatable='yes'>Quit</property>"
" <property name='action_name'>example.quit</property>"
" <property name='icon_name'>application-exit</property>"
" <property name='hexpand'>False</property>"
" <property name='vexpand'>False</property>"
" </object>"
" </child>"
" </object>"
"</interface>";
try
{
m_refBuilder->add_from_string(ui_info);
}
catch(const Glib::Error& ex)
{
std::cerr << "building menubar and toolbar failed: " << ex.what();
}
//Get the menubar and toolbar widgets, and add them to a container widget:
auto object = m_refBuilder->get_object("menubar");
auto gmenu = std::dynamic_pointer_cast<Gio::Menu>(object);
if (gmenu)
{
//Menubar:
auto pMenubar = Gtk::make_managed<Gtk::PopoverMenuBar>(gmenu);
m_Box.append(*pMenubar);
}
else
g_warning("GMenu not found");
auto pToolbar = m_refBuilder->get_widget<Gtk::Box>("toolbar");
if (pToolbar)
//Toolbar:
m_Box.append(*pToolbar);
else
g_warning("toolbar not found");
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::on_menu_file_new()
{
std::cout << " New File" << std::endl;
}
void ExampleWindow::on_menu_file_quit()
{
set_visible(false); //Closes the main window to stop the app->make_window_and_run().
}
void ExampleWindow::on_menu_file_files_dialog()
{
if (!m_refFileDialog)
{
m_refFileDialog = Gtk::FileDialog::create();
m_refFileDialog->set_modal(true);
}
m_refFileDialog->open(*this, sigc::mem_fun(*this, &ExampleWindow::on_dialog_finish));
}
void ExampleWindow::on_dialog_finish(Glib::RefPtr<Gio::AsyncResult>& result)
{
Glib::RefPtr<Gio::File> file;
try
{
file = m_refFileDialog->open_finish(result);
}
catch (const Gtk::DialogError& err)
{
std::cout << "No file selected, " << err.what() << std::endl;
return;
}
auto selected_uri = file->get_uri();
std::cout << "URI selected = " << selected_uri << std::endl;
std::cout << (m_refRecentManager->has_item(selected_uri) ? "A" : "Not a")
<< " recently used file" << std::endl;
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
//Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv, app);
}
]]></code></programlisting>
<!-- end inserted example code -->
<para xml:lang="en">
The constructor for <classname>ExampleWindow</classname> creates the
menu and the toolbar using <classname>Builder</classname> (see <xref linkend="chapter-menus-and-toolbars"/> for more information). It then adds
the menu and the toolbar to the window.
</para>
</section>
<section xml:id="recent-files-filtering">
<title xml:lang="en">Filtering Files</title>
<para xml:lang="en">
For any of the <classname>FileChooser</classname> classes, if you
don't wish to display all of the items in the list of files, you
can filter the list to show only those that you want. You can filter
the list with the help of the <classname>FileFilter</classname> class.
This class allows you to filter files by their name
(<methodname>add_pattern()</methodname>), or their mime type
(<methodname>add_mime_type()</methodname>).
</para>
<para xml:lang="en">
After you've created and set up the filter to match only the items you
want, you can apply a filter to a chooser widget with the
<methodname>FileChooser::add_filter()</methodname> function.
</para>
</section>
</section>
</chapter>
<chapter xml:id="chapter-keyboardevents">
<title xml:lang="en">Keyboard and Mouse Events</title>
<para xml:lang="en">
Event signals differ in some ways from other signals. These differences are described
in the <link linkend="sec-eventsignals">Event signals</link> section in
the appendix. Here we will use keyboard events and mouse events to show how
events can be used in a program.
</para>
<section xml:id="sec-keyboardevents-overview">
<title>Vista general</title>
<para xml:lang="en">
Whenever you press or release a key, an event is emitted. You can add an
event controller and connect a signal handler to handle such events.
</para>
<para xml:lang="en">
The event signal handler will receive arguments that depend on the type of event.
For key press events the arguments are (<type>guint</type> <varname>keyval</varname>,
<type>guint</type> <varname>keycode</varname>, <type>Gdk::ModifierType</type> <varname>state</varname>).
As described in the <link linkend="sec-eventsignals">appendix</link>,
the key press event signal handler returns a <type>bool</type> value, to indicate that
the signal is fully handled (<literal>true</literal>) or allow event propagation
(<literal>false</literal>).
</para>
<para xml:lang="en">
To determine which key was pressed or released, you read the value of
the <varname>keyval</varname> argument and compare it with a constant in the
<link xlink:href="https://gitlab.gnome.org/GNOME/gtk/tree/main/gdk/gdkkeysyms.h">
<filename><gdk/gdkkeysyms.h></filename></link> header file. The states of
modifier keys (shift, ctrl, etc.) are available as bit-flags in
<varname>state</varname>.
</para>
<para xml:lang="en">
Here's a simple example:
</para>
<programlisting xml:lang="en"><code><![CDATA[
bool MyClass::on_key_pressed(guint keyval, guint, Gdk::ModifierType state)
{
if (keyval == GDK_KEY_1 &&
(state & (Gdk::ModifierType::SHIFT_MASK | Gdk::ModifierType::CONTROL_MASK |
Gdk::ModifierType::ALT_MASK)) == Gdk::ModifierType::ALT_MASK)
{
handle_alt_press();
return true;
}
return false;
}
// in MyClass constructor
auto controller = Gtk::EventControllerKey::create();
controller->signal_key_pressed().connect(
sigc::mem_fun(*this, &MyClass::on_key_pressed), false);
add_controller(controller);
]]></code></programlisting>
<section xml:id="keyboardevents-simple-example">
<title>Ejemplo</title>
<para xml:lang="en">
In this example there are three keyboard shortcuts:
<keycap>Alt</keycap>+<keycap>1</keycap> selects the first radio button,
<keycap>Alt</keycap>+<keycap>2</keycap> selects the second one, and the
<keycap>Esc</keycap> key hides (closes) the window.
</para>
<figure xml:id="figure-keyboardevents-simple">
<title>Eventos del teclado: simple</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/events_simple.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/events/keyboard_simple/">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
virtual ~ExampleWindow();
private:
// Signal handler:
bool on_window_key_pressed(guint keyval, guint keycode, Gdk::ModifierType state);
Gtk::Box m_container;
Gtk::CheckButton m_first;
Gtk::CheckButton m_second;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
ExampleWindow::ExampleWindow()
{
set_title("Keyboard Events");
m_container.set_margin(10);
set_child(m_container);
// Radio buttons:
m_first.set_label("First");
m_second.set_label("Second");
m_second.set_group(m_first);
m_first.set_active();
// Main Container:
m_container.set_orientation(Gtk::Orientation::HORIZONTAL);
m_container.append(m_first);
m_container.append(m_second);
// Events.
auto controller = Gtk::EventControllerKey::create();
controller->signal_key_pressed().connect(
sigc::mem_fun(*this, &ExampleWindow::on_window_key_pressed), false);
add_controller(controller);
}
bool ExampleWindow::on_window_key_pressed(guint keyval, guint, Gdk::ModifierType state)
{
//Gdk::ModifierType::ALT_MASK -> the 'Alt' key(mask)
//GDK_KEY_1 -> the '1' key
//GDK_KEY_2 -> the '2' key
//select the first radio button, when we press alt + 1
if((keyval == GDK_KEY_1) &&
(state & (Gdk::ModifierType::SHIFT_MASK | Gdk::ModifierType::CONTROL_MASK | Gdk::ModifierType::ALT_MASK)) == Gdk::ModifierType::ALT_MASK)
{
m_first.set_active();
//returning true, cancels the propagation of the event
return true;
}
else if((keyval == GDK_KEY_2) &&
(state & (Gdk::ModifierType::SHIFT_MASK | Gdk::ModifierType::CONTROL_MASK | Gdk::ModifierType::ALT_MASK)) == Gdk::ModifierType::ALT_MASK)
{
//and the second radio button, when we press alt + 2
m_second.set_active();
return true;
}
else if(keyval == GDK_KEY_Escape)
{
//close the window, when the 'esc' key is pressed
set_visible(false);
return true;
}
//the event has not been handled
return false;
}
ExampleWindow::~ExampleWindow()
{
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
//Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</section>
<section xml:id="sec-keyboardevents-propagation">
<title>Propagación de eventos</title>
<para xml:lang="en">
As described in the <link linkend="signal-handler-sequence">appendix</link>
event signals are propagated in 3 phases:
<orderedlist inheritnum="ignore" continuation="restarts">
<listitem><simpara xml:lang="en">Capture phase - runs from the toplevel down to the event widget.</simpara></listitem>
<listitem><simpara xml:lang="en">Target phase - runs only on the event widget.</simpara></listitem>
<listitem><simpara xml:lang="en">Bubble phase - runs from the event widget up to the toplevel.</simpara></listitem>
</orderedlist>
</para>
<para xml:lang="en">
A keyboard event is first sent to the toplevel window
(<classname>Gtk::Window</classname>), where it will be checked
for any keyboard shortcuts that may be set (accelerator keys and mnemonics,
used for selecting menu items from the keyboard). After this (and assuming
the event wasn't handled), it is propagated down until it reaches the widget
which has keyboard focus. The event will then propagate up until it reaches
the top-level widget, or until you stop the propagation by returning
<literal>true</literal> from an event handler.
</para>
<para>Tenga en cuenta que, después de haber cancelado un evento, no se llamará a ninguna otra función (incluso si es del mismo widget).</para>
<section xml:id="keyboardevents-propagation-example">
<title>Ejemplo</title>
<para xml:lang="en">
In this example there are 9 <classname>EventControllerKey</classname>s,
3 in each of a <classname>Gtk::Window</classname>, a <classname>Gtk::Box</classname>
and a <classname>Gtk::Label</classname>. In each of the widgets there is
one event controller for each propagation phase.
</para>
<para>El propósito de este ejemplo es mostrar los pasos que el evento sigue cuando se emite.</para>
<para xml:lang="en">
When you write in the label, a key press event will be emitted,
which will go first, in the capture phase, to the toplevel window
(<classname>Gtk::Window</classname>), then down to its child, the
box, then to the box's child, the label with the keyboard focus.
In the target phase the event goes only to the widget with the keyboard
focus (the label). In the bubble phase the event goes first to the widget
with the keyboard focus (the label), then to its parent (the box), then
to the box's parent (the window). If the event propagates all the way
down to the label and then up to the window without being stopped,
the text you're writing will appear in the <classname>Label</classname>
above the <classname>Label</classname> you're writing in.
</para>
<figure xml:id="figure-keyboardevents-propagation">
<title>Eventos de teclado: propagación de eventos</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/events_propagation.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/events/keyboard_propagation/">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EVENT_PROPAGATION_H
#define GTKMM_EVENT_PROPAGATION_H
#include <gtkmm.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
~ExampleWindow() override;
private:
// Signal handlers:
bool label2_key_pressed(guint keyval, guint keycode, Gdk::ModifierType state, const Glib::ustring& phase);
bool box_key_pressed(guint keyval, guint keycode, Gdk::ModifierType state, const Glib::ustring& phase);
bool window_key_pressed(guint keyval, guint keycode, Gdk::ModifierType state, const Glib::ustring& phase);
bool m_first = true;
Gtk::Box m_container;
Gtk::Frame m_frame;
Gtk::Label m_label1 {"A label"};
Gtk::Label m_label2 {"Write here"};
Gtk::CheckButton m_checkbutton_can_propagate_down {"Can propagate down in the capture phase"};
Gtk::CheckButton m_checkbutton_can_propagate_up {"Can propagate up in the bubble phase"};
};
#endif //GTKMM_EVENT_PROPAGATION_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <iostream>
ExampleWindow::ExampleWindow()
{
set_title("Event Propagation");
m_container.set_margin(10);
set_child(m_container);
m_frame.set_child(m_label2);
m_label2.set_selectable();
m_checkbutton_can_propagate_down.set_active();
m_checkbutton_can_propagate_up.set_active();
// Main container
m_container.set_orientation(Gtk::Orientation::VERTICAL);
m_container.append(m_label1);
m_container.append(m_frame);
m_container.append(m_checkbutton_can_propagate_down);
m_container.append(m_checkbutton_can_propagate_up);
// Event controllers
const bool after = false; // Run before or after the default signal handlers.
// Called in the capture phase of the event handling.
auto controller = Gtk::EventControllerKey::create();
controller->set_propagation_phase(Gtk::PropagationPhase::CAPTURE);
controller->signal_key_pressed().connect(
sigc::bind(sigc::mem_fun(*this, &ExampleWindow::label2_key_pressed), "capture"), after);
m_label2.add_controller(controller);
controller = Gtk::EventControllerKey::create();
controller->set_propagation_phase(Gtk::PropagationPhase::CAPTURE);
controller->signal_key_pressed().connect(
sigc::bind(sigc::mem_fun(*this, &ExampleWindow::box_key_pressed), "capture"), after);
m_container.add_controller(controller);
controller = Gtk::EventControllerKey::create();
controller->set_propagation_phase(Gtk::PropagationPhase::CAPTURE);
controller->signal_key_pressed().connect(
sigc::bind(sigc::mem_fun(*this, &ExampleWindow::window_key_pressed), "capture"), after);
add_controller(controller);
// Called in the target phase of the event handling.
controller = Gtk::EventControllerKey::create();
controller->set_propagation_phase(Gtk::PropagationPhase::TARGET);
controller->signal_key_pressed().connect(
sigc::bind(sigc::mem_fun(*this, &ExampleWindow::label2_key_pressed), "target"), after);
m_label2.add_controller(controller);
controller = Gtk::EventControllerKey::create();
controller->set_propagation_phase(Gtk::PropagationPhase::TARGET);
controller->signal_key_pressed().connect(
sigc::bind(sigc::mem_fun(*this, &ExampleWindow::box_key_pressed), "target"), after);
m_container.add_controller(controller);
controller = Gtk::EventControllerKey::create();
controller->set_propagation_phase(Gtk::PropagationPhase::TARGET);
controller->signal_key_pressed().connect(
sigc::bind(sigc::mem_fun(*this, &ExampleWindow::window_key_pressed), "target"), after);
add_controller(controller);
// Called in the bubble phase of the event handling.
// This is the default, if set_propagation_phase() is not called.
controller = Gtk::EventControllerKey::create();
controller->set_propagation_phase(Gtk::PropagationPhase::BUBBLE);
controller->signal_key_pressed().connect(
sigc::bind(sigc::mem_fun(*this, &ExampleWindow::label2_key_pressed), "bubble"), after);
m_label2.add_controller(controller);
controller = Gtk::EventControllerKey::create();
controller->set_propagation_phase(Gtk::PropagationPhase::BUBBLE);
controller->signal_key_pressed().connect(
sigc::bind(sigc::mem_fun(*this, &ExampleWindow::box_key_pressed), "bubble"), after);
m_container.add_controller(controller);
controller = Gtk::EventControllerKey::create();
controller->set_propagation_phase(Gtk::PropagationPhase::BUBBLE);
controller->signal_key_pressed().connect(
sigc::bind(sigc::mem_fun(*this, &ExampleWindow::window_key_pressed), "bubble"), after);
add_controller(controller);
}
// By changing the return value we allow, or don't allow, the event to propagate to other elements.
bool ExampleWindow::label2_key_pressed(guint keyval, guint, Gdk::ModifierType, const Glib::ustring& phase)
{
std::cout << "Label, " << phase << " phase" << std::endl;
if (phase == "bubble")
{
const gunichar unichar = gdk_keyval_to_unicode(keyval);
if (unichar != 0)
{
if (m_first)
{
m_label2.set_label("");
m_first = false;
}
if (unichar == '\b')
m_label2.set_label("");
else
{
const Glib::ustring newchar(1, unichar);
m_label2.set_label(m_label2.get_label() + newchar);
}
}
if (!m_checkbutton_can_propagate_up.get_active())
return true; // Don't propagate
}
return false;
}
bool ExampleWindow::box_key_pressed(guint, guint, Gdk::ModifierType, const Glib::ustring& phase)
{
std::cout << "Box, " << phase << " phase" << std::endl;
// Let it propagate
return false;
}
// This will set the second label's text in the first label every time a key is pressed.
bool ExampleWindow::window_key_pressed(guint, guint, Gdk::ModifierType, const Glib::ustring& phase)
{
if (phase == "capture")
std::cout << std::endl;
std::cout << "Window, " << phase << " phase";
// Checking if the second label is on focus, otherwise the label would get
// changed by pressing keys on the window (when the label is not on focus),
// even if m_checkbutton_can_propagate_up wasn't active.
if (phase == "bubble" && m_label2.has_focus())
{
m_label1.set_label(m_label2.get_label());
std::cout << ", " << m_label2.get_label();
}
std::cout << std::endl;
if (phase == "capture" && !m_checkbutton_can_propagate_down.get_active())
return true; // Don't propagate
return false;
}
ExampleWindow::~ExampleWindow()
{
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
//Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</section>
<section xml:id="sec-keyboardevents-mouse">
<title xml:lang="en">Mouse Events</title>
<para xml:lang="en">
Mouse events are similar to keyboard events. There are some differences, though.
<itemizedlist>
<listitem><para xml:lang="en">You use <classname>Gtk::GestureClick</classname> instead of
<classname>Gtk::EventControllerKey</classname>.</para></listitem>
<listitem><para xml:lang="en">There are other event classes that can be used for handling
mouse events, for instance <classname>Gtk::EventControllerMotion</classname> and
<classname>Gtk::EventControllerScroll</classname>.</para></listitem>
<listitem><para xml:lang="en">Many signal handlers don't return a <literal>bool</literal>.
You can't stop those events from propagating.</para></listitem>
</itemizedlist>
</para>
<para xml:lang="en">
The event classes that handle mouse events are also useful for handling
events from touchscreens. There are also many subclasses of
<classname>Gtk::EventController</classname> which are only (or mainly) useful
for touchscreens. Examples: <classname>Gtk::GestureRotate</classname>,
<classname>Gtk::GestureZoom</classname>, <classname>Gtk::GestureSwipe</classname>,
<classname>Gtk::GestureLongPress</classname>.
</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1EventController.html">Reference</link></para>
<section xml:id="keyboardevents-mouse-example">
<title>Ejemplo</title>
<para xml:lang="en">
This is an expanded version of the simple keyboard events example.
</para>
<figure xml:id="figure-keyboardevents-mouse">
<title xml:lang="en">Keyboard and Mouse Events</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/events_mouse.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/events/mouse/">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
~ExampleWindow() override;
private:
// Signal handlers:
void on_window_mouse_pressed(int n_press, double x, double y);
void on_first_button_mouse_pressed(int n_press, double x, double y);
void on_second_button_mouse_pressed(int n_press, double x, double y);
void on_third_button_mouse_pressed(int n_press, double x, double y);
void on_third_button_clicked();
void on_mouse_released(int n_press, double x, double y);
void on_mouse_motion(double x, double y);
bool on_mouse_scroll(double dx, double dy);
bool on_key_pressed(guint keyval, guint keycode, Gdk::ModifierType state);
Gtk::Box m_box;
Gtk::CheckButton m_first;
Gtk::CheckButton m_second;
Gtk::Button m_third;
Glib::RefPtr<Gtk::GestureClick> m_window_click;
Glib::RefPtr<Gtk::GestureClick> m_first_button_click;
Glib::RefPtr<Gtk::GestureClick> m_second_button_click;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <map>
#include <iostream>
namespace
{
std::map<unsigned int, Glib::ustring> mouse_buttons
{
{0, "No button"},
{GDK_BUTTON_PRIMARY, "Primary"},
{GDK_BUTTON_MIDDLE, "Middle"},
{GDK_BUTTON_SECONDARY, "Secondary"},
};
}
ExampleWindow::ExampleWindow()
: m_first("First"),
m_second("Second"),
m_third("Click me")
{
set_title("Keyboard and Mouse Events");
m_box.set_margin(10);
set_child(m_box);
// Radio buttons
m_second.set_group(m_first);
m_first.set_active();
// Main container
m_box.set_orientation(Gtk::Orientation::HORIZONTAL);
m_box.append(m_first);
m_box.append(m_second);
m_box.append(m_third);
// Keyboard events
auto key_controller = Gtk::EventControllerKey::create();
key_controller->signal_key_pressed().connect(
sigc::mem_fun(*this, &ExampleWindow::on_key_pressed), false);
add_controller(key_controller);
// Mouse events, window
auto motion_controller = Gtk::EventControllerMotion::create();
motion_controller->signal_motion().connect(
sigc::mem_fun(*this, &ExampleWindow::on_mouse_motion));
add_controller(motion_controller);
m_window_click = Gtk::GestureClick::create();
m_window_click->set_button(0); // All mouse buttons
m_window_click->signal_pressed().connect(
sigc::mem_fun(*this, &ExampleWindow::on_window_mouse_pressed));
add_controller(m_window_click);
auto mouse_click = Gtk::GestureClick::create();
mouse_click->set_button(GDK_BUTTON_PRIMARY);
mouse_click->signal_released().connect(sigc::mem_fun(*this, &ExampleWindow::on_mouse_released));
add_controller(mouse_click);
auto mouse_scroll = Gtk::EventControllerScroll::create();
mouse_scroll->set_flags(Gtk::EventControllerScroll::Flags::VERTICAL);
// Handle mouse wheel rotations
mouse_scroll->signal_scroll().connect(
sigc::mem_fun(*this, &ExampleWindow::on_mouse_scroll), true);
add_controller(mouse_scroll);
// Mouse events, buttons
m_first_button_click = Gtk::GestureClick::create();
m_first_button_click->set_button(GDK_BUTTON_PRIMARY);
m_first_button_click->signal_pressed().connect(
sigc::mem_fun(*this, &ExampleWindow::on_first_button_mouse_pressed));
m_first.add_controller(m_first_button_click);
m_second_button_click = Gtk::GestureClick::create();
m_second_button_click->set_button(GDK_BUTTON_PRIMARY);
m_second_button_click->signal_pressed().connect(
sigc::mem_fun(*this, &ExampleWindow::on_second_button_mouse_pressed));
m_second.add_controller(m_second_button_click);
mouse_click = Gtk::GestureClick::create();
// This mouse event must be handled in the capture phase.
// The GtkButton C class handles the event in the capture phase and
// does not let it propagate to the bubble phase where events are
// handled by default.
mouse_click->set_propagation_phase(Gtk::PropagationPhase::CAPTURE);
mouse_click->set_button(GDK_BUTTON_PRIMARY);
mouse_click->signal_pressed().connect(
sigc::mem_fun(*this, &ExampleWindow::on_third_button_mouse_pressed));
m_third.add_controller(mouse_click);
m_third.signal_clicked().connect(
sigc::mem_fun(*this, &ExampleWindow::on_third_button_clicked));
}
void ExampleWindow::on_window_mouse_pressed(int n_press, double x, double y)
{
const auto current_button = mouse_buttons[m_window_click->get_current_button()];
std::cout << "Mouse pressed in window: " << current_button
<< ", " << n_press << ", " << x << ", " << y << std::endl;
}
void ExampleWindow::on_first_button_mouse_pressed(int n_press, double x, double y)
{
const auto current_button = mouse_buttons[m_first_button_click->get_current_button()];
std::cout << "Mouse pressed in first button: " << current_button
<< ", " << n_press << ", " << x << ", " << y << std::endl;
}
void ExampleWindow::on_second_button_mouse_pressed(int n_press, double x, double y)
{
const auto current_button = mouse_buttons[m_second_button_click->get_current_button()];
std::cout << "Mouse pressed in second button: " << current_button
<< ", " << n_press << ", " << x << ", " << y << std::endl;
}
void ExampleWindow::on_third_button_mouse_pressed(int n_press, double x, double y)
{
std::cout << "Mouse pressed in third button: "
<< n_press << ", " << x << ", " << y << std::endl;
}
void ExampleWindow::on_third_button_clicked()
{
std::cout << "Third button clicked" << std::endl;
}
void ExampleWindow::on_mouse_released(int n_press, double x, double y)
{
std::cout << "Mouse released: " << n_press << ", " << x << ", " << y << std::endl;
}
void ExampleWindow::on_mouse_motion(double x, double y)
{
std::cout << "Mouse motion: " << x << ", " << y << std::endl;
}
bool ExampleWindow::on_mouse_scroll(double dx, double dy)
{
std::cout << "Mouse scroll: " << dx << ", " << dy << std::endl;
return true; // handled
}
bool ExampleWindow::on_key_pressed(guint keyval, guint, Gdk::ModifierType state)
{
// Gdk::ModifierType::ALT_MASK -> the 'Alt' key(mask)
// GDK_KEY_1 -> the '1' key
// GDK_KEY_2 -> the '2' key
// Select the first radio button, when we press Alt + 1
if ((keyval == GDK_KEY_1) &&
(state & (Gdk::ModifierType::SHIFT_MASK | Gdk::ModifierType::CONTROL_MASK | Gdk::ModifierType::ALT_MASK)) == Gdk::ModifierType::ALT_MASK)
{
m_first.set_active();
// Returning true, cancels the propagation of the event
return true;
}
else if ((keyval == GDK_KEY_2) &&
(state & (Gdk::ModifierType::SHIFT_MASK | Gdk::ModifierType::CONTROL_MASK | Gdk::ModifierType::ALT_MASK)) == Gdk::ModifierType::ALT_MASK)
{
// and the second radio button, when we press Alt + 2
m_second.set_active();
return true;
}
else if (keyval == GDK_KEY_Escape)
{
// Close the window, when the 'Esc' key is pressed
set_visible(false);
return true;
}
// The event has not been handled
return false;
}
ExampleWindow::~ExampleWindow()
{
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
//Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</section>
</chapter>
<chapter xml:id="chapter-chapter-timeouts">
<title>Tiempos de espera, E/S y funciones en espera</title>
<section xml:id="sec-timeouts">
<title>Tiempos de espera</title>
<para>Puede preguntarse cómo hacer que <application>gtkmm</application> haga trabajo útil mientras está en espera. Afortunadamente, cuenta con varias opciones. Usando los siguientes métodos puede crear un método de tiempo de espera que se llamará cada una cierta cantidad de milisegundos.</para>
<programlisting xml:lang="en"><code>sigc::connection Glib::SignalTimeout::connect(const sigc::slot<bool()>& slot,
unsigned int interval, int priority = Glib::PRIORITY_DEFAULT);
</code></programlisting>
<para>El primer argumento es un <classname>slot</classname> al que quiere que se llame cuando pase el período de espera. El segundo argumento es el número de milisegundos entre llamadas a ese método. Recibirá un objeto <classname>sigc::connection</classname> que podrá usar para desactivar la conexión utilizando su método <methodname>disconnect()</methodname>:</para>
<programlisting xml:lang="en"><code>my_connection.disconnect();
</code></programlisting>
<para xml:lang="en">
Another way of destroying the connection is your signal handler.
It has to be of the type <classname>sigc::slot<bool()></classname>.
As you see from the definition your signal handler has to return a value of
the type <literal>bool</literal>. A definition of a sample method might
look like this:
</para>
<programlisting xml:lang="en"><code>bool MyCallback() { std::cout << "Hello World!\n" << std::endl; return true; }
</code></programlisting>
<para>Puede detener el método del período de espera mediante la devolución de <literal>false</literal> desde su gestor de señales. Por lo tanto, si quiere que su método se llame repetidamente, debe devolver <literal>true</literal>.</para>
<para>Un ejemplo de esta técnica:</para>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/timeout/">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>timerexample.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLE_TIMEREXAMPLE_H
#define GTKMM_EXAMPLE_TIMEREXAMPLE_H
#include <gtkmm.h>
#include <iostream>
#include <map>
class TimerExample : public Gtk::Window
{
public:
TimerExample();
protected:
// signal handlers
void on_button_add_timer();
void on_button_delete_timer();
void on_button_quit();
// This is the callback function the timeout will call
bool on_timeout(int timer_number);
// Member data:
Gtk::Box m_Box;
Gtk::Button m_ButtonAddTimer, m_ButtonDeleteTimer, m_ButtonQuit;
// Keep track of the timers being added:
int m_timer_number;
// These two constants are initialized in the constructor's member initializer:
const int count_value;
const int timeout_value;
// STL map for storing our connections
std::map<int, sigc::connection> m_timers;
// STL map for storing our timer values.
// Each timer counts back from COUNT_VALUE to 0 and is removed when it reaches 0
std::map<int, int> m_counters;
};
#endif // GTKMM_EXAMPLE_TIMEREXAMPLE_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "timerexample.h"
#include <gtkmm/application.h>
int main (int argc, char *argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
return app->make_window_and_run<TimerExample>(argc, argv);
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>timerexample.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "timerexample.h"
TimerExample::TimerExample() :
m_Box(Gtk::Orientation::HORIZONTAL, 10),
m_ButtonAddTimer("_Add", true),
m_ButtonDeleteTimer("_Remove", true),
m_ButtonQuit("_Quit", true),
m_timer_number(0), // start numbering the timers at 0
count_value(5), // each timer will count down 5 times before disconnecting
timeout_value(1500) // 1500 ms = 1.5 seconds
{
m_Box.set_margin(10);
set_child(m_Box);
m_Box.append(m_ButtonAddTimer);
m_Box.append(m_ButtonDeleteTimer);
m_Box.append(m_ButtonQuit);
m_ButtonAddTimer.set_expand();
m_ButtonDeleteTimer.set_expand();
m_ButtonQuit.set_expand();
// Connect the three buttons:
m_ButtonQuit.signal_clicked().connect(sigc::mem_fun(*this,
&TimerExample::on_button_quit));
m_ButtonAddTimer.signal_clicked().connect(sigc::mem_fun(*this,
&TimerExample::on_button_add_timer));
m_ButtonDeleteTimer.signal_clicked().connect(sigc::mem_fun(*this,
&TimerExample::on_button_delete_timer));
}
void TimerExample::on_button_quit()
{
set_visible(false);
}
void TimerExample::on_button_add_timer()
{
// Creation of a new object prevents long lines and shows us a little
// how slots work. We have 0 parameters and bool as a return value
// after calling sigc::bind.
sigc::slot<bool()> my_slot = sigc::bind(sigc::mem_fun(*this,
&TimerExample::on_timeout), m_timer_number);
// This is where we connect the slot to the Glib::signal_timeout()
auto conn = Glib::signal_timeout().connect(my_slot,
timeout_value);
// Remember the connection:
m_timers[m_timer_number] = conn;
// Initialize timer count:
m_counters[m_timer_number] = count_value + 1;
// Print some info to the console for the user:
std::cout << "added timeout " << m_timer_number++ << std::endl;
}
void TimerExample::on_button_delete_timer()
{
// any timers?
if(m_timers.empty())
{
// no timers left
std::cout << "Sorry, there are no timers left." << std::endl;
}
else
{
// get the number of the first timer
int timer_number = m_timers.begin()->first;
// Give some info to the user:
std::cout << "manually disconnecting timer " << timer_number
<< std::endl;
// Remove the entry in the counter values
m_counters.erase(timer_number);
// Diconnect the signal handler:
m_timers[timer_number].disconnect();
// Forget the connection:
m_timers.erase(timer_number);
}
}
bool TimerExample::on_timeout(int timer_number)
{
// Print the timer:
std::cout << "This is timer " << timer_number;
// decrement and check counter value
if (--m_counters[timer_number] == 0)
{
std::cout << " being disconnected" << std::endl;
// delete the counter entry in the STL MAP
m_counters.erase(timer_number);
// delete the connection entry in the STL MAP
m_timers.erase(timer_number);
// Note that we do not have to explicitly call disconnect() on the
// connection since Gtk::Main does this for us when we return false.
return false;
}
// Print the timer value
std::cout << " - " << m_counters[timer_number] << "/"
<< count_value << std::endl;
// Keep going (do not disconnect yet):
return true;
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
<section xml:id="sec-monitoring-io">
<title>Monitorizar E/S</title>
<para>Una característica elegante de Glib (una de las bibliotecas subyacentes de <application>gtkmm</application>) es la capacidad de verificar los datos en un descriptor de archivos. Esto es especialmente útil para aplicaciones de red. Se usa el siguiente método para lograrlo:</para>
<programlisting xml:lang="en"><code>sigc::connection Glib::SignalIO::connect(const sigc::slot<bool(Glib::IOCondition)>& slot,
Glib::PollFD::fd_t fd, Glib::IOCondition condition,
int priority = Glib::PRIORITY_DEFAULT);
</code></programlisting>
<para xml:lang="en">
The first argument is a slot you wish to have called when
the specified event (see argument 3) occurs on the file descriptor you specify
using argument two. Argument three may be one or more (using
<literal>|</literal>) of:
</para>
<itemizedlist>
<listitem>
<para xml:lang="en">
Glib::IOCondition::IO_IN - Call your method when there is data ready for
reading on your file descriptor.
</para>
</listitem>
<listitem>
<para xml:lang="en">
Glib::IOCondition::IO_OUT - Call your method when the file descriptor is
ready for writing.
</para>
</listitem>
<listitem>
<para xml:lang="en">
Glib::IOCondition::IO_PRI - Call your method when the file descriptor has urgent data to be read.
</para>
</listitem>
<listitem>
<para xml:lang="en">
Glib::IOCondition::IO_ERR - Call your method when an error has occurred on the file descriptor.
</para>
</listitem>
<listitem>
<para xml:lang="en">
Glib::IOCondition::IO_HUP - Call your method when hung up (the connection has been broken usually for pipes and sockets).
</para>
</listitem>
</itemizedlist>
<para>El valor de retorno es una <classname>sigc::connection</classname> que puede usarse para detener la monitorización del descriptor de archivos usando su método <methodname>disconnect()</methodname>. El gestor de señales <parameter>slot</parameter> debe declararse de la siguiente manera:</para>
<programlisting xml:lang="en"><code>bool input_callback(Glib::IOCondition condition);
</code></programlisting>
<para xml:lang="en">
where <parameter>condition</parameter> is as
specified above. As usual the slot is created with
<function>sigc::mem_fun()</function> (for a member method of an object), or
<function>sigc::ptr_fun()</function> (for a function). A lambda expression can
be used, if you don't need the automatic disconnection that <function>sigc::mem_fun()</function>
provides when the object goes out of scope.
</para>
<para>A continuación se muestra un pequeño ejemplo. Para usar el ejemplo, sólo ejecútelo desde un terminal; no crea una ventana. Creará una tubería llamada <literal>testfifo</literal> en la carpeta actual. Después inicie otro intérprete de comandos y ejecute <literal>echo "Hello" > testfifo</literal>. El ejemplo imprimirá cada línea que introduzca hasta que ejecute <literal>echo "Q" > testfifo</literal>.</para>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/input/">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include <gtkmm/application.h>
#include <glibmm/main.h>
#include <glibmm/iochannel.h>
#include <fcntl.h>
#include <iostream>
#include <unistd.h> //The SUN Forte compiler puts F_OK here.
//The SUN Forte compiler needs these for mkfifo:
#include <sys/types.h>
#include <sys/stat.h>
Glib::RefPtr<Gtk::Application> app;
int read_fd;
Glib::RefPtr<Glib::IOChannel> iochannel;
/*
send to the fifo with:
echo "Hello" > testfifo
quit the program with:
echo "Q" > testfifo
*/
// this will be our signal handler for read operations
// it will print out the message sent to the fifo
// and quit the program if the message was 'Q'.
bool MyCallback(Glib::IOCondition io_condition)
{
if ((io_condition & Glib::IOCondition::IO_IN) != Glib::IOCondition::IO_IN)
{
std::cerr << "Invalid fifo response" << std::endl;
}
else
{
Glib::ustring buf;
iochannel->read_line(buf);
std::cout << buf;
if (buf == "Q\n")
app->quit();
}
return true;
}
int main(int argc, char *argv[])
{
app = Gtk::Application::create("org.gtkmm.example");
if (access("testfifo", F_OK) == -1)
{
// fifo doesn't exist - create it
#ifndef DONT_HAVE_MKFIFO
if (mkfifo("testfifo", 0666) != 0)
{
std::cerr << "error creating fifo" << std::endl;
return -1;
}
#else
std::cerr << "error creating fifo: This platform does not have mkfifo()"
<< std::endl;
#endif //DONT_HAVE_MKFIFO
}
// Although we will only read from the fifo, we open it in read/write mode.
// Due to a peculiarity with the poll() system call, used deep down in glib,
// this small program will use all available CPU time, if the fifo is opened
// as O_RDONLY. See a discussion on the gtkmm-list, e.g.
// https://mail.gnome.org/archives/gtkmm-list/2015-September/msg00034.html
// and the link from there to stackoverflow.
read_fd = open("testfifo", O_RDWR);
if (read_fd == -1)
{
std::cerr << "error opening fifo" << std::endl;
return -1;
}
// connect the signal handler
Glib::signal_io().connect(
[](Glib::IOCondition io_condition){ return MyCallback(io_condition); },
read_fd, Glib::IOCondition::IO_IN);
// Creates a iochannel from the file descriptor
iochannel = Glib::IOChannel::create_from_fd(read_fd);
// and last but not least - run the application main loop
app->hold(); // keep the application running without a window
app->run(argc, argv);
// now remove the temporary fifo
if(unlink("testfifo"))
std::cerr << "error removing fifo" << std::endl;
return 0;
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
<section xml:id="sec-idle-functions">
<title>Funciones en espera</title>
<para>Si quiere especificar un método que se llame cuando no está sucediendo nada más, use lo siguiente:</para>
<programlisting xml:lang="en"><code>sigc::connection Glib::SignalIdle::connect(const sigc::slot<bool()>& slot,
int priority = Glib::PRIORITY_DEFAULT_IDLE);
</code></programlisting>
<para>Esto hace que <application>gtkmm</application> llame al método especificado siempre que no esté sucediendo nada más. Puede añadir una prioridad (los números más bajos indican prioridades más altas). Hay dos maneras de eliminar el gestor de señales: llamando a <methodname>disconnect()</methodname> en el objeto <classname>sigc::connection</classname>, o devolviendo <literal>false</literal> en el gestor de señales, que debe declararse como se indica a continuación:</para>
<programlisting xml:lang="en"><code>bool idleFunc();
</code></programlisting>
<para>Dado que esto es muy similar a los métodos mencionados anteriormente esta explicación es suficiente para entender lo que sucede. Sin embargo, aquí hay un pequeño ejemplo:</para>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/idle/">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>idleexample.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLE_IDLEEXAMPLE_H
#define GTKMM_EXAMPLE_IDLEEXAMPLE_H
#include <gtkmm.h>
#include <iostream>
class IdleExample : public Gtk::Window
{
public:
IdleExample();
protected:
// Signal Handlers:
bool on_timer();
bool on_idle();
void on_button_clicked();
// Member data:
Gtk::Box m_Box;
Gtk::Button m_ButtonQuit;
Gtk::ProgressBar m_ProgressBar_c;
Gtk::ProgressBar m_ProgressBar_d;
};
#endif // GTKMM_EXAMPLE_IDLEEXAMPLE_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>idleexample.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "idleexample.h"
IdleExample::IdleExample() :
m_Box(Gtk::Orientation::VERTICAL, 5),
m_ButtonQuit("_Quit", true)
{
m_Box.set_margin(5);
// Put buttons into container
// Adding a few widgets:
set_child(m_Box);
m_Box.append(*Gtk::make_managed<Gtk::Label>("Formatting Windows drive C:"));
m_Box.append(*Gtk::make_managed<Gtk::Label>("100 MB"));
m_Box.append(m_ProgressBar_c);
m_ProgressBar_c.set_expand();
m_Box.append(*Gtk::make_managed<Gtk::Label>(""));
m_Box.append(*Gtk::make_managed<Gtk::Label>("Formatting Windows drive D:"));
m_Box.append(*Gtk::make_managed<Gtk::Label>("5000 MB"));
m_Box.append(m_ProgressBar_d);
m_ProgressBar_d.set_expand();
auto hbox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL,10);
m_Box.append(*hbox);
hbox->append(m_ButtonQuit);
m_ButtonQuit.set_expand();
m_ButtonQuit.set_halign(Gtk::Align::END);
m_ButtonQuit.set_valign(Gtk::Align::END);
// Connect the signal handlers:
m_ButtonQuit.signal_clicked().connect( sigc::mem_fun(*this,
&IdleExample::on_button_clicked) );
// formatting drive c in timeout signal handler - called once every 50ms
Glib::signal_timeout().connect( sigc::mem_fun(*this, &IdleExample::on_timer),
50 );
// formatting drive d in idle signal handler - called as quickly as possible
Glib::signal_idle().connect( sigc::mem_fun(*this, &IdleExample::on_idle) );
}
void IdleExample::on_button_clicked()
{
set_visible(false);
}
// this timer callback function is executed once every 50ms (set in connection
// above). Use timeouts when speed is not critical. (ie periodically updating
// something).
bool IdleExample::on_timer()
{
double value = m_ProgressBar_c.get_fraction();
// Update progressbar 1/500th each time:
m_ProgressBar_c.set_fraction(value + 0.002);
return value < 0.99; // return false when done
}
// This idle callback function is executed as often as possible, hence it is
// ideal for processing intensive tasks.
bool IdleExample::on_idle()
{
double value = m_ProgressBar_d.get_fraction();
// Update progressbar 1/5000th each time:
m_ProgressBar_d.set_fraction(value + 0.0002);
return value < 0.99; // return false when done
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "idleexample.h"
#include <gtkmm/application.h>
int main (int argc, char *argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
return app->make_window_and_run<IdleExample>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
<para>Este ejemplo señala un poco la diferencia entre los métodos en espera y los de tiempo de espera. Si necesita métodos que se llamen periódicamente, y la velocidad no es muy importante, entonces quiere métodos de tiempo de espera. Si quiere métodos que se llamen tan a menudo como sea posible (como calcular un fractal en segundo plano), entonces use métodos de espera.</para>
<para>Pruebe a ejecutar este ejemplo e incrementar la carga del sistema. La barra de progreso superior incrementará progresivamente, la inferior irá más lentamente.</para>
</section>
</chapter>
<chapter xml:id="chapter-memory">
<title>Gestión de la memoria</title>
<section xml:id="sec-memory-widgets">
<title>Widgets</title>
<section xml:id="memory-normal">
<title>Gestión normal de la memoria en C++</title>
<para><application>gtkmm</application> le permite al programador controlar la vida (esto es, la construcción y la destrucción) de cualquier widget de la misma manera que cualquier otro objeto de C++. Esta flexibilidad le permite usar <literal>new</literal> y <literal>delete</literal> para crear y destruir objetos dinámicamente o para usar miembros de clase regulares (que se destruyen automáticamente cuando se destruye la clase) o usar instancias locales (que se destruyen cuando la instancia sale del alcance). Esta flexibilidad no está presente en algunos toolkits de IGU de C++, que sólo le permiten al programador usar un subconjunto de las características de gestión de memoria de C++.</para>
<para xml:lang="en">
An important difference in <application>gtkmm</application>-4.0 vs. older versions is that a C++ widgetʼs
destruction no longer causes the widget to be destroyed if itʼs within a parent;
in that case the C GtkWidget stays alive. If you had relied on that behavior in
an older version, in <application>gtkmm</application>-4.0 you must now remove the widget from its parent
first. See <link linkend="changes-gtkmm4">Changes in <application>gtkmm</application>-4.0</link>.
</para>
<para>Algunos ejemplos de gestión normal de la memoria en C++:</para>
<section xml:id="memory-class-scope">
<title>Widgets de alcance de clase</title>
<para>Si un programador no necesita asignación dinámica de memoria, puede usar los widgets automáticos en el alcance de clase. Una ventaja de los widgets automáticos en el alcance de clase es que la gestión de la memoria se agrupa en un único lugar. El programador no se arriesga a fugas de memoria por no <literal>eliminar</literal> un widget.</para>
<para>La principal desventaja de usar widgets de alcance de clase es revelar la implementación de la clase en lugar de la interfaz en su cabecera.</para>
<programlisting xml:lang="en"><code>#include <gtkmm/button.h>
#include <gtkmm/window.h>
class Foo : public Gtk::Window
{
private:
Gtk::Button theButton;
// will be destroyed when the Foo object is destroyed
};
</code></programlisting>
</section>
<section xml:id="memory-function-scope">
<title>Widgets de alcance de función</title>
<para xml:lang="en">
If a programmer does not need a class scope widget, a function scope widget
may also be used. The advantages to function scope over class scope are the
increased data hiding and reduced dependencies.
</para>
<programlisting xml:lang="en"><code>{
Gtk::Button aButton;
aButton.set_visible(true);
...
app->run();
}
</code></programlisting>
<para xml:lang="en">
However, this technique is rarely useful. Most widgets can't safely be created
before the application has been registered or activated. They can't safely be
deleted after <methodname>Gtk::Application::run()</methodname> or
<methodname>Gtk::Application::make_window_and_run()</methodname> returns.
</para>
</section>
<section xml:id="memory-dynamic-allocation">
<title>Asignación dinámica con new y delete</title>
<para xml:lang="en">
Usually, the programmer will prefer to allow containers to automatically destroy
their children by creating them using <function>Gtk::make_managed()</function>
(see below). This is not strictly required, as the <literal>new</literal> and
<literal>delete</literal> operators may also be used, but modern C++ style
discourages those in favor of safer models of memory management, so it is
better to create widgets using <function>Gtk::make_managed()</function> and
let their parent destroy them, than to manually perform dynamic allocation.
</para>
<programlisting xml:lang="en"><code>auto pButton = new Gtk::Button("Test");
// do something useful with pButton
delete pButton;
</code></programlisting>
<para xml:lang="en">Here, the programmer deletes <varname>pButton</varname> to prevent a memory leak.
</para>
</section>
</section>
<section xml:id="memory-managed-widgets">
<title>Widgets gestionados</title>
<para xml:lang="en">
Alternatively, you can let a widget's container control when the widget is
destroyed. In most cases, you want a widget to last only as long as the
container it is in. To delegate the management of a widget's lifetime to its
container, create it with <function>Gtk::make_managed()</function> and then
pack it into its container with <methodname>Gtk::Box::append()</methodname> or
a similar method. Now the widget will be destroyed whenever its container is destroyed.
</para>
<section xml:id="memory-managed-dynamic">
<title xml:lang="en">Dynamic allocation with make_managed() and append()</title>
<para xml:lang="en">
<application>gtkmm</application> provides ways including the <function>make_managed()</function> function
and <methodname>Gtk::Box::append()</methodname> method to simplify creation
and destruction of widgets whose lifetime can be managed by a parent.
</para>
<para xml:lang="en">
Every widget except a top-level window must be added to a parent container in
order to be displayed. The <function>manage()</function> function marks a widget
so that when that widget is added to a parent container, said container becomes
responsible for deleting the widget, meaning the user no longer needs to do so.
The original way to create widgets whose lifetime is managed by their parent in
this way was to call <function>manage()</function>, passing in the result of a
<literal>new</literal> expression that created a dynamically allocated widget.
</para>
<para xml:lang="en">
However, usually, when you create such a widget, you will already know that its
parent container should be responsible for destroying it. In addition, modern
C++ style discourages use of the <literal>new</literal> operator, which was
required when passing a newly created widget to <function>manage()</function>.
Therefore, <application>gtkmm</application> has added <function>make_managed()</function>, which combines
creation and marking with <function>manage()</function> into a single step. This
avoids you having to write <literal>new</literal>, which is discouraged in
modern C++ style, and more clearly expresses intent to create a managed widget.
</para>
<programlisting xml:lang="en"><code>MyContainer::MyContainer()
{
auto pButton = Gtk::make_managed<Gtk::Button>("Test");
append(*pButton); //add *pButton to MyContainer
}
</code></programlisting>
<para xml:lang="en">
Now, when objects of type <classname>MyContainer</classname> are destroyed, the
button will also be deleted. It is no longer necessary to delete <varname>pButton</varname>
to free the button's memory; its deletion has been delegated to the
<classname>MyContainer</classname> object.
</para>
<para xml:lang="en">
If you never added the widget to any parent container, it's your responsibility
to delete it. If you add it to a container widget, and later
remove it (for instance with <methodname>Gtk::Box::remove()</methodname>),
it's deleted by the container.
</para>
<para xml:lang="en">
Of course, a top-level container will not be added to another container. The
programmer is responsible for destroying the top-level container using one of
the traditional C++ techniques. Or you can let <methodname>Gtk::Application::make_window_and_run()</methodname>
create a top-level window and delete it when it's hidden.
</para>
</section>
</section>
</section>
<section xml:id="sec-memory-shared-resources">
<title>Recursos compartidos</title>
<para>Algunos objetos, como <classname>Gdk::Pixbuf</classname> y <classname>Pango::Font</classname>, se obtienen de un almacén compartido. Por lo tanto, no puede instanciar sus propias instancias. Estas clases típicamente heredan de <classname>Glib::Object</classname>. En lugar de requerirle referenciar y desreferenciar estos objetos, <application>gtkmm</application> usa el puntero inteligente <classname>Glib::RefPtr<></classname>. Cairomm tiene su propio puntero inteligente, <classname>Cairo::RefPtr<></classname>.</para>
<para xml:lang="en">
Objects such as <classname>Gdk::Pixbuf</classname> can only be instantiated
with a <methodname>create()</methodname> function. For instance,
</para>
<programlisting xml:lang="en"><code>auto pixbuf = Gdk::Pixbuf::create_from_file(filename);
</code></programlisting>
<para xml:lang="en">
You have no way of getting a bare <classname>Gdk::Pixbuf</classname>. In the
example, <varname>pixbuf</varname> is a smart pointer, so you can do this, much
like a normal pointer:
</para>
<programlisting xml:lang="en"><code>auto width = 0;
if(pixbuf)
{
width = pixbuf->get_width();
}
</code></programlisting>
<para>Cuando <varname>pixbuf</varname> salga del alcance, un <methodname>unref()</methodname> sucederá en segundo plano y ya no necesitará preocuparse más de él. No hay <literal>new</literal>, por lo que no hay <literal>delete</literal>.</para>
<para xml:lang="en">
If you copy a <classname>RefPtr</classname>, for instance</para>
<programlisting xml:lang="en"><code>auto pixbuf2 = pixbuf;
</code></programlisting>
<para xml:lang="en">or if you pass it as a method argument or a return type, then
<classname>RefPtr</classname> will do any necessary referencing to ensure that
the instance will not be destroyed until the last <classname>RefPtr</classname>
has gone out of scope.
</para>
<para>Consulte el <link linkend="chapter-refptr">apéndice</link> para obtener información detallada acerca de «RefPtr».</para>
<para xml:lang="en">
If you wish to learn more about smartpointers, you might look in these
books:
<itemizedlist>
<listitem><para xml:lang="en">
Bjarne Stroustrup, "The C++ Programming Language" Fourth Edition - section 34.3
</para></listitem>
<listitem><para xml:lang="en">
Nicolai M. Josuttis, "The C++ Standard Library" - section 4.2
</para></listitem>
</itemizedlist>
</para>
</section>
</chapter>
<chapter xml:id="chapter-builder">
<title xml:lang="en">Gtk::Builder</title>
<para xml:lang="en">
Although you can use C++ code to instantiate and arrange widgets, this can soon
become tedious and repetitive. And it requires a recompilation to show changes.
The <link xlink:href="https://gitlab.gnome.org/jpu/cambalache">Cambalache</link>
application allows you to layout widgets on screen and then save an XML description
of the arrangement. Your application can then use the <application>Gtk::Builder</application>
API to load that XML file at runtime and obtain a pointer to specifically named
widget instances.
</para>
<para xml:lang="en">
This has the following advantages:
<orderedlist inheritnum="ignore" continuation="restarts">
<listitem><simpara xml:lang="en">Less C++ code is required.</simpara></listitem>
<listitem><simpara xml:lang="en">UI changes can be seen more quickly, so UIs are able to improve.</simpara></listitem>
<listitem><simpara xml:lang="en">Designers without programming skills can create and edit UIs.</simpara></listitem>
</orderedlist>
</para>
<para>Aún así necesitará código C++ para ocuparse de los cambios en la interfaz de usuario desencadenados por las acciones del usuario, pero usar <application>Gtk::Builder</application> para la distribución de los widgets le permite enfocarse en la implementación de esa funcionalidad.</para>
<para xml:lang="en">
<application>Cambalache</application> replaces the <application>Glade</application>
application. <application>Glade</application> can generate XML files to be used
with gtk3/<application>gtkmm</application>3, but it does not support gtk4/<application>gtkmm</application>4.
</para>
<section xml:id="sec-builder-loading-ui-file">
<title xml:lang="en">Loading the .ui file</title>
<para xml:lang="en">
<classname>Gtk::Builder</classname> must be used via a
<classname>Glib::RefPtr</classname>. Like all such classes, you need to use a
<methodname>create()</methodname> method to instantiate it. For instance,</para>
<programlisting xml:lang="en"><code>auto builder = Gtk::Builder::create_from_file("basic.ui");
</code></programlisting>
<para xml:lang="en">This will instantiate the windows defined in the <filename class="extension">.ui</filename> file.
</para>
<para xml:lang="en">To instantiate just one window, or just one of the child widgets, you can specify the name of a widget as the second parameter. For instance,
</para>
<programlisting xml:lang="en"><code>auto builder = Gtk::Builder::create_from_file("basic.ui", "treeview_products");
</code></programlisting>
</section>
<section xml:id="sec-builder-accessing-widgets">
<title>Acceso a widgets</title>
<para xml:lang="en">
To access a widget, for instance to show a dialog, use
the <methodname>get_widget()</methodname> method, providing the widget's name.
This name should be specified in the <application>Cambalache</application>
window. If the widget could not be found, or is of the wrong type, then the
pointer will be set to <literal>nullptr</literal>.
</para>
<para xml:lang="en">
The dialogs in this chapter are derived from <classname>Gtk::Window</classname>
because <classname>Gtk::Dialog</classname> is deprecated since <application>gtkmm</application> 4.10.
</para>
<programlisting xml:lang="en"><code>auto pDialog = builder->get_widget<Gtk::Window>("DialogBasic");
</code></programlisting>
<para xml:lang="en">
<classname>Gtk::Builder</classname> checks for a null pointer, and checks
that the widget is of the expected type, and will show warnings on the command
line about these.
</para>
<para>Recuerde que no está instanciando un widget con <methodname>get_widget()</methodname>, sólo está obteniendo un puntero a uno que ya existe. Siempre recibirá un puntero a la misma instancia cuando llame a <methodname>get_widget()</methodname> en el mismo <classname>Gtk::Builder</classname> con el mismo nombre de widget. Los widgets se instancian durante <methodname>Gtk::Builder::create_from_file()</methodname>.</para>
<para xml:lang="en">
<methodname>get_widget()</methodname> returns child widgets that are
<function>manage()</function>ed (see the <link linkend="chapter-memory">Memory
Management</link> chapter), so they will be deleted when their parent
container is deleted. <classname>Window</classname>s (such as <classname>Dialog</classname>s)
cannot be managed because they have no parent container, so you must delete them at
some point. The documentation of <classname>Gtk::Builder</classname> has more to say
about the memory management of different kinds of objects.
</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1Builder.html">Reference</link></para>
<section xml:id="builder-example-basic">
<title>Ejemplo</title>
<para xml:lang="en">
This simple example shows how to load a <filename class="extension">.ui</filename>
file at runtime and access the widgets with <classname>Gtk::Builder</classname>.
</para>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/builder/basic">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include <gtkmm.h>
#include <iostream>
namespace
{
Gtk::Window* pDialog = nullptr;
Glib::RefPtr<Gtk::Application> app;
void on_button_clicked()
{
if (pDialog)
pDialog->set_visible(false); // set_visible(false) will cause Gtk::Application::run() to end.
}
void on_app_activate()
{
// Load the GtkBuilder file and instantiate its widgets:
auto refBuilder = Gtk::Builder::create();
try
{
refBuilder->add_from_file("basic.ui");
}
catch(const Glib::FileError& ex)
{
std::cerr << "FileError: " << ex.what() << std::endl;
return;
}
catch(const Glib::MarkupError& ex)
{
std::cerr << "MarkupError: " << ex.what() << std::endl;
return;
}
catch(const Gtk::BuilderError& ex)
{
std::cerr << "BuilderError: " << ex.what() << std::endl;
return;
}
// Get the GtkBuilder-instantiated dialog:
pDialog = refBuilder->get_widget<Gtk::Window>("DialogBasic");
if (!pDialog)
{
std::cerr << "Could not get the dialog" << std::endl;
return;
}
// Get the GtkBuilder-instantiated button, and connect a signal handler:
auto pButton = refBuilder->get_widget<Gtk::Button>("quit_button");
if (pButton)
pButton->signal_clicked().connect([] () { on_button_clicked(); });
// It's not possible to delete widgets after app->run() has returned.
// Delete the dialog with its child widgets before app->run() returns.
pDialog->signal_hide().connect([] () { delete pDialog; });
app->add_window(*pDialog);
pDialog->set_visible(true);
}
} // anonymous namespace
int main(int argc, char** argv)
{
app = Gtk::Application::create("org.gtkmm.example");
// Instantiate a dialog when the application has been activated.
// This can only be done after the application has been registered.
// It's possible to call app->register_application() explicitly, but
// usually it's easier to let app->run() do it for you.
app->signal_activate().connect([] () { on_app_activate(); });
return app->run(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</section>
<section xml:id="sec-builder-using-derived-widgets">
<title>Usar widgets derivados</title>
<para xml:lang="en">
You can use <application>Cambalache</application> and
<classname>Gtk::Builder</classname> to layout your own custom widgets
derived from <application>gtkmm</application> widget classes. This keeps your code organized and
encapsulated, separating declarative presentation from business logic, avoiding
having most of your source just be setting properties and packing in containers.
</para>
<para xml:lang="en">Use <methodname>Gtk::Builder::get_widget_derived()</methodname> like so:
</para>
<programlisting xml:lang="en"><code>auto pDialog = Gtk::Builder::get_widget_derived<DerivedDialog>(builder, "DialogDerived");
</code></programlisting>
<para xml:lang="en">
Your derived class must have a constructor that takes a pointer to the
underlying C type, and the <classname>Gtk::Builder</classname> instance.
All relevant classes of <application>gtkmm</application> typedef their underlying C type as
<classname>BaseObjectType</classname> (<classname>Gtk::Window</classname>
typedefs <classname>BaseObjectType</classname> as <type>GtkWindow</type>, for instance).
</para>
<para xml:lang="en">
You must call the base class's constructor in the initialization list, providing the C pointer. For
instance,
</para>
<programlisting xml:lang="en"><code>DerivedDialog::DerivedDialog(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& builder)
: Gtk::Window(cobject)
{
}
</code></programlisting>
<para xml:lang="en">
You could then encapsulate the manipulation of the child widgets in the
constructor of the derived class, maybe using <methodname>get_widget()</methodname>
or <methodname>get_widget_derived()</methodname> again. For instance,
</para>
<programlisting xml:lang="en"><code>DerivedDialog::DerivedDialog(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& builder)
: Gtk::Window(cobject),
m_builder(builder),
// Get the GtkBuilder-instantiated Button, and connect a signal handler:
m_pButton(m_builder->get_widget<Gtk::Button>("quit_button"))
{
if (m_pButton)
{
m_pButton->signal_clicked().connect( sigc::mem_fun(*this, &DerivedDialog::on_button_quit) );
}
}
</code></programlisting>
<para xml:lang="en">
It's possible to pass additional arguments from
<methodname>get_widget_derived()</methodname> to the constructor of the derived
widget. For instance, this call to <methodname>get_widget_derived()</methodname>
</para>
<programlisting xml:lang="en"><code>auto pDialog = Gtk::Builder::get_widget_derived<DerivedDialog>(builder, "DialogDerived", true);
</code></programlisting>
<para xml:lang="en">can invoke this constructor
</para>
<programlisting xml:lang="en"><code>DerivedDialog::DerivedDialog(BaseObjectType* cobject,
const Glib::RefPtr<Gtk::Builder>& builder, bool warning)
: Gtk::Window(cobject),
m_builder(builder),
m_pButton(m_builder->get_widget<Gtk::Button>("quit_button"))
{
// ....
}
</code></programlisting>
<section xml:id="sec-builder-and-property">
<title xml:lang="en">Gtk::Builder and Glib::Property</title>
<para xml:lang="en">
If your derived widget uses <classname>Glib::Property</classname>, it becomes slightly
more complicated. A derived widget that contains <classname>Glib::Property</classname>
members must be registered with its own name in the <type>GType</type> system.
It must be registered before any of the <methodname>create_from_*()</methodname> or
<methodname>add_from_*()</methodname> methods are called, meaning that you may have
to create an instance of your derived widget just to have its class registered.
Your derived widget must have a constructor that has the parameters required by
<methodname>get_widget_derived()</methodname> and calls the <classname>Glib::ObjectBase</classname>
constructor to register the <type>GType</type>.
</para>
<programlisting xml:lang="en"><code>DerivedButton::DerivedButton(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& builder)
: Glib::ObjectBase("MyButton"), // The GType name will be gtkmm__CustomObject_MyButton.
Gtk::Button(cobject),
prop_ustring(*this, "button-ustring"),
prop_int(*this, "button-int", 10)
{
// ....
}
</code></programlisting>
<para xml:lang="en">
It is possible also to specify properties of derived widgets, declared in C++
using <application>gtkmm</application>, within <filename class="extension">.ui</filename> files and load/set
these using <classname>Gtk::Builder</classname>. See the documentation of
<classname>Gtk::Builder</classname> for more details on how to achieve this.
<application>Cambalache</application> won’t recognize such properties as-is,
but you should be able to add a few lines of hand-coded XML to the XML code
generated by <application>Cambalache</application>.
</para>
</section>
<section xml:id="builder-example-derived">
<title>Ejemplo</title>
<para xml:lang="en">
This example shows how to load a <filename class="extension">.ui</filename> file
at runtime and access the widgets via derived classes.
</para>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/builder/derived">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>derivedbutton.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLE_DERIVED_BUTTON_H
#define GTKMM_EXAMPLE_DERIVED_BUTTON_H
#include <gtkmm.h>
class DerivedButton : public Gtk::Button
{
public:
DerivedButton();
DerivedButton(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& refBuilder);
virtual ~DerivedButton();
// Provide proxies for the properties. The proxy allows you to connect to
// the 'changed' signal, etc.
Glib::PropertyProxy<Glib::ustring> property_ustring() { return prop_ustring.get_proxy(); }
Glib::PropertyProxy<int> property_int() { return prop_int.get_proxy(); }
private:
Glib::Property<Glib::ustring> prop_ustring;
Glib::Property<int> prop_int;
};
#endif //GTKMM_EXAMPLE_DERIVED_BUTTON_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>deriveddialog.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLE_DERIVED_DIALOG_H
#define GTKMM_EXAMPLE_DERIVED_DIALOG_H
#include <gtkmm.h>
#include "derivedbutton.h"
class DerivedDialog : public Gtk::Window
{
public:
DerivedDialog(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& refBuilder);
DerivedDialog(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& refBuilder,
bool is_glad);
~DerivedDialog() override;
protected:
//Signal handlers:
void on_button_quit();
Glib::RefPtr<Gtk::Builder> m_refBuilder;
DerivedButton* m_pButton;
};
#endif //GTKMM_EXAMPLE_DERIVED_DIALOG_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>derivedbutton.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "derivedbutton.h"
#include <iostream>
// For creating a dummy object in main.cc.
DerivedButton::DerivedButton()
: Glib::ObjectBase("MyButton"),
prop_ustring(*this, "button-ustring"),
prop_int(*this, "button-int", 10)
{
}
DerivedButton::DerivedButton(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& /* refBuilder */)
: // To register custom properties, you must register a custom GType. If
// you don't know what that means, don't worry, just remember to add
// this Glib::ObjectBase constructor call to your class' constructor.
// The GType name will be gtkmm__CustomObject_MyButton.
Glib::ObjectBase("MyButton"),
Gtk::Button(cobject),
// register the properties with the object and give them names
prop_ustring(*this, "button-ustring"),
// this one has a default value
prop_int(*this, "button-int", 10)
{
// Register some handlers that will be called when the values of the
// specified parameters are changed.
property_ustring().signal_changed().connect(
[](){ std::cout << "- ustring property changed!" << std::endl; }
);
property_int().signal_changed().connect(
[](){ std::cout << "- int property changed!" << std::endl; }
);
}
DerivedButton::~DerivedButton()
{
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>deriveddialog.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "deriveddialog.h"
#include <iostream>
DerivedDialog::DerivedDialog(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& refBuilder)
: Gtk::Window(cobject),
m_refBuilder(refBuilder),
m_pButton(nullptr)
{
// Get the GtkBuilder-instantiated Button, and connect a signal handler:
m_pButton = Gtk::Builder::get_widget_derived<DerivedButton>(m_refBuilder, "quit_button");
if (m_pButton)
{
m_pButton->signal_clicked().connect( sigc::mem_fun(*this, &DerivedDialog::on_button_quit) );
std::cout << "ustring, int: " << m_pButton->property_ustring()
<< ", " << m_pButton->property_int() << std::endl;
m_pButton->property_int() = 99;
std::cout << "ustring, int: " << m_pButton->property_ustring()
<< ", " << m_pButton->property_int() << std::endl;
}
}
// The first two parameters are mandatory in a constructor that will be called
// from Gtk::Builder::get_widget_derived().
// Additional parameters, if any, correspond to additional arguments in the call
// to Gtk::Builder::get_widget_derived().
DerivedDialog::DerivedDialog(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& refBuilder,
bool is_glad)
: DerivedDialog(cobject, refBuilder) // Delegate to the other constructor
{
// Show an icon.
auto content_area = refBuilder->get_widget<Gtk::Box>("dialog-content_area");
if (!content_area)
{
std::cerr << "Could not get the content area" << std::endl;
return;
}
auto pImage = Gtk::make_managed<Gtk::Image>();
pImage->set_from_icon_name(is_glad ? "face-smile" : "face-sad");
pImage->set_icon_size(Gtk::IconSize::LARGE);
pImage->set_expand();
content_area->append(*pImage);
}
DerivedDialog::~DerivedDialog()
{
}
void DerivedDialog::on_button_quit()
{
set_visible(false); // set_visible(false) will cause Gtk::Application::run() to end.
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "deriveddialog.h"
#include <iostream>
#include <cstring>
namespace
{
bool show_icon = false;
bool is_glad = true;
DerivedDialog* pDialog = nullptr;
Glib::RefPtr<Gtk::Application> app;
void on_app_activate()
{
// Create a dummy instance before the call to refBuilder->add_from_file().
// This creation registers DerivedButton's class in the GType system.
// This is necessary because DerivedButton contains user-defined properties
// (Glib::Property) and is created by Gtk::Builder.
static_cast<void>(DerivedButton());
// Load the GtkBuilder file and instantiate its widgets:
auto refBuilder = Gtk::Builder::create();
try
{
refBuilder->add_from_file("derived.ui");
}
catch(const Glib::FileError& ex)
{
std::cerr << "FileError: " << ex.what() << std::endl;
return;
}
catch(const Glib::MarkupError& ex)
{
std::cerr << "MarkupError: " << ex.what() << std::endl;
return;
}
catch(const Gtk::BuilderError& ex)
{
std::cerr << "BuilderError: " << ex.what() << std::endl;
return;
}
// Get the GtkBuilder-instantiated dialog:
if (show_icon)
pDialog = Gtk::Builder::get_widget_derived<DerivedDialog>(refBuilder, "DialogDerived", is_glad);
else
pDialog = Gtk::Builder::get_widget_derived<DerivedDialog>(refBuilder, "DialogDerived");
if (!pDialog)
{
std::cerr << "Could not get the dialog" << std::endl;
return;
}
// It's not possible to delete widgets after app->run() has returned.
// Delete the dialog with its child widgets before app->run() returns.
pDialog->signal_hide().connect([] () { delete pDialog; });
app->add_window(*pDialog);
pDialog->set_visible(true);
}
} // anonymous namespace
int main(int argc, char** argv)
{
int argc1 = argc;
if (argc > 1)
{
if (std::strcmp(argv[1], "--glad") == 0)
{
show_icon = true;
is_glad = true;
argc1 = 1; // Don't give the command line arguments to Gtk::Application.
}
else if (std::strcmp(argv[1], "--sad") == 0)
{
show_icon = true;
is_glad = false;
argc1 = 1; // Don't give the command line arguments to Gtk::Application.
}
}
app = Gtk::Application::create("org.gtkmm.example");
// Instantiate a dialog when the application has been activated.
// This can only be done after the application has been registered.
// It's possible to call app->register_application() explicitly, but
// usually it's easier to let app->run() do it for you.
app->signal_activate().connect([] () { on_app_activate(); });
return app->run(argc1, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</section>
</chapter>
<chapter xml:id="chapter-internationalization">
<title>Internacionalización y localización</title>
<para>Las aplicaciones de <application>gtkmm</application> pueden soportar múltiples lenguajes fácilmente, incluyendo los no europeos como chino y los de derecha a izquierda como árabe. Una aplicación de <application>gtkmm</application> escrita apropiadamente y traducida usará el lenguaje apropiado en tiempo de ejecución basado en el entorno del usuario.</para>
<para>Puede no anticipar la necesidad de soportar lenguajes adicionales, pero nunca puede excluirla. Y es más fácil desarrollar la aplicación apropiadamente en primer lugar en vez de adecuarla después.</para>
<para>El proceso de escribir código fuente que se pueda traducir se llama <literal>internacionalización</literal>, a menudo abreviado como <literal>i18n</literal>. El proceso de <literal>localización</literal>, a veces abreviado <literal>l10n</literal>, proporciona el texto traducido a otros lenguajes, basado en ese código fuente.</para>
<para>La principal actividad en el proceso de internacionalización es encontrar las cadenas vistas por los usuarios y marcarlas para traducir. No necesita hacerlo todo de una vez: si crea la infraestructura del proyecto necesaria correctamente, entonces su aplicación funcionará normalmente sin importar cuántas cadenas ha cubierto.</para>
<para>Las cadenas literales deben escribirse en el código fuente en inglés, pero rodeadas por una macro. La utilidad <application>gettext</application> (o intltool) puede entonces extraer las cadenas marcadas para traducción, y sustituir el texto traducido en tiempo de ejecución.</para>
<section xml:id="sec-internationalization-intro">
<title>Preparar su proyecto</title>
<note>
<para xml:lang="en">
In the instructions below we will assume that you will not be using
<application>gettext</application> directly, but
<application>intltool</application>, which was written specifically for
<literal>GNOME</literal>. <application>intltool</application> uses
<function>gettext()</function>, which extracts strings from source code,
but <application>intltool</application> can also combine strings from
other files, for example from desktop menu details, and GUI resource
files such as <filename class="extension">.ui</filename> files, into standard
<application>gettext</application> <filename>.pot/.po</filename> files.
</para>
<para xml:lang="en">
We also assume that you are using autotools (<application>automake</application>
and <application>autoconf</application>) to build your project (although
autotools is not recommended for new applications), and
that you are using <link xlink:href="https://gitlab.gnome.org/GNOME/gnome-common/blob/master/autogen.sh">
<literal>./autogen.sh</literal> from <application>gnome-common</application></link>
or a similar <literal>autogen.sh</literal> file, which, among other
things, takes care of some <application>intltool</application>
initialization.
</para>
</note>
<note>
<para xml:lang="en">
If you are using <application>meson</application> (recommended), see the
<link xlink:href="https://mesonbuild.com/Localisation.html">Localisation</link>
chapter in Meson's manual. You can then skip this section.
</para>
</note>
<para xml:lang="en">
An alternative to <application>gnome-common</application>'s
<literal>autogen.sh</literal> may look like this:
</para>
<programlisting xml:lang="en"><code>#! /bin/sh -e
test -n "$srcdir" || srcdir=`dirname "$0"`
test -n "$srcdir" || srcdir=.
autoreconf --force --install --verbose --warnings=all "$srcdir"
echo "Running intltoolize --copy --force --automake"
intltoolize --copy --force --automake
test -n "$NOCONFIGURE" || "$srcdir/configure" "$@"</code></programlisting>
<para>Cree una subcarpeta llamada <literal>po</literal> en la carpeta raíz de su proyecto. Esta carpeta eventualmente contendrá todas sus traducciones. Dentro de ella, cree un archivo llamado <literal>LINGUAS</literal> y otro llamado <literal>POTFILES.in</literal>. Es práctica común crear también un archivo <literal>ChangeLog</literal> en la carpeta <literal>po</literal> para que los traductores puedan rastrear los cambios de las traducciones.</para>
<para><literal>LINGUAS</literal> contiene una lista ordenada alfabéticamente de códigos que identifican los idiomas a los que se traduce su programa (las líneas de comentarios que comienzan con <literal>#</literal> se ignoran). Cada código de idioma listado en el archivo <literal>LINGUAS</literal> debe tener un archivo <literal>.po</literal> correspondiente. Entonces, si su programa estuviera traducido al alemán y al japonés, su archivo <literal>LINGUAS</literal> se vería así:</para>
<programlisting xml:lang="en"><code># keep this file sorted alphabetically, one language code per line
de
ja</code></programlisting>
<para xml:lang="en">
(In addition, you'd have the files <literal>de.po</literal> and
<literal>ja.po</literal> in your
<literal>po</literal> directory which contain the German and Japanese
translations, respectively.)
</para>
<para><literal>POTFILES.in</literal> es una lista de rutas a todos los archivos que contienen cadenas marcadas para traducción, comenzando desde la carpeta raíz del proyecto. Entonces, por ejemplo, si el código fuente de su proyecto estuviera en una subcarpeta llamda <literal>src</literal>, y si tuviera dos archivos conteniendo cadenas que deben traducirse, su archivo <literal>POTFILES.in</literal> se vería así:</para>
<programlisting xml:lang="en"><code>src/main.cc
src/other.cc</code></programlisting>
<para xml:lang="en">
If you are using <application>gettext</application> directly, you can only
mark strings for translation if they are in source code file. However, if
you use <application>intltool</application>, you can mark strings for
translation in a variety of other file formats, including
<filename class="extension">.ui</filename> files, xml,
<link xlink:href="http://standards.freedesktop.org/desktop-entry-spec/latest/">.desktop files</link>
and several more. So, if you have designed some of the
application UI in xml files then also add your
<filename class="extension">.ui</filename> files to the list in
<literal>POTFILES.in</literal>.
</para>
<para>Ahora que hay un lugar en el que poner sus traducciones, necesita inicializar <application>intltool</application> y <application>gettext</application>. Añádale el siguiente código a su <literal>configure.ac</literal>, substituyendo «programname» con el nombre de su programa:</para>
<programlisting xml:lang="en"><code>IT_PROG_INTLTOOL([0.35.0])
GETTEXT_PACKAGE=programname
AC_SUBST(GETTEXT_PACKAGE)
AC_DEFINE_UNQUOTED([GETTEXT_PACKAGE], ["$GETTEXT_PACKAGE"],
[The domain to use with gettext])
AM_GNU_GETTEXT([external])
AM_GNU_GETTEXT_VERSION([0.17])
PROGRAMNAME_LOCALEDIR=[${datadir}/locale]
AC_SUBST(PROGRAMNAME_LOCALEDIR)</code></programlisting>
<para>Esta variable <varname>PROGRAMNAME_LOCALEDIR</varname> se usará luego en el archivo <literal>Makefile.am</literal>, para definir una macro que se usará cuando inicialice <application>gettext</application> en su código fuente.</para>
<para xml:lang="en">
<literal>AM_GLIB_GNU_GETTEXT</literal> has been an alternative to
<literal>AM_GNU_GETTEXT</literal> and <literal>AM_GNU_GETTEXT_VERSION</literal>,
but <literal>AM_GLIB_GNU_GETTEXT</literal> is now deprecated, and shall
not be used in new code.
</para>
<para xml:lang="en">
In the top-level Makefile.am:
<itemizedlist>
<listitem>
<para xml:lang="en">Add <literal>po</literal> to the <literal>SUBDIRS</literal>
variable. Without this, your translations won't get built and
installed when you build the program</para>
</listitem>
<listitem>
<para xml:lang="en">
Define <literal>INTLTOOL_FILES</literal> as:</para>
<programlisting xml:lang="en"><code>INTLTOOL_FILES = intltool-extract.in \
intltool-merge.in \
intltool-update.in</code></programlisting>
</listitem>
<listitem>
<para xml:lang="en">
Add <literal>INTLTOOL_FILES</literal> to the
<literal>EXTRA_DIST</literal> list of files. This ensures that when
you do a <command>make dist</command>, these files will be
included in the source tarball.
</para>
</listitem>
<listitem>
<para xml:lang="en">
Update your <literal>DISTCLEANFILES</literal>:</para>
<programlisting xml:lang="en"><code>DISTCLEANFILES = ... intltool-extract \
intltool-merge \
intltool-update \
po/.intltool-merge-cache</code></programlisting>
</listitem>
<listitem>
<para xml:lang="en">
Depending on the types of files that contain translatable strings,
add code such as</para>
<programlisting xml:lang="en"><code>desktopdir = $(datadir)/applications
desktop_in_files = programname.desktop.in
desktop_DATA = $(desktop_in_files:.desktop.in=.desktop)
@INTLTOOL_DESKTOP_RULE@</code></programlisting>
</listitem>
</itemizedlist>
</para>
<para>En su <literal>src/Makefile.am</literal>, actualice su <literal>AM_CPPFLAGS</literal> para añadir la siguiente definición de macro de preprocesador:</para>
<programlisting xml:lang="en"><code>AM_CPPFLAGS = ... -DPROGRAMNAME_LOCALEDIR=\"${PROGRAMNAME_LOCALEDIR}\"</code></programlisting>
<para>Esta macro se usará cuando inicialice <literal>gettext</literal> en su código fuente.</para>
</section>
<section xml:id="sec-i18n-marking-strings">
<title>Marcar cadenas para traducir</title>
<para>Las cadenas literales deben introducirse en el código fuente en inglés, pero deben rodearse por una llamada a la función <function>gettext()</function>. Estas cadenas se extraerán para traducción y las traducciones podrán usarse en tiempo de ejecución en lugar de las cadenas originales en inglés.</para>
<para>El paquete <application>GNU gettext</application> le permite marcar cadenas en el código fuente, extraer esas cadenas para su traducción, y usar las cadenas traducidas en su aplicación.</para>
<para xml:lang="en">
However, <application>Glib</application> defines
<function>gettext()</function>
support macros which are shorter wrappers in an easy-to-use form.
To use these macros, include <literal><glibmm/i18n.h></literal>,
and then, for example, substitute:</para>
<programlisting xml:lang="en"><code>display_message("Getting ready for i18n.");</code></programlisting>
<para xml:lang="en">with:</para>
<programlisting xml:lang="en"><code>display_message(_("Getting ready for i18n."));</code></programlisting>
<para xml:lang="en">
For reference, it is possible to generate a file which contains all
strings which appear in your code, even if they are not marked for translation,
together with file name and line
number references. To generate such a file named
<literal>my-strings</literal>, execute the following command,
within the source code directory:
</para>
<programlisting xml:lang="en"><code>xgettext -a -o my-strings --omit-header *.cc *.h</code></programlisting>
<para xml:lang="en">
Finally, to let your program use the translation for the current locale,
add this code to the beginning of your <filename>main.cc</filename> file, to initialize gettext.
</para>
<programlisting xml:lang="en"><code>bindtextdomain(GETTEXT_PACKAGE, PROGRAMNAME_LOCALEDIR);
bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
textdomain(GETTEXT_PACKAGE);</code></programlisting>
<section xml:id="sec-i18n-gettext">
<title>Cómo funciona gettext</title>
<para xml:lang="en">
The <application>intltool-update</application> or
<application>xgettext</application> script extracts the strings
and puts them in a <filename>mypackage.pot</filename> file.
The translators of your application create their translations by
first copying this <filename>.pot</filename> file to a
<filename>localename.po</filename> file. A locale identifies a
language and an encoding for that language, including date and numerical
formats. Later, when the text in your source code has changed, the
<application>msgmerge</application> or <application>intltool-update</application>
script is used to update the <filename>localename.po</filename> files from
the regenerated <filename>.pot</filename> file.
</para>
<para>En tiempo de instalación, los archivos <filename>.po</filename> se convierten a formato binario (con la extensión <filename>.mo</filename>) y se ponen en una carpeta de sistema para archivos de localización, por ejemplo <filename>/usr/share/locale/</filename>.</para>
<para>Cuando la aplicación se ejecuta, la biblioteca <application>gettext</application> verifica la carpeta de sistema para comprobar si hay un archivo <filename>.mo</filename> para el entorno de localización del usuario (puede establecer la localización, por ejemplo, con «export LANG=de_DE.UTF-8» desde una consola bash). Más tarde, cuando el programa alcance una llamada a <literal>gettext</literal>, buscará la traducción de la cadena particular. Si no encuentra ninguna, usará la cadena original.</para>
</section>
<section xml:id="sec-i18n-testing">
<title>Comprobar y añadir las traducciones</title>
<para xml:lang="en">
To convince yourself that you've done well, you may wish to add a
translation for a new locale. In order to do that, go to the
<filename>po</filename> subdirectory of your project and
execute the following command:
</para>
<programlisting xml:lang="en"><code>intltool-update --pot</code></programlisting>
<para>Eso creará un archivo llamado <filename>programname.pot</filename>. Ahora, copie ese archivo a <filename>languagecode.po</filename>, como por ejemplo <filename>de.po</filename> o <filename>hu.po</filename>. También añada ese código de lenguaje a <literal>LINGUAS</literal>. El archivo <filename>.po</filename> contiene una cabecera y una lista de cadenas en inglés, con espacios para introducir las cadenas traducidas. Asegúrese de establecer la codificación del archivo <filename>.po</filename> (especificada en la cabecera, pero también en el contenido) a <literal>UTF-8</literal>.</para>
<!-- TODO: This need more explanation. What's the point of the fuzzy tag then? murrayc -->
<note>
<para xml:lang="en">
It's possible that certain strings will be marked as
<literal>fuzzy</literal> in the <filename>.po</filename> file.
These translations will not substitute the original string. To make
them appear, simply remove the <literal>fuzzy</literal> tag.
A <literal>fuzzy</literal> tag appears if a string content changed,
but the location is still the same.
</para>
</note>
</section>
<section xml:id="sec-i18n-resources">
<title>Recursos</title>
<para xml:lang="en">
More information about what lies behind the internationalization and localization process
is presented and demonstrated in:
<itemizedlist>
<listitem>
<para xml:lang="en">
<link xlink:href="https://wiki.gnome.org/TranslationProject/DevGuidelines">
L10N Guidelines for Developers</link>
</para>
</listitem>
<listitem>
<para xml:lang="en">
<link xlink:href="http://bazaar.launchpad.net/~intltool/intltool/trunk/view/head:/README">Intltool README</link>
</para>
</listitem>
<listitem>
<para xml:lang="en">
<link xlink:href="https://wiki.gnome.org/TranslationProject/GitHowTo">How to use Git for GNOME translators</link>
</para>
</listitem>
<listitem>
<para xml:lang="en">
<link xlink:href="http://www.gnu.org/software/gettext/manual/gettext.html">gettext manual</link>
</para>
</listitem>
<listitem>
<para xml:lang="en">
<link xlink:href="http://ftp.gnome.org/pub/GNOME/sources/gtkmm_hello/"><literal>gtkmm_hello</literal> example package</link>
</para>
</listitem>
<listitem>
<para xml:lang="en">
<link xlink:href="http://ftp.gnome.org/pub/GNOME/sources/gnomemm_hello/"><literal>gnomemm_hello</literal> example package</link>
</para>
</listitem>
</itemizedlist>
</para>
</section>
</section>
<section xml:id="sec-i18n-expecting-utf8">
<title>Esperar UTF8</title>
<para>Una aplicación adecuadamente internacionalizada no asumirá nada sobre el número de bytes en un carácter. Eso significa que no debe usar aritmética de punteros para avanzar sobre los caracteres en una cadena, y significa que no debe usar <classname>std::string</classname> o funciones estándar de C como <function>strlen()</function> porque asumen lo mismo.</para>
<para>Sin embargo, probablemente ya evite matrices char* simples y aritmética de punteros usando <classname>std::string</classname>, por lo que sólo necesita comenzar a usar <classname>Glib::ustring</classname> en su lugar. Consulte el capítulo <link linkend="sec-basics-ustring">Conceptos básicos</link> acerca de <classname>Glib::ustring</classname>.</para>
<section xml:id="i18n-ustring-iostreams">
<title>Glib::ustring y std::iostreams</title>
<!-- <para>TODO: This section is not clear - it needs to spell things out more clearly and obviously.</para> -->
<para xml:lang="en">
Unfortunately, the integration with the standard iostreams is not completely
foolproof. <application>gtkmm</application> converts <classname>Glib::ustring</classname>s to a
locale-specific encoding (which usually is not UTF-8) if you output them to an
<classname>ostream</classname> with <function>operator<<</function>.
Likewise, retrieving <classname>Glib::ustring</classname>s from
<classname>istream</classname> with <function>operator>></function>
causes a conversion in the opposite direction. But this scheme breaks down if
you go through a <classname>std::string</classname>, e.g. by inputting text
from a stream to a <classname>std::string</classname> and then implicitly
converting it to a <classname>Glib::ustring</classname>. If the string
contained non-ASCII characters and the current locale is not UTF-8 encoded, the
result is a corrupted <classname>Glib::ustring</classname>. You can work around
this with a manual conversion. For instance, to retrieve the
<classname>std::string</classname> from a <classname>ostringstream</classname>:
</para>
<programlisting xml:lang="en"><code>std::locale::global(std::locale("")); // Set the global locale to the user's preferred locale.
// Usually unnecessary here, because Glib::init()
// or Gtk::Application::create() does it for you.
std::ostringstream output;
output << percentage << " % done";
label->set_text(Glib::locale_to_utf8(output.str()));</code></programlisting>
</section>
</section>
<section xml:id="sec-i18n-pitfalls">
<title>Errores comunes</title>
<para>Hay algunos errores comunes que eventualmente descubriría por sí mismo. Pero esta sección le ayudará a evitarlos.</para>
<section xml:id="i18n-string-semantics">
<title>Mismas cadenas, semánticas diferentes</title>
<para xml:lang="en">Sometimes two English strings are identical but have different meanings in
different contexts, so they would probably not be identical when translated. Since the English strings are
used as look-up keys, this causes problems.</para>
<para>En estos casos, debe agregar caracteres adicionales a las cadenas. Por ejemplo, use <literal>"jumps[noun]"</literal> y <literal>"jumps[verb]"</literal> en lugar de sólo <literal>"jumps"</literal> y límpielas de nuevo fuera de la llamada a <function>gettext</function>. Si añade caracteres adicionales también debe añadir un comentario para los traductores antes de la llamada a <function>gettext</function>. Tales comentarios se mostrarán en los archivos <filename>.po</filename>. Por ejemplo:</para>
<programlisting xml:lang="en"><code>// note to translators: don't translate the "[noun]" part - it is
// just here to distinguish the string from another "jumps" string
text = strip(gettext("jumps[noun]"), "[noun]");</code></programlisting>
<para xml:lang="en">
If you use <application>Glib</application>'s support macros, it's easier. Use
<function>C_()</function> instead of <function>_()</function>. For instance:
</para>
<programlisting xml:lang="en"><code>GLib::ustring text(C_("noun", "jumps"));</code></programlisting>
</section>
<section xml:id="i18n-composition">
<title>Composición de cadenas</title>
<para xml:lang="en">
C programmers use <function>sprintf()</function> to compose and concatenate
strings. C++ favors streams, but unfortunately, this approach makes
translation difficult, because each fragment of text is translated separately,
without allowing the translators to rearrange them according to the grammar of
the language.</para>
<para>Por ejemplo, este código podría ser problemático:</para>
<programlisting xml:lang="en"><code>std::cout << _("Current amount: ") << amount
<< _(" Future: ") << future << std::endl;
label.set_text(_("Really delete ") + filename + _(" now?"));</code></programlisting>
<para xml:lang="en">
So you should either avoid this situation or use
<link xlink:href="https://gnome.pages.gitlab.gnome.org/glibmm/classGlib_1_1ustring.html"><function>Glib::ustring::compose()</function></link>
which supports syntax such as:
</para>
<programlisting xml:lang="en"><code>std::cout << Glib::ustring::compose(
_("Current amount: %1 Future: %2"), amount, future) << std::endl;
label.set_text(Glib::ustring::compose(_("Really delete %1 now?"), filename));</code></programlisting>
</section>
<section xml:id="i18n-display-size">
<title>Asumir el tamaño de las cadenas mostradas</title>
<para>Nunca sabe cuánto espacio ocupará una cadena en la pantalla cuando se haya traducido. Bien podría duplicar el tamaño de la cadena original en inglés. Afortunadamente, la mayoría de los widgets de <application>gtkmm</application> se expandirán en tiempo de ejecución al tamaño requerido.</para>
</section>
<section xml:id="i18n-unusual-words">
<title>Palabras poco frecuentes</title>
<para>Debe evitar abreviaciones crípticas, lenguaje vulgar o técnico. Estos son generalmente difíciles de traducir e incluso les pueden resultar difíciles de entender a los hablantes nativos. Por ejemplo, es preferible «application» antes que «app».</para>
</section>
<section xml:id="i18n-non-ascii-characters">
<title>Usar caracteres no ASCII en cadenas</title>
<para>Actualmente, <application>gettext</application> no soporta caracteres no ASCII (es decir, cualquier carácter con un código superior a 127) en el código fuente. Por ejemplo, no puede usar el signo de derechos de autor (©).</para>
<para xml:lang="en">To work around this, you could write a comment in the
source code just before the string, telling the translators to
use the special character if it is available in their languages. For English, you could then make an American English
<filename>en_US.po</filename> translation which used that special character.</para>
</section>
</section>
<section xml:id="sec-i18n-getting-help-with-translations">
<title>Obtener ayuda con las traducciones</title>
<para xml:lang="en">If your program is free software, there is a whole <literal>GNOME</literal>
subproject devoted to helping you make translations, the
<link xlink:href="https://wiki.gnome.org/TranslationProject/"><literal>GNOME</literal>
Translation Project</link>.</para>
<para xml:lang="en">The way it works is that you upload your source code to a git
repository where translators can access it, then contact the gnome-i18n
mailing list and ask to have your program added to the
<link xlink:href="http://l10n.gnome.org/module/">list of modules to translate</link>.</para>
<para xml:lang="en">Then you make sure you update the file
<filename>POTFILES.in</filename> in the
<filename>po/</filename> subdirectory
(<command>intltool-update -m</command> can help with this) so
that the translators always access updated
<filename>myprogram.pot</filename> files, and simply freeze
the strings at least a couple of days before you make a new
release, announcing it on gnome-i18n. Depending on the number
of strings your program contains and how popular it is, the
translations will then start to tick in as
<filename>languagename.po</filename> files.</para>
<para>Tenga en cuenta que la mayoría de los equipos de idiomas sólo constan de 1 a 3 personas, por lo que si su programa contiene muchas cadenas, puede pasar un tiempo antes de que alguien tenga el tiempo de echarle un vistazo. Además, la mayoría de los traductores no quieren perder el tiempo (traducir es una tarea que consume mucho) por lo que si no juzgan que su proyecto es realmente serio (en el sentido de que esté pulido y se mantenga) podrían decidir usar su tiempo en algún otro proyecto.</para>
</section>
</chapter>
<chapter xml:id="chapter-customwidgets">
<title>Widgets personalizados</title>
<para xml:lang="en"><application>gtkmm</application> makes it very easy to derive new widgets by inheriting from an
existing widget class, either by deriving from a container and adding child
widgets, or by deriving from a single-item widget, and changing its behavior.
But you might occasionally find that no suitable starting point already exists.
In this case, you can implement a widget from scratch.</para>
<section xml:id="sec-custom-containers">
<title>Contenedores personalizados</title>
<para xml:lang="en">When deriving a custom container widget directly from <classname>Gtk::Widget</classname>,
you should override the following virtual methods:
<itemizedlist>
<listitem><para xml:lang="en"><methodname>get_request_mode_vfunc()</methodname>: Return what <literal>Gtk::SizeRequestMode</literal> is preferred by the container.</para></listitem>
<listitem><para xml:lang="en"><methodname>measure_vfunc()</methodname>: Calculate the minimum and natural width or height of the container.</para></listitem>
<listitem><para xml:lang="en"><methodname>size_allocate_vfunc()</methodname>: Position the child widgets, given the height and width that the container has actually been given.</para></listitem>
</itemizedlist>
</para>
<para xml:lang="en">The <methodname>get_request_mode_vfunc()</methodname>,
<methodname>measure_vfunc()</methodname>, and
<methodname>size_allocate_vfunc()</methodname> virtual methods control the
layout of the child widgets. For instance, if your container has 2
child widgets, with one below the other, your
<methodname>get_request_mode_vfunc()</methodname> might request
height-for-width layout. Then your
<methodname>measure_vfunc()</methodname>
might report the maximum of the widths of the child widgets when asked
to report width, and it might report the sum of their heights when asked
to report height. If you want padding between
the child widgets then you would add that to the width and height too.
Your widget's container will use this result to ensure that your widget
gets enough space, and not less. By examining each widget's parent, and
its parent, this logic will eventually decide the size of the top-level
window.</para>
<para xml:lang="en">You are not guaranteed to get the <literal>Gtk::SizeRequestMode</literal>
that you request. Therefore <methodname>measure_vfunc()</methodname> must
return sensible values for all reasonable values of its input parameters.
For a description of <methodname>measure_vfunc()</methodname>'s parameters see
also the description of <methodname>Gtk::Widget::measure()</methodname>, which
may be better documented than <methodname>measure_vfunc()</methodname>.</para>
<para xml:lang="en"><methodname>size_allocate_vfunc()</methodname> receives the actual
height and width that the parent container has decided to give to your
widget. This might be more than the minimum, or even more than the natural
size, for instance if the
top-level window has been expanded. You might choose to ignore the extra
space and leave a blank area, or you might choose to expand your child
widgets to fill the space, or you might choose to expand the padding
between your widgets. It's your container, so you decide.</para>
<para xml:lang="en">Your container must unparent its children before the underlying C object
(a <classname>gtkmm__GtkWidget</classname>) is finalized. If your container
is used as a managed widget, it shall unparent its children in a
<methodname>Gtk::Widget::signal_destroy()</methodname> handler (available
since <application>gtkmm</application> 4.8). If your container is not managed, that signal handler
is not called. Instead the children shall be unparented in the C++ destructor.
If you want your container to be useful both ways, unparent the children
both in the destructor and in a signal handler. See the example code.</para>
<section xml:id="custom-container-example">
<title>Ejemplo</title>
<para xml:lang="en">This example implements a container with child widgets, one above
the other. Of course, in this case it would be far simpler just to use
a vertical <classname>Gtk::Box</classname> or <classname>Gtk::Grid</classname>.</para>
<figure xml:id="figure-custom-container">
<title>Contenedor personalizado</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/custom_container.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/custom/custom_container/">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm.h>
#include "mycontainer.h"
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
~ExampleWindow() override;
protected:
//Signal handlers:
void on_button_quit();
//Child widgets:
Gtk::Box m_VBox;
MyContainer m_MyContainer;
Gtk::Button m_Button_Child;
Gtk::Label m_Label_Child;
Gtk::Box m_ButtonBox;
Gtk::Button m_Button_Quit;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>mycontainer.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_CUSTOM_CONTAINER_MYCONTAINER_H
#define GTKMM_CUSTOM_CONTAINER_MYCONTAINER_H
#include <gtkmm/widget.h>
#include <gtkmm/version.h>
#define HAS_SIGNAL_DESTROY GTKMM_CHECK_VERSION(4,7,1)
class MyContainer : public Gtk::Widget
{
public:
MyContainer();
~MyContainer() override;
void append(Gtk::Widget& child);
void prepend(Gtk::Widget& child);
void remove(Gtk::Widget& child);
protected:
int get_nvis_children() const;
//Overrides:
Gtk::SizeRequestMode get_request_mode_vfunc() const override;
void measure_vfunc(Gtk::Orientation orientation, int for_size, int& minimum, int& natural,
int& minimum_baseline, int& natural_baseline) const override;
void size_allocate_vfunc(int width, int height, int baseline) override;
#if HAS_SIGNAL_DESTROY
// Signal handler:
void on_container_destroy();
#endif
};
#endif //GTKMM_CUSTOM_CONTAINER_MYCONTAINER_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
ExampleWindow::ExampleWindow()
: m_VBox(Gtk::Orientation::VERTICAL),
m_Button_Child("Child button"),
m_Label_Child("Child label", Gtk::Align::END, Gtk::Align::CENTER),
m_Button_Quit("Quit")
{
set_title("Custom Container example");
set_default_size(400, 200);
m_VBox.set_margin(6);
set_child(m_VBox);
//Add the child widgets to the custom container:
m_MyContainer.append(m_Button_Child);
m_MyContainer.append(m_Label_Child);
m_MyContainer.prepend(*Gtk::make_managed<Gtk::Label>(
"First line\nSecond line\nThird line"));
m_MyContainer.set_expand();
m_VBox.append(m_MyContainer);
#if HAS_SIGNAL_DESTROY
// A managed custom container.
auto container = Gtk::make_managed<MyContainer>();
container->prepend(*Gtk::make_managed<Gtk::Label>("Second custom container"));
container->set_expand();
m_VBox.append(*container);
#endif
m_VBox.append(m_ButtonBox);
m_ButtonBox.append(m_Button_Quit);
m_ButtonBox.set_margin(6);
m_Button_Quit.set_hexpand(true);
m_Button_Quit.set_halign(Gtk::Align::END);
m_Button_Quit.signal_clicked().connect( sigc::mem_fun(*this,
&ExampleWindow::on_button_quit) );
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::on_button_quit()
{
set_visible(false);
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
//Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>mycontainer.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "mycontainer.h"
#include <algorithm> // std::max()
// This example container is a simplified vertical Box.
//
// It can't be used as a managed widget, managed by another container
// unless Gtk::Widget::signal_destroy() exists.
// It would cause an error like
// Gtk-WARNING **: 08:31:48.137: Finalizing gtkmm__GtkWidget 0x561b777462c0, but it still has children left:
MyContainer::MyContainer()
{
#if HAS_SIGNAL_DESTROY
signal_destroy().connect(sigc::mem_fun(*this, &MyContainer::on_container_destroy));
#endif
}
MyContainer::~MyContainer()
{
// If MyContainer is a managed widget, the underlying C object is destructed
// before this C++ destructor is executed.
if (!gobj())
return;
// If MyContainer is not a managed widget, unparent all children here.
while (Widget* child = get_first_child())
child->unparent();
}
#if HAS_SIGNAL_DESTROY
// This signal handler is called only if MyContainer is a managed widget.
void MyContainer::on_container_destroy()
{
while (Widget* child = get_first_child())
child->unparent();
}
#endif
// Get number of visible children.
int MyContainer::get_nvis_children() const
{
int nvis_children = 0;
for (const Widget* child = get_first_child(); child; child = child->get_next_sibling())
if (child->get_visible())
++nvis_children;
return nvis_children;
}
Gtk::SizeRequestMode MyContainer::get_request_mode_vfunc() const
{
return Gtk::SizeRequestMode::HEIGHT_FOR_WIDTH;
}
// Discover the total amount of minimum space and natural space needed by
// this container and its children.
void MyContainer::measure_vfunc(Gtk::Orientation orientation, int for_size,
int& minimum, int& natural, int& minimum_baseline, int& natural_baseline) const
{
// Don't use baseline alignment.
minimum_baseline = -1;
natural_baseline = -1;
minimum = 0;
natural = 0;
// Number of visible children.
const int nvis_children = get_nvis_children();
if (orientation == Gtk::Orientation::HORIZONTAL)
{
// Divide the height equally among the visible children.
if (for_size > 0 && nvis_children > 0)
for_size /= nvis_children;
// Request a width equal to the width of the widest visible child.
}
for (const Widget* child = get_first_child(); child; child = child->get_next_sibling())
if (child->get_visible())
{
int child_minimum, child_natural, ignore;
child->measure(orientation, for_size, child_minimum, child_natural, ignore, ignore);
minimum = std::max(minimum, child_minimum);
natural = std::max(natural, child_natural);
}
if (orientation == Gtk::Orientation::VERTICAL)
{
// The allocated height will be divided equally among the visible children.
// Request a height equal to the number of visible children times the height
// of the highest child.
minimum *= nvis_children;
natural *= nvis_children;
}
}
void MyContainer::size_allocate_vfunc(int width, int height, int baseline)
{
//Do something with the space that we have actually been given:
//(We will not be given heights or widths less than we have requested, though
//we might get more.)
// Number of visible children.
const int nvis_children = get_nvis_children();
if (nvis_children <= 0)
{
// No visible child.
return;
}
//Assign space to the children:
Gtk::Allocation child_allocation;
const int height_per_child = height / nvis_children;
//Place the first visible child at the top-left:
child_allocation.set_x(0);
child_allocation.set_y(0);
//Make it take up the full width available:
child_allocation.set_width(width);
child_allocation.set_height(height_per_child);
//Divide the height equally among the visible children.
for (Widget* child = get_first_child(); child; child = child->get_next_sibling())
if (child->get_visible())
{
child->size_allocate(child_allocation, baseline);
child_allocation.set_y(child_allocation.get_y() + height_per_child);
}
}
void MyContainer::append(Gtk::Widget& child)
{
child.insert_at_end(*this);
}
void MyContainer::prepend(Gtk::Widget& child)
{
child.insert_at_start(*this);
}
void MyContainer::remove(Gtk::Widget& child)
{
child.unparent();
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</section>
<section xml:id="sec-custom-widgets">
<title>Widgets personalizados</title>
<para>Derivando directamente de <classname>Gtk::Widget</classname> puede hacer todo el dibujo de su widget directamente, en lugar de sólo ordenar widgets hijos. Por ejemplo, una <classname>Gtk::Label</classname> dibuja el texto de la etiqueta, pero no hace esto usando a otros widgets.</para>
<para xml:lang="en">When deriving from <classname>Gtk::Widget</classname>, you should
override the following virtual methods. The methods marked (optional)
need not be overridden in all custom widgets. The base class's methods
may be appropriate.
<itemizedlist>
<listitem><para xml:lang="en"><methodname>get_request_mode_vfunc()</methodname>: (optional) Return what <literal>Gtk::SizeRequestMode</literal> is preferred by the widget.</para></listitem>
<listitem><para xml:lang="en"><methodname>measure_vfunc()</methodname>: Calculate the minimum and natural width or height of the widget.</para></listitem>
<listitem><para xml:lang="en"><methodname>size_allocate_vfunc()</methodname>: (optional) Position the widget, given the height and width that it has actually been given.</para></listitem>
<listitem><para xml:lang="en"><methodname>on_realize()</methodname>: (optional)</para></listitem>
<listitem><para xml:lang="en"><methodname>on_unrealize()</methodname>: (optional)</para></listitem>
<listitem><para xml:lang="en"><methodname>on_map()</methodname>: (optional)</para></listitem>
<listitem><para xml:lang="en"><methodname>on_unmap()</methodname>: (optional)</para></listitem>
<listitem><para xml:lang="en"><methodname>snapshot_vfunc()</methodname>: Create a render node, e.g. a <classname>Cairo::Context</classname> node, and draw on it.</para></listitem>
</itemizedlist>
</para>
<para xml:lang="en">The first 3 methods in the previous table are also overridden in custom
containers. They are briefly described in the
<link linkend="sec-custom-containers">Custom Containers</link> section.
</para>
<section xml:id="custom-init-functions">
<title xml:lang="en">Class Init and Instance Init Functions</title>
<para xml:lang="en">Some <application>GTK</application> functions, if called at all, must be
called from the class init function. Some other <application>GTK</application>
functions, if called, must be called from the instance init function.
If your custom widget must call any of those functions, you can derive a class
from <classname>Glib::ExtraClassInit</classname> and derive your custom class
from that class. The <link linkend="custom-css-name-example">custom CSS name example</link>
shows how that's done.</para>
</section>
<section xml:id="custom-widget-example">
<title>Ejemplo</title>
<para xml:lang="en">This example implements a widget which draws Penrose triangles.</para>
<figure xml:id="figure-custom-widget">
<title>Widget personalizado</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/custom_widget.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/custom/custom_widget/">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm.h>
#include "mywidget.h"
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
virtual ~ExampleWindow();
protected:
//Signal handlers:
void on_button_quit();
//Child widgets:
Gtk::Grid m_Grid;
MyWidget m_MyWidgetS1;
MyWidget m_MyWidgetS2;
Gtk::Box m_ButtonBox;
Gtk::Button m_Button_Quit;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>mywidget.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_CUSTOM_WIDGET_MYWIDGET_H
#define GTKMM_CUSTOM_WIDGET_MYWIDGET_H
#include <gtkmm/widget.h>
#include <gtkmm/border.h>
#include <gdkmm/rgba.h>
class MyWidget : public Gtk::Widget
{
public:
MyWidget();
~MyWidget() override;
protected:
// Overrides:
Gtk::SizeRequestMode get_request_mode_vfunc() const override;
void measure_vfunc(Gtk::Orientation orientation, int for_size, int& minimum, int& natural,
int& minimum_baseline, int& natural_baseline) const override;
void on_map() override;
void on_unmap() override;
void on_realize() override;
void on_unrealize() override;
void snapshot_vfunc(const Glib::RefPtr<Gtk::Snapshot>& snapshot) override;
Gtk::Border m_padding;
Gdk::RGBA m_foreground{0.0, 0.0, 1.0};
Gdk::RGBA m_background{1.0, 0.0, 0.0};
};
#endif //GTKMM_CUSTOM_WIDGET_MYWIDGET_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
ExampleWindow::ExampleWindow()
: m_Button_Quit("Quit")
{
set_title("Custom Widget example");
set_default_size(600, 400);
m_Grid.set_margin(6);
m_Grid.set_row_spacing(10);
m_Grid.set_column_spacing(10);
set_child(m_Grid);
m_Grid.attach(m_MyWidgetS1, 0, 0);
m_Grid.attach(m_MyWidgetS2, 1, 1);
m_Grid.attach(m_ButtonBox, 0, 2, 2, 1);
m_ButtonBox.append(m_Button_Quit);
m_ButtonBox.set_margin(6);
m_Button_Quit.set_hexpand(true);
m_Button_Quit.set_halign(Gtk::Align::END);
m_Button_Quit.signal_clicked().connect( sigc::mem_fun(*this, &ExampleWindow::on_button_quit) );
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::on_button_quit()
{
set_visible(false);
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
//Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>mywidget.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "mywidget.h"
#include <gdkmm/general.h> // for Gdk::Cairo:: functions
#include <gtkmm/snapshot.h>
MyWidget::MyWidget()
{
// Expand, if there is extra space.
set_expand(true);
m_padding.set_left(5);
m_padding.set_right(15);
m_padding.set_top(10);
m_padding.set_bottom(20);
}
MyWidget::~MyWidget()
{
}
Gtk::SizeRequestMode MyWidget::get_request_mode_vfunc() const
{
//Accept the default value supplied by the base class.
return Gtk::Widget::get_request_mode_vfunc();
}
//Discover the total amount of minimum space and natural space needed by
//this widget.
//Let's make this simple example widget always need minimum 60 by 50 and
//natural 100 by 70.
void MyWidget::measure_vfunc(Gtk::Orientation orientation, int /* for_size */,
int& minimum, int& natural, int& minimum_baseline, int& natural_baseline) const
{
if (orientation == Gtk::Orientation::HORIZONTAL)
{
minimum = 60;
natural = 100;
}
else
{
minimum = 50;
natural = 70;
}
// Don't use baseline alignment.
minimum_baseline = -1;
natural_baseline = -1;
}
void MyWidget::on_map()
{
//Call base class:
Gtk::Widget::on_map();
}
void MyWidget::on_unmap()
{
//Call base class:
Gtk::Widget::on_unmap();
}
void MyWidget::on_realize()
{
//Call base class:
Gtk::Widget::on_realize();
}
void MyWidget::on_unrealize()
{
//Call base class:
Gtk::Widget::on_unrealize();
}
void MyWidget::snapshot_vfunc(const Glib::RefPtr<Gtk::Snapshot>& snapshot)
{
const auto allocation = get_allocation();
const Gdk::Rectangle rect(0, 0, allocation.get_width(), allocation.get_height());
// Create a cairo context to draw on.
auto cr = snapshot->append_cairo(rect);
// paint the background
Gdk::Cairo::set_source_rgba(cr, m_background);
Gdk::Cairo::add_rectangle_to_path(cr, rect);
cr->fill();
// draw the foreground
const double scale_x = 0.001 * (allocation.get_width() - m_padding.get_left() - m_padding.get_right());
const double scale_y = 0.001 * (allocation.get_height() - m_padding.get_top() - m_padding.get_bottom());
Gdk::Cairo::set_source_rgba(cr, m_foreground);
cr->translate(m_padding.get_left(), m_padding.get_top());
cr->rectangle(0.0, 0.0, 1000.0*scale_x, 1000.0*scale_y);
cr->move_to(155.*scale_x, 165.*scale_y);
cr->line_to(155.*scale_x, 838.*scale_y);
cr->line_to(265.*scale_x, 900.*scale_y);
cr->line_to(849.*scale_x, 564.*scale_y);
cr->line_to(849.*scale_x, 438.*scale_y);
cr->line_to(265.*scale_x, 100.*scale_y);
cr->line_to(155.*scale_x, 165.*scale_y);
cr->move_to(265.*scale_x, 100.*scale_y);
cr->line_to(265.*scale_x, 652.*scale_y);
cr->line_to(526.*scale_x, 502.*scale_y);
cr->move_to(369.*scale_x, 411.*scale_y);
cr->line_to(633.*scale_x, 564.*scale_y);
cr->move_to(369.*scale_x, 286.*scale_y);
cr->line_to(369.*scale_x, 592.*scale_y);
cr->move_to(369.*scale_x, 286.*scale_y);
cr->line_to(849.*scale_x, 564.*scale_y);
cr->move_to(633.*scale_x, 564.*scale_y);
cr->line_to(155.*scale_x, 838.*scale_y);
cr->stroke();
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</section>
<section xml:id="sec-custom-css-names">
<title xml:lang="en">Custom CSS Names</title>
<para xml:lang="en">Many aspects of the look of widgets are controlled by CSS (Cascading Style
Sheet) files. With CSS files you can choose color, font, line thickness, etc.
If you give some widgets their own names or their own CSS classes, you can define
CSS rules that apply only to those widgets, for instance certain buttons,
without affecting other similar widgets.</para>
<section xml:id="css-widget-class-name">
<title xml:lang="en">CSS Node Name, Widget Name, CSS Class Name</title>
<para xml:lang="en">There are three ways of referring from a widget to data in a CSS file:
<itemizedlist>
<listitem><para xml:lang="en"><methodname>gtk_widget_class_set_css_name()</methodname>
can only be called from the class init function. It sets the CSS node name
of all instances of a class (a GType). See the <link linkend="custom-init-functions">
Class Init and Instance Init Functions</link> section.</para></listitem>
<listitem><para xml:lang="en"><methodname>Gtk::Widget::set_name()</methodname>
can be called from a C++ constructor. It sets the name of
a widget instance.</para></listitem>
<listitem><para xml:lang="en"><methodname>Gtk::Widget::add_class_name()</methodname>
can be called from a C++ constructor. It adds the name of a CSS class,
used by a widget instance.</para></listitem>
</itemizedlist>
The following example shows a button with its own CSS node name, a label with
a widget name and a label that uses its own CSS class.</para>
</section>
<section xml:id="custom-style-information">
<title xml:lang="en">Custom Style Information</title>
<para xml:lang="en">To add a style sheet to an application, use one of the
<methodname>Gtk::CssProvider::load_from_*()</methodname> methods.
Then add it with <methodname>Gtk::StyleProvider::add_provider_for_display()</methodname>
(available since <application>gtkmm</application> 4.10) or <methodname>Gtk::StyleContext::add_provider_for_display()</methodname>.
<classname>Gtk::StyleContext</classname> also contains methods to read some style
information, but this class is deprecated since <application>gtkmm</application> 4.10.</para>
<para xml:lang="en">CSS files are described in the documentation of GTK.</para>
</section>
<section xml:id="custom-css-name-example">
<title>Ejemplo</title>
<para xml:lang="en">This example implements a button and two labels with custom style information.</para>
<figure xml:id="figure-custom-css-name">
<title xml:lang="en">Custom CSS Name</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/custom_css_name.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/custom/custom_css_name/">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm.h>
#include "mybutton.h"
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
~ExampleWindow() override;
protected:
// Signal handlers:
void on_button_quit();
static void on_parsing_error(const Glib::RefPtr<const Gtk::CssSection>& section,
const Glib::Error& error);
// Child widgets:
Gtk::Box m_VBox;
MyButton m_Button_Child;
Gtk::Label m_Label_Child1;
Gtk::Label m_Label_Child2;
Gtk::Box m_ButtonBox;
Gtk::Button m_Button_Quit;
Glib::RefPtr<Gtk::CssProvider> m_refCssProvider;
};
#endif //GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>mybutton.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLE_MYBUTTON_H
#define GTKMM_EXAMPLE_MYBUTTON_H
#include <gtkmm/button.h>
#include "myextrainit.h"
class MyButton : public MyExtraInit, public Gtk::Button
{
public:
MyButton(const Glib::ustring& label, bool mnemonic=false);
~MyButton() override;
};
#endif //GTKMM_EXAMPLE_MYBUTTON_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>myextrainit.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLE_MYEXTRAINIT_H
#define GTKMM_EXAMPLE_MYEXTRAINIT_H
#include <glibmm/extraclassinit.h>
#include <glibmm/ustring.h>
// Calls gtk_widget_class_set_css_name() in the class init function.
class MyExtraInit : public Glib::ExtraClassInit
{
public:
MyExtraInit(const Glib::ustring& css_name);
private:
Glib::ustring m_css_name;
};
#endif //GTKMM_EXAMPLE_MYEXTRAINIT_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><
{ on_parsing_error(section, error); }
);
m_refCssProvider->load_from_path("custom_gtkmm.css");
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::on_button_quit()
{
set_visible(false);
}
void ExampleWindow::on_parsing_error(const Glib::RefPtr<const Gtk::CssSection>& section,
const Glib::Error& error)
{
std::cerr << "on_parsing_error(): " << error.what() << std::endl;
if (section)
{
const auto file = section->get_file();
if (file)
{
std::cerr << " URI = " << file->get_uri() << std::endl;
}
auto start_location = section->get_start_location();
auto end_location = section->get_end_location();
std::cerr << " start_line = " << start_location.get_lines()+1
<< ", end_line = " << end_location.get_lines()+1 << std::endl;
std::cerr << " start_position = " << start_location.get_line_chars()
<< ", end_position = " << end_location.get_line_chars() << std::endl;
}
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char* argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
// Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>mybutton.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "mybutton.h"
#include <gtk/gtk.h> // For GTK_IS_BUTTON()
#include <iostream>
MyButton::MyButton(const Glib::ustring& label, bool mnemonic)
:
// The GType name will actually be gtkmm__CustomObject_MyButton
Glib::ObjectBase("MyButton"),
MyExtraInit("my-button"), // CSS node name, which must be used in the CSS file.
Gtk::Button(label, mnemonic)
{
// This shows the GType name:
std::cout << "GType name: " << G_OBJECT_TYPE_NAME(gobj()) << std::endl;
// This shows that the GType still derives from GtkButton:
std::cout << "Gtype is a GtkButton?: " << GTK_IS_BUTTON(gobj()) << std::endl;
// The CSS name can be set either
// - for a GType (in this case for your custom class) with gtk_widget_class_set_css_name(), or
// - for a widget instance with gtk_widget_set_name() (Gtk::Widget::set_name()).
//
// gtk_widget_class_set_css_name(), if used, must be called in the class init function.
// It has not been wrapped in a C++ function.
// Gtk::Widget::set_name() can be called in a C++ constructor.
//
// Another alternative: The custom button inherits the CSS name "button" from
// GtkButton. That name can be used in the CSS file.
}
MyButton::~MyButton()
{
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>myextrainit.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "myextrainit.h"
#include <gtkmm/widget.h>
#include <gtk/gtk.h>
namespace
{
using BaseObjectType = GtkWidget;
using BaseClassType = GtkWidgetClass;
// These callback functions are called from GLib (a C library).
// They shall have C linkage. (Many compilers accept callback functions
// with C++ linkage, but such a program has undefined behavior.)
//
// If you want the functions with C linkage to have internal linkage,
// they must be declared 'static', even though they are defined in an anonymous
// namespace. The compiler respects namespace declarations of functions
// with C linkage, but the linker does not.
extern "C"
{
// Extra class init function.
static void class_init_function(void* g_class, void* class_data)
{
g_return_if_fail(GTK_IS_WIDGET_CLASS(g_class));
const auto klass = static_cast<BaseClassType*>(g_class);
const auto css_name = static_cast<Glib::ustring*>(class_data);
gtk_widget_class_set_css_name(klass, css_name->c_str());
}
// Extra instance init function.
static void instance_init_function(GTypeInstance* instance, void* /* g_class */)
{
g_return_if_fail(GTK_IS_WIDGET(instance));
// Nothing to do here.
// This extra instance init function just shows how such a function can
// be added to a custom widget, if necessary.
}
} // extern "C"
} // anonymous namespace
MyExtraInit::MyExtraInit(const Glib::ustring& css_name)
:
Glib::ExtraClassInit(class_init_function, &m_css_name, instance_init_function),
m_css_name(css_name)
{
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>custom_gtkmm.css</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[/* Example of a CSS style sheet. */
my-button {
background-color: red;
color: blue;
padding: 10px 15px 20px 5px; /* top right bottom left */
border: solid black 2px;
border-radius: 8px;
}
my-button:active {
background-color: rgb(0,255,0);
}
#my-label1 {
background-color: rgb(180,180,180);
color: rgb(0,255,255);
padding: 10px 15px 10px 25px; /* top right bottom left */
}
#my-label1:hover {
background-color: rgb(220,220,220);
}
.my-label2 {
background-color: rgb(180,180,100);
color: black;
padding: 5px 15px 5px 25px; /* top right bottom left */
}
.my-label2:hover {
background-color: rgb(220,220,140);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</section>
</chapter>
<chapter xml:id="chapter-multi-threaded-programs">
<title>Programas con múltiples hilos</title>
<section xml:id="sec-the-constraints">
<title>Las limitaciones</title>
<para xml:lang="en">
Care is required when writing programs based on <application>gtkmm</application> using
multiple threads of execution, arising from the fact that
<application>libsigc++</application>, and in particular
<classname>sigc::trackable</classname>, are not thread-safe. That's
because none of the complex interactions that occur behind the scenes
when using <application>libsigc++</application> are protected by a
mutex or other means of synchronization.
<footnote>
<para xml:lang="en">
These interactions arise from the fact that, amongst other things, a
class inheriting from <classname>sigc::trackable</classname> will, via
that inheritance, have a <classname>std::list</classname> object
keeping track of slots created by calls to
<function>sigc::mem_fun()</function> representing any of its
non-static methods (more particularly it keeps a list of callbacks
which will null the connected slots on its destruction). Each
<classname>sigc::slot</classname> object also keeps, via
<classname>sigc::slot_rep</classname>, its own
<classname>sigc::trackable</classname> object to track any
<classname>sigc::connection</classname> objects which it needs to
inform about its demise, and also has a function to deregister itself
from any <classname>sigc::trackable</classname> on disconnection or
destruction. <classname>sigc::signal</classname> objects also keep
lists of slots, which will be updated by a call to their
<methodname>connect()</methodname> method or calls to any
<classname>sigc::connection</classname> object relating to such a
connection.
</para>
</footnote>
</para>
<section xml:id="the-rules">
<title>Las reglas</title>
<para>Esto requiere que se observen una cantidad de reglas cuando escribe programas de múltiples hilos usando <application>gtkmm</application>. Estas se exponen abajo, pero un punto a destacar es que se requiere cuidado adicional cuando deriva clases de <classname>sigc::trackable</classname>, porque los efectos no son intuitivos (consulte particularmente los puntos 4 y 5 debajo).</para>
<orderedlist inheritnum="ignore" continuation="restarts">
<listitem>
<para>Use <classname>Glib::Dispatcher</classname> para invocar las funciones de <application>gtkmm</application> desde hilos de trabajo (esto se explica con má detalle en la próxima sección).</para>
</listitem>
<listitem>
<para>Un objeto <classname>sigc::signal</classname> debe considerarse propiedad del hilo que lo creó. Solo ese hilo debe conectar un objeto <classname>sigc::slot</classname> al objeto de la señal, y solo ese hilo debe llamar a <methodname>emit()</methodname> u <methodname>operator()()</methodname> sobre la señal, o anular cualquier objeto <classname>sigc::slot</classname> conectado. Sigue (junto a otras cosas) que cualquier objeto de señal proporcionado por un widget de <application>gtkmm</application> solo se opere en el hilo principal de la IGU y cualquier objeto que derive de <classname>sigc::trackable</classname> con métodos no estáticos referenciados por «slots» conectados al objeto de señal solo deben destruirse en ese hilo.</para>
</listitem>
<listitem>
<para>Cualquier objeto <classname>sigc::connection</classname> solo debe considerarse propiedad del hilo en el que se llamó al método que devuelve el objeto <classname>sigc::connection</classname>. Solo ese hilo debe llamar a métodos de <classname>sigc::connection</classname> sobre el objeto.</para>
</listitem>
<listitem>
<para xml:lang="en">
A <classname>sigc::slot</classname> object created by a call to
<function>sigc::mem_fun()</function> which references a method of a
class deriving from <classname>sigc::trackable</classname> should
never be copied to another thread, nor destroyed by a different thread
than the one which created it.
</para>
</listitem>
<listitem>
<para>Si un objeto de clase particular deriva de <classname>sigc::trackable</classname>, solo un hilo debe crear objetos <classname>sigc::slot</classname> representando cualquiera de los métodos no estáticos de la clase llamando a <function>sigc::mem_fun()</function>. El primer hilo que cree un «slot» semejante debe considerarse dueño del objeto relevante con el propósito de crear más «slots» referenciando a <emphasis>cualquiera</emphasis> de sus métodos no estáticos que usan esa función, o anulando aquellos «slots» desconectándolos o destruyendo el objeto «trackable».</para>
</listitem>
<listitem>
<para>A pesar de que <application>glib</application> en sí es segura para hilos, cualquier envoltorio de <application>glibmm</application> que use <application>libsigc++</application> no lo será. Entonces por ejemplo solo el hilo en el bucle principal debe llamar a <methodname>Glib::SignalIdle::connect()</methodname>, <methodname>Glib::SignalIO::connect()</methodname>, <methodname>Glib::SignalTimeout::connect()</methodname>, <methodname>Glib::SignalTimeout::connect_seconds</methodname> para ese bucle principal, o manipular cualquier objeto <classname>sigc::connection</classname> que devuelvan.</para>
<para xml:lang="en">
The connect*_once() variants,
<methodname>Glib::SignalIdle::connect_once()</methodname>,
<methodname>Glib::SignalTimeout::connect_once()</methodname>,
<methodname>Glib::SignalTimeout::connect_seconds_once()</methodname>,
are thread-safe for any case where the slot is not created by a call to
<function>sigc::mem_fun()</function> which represents a method of a class
deriving from <classname>sigc::trackable</classname>.
</para>
</listitem>
</orderedlist>
</section>
</section>
<section xml:id="sec-using-glib-dispatcher">
<title>Usar Glib::Dispatcher</title>
<para>Los «slots» conectados a objetos <classname>sigc::signal</classname> se ejecutan en el hilo que llama a <methodname>emit()</methodname> u <methodname>operator()()</methodname> en la señal. <classname>Glib::Dispatcher</classname> no se comporta de esa manera: en su lugar, sus «slots» conectados se ejecutan en el hilo en el que el objeto <classname>Glib::Dispatcher</classname> se construyó (que debe tener un bucle principal de glib). Si un objeto <classname>Glib::Dispatcher</classname> se construye en el hilo principal de la IGU (que por ende será el hilo receptor), cualquier hilo de trabajo puede emitir sobre él y sus «slot» conectados podrán ejecutar funciones de <application>gtkmm</application> con seguridad.</para>
<para>Aún así, algunas reglas de seguridad de hilos sobre el uso de <classname>Glib::Dispatcher</classname> se aplican. Como se mencionó, un objeto <classname>Glib::Dispatcher</classname> debe construirse en el hilo receptor (aquel en cuyo bucle principal ejecutará sus «slot» conectados). Por defecto este es el hilo principal del programa, a pesar de que hay un constructor <classname>Glib::Dispatcher</classname> que toma el objeto <classname>Glib::MainContext</classname> de cualquier hilo que tiene un bucle principal. Solo el hilo receptor debe llamar a <methodname>connect()</methodname> en el objeto <classname>Glib::Dispatcher</classname>, o manipular cualquier objeto <classname>sigc::connection</classname> relacionado, a menos que se emplee sincronización adicional. Sin embargo, cualquier hilo de trabajo puede emitir con seguridad en el objeto <classname>Glib::Dispatcher</classname> sin bloquear una vez que el hilo receptor ha conectado los «slot», siempre que se construya antes de que el hilo de trabajo arranque (si se construye después, normalmente se requerirá sincronización adicional para asegurar la visibilidad).</para>
<para xml:lang="en">
Aside from the fact that connected slots always execute in the
receiver thread, <classname>Glib::Dispatcher</classname> objects are
similar to <classname>sigc::signal<void()></classname> objects.
They therefore cannot pass unbound arguments nor return a value. The
best way to pass unbound arguments is with a thread-safe
(asynchronous) queue. At the time of writing
<application>glibmm</application> does not have one, although most
people writing multi-threaded code will have one available to them
(they are relatively easy to write although there are subtleties in
combining thread safety with strong exception safety).
</para>
<para xml:lang="en">
A <classname>Glib::Dispatcher</classname> object can be emitted on by
the receiver thread as well as by a worker thread, although this
should be done within reasonable bounds. On unix-like systems
<classname>Glib::Dispatcher</classname> objects share a single common
pipe, which could in theory at least fill up on a very heavily loaded
system running a program with a very large number of
<classname>Dispatcher</classname> objects in use. Were the pipe to
fill up before the receiver thread's main loop has had an opportunity
to read from it to empty it, and the receiver thread attempt to emit
and so write to it when it is in that condition, the receiver thread
would block on the write, so deadlocking. Where the receiver thread is
to emit, a normal <classname>sigc::signal<void()></classname>
object could of course be used instead.
</para>
</section>
<section xml:id="sec-multithread-example">
<title>Ejemplo</title>
<para xml:lang="en">
This is an example program with two threads, one GUI thread, like in all
<application>gtkmm</application> programs, and one worker thread. The worker thread is created when you
press the <literal>Start work</literal> button. It is deleted when the work is
finished, when you press the <literal>Stop work</literal> button, or when you
press the <literal>Quit</literal> button.
</para>
<para xml:lang="en">
A <classname>Glib::Dispatcher</classname> is used for sending notifications
from the worker thread to the GUI thread. The <classname>ExampleWorker</classname>
class contains data which is accessed by both threads. This data is protected
by a <classname>std::mutex</classname>.
Only the GUI thread updates the GUI.
</para>
<para xml:lang="en">
Compiling and linking a multi-threaded program can require special compiler and
linker options. If you use the <application>g++</application> compiler, add the
<literal>-pthread</literal> option. Other compilers may require other options.
If you build with <application>meson</application>, it handles the multi-threading
complications for you, if you add <function>dependency('threads')</function>.
</para>
<figure xml:id="figure-multithread">
<title>Programa con múltiples hilos</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/multithread.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/multithread">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>examplewindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm.h>
#include "exampleworker.h"
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
// Called from the worker thread.
void notify();
private:
// Signal handlers.
void on_start_button_clicked();
void on_stop_button_clicked();
void on_quit_button_clicked();
void update_start_stop_buttons();
void update_widgets();
// Dispatcher handler.
void on_notification_from_worker_thread();
// Member data.
Gtk::Box m_VBox;
Gtk::Box m_ButtonBox;
Gtk::Button m_ButtonStart;
Gtk::Button m_ButtonStop;
Gtk::Button m_ButtonQuit;
Gtk::ProgressBar m_ProgressBar;
Gtk::ScrolledWindow m_ScrolledWindow;
Gtk::TextView m_TextView;
Glib::Dispatcher m_Dispatcher;
ExampleWorker m_Worker;
std::thread* m_WorkerThread;
};
#endif // GTKMM_EXAMPLEWINDOW_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>exampleworker.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEWORKER_H
#define GTKMM_EXAMPLEWORKER_H
#include <gtkmm.h>
#include <thread>
#include <mutex>
class ExampleWindow;
class ExampleWorker
{
public:
ExampleWorker();
// Thread function.
void do_work(ExampleWindow* caller);
void get_data(double* fraction_done, Glib::ustring* message) const;
void stop_work();
bool has_stopped() const;
private:
// Synchronizes access to member data.
mutable std::mutex m_Mutex;
// Data used by both GUI thread and worker thread.
bool m_shall_stop;
bool m_has_stopped;
double m_fraction_done;
Glib::ustring m_message;
};
#endif // GTKMM_EXAMPLEWORKER_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>examplewindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <iostream>
ExampleWindow::ExampleWindow() :
m_VBox(Gtk::Orientation::VERTICAL, 5),
m_ButtonBox(Gtk::Orientation::HORIZONTAL),
m_ButtonStart("Start work"),
m_ButtonStop("Stop work"),
m_ButtonQuit("_Quit", /* mnemonic= */ true),
m_ProgressBar(),
m_ScrolledWindow(),
m_TextView(),
m_Dispatcher(),
m_Worker(),
m_WorkerThread(nullptr)
{
set_title("Multi-threaded example");
set_default_size(300, 300);
m_VBox.set_margin(5);
set_child(m_VBox);
// Add the ProgressBar.
m_VBox.append(m_ProgressBar);
m_ProgressBar.set_text("Fraction done");
m_ProgressBar.set_show_text();
// Add the TextView, inside a ScrolledWindow.
m_ScrolledWindow.set_child(m_TextView);
// Only show the scrollbars when they are necessary.
m_ScrolledWindow.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
m_ScrolledWindow.set_expand();
m_VBox.append(m_ScrolledWindow);
m_TextView.set_editable(false);
// Add the buttons to the ButtonBox.
m_VBox.append(m_ButtonBox);
m_ButtonBox.append(m_ButtonStart);
m_ButtonBox.append(m_ButtonStop);
m_ButtonBox.append(m_ButtonQuit);
m_ButtonBox.set_margin(5);
m_ButtonBox.set_spacing(5);
m_ButtonStart.set_hexpand(true);
m_ButtonStart.set_halign(Gtk::Align::END);
// Connect the signal handlers to the buttons.
m_ButtonStart.signal_clicked().connect(sigc::mem_fun(*this, &ExampleWindow::on_start_button_clicked));
m_ButtonStop.signal_clicked().connect(sigc::mem_fun(*this, &ExampleWindow::on_stop_button_clicked));
m_ButtonQuit.signal_clicked().connect(sigc::mem_fun(*this, &ExampleWindow::on_quit_button_clicked));
// Connect the handler to the dispatcher.
m_Dispatcher.connect(sigc::mem_fun(*this, &ExampleWindow::on_notification_from_worker_thread));
// Create a text buffer mark for use in update_widgets().
auto buffer = m_TextView.get_buffer();
buffer->create_mark("last_line", buffer->end(), /* left_gravity= */ true);
update_start_stop_buttons();
}
void ExampleWindow::on_start_button_clicked()
{
if (m_WorkerThread)
{
std::cout << "Can't start a worker thread while another one is running." << std::endl;
}
else
{
// Start a new worker thread.
m_WorkerThread = new std::thread(
[this]
{
m_Worker.do_work(this);
});
}
update_start_stop_buttons();
}
void ExampleWindow::on_stop_button_clicked()
{
if (!m_WorkerThread)
{
std::cout << "Can't stop a worker thread. None is running." << std::endl;
}
else
{
// Order the worker thread to stop.
m_Worker.stop_work();
m_ButtonStop.set_sensitive(false);
}
}
void ExampleWindow::update_start_stop_buttons()
{
const bool thread_is_running = m_WorkerThread != nullptr;
m_ButtonStart.set_sensitive(!thread_is_running);
m_ButtonStop.set_sensitive(thread_is_running);
}
void ExampleWindow::update_widgets()
{
double fraction_done;
Glib::ustring message_from_worker_thread;
m_Worker.get_data(&fraction_done, &message_from_worker_thread);
m_ProgressBar.set_fraction(fraction_done);
if (message_from_worker_thread != m_TextView.get_buffer()->get_text())
{
auto buffer = m_TextView.get_buffer();
buffer->set_text(message_from_worker_thread);
// Scroll the last inserted line into view. That's somewhat complicated.
auto iter = buffer->end();
iter.set_line_offset(0); // Beginning of last line
auto mark = buffer->get_mark("last_line");
buffer->move_mark(mark, iter);
m_TextView.scroll_to(mark);
// TextView::scroll_to(iter) is not perfect.
// We do need a TextMark to always get the last line into view.
}
}
void ExampleWindow::on_quit_button_clicked()
{
if (m_WorkerThread)
{
// Order the worker thread to stop and wait for it to stop.
m_Worker.stop_work();
if (m_WorkerThread->joinable())
m_WorkerThread->join();
}
set_visible(false);
}
// notify() is called from ExampleWorker::do_work(). It is executed in the worker
// thread. It triggers a call to on_notification_from_worker_thread(), which is
// executed in the GUI thread.
void ExampleWindow::notify()
{
m_Dispatcher.emit();
}
void ExampleWindow::on_notification_from_worker_thread()
{
if (m_WorkerThread && m_Worker.has_stopped())
{
// Work is done.
if (m_WorkerThread->joinable())
m_WorkerThread->join();
delete m_WorkerThread;
m_WorkerThread = nullptr;
update_start_stop_buttons();
}
update_widgets();
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>exampleworker.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "exampleworker.h"
#include "examplewindow.h"
#include <sstream>
#include <chrono>
ExampleWorker::ExampleWorker() :
m_Mutex(),
m_shall_stop(false),
m_has_stopped(false),
m_fraction_done(0.0),
m_message()
{
}
// Accesses to these data are synchronized by a mutex.
// Some microseconds can be saved by getting all data at once, instead of having
// separate get_fraction_done() and get_message() methods.
void ExampleWorker::get_data(double* fraction_done, Glib::ustring* message) const
{
std::lock_guard<std::mutex> lock(m_Mutex);
if (fraction_done)
*fraction_done = m_fraction_done;
if (message)
*message = m_message;
}
void ExampleWorker::stop_work()
{
std::lock_guard<std::mutex> lock(m_Mutex);
m_shall_stop = true;
}
bool ExampleWorker::has_stopped() const
{
std::lock_guard<std::mutex> lock(m_Mutex);
return m_has_stopped;
}
void ExampleWorker::do_work(ExampleWindow* caller)
{
{
std::lock_guard<std::mutex> lock(m_Mutex);
m_has_stopped = false;
m_fraction_done = 0.0;
m_message = "";
} // The mutex is unlocked here by lock's destructor.
// Simulate a long calculation.
for (int i = 0; ; ++i) // do until break
{
std::this_thread::sleep_for(std::chrono::milliseconds(250));
{
std::lock_guard<std::mutex> lock(m_Mutex);
m_fraction_done += 0.01;
if (i % 4 == 3)
{
std::ostringstream ostr;
ostr << (m_fraction_done * 100.0) << "% done\n";
m_message += ostr.str();
}
if (m_fraction_done >= 1.0)
{
m_message += "Finished";
break;
}
if (m_shall_stop)
{
m_message += "Stopped";
break;
}
}
caller->notify();
}
{
std::lock_guard<std::mutex> lock(m_Mutex);
m_shall_stop = false;
m_has_stopped = true;
}
caller->notify();
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "examplewindow.h"
#include <gtkmm/application.h>
int main(int argc, char* argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
//Shows the window and returns when it is closed.
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</chapter>
<chapter xml:id="chapter-recommended-techniques">
<title>Técnicas recomendadas</title>
<para>Esta sección es simplemente un acopio de sabiduría, pautas generales de estilo y consejos para crear aplicaciones en <application>gtkmm</application>.</para>
<para xml:lang="en">Use <application>Meson</application>! It is your friend :)
It examines C and C++ files, determines how they depend on each other,
and generates <filename>build.ninja</filename> or an equivalent file so the
files can be compiled in the correct order. <application>Meson</application>
permits automatic configuration of software installation, handling a large
number of system quirks to increase portability.
</para>
<para>Herede los widgets para organizar mejor su código. Probablemente deba heredar al menos su <classname>Window</classname> principal. Entonces podrá hacer widgets hijos y gestores de señales miembros de esa clase.</para>
<para xml:lang="en">Create your own signals instead of passing pointers around. Objects can
communicate with each other via signals and signal handlers. This is much
simpler than objects holding pointers to each other and calling each
other's methods. <application>gtkmm</application>'s classes use special versions of
<classname>sigc::signal</classname>, but you should use normal
<classname>sigc::signal</classname>s, as described in the
<application>libsigc++</application> documentation.</para>
<section xml:id="sec-application-lifetime">
<title>Tiempo de vida de la aplicación</title>
<para xml:lang="en">Most applications will have only one <classname>Window</classname>, or
only one main window. These applications can use
<methodname>Gtk::Application::make_window_and_run(int argc, char** argv, T_Args&&... args)</methodname>.
It creates and shows a window. When the window is hidden, <methodname>make_window_and_run()</methodname>
deletes the window and returns to the caller.
This might happen when the user closes the window, or when your code decides to
hide the window with <methodname>set_visible(false)</methodname>. You can prevent the user from
closing the window (for instance, if there are unsaved changes) by
overriding <methodname>Gtk::Window::on_close_request()</methodname>.</para>
<para>La mayoría de los ejemplos usan esta técnica.</para>
</section>
<section xml:id="sec-using-a-gtkmm-widget">
<title>Usar un widget de <application>gtkmm</application></title>
<para>Todos los ejemplos tienden a tener la misma estructura. Siguen estos pasos para usar un <classname>widget</classname>:</para>
<para>
<orderedlist inheritnum="ignore" continuation="restarts">
<listitem>
<para xml:lang="en">
Declare a variable of the type of <classname>Widget</classname> you wish to
use, generally as member variable of a derived container class. You could also
declare a pointer to the widget type, and then create it with
<literal>new</literal> or <function>Gtk::make_managed()</function> in your code.
</para>
</listitem>
<listitem>
<para xml:lang="en">
Set the attributes of the widget. If the widget has no default constructor,
then you will need to initialize the widget in the initializer list of your
container class's constructor.
</para>
</listitem>
<listitem>
<para>Conecte las señales que quiere usar a los gestores apropiados.</para>
</listitem>
<listitem>
<para xml:lang="en">
Pack the widget into a container using the appropriate call,
e.g. <methodname>Gtk::Box::append()</methodname>.
</para>
</listitem>
</orderedlist>
</para>
<para xml:lang="en">
If you don't want all widgets to be shown, call <methodname>Gtk::Widget::set_visible(false)</methodname>
on the widgets that you don't want to show. If a container widget is hidden, all
of its child widgets are also hidden, even if <methodname>set_visible(false)</methodname> is
not called on the child widgets.
</para>
</section>
</chapter>
<chapter xml:id="chapter-building-applications">
<title xml:lang="en">Building applications</title>
<para xml:lang="en">
This chapter is similar to <emphasis>Building applications</emphasis> and following sections in the
<link xlink:href="https://docs.gtk.org/gtk4/getting_started.html">Getting Started</link>
chapter in the GTK documentation.
The same application is built, but <application>gtkmm</application> is used instead of <application>GTK</application>.
</para>
<para xml:lang="en">
An application consists of a number of files:
<variablelist>
<varlistentry>
<term xml:lang="en">The binary file</term>
<listitem><para xml:lang="en">This gets installed in <filename>/usr/bin</filename>.</para></listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en">A desktop file</term>
<listitem><para xml:lang="en">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>.</para></listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en">An icon</term>
<listitem><para xml:lang="en">The icon gets installed in <filename>/usr/share/icons/hicolor/48x48/apps</filename>,
where it will be found regardless of the current theme.</para></listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en">A settings schema</term>
<listitem><para xml:lang="en">If the application uses <classname>Gio::Settings</classname>,
it will install its schema in <filename>/usr/share/glib-2.0/schemas</filename>,
so that tools like dconf-editor can find it.</para></listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en">Other resources</term>
<listitem><para xml:lang="en">Other files, such as <classname>Gtk::Builder</classname> 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>.</para></listitem>
</varlistentry>
</variablelist>
</para>
<para xml:lang="en">
<application>gtkmm</application> includes application support that is built on top of <classname>Gio::Application</classname>.
In this chapter we'll build a simple application by starting from scratch, adding more
and more pieces over time. Along the way, we'll learn about <classname>Gtk::Application</classname>,
<classname>Gtk::Builder</classname>, resources, menus, settings,
<classname>Gtk::HeaderBar</classname>, <classname>Gtk::Stack</classname>,
<classname>Gtk::SearchBar</classname>, <classname>Gtk::ListBox</classname>, and more.
</para>
<para xml:lang="en">
The full, buildable sources for these examples can be found in the
<filename>examples/book/buildapp</filename> directory of the
<application>gtkmm-documentation</application> source distribution, or online in the
<link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/buildapp"><application>gtkmm-documentation</application>
git repository</link>. You can build each example separately by using <command>meson</command>
and <command>ninja</command> with the <filename>meson.build</filename> file or by using
<command>make</command> with the <filename>Makefile.example</filename> file. For more
information, see the <filename>README</filename> included in the <filename>buildapp</filename>
directory.
</para>
<section xml:id="sec-buildapp-trivial-app">
<title xml:lang="en">A trivial application</title>
<para xml:lang="en">
When using <classname>Gtk::Application</classname>, the <function>main()</function> function
can be very simple. We just call <methodname>Gtk::Application::run()</methodname> on an
instance of our application class.
</para>
<para xml:lang="en">
All the application logic is in the application class, which is a subclass of
<classname>Gtk::Application</classname>. 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. (Or rather, our
application class tries to open the files, but our subclassed application window
does not yet do what it's told to do.)
</para>
<para xml:lang="en">
To handle these two cases, we override <methodname>signal_activate()</methodname>'s
default handler, which gets called when the application is launched without commandline
arguments, and <methodname>signal_open()</methodname>'s default handler, which gets
called when the application is launched with commandline arguments.
</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/glibmm/classGio_1_1Application.html">Gio::Application Reference</link></para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/gtkmm/classGtk_1_1Application.html">Gtk::Application Reference</link></para>
<para xml:lang="en">
Another important class that is part of the application support in <application>gtkmm</application> is
<classname>Gtk::ApplicationWindow</classname>. It is typically subclassed as well.
Our subclass does not do anything yet, so we will just get an empty window.
</para>
<para xml:lang="en">
As part of the initial setup of our application, we also create an icon and a desktop file.
Note that @bindir@ in the desktop file needs to be replaced with the actual path
to the binary before this desktop file can be used.
</para>
<para xml:lang="en">
Here is what we've achieved so far:
</para>
<figure xml:id="figure-buildapp-trivial-app">
<title xml:lang="en">A trivial application</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/buildapp_trivial_app.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en">
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>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/buildapp/step1">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>exampleapplication.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEAPPLICATION_H
#define GTKMM_EXAMPLEAPPLICATION_H
#include <gtkmm.h>
class ExampleAppWindow;
class ExampleApplication: public Gtk::Application
{
protected:
ExampleApplication();
public:
static Glib::RefPtr<ExampleApplication> create();
protected:
// Override default signal handlers:
void on_activate() override;
void on_open(const Gio::Application::type_vec_files& files,
const Glib::ustring& hint) override;
private:
ExampleAppWindow* create_appwindow();
};
#endif /* GTKMM_EXAMPLEAPPLICATION_H */
]]></code></programlisting>
<para xml:lang="en">File: <filename>exampleappwindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEAPPWINDOW_H_
#define GTKMM_EXAMPLEAPPWINDOW_H_
#include <gtkmm.h>
class ExampleAppWindow : public Gtk::ApplicationWindow
{
public:
ExampleAppWindow();
void open_file_view(const Glib::RefPtr<Gio::File>& file);
};
#endif /* GTKMM_EXAMPLEAPPWINDOW_H */
]]></code></programlisting>
<para xml:lang="en">File: <filename>exampleapplication.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "exampleapplication.h"
#include "exampleappwindow.h"
ExampleApplication::ExampleApplication()
: Gtk::Application("org.gtkmm.examples.application", Gio::Application::Flags::HANDLES_OPEN)
{
}
Glib::RefPtr<ExampleApplication> ExampleApplication::create()
{
return Glib::make_refptr_for_instance<ExampleApplication>(new ExampleApplication());
}
ExampleAppWindow* ExampleApplication::create_appwindow()
{
auto appwindow = new ExampleAppWindow();
// Make sure that the application runs for as long this window is still open.
add_window(*appwindow);
// A window can be added to an application with Gtk::Application::add_window()
// or Gtk::Window::set_application(). When all added windows have been hidden
// or removed, the application stops running (Gtk::Application::run() returns()),
// unless Gio::Application::hold() has been called.
// Delete the window when it is hidden.
appwindow->signal_hide().connect([appwindow](){ delete appwindow; });
return appwindow;
}
void ExampleApplication::on_activate()
{
// The application has been started, so let's show a window.
auto appwindow = create_appwindow();
appwindow->present();
}
void ExampleApplication::on_open(const Gio::Application::type_vec_files& files,
const Glib::ustring& /* hint */)
{
// The application has been asked to open some files,
// so let's open a new view for each one.
ExampleAppWindow* appwindow = nullptr;
auto windows = get_windows();
if (windows.size() > 0)
appwindow = dynamic_cast<ExampleAppWindow*>(windows[0]);
if (!appwindow)
appwindow = create_appwindow();
for (const auto& file : files)
appwindow->open_file_view(file);
appwindow->present();
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>exampleappwindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "exampleappwindow.h"
ExampleAppWindow::ExampleAppWindow()
: Gtk::ApplicationWindow()
{
}
void ExampleAppWindow::open_file_view(const Glib::RefPtr<Gio::File>& /* file */)
{
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "exampleapplication.h"
int main(int argc, char* argv[])
{
auto application = ExampleApplication::create();
// Start the application, showing the initial window,
// and opening extra views for any files that it is asked to open,
// for instance as a command-line parameter.
// run() will return when the last window has been closed.
return application->run(argc, argv);
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>exampleapp.desktop.in</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[[Desktop Entry]
Type=Application
Name=Gtkmm example
GenericName=Example
Comment=From the "Programming with gtkmm 4" tutorial
Icon=exampleapp
StartupNotify=true
Exec=@bindir@/exampleapp %U
Categories=GNOME;GTK;Utility
]]></code></programlisting>
<!-- end inserted example code -->
</section>
<section xml:id="sec-buildapp-populating-window">
<title xml:lang="en">Populating the window</title>
<para xml:lang="en">
In this step, we use a <classname>Gtk::Builder</classname> instance to associate a
<classname>Gtk::Builder</classname> ui file with our application window class.
</para>
<para xml:lang="en">
Our simple ui file gives the window a title, and puts a <classname>Gtk::Stack</classname>
widget as the main content.
</para>
<para xml:lang="en">
To make use of this file in our application, we revisit our
<classname>Gtk::ApplicationWindow</classname> subclass, and call
<methodname>Gtk::Builder::create_from_resource()</methodname> and
<methodname>Gtk::Builder::get_widget_derived()</methodname> from the
<methodname>ExampleAppWindow::create()</methodname> method to get an instance of
our subclassed <classname>Gtk::ApplicationWindow</classname>. See the
<link linkend="sec-builder-using-derived-widgets">Using derived widgets</link> section
for more information about <methodname>get_widget_derived()</methodname>.
</para>
<para xml:lang="en">
You may have noticed that we use the <methodname>_from_resource()</methodname> variant
of the method that reads the ui file. Now we need to use <application>GLib</application>'s
resource functionality to include the ui file in the binary. This is commonly done by
listing all resources in a .gresource.xml file.
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
<application>glib-compile-resources</application> utility:
<screen xml:lang="en">$ glib-compile-resources --target=resources.c --generate-source exampleapp.gresource.xml</screen>
The <link linkend="sec-gio-resource">Gio::Resource and glib-compile-resources</link>
section contains more information about resource files. If you build with Meson,
use the <function>compile_resources()</function> function in Meson's
<link xlink:href="https://mesonbuild.com/Gnome-module.html">GNOME module</link>.
</para>
<para xml:lang="en">
Our application now looks like this:
</para>
<figure xml:id="figure-buildapp-populating-window">
<title xml:lang="en">Populating the window</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/buildapp_populating_window.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/buildapp/step2">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>exampleapplication.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "../step1/exampleapplication.h"
// Equal to the corresponding file in step1
]]></code></programlisting>
<para xml:lang="en">File: <filename>exampleappwindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEAPPWINDOW_H_
#define GTKMM_EXAMPLEAPPWINDOW_H_
#include <gtkmm.h>
class ExampleAppWindow : public Gtk::ApplicationWindow
{
public:
ExampleAppWindow(BaseObjectType* cobject,
const Glib::RefPtr<Gtk::Builder>& refBuilder);
static ExampleAppWindow* create();
void open_file_view(const Glib::RefPtr<Gio::File>& file);
protected:
Glib::RefPtr<Gtk::Builder> m_refBuilder;
};
#endif /* GTKMM_EXAMPLEAPPWINDOW_H */
]]></code></programlisting>
<para xml:lang="en">File: <filename>exampleapplication.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "exampleapplication.h"
#include "exampleappwindow.h"
#include <iostream>
#include <exception>
ExampleApplication::ExampleApplication()
: Gtk::Application("org.gtkmm.examples.application", Gio::Application::Flags::HANDLES_OPEN)
{
}
Glib::RefPtr<ExampleApplication> ExampleApplication::create()
{
return Glib::make_refptr_for_instance<ExampleApplication>(new ExampleApplication());
}
ExampleAppWindow* ExampleApplication::create_appwindow()
{
auto appwindow = ExampleAppWindow::create();
// Make sure that the application runs for as long this window is still open.
add_window(*appwindow);
// A window can be added to an application with Gtk::Application::add_window()
// or Gtk::Window::set_application(). When all added windows have been hidden
// or removed, the application stops running (Gtk::Application::run() returns()),
// unless Gio::Application::hold() has been called.
// Delete the window when it is hidden.
appwindow->signal_hide().connect([appwindow](){ delete appwindow; });
return appwindow;
}
void ExampleApplication::on_activate()
{
try
{
// The application has been started, so let's show a window.
auto appwindow = create_appwindow();
appwindow->present();
}
// If create_appwindow() throws an exception (perhaps from Gtk::Builder),
// no window has been created, no window has been added to the application,
// and therefore the application will stop running.
catch (const Glib::Error& ex)
{
std::cerr << "ExampleApplication::on_activate(): " << ex.what() << std::endl;
}
catch (const std::exception& ex)
{
std::cerr << "ExampleApplication::on_activate(): " << ex.what() << std::endl;
}
}
void ExampleApplication::on_open(const Gio::Application::type_vec_files& files,
const Glib::ustring& /* hint */)
{
// The application has been asked to open some files,
// so let's open a new view for each one.
ExampleAppWindow* appwindow = nullptr;
auto windows = get_windows();
if (windows.size() > 0)
appwindow = dynamic_cast<ExampleAppWindow*>(windows[0]);
try
{
if (!appwindow)
appwindow = create_appwindow();
for (const auto& file : files)
appwindow->open_file_view(file);
appwindow->present();
}
catch (const Glib::Error& ex)
{
std::cerr << "ExampleApplication::on_open(): " << ex.what() << std::endl;
}
catch (const std::exception& ex)
{
std::cerr << "ExampleApplication::on_open(): " << ex.what() << std::endl;
}
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>exampleappwindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "exampleappwindow.h"
#include <stdexcept>
ExampleAppWindow::ExampleAppWindow(BaseObjectType* cobject,
const Glib::RefPtr<Gtk::Builder>& refBuilder)
: Gtk::ApplicationWindow(cobject),
m_refBuilder(refBuilder)
{
}
//static
ExampleAppWindow* ExampleAppWindow::create()
{
// Load the Builder file and instantiate its widgets.
auto refBuilder = Gtk::Builder::create_from_resource("/org/gtkmm/exampleapp/window.ui");
auto window = Gtk::Builder::get_widget_derived<ExampleAppWindow>(refBuilder, "app_window");
if (!window)
throw std::runtime_error("No \"app_window\" object in window.ui");
return window;
}
void ExampleAppWindow::open_file_view(const Glib::RefPtr<Gio::File>& /* file */)
{
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "../step1/main.cc"
// Equal to the corresponding file in step1
]]></code></programlisting>
<para xml:lang="en">File: <filename>exampleapp.gresource.xml</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/org/gtkmm/exampleapp">
<file preprocess="xml-stripblanks">window.ui</file>
</gresource>
</gresources>
]]></code></programlisting>
<para xml:lang="en">File: <filename>window.ui</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkApplicationWindow" id="app_window">
<property name="title" translatable="yes">Example Application</property>
<property name="default-width">600</property>
<property name="default-height">400</property>
<property name="hide-on-close">True</property>
<child>
<object class="GtkBox" id="content_box">
<property name="orientation">vertical</property>
<child>
<object class="GtkStack" id="stack"/>
</child>
</object>
</child>
</object>
</interface>
]]></code></programlisting>
<!-- end inserted example code -->
</section>
<section xml:id="sec-buildapp-opening-files">
<title xml:lang="en">Opening files</title>
<para xml:lang="en">
In this step, we make our application show the contents of all the files that it is
given on the commandline.
</para>
<para xml:lang="en">
To this end, we add a data member to our application window and keep a pointer to the
<classname>Gtk::Stack</classname> there. We get the pointer with a call to
<methodname>Gtk::Builder::get_widget()</methodname> in the application window's constructor.
</para>
<para xml:lang="en">
Now we revisit the <methodname>ExampleAppWindow::open_file_view()</methodname> method
that is called for each commandline argument, and construct a <classname>Gtk::TextView</classname>
that we then add as a page to the stack.
</para>
<para xml:lang="en">
Lastly, we add a <classname>Gtk::StackSwitcher</classname> to the titlebar area
in the ui file, and we tell it to display information about our stack.
</para>
<para xml:lang="en">
The stack switcher gets all its information it needs to display tabs from
the stack that it belongs to. Here, we are passing the label to show for
each file as the last argument to the <methodname>Gtk::Stack::add()</methodname>
method.
</para>
<para xml:lang="en">
Our application is beginning to take shape:
</para>
<figure xml:id="figure-buildapp-opening-files">
<title xml:lang="en">Opening files</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/buildapp_opening_files.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/buildapp/step3">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>exampleapplication.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "../step1/exampleapplication.h"
// Equal to the corresponding file in step1
]]></code></programlisting>
<para xml:lang="en">File: <filename>exampleappwindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEAPPWINDOW_H_
#define GTKMM_EXAMPLEAPPWINDOW_H_
#include <gtkmm.h>
class ExampleAppWindow : public Gtk::ApplicationWindow
{
public:
ExampleAppWindow(BaseObjectType* cobject,
const Glib::RefPtr<Gtk::Builder>& refBuilder);
static ExampleAppWindow* create();
void open_file_view(const Glib::RefPtr<Gio::File>& file);
protected:
Glib::RefPtr<Gtk::Builder> m_refBuilder;
Gtk::Stack* m_stack {nullptr};
};
#endif /* GTKMM_EXAMPLEAPPWINDOW_H */
]]></code></programlisting>
<para xml:lang="en">File: <filename>exampleapplication.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "../step2/exampleapplication.cc"
// Equal to the corresponding file in step2
]]></code></programlisting>
<para xml:lang="en">File: <filename>exampleappwindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "exampleappwindow.h"
#include <iostream>
#include <stdexcept>
ExampleAppWindow::ExampleAppWindow(BaseObjectType* cobject,
const Glib::RefPtr<Gtk::Builder>& refBuilder)
: Gtk::ApplicationWindow(cobject),
m_refBuilder(refBuilder)
{
m_stack = m_refBuilder->get_widget<Gtk::Stack>("stack");
if (!m_stack)
throw std::runtime_error("No \"stack\" object in window.ui");
}
//static
ExampleAppWindow* ExampleAppWindow::create()
{
// Load the Builder file and instantiate its widgets.
auto refBuilder = Gtk::Builder::create_from_resource("/org/gtkmm/exampleapp/window.ui");
auto window = Gtk::Builder::get_widget_derived<ExampleAppWindow>(refBuilder, "app_window");
if (!window)
throw std::runtime_error("No \"app_window\" object in window.ui");
return window;
}
void ExampleAppWindow::open_file_view(const Glib::RefPtr<Gio::File>& file)
{
const Glib::ustring basename = file->get_basename();
auto scrolled = Gtk::make_managed<Gtk::ScrolledWindow>();
scrolled->set_expand(true);
auto view = Gtk::make_managed<Gtk::TextView>();
view->set_editable(false);
view->set_cursor_visible(false);
scrolled->set_child(*view);
m_stack->add(*scrolled, basename, basename);
try
{
char* contents = nullptr;
gsize length = 0;
file->load_contents(contents, length);
view->get_buffer()->set_text(contents, contents+length);
g_free(contents);
}
catch (const Glib::Error& ex)
{
std::cout << "ExampleAppWindow::open_file_view(\"" << file->get_parse_name()
<< "\"):\n " << ex.what() << std::endl;
}
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "../step1/main.cc"
// Equal to the corresponding file in step1
]]></code></programlisting>
<para xml:lang="en">File: <filename>window.ui</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkApplicationWindow" id="app_window">
<property name="title" translatable="yes">Example Application</property>
<property name="default-width">600</property>
<property name="default-height">400</property>
<property name="hide-on-close">True</property>
<child type="titlebar">
<object class="GtkHeaderBar" id="header">
<child type="title">
<object class="GtkStackSwitcher" id="tabs">
<property name="stack">stack</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkBox" id="content_box">
<property name="orientation">vertical</property>
<child>
<object class="GtkStack" id="stack"/>
</child>
</object>
</child>
</object>
</interface>
]]></code></programlisting>
<!-- end inserted example code -->
</section>
<section xml:id="sec-buildapp-menu">
<title xml:lang="en">A menu</title>
<para xml:lang="en">
The menu is shown at the right side of the headerbar. It is meant to collect
infrequently used actions that affect the whole application.
</para>
<para xml:lang="en">
Just like the application window, we specify our menu in a ui file, and add it
as a resource to our binary.
</para>
<para xml:lang="en">
To make the menu appear, we have to load the ui file and associate the
resulting menu model with the menu button that we've added to the headerbar.
Since menus work by activating <classname>Gio::Action</classname>s, we also
have to add a suitable set of actions to our application.
</para>
<para xml:lang="en">
Adding the actions is best done in the <methodname>on_startup()</methodname> default
signal handler, which is guaranteed to be called once for each primary application instance.
</para>
<para xml:lang="en">
Our preferences menu item does not do anything yet, but the Quit menu item is fully
functional. It can also be activated by the usual Ctrl-Q shortcut. The shortcut
is added with <methodname>Gtk::Application::set_accel_for_action()</methodname>.
</para>
<para xml:lang="en">
The menu looks like this:
</para>
<figure xml:id="figure-buildapp-menu">
<title xml:lang="en">A menu</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/buildapp_menu.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/buildapp/step4">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>exampleapplication.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEAPPLICATION_H
#define GTKMM_EXAMPLEAPPLICATION_H
#include <gtkmm.h>
class ExampleAppWindow;
class ExampleApplication: public Gtk::Application
{
protected:
ExampleApplication();
public:
static Glib::RefPtr<ExampleApplication> create();
protected:
// Override default signal handlers:
void on_startup() override;
void on_activate() override;
void on_open(const Gio::Application::type_vec_files& files,
const Glib::ustring& hint) override;
private:
ExampleAppWindow* create_appwindow();
void on_action_preferences();
void on_action_quit();
};
#endif /* GTKMM_EXAMPLEAPPLICATION_H */
]]></code></programlisting>
<para xml:lang="en">File: <filename>exampleappwindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEAPPWINDOW_H_
#define GTKMM_EXAMPLEAPPWINDOW_H_
#include <gtkmm.h>
class ExampleAppWindow : public Gtk::ApplicationWindow
{
public:
ExampleAppWindow(BaseObjectType* cobject,
const Glib::RefPtr<Gtk::Builder>& refBuilder);
static ExampleAppWindow* create();
void open_file_view(const Glib::RefPtr<Gio::File>& file);
protected:
Glib::RefPtr<Gtk::Builder> m_refBuilder;
Gtk::Stack* m_stack {nullptr};
Gtk::MenuButton* m_gears {nullptr};
};
#endif /* GTKMM_EXAMPLEAPPWINDOW_H */
]]></code></programlisting>
<para xml:lang="en">File: <filename>exampleapplication.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "exampleapplication.h"
#include "exampleappwindow.h"
#include <iostream>
#include <exception>
ExampleApplication::ExampleApplication()
: Gtk::Application("org.gtkmm.examples.application", Gio::Application::Flags::HANDLES_OPEN)
{
}
Glib::RefPtr<ExampleApplication> ExampleApplication::create()
{
return Glib::make_refptr_for_instance<ExampleApplication>(new ExampleApplication());
}
ExampleAppWindow* ExampleApplication::create_appwindow()
{
auto appwindow = ExampleAppWindow::create();
// Make sure that the application runs for as long this window is still open.
add_window(*appwindow);
// A window can be added to an application with Gtk::Application::add_window()
// or Gtk::Window::set_application(). When all added windows have been hidden
// or removed, the application stops running (Gtk::Application::run() returns()),
// unless Gio::Application::hold() has been called.
// Delete the window when it is hidden.
appwindow->signal_hide().connect([appwindow](){ delete appwindow; });
return appwindow;
}
void ExampleApplication::on_startup()
{
// Call the base class's implementation.
Gtk::Application::on_startup();
// Add actions and keyboard accelerators for the menu.
add_action("preferences", sigc::mem_fun(*this, &ExampleApplication::on_action_preferences));
add_action("quit", sigc::mem_fun(*this, &ExampleApplication::on_action_quit));
set_accel_for_action("app.quit", "<Ctrl>Q");
}
void ExampleApplication::on_activate()
{
try
{
// The application has been started, so let's show a window.
auto appwindow = create_appwindow();
appwindow->present();
}
// If create_appwindow() throws an exception (perhaps from Gtk::Builder),
// no window has been created, no window has been added to the application,
// and therefore the application will stop running.
catch (const Glib::Error& ex)
{
std::cerr << "ExampleApplication::on_activate(): " << ex.what() << std::endl;
}
catch (const std::exception& ex)
{
std::cerr << "ExampleApplication::on_activate(): " << ex.what() << std::endl;
}
}
void ExampleApplication::on_open(const Gio::Application::type_vec_files& files,
const Glib::ustring& /* hint */)
{
// The application has been asked to open some files,
// so let's open a new view for each one.
ExampleAppWindow* appwindow = nullptr;
auto windows = get_windows();
if (windows.size() > 0)
appwindow = dynamic_cast<ExampleAppWindow*>(windows[0]);
try
{
if (!appwindow)
appwindow = create_appwindow();
for (const auto& file : files)
appwindow->open_file_view(file);
appwindow->present();
}
catch (const Glib::Error& ex)
{
std::cerr << "ExampleApplication::on_open(): " << ex.what() << std::endl;
}
catch (const std::exception& ex)
{
std::cerr << "ExampleApplication::on_open(): " << ex.what() << std::endl;
}
}
void ExampleApplication::on_action_preferences()
{
}
void ExampleApplication::on_action_quit()
{
// Gio::Application::quit() will make Gio::Application::run() return,
// but it's a crude way of ending the program. The window is not removed
// from the application. Neither the window's nor the application's
// destructors will be called, because there will be remaining reference
// counts in both of them. If we want the destructors to be called, we
// must remove the window from the application. One way of doing this
// is to hide the window. See comment in create_appwindow().
auto windows = get_windows();
for (auto window : windows)
window->set_visible(false);
// Not really necessary, when Gtk::Widget::set_visible(false) is called,
// unless Gio::Application::hold() has been called without a corresponding
// call to Gio::Application::release().
quit();
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>exampleappwindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "exampleappwindow.h"
#include <iostream>
#include <stdexcept>
ExampleAppWindow::ExampleAppWindow(BaseObjectType* cobject,
const Glib::RefPtr<Gtk::Builder>& refBuilder)
: Gtk::ApplicationWindow(cobject),
m_refBuilder(refBuilder)
{
// Get widgets from the Gtk::Builder file.
m_stack = m_refBuilder->get_widget<Gtk::Stack>("stack");
if (!m_stack)
throw std::runtime_error("No \"stack\" object in window.ui");
m_gears = m_refBuilder->get_widget<Gtk::MenuButton>("gears");
if (!m_gears)
throw std::runtime_error("No \"gears\" object in window.ui");
// Connect the menu to the MenuButton m_gears.
// (The connection between action and menu item is specified in gears_menu.ui.)
auto menu_builder = Gtk::Builder::create_from_resource("/org/gtkmm/exampleapp/gears_menu.ui");
auto menu = menu_builder->get_object<Gio::MenuModel>("menu");
if (!menu)
throw std::runtime_error("No \"menu\" object in gears_menu.ui");
m_gears->set_menu_model(menu);
}
//static
ExampleAppWindow* ExampleAppWindow::create()
{
// Load the Builder file and instantiate its widgets.
auto refBuilder = Gtk::Builder::create_from_resource("/org/gtkmm/exampleapp/window.ui");
auto window = Gtk::Builder::get_widget_derived<ExampleAppWindow>(refBuilder, "app_window");
if (!window)
throw std::runtime_error("No \"app_window\" object in window.ui");
return window;
}
void ExampleAppWindow::open_file_view(const Glib::RefPtr<Gio::File>& file)
{
const Glib::ustring basename = file->get_basename();
auto scrolled = Gtk::make_managed<Gtk::ScrolledWindow>();
scrolled->set_expand(true);
auto view = Gtk::make_managed<Gtk::TextView>();
view->set_editable(false);
view->set_cursor_visible(false);
scrolled->set_child(*view);
m_stack->add(*scrolled, basename, basename);
try
{
char* contents = nullptr;
gsize length = 0;
file->load_contents(contents, length);
view->get_buffer()->set_text(contents, contents+length);
g_free(contents);
}
catch (const Glib::Error& ex)
{
std::cout << "ExampleAppWindow::open_file_view(\"" << file->get_parse_name()
<< "\"):\n " << ex.what() << std::endl;
}
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "../step1/main.cc"
// Equal to the corresponding file in step1
]]></code></programlisting>
<para xml:lang="en">File: <filename>exampleapp.gresource.xml</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/org/gtkmm/exampleapp">
<file preprocess="xml-stripblanks">window.ui</file>
<file preprocess="xml-stripblanks">gears_menu.ui</file>
</gresource>
</gresources>
]]></code></programlisting>
<para xml:lang="en">File: <filename>gears_menu.ui</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[<?xml version="1.0" encoding="UTF-8"?>
<interface>
<menu id="menu">
<section>
<item>
<attribute name="label" translatable="yes">_Preferences</attribute>
<attribute name="action">app.preferences</attribute>
</item>
</section>
<section>
<item>
<attribute name="label" translatable="yes">_Quit</attribute>
<attribute name="action">app.quit</attribute>
</item>
</section>
</menu>
</interface>
]]></code></programlisting>
<para xml:lang="en">File: <filename>window.ui</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkApplicationWindow" id="app_window">
<property name="title" translatable="yes">Example Application</property>
<property name="default-width">600</property>
<property name="default-height">400</property>
<property name="hide-on-close">True</property>
<child type="titlebar">
<object class="GtkHeaderBar" id="header">
<child type="title">
<object class="GtkStackSwitcher" id="tabs">
<property name="stack">stack</property>
</object>
</child>
<child type="end">
<object class="GtkMenuButton" id="gears">
<property name="direction">none</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkBox" id="content_box">
<property name="orientation">vertical</property>
<child>
<object class="GtkStack" id="stack"/>
</child>
</object>
</child>
</object>
</interface>
]]></code></programlisting>
<!-- end inserted example code -->
</section>
<section xml:id="sec-buildapp-pref-dialog">
<title xml:lang="en">A preference dialog</title>
<para xml:lang="en">
A typical application will have 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 xml:lang="en">
We are going to use <classname>Gio::Settings</classname> to store our preferences.
<classname>Gio::Settings</classname> requires a schema that describes our settings,
in our case the <filename>org.gtkmm.exampleapp.gschema.xml</filename> file.
</para>
<para xml:lang="en">
Before we can make use of this schema in our application, we need to compile it into
the binary form that <classname>Gio::Settings</classname> expects. GIO provides macros
to do this in autotools-based projects. See the description of
<link xlink:href="https://docs.gtk.org/gio/class.Settings.html">GSettings</link>.
Meson provides the <function>compile_schemas()</function> function in the
<link xlink:href="https://mesonbuild.com/Gnome-module.html">GNOME module</link>.
</para>
<para xml:lang="en">
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 <methodname>Gio::Settings::bind()</methodname>
to bind settings keys to object properties, as we do for the transition setting in
<classname>ExampleAppWindow</classname>'s constructor.
</para>
<programlisting xml:lang="en"><code>m_settings = Gio::Settings::create("org.gtkmm.exampleapp");
m_settings->bind("transition", m_stack->property_transition_type());
</code></programlisting>
<para xml:lang="en">
The code to connect the font setting is a little more involved, since it corresponds to
an object property in a <classname>Gtk::TextTag</classname> that we must first create.
The code is in <methodname>ExampleAppWindow::open_file_view()</methodname>.
</para>
<programlisting xml:lang="en"><code>auto tag = buffer->create_tag();
m_settings->bind("font", tag->property_font());
buffer->apply_tag(tag, buffer->begin(), buffer->end());
</code></programlisting>
<para xml:lang="en">
At this point, the application will already react if you change one of the settings,
e.g. using the <command>gsettings</command> 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 <classname>Gtk::Window</classname>, and
we'll use the same techniques that we've already seen in <classname>ExampleAppWindow</classname>:
a <classname>Gtk::Builder</classname> ui file and settings bindings.
In this case the bindings are more involved, though. We use
<classname>Gtk::FontDialogButton</classname> and <classname>Gtk::DropDown</classname>
in the preference dialog. The types of the properties in these classes can't be
automatically converted to the string type that <classname>Gio::Settings</classname> requires.
</para>
<para xml:lang="en">
When we've created the <filename>prefs.ui</filename> file and the <classname>ExampleAppPrefs</classname>
class, we revisit the <methodname>ExampleApplication::on_action_preferences()</methodname>
method in our application class, and make it open a new preference dialog.
</para>
<programlisting xml:lang="en"><code>auto prefs_dialog = ExampleAppPrefs::create(*get_active_window());
prefs_dialog->present();
</code></programlisting>
<para xml:lang="en">
After all this work, our application can now show a preference dialog like this:
</para>
<figure xml:id="figure-buildapp-pref-dialog">
<title xml:lang="en">A preference dialog</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/buildapp_pref_dialog.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/buildapp/step5">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>exampleapplication.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "../step4/exampleapplication.h"
// Equal to the corresponding file in step4
]]></code></programlisting>
<para xml:lang="en">File: <filename>exampleappprefs.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEAPPPREFS_H_
#define GTKMM_EXAMPLEAPPPREFS_H_
#include <gtkmm.h>
#ifdef GLIBMM_CHECK_VERSION
#define HAS_GIO_SETTINGS_BIND_WITH_MAPPING GLIBMM_CHECK_VERSION(2,75,0)
#else
#define HAS_GIO_SETTINGS_BIND_WITH_MAPPING 0
#endif
class ExampleAppPrefs : public Gtk::Window
{
public:
ExampleAppPrefs(BaseObjectType* cobject,
const Glib::RefPtr<Gtk::Builder>& refBuilder);
static ExampleAppPrefs* create(Gtk::Window& parent);
protected:
#if HAS_GIO_SETTINGS_BIND_WITH_MAPPING
// Mappings from Gio::Settings to properties
static std::optional<unsigned int> map_from_ustring_to_int(const Glib::ustring& transition);
static std::optional<Glib::ustring> map_from_int_to_ustring(const unsigned int& pos);
#else
// Signal handlers
void on_font_setting_changed(const Glib::ustring& key);
void on_font_selection_changed();
void on_transition_setting_changed(const Glib::ustring& key);
void on_transition_selection_changed();
#endif
Glib::RefPtr<Gtk::Builder> m_refBuilder;
Glib::RefPtr<Gio::Settings> m_settings;
Gtk::FontDialogButton* m_font {nullptr};
Gtk::DropDown* m_transition {nullptr};
};
#endif /* GTKMM_EXAMPLEAPPPREFS_H_ */
]]></code></programlisting>
<para xml:lang="en">File: <filename>exampleappwindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEAPPWINDOW_H_
#define GTKMM_EXAMPLEAPPWINDOW_H_
#include <gtkmm.h>
class ExampleAppWindow : public Gtk::ApplicationWindow
{
public:
ExampleAppWindow(BaseObjectType* cobject,
const Glib::RefPtr<Gtk::Builder>& refBuilder);
static ExampleAppWindow* create();
void open_file_view(const Glib::RefPtr<Gio::File>& file);
protected:
Glib::RefPtr<Gtk::Builder> m_refBuilder;
Glib::RefPtr<Gio::Settings> m_settings;
Gtk::Stack* m_stack {nullptr};
Gtk::MenuButton* m_gears {nullptr};
};
#endif /* GTKMM_EXAMPLEAPPWINDOW_H */
]]></code></programlisting>
<para xml:lang="en">File: <filename>exampleapplication.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "exampleapplication.h"
#include "exampleappwindow.h"
#include "exampleappprefs.h"
#include <iostream>
#include <exception>
ExampleApplication::ExampleApplication()
: Gtk::Application("org.gtkmm.examples.application", Gio::Application::Flags::HANDLES_OPEN)
{
}
Glib::RefPtr<ExampleApplication> ExampleApplication::create()
{
return Glib::make_refptr_for_instance<ExampleApplication>(new ExampleApplication());
}
ExampleAppWindow* ExampleApplication::create_appwindow()
{
auto appwindow = ExampleAppWindow::create();
// Make sure that the application runs for as long this window is still open.
add_window(*appwindow);
// A window can be added to an application with Gtk::Application::add_window()
// or Gtk::Window::set_application(). When all added windows have been hidden
// or removed, the application stops running (Gtk::Application::run() returns()),
// unless Gio::Application::hold() has been called.
// Delete the window when it is hidden.
appwindow->signal_hide().connect([appwindow](){ delete appwindow; });
return appwindow;
}
void ExampleApplication::on_startup()
{
// Call the base class's implementation.
Gtk::Application::on_startup();
// Add actions and keyboard accelerators for the menu.
add_action("preferences", sigc::mem_fun(*this, &ExampleApplication::on_action_preferences));
add_action("quit", sigc::mem_fun(*this, &ExampleApplication::on_action_quit));
set_accel_for_action("app.quit", "<Ctrl>Q");
}
void ExampleApplication::on_activate()
{
try
{
// The application has been started, so let's show a window.
auto appwindow = create_appwindow();
appwindow->present();
}
// If create_appwindow() throws an exception (perhaps from Gtk::Builder),
// no window has been created, no window has been added to the application,
// and therefore the application will stop running.
catch (const Glib::Error& ex)
{
std::cerr << "ExampleApplication::on_activate(): " << ex.what() << std::endl;
}
catch (const std::exception& ex)
{
std::cerr << "ExampleApplication::on_activate(): " << ex.what() << std::endl;
}
}
void ExampleApplication::on_open(const Gio::Application::type_vec_files& files,
const Glib::ustring& /* hint */)
{
// The application has been asked to open some files,
// so let's open a new view for each one.
ExampleAppWindow* appwindow = nullptr;
auto windows = get_windows();
if (windows.size() > 0)
appwindow = dynamic_cast<ExampleAppWindow*>(windows[0]);
try
{
if (!appwindow)
appwindow = create_appwindow();
for (const auto& file : files)
appwindow->open_file_view(file);
appwindow->present();
}
catch (const Glib::Error& ex)
{
std::cerr << "ExampleApplication::on_open(): " << ex.what() << std::endl;
}
catch (const std::exception& ex)
{
std::cerr << "ExampleApplication::on_open(): " << ex.what() << std::endl;
}
}
void ExampleApplication::on_action_preferences()
{
try
{
auto prefs_dialog = ExampleAppPrefs::create(*get_active_window());
prefs_dialog->present();
// Delete the dialog when it is hidden.
prefs_dialog->signal_hide().connect([prefs_dialog](){ delete prefs_dialog; });
}
catch (const Glib::Error& ex)
{
std::cerr << "ExampleApplication::on_action_preferences(): " << ex.what() << std::endl;
}
catch (const std::exception& ex)
{
std::cerr << "ExampleApplication::on_action_preferences(): " << ex.what() << std::endl;
}
}
void ExampleApplication::on_action_quit()
{
// Gio::Application::quit() will make Gio::Application::run() return,
// but it's a crude way of ending the program. The window is not removed
// from the application. Neither the window's nor the application's
// destructors will be called, because there will be remaining reference
// counts in both of them. If we want the destructors to be called, we
// must remove the window from the application. One way of doing this
// is to hide the window. See comment in create_appwindow().
auto windows = get_windows();
for (auto window : windows)
window->set_visible(false);
// Not really necessary, when Gtk::Widget::set_visible(false) is called,
// unless Gio::Application::hold() has been called without a corresponding
// call to Gio::Application::release().
quit();
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>exampleappprefs.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code>< { return Pango::FontDescription(font); },
[](const auto& fontdesc) { return fontdesc.to_string(); }
);
m_settings->bind<Glib::ustring, unsigned int>("transition",
m_transition->property_selected(), Gio::Settings::BindFlags::DEFAULT,
[](const auto& transition) { return map_from_ustring_to_int(transition); },
[](const auto& pos) { return map_from_int_to_ustring(pos); }
);
#else
// This is easier when g_settings_bind_with_mapping() is
// wrapped in a Gio::Settings method.
m_settings->signal_changed("font").connect(
sigc::mem_fun(*this, &ExampleAppPrefs::on_font_setting_changed));
m_font->property_font_desc().signal_changed().connect(
sigc::mem_fun(*this, &ExampleAppPrefs::on_font_selection_changed));
m_settings->signal_changed("transition").connect(
sigc::mem_fun(*this, &ExampleAppPrefs::on_transition_setting_changed));
m_transition->property_selected().signal_changed().connect(
sigc::mem_fun(*this, &ExampleAppPrefs::on_transition_selection_changed));
// Synchronize the preferences dialog with m_settings.
on_font_setting_changed("font");
on_transition_setting_changed("transition");
#endif
}
//static
ExampleAppPrefs* ExampleAppPrefs::create(Gtk::Window& parent)
{
// Load the Builder file and instantiate its widgets.
auto refBuilder = Gtk::Builder::create_from_resource("/org/gtkmm/exampleapp/prefs.ui");
auto dialog = Gtk::Builder::get_widget_derived<ExampleAppPrefs>(refBuilder, "prefs_dialog");
if (!dialog)
throw std::runtime_error("No \"prefs_dialog\" object in prefs.ui");
dialog->set_transient_for(parent);
return dialog;
}
#if HAS_GIO_SETTINGS_BIND_WITH_MAPPING
std::optional<unsigned int> ExampleAppPrefs::map_from_ustring_to_int(const Glib::ustring& transition)
{
for (std::size_t i = 0; i < transitionTypes.size(); ++i)
{
if (transitionTypes[i].id == transition)
return i;
}
return std::nullopt;
}
std::optional<Glib::ustring> ExampleAppPrefs::map_from_int_to_ustring(const unsigned int& pos)
{
if (pos >= transitionTypes.size())
return std::nullopt;
return transitionTypes[pos].id;
}
#else
void ExampleAppPrefs::on_font_setting_changed(const Glib::ustring& /* key */)
{
const auto font_setting = m_settings->get_string("font");
const auto font_button = m_font->get_font_desc().to_string();
if (font_setting != font_button)
m_font->set_font_desc(Pango::FontDescription(font_setting));
}
void ExampleAppPrefs::on_font_selection_changed()
{
const auto font_setting = m_settings->get_string("font");
const auto font_button = m_font->get_font_desc().to_string();
if (font_setting != font_button)
m_settings->set_string("font", font_button);
}
void ExampleAppPrefs::on_transition_setting_changed(const Glib::ustring& /* key */)
{
const auto transition_setting = m_settings->get_string("transition");
const auto transition_button = transitionTypes[m_transition->get_selected()].id;
if (transition_setting != transition_button)
{
for (std::size_t i = 0; i < transitionTypes.size(); ++i)
{
if (transitionTypes[i].id == transition_setting)
{
m_transition->set_selected(i);
break;
}
}
}
}
void ExampleAppPrefs::on_transition_selection_changed()
{
const auto pos = m_transition->get_selected();
if (pos >= transitionTypes.size())
return;
const auto transition_setting = m_settings->get_string("transition");
const auto transition_button = transitionTypes[pos].id;
if (transition_setting != transition_button)
m_settings->set_string("transition", transition_button);
}
#endif
]]></code></programlisting>
<para xml:lang="en">File: <filename>exampleappwindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "exampleappwindow.h"
#include <iostream>
#include <stdexcept>
ExampleAppWindow::ExampleAppWindow(BaseObjectType* cobject,
const Glib::RefPtr<Gtk::Builder>& refBuilder)
: Gtk::ApplicationWindow(cobject),
m_refBuilder(refBuilder)
{
// Get widgets from the Gtk::Builder file.
m_stack = m_refBuilder->get_widget<Gtk::Stack>("stack");
if (!m_stack)
throw std::runtime_error("No \"stack\" object in window.ui");
m_gears = m_refBuilder->get_widget<Gtk::MenuButton>("gears");
if (!m_gears)
throw std::runtime_error("No \"gears\" object in window.ui");
// Bind settings.
m_settings = Gio::Settings::create("org.gtkmm.exampleapp");
m_settings->bind("transition", m_stack->property_transition_type());
// Connect the menu to the MenuButton m_gears.
// (The connection between action and menu item is specified in gears_menu.ui.)
auto menu_builder = Gtk::Builder::create_from_resource("/org/gtkmm/exampleapp/gears_menu.ui");
auto menu = menu_builder->get_object<Gio::MenuModel>("menu");
if (!menu)
throw std::runtime_error("No \"menu\" object in gears_menu.ui");
m_gears->set_menu_model(menu);
}
//static
ExampleAppWindow* ExampleAppWindow::create()
{
// Load the Builder file and instantiate its widgets.
auto refBuilder = Gtk::Builder::create_from_resource("/org/gtkmm/exampleapp/window.ui");
auto window = Gtk::Builder::get_widget_derived<ExampleAppWindow>(refBuilder, "app_window");
if (!window)
throw std::runtime_error("No \"app_window\" object in window.ui");
return window;
}
void ExampleAppWindow::open_file_view(const Glib::RefPtr<Gio::File>& file)
{
const Glib::ustring basename = file->get_basename();
auto scrolled = Gtk::make_managed<Gtk::ScrolledWindow>();
scrolled->set_expand(true);
auto view = Gtk::make_managed<Gtk::TextView>();
view->set_editable(false);
view->set_cursor_visible(false);
scrolled->set_child(*view);
m_stack->add(*scrolled, basename, basename);
auto buffer = view->get_buffer();
try
{
char* contents = nullptr;
gsize length = 0;
file->load_contents(contents, length);
buffer->set_text(contents, contents+length);
g_free(contents);
}
catch (const Glib::Error& ex)
{
std::cout << "ExampleAppWindow::open_file_view(\"" << file->get_parse_name()
<< "\"):\n " << ex.what() << std::endl;
}
auto tag = buffer->create_tag();
m_settings->bind("font", tag->property_font());
buffer->apply_tag(tag, buffer->begin(), buffer->end());
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "exampleapplication.h"
int main(int argc, char* argv[])
{
// Since this example is running uninstalled, we have to help it find its
// schema. This is *not* necessary in a properly installed application.
Glib::setenv ("GSETTINGS_SCHEMA_DIR", ".", false);
auto application = ExampleApplication::create();
// Start the application, showing the initial window,
// and opening extra views for any files that it is asked to open,
// for instance as a command-line parameter.
// run() will return when the last window has been closed.
return application->run(argc, argv);
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>exampleapp.gresource.xml</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/org/gtkmm/exampleapp">
<file preprocess="xml-stripblanks">window.ui</file>
<file preprocess="xml-stripblanks">gears_menu.ui</file>
<file preprocess="xml-stripblanks">prefs.ui</file>
</gresource>
</gresources>
]]></code></programlisting>
<para xml:lang="en">File: <filename>prefs.ui</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkWindow" id="prefs_dialog">
<property name="title" translatable="yes">Preferences</property>
<property name="resizable">False</property>
<property name="modal">True</property>
<property name="hide_on_close">True</property>
<child>
<object class="GtkGrid" id="grid">
<property name="margin-start">12</property>
<property name="margin-end">12</property>
<property name="margin-top">12</property>
<property name="margin-bottom">12</property>
<property name="row-spacing">12</property>
<property name="column-spacing">12</property>
<child>
<object class="GtkLabel" id="fontlabel">
<property name="label">_Font:</property>
<property name="use-underline">True</property>
<property name="mnemonic-widget">font</property>
<property name="xalign">1</property>
<layout>
<property name="column">0</property>
<property name="row">0</property>
</layout>
</object>
</child>
<child>
<object class="GtkFontDialogButton" id="font">
<property name="dialog">
<object class="GtkFontDialog"/>
</property>
<layout>
<property name="column">1</property>
<property name="row">0</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="transitionlabel">
<property name="label">_Transition:</property>
<property name="use-underline">True</property>
<property name="mnemonic-widget">transition</property>
<property name="xalign">1</property>
<layout>
<property name="column">0</property>
<property name="row">1</property>
</layout>
</object>
</child>
<child>
<object class="GtkDropDown" id="transition">
<layout>
<property name="column">1</property>
<property name="row">1</property>
</layout>
</object>
</child>
</object>
</child>
</object>
</interface>
]]></code></programlisting>
<para xml:lang="en">File: <filename>org.gtkmm.exampleapp.gschema.xml</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[<?xml version="1.0" encoding="UTF-8"?>
<schemalist>
<schema path="/org/gtkmm/exampleapp/" id="org.gtkmm.exampleapp">
<key name="font" type="s">
<default>'Monospace 12'</default>
<summary>Font</summary>
<description>The font to be used for content.</description>
</key>
<key name="transition" type="s">
<choices>
<choice value='none'/>
<choice value='crossfade'/>
<choice value='slide-left-right'/>
</choices>
<default>'none'</default>
<summary>Transition</summary>
<description>The transition to use when switching tabs.</description>
</key>
</schema>
</schemalist>
]]></code></programlisting>
<!-- end inserted example code -->
</section>
<section xml:id="sec-buildapp-search-bar">
<title xml:lang="en">Adding a search bar</title>
<para xml:lang="en">
We continue to flesh out the functionality of our application. For now, we add search.
<application>gtkmm</application> supports this with <classname>Gtk::SearchEntry</classname> and <classname>Gtk::SearchBar</classname>.
The search bar is a widget that can slide in from the top to present a search entry.
</para>
<para xml:lang="en">
We add a toggle button to the header bar, which can be used to slide out the search bar
below the header bar. The new widgets are added in the <filename>window.ui</filename> file.
</para>
<para xml:lang="en">
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, shown here without error handling.
</para>
<programlisting xml:lang="en"><code>void ExampleAppWindow::on_search_text_changed()
{
const auto text = m_searchentry->get_text();
auto tab = dynamic_cast<Gtk::ScrolledWindow*>(m_stack->get_visible_child());
auto view = dynamic_cast<Gtk::TextView*>(tab->get_child());
// Very simple-minded search implementation.
auto buffer = view->get_buffer();
Gtk::TextIter match_start;
Gtk::TextIter match_end;
if (buffer->begin().forward_search(text, Gtk::TextSearchFlags::CASE_INSENSITIVE,
match_start, match_end))
{
buffer->select_range(match_start, match_end);
view->scroll_to(match_start);
}
}
</code></programlisting>
<para xml:lang="en">
With the search bar, our application now looks like this:
</para>
<figure xml:id="figure-buildapp-search-bar">
<title xml:lang="en">Adding a search bar</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/buildapp_search_bar.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/buildapp/step6">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>exampleapplication.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "../step4/exampleapplication.h"
// Equal to the corresponding file in step4
]]></code></programlisting>
<para xml:lang="en">File: <filename>exampleappprefs.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "../step5/exampleappprefs.h"
// Equal to the corresponding file in step5
]]></code></programlisting>
<para xml:lang="en">File: <filename>exampleappwindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEAPPWINDOW_H_
#define GTKMM_EXAMPLEAPPWINDOW_H_
#include <gtkmm.h>
#define HAS_SEARCH_ENTRY2 GTKMM_CHECK_VERSION(4,13,2)
class ExampleAppWindow : public Gtk::ApplicationWindow
{
public:
ExampleAppWindow(BaseObjectType* cobject,
const Glib::RefPtr<Gtk::Builder>& refBuilder);
static ExampleAppWindow* create();
void open_file_view(const Glib::RefPtr<Gio::File>& file);
protected:
// Signal handlers
void on_search_text_changed();
void on_visible_child_changed();
Glib::RefPtr<Gtk::Builder> m_refBuilder;
Glib::RefPtr<Gio::Settings> m_settings;
Gtk::Stack* m_stack {nullptr};
Gtk::ToggleButton* m_search {nullptr};
Gtk::SearchBar* m_searchbar {nullptr};
#if HAS_SEARCH_ENTRY2
Gtk::SearchEntry2* m_searchentry {nullptr};
#else
Gtk::SearchEntry* m_searchentry {nullptr};
#endif
Gtk::MenuButton* m_gears {nullptr};
Glib::RefPtr<Glib::Binding> m_prop_binding;
};
#endif /* GTKMM_EXAMPLEAPPWINDOW_H */
]]></code></programlisting>
<para xml:lang="en">File: <filename>exampleapplication.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "../step5/exampleapplication.cc"
// Equal to the corresponding file in step5
]]></code></programlisting>
<para xml:lang="en">File: <filename>exampleappprefs.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "../step5/exampleappprefs.cc"
// Equal to the corresponding file in step5
]]></code></programlisting>
<para xml:lang="en">File: <filename>exampleappwindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "exampleappwindow.h"
#include <iostream>
#include <stdexcept>
ExampleAppWindow::ExampleAppWindow(BaseObjectType* cobject,
const Glib::RefPtr<Gtk::Builder>& refBuilder)
: Gtk::ApplicationWindow(cobject),
m_refBuilder(refBuilder)
{
// Get widgets from the Gtk::Builder file.
m_stack = m_refBuilder->get_widget<Gtk::Stack>("stack");
if (!m_stack)
throw std::runtime_error("No \"stack\" object in window.ui");
m_search = m_refBuilder->get_widget<Gtk::ToggleButton>("search");
if (!m_search)
throw std::runtime_error("No \"search\" object in window.ui");
m_searchbar = m_refBuilder->get_widget<Gtk::SearchBar>("searchbar");
if (!m_searchbar)
throw std::runtime_error("No \"searchbar\" object in window.ui");
#if HAS_SEARCH_ENTRY2
m_searchentry = m_refBuilder->get_widget<Gtk::SearchEntry2>("searchentry");
#else
m_searchentry = m_refBuilder->get_widget<Gtk::SearchEntry>("searchentry");
#endif
if (!m_searchentry)
throw std::runtime_error("No \"searchentry\" object in window.ui");
m_gears = m_refBuilder->get_widget<Gtk::MenuButton>("gears");
if (!m_gears)
throw std::runtime_error("No \"gears\" object in window.ui");
// Bind settings.
m_settings = Gio::Settings::create("org.gtkmm.exampleapp");
m_settings->bind("transition", m_stack->property_transition_type());
// Bind properties.
m_prop_binding = Glib::Binding::bind_property(m_search->property_active(),
m_searchbar->property_search_mode_enabled(), Glib::Binding::Flags::BIDIRECTIONAL);
// Connect signal handlers.
m_searchentry->signal_search_changed().connect(
sigc::mem_fun(*this, &ExampleAppWindow::on_search_text_changed));
m_stack->property_visible_child().signal_changed().connect(
sigc::mem_fun(*this, &ExampleAppWindow::on_visible_child_changed));
// Connect the menu to the MenuButton m_gears.
// (The connection between action and menu item is specified in gears_menu.ui.)
auto menu_builder = Gtk::Builder::create_from_resource("/org/gtkmm/exampleapp/gears_menu.ui");
auto menu = menu_builder->get_object<Gio::MenuModel>("menu");
if (!menu)
throw std::runtime_error("No \"menu\" object in gears_menu.ui");
m_gears->set_menu_model(menu);
}
//static
ExampleAppWindow* ExampleAppWindow::create()
{
// Load the Builder file and instantiate its widgets.
auto refBuilder = Gtk::Builder::create_from_resource("/org/gtkmm/exampleapp/window.ui");
auto window = Gtk::Builder::get_widget_derived<ExampleAppWindow>(refBuilder, "app_window");
if (!window)
throw std::runtime_error("No \"app_window\" object in window.ui");
return window;
}
void ExampleAppWindow::open_file_view(const Glib::RefPtr<Gio::File>& file)
{
const Glib::ustring basename = file->get_basename();
auto scrolled = Gtk::make_managed<Gtk::ScrolledWindow>();
scrolled->set_expand(true);
auto view = Gtk::make_managed<Gtk::TextView>();
view->set_editable(false);
view->set_cursor_visible(false);
scrolled->set_child(*view);
m_stack->add(*scrolled, basename, basename);
auto buffer = view->get_buffer();
try
{
char* contents = nullptr;
gsize length = 0;
file->load_contents(contents, length);
buffer->set_text(contents, contents+length);
g_free(contents);
}
catch (const Glib::Error& ex)
{
std::cout << "ExampleAppWindow::open_file_view(\"" << file->get_parse_name()
<< "\"):\n " << ex.what() << std::endl;
return;
}
auto tag = buffer->create_tag();
m_settings->bind("font", tag->property_font());
buffer->apply_tag(tag, buffer->begin(), buffer->end());
m_search->set_sensitive(true);
}
void ExampleAppWindow::on_search_text_changed()
{
const auto text = m_searchentry->get_text();
if (text.empty())
return;
auto tab = dynamic_cast<Gtk::ScrolledWindow*>(m_stack->get_visible_child());
if (!tab)
{
std::cout << "ExampleAppWindow::on_search_text_changed(): No visible child." << std::endl;
return;
}
auto view = dynamic_cast<Gtk::TextView*>(tab->get_child());
if (!view)
{
std::cout << "ExampleAppWindow::on_search_text_changed(): No visible text view." << std::endl;
return;
}
// Very simple-minded search implementation.
auto buffer = view->get_buffer();
Gtk::TextIter match_start;
Gtk::TextIter match_end;
if (buffer->begin().forward_search(text, Gtk::TextSearchFlags::CASE_INSENSITIVE,
match_start, match_end))
{
buffer->select_range(match_start, match_end);
view->scroll_to(match_start);
}
}
void ExampleAppWindow::on_visible_child_changed()
{
m_searchbar->set_search_mode(false);
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "exampleapplication.h"
#include "exampleappwindow.h"
int main(int argc, char* argv[])
{
#if HAS_SEARCH_ENTRY2
// A GtkSearchEntry (e.g. from window.ui) shall be wrapped in
// a Gtk::SearhcEntry2 and not in a deprecated Gtk::SearchEntry.
Gtk::Application::wrap_in_search_entry2(true);
#endif
// Since this example is running uninstalled, we have to help it find its
// schema. This is *not* necessary in a properly installed application.
Glib::setenv ("GSETTINGS_SCHEMA_DIR", ".", false);
auto application = ExampleApplication::create();
// Start the application, showing the initial window,
// and opening extra views for any files that it is asked to open,
// for instance as a command-line parameter.
// run() will return when the last window has been closed.
return application->run(argc, argv);
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>window.ui</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkApplicationWindow" id="app_window">
<property name="title" translatable="yes">Example Application</property>
<property name="default-width">600</property>
<property name="default-height">400</property>
<property name="hide-on-close">True</property>
<child type="titlebar">
<object class="GtkHeaderBar" id="header">
<child type="title">
<object class="GtkStackSwitcher" id="tabs">
<property name="stack">stack</property>
</object>
</child>
<child type="end">
<object class="GtkMenuButton" id="gears">
<property name="direction">none</property>
</object>
</child>
<child type="end">
<object class="GtkToggleButton" id="search">
<property name="sensitive">False</property>
<property name="icon-name">edit-find-symbolic</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkBox" id="content_box">
<property name="orientation">vertical</property>
<child>
<object class="GtkSearchBar" id="searchbar">
<child>
<object class="GtkSearchEntry" id="searchentry">
</object>
</child>
</object>
</child>
<child>
<object class="GtkStack" id="stack">
<property name="transition-duration">500</property>
</object>
</child>
</object>
</child>
</object>
</interface>
]]></code></programlisting>
<!-- end inserted example code -->
</section>
<section xml:id="sec-buildapp-side-bar">
<title xml:lang="en">Adding a side bar</title>
<para xml:lang="en">
As another piece of functionality, we are adding a sidebar, which demonstrates
<classname>Gtk::Revealer</classname> and <classname>Gtk::ListBox</classname>.
The new widgets are added in the <filename>window.ui</filename> file.
</para>
<para xml:lang="en">
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 a checkbutton for the new feature to the menu. A menu item is added to
the ui file <filename>gears_menu.ui</filename>.
</para>
<para xml:lang="en">
To connect the menu item to the new <literal>show-words</literal> setting, we use a
<classname>Gio::Action</classname> corresponding to the given <classname>Gio::Settings</classname>
key. In <classname>ExampleAppWindow</classname>'s constructor:
</para>
<programlisting xml:lang="en"><code>// Connect the menu to the MenuButton m_gears, and bind the show-words setting
// to the win.show-words action and the "Words" menu item.
// (The connection between action and menu item is specified in gears_menu.ui.)
auto menu_builder = Gtk::Builder::create_from_resource("/org/gtkmm/exampleapp/gears_menu.ui");
auto menu = menu_builder->get_object<Gio::MenuModel>("menu");
m_gears->set_menu_model(menu);
add_action(m_settings->create_action("show-words"));
</code></programlisting>
<para xml:lang="en">
What our application looks like now:
</para>
<figure xml:id="figure-buildapp-side-bar">
<title xml:lang="en">Adding a side bar</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/buildapp_side_bar.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/buildapp/step7">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>exampleapplication.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "../step4/exampleapplication.h"
// Equal to the corresponding file in step4
]]></code></programlisting>
<para xml:lang="en">File: <filename>exampleappprefs.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "../step5/exampleappprefs.h"
// Equal to the corresponding file in step5
]]></code></programlisting>
<para xml:lang="en">File: <filename>exampleappwindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEAPPWINDOW_H_
#define GTKMM_EXAMPLEAPPWINDOW_H_
#include <gtkmm.h>
#define HAS_SEARCH_ENTRY2 GTKMM_CHECK_VERSION(4,13,2)
class ExampleAppWindow : public Gtk::ApplicationWindow
{
public:
ExampleAppWindow(BaseObjectType* cobject,
const Glib::RefPtr<Gtk::Builder>& refBuilder);
static ExampleAppWindow* create();
void open_file_view(const Glib::RefPtr<Gio::File>& file);
protected:
// Signal handlers
void on_search_text_changed();
void on_visible_child_changed();
void on_find_word(const Gtk::Button* button);
void on_reveal_child_changed();
void update_words();
Glib::RefPtr<Gtk::Builder> m_refBuilder;
Glib::RefPtr<Gio::Settings> m_settings;
Gtk::Stack* m_stack {nullptr};
Gtk::ToggleButton* m_search {nullptr};
Gtk::SearchBar* m_searchbar {nullptr};
#if HAS_SEARCH_ENTRY2
Gtk::SearchEntry2* m_searchentry {nullptr};
#else
Gtk::SearchEntry* m_searchentry {nullptr};
#endif
Gtk::MenuButton* m_gears {nullptr};
Gtk::Revealer* m_sidebar {nullptr};
Gtk::ListBox* m_words {nullptr};
Glib::RefPtr<Glib::Binding> m_prop_binding;
};
#endif /* GTKMM_EXAMPLEAPPWINDOW_H */
]]></code></programlisting>
<para xml:lang="en">File: <filename>exampleapplication.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "../step5/exampleapplication.cc"
// Equal to the corresponding file in step5
]]></code></programlisting>
<para xml:lang="en">File: <filename>exampleappprefs.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "../step5/exampleappprefs.cc"
// Equal to the corresponding file in step5
]]></code></programlisting>
<para xml:lang="en">File: <filename>exampleappwindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "exampleappwindow.h"
#include <iostream>
#include <stdexcept>
#include <set>
ExampleAppWindow::ExampleAppWindow(BaseObjectType* cobject,
const Glib::RefPtr<Gtk::Builder>& refBuilder)
: Gtk::ApplicationWindow(cobject),
m_refBuilder(refBuilder)
{
// Get widgets from the Gtk::Builder file.
m_stack = m_refBuilder->get_widget<Gtk::Stack>("stack");
if (!m_stack)
throw std::runtime_error("No \"stack\" object in window.ui");
m_search = m_refBuilder->get_widget<Gtk::ToggleButton>("search");
if (!m_search)
throw std::runtime_error("No \"search\" object in window.ui");
m_searchbar = m_refBuilder->get_widget<Gtk::SearchBar>("searchbar");
if (!m_searchbar)
throw std::runtime_error("No \"searchbar\" object in window.ui");
#if HAS_SEARCH_ENTRY2
m_searchentry = m_refBuilder->get_widget<Gtk::SearchEntry2>("searchentry");
#else
m_searchentry = m_refBuilder->get_widget<Gtk::SearchEntry>("searchentry");
#endif
if (!m_searchentry)
throw std::runtime_error("No \"searchentry\" object in window.ui");
m_gears = m_refBuilder->get_widget<Gtk::MenuButton>("gears");
if (!m_gears)
throw std::runtime_error("No \"gears\" object in window.ui");
m_sidebar = m_refBuilder->get_widget<Gtk::Revealer>("sidebar");
if (!m_sidebar)
throw std::runtime_error("No \"sidebar\" object in window.ui");
m_words = m_refBuilder->get_widget<Gtk::ListBox>("words");
if (!m_words)
throw std::runtime_error("No \"words\" object in window.ui");
// Bind settings.
m_settings = Gio::Settings::create("org.gtkmm.exampleapp");
m_settings->bind("transition", m_stack->property_transition_type());
m_settings->bind("show-words", m_sidebar->property_reveal_child());
// Bind properties.
m_prop_binding = Glib::Binding::bind_property(m_search->property_active(),
m_searchbar->property_search_mode_enabled(), Glib::Binding::Flags::BIDIRECTIONAL);
// Connect signal handlers.
m_searchentry->signal_search_changed().connect(
sigc::mem_fun(*this, &ExampleAppWindow::on_search_text_changed));
m_stack->property_visible_child().signal_changed().connect(
sigc::mem_fun(*this, &ExampleAppWindow::on_visible_child_changed));
m_sidebar->property_reveal_child().signal_changed().connect(
sigc::mem_fun(*this, &ExampleAppWindow::on_reveal_child_changed));
// Connect the menu to the MenuButton m_gears, and bind the show-words setting
// to the win.show-words action and the "Words" menu item.
// (The connection between action and menu item is specified in gears_menu.ui.)
auto menu_builder = Gtk::Builder::create_from_resource("/org/gtkmm/exampleapp/gears_menu.ui");
auto menu = menu_builder->get_object<Gio::MenuModel>("menu");
if (!menu)
throw std::runtime_error("No \"menu\" object in gears_menu.ui");
m_gears->set_menu_model(menu);
add_action(m_settings->create_action("show-words"));
}
//static
ExampleAppWindow* ExampleAppWindow::create()
{
// Load the Builder file and instantiate its widgets.
auto refBuilder = Gtk::Builder::create_from_resource("/org/gtkmm/exampleapp/window.ui");
auto window = Gtk::Builder::get_widget_derived<ExampleAppWindow>(refBuilder, "app_window");
if (!window)
throw std::runtime_error("No \"app_window\" object in window.ui");
return window;
}
void ExampleAppWindow::open_file_view(const Glib::RefPtr<Gio::File>& file)
{
const Glib::ustring basename = file->get_basename();
auto scrolled = Gtk::make_managed<Gtk::ScrolledWindow>();
scrolled->set_expand(true);
auto view = Gtk::make_managed<Gtk::TextView>();
view->set_editable(false);
view->set_cursor_visible(false);
scrolled->set_child(*view);
m_stack->add(*scrolled, basename, basename);
auto buffer = view->get_buffer();
try
{
char* contents = nullptr;
gsize length = 0;
file->load_contents(contents, length);
buffer->set_text(contents, contents+length);
g_free(contents);
}
catch (const Glib::Error& ex)
{
std::cout << "ExampleAppWindow::open_file_view(\"" << file->get_parse_name()
<< "\"):\n " << ex.what() << std::endl;
return;
}
auto tag = buffer->create_tag();
m_settings->bind("font", tag->property_font());
buffer->apply_tag(tag, buffer->begin(), buffer->end());
m_search->set_sensitive(true);
update_words();
}
void ExampleAppWindow::on_search_text_changed()
{
const auto text = m_searchentry->get_text();
if (text.empty())
return;
auto tab = dynamic_cast<Gtk::ScrolledWindow*>(m_stack->get_visible_child());
if (!tab)
{
std::cout << "ExampleAppWindow::on_search_text_changed(): No visible child." << std::endl;
return;
}
auto view = dynamic_cast<Gtk::TextView*>(tab->get_child());
if (!view)
{
std::cout << "ExampleAppWindow::on_search_text_changed(): No visible text view." << std::endl;
return;
}
// Very simple-minded search implementation.
auto buffer = view->get_buffer();
Gtk::TextIter match_start;
Gtk::TextIter match_end;
if (buffer->begin().forward_search(text, Gtk::TextSearchFlags::CASE_INSENSITIVE,
match_start, match_end))
{
buffer->select_range(match_start, match_end);
view->scroll_to(match_start);
}
}
void ExampleAppWindow::on_visible_child_changed()
{
m_searchbar->set_search_mode(false);
update_words();
}
void ExampleAppWindow::on_find_word(const Gtk::Button* button)
{
m_searchentry->set_text(button->get_label());
}
void ExampleAppWindow::on_reveal_child_changed()
{
update_words();
}
void ExampleAppWindow::update_words()
{
auto tab = dynamic_cast<Gtk::ScrolledWindow*>(m_stack->get_visible_child());
if (!tab)
return;
auto view = dynamic_cast<Gtk::TextView*>(tab->get_child());
if (!view)
{
std::cout << "ExampleAppWindow::update_words(): No visible text view." << std::endl;
return;
}
auto buffer = view->get_buffer();
// Find all words in the text buffer.
std::set<Glib::ustring> words;
auto start = buffer->begin();
Gtk::TextIter end;
while (start)
{
while (start && !start.starts_word())
++start;
if (!start)
break;
end = start;
end.forward_word_end();
if (start == end)
break;
auto word = buffer->get_text(start, end, false);
words.insert(word.lowercase());
start = end;
}
// Remove old children from the ListBox.
while (auto child = m_words->get_first_child())
m_words->remove(*child);
// Add new child buttons, one per unique word.
for (const auto& word : words)
{
auto row = Gtk::make_managed<Gtk::Button>(word);
row->signal_clicked().connect(sigc::bind(sigc::mem_fun(*this,
&ExampleAppWindow::on_find_word), row));
m_words->append(*row);
}
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "../step6/main.cc"
// Equal to the corresponding file in step6
]]></code></programlisting>
<para xml:lang="en">File: <filename>gears_menu.ui</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[<?xml version="1.0" encoding="UTF-8"?>
<interface>
<menu id="menu">
<section>
<item>
<attribute name="label" translatable="yes">_Words</attribute>
<attribute name="action">win.show-words</attribute>
</item>
<item>
<attribute name="label" translatable="yes">_Preferences</attribute>
<attribute name="action">app.preferences</attribute>
</item>
</section>
<section>
<item>
<attribute name="label" translatable="yes">_Quit</attribute>
<attribute name="action">app.quit</attribute>
</item>
</section>
</menu>
</interface>
]]></code></programlisting>
<para xml:lang="en">File: <filename>window.ui</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkApplicationWindow" id="app_window">
<property name="title" translatable="yes">Example Application</property>
<property name="default-width">600</property>
<property name="default-height">400</property>
<property name="hide-on-close">True</property>
<child type="titlebar">
<object class="GtkHeaderBar" id="header">
<child type="title">
<object class="GtkStackSwitcher" id="tabs">
<property name="stack">stack</property>
</object>
</child>
<child type="end">
<object class="GtkToggleButton" id="search">
<property name="sensitive">False</property>
<property name="icon-name">edit-find-symbolic</property>
</object>
</child>
<child type="end">
<object class="GtkMenuButton" id="gears">
<property name="direction">none</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkBox" id="content_box">
<property name="orientation">vertical</property>
<child>
<object class="GtkSearchBar" id="searchbar">
<child>
<object class="GtkSearchEntry" id="searchentry">
</object>
</child>
</object>
</child>
<child>
<object class="GtkBox" id="hbox">
<child>
<object class="GtkRevealer" id="sidebar">
<property name="transition-type">slide-right</property>
<child>
<object class="GtkScrolledWindow" id="sidebar-sw">
<property name="hscrollbar-policy">never</property>
<child>
<object class="GtkListBox" id="words">
<property name="selection-mode">none</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkStack" id="stack">
<property name="transition-duration">500</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</interface>
]]></code></programlisting>
<para xml:lang="en">File: <filename>org.gtkmm.exampleapp.gschema.xml</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[<?xml version="1.0" encoding="UTF-8"?>
<schemalist>
<schema path="/org/gtkmm/exampleapp/" id="org.gtkmm.exampleapp">
<key name="font" type="s">
<default>'Monospace 12'</default>
<summary>Font</summary>
<description>The font to be used for content.</description>
</key>
<key name="transition" type="s">
<choices>
<choice value='none'/>
<choice value='crossfade'/>
<choice value='slide-left-right'/>
</choices>
<default>'none'</default>
<summary>Transition</summary>
<description>The transition to use when switching tabs.</description>
</key>
<key name="show-words" type="b">
<default>false</default>
<summary>Show words</summary>
<description>Whether to show a word list in the sidebar.</description>
</key>
</schema>
</schemalist>
]]></code></programlisting>
<!-- end inserted example code -->
</section>
<section xml:id="sec-buildapp-properties">
<title xml:lang="en">Properties</title>
<para xml:lang="en">
Widgets and other objects have many useful properties. Here we show some ways to use
them in new and flexible ways, by wrapping them in actions with <classname>Gio::PropertyAction</classname>
or by binding them with <classname>Glib::Binding</classname>.
</para>
<para xml:lang="en">
To set this up, we add two labels to the header bar in our <filename>window.ui</filename> file,
named <literal>lines_label</literal> and <literal>lines</literal>, and get pointers to them
in the application window's constructor, as we've seen a couple of times by now.
We add a new "Lines" menu item to the gears menu, which triggers the
<literal>show-lines</literal> action.
</para>
<para xml:lang="en">
To make this menu item do something, we create a property action for the <literal>visible</literal>
property of the <literal>lines</literal> 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.
Since we want both labels to appear and disappear together, we bind the <literal>visible</literal>
property of the <literal>lines_label</literal> widget to the same property of the
<literal>lines</literal> widget.
In <classname>ExampleAppWindow</classname>'s constructor:
</para>
<programlisting xml:lang="en"><code>add_action(Gio::PropertyAction::create("show-lines", m_lines->property_visible()));
m_binding_lines_visible = Glib::Binding::bind_property(m_lines->property_visible(),
m_lines_label->property_visible());
</code></programlisting>
<para xml:lang="en">
We also need a function that counts the lines of the currently active tab, and updates
the <literal>lines</literal> label. See the full source if you are interested in the details.
</para>
<para xml:lang="en">
This brings our example application to this appearance:
</para>
<figure xml:id="figure-buildapp-properties">
<title xml:lang="en">Properties</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/buildapp_properties.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/buildapp/step8">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>exampleapplication.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "../step4/exampleapplication.h"
// Equal to the corresponding file in step4
]]></code></programlisting>
<para xml:lang="en">File: <filename>exampleappprefs.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "../step5/exampleappprefs.h"
// Equal to the corresponding file in step5
]]></code></programlisting>
<para xml:lang="en">File: <filename>exampleappwindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLEAPPWINDOW_H_
#define GTKMM_EXAMPLEAPPWINDOW_H_
#include <gtkmm.h>
#define HAS_SEARCH_ENTRY2 GTKMM_CHECK_VERSION(4,13,2)
class ExampleAppWindow : public Gtk::ApplicationWindow
{
public:
ExampleAppWindow(BaseObjectType* cobject,
const Glib::RefPtr<Gtk::Builder>& refBuilder);
static ExampleAppWindow* create();
void open_file_view(const Glib::RefPtr<Gio::File>& file);
protected:
// Signal handlers
void on_search_text_changed();
void on_visible_child_changed();
void on_find_word(const Gtk::Button* button);
void on_reveal_child_changed();
void update_words();
void update_lines();
Glib::RefPtr<Gtk::Builder> m_refBuilder;
Glib::RefPtr<Gio::Settings> m_settings;
Gtk::Stack* m_stack {nullptr};
Gtk::ToggleButton* m_search {nullptr};
Gtk::SearchBar* m_searchbar {nullptr};
#if HAS_SEARCH_ENTRY2
Gtk::SearchEntry2* m_searchentry {nullptr};
#else
Gtk::SearchEntry* m_searchentry {nullptr};
#endif
Gtk::MenuButton* m_gears {nullptr};
Gtk::Revealer* m_sidebar {nullptr};
Gtk::ListBox* m_words {nullptr};
Gtk::Label* m_lines {nullptr};
Gtk::Label* m_lines_label {nullptr};
Glib::RefPtr<Glib::Binding> m_binding_search_enabled;
Glib::RefPtr<Glib::Binding> m_binding_lines_visible;
};
#endif /* GTKMM_EXAMPLEAPPWINDOW_H */
]]></code></programlisting>
<para xml:lang="en">File: <filename>exampleapplication.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "../step5/exampleapplication.cc"
// Equal to the corresponding file in step5
]]></code></programlisting>
<para xml:lang="en">File: <filename>exampleappprefs.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "../step5/exampleappprefs.cc"
// Equal to the corresponding file in step5
]]></code></programlisting>
<para xml:lang="en">File: <filename>exampleappwindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "exampleappwindow.h"
#include <iostream>
#include <stdexcept>
#include <set>
ExampleAppWindow::ExampleAppWindow(BaseObjectType* cobject,
const Glib::RefPtr<Gtk::Builder>& refBuilder)
: Gtk::ApplicationWindow(cobject),
m_refBuilder(refBuilder)
{
// Get widgets from the Gtk::Builder file.
m_stack = m_refBuilder->get_widget<Gtk::Stack>("stack");
if (!m_stack)
throw std::runtime_error("No \"stack\" object in window.ui");
m_search = m_refBuilder->get_widget<Gtk::ToggleButton>("search");
if (!m_search)
throw std::runtime_error("No \"search\" object in window.ui");
m_searchbar = m_refBuilder->get_widget<Gtk::SearchBar>("searchbar");
if (!m_searchbar)
throw std::runtime_error("No \"searchbar\" object in window.ui");
#if HAS_SEARCH_ENTRY2
m_searchentry = m_refBuilder->get_widget<Gtk::SearchEntry2>("searchentry");
#else
m_searchentry = m_refBuilder->get_widget<Gtk::SearchEntry>("searchentry");
#endif
if (!m_searchentry)
throw std::runtime_error("No \"searchentry\" object in window.ui");
m_gears = m_refBuilder->get_widget<Gtk::MenuButton>("gears");
if (!m_gears)
throw std::runtime_error("No \"gears\" object in window.ui");
m_sidebar = m_refBuilder->get_widget<Gtk::Revealer>("sidebar");
if (!m_sidebar)
throw std::runtime_error("No \"sidebar\" object in window.ui");
m_words = m_refBuilder->get_widget<Gtk::ListBox>("words");
if (!m_words)
throw std::runtime_error("No \"words\" object in window.ui");
m_lines = m_refBuilder->get_widget<Gtk::Label>("lines");
if (!m_lines)
throw std::runtime_error("No \"lines\" object in window.ui");
m_lines_label = m_refBuilder->get_widget<Gtk::Label>("lines_label");
if (!m_lines_label)
throw std::runtime_error("No \"lines_label\" object in window.ui");
// Bind settings.
m_settings = Gio::Settings::create("org.gtkmm.exampleapp");
m_settings->bind("transition", m_stack->property_transition_type());
m_settings->bind("show-words", m_sidebar->property_reveal_child());
// Bind properties of the search button to the search bar.
m_binding_search_enabled = Glib::Binding::bind_property(m_search->property_active(),
m_searchbar->property_search_mode_enabled(), Glib::Binding::Flags::BIDIRECTIONAL);
// Connect signal handlers.
m_searchentry->signal_search_changed().connect(
sigc::mem_fun(*this, &ExampleAppWindow::on_search_text_changed));
m_stack->property_visible_child().signal_changed().connect(
sigc::mem_fun(*this, &ExampleAppWindow::on_visible_child_changed));
m_sidebar->property_reveal_child().signal_changed().connect(
sigc::mem_fun(*this, &ExampleAppWindow::on_reveal_child_changed));
// Connect the menu to the MenuButton m_gears, and bind the show-words setting
// to the win.show-words action and the "Words" menu item.
// (The connection between action and menu item is specified in gears_menu.ui.)
auto menu_builder = Gtk::Builder::create_from_resource("/org/gtkmm/exampleapp/gears_menu.ui");
auto menu = menu_builder->get_object<Gio::MenuModel>("menu");
if (!menu)
throw std::runtime_error("No \"menu\" object in gears_menu.ui");
m_gears->set_menu_model(menu);
add_action(m_settings->create_action("show-words"));
// Bind the "visible" property of m_lines to the win.show-lines action, to
// the "Lines" menu item and to the "visible" property of m_lines_label.
add_action(Gio::PropertyAction::create("show-lines", m_lines->property_visible()));
m_binding_lines_visible = Glib::Binding::bind_property(m_lines->property_visible(),
m_lines_label->property_visible());
}
//static
ExampleAppWindow* ExampleAppWindow::create()
{
// Load the Builder file and instantiate its widgets.
auto refBuilder = Gtk::Builder::create_from_resource("/org/gtkmm/exampleapp/window.ui");
auto window = Gtk::Builder::get_widget_derived<ExampleAppWindow>(refBuilder, "app_window");
if (!window)
throw std::runtime_error("No \"app_window\" object in window.ui");
return window;
}
void ExampleAppWindow::open_file_view(const Glib::RefPtr<Gio::File>& file)
{
const Glib::ustring basename = file->get_basename();
auto scrolled = Gtk::make_managed<Gtk::ScrolledWindow>();
scrolled->set_expand(true);
auto view = Gtk::make_managed<Gtk::TextView>();
view->set_editable(false);
view->set_cursor_visible(false);
scrolled->set_child(*view);
m_stack->add(*scrolled, basename, basename);
auto buffer = view->get_buffer();
try
{
char* contents = nullptr;
gsize length = 0;
file->load_contents(contents, length);
buffer->set_text(contents, contents+length);
g_free(contents);
}
catch (const Glib::Error& ex)
{
std::cout << "ExampleAppWindow::open_file_view(\"" << file->get_parse_name()
<< "\"):\n " << ex.what() << std::endl;
return;
}
auto tag = buffer->create_tag();
m_settings->bind("font", tag->property_font());
buffer->apply_tag(tag, buffer->begin(), buffer->end());
m_search->set_sensitive(true);
update_words();
update_lines();
}
void ExampleAppWindow::on_search_text_changed()
{
const auto text = m_searchentry->get_text();
if (text.empty())
return;
auto tab = dynamic_cast<Gtk::ScrolledWindow*>(m_stack->get_visible_child());
if (!tab)
{
std::cout << "ExampleAppWindow::on_search_text_changed(): No visible child." << std::endl;
return;
}
auto view = dynamic_cast<Gtk::TextView*>(tab->get_child());
if (!view)
{
std::cout << "ExampleAppWindow::on_search_text_changed(): No visible text view." << std::endl;
return;
}
// Very simple-minded search implementation.
auto buffer = view->get_buffer();
Gtk::TextIter match_start;
Gtk::TextIter match_end;
if (buffer->begin().forward_search(text, Gtk::TextSearchFlags::CASE_INSENSITIVE,
match_start, match_end))
{
buffer->select_range(match_start, match_end);
view->scroll_to(match_start);
}
}
void ExampleAppWindow::on_visible_child_changed()
{
m_searchbar->set_search_mode(false);
update_words();
update_lines();
}
void ExampleAppWindow::on_find_word(const Gtk::Button* button)
{
m_searchentry->set_text(button->get_label());
}
void ExampleAppWindow::on_reveal_child_changed()
{
update_words();
}
void ExampleAppWindow::update_words()
{
auto tab = dynamic_cast<Gtk::ScrolledWindow*>(m_stack->get_visible_child());
if (!tab)
return;
auto view = dynamic_cast<Gtk::TextView*>(tab->get_child());
if (!view)
{
std::cout << "ExampleAppWindow::update_words(): No visible text view." << std::endl;
return;
}
auto buffer = view->get_buffer();
// Find all words in the text buffer.
std::set<Glib::ustring> words;
auto start = buffer->begin();
Gtk::TextIter end;
while (start)
{
while (start && !start.starts_word())
++start;
if (!start)
break;
end = start;
end.forward_word_end();
if (start == end)
break;
auto word = buffer->get_text(start, end, false);
words.insert(word.lowercase());
start = end;
}
// Remove old children from the ListBox.
while (auto child = m_words->get_first_child())
m_words->remove(*child);
// Add new child buttons, one per unique word.
for (const auto& word : words)
{
auto row = Gtk::make_managed<Gtk::Button>(word);
row->signal_clicked().connect(sigc::bind(sigc::mem_fun(*this,
&ExampleAppWindow::on_find_word), row));
m_words->append(*row);
}
}
void ExampleAppWindow::update_lines()
{
auto tab = dynamic_cast<Gtk::ScrolledWindow*>(m_stack->get_visible_child());
if (!tab)
return;
auto view = dynamic_cast<Gtk::TextView*>(tab->get_child());
if (!view)
{
std::cout << "ExampleAppWindow::update_lines(): No visible text view." << std::endl;
return;
}
auto buffer = view->get_buffer();
int count = 0;
auto iter = buffer->begin();
while (iter)
{
++count;
if (!iter.forward_line())
break;
}
m_lines->set_text(Glib::ustring::format(count));
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "../step6/main.cc"
// Equal to the corresponding file in step6
]]></code></programlisting>
<para xml:lang="en">File: <filename>gears_menu.ui</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[<?xml version="1.0" encoding="UTF-8"?>
<interface>
<menu id="menu">
<section>
<item>
<attribute name="label" translatable="yes">_Words</attribute>
<attribute name="action">win.show-words</attribute>
</item>
<item>
<attribute name="label" translatable="yes">_Lines</attribute>
<attribute name="action">win.show-lines</attribute>
</item>
<item>
<attribute name="label" translatable="yes">_Preferences</attribute>
<attribute name="action">app.preferences</attribute>
</item>
</section>
<section>
<item>
<attribute name="label" translatable="yes">_Quit</attribute>
<attribute name="action">app.quit</attribute>
</item>
</section>
</menu>
</interface>
]]></code></programlisting>
<para xml:lang="en">File: <filename>window.ui</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkApplicationWindow" id="app_window">
<property name="title" translatable="yes">Example Application</property>
<property name="default-width">600</property>
<property name="default-height">400</property>
<property name="hide-on-close">True</property>
<child type="titlebar">
<object class="GtkHeaderBar" id="header">
<child>
<object class="GtkLabel" id="lines_label">
<property name="visible">False</property>
<property name="label" translatable="yes">Lines:</property>
</object>
</child>
<child>
<object class="GtkLabel" id="lines">
<property name="visible">False</property>
</object>
</child>
<child type="title">
<object class="GtkStackSwitcher" id="tabs">
<property name="stack">stack</property>
</object>
</child>
<child type="end">
<object class="GtkToggleButton" id="search">
<property name="sensitive">False</property>
<property name="icon-name">edit-find-symbolic</property>
</object>
</child>
<child type="end">
<object class="GtkMenuButton" id="gears">
<property name="direction">none</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkBox" id="content_box">
<property name="orientation">vertical</property>
<child>
<object class="GtkSearchBar" id="searchbar">
<child>
<object class="GtkSearchEntry" id="searchentry">
</object>
</child>
</object>
</child>
<child>
<object class="GtkBox" id="hbox">
<child>
<object class="GtkRevealer" id="sidebar">
<property name="transition-type">slide-right</property>
<child>
<object class="GtkScrolledWindow" id="sidebar-sw">
<property name="hscrollbar-policy">never</property>
<child>
<object class="GtkListBox" id="words">
<property name="selection-mode">none</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkStack" id="stack">
<property name="transition-duration">500</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</interface>
]]></code></programlisting>
<!-- end inserted example code -->
</section>
<section xml:id="sec-buildapp-header-bar">
<title>Barra de cabecera</title>
<para xml:lang="en">
Our application already uses a <classname>Gtk::HeaderBar</classname> instead of
a 'normal' window titlebar. The header bar is a direct child of the window,
and its type is <literal>titlebar</literal>. This is set in the
<filename>window.ui</filename> file.
</para>
<para xml:lang="en">
Here we'll just make two small changes to the header bar. The <literal>decoration-layout</literal>
property is set in the <filename>window.ui</filename> file, to show only the close
button, and hide the minimize and maximize buttons. We also include an icon in the
resource file, and set up this icon as the window icon.
In <classname>ExampleAppWindow</classname>'s constructor:
</para>
<programlisting xml:lang="en"><code>Gtk::IconTheme::get_for_display(get_display())->add_resource_path("/org/gtkmm/exampleapp");
set_icon_name("exampleapp");
</code></programlisting>
<para xml:lang="en">
Here is how the application now looks:
</para>
<figure xml:id="figure-buildapp-header-bar">
<title>Barra de cabecera</title>
<screenshot>
<mediaobject><imageobject><imagedata format="PNG" fileref="figures/buildapp_header_bar.png"/></imageobject></mediaobject>
</screenshot>
</figure>
<para xml:lang="en">
The <filename>window.ui</filename> file sets a header bar title, but this title is not shown.
That's because the stack switcher is a child of type <literal>title</literal>. The stack
switcher becomes a custom title that hides the title label.
</para>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/buildapp/step9">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>exampleapplication.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "../step4/exampleapplication.h"
// Equal to the corresponding file in step4
]]></code></programlisting>
<para xml:lang="en">File: <filename>exampleappprefs.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "../step5/exampleappprefs.h"
// Equal to the corresponding file in step5
]]></code></programlisting>
<para xml:lang="en">File: <filename>exampleappwindow.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "../step8/exampleappwindow.h"
// Equal to the corresponding file in step8
]]></code></programlisting>
<para xml:lang="en">File: <filename>exampleapplication.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "../step5/exampleapplication.cc"
// Equal to the corresponding file in step5
]]></code></programlisting>
<para xml:lang="en">File: <filename>exampleappprefs.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "../step5/exampleappprefs.cc"
// Equal to the corresponding file in step5
]]></code></programlisting>
<para xml:lang="en">File: <filename>exampleappwindow.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "exampleappwindow.h"
#include <iostream>
#include <stdexcept>
#include <set>
ExampleAppWindow::ExampleAppWindow(BaseObjectType* cobject,
const Glib::RefPtr<Gtk::Builder>& refBuilder)
: Gtk::ApplicationWindow(cobject),
m_refBuilder(refBuilder)
{
// Get widgets from the Gtk::Builder file.
m_stack = m_refBuilder->get_widget<Gtk::Stack>("stack");
if (!m_stack)
throw std::runtime_error("No \"stack\" object in window.ui");
m_search = m_refBuilder->get_widget<Gtk::ToggleButton>("search");
if (!m_search)
throw std::runtime_error("No \"search\" object in window.ui");
m_searchbar = m_refBuilder->get_widget<Gtk::SearchBar>("searchbar");
if (!m_searchbar)
throw std::runtime_error("No \"searchbar\" object in window.ui");
#if HAS_SEARCH_ENTRY2
m_searchentry = m_refBuilder->get_widget<Gtk::SearchEntry2>("searchentry");
#else
m_searchentry = m_refBuilder->get_widget<Gtk::SearchEntry>("searchentry");
#endif
if (!m_searchentry)
throw std::runtime_error("No \"searchentry\" object in window.ui");
m_gears = m_refBuilder->get_widget<Gtk::MenuButton>("gears");
if (!m_gears)
throw std::runtime_error("No \"gears\" object in window.ui");
m_sidebar = m_refBuilder->get_widget<Gtk::Revealer>("sidebar");
if (!m_sidebar)
throw std::runtime_error("No \"sidebar\" object in window.ui");
m_words = m_refBuilder->get_widget<Gtk::ListBox>("words");
if (!m_words)
throw std::runtime_error("No \"words\" object in window.ui");
m_lines = m_refBuilder->get_widget<Gtk::Label>("lines");
if (!m_lines)
throw std::runtime_error("No \"lines\" object in window.ui");
m_lines_label = m_refBuilder->get_widget<Gtk::Label>("lines_label");
if (!m_lines_label)
throw std::runtime_error("No \"lines_label\" object in window.ui");
// Bind settings.
m_settings = Gio::Settings::create("org.gtkmm.exampleapp");
m_settings->bind("transition", m_stack->property_transition_type());
m_settings->bind("show-words", m_sidebar->property_reveal_child());
// Bind properties of the search button to the search bar.
m_binding_search_enabled = Glib::Binding::bind_property(m_search->property_active(),
m_searchbar->property_search_mode_enabled(), Glib::Binding::Flags::BIDIRECTIONAL);
// Connect signal handlers.
m_searchentry->signal_search_changed().connect(
sigc::mem_fun(*this, &ExampleAppWindow::on_search_text_changed));
m_stack->property_visible_child().signal_changed().connect(
sigc::mem_fun(*this, &ExampleAppWindow::on_visible_child_changed));
m_sidebar->property_reveal_child().signal_changed().connect(
sigc::mem_fun(*this, &ExampleAppWindow::on_reveal_child_changed));
// Connect the menu to the MenuButton m_gears, and bind the show-words setting
// to the win.show-words action and the "Words" menu item.
// (The connection between action and menu item is specified in gears_menu.ui.)
auto menu_builder = Gtk::Builder::create_from_resource("/org/gtkmm/exampleapp/gears_menu.ui");
auto menu = menu_builder->get_object<Gio::MenuModel>("menu");
if (!menu)
throw std::runtime_error("No \"menu\" object in gears_menu.ui");
m_gears->set_menu_model(menu);
add_action(m_settings->create_action("show-words"));
// Bind the "visible" property of m_lines to the win.show-lines action, to
// the "Lines" menu item and to the "visible" property of m_lines_label.
add_action(Gio::PropertyAction::create("show-lines", m_lines->property_visible()));
m_binding_lines_visible = Glib::Binding::bind_property(m_lines->property_visible(),
m_lines_label->property_visible());
// Set the window icon.
Gtk::IconTheme::get_for_display(get_display())->add_resource_path("/org/gtkmm/exampleapp");
set_icon_name("exampleapp");
}
//static
ExampleAppWindow* ExampleAppWindow::create()
{
// Load the Builder file and instantiate its widgets.
auto refBuilder = Gtk::Builder::create_from_resource("/org/gtkmm/exampleapp/window.ui");
auto window = Gtk::Builder::get_widget_derived<ExampleAppWindow>(refBuilder, "app_window");
if (!window)
throw std::runtime_error("No \"app_window\" object in window.ui");
return window;
}
void ExampleAppWindow::open_file_view(const Glib::RefPtr<Gio::File>& file)
{
const Glib::ustring basename = file->get_basename();
auto scrolled = Gtk::make_managed<Gtk::ScrolledWindow>();
scrolled->set_expand(true);
auto view = Gtk::make_managed<Gtk::TextView>();
view->set_editable(false);
view->set_cursor_visible(false);
scrolled->set_child(*view);
m_stack->add(*scrolled, basename, basename);
auto buffer = view->get_buffer();
try
{
char* contents = nullptr;
gsize length = 0;
file->load_contents(contents, length);
buffer->set_text(contents, contents+length);
g_free(contents);
}
catch (const Glib::Error& ex)
{
std::cout << "ExampleAppWindow::open_file_view(\"" << file->get_parse_name()
<< "\"):\n " << ex.what() << std::endl;
return;
}
auto tag = buffer->create_tag();
m_settings->bind("font", tag->property_font());
buffer->apply_tag(tag, buffer->begin(), buffer->end());
m_search->set_sensitive(true);
update_words();
update_lines();
}
void ExampleAppWindow::on_search_text_changed()
{
const auto text = m_searchentry->get_text();
if (text.empty())
return;
auto tab = dynamic_cast<Gtk::ScrolledWindow*>(m_stack->get_visible_child());
if (!tab)
{
std::cout << "ExampleAppWindow::on_search_text_changed(): No visible child." << std::endl;
return;
}
auto view = dynamic_cast<Gtk::TextView*>(tab->get_child());
if (!view)
{
std::cout << "ExampleAppWindow::on_search_text_changed(): No visible text view." << std::endl;
return;
}
// Very simple-minded search implementation.
auto buffer = view->get_buffer();
Gtk::TextIter match_start;
Gtk::TextIter match_end;
if (buffer->begin().forward_search(text, Gtk::TextSearchFlags::CASE_INSENSITIVE,
match_start, match_end))
{
buffer->select_range(match_start, match_end);
view->scroll_to(match_start);
}
}
void ExampleAppWindow::on_visible_child_changed()
{
m_searchbar->set_search_mode(false);
update_words();
update_lines();
}
void ExampleAppWindow::on_find_word(const Gtk::Button* button)
{
m_searchentry->set_text(button->get_label());
}
void ExampleAppWindow::on_reveal_child_changed()
{
update_words();
}
void ExampleAppWindow::update_words()
{
auto tab = dynamic_cast<Gtk::ScrolledWindow*>(m_stack->get_visible_child());
if (!tab)
return;
auto view = dynamic_cast<Gtk::TextView*>(tab->get_child());
if (!view)
{
std::cout << "ExampleAppWindow::update_words(): No visible text view." << std::endl;
return;
}
auto buffer = view->get_buffer();
// Find all words in the text buffer.
std::set<Glib::ustring> words;
auto start = buffer->begin();
Gtk::TextIter end;
while (start)
{
while (start && !start.starts_word())
++start;
if (!start)
break;
end = start;
end.forward_word_end();
if (start == end)
break;
auto word = buffer->get_text(start, end, false);
words.insert(word.lowercase());
start = end;
}
// Remove old children from the ListBox.
while (auto child = m_words->get_first_child())
m_words->remove(*child);
// Add new child buttons, one per unique word.
for (const auto& word : words)
{
auto row = Gtk::make_managed<Gtk::Button>(word);
row->signal_clicked().connect(sigc::bind(sigc::mem_fun(*this,
&ExampleAppWindow::on_find_word), row));
m_words->append(*row);
}
}
void ExampleAppWindow::update_lines()
{
auto tab = dynamic_cast<Gtk::ScrolledWindow*>(m_stack->get_visible_child());
if (!tab)
return;
auto view = dynamic_cast<Gtk::TextView*>(tab->get_child());
if (!view)
{
std::cout << "ExampleAppWindow::update_lines(): No visible text view." << std::endl;
return;
}
auto buffer = view->get_buffer();
int count = 0;
auto iter = buffer->begin();
while (iter)
{
++count;
if (!iter.forward_line())
break;
}
m_lines->set_text(Glib::ustring::format(count));
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "../step6/main.cc"
// Equal to the corresponding file in step6
]]></code></programlisting>
<para xml:lang="en">File: <filename>exampleapp.gresource.xml</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/org/gtkmm/exampleapp">
<file preprocess="xml-stripblanks">window.ui</file>
<file preprocess="xml-stripblanks">gears_menu.ui</file>
<file preprocess="xml-stripblanks">prefs.ui</file>
<file>exampleapp.png</file>
</gresource>
</gresources>
]]></code></programlisting>
<para xml:lang="en">File: <filename>window.ui</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkApplicationWindow" id="app_window">
<property name="title" translatable="yes">Example Application</property>
<property name="default-width">600</property>
<property name="default-height">400</property>
<property name="hide-on-close">True</property>
<child type="titlebar">
<object class="GtkHeaderBar" id="header">
<property name="decoration-layout">icon:close</property>
<child>
<object class="GtkLabel" id="lines_label">
<property name="visible">False</property>
<property name="label" translatable="yes">Lines:</property>
</object>
</child>
<child>
<object class="GtkLabel" id="lines">
<property name="visible">False</property>
</object>
</child>
<child type="title">
<object class="GtkStackSwitcher" id="tabs">
<property name="stack">stack</property>
</object>
</child>
<child type="end">
<object class="GtkToggleButton" id="search">
<property name="sensitive">False</property>
<property name="icon-name">edit-find-symbolic</property>
</object>
</child>
<child type="end">
<object class="GtkMenuButton" id="gears">
<property name="direction">none</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkBox" id="content_box">
<property name="orientation">vertical</property>
<child>
<object class="GtkSearchBar" id="searchbar">
<child>
<object class="GtkSearchEntry" id="searchentry">
</object>
</child>
</object>
</child>
<child>
<object class="GtkBox" id="hbox">
<child>
<object class="GtkRevealer" id="sidebar">
<property name="transition-type">slide-right</property>
<child>
<object class="GtkScrolledWindow" id="sidebar-sw">
<property name="hscrollbar-policy">never</property>
<child>
<object class="GtkListBox" id="words">
<property name="selection-mode">none</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkStack" id="stack">
<property name="transition-duration">500</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</interface>
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</chapter>
<chapter xml:id="chapter-contributing">
<title>Contribuir</title>
<para>Fueron voluntarios los que crearon este documento, al igual que mucho otro software grandioso, gratuitamente. Si tiene conocimientos de cualquier aspecto de <application>gtkmm</application> que todavía no esté documentado, por favor considere contribuir a este documento.</para>
<para xml:lang="en">
Ideally, we would like you to <link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/-/merge_requests">
provide a merge request</link> to the <filename>docs/tutorial/C/index-in.docbook</filename> file.
This file is in the <literal>gtkmm-documentation</literal> module in GNOME git.
</para>
<para xml:lang="en">
If you do decide to contribute, please post your contribution as an issue or merge request
to <link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation">GitLab</link>.
You can also discuss your ideas on GNOME's <link xlink:href="https://discourse.gnome.org">Discourse</link>
instance, under the <link xlink:href="https://discourse.gnome.org/c/platform/language-bindings">
Platform/Language bindings</link> category with a <literal>cplusplus</literal> tag.
Also, be aware that the entirety of this document is free, and any addition you provide
must also be free. That is, people must be able to use any portion of
your examples in their programs, and copies of this document
(including your contribution) may be distributed freely.
</para>
</chapter>
<appendix xml:id="chapter-refptr">
<title>El puntero inteligente RefPtr</title>
<para xml:lang="en">
<classname>Glib::RefPtr</classname> is a smartpointer. Specifically, it is a
reference-counting smartpointer. You might be familiar with
<classname>std::unique_ptr<></classname>
and <classname>std::shared_ptr<></classname>, which are also smartpointers.
In <application>gtkmm</application>-4.0 <classname>Glib::RefPtr<></classname> is an alias for
<classname>std::shared_ptr<></classname>,
which is reference-counting. <classname>Glib::RefPtr<></classname> was introduced
long before there was a reference-counting smartpointer in the C++ Standard Library.
</para>
<para xml:lang="en">
If you make your own <classname>Glib::ObjectBase</classname>-derived classes with
<methodname>create()</methodname> methods that return a <classname>Glib::RefPtr</classname>,
you must use <function>Glib::make_refptr_for_instance()</function> in your
<methodname>create()</methodname> methods. This function creates a <classname>std::shared_ptr</classname>
with a special deleter, which handles the reference-count for the wrapped C object.
</para>
<para xml:lang="en"><link xlink:href="https://gnome.pages.gitlab.gnome.org/glibmm/group__RefPtr.html">Reference</link></para>
<para>Un puntero inteligente actúa como un puntero normal. Aquí hay algunos ejemplos.</para>
<section xml:id="sec-refptr-copying">
<title>Copiado</title>
<para>Puede copiar <classname>RefPtr</classname> como punteros normales. Pero, a diferencia de los punteros normales, no necesita preocuparse por la eliminación de la instancia subyacente.</para>
<programlisting xml:lang="en"><code>auto refPixbuf = Gdk::Pixbuf::create_from_file(filename);
auto refPixbuf2 = refPixbuf;
</code></programlisting>
<para>Por supuesto esto significa que puede almacenar <classname>RefPtr</classname> en contenedores estándar, como <classname>std::vector</classname> o <classname>std::list</classname>.</para>
<programlisting xml:lang="en"><code>std::list<Glib::RefPtr<Gdk::Pixbuf>> listPixbufs;
auto refPixbuf = Gdk::Pixbuf::create_from_file(filename);
listPixbufs.push_back(refPixbuf);
</code></programlisting>
</section>
<section xml:id="sec-refptr-dereferencing">
<title>Eliminar referencia</title>
<para>Puede desreferenciar un puntero inteligente con el operador -> para llamar a los métodos de la instancia subyacente, al igual que un puntero normal.</para>
<programlisting xml:lang="en"><code>auto refPixbuf = Gdk::Pixbuf::create_from_file(filename);
auto width = refPixbuf->get_width();
</code></programlisting>
<para xml:lang="en">You can also use the * operator and the <methodname>get()</methodname> method
to access the underlying instance, but it's usually a bad idea to do so. Unless
you are careful, you can end up with a pointer or a reference which is not included
in the reference count.
</para>
<programlisting xml:lang="en"><code>auto refPixbuf = Gdk::Pixbuf::create_from_file(filename);
auto& underlying = *refPixbuf; // Possible, but not recommended
</code></programlisting>
</section>
<section xml:id="sec-refptr-casting">
<title>Conversión de tipos</title>
<para>Puede convertir <classname>RefPtr</classname> a tipos base, al igual que a los punteros normales.</para>
<programlisting xml:lang="en"><code>auto refStore = Gtk::TreeStore::create(columns);
Glib::RefPtr<Gtk::TreeModel> refModel = refStore;
</code></programlisting>
<para xml:lang="en">This means that any method which takes a <type>const
Glib::RefPtr<BaseType>&</type> argument can also take a
<type>const Glib::RefPtr<DerivedType>&</type>. The cast is
implicit, just as it would be for a normal pointer.</para>
<para>También puede convertir a un tipo derivado, pero la sintaxis es un poco diferente que con un puntero normal.</para>
<programlisting xml:lang="en"><code>auto refStore = std::dynamic_pointer_cast<Gtk::TreeStore>(refModel);
auto refStore2 = std::static_pointer_cast<Gtk::TreeStore>(refModel);
</code></programlisting>
</section>
<section xml:id="sec-refptr-checking-for-null">
<title xml:lang="en">Checking for nullptr</title>
<para>Al igual que con los punteros normales, puede verificar si un <classname>RefPtr</classname> apunta a algo.</para>
<programlisting xml:lang="en"><code>auto refModel = m_TreeView.get_model();
if (refModel)
{
auto cols_count = refModel->get_n_columns();
...
}
</code></programlisting>
<para xml:lang="en">
But unlike normal pointers, <classname>RefPtr</classname>s are automatically
initialized to <literal>nullptr</literal> so you don't need to remember to do that yourself.
</para>
</section>
<section xml:id="sec-refptr-constness">
<title>Constancia</title>
<para xml:lang="en">
The use of the <literal>const</literal> keyword in C++ is not always clear. You
might not realize that <type>const Something*</type> declares a pointer to a
<type>const Something</type>. The pointer can be changed, but not the
<type>Something</type> that it points to.
</para>
<para>Por lo tanto, el equivalente <classname>RefPtr</classname> de <type>Algo*</type> para un parámetro de un método es <type>const Glib::RefPtr<Something>&</type>, y el equivalente de <type>const Algo*</type> es <type>const Glib::RefPtr<const Something>&</type>.</para>
<para>El <literal>const ... &</literal> que los rodea está sólo por eficiencia, como usar <literal>const std::string&</literal> en lugar de <classname>std::string</classname> como método de parámetro para evitar una copia innecesaria.</para>
</section>
</appendix>
<appendix xml:id="chapter-signals">
<title>Señales</title>
<section xml:id="sec-connecting-signal-handlers">
<title>Conectar gestores de señales</title>
<para xml:lang="en">
<application>gtkmm</application> widget classes have signal accessor methods, such as
<methodname>Gtk::Button::signal_clicked()</methodname>, which allow you to connect
your signal handler. Thanks to the flexibility of
<application>libsigc++</application>, the callback library used by <application>gtkmm</application>, the
signal handler can be almost any kind of function, but you will probably want
to use a class method. Among <application>GTK</application> C coders, these
signal handlers are often named callbacks.
</para>
<para>Aquí hay un ejemplo de un gestor de señales que se conecta a una señal:</para>
<programlisting xml:lang="en"><code><![CDATA[
#include <gtkmm/button.h>
void on_button_clicked()
{
std::cout << "Hello World" << std::endl;
}
class some_class
{
public:
some_class
{
button.signal_clicked().connect(sigc::ptr_fun(&on_button_clicked));
}
private:
Gtk::Button button {"Hello World"};
};
]]></code></programlisting>
<para>Hay mucho para pensar en este código no funcional. Primero, identifique a los grupos involucrados:</para>
<itemizedlist>
<listitem>
<para>El gestor de señales es <methodname>on_button_clicked()</methodname>.</para>
</listitem>
<listitem>
<para>Se engancha al objeto <classname>Gtk::Button</classname> llamado <varname>button</varname>.</para>
</listitem>
<listitem>
<para>Cuando el botón emita su señal <literal>clicked</literal>, se llamará a <methodname>on_button_clicked()</methodname>.</para>
</listitem>
</itemizedlist>
<para>Ahora, mire la conexión nuevamente:</para>
<programlisting xml:lang="en"><code> ...
button.signal_clicked().connect(sigc::ptr_fun(&on_button_clicked));
...
</code></programlisting>
<para>Tenga en cuenta que no se le pasa un puntero a <methodname>on_button_clicked()</methodname> directamente al método de la señal <methodname>connect()</methodname>. En su lugar, se llama a <function>sigc::ptr_fun()</function>, y se le pasa el resultado a <methodname>connect()</methodname>.</para>
<para><function>sigc::ptr_fun()</function> genera un <classname>sigc::slot</classname>. Un «slot» es un objeto que se comporta como una función, pero en realidad es un objeto. Estos también se llaman objetos función, o funtores. <function>sigc::ptr_fun()</function> genera un «slot» para una función independiente o un método estático. <function>sigc::mem_fun()</function> genera un «slot» para un método miembro de una instancia particular.</para>
<para xml:lang="en">
A C++ lambda expression is a functor which can be implicitly converted to a
<classname>sigc::slot</classname> in the call to <methodname>connect()</methodname>.
A lambda expression can be used instead of <function>sigc::ptr_fun()</function>.
It's also possible to use a lambda expression instead of <function>sigc::mem_fun()</function>,
but then you won't get automatic disconnection of the signal handler when a
<classname>sigc::trackable</classname>-derived object goes out of scope.
</para>
<para>Aquí hay un ejemplo ligeramente más amplio de los «slots» en acción:</para>
<programlisting xml:lang="en"><code><![CDATA[
#include <gtkmm/button.h>
void on_button_clicked()
{
std::cout << "Hello World" << std::endl;
}
class some_class
{
public:
some_class
{
button.signal_clicked().connect(sigc::ptr_fun(&on_button_clicked));
button.signal_clicked().connect(sigc::mem_fun(*this, &some_class::on_button_clicked));
}
void on_button_clicked();
private:
Gtk::Button button {"Hello World"};
};
]]></code></programlisting>
<para>La primera llamada a <methodname>connect()</methodname> es igual a la que vio la última vez; no hay nada nuevo aquí.</para>
<para xml:lang="en">The next is more interesting.
<function>sigc::mem_fun()</function> is called with two arguments. The first
argument is <parameter>*this</parameter>, which is the object that our
new slot will be pointing at. The second argument is a pointer to one of its
methods. This particular version of <function>sigc::mem_fun()</function>
creates a slot which will, when "called", call the pointed-to method of the
specified object.
</para>
<para>Otra cosa notable acerca de este ejemplo es que se ha hecho la llamada a <methodname>connect()</methodname> dos veces para el mismo objeto de señal. Esto está perfectamente bien: cuando se pulse el botón, se llamará a ambos gestores de señales.</para>
<para>Como se mencionó, la señal <literal>clicked</literal> del botón está esperando llamar a un método sin argumentos. Todas las señales tienen requerimientos como este: no puede enganchar una función con dos argumentos a una señal que no espera ninguno (a menos que use un adaptador, como <function>sigc::bind()</function>, por supuesto). Por lo tanto, es importante saber qué tipo de gestor de señales se esperará que conecte a una señal dada.</para>
</section>
<section xml:id="sec-writing-signal-handlers">
<title>Escribir gestores de señales</title>
<para>Para descubrir qué tipo de gestor de señales puede conectar a una señal, puede buscarlo en la documentación de referencia en el archivo de cabecera. Aquí hay un ejemplo de una declaración de señal que puede ver en las cabeceras de <application>gtkmm</application>:</para>
<programlisting xml:lang="en"><code>Glib::SignalProxy<bool(Gtk::DirectionType)> signal_focus()
</code></programlisting>
<para xml:lang="en">
Other than the signal's name (<literal>focus</literal>), the template arguments are
important to note here. The first argument, <type>bool</type>, is the type that
the signal handler should return; and the type within parentheses,
<type>Gtk::DirectionType</type>, is the type of this signal's first, and only,
argument. By looking at the reference documentation, you can
see the names of the arguments too.
</para>
<para>Los mismos principios se aplican a las señales que tienen más argumentos. Aquí hay una con tres (tomada de <filename><gtkmm/textbuffer.h></filename>):</para>
<programlisting xml:lang="en"><code>Glib::SignalProxy<void(TextBuffer::iterator&, const Glib::ustrin&, int)> signal_insert();
</code></programlisting>
<para xml:lang="en">
It follows the same form. The first type is <type>void</type>, so that should be
our signal handler's return type.
The following three types are the argument types, in order. Our signal
handler's prototype could look like this:
</para>
<programlisting xml:lang="en"><code>void on_insert(TextBuffer::iterator& pos, const Glib::ustring& text, int bytes)
</code></programlisting>
</section>
<section xml:id="sec-disconnecting-signal-handlers">
<title>Desconectar gestores de señales</title>
<para>Eche otro vistazo al método <literal>connect</literal> de la señal:</para>
<programlisting xml:lang="en"><code>sigc::connection signal<void(int)>::connect(const sigc::slot<void(int)>&);
</code></programlisting>
<para xml:lang="en">
The returned <classname>sigc::connection</classname> can be used to control the
connection. By keeping a connection object you can disconnect its associated signal
handler using the <methodname>sigc::connection::disconnect()</methodname> method.
</para>
</section>
<section xml:id="sec-overriding-default-signal-handlers">
<title>Reemplazar gestores de señales predeterminados</title>
<para>Hasta ahora se la ha enseñado a realizar acciones en respuesta a pulsaciones de botones o eventos similares manejando señales. Esa es seguramente una buena manera de hacer las cosas, pero no es la única.</para>
<para>En lugar de conectar gestores de señales a señales laboriosamente, puede simplemente hacer una clase nueva que herede de un widget (por ejemplo, un botón) y luego reemplazar el gestor de señales predeterminado, como Button::on_clicked(). Esto puede ser mucho más simple que enganchar gestores de señales para todo.</para>
<para>La herencia no es siempre la mejor manera de realizar cosas. Sólo es útil cuando quiere que el widget maneje su propia señal por sí mismo. Si quiere que alguna otra clase maneje la señal entonces necesitará conectar un gestor separado. Esto es aún más cierto si quiere que varios objetos manejen la misma señal, o si quiere que un gestor de señales responda a la misma señal desde diferentes objetos.</para>
<para>Las clases de <application>gtkmm</application> se diseñaron con los reemplazos en mente; contienen métodos miembro virtuales específicamente pensados para reemplazarse.</para>
<para>Eche un vistazo a un ejemplo de reemplazo:</para>
<programlisting xml:lang="en"><code>#include <gtkmm/button.h>
class OverriddenButton : public Gtk::Button
{
protected:
void on_clicked() override;
}
void OverriddenButton::on_clicked()
{
std::cout << "Hello World" << std::endl;
// call the base class's version of the method:
Gtk::Button::on_clicked();
}
</code></programlisting>
<para>Aquí se define una clase nueva llamada <classname>OverridenButton</classname>, que hereda de <classname>Gtk::Button</classname>. Lo único que se cambia es el método <methodname>on_clicked()</methodname>, que se llama siempre que <classname>Gtk::Button</classname> emite la señal <literal>clicked</literal>. Este método imprime «Hello World» a <literal>stdout</literal> y después llama al método original, reemplazado, para dejarle a <classname>Gtk::Button</classname> hacer lo que hubiera hecho si no se hubiera reemplazado.</para>
<para xml:lang="en">
You don't always need to call the parent's method; there are times
when you might not want to. Note that we called the parent method
<emphasis>after</emphasis> writing "Hello World", but we could have called it before.
In this simple example, it hardly matters much, but there are times
when it will. With connected signal handlers, it's not quite so easy to change details
like this, and you can do something here which you can't do at all
with connected signal handlers: you can call the parent method in the <emphasis>middle</emphasis> of
your custom code.
</para>
</section>
<section xml:id="sec-binding-extra-arguments">
<title>Enlazar argumentos adicionales</title>
<para xml:lang="en">
If you use one signal handler to catch the same signal from several widgets,
you might like that signal handler to receive some extra information. For
instance, you might want to know which button was clicked. You can do this with
<function>sigc::bind()</function>. Here's some code from the <link linkend="sec-helloworld2">helloworld2</link> example.
</para>
<programlisting xml:lang="en"><code>m_button1.signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &HelloWorld::on_button_clicked), "button 1"));
</code></programlisting>
<para xml:lang="en">This says that we want the signal to send an extra
<classname>Glib::ustring</classname> argument to the signal handler, and that
the value of that argument should be "button 1". Of course we will need to add
that extra argument to the declaration of our signal handler:
</para>
<programlisting xml:lang="en"><code>void on_button_clicked(const Glib::ustring& data);
</code></programlisting>
<para xml:lang="en">Of course, a normal "clicked" signal handler would have no arguments.
</para>
<para xml:lang="en">
<function>sigc::bind()</function> is not commonly used, but you might find it
helpful sometimes. If you are familiar with <application>GTK</application>
programming then you have probably noticed that this is similar to the extra
<literal>gpointer data</literal> arguments which all GTK callbacks have. This
is generally overused in <application>GTK</application> to pass information
that should be stored as member data in a derived widget, but widget derivation
is very difficult in C. We have far less need of this hack in <application>gtkmm</application>.
</para>
</section>
<section xml:id="sec-eventsignals">
<title xml:lang="en">Event signals</title>
<para xml:lang="en">
Event signals are emitted as a result of some user input, for instance a key press
or a mouse motion. Usually you don't handle these events directly. Instead, you use
a subclass of <classname>Gtk::EventController</classname>, such as <classname>Gtk::EventControllerKey</classname>
or <classname>Gtk::GestureClick</classname>. Event controllers can be added to a
widget with <methodname>Gtk::Widget::add_controller()</methodname>.
</para>
<para xml:lang="en">
You might occasionally find it useful to handle events when there's something
you can't accomplish with normal signals. <classname>Gtk::Button</classname>,
for example, does not send mouse-pointer coordinates with its
<literal>clicked</literal> signal, but you could handle
<methodname>Gtk::GestureClick::signal_pressed()</methodname> if you needed this
information. <methodname>Gtk::EventControllerKey::signal_key_pressed()</methodname>
is often used to handle key-presses.
</para>
<para xml:lang="en">
Some event controller signals behave slightly differently. The value returned from
the signal handler indicates whether it has fully "handled" the event. If the value
is <literal>false</literal> then <application>gtkmm</application> will pass the event on to the next
signal handler. If the value is <literal>true</literal> then no other signal handlers
will need to be called.
</para>
<para xml:lang="en">
Handling an event doesn't affect the Widget's other signals. If you handle
<methodname>Gtk::GestureClick::signal_pressed()</methodname> for
<classname>Gtk::Button</classname>, you'll still be able to get the
<literal>clicked</literal> signal. They are emitted at (nearly) the same time.
</para>
<para xml:lang="en">
Here's a simple example:</para>
<programlisting xml:lang="en"><code>void on_button_press(int n_press, double x, double y);
Gtk::Button button("label");
auto controller = Gtk::GestureClick::create();
controller->set_propagation_phase(Gtk::PropagationPhase::CAPTURE);
controller->signal_pressed().connect(sigc::ptr_fun(&on_button_press));
button.add_controller(controller);
</code></programlisting>
<para>Cuando el ratón esté sobre el botón y el botón del ratón se presione, se llamará a <methodname>on_button_press()</methodname>.</para>
<para xml:lang="en">
The call to <methodname>set_propagation_phase()</methodname> is necessary in
this case because the <classname>GtkButton</classname> C class adds an event
controller, handling button clicks in the capture phase. <classname>GtkButton</classname>
claims the event, meaning that the event is not propagated in the bubble phase,
where event controllers handle events by default.
</para>
<section xml:id="signal-handler-sequence">
<title>Secuencia de los gestores de señales</title>
<para xml:lang="en">By default, signal handlers that return <type>void</type> are called after
any previously-connected signal handlers. However, this can be a problem with
event signals that can stop event propagation by returning <literal>true</literal>.
For instance, the existing signal handlers, or the default signal handler, might return
<literal>true</literal> to stop other signal handlers from being called.
To specify that your signal handler should be called before the other signal handlers,
you can specify <literal>false</literal> for the <literal>after</literal> parameter.
This <methodname>connect()</methodname> parameter is optional, if the signal handler
returns <type>void</type>. For instance,
</para>
<programlisting xml:lang="en"><code>key_controller->signal_key_pressed().connect(sigc::ptr_fun(&on_mywindow_key_pressed), false);
</code></programlisting>
<para xml:lang="en">The event is propagated between widgets in 3 phases.
<orderedlist inheritnum="ignore" continuation="restarts">
<listitem><simpara xml:lang="en">Capture phase - runs from the toplevel down to the event widget.</simpara></listitem>
<listitem><simpara xml:lang="en">Target phase - runs only on the event widget.</simpara></listitem>
<listitem><simpara xml:lang="en">Bubble phase - runs from the event widget up to the toplevel.</simpara></listitem>
</orderedlist>
</para>
<para xml:lang="en">
The <link xlink:href="https://docs.gtk.org/gtk4/input-handling.html">Input Handling</link>
chapter in the GTK documentation describes user input handling in more detail.
</para>
</section>
</section>
<section xml:id="sec-exceptions-in-signal-handlers">
<title>Excepciones en gestores de señales</title>
<para>Cuando se aborta un programa por una excepción de C++ no manejada, a veces es posible usar un depurador para encontrar el lugar en el que la excepción se lanzó. Esto es más difícil de lo normal si un gestor de señales lanzó la excepción.</para>
<para xml:lang="en">
This section describes primarily what you can expect on a Linux system, when you
use <link xlink:href="http://www.gnu.org/software/gdb/">the gdb debugger</link>.
</para>
<para xml:lang="en">
First, let's look at a simple example where an exception is thrown from a normal
function (no signal handler).
</para>
<programlisting xml:lang="en"><code>// without_signal.cc
#include <gtkmm.h>
bool throwSomething()
{
throw "Something";
return true;
}
int main(int argc, char** argv)
{
throwSomething();
auto app = Gtk::Application::create("org.gtkmm.without_signal");
return app->run();
}
</code></programlisting>
<para xml:lang="en">
Here is an excerpt from a <application>gdb</application> session. Only the most
interesting parts of the output are shown.
</para>
<programlisting xml:lang="en"><code>> gdb without_signal
(gdb) run
terminate called after throwing an instance of 'char const*'
Program received signal SIGABRT, Aborted.
(gdb) backtrace
#7 0x08048864 in throwSomething () at without_signal.cc:6
#8 0x0804887d in main (argc=1, argv=0xbfffecd4) at without_signal.cc:12
</code></programlisting>
<para xml:lang="en">You can see that the exception was thrown from <filename>without_signal.cc</filename>,
line 6 (<code>throw "Something";</code>).
</para>
<para xml:lang="en">
Now let's see what happens when an exception is thrown from a signal handler.
Here's the source code.
</para>
<programlisting xml:lang="en"><code>// with_signal.cc
#include <gtkmm.h>
bool throwSomething()
{
throw "Something";
return true;
}
int main(int argc, char** argv)
{
Glib::signal_timeout().connect(sigc::ptr_fun(throwSomething), 500);
auto app = Gtk::Application::create("org.gtkmm.with_signal");
app->hold();
return app->run();
}
</code></programlisting>
<para xml:lang="en">
And here's an excerpt from a <application>gdb</application> session.
</para>
<programlisting xml:lang="en"><code>> gdb with_signal
(gdb) run
(with_signal:2703): glibmm-ERROR **:
unhandled exception (type unknown) in signal handler
Program received signal SIGTRAP, Trace/breakpoint trap.
(gdb) backtrace
#2 0x0063c6ab in glibmm_unexpected_exception () at exceptionhandler.cc:77
#3 Glib::exception_handlers_invoke () at exceptionhandler.cc:150
#4 0x0063d370 in glibmm_source_callback (data=0x804d620) at main.cc:212
#13 0x002e1b31 in Gtk::Application::run (this=0x804f300) at application.cc:178
#14 0x08048ccc in main (argc=1, argv=0xbfffecd4) at with_signal.cc:16
</code></programlisting>
<para xml:lang="en">The exception is caught in <application>glibmm</application>, and the program
ends with a call to <function>g_error()</function>. Other exceptions may result
in different behavior, but in any case the exception from a signal handler is
caught in <application>glibmm</application> or <application>gtkmm</application>, and
<application>gdb</application> can't see where it was thrown.
</para>
<para xml:lang="en">
To see where the exception is thrown, you can use the <application>gdb</application>
command <userinput>catch throw</userinput>.
</para>
<programlisting xml:lang="en"><code>> gdb with_signal
(gdb) catch throw
Catchpoint 1 (throw)
(gdb) run
Catchpoint 1 (exception thrown), 0x00714ff0 in __cxa_throw ()
(gdb) backtrace
#0 0x00714ff0 in __cxa_throw () from /usr/lib/i386-linux-gnu/libstdc++.so.6
#1 0x08048bd4 in throwSomething () at with_signal.cc:6
(gdb) continue
Continuing.
(with_signal:2375): glibmm-ERROR **
unhandled exception (type unknown) in signal handler
Program received signal SIGTRAP, Trace/breakpoint trap.
</code></programlisting>
<para xml:lang="en">
If there are many caught exceptions before the interesting uncaught one, this
method can be tedious. It can be automated with the following
<application>gdb</application> commands.
</para>
<programlisting xml:lang="en"><code>(gdb) catch throw
(gdb) commands
(gdb) backtrace
(gdb) continue
(gdb) end
(gdb) set pagination off
(gdb) run
</code></programlisting>
<para xml:lang="en">These commands will print a backtrace from each <code>throw</code> and continue.
The backtrace from the last (or possibly the last but one) <code>throw</code>
before the program stops, is the interesting one.
</para>
</section>
</appendix>
<appendix xml:id="chapter-custom-signals">
<title>Crear sus propias señales</title>
<para>Ahora que ha visto señales y gestores de señales en <application>gtkmm</application>, tal vez quiera usar la misma técnica para permitir interacción entre sus propias clases. Eso es realmente muy simple usando la biblioteca <application>libsigc++</application> directamente.</para>
<para xml:lang="en">
This isn't purely a <application>gtkmm</application> or GUI issue. <application>gtkmm</application> uses
<application>libsigc++</application> to implement its proxy wrappers for the
<application>GTK</application> signal system, but for new,
non-GTK signals, you can create pure C++ signals, using the
<classname>sigc::signal<></classname> template.
</para>
<para xml:lang="en">
For instance, to create a signal that sends 2 parameters, a <type>bool</type>
and an <type>int</type>, just declare a <classname>sigc::signal</classname>,
like so:
</para>
<programlisting xml:lang="en"><code>sigc::signal<void(bool, int)> signal_something;
</code></programlisting>
<para xml:lang="en">
You could just declare that signal as a public member variable, but
some people find that distasteful and prefer to make it available via
an accessor method, like so:
</para>
<programlisting xml:lang="en"><code>class Server
{
public:
//signal accessor:
using type_signal_something = sigc::signal<void(bool, int)>;
type_signal_something signal_something();
protected:
type_signal_something m_signal_something;
};
Server::type_signal_something Server::signal_something()
{
return m_signal_something;
}
</code></programlisting>
<para xml:lang="en">
You can then connect to the signal using the same syntax used when
connecting to <application>gtkmm</application> signals. For instance,
</para>
<programlisting xml:lang="en"><code>server.signal_something().connect(
sigc::mem_fun(client, &Client::on_server_something) );
</code></programlisting>
<section xml:id="chapter-custom-signals-example">
<title>Ejemplo</title>
<para>Este es un ejemplo funcional completo que define y usa señales personalizadas.</para>
<para xml:lang="en"><link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm-documentation/tree/master/examples/book/signals/custom/">Source Code</link></para>
<!-- start inserted example code -->
<para xml:lang="en">File: <filename>client.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLE_CLIENT_H
#define GTKMM_EXAMPLE_CLIENT_H
#include <sigc++/sigc++.h>
//Client must inherit from sigc::trackable.
//because libsigc++ needs to keep track of the lifetime of signal handlers.
class Client : public sigc::trackable
{
public:
Client();
virtual ~Client();
//Signal handler:
void on_server_something(bool a, int b);
};
#endif //GTKMM_EXAMPLE_CLIENT_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>server.h</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#ifndef GTKMM_EXAMPLE_SERVER_H
#define GTKMM_EXAMPLE_SERVER_H
#include <sigc++/sigc++.h>
class Server
{
public:
Server();
virtual ~Server();
void do_something();
//signal accessor:
using type_signal_something = sigc::signal<void(bool, int)>;
type_signal_something signal_something();
protected:
type_signal_something m_signal_something;
};
#endif //GTKMM_EXAMPLE_SERVER_H
]]></code></programlisting>
<para xml:lang="en">File: <filename>client.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "client.h"
#include <iostream>
Client::Client()
{
}
Client::~Client()
{
}
void Client::on_server_something(bool a, int b)
{
std::cout << "Client::on_server_something() called with these parameters: "
<< a << ", " << b << std::endl;
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>main.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "server.h"
#include "client.h"
#include <iostream>
int main(int, char**)
{
Server server;
Client client;
//Connect a Server signal to the signal handler in Client.
server.signal_something().connect(sigc::mem_fun(client,
&Client::on_server_something) );
std::cout << "Before Server::do_something()" << std::endl;
//Tell the server to do something that will eventually cause it to emit the
//"something" signal.
server.do_something(); // Client::on_server_something() will run before
// Server::do_something() has completed.
std::cout << "After Server::do_something()" << std::endl;
return 0;
}
]]></code></programlisting>
<para xml:lang="en">File: <filename>server.cc</filename> (For use with gtkmm 4)</para>
<programlisting xml:lang="en"><code><![CDATA[#include "server.h"
#include <iostream>
Server::Server()
{
}
Server::~Server()
{
}
Server::type_signal_something Server::signal_something()
{
return m_signal_something;
}
void Server::do_something()
{
m_signal_something.emit(false, 5);
}
]]></code></programlisting>
<!-- end inserted example code -->
</section>
</appendix>
<appendix xml:id="sec-signals-comparison">
<title>Comparación con otros sistemas de señales</title>
<para xml:lang="en">
<!-- TODO: Rewrite this paragraph and talk about Qt's moc. -->
(An aside: <application>GTK</application> calls this scheme "signalling"; the
sharp-eyed reader with GUI toolkit experience will note that this same design
is often
seen under the name of "broadcaster-listener" (e.g., in Metrowerks'
PowerPlant framework for the Macintosh). It works in much the same
way: one sets up <literal>broadcasters</literal>, and then connects
<literal>listeners</literal> to them; the broadcaster keeps a list of the
objects listening to it, and when someone gives the broadcaster a
message, it calls all of its objects in its list with the message. In
<application>gtkmm</application>, signal objects play the role of broadcasters, and slots
play the role of listeners - sort of. More on this later.)
</para>
<para xml:lang="en">
<application>gtkmm</application> signal handlers are strongly-typed, whereas
<application>GTK</application> C code allows you to connect a callback with
the wrong number and type of arguments, leading to a segfault at runtime. And,
unlike <application>Qt</application>, <application>gtkmm</application> achieves this without modifying
the C++ language.</para>
<para xml:lang="en">
Re. Overriding signal handlers: You can do this in the straight-C world of GTK too; that's what GTK's
object system is for. But in GTK, you have to go through some
complicated procedures to get object-oriented features like
inheritance and overloading. In C++, it's simple, since those
features are supported in the language itself; you can let the
compiler do the dirty work.
</para>
<para xml:lang="en">
This is one of the places where the beauty of C++ really comes out.
One wouldn't think of subclassing a GTK widget simply to override its
action method; it's just too much trouble. In GTK, you almost always
use signals to get things done, unless you're writing a new widget.
But because overriding methods is so easy in C++, it's entirely
practical - and sensible - to subclass a button for that purpose.
</para>
</appendix>
<appendix xml:id="sec-windows-installation">
<title><application>gtkmm</application> y Win32</title>
<para>Una de las mayores ventajas de <application>gtkmm</application> es que es multiplataforma. Los programas de <application>gtkmm</application> escritos en otras plataformas como GNU/Linux generalmente pueden transferirse a Windows (y viceversa) con pocas modificaciones al código fuente.</para>
<para xml:lang="en">
<application>gtkmm</application> currently works with the
<link xlink:href="http://mingw.org/">MinGW/GCC compiler</link> with a compiler version
that supports C++17, such as gcc 7 or 8. It also works with Microsoft
Visual C++ 2017 15.7.x or later (including the freely available express/community
editions) on the Windows platform. There is an
<link xlink:href="ftp://ftp.gnome.org/pub/GNOME/binaries/win32/gtkmm">installer</link>
available for <application>gtkmm</application> on Microsoft Windows, but as of this writing
(October 2020) it has not been updated for a long time.
Please be aware that although normally it is fine to mix builds done with
Visual Studio 2017 and 2019, please do not do so when building
<application>gtkmm</application> with its -mm dependencies.
</para>
<para xml:lang="en">Refer to the <link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm/tree/master/README.win32">README.win32</link>,
as well as the <link xlink:href="https://gitlab.gnome.org/GNOME/gtkmm/tree/master/MSVC_NMake/README">README</link>
files in the <application>gtkmm</application>, pangomm and glibmm for instructions on how to build <application>gtkmm</application> on Windows.
</para>
</appendix>
<appendix xml:id="chapter-working-with-source">
<title>Trabajar con el código fuente de gtkmm</title>
<para>Si está interesado o interesada en ayudar con el desarrollo de <application>gtkmm</application>, o arreglar un error en <application>gtkmm</application>, probablemente necesite construir la versión de desarrollo de <application>gtkmm</application>. Sin embargo, no debe instalar una versión de desarrollo sobre su versión estable. En su lugar, debe instalarla junto a su instalación de <application>gtkmm</application> existente, en una ruta separada.</para>
<para xml:lang="en">
The easiest way to do this is using <link xlink:href="https://wiki.gnome.org/Projects/Jhbuild">jhbuild</link>.
<application>jhbuild</application> is a program that makes building GNOME
software much easier by calculating dependencies and building things in the
correct order. This section will give a brief explanation of how to set up
<application>jhbuild</application> to build and install <application>gtkmm</application> from the
source repository (git). For up-to-date information
on <application>jhbuild</application>, please refer to the <link xlink:href="https://gnome.pages.gitlab.gnome.org/jhbuild/">jhbuild manual</link>.
</para>
<note>
<para xml:lang="en">
Note that to build <application>gtkmm</application> from git, you'll often need to build many of its
dependencies from git as well. <application>jhbuild</application> makes
this easier than it would normally be, but it will take quite a while to
build and install them all. You will probably encounter build problems,
though these will usually be corrected quickly if you report them.
</para>
</note>
<para xml:lang="en">
<application>gnome-build-meta</application> is an alternative to
<application>jhbuild</application>. It is described at the
<link xlink:href="https://wiki.gnome.org/Newcomers/BuildSystemComponent">Building system components</link>
wiki page, but here we concentrate on <application>jhbuild</application>.
</para>
<section xml:id="sec-setting-up-jhbuild">
<title>Configurar JHBuild</title>
<para xml:lang="en">
To set up <application>jhbuild</application>, follow the basic
installation instructions from the <link xlink:href="https://gnome.pages.gitlab.gnome.org/jhbuild/">jhbuild manual</link>.
After you have installed <application>jhbuild</application>, you
should copy the sample <application>jhbuild</application> configuration
file into your home directory by executing the following command from the
<application>jhbuild</application> directory:
<screen xml:lang="en">$ cp examples/sample.jhbuildrc ~/.config/jhbuildrc</screen>
</para>
<para xml:lang="en">
The <application>gtkmm</application> module is defined in the
<filename>gnome-suites-core-deps-latest.modules</filename> moduleset. So edit your
<filename>jhbuildrc</filename> file and set your moduleset setting like so:
</para>
<programlisting xml:lang="en"><code>moduleset = 'gnome-suites-core-deps-latest'</code></programlisting>
<para xml:lang="en">
After setting the correct moduleset, you need to tell
<application>jhbuild</application> which module or modules to build. To
build <application>gtkmm</application> and all of its dependencies, set <varname>modules</varname>
like so:
</para>
<programlisting xml:lang="en"><code>modules = [ 'gtkmm' ]</code></programlisting>
<para>Puede construir varios módulos estableciendo la variable <varname>modules</varname> a un metapaquete, como por ejemplo <literal>meta-gnome-core</literal>, o listando más de un nombre de módulo. La variable <varname>modules</varname> especifica qué módulos se construirán cuando no especifique explícitamente nada en la línea de comandos. Puede construir un conjunto de módulos diferente más tarde especificándolo en la línea de comandos (por ejemplo, <command>jhbuild build gtkmm</command>).</para>
<important>
<title>Establecer un prefijo</title>
<para xml:lang="en">
By default, <application>jhbuild</application>'s configuration is
configured to install all software built with
<application>jhbuild</application> under the
<filename>~/jhbuild/install</filename> prefix. You can choose a different
prefix, but it is recommended that you keep this prefix different from
other software that you've installed (don't set it to
<filename>/usr</filename>!) If you've followed the jhbuild instructions
then this prefix belongs to your user, so you don't need to run jhbuild
as <literal>root</literal>.
</para>
</important>
<para xml:lang="en">
You should also set <varname>buildroot</varname> in <filename>jhbuildrc</filename>.
<application>jhbuild</application> builds <application>gtkmm</application> and many of its dependencies
with Meson. Meson does not allow building in the source tree.
<application>jhbuild</application>'s default action is to build in a
<filename>build</filename> directory directly below the source root directory.
Some modules have a <filename>build</filename> directory with files
used when building with Autotools. Those files can be destroyed if you
let <application>jhbuild</application> build in that directory.
</para>
<para xml:lang="en">
When you downloaded <application>jhbuild</application> from the git repository,
you got a number of <filename>.modules</filename> files, specifying
dependencies between modules. By default <application>jhbuild</application>
does not use the downloaded versions of these files, but reads the
latest versions in the git repository. This is usually what you want.
If you don't want it, use the <varname>use_local_modulesets</varname>
variable in <filename>jhbuildrc</filename>.
</para>
</section>
<section xml:id="sec-installing-jhbuild">
<title>Instalar y utilizar la versión de git de <application>gtkmm</application></title>
<para xml:lang="en">
Once you've configured <application>jhbuild</application> as described
above, building <application>gtkmm</application> should be relatively straightforward. The first
time you run <application>jhbuild</application>, you should run the
following sequence of commands to ensure that
<application>jhbuild</application> has the required tools and verify that
it is set up correctly:
<screen xml:lang="en">$ jhbuild bootstrap
$ jhbuild sanitycheck</screen>
</para>
<section xml:id="jhbuild-installing-gtkmm">
<title>Instalar <application>gtkmm</application> con <application>jhbuild</application></title>
<para>Si todo funcionó correctamente, podrá construir <application>gtkmm</application> y a todas sus dependencias desde git ejecutando <command>jhbuild build</command> (o, si no especificó <application>gtkmm</application> en la variable <varname>modules</varname>, con el comando <command>jhbuild build gtkmm</command>).</para>
<para>Este comando construirá e instalará una serie de módulos y probablemente tarde mucho tiempo la primera vez. Después de la primera vez, sin embargo, debería ir mucho más rápido dado que sólo tendrá que reconstruir los archivos que han cambiado desde la última vez. Alternativamente, después de haber construido e instalado <application>gtkmm</application> por primera vez, lo podrá reconstruir por sí mismo (sin reconstruir todas sus dependencias) con el comando <command>jhbuild buildone gtkmm</command>.</para>
</section>
<section xml:id="jhbuild-using-gtkmm">
<title>Usar la versión de git de <application>gtkmm</application></title>
<para xml:lang="en">
After you've installed the git version of <application>gtkmm</application>, you're ready to start
using and experimenting with it. In order to use the new version of
<application>gtkmm</application> you've just installed, you need to set some environment
variables so that your <filename>configure</filename> or <filename>meson.build</filename>
script knows where to find the new libraries. Fortunately,
<application>jhbuild</application> offers an easy solution to this
problem. Executing the command <command>jhbuild shell</command> will
start a new shell with all of the correct environment variables set.
Now if you re-configure and build your project just as you usually do,
it should link against the newly installed libraries. To return to your
previous environment, simply exit the <application>jhbuild</application>
shell.
</para>
<para>Una vez que haya construido su software, también necesitará ejecutar su programa dentro del entorno de jhbuild. Para hacerlo, puede usar de nuevo el comando <command>jhbuild shell</command> para arrancar un intérprete nuevo con el entorno de <application>jhbuild</application> configurado. Alternativamente, puede ejecutar un solo comando en el entorno de <application>jhbuild</application> usando el siguiente comando: <command>jhbuild run nombre-de-comando</command>. En este caso, el comando se ejecutará con las variables de entorno correctas establecidas, pero retornará a su entorno previo después de que el programa salga.</para>
</section>
</section>
</appendix>
<appendix xml:id="chapter-wrapping-c-libraries">
<title>Envolver bibliotecas C con gmmproc</title>
<para><application>gtkmm</application> usa la herramienta <command>gmmproc</command> para generar la mayor parte de su código fuente, usando archivos .defs que definen las API de las bibliotecas basadas en <classname>GObject</classname>. Es por esto que es bastante fácil crear envoltorios con el estilo de gtkmm adicionales de otras bibliotecas basadas en glib/GObject.</para>
<para>Esto involucra una variedad de herramientas, algunas de ellas obsoletas, pero que al menos funcionan, y que varios proyectos han usado exitosamente.</para>
<section xml:id="sec-wrapping-build-structure">
<title>La estructura de construcción</title>
<para xml:lang="en">Generation of the source code for a gtkmm-style wrapper API requires use
of tools such as <command>gmmproc</command> and
<filename>generate_wrap_init.pl</filename>, which are included in
<application>glibmm</application>. In theory you could write your
own build files to use these appropriately, but a much better option is to
make use of the build infrastructure provided by the <application>mm-common</application>
module. To get started, it helps a lot to pick an existing binding module as an example
to look at.</para>
<para xml:lang="en">For instance, let's pretend that we are wrapping a C library called
libsomething. It provides a <classname>GObject</classname>-based API with
types named, for instance, <classname>SomeWidget</classname> and
<classname>SomeStuff</classname>.</para>
<section xml:id="copying-skeleton-project">
<title>Copiar el esqueleto del proyecto</title>
<para xml:lang="en">Typically our wrapper library would be called libsomethingmm. We can start by
copying the <link xlink:href="https://gitlab.gnome.org/GNOME/mm-common/tree/master/skeletonmm">
skeleton source tree</link> from the <application>mm-common</application> module.
Starting with <application>mm-common</application> 1.0.0 this skeleton application
is built with the <link xlink:href="https://mesonbuild.com/">Meson build system</link>.
</para>
<programlisting xml:lang="en"><code> $ git clone https://gitlab.gnome.org/GNOME/mm-common.git
$ cp -a mm-common/skeletonmm libsomethingmm
</code></programlisting>
<para xml:lang="en">This provides a directory structure for the source .hg and .ccg files and the hand-written
.h and .cc files, with <filename>meson.build</filename> files that can specify the
various files in use, in terms of Meson variables. The directory structure usually
looks like this, after we have renamed the directories appropriately:
<itemizedlist>
<listitem><para xml:lang="en"><filename>libsomethingmm</filename>: The top-level directory.</para>
<itemizedlist>
<listitem><para xml:lang="en"><filename>libsomething</filename>: Contains the main include file and the pkg-config .pc file.</para>
<itemizedlist>
<listitem><para xml:lang="en"><filename>src</filename>: Contains .hg and .ccg source files.</para></listitem>
<listitem><para xml:lang="en"><filename>libsomethingmm</filename>: Contains hand-written .h and .cc files.</para>
</listitem>
</itemizedlist>
</listitem>
</itemizedlist>
</listitem>
</itemizedlist>
</para>
<para xml:lang="en">As well as renaming the directories, we should rename some of the source
files. For instance:</para>
<programlisting xml:lang="en"><code>$ for f in $(find libsomethingmm -depth -name '*skeleton*'); do \
d="${f%/*}"; b="${f##*/}"; mv "$f" "$d/${b//skeleton/libsomething}"; \
done
</code></programlisting>
<para xml:lang="en">A number of the skeleton files must still be filled in with project-specific content later.
</para>
<para>Tenga en cuenta que los archivos que terminan en <filename>.in</filename> se utilizan para generar archivos con el mismo nombre pero sin el sufijo <filename>.in</filename>, mediante la sustitución de algunas variables con valores reales durante la fase de configuración.</para>
<para xml:lang="en">Generated files are saved in the build tree, which is separated from the
source tree when <command>meson</command> and <command>ninja</command> are used.</para>
</section>
<section xml:id="modifying-build-files">
<title>Modificar archivos de construcción</title>
<para xml:lang="en">Now we edit the files to adapt them to our needs. You might prefer to use a multiple-file
search-replace utility for this, such as <command>regexxer</command>. Note that nearly all of the
files provided with the skeleton source tree contain placeholder text. Thus, the substitutions
should be performed globally, and not be limited to the Meson files.</para>
<para>Todas las menciones de <varname>skeleton</varname> deben reemplazarse por el nombre correcto de la biblioteca de C que está envolviendo, como «something» o «libsomething». De la misma manera, todas las instancias de <varname>SKELETON</varname> deben reemplazarse por «SOMETHING» o «LIBSOMETHING», y todas las apariciones de <varname>Skeleton</varname> deben cambiarse por «Something».</para>
<para>De la misma manera, reemplace todas las instancias de <varname>Joe Hacker</varname> por el nombre del titular de los derechos de autor, quien probablemente sea usted. Haga lo mismo para la dirección de correo-e <varname>[email protected]</varname>.</para>
<section xml:id="modifying-top-meson.build">
<title xml:lang="en">meson.build in the top-level directory</title>
<para>
<itemizedlist>
<listitem><para>Es común cuando se enlazan módulos rastrear el número de versión de la biblioteca que se está envolviendo. Entonces, por ejemplo, si la biblioteca de C está en una versión 1.23.4, el número de versión inicial del módulo de enlace sería 1.23.0. Sin embargo, evite comenzar con un número de versión menor par, ya que generalmente indica una versión estable.</para></listitem>
<listitem><para xml:lang="en">In the <function>project()</function> function, change the
license and the C++ version, if necessary.</para></listitem>
<listitem><para xml:lang="en">You probably need to add more required modules than
<application>glibmm</application> and <application>skeleton</application>
(<application>libsomething</application>).</para></listitem>
</itemizedlist>
</para>
</section>
<section xml:id="modifying-other-meson.build">
<title xml:lang="en">Other meson.build files</title>
<para xml:lang="en">Next we must adapt the other <filename>meson.build</filename> files:
<itemizedlist>
<listitem><para xml:lang="en"><filename>skeleton/meson.build</filename>: Perhaps not much
to change here more than the global name substitutions.</para>
</listitem>
<listitem><para xml:lang="en"><filename>skeleton/skeletonmm/meson.build</filename></para>
<variablelist>
<varlistentry>
<term xml:lang="en"><varname>defs_basefiles</varname></term>
<listitem><para xml:lang="en">If we have more .defs and docs.xml files,
we add them here.</para></listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en"><varname>hg_ccg_basenames</varname></term>
<listitem><para xml:lang="en">We must mention all of our <filename>.hg</filename> and
<filename>.ccg</filename> files here.</para></listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en"><varname>extra_cc_files, extra_h_files</varname></term>
<listitem><para xml:lang="en">Any additional hand-written <filename>.h</filename> and
<filename>.cc</filename> source files go here.</para></listitem>
</varlistentry>
</variablelist>
</listitem>
</itemizedlist>
</para>
</section>
<section xml:id="creating-hg-ccg">
<title>Crear archivos .hg y .ccg</title>
<para>Ahora se deben crear los primeros archivos <filename>.hg</filename> y <filename>.ccg</filename>, para envolver uno de los objetos en la biblioteca de C. Ya existen un par de archivos de fuentes de ejemplo: <filename>skeleton.ccg</filename> y <filename>skeleton.hg</filename>. Cree copias de estos archivos si es necesario.</para>
<para>En la sección <link linkend="sec-wrapping-hg-files">archivos .hg y .ccg</link> puede aprender acerca de la sintaxis usada en estos archivos.</para>
</section>
</section>
</section>
<section xml:id="sec-wrapping-defs-files">
<title>Generar los archivos .defs.</title>
<para xml:lang="en">The <filename>.defs</filename> files are text files, in a lisp format, that describe the API
of a C library, including its
<itemizedlist>
<listitem><para xml:lang="en">objects (GObjects, widgets, interfaces, boxed-types and plain structs)</para></listitem>
<listitem><para xml:lang="en">functions</para></listitem>
<listitem><para xml:lang="en">enums</para></listitem>
<listitem><para xml:lang="en">signals</para></listitem>
<listitem><para xml:lang="en">properties</para></listitem>
<listitem><para xml:lang="en">vfuncs</para></listitem>
</itemizedlist>
</para>
<para xml:lang="en">At the moment, we have separate tools for generating different parts of
these <filename>.defs</filename>, so we split them up into separate files.
For instance, in the <filename>gtk/src</filename> directory of the <application>gtkmm</application>
sources, you will find these files:
<variablelist>
<varlistentry>
<term xml:lang="en"><filename>gtk.defs</filename></term>
<listitem><para xml:lang="en">Includes the other files.</para></listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en"><filename>gtk_methods.defs</filename></term>
<listitem><para xml:lang="en">Objects and functions.</para></listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en"><filename>gtk_enums.defs</filename></term>
<listitem><para xml:lang="en">Enumerations.</para></listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en"><filename>gtk_signals.defs</filename></term>
<listitem><para xml:lang="en">Signals and properties.</para></listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en"><filename>gtk_vfuncs.defs</filename></term>
<listitem><para xml:lang="en">vfuncs (function pointer member fields in structs), written by hand.</para></listitem>
</varlistentry>
</variablelist>
</para>
<para xml:lang="en">The <filename>skeletonmm/tools/generate_defs_and_docs.sh</filename> script
generates all <filename>.defs</filename> files and the <filename>*_docs.xml</filename> file,
described in the <link linkend="sec-wrapping-documentation">Documentation</link> section.
</para>
<section xml:id="generating-defs-methods">
<title>Generar los .defs de métodos</title>
<para xml:lang="en">This <filename>.defs</filename> file describes objects and their functions.
It is generated by the <command>h2def.py</command> script which you can find in
glibmm's <filename>tools/defs_gen</filename> directory. For instance,
</para>
<programlisting xml:lang="en"><code>$ ./h2def.py /usr/include/gtk-4.0/gtk/*.h > gtk_methods.defs
</code></programlisting>
</section>
<section xml:id="generating-defs-enums">
<title>Generar los .defs de enumeraciones</title>
<para xml:lang="en">This <filename>.defs</filename> file describes enum types and their possible
values. It is generated by the <filename>enumextract.py</filename> script which you can
also find in glibmm's <filename>tools/defs_gen</filename> directory. For instance,
</para>
<programlisting xml:lang="en"><code>$ ./enumextract.py /usr/include/gtk-4.0/gtk/*.h > gtk_enums.defs
</code></programlisting>
</section>
<section xml:id="generating-defs-signals-properties">
<title>Generar los .defs de señales y propiedades</title>
<para xml:lang="en">This <filename>.defs</filename> file describes signals and properties. It is
generated by the special <filename>generate_extra_defs</filename> utility that is in every
wrapping project, such as <filename>gtkmm/tools/extra_defs_gen/</filename>.
For instance
</para>
<programlisting xml:lang="en"><code>$ cd tools/extra_defs_gen
$ ./generate_extra_defs > gtk_signals.defs
</code></programlisting>
<para xml:lang="en">You must edit the source code of your own <filename>generate_extra_defs</filename> tool
in order to generate the <filename>.defs</filename> for the GObject C types that you wish to
wrap. In the skeleton source tree, the source file is named
<filename>tools/extra_defs_gen/generate_defs_skeleton.cc</filename>. If not done so
already, the file should be renamed, with the basename of your new binding substituted
for the <varname>skeleton</varname> placeholder. The <filename>tools/extra_defs_gen/meson.build</filename>
file should also mention the new source filename.</para>
<para xml:lang="en">Then edit the <filename>.cc</filename> file to specify the correct types.
For instance, your <function>main()</function> function might look like this:
</para>
<programlisting xml:lang="en"><code>#include <glibmm_generate_extra_defs/generate_extra_defs.h>
#include <libsomething.h>
#include <iostream>
int main(int, char**)
{
something_init();
std::cout << get_defs(SOME_TYPE_WIDGET)
<< get_defs(SOME_TYPE_STUFF);
return 0;
}
</code></programlisting>
</section>
<section xml:id="writing-defs-vfuncs">
<title>Escribir los .defs de las «vfuncs»</title>
<para xml:lang="en">
This <filename>.defs</filename> file describes virtual functions (vfuncs).
It must be written by hand. There is the skeleton file
<filename>skeleton/src/skeleton_vfunc.defs</filename> to start from. You can also look
at <application>gtkmm</application>'s <filename>gtk/src/gtk_vfuncs.defs</filename> file.
</para>
</section>
</section>
<section xml:id="sec-wrapping-hg-files">
<title>Los archivos .hg y .ccg</title>
<para>Los archivos de fuentes .hg y .ccg se parecen mucho a los archivos de fuentes .h y .cc de C++, pero contienen macros adicionales, como <function>_CLASS_GOBJECT()</function> y <function>_WRAP_METHOD()</function>, desde las que <command>gmmproc</command> genera código fuente de C++ apropiado, generalmente en la misma posición en la cabecera. Cualquier código fuente adicional de C++ se copiará tal cual en el archivo .h o .cc correspondiente.</para>
<para xml:lang="en">A .hg file will typically include some headers
and then declare a class, using some macros to add API or behavior to
this class. For instance, <application>gtkmm</application>'s <filename>button.hg</filename> looks
roughly like this:</para>
<programlisting xml:lang="en"><code>#include <gtkmm/widget.h>
#include <gtkmm/actionable.h>
_DEFS(gtkmm,gtk)
_PINCLUDE(gtkmm/private/widget_p.h)
namespace Gtk
{
class Button
: public Widget,
public Actionable
{
_CLASS_GTKOBJECT(Button, GtkButton, GTK_BUTTON, Gtk::Widget, GtkWidget)
_IMPLEMENTS_INTERFACE(Actionable)
public:
_CTOR_DEFAULT
explicit Button(const Glib::ustring& label, bool mnemonic = false);
_WRAP_METHOD(void set_label(const Glib::ustring& label), gtk_button_set_label)
...
_WRAP_SIGNAL(void clicked(), "clicked")
...
_WRAP_PROPERTY("label", Glib::ustring)
};
} // namespace Gtk
</code></programlisting>
<para xml:lang="en">The macros in this example do the following:
<variablelist>
<varlistentry>
<term xml:lang="en"><function>_DEFS()</function></term>
<listitem><para xml:lang="en">Specifies the destination directory for generated sources, and the name of the main .defs file that <command>gmmproc</command> should parse.</para></listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en"><function>_PINCLUDE()</function></term>
<listitem><para xml:lang="en">Tells <command>gmmproc</command> to include a header in the generated <filename>private/button_p.h</filename> file.</para></listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en"><function>_CLASS_GTKOBJECT()</function></term>
<listitem><para xml:lang="en">Tells <command>gmmproc</command> to add some typedefs, constructors, and standard methods to this class, as appropriate when wrapping a widget.</para></listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en"><function>_IMPLEMENTS_INTERFACE()</function></term>
<listitem><para xml:lang="en">Tells <command>gmmproc</command> to add initialization code for the interface.</para></listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en"><function>_CTOR_DEFAULT</function></term>
<listitem><para xml:lang="en">Adds a default constructor.</para></listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en"><function>_WRAP_METHOD()</function>,
<function>_WRAP_SIGNAL()</function>, and
<function>_WRAP_PROPERTY()</function></term>
<listitem><para xml:lang="en">Add methods to wrap parts of the C API.</para></listitem>
</varlistentry>
</variablelist>
</para>
<para xml:lang="en">The .h and .cc files will be generated from the .hg and .ccg files by
processing them with <command>gmmproc</command> like so, though this happens
automatically when using the above build structure:
</para>
<programlisting xml:lang="en"><code>$ cd gtk/src
$ /usr/lib/glibmm-2.68/proc/gmmproc -I ../../tools/m4 --defs . button . ./../gtkmm
</code></programlisting>
<para>Tenga en cuenta que se le proporcionó a <command>gmmproc</command> la ruta de los archivos de conversión .m4, la ruta del archivo .defs, el nombre de un archivo .hg, la carpeta de las fuentes, y la carpeta de destino.</para>
<para>Debe evitar incluir la cabecera de C desde su cabecera de C++, para evitar contaminar el espacio de nombres global, y para evitar exportar API pública innecesaria. Pero necesitará incluir las cabeceras de C desde su archivo .ccg.</para>
<para>Las macros se explican en mayor detalle en las secciones siguientes.</para>
<section xml:id="gmmproc-m4-conversions">
<title>Conversiones m4</title>
<para xml:lang="en">The macros that you use in the .hg and .ccg files often need to know how
to convert a C++ type to a C type, or vice-versa. <command>gmmproc</command> takes this information
from an .m4 file in your <literal>tools/m4/</literal> or <literal>codegen/m4/</literal> directory.
This allows it to call a C function in the implementation of your C++ method,
passing the appropriate parameters to that C function. For instance, this
tells <command>gmmproc</command> how to convert a <classname>GtkTreeView</classname>
pointer to a <classname>Gtk::TreeView</classname> pointer:
</para>
<programlisting xml:lang="en"><code>_CONVERSION(`GtkTreeView*',`TreeView*',`Glib::wrap($3)')
</code></programlisting>
<para xml:lang="en"><literal>$3</literal> will be replaced by the parameter name when this
conversion is used by <command>gmmproc</command>.
</para>
<para xml:lang="en">
Some extra macros make this easier and consistent. Look in <application>gtkmm</application>'s .m4 files
for examples. For instance:
</para>
<programlisting xml:lang="en"><code>_CONVERSION(`PrintSettings&',`GtkPrintSettings*',__FR2P)
_CONVERSION(`const PrintSettings&',`GtkPrintSettings*',__FCR2P)
_CONVERSION(`const Glib::RefPtr<Printer>&',`GtkPrinter*',__CONVERT_REFPTR_TO_P($3))
</code></programlisting>
</section>
<section xml:id="gmmproc-m4-initializations">
<title>Inicializaciones de m4</title>
<para xml:lang="en">
Often when wrapping methods, it is desirable to store the return of the C
function in what is called an output parameter. In this case, the C++ method
returns <type>void</type> but an output parameter in which to store the value
of the C function is included in the argument list of the C++ method.
<command>gmmproc</command> allows such functionality, but appropriate initialization macros must
be included to tell <command>gmmproc</command> how to initialize the C++ parameter from the
return of the C function.
</para>
<para xml:lang="en">
For example, if there was a C function that returned a
<type>GtkWidget*</type> and for some reason, instead of having the C++ method
also return the widget, it was desirable to have the C++ method place the
widget in a specified output parameter, an initialization macro such as the
following would be necessary:
</para>
<programlisting xml:lang="en"><code>_INITIALIZATION(`Gtk::Widget&',`GtkWidget*',`$3 = Glib::wrap($4)')
</code></programlisting>
<para xml:lang="en">
<literal>$3</literal> will be replaced by the output parameter name of the
C++ method and <literal>$4</literal> will be replaced by the return of the C
function when this initialization is used by <command>gmmproc</command>. For convenience,
<literal>$1</literal> will also be replaced by the C++ type without the
ampersand (&) and <literal>$2</literal> will be replaced by the C type.
</para>
</section>
<section xml:id="gmmproc-class-macros">
<title>Macros de clases</title>
<para>Las macros de clases declaran la clase en sí y su relación con el tipo de C subyacente. Generan algunos constructores internos, el miembro <varname>gobject_</varname>, «typedefs», los <function>gobj()</function> de acceso, registro de tipos, y el método <function>Glib::wrap()</function>, entre otras cosas.</para>
<para>Otras macros, como <function>_WRAP_METHOD()</function> y <function>_WRAP_SIGNAL()</function> sólo pueden usarse después de una llamada a una macro <function>_CLASS_*</function>.</para>
<section xml:id="gmmproc-class-gobject">
<title>_CLASS_GOBJECT</title>
<para>Esta macro declara un envoltorio de un tipo que deriva de <classname>GObject</classname>, pero cuyo envoltorio no deriva de <classname>Gtk::Object</classname>.</para>
<para><function>_CLASS_GOBJECT( C++ class, C class, C casting macro, C++ base class, C base class )</function></para>
<para xml:lang="en">For instance, from <filename>adjustment.hg</filename>:</para>
<programlisting xml:lang="en"><code>_CLASS_GOBJECT(Adjustment, GtkAdjustment, GTK_ADJUSTMENT, Glib::Object, GObject)
</code></programlisting>
</section>
<section xml:id="gmmproc-class-gtkobject">
<title>_CLASS_GTKOBJECT</title>
<para>Esta macro declara un envoltorio para un tipo cuyo envoltorio deriva de <classname>Gtk::Object</classname>, como un widget o un diálogo.</para>
<para><function>_CLASS_GTKOBJECT( C++ class, C class, C casting macro, C++ base class, C base class )</function></para>
<para xml:lang="en">For instance, from <filename>button.hg</filename>:</para>
<programlisting xml:lang="en"><code>_CLASS_GTKOBJECT(Button, GtkButton, GTK_BUTTON, Gtk::Widget, GtkWidget)
</code></programlisting>
<para xml:lang="en">You will typically use this macro when the class already derives from
<classname>Gtk::Object</classname>. For instance, you will use it when wrapping
a GTK Widget, because <classname>Gtk::Widget</classname> derives from
<classname>Gtk::Object</classname>.</para>
<para xml:lang="en">You might also derive non-widget classes from
<classname>Gtk::Object</classname> so they can be used without
<classname>Glib::RefPtr</classname>. For instance, they could then be
instantiated with <function>Gtk::make_managed()</function> or on the stack
as a member variable. This is convenient, but you should use this only when
you are sure that true reference-counting is not needed. We consider it
useful for widgets.</para>
</section>
<section xml:id="gmmproc-class-boxedtype">
<title>_CLASS_BOXEDTYPE</title>
<para>Esta macro declara un envoltorio para una estructura que no es <classname>GObject</classname>, registrada con <function>g_boxed_type_register_static()</function>.</para>
<para><function>_CLASS_BOXEDTYPE( C++ class, C class, new function, copy function, free function )</function></para>
<para xml:lang="en">For instance, from <classname>Gdk::RGBA</classname>:</para>
<programlisting xml:lang="en"><code>_CLASS_BOXEDTYPE(RGBA, GdkRGBA, NONE, gdk_rgba_copy, gdk_rgba_free)
</code></programlisting>
</section>
<section xml:id="gmmproc-class-boxedtype-static">
<title>_CLASS_BOXEDTYPE_STATIC</title>
<para>Esta macro declara un envoltorio para una estructura asignable simple como <classname>GdkRectangle</classname>. Es similar a <function>_CLASS_BOXEDTYPE</function>, pero la estructura de C no se asigna dinámicamente.</para>
<para><function>_CLASS_BOXEDTYPE_STATIC( C++ class, C class )</function></para>
<para xml:lang="en">For instance, for <classname>Gdk::Rectangle</classname>:</para>
<programlisting xml:lang="en"><code>_CLASS_BOXEDTYPE_STATIC(Rectangle, GdkRectangle)
</code></programlisting>
</section>
<section xml:id="gmmproc-class-opaque-copyable">
<title>_CLASS_OPAQUE_COPYABLE</title>
<para>Esta macro declara un envoltorio para una estructura opaca que tiene funciones «copy» y «free». Las funciones «new», «copy» y «free» se usarán para instanciar el constructor predeterminado, copiar el constructor y el destructor.</para>
<para><function>_CLASS_OPAQUE_COPYABLE( C++ class, C class, new function, copy function, free function )</function></para>
<para xml:lang="en">For instance, from <classname>Glib::VariantType</classname>:</para>
<programlisting xml:lang="en"><code>_CLASS_OPAQUE_COPYABLE(VariantType, GVariantType, NONE, g_variant_type_copy, g_variant_type_free)
</code></programlisting>
</section>
<section xml:id="gmmproc-class-opaque-refcounted">
<title>_CLASS_OPAQUE_REFCOUNTED</title>
<para>Esta macro declara un envoltorio para una estructura opaca con conteo de referencias. El envoltorio de C++ no puede instanciarse directamente y sólo lo puede usar <classname>Glib::RefPtr</classname>.</para>
<para><function>_CLASS_OPAQUE_REFCOUNTED( C++ class, C class, new function, ref function, unref function )</function></para>
<para xml:lang="en">For instance, for <classname>Gtk::CssSection</classname>:</para>
<programlisting xml:lang="en"><code>_CLASS_OPAQUE_REFCOUNTED(CssSection, GtkCssSection, NONE, gtk_css_section_ref, gtk_css_section_unref)
</code></programlisting>
</section>
<section xml:id="gmmproc-class-generic">
<title>_CLASS_GENERIC</title>
<para>Esta macro puede usarse para envolver estructuras que no se ajustan a ninguna categoría especializada.</para>
<para><function>_CLASS_GENERIC( C++ class, C class )</function></para>
<para xml:lang="en">For instance, for <classname>Gdk::TimeCoord</classname>:</para>
<programlisting xml:lang="en"><code>_CLASS_GENERIC(TimeCoord, GdkTimeCoord)
</code></programlisting>
</section>
<section xml:id="gmmproc-class-interface">
<title>_CLASS_INTERFACE</title>
<para>Esta macro declara un envoltorio para un tipo que deriva de <classname>GTypeInterface</classname>.</para>
<para><function>_CLASS_INTERFACE( C++ class, C class, C casting macro, C interface struct, Base C++ class (optional), Base C class (optional) )</function></para>
<para xml:lang="en">
For instance, from <filename>celleditable.hg</filename>:</para>
<programlisting xml:lang="en"><code>_CLASS_INTERFACE(CellEditable, GtkCellEditable, GTK_CELL_EDITABLE, GtkCellEditableIface)
</code></programlisting>
<para xml:lang="en">Two extra optional parameters were once added, for the case that the interface derives
from another interface, which was believed to be the case when the GInterface has another
GInterface as a prerequisite. This is a misunderstanding, though.
When GInterface A has GInterface B as a prerequisite, it means that every class
that implements A shall also implement B.
For instance, from <filename>loadableicon.hg</filename> in glibmm-2.4:</para>
<programlisting xml:lang="en"><code>_CLASS_INTERFACE(LoadableIcon, GLoadableIcon, G_LOADABLE_ICON, GLoadableIconIface, Icon, GIcon)
</code></programlisting>
</section>
</section>
<section xml:id="gmmproc-constructor-macros">
<title>Macros de constructores</title>
<para xml:lang="en">The <function>_CTOR_DEFAULT()</function> and
<function>_WRAP_CTOR()</function> macros add constructors, wrapping the
specified <function>*_new()</function> C functions. These macros assume that
the C object has properties with the same names as the function parameters,
as is usually the case, so that it can supply the parameters directly to a
<function>g_object_new()</function> call. These constructors never actually
call the <function>*_new()</function> C functions,
because <application>gtkmm</application> must actually instantiate derived GTypes, and the
<function>*_new()</function> C functions are meant only as convenience
functions for C programmers.</para>
<para xml:lang="en">When using <function>_CLASS_GOBJECT()</function>, the constructors should
be protected (rather than public) and each constructor should have a
corresponding <function>_WRAP_CREATE()</function> in the public section.
This prevents the class from being instantiated without using a
<classname>RefPtr</classname>. For instance:</para>
<programlisting xml:lang="en"><code>class TextMark : public Glib::Object
{
_CLASS_GOBJECT(TextMark, GtkTextMark, GTK_TEXT_MARK, Glib::Object, GObject)
protected:
_WRAP_CTOR(TextMark(const Glib::ustring& name, bool left_gravity = true), gtk_text_mark_new)
public:
_WRAP_CREATE(const Glib::ustring& name, bool left_gravity = true)
</code></programlisting>
<section xml:id="gmmproc-ctor-default">
<title>_CTOR_DEFAULT</title>
<para>Esta macro crea un constructor predeterminado sin argumentos.</para>
</section>
<section xml:id="gmmproc-wrap-ctor">
<title>_WRAP_CTOR</title>
<para>Esta macro crea un constructor con argumentos, equivalente a una función de C <function>*_new()</function>. En realidad no llamará a la función <function>*_new()</function>, sino que simplemente creará un constructor equivalente con los mismos tipos de argumentos. Toma una firma de constructor de C++ y un nombre de función de C.</para>
<para xml:lang="en">It also takes an optional extra argument:
<variablelist>
<varlistentry>
<term xml:lang="en">errthrow</term>
<listitem>
<para xml:lang="en">This tells <command>gmmproc</command> that the C <function>*_new()</function> has
a final <type>GError**</type> parameter which should be ignored.</para>
</listitem>
</varlistentry>
</variablelist>
</para>
</section>
<section xml:id="gmmproc-ctor-manual">
<title>Escribir constructores a mano</title>
<para xml:lang="en">When a constructor must be partly hand written because, for instance, the
<function>*_new()</function> C function's parameters do not correspond
directly to object properties, or because the <function>*_new()</function> C
function does more than call <function>g_object_new()</function>, the
<function>_CONSTRUCT()</function> macro may be used in the
.ccg file to save some work. The <function>_CONSTRUCT</function> macro takes
a series of property names and values. For instance, from
<filename>button.ccg</filename>:</para>
<programlisting xml:lang="en"><code>Button::Button(const Glib::ustring& label, bool mnemonic)
:
_CONSTRUCT("label", label.c_str(), "use_underline", gboolean(mnemonic))
{}
</code></programlisting>
</section>
</section>
<section xml:id="gmmproc-suppressing-macros">
<title xml:lang="en">Macros that suppress generation of some code</title>
<para xml:lang="en">Some macros suppress the generation of some code when they are used after
a <function>_CLASS_*</function> macro. Some suppress the definition in the
generated .cc file, others suppress both the declaration in the .h file and
the definition in the .cc file.
</para>
<section xml:id="gmmproc-custom-default-ctor">
<title xml:lang="en">_CUSTOM_DEFAULT_CTOR</title>
<para xml:lang="en">Suppresses declaration and definition of default constructor in
<function>_CLASS_BOXEDTYPE</function>, <function>_CLASS_BOXEDTYPE_STATIC</function>
and <function>_CLASS_OPAQUE_COPYABLE</function>.
</para>
</section>
<section xml:id="gmmproc-custom-ctor-cast">
<title>_CUSTOM_CTOR_CAST</title>
<para xml:lang="en">Suppresses declaration and definition of the constructor that takes a pointer
to the wrapped C object in <function>_CLASS_BOXEDTYPE</function> and
<function>_CLASS_BOXEDTYPE_STATIC</function>.
</para>
<para xml:lang="en">Suppresses definition of the constructor that takes a pointer to the
wrapped C object in <function>_CLASS_INTERFACE</function> and
<function>_CLASS_OPAQUE_COPYABLE</function>.
</para>
<para xml:lang="en">Suppresses definition of the constructor that takes a pointer to the
wrapped C object and the constructor that takes construct_params in
<function>_CLASS_GOBJECT</function> and <function>_CLASS_GTKOBJECT</function>.
</para>
</section>
<section xml:id="gmmproc-custom-dtor">
<title>_CUSTOM_DTOR</title>
<para xml:lang="en">Suppresses definition of destructor in
<function>_CLASS_GOBJECT</function> and <function>_CLASS_GTKOBJECT</function>.
</para>
</section>
<section xml:id="gmmproc-custom-move-operations">
<title>_CUSTOM_MOVE_OPERATIONS</title>
<para xml:lang="en">Suppresses declaration and definition of move constructor and move
assignment operator in <function>_CLASS_GOBJECT</function> and
<function>_CLASS_GTKOBJECT</function>.
</para>
<para>Por ejemplo:</para>
<programlisting xml:lang="en"><code>class Derived : public Glib::Object
{
_CLASS_GOBJECT(Derived, GDerived, G_DERIVED, Glib::Object, GObject)
_CUSTOM_MOVE_OPERATIONS
public:
Derived(Derived&& src) noexcept;
Derived& operator=(Derived&& src) noexcept;
// ...
};
</code></programlisting>
</section>
<section xml:id="gmmproc-custom-wrap-new">
<title>_CUSTOM_WRAP_NEW</title>
<para xml:lang="en">Suppresses definition of <function>Glib::wrap_new()</function> function in
<function>_CLASS_GOBJECT</function>.
</para>
</section>
<section xml:id="gmmproc-custom-wrap-function">
<title>_CUSTOM_WRAP_FUNCTION</title>
<para xml:lang="en">Suppresses definition of <function>Glib::wrap()</function> function in
<function>_CLASS_GOBJECT</function> and <function>_CLASS_GTKOBJECT</function>.
</para>
</section>
<section xml:id="gmmproc-no-wrap-function">
<title>_NO_WRAP_FUNCTION</title>
<para xml:lang="en">Suppresses declaration and definition of <function>Glib::wrap()</function>
function in <function>_CLASS_GOBJECT</function>, <function>_CLASS_BOXEDTYPE</function>,
<function>_CLASS_BOXEDTYPE_STATIC</function>, <function>_CLASS_OPAQUE_COPYABLE</function>,
<function>_CLASS_INTERFACE</function> and <function>_CLASS_GTKOBJECT</function>.
</para>
</section>
</section>
<section xml:id="gmmproc-method-macros">
<title>Macros de métodos</title>
<section xml:id="gmmproc-wrap-method">
<title>_WRAP_METHOD</title>
<para>Esta macro genera el método de C++ para envolver una función de C.</para>
<para xml:lang="en"><function>_WRAP_METHOD( C++ method signature, C function name)</function></para>
<para xml:lang="en">For instance, from <filename>entry.hg</filename>:</para>
<programlisting xml:lang="en"><code>_WRAP_METHOD(void set_text(const Glib::ustring& text), gtk_entry_set_text)
</code></programlisting>
<para>La función de C (por ejemplo, <function>gtk_entry_set_text</function>) se describe en mayor detalle en el archivo .defs, y los archivos <filename>convert*.m4</filename> contienen la conversión necesaria del tipo de parámetro de C++ al tipo de C. Esta macro también genera comentarios de documentación de Doxygen basados en los archivos <filename>*_docs.xml</filename> y <filename>*_docs_override.xml</filename>.</para>
<para xml:lang="en">There are some optional extra arguments:
<variablelist>
<varlistentry>
<term xml:lang="en">refreturn</term>
<listitem>
<para xml:lang="en">Do an extra <function>reference()</function> on the return value,
in case the C function does not provide a reference.</para>
</listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en">errthrow ["<exceptions>"]</term>
<listitem>
<para xml:lang="en">Use the last GError** parameter of the C function to
throw an exception.
The optional "<exceptions>" is a comma-separated list
of exceptions that can be thrown. It determines which @throws
Doxygen commands are added to the documentation. Default value
is <classname>Glib::Error</classname>. If you want a comma in
the description of an exception, precede it by a backslash. Example:
<code>errthrow "Glib::OptionError Hello\, world, Glib::ConvertError"</code></para>
</listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en">deprecated ["<text>"]</term>
<listitem>
<para xml:lang="en">Puts the generated code in #ifdef blocks. Text about the
deprecation can be specified as an optional
parameter.</para>
</listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en">ignore_deprecations</term>
<listitem>
<para xml:lang="en">Puts the generated code in the .cc file in a
G_GNUC_BEGIN_IGNORE_DEPRECATIONS / G_GNUC_END_IGNORE_DEPRECATIONS
block. (Only in glibmm >= 2.70.1)</para>
</listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en">constversion</term>
<listitem>
<para xml:lang="en">Just call the non-const version of the same function,
instead of generating almost duplicate code.</para>
</listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en">newin "<version>"</term>
<listitem>
<para xml:lang="en">Adds a @newin Doxygen command to the documentation, or replaces
the @newin command generated from the C documentation.</para>
</listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en">ifdef <identifier></term>
<listitem>
<para xml:lang="en">Puts the generated code in #ifdef blocks.</para>
</listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en">slot_name <parameter_name></term>
<listitem>
<para xml:lang="en">Specifies the name of the slot parameter of the method, if it
has one. This enables <command>gmmproc</command> to generate code
to copy the slot and pass the copy on to the C function in its
final <literal>gpointer user_data</literal> parameter. The
<literal>slot_callback</literal> option must also be used to
specify the name of the glue callback function to also pass on to
the C function.</para>
</listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en">slot_callback <function_name></term>
<listitem>
<para xml:lang="en">Used in conjunction with the <literal>slot_name</literal>
option to specify the name of the glue callback function that
handles extracting the slot and then calling it. The address of
this callback is also passed on to the C function that the method
wraps.</para>
</listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en">no_slot_copy</term>
<listitem>
<para xml:lang="en">Tells <command>gmmproc</command> not to pass a copy of the slot
to the C function, if the method has one. Instead the slot itself
is passed. The slot parameter name and the glue callback function
must have been specified with the <literal>slot_name</literal> and
<literal>slot_callback</literal> options respectively.</para>
</listitem>
</varlistentry>
</variablelist>
</para>
<para xml:lang="en">Selecting which C++ types should be used is also important when wrapping
C API. Though it's usually obvious what C++ types should be used in the C++
method, here are some hints:
<itemizedlist>
<listitem><para xml:lang="en">Objects used via <classname>RefPtr</classname>: Pass the
<classname>RefPtr</classname> as a const reference. For instance,
<code>const Glib::RefPtr<Gtk::FileFilter>&
filter</code>.</para></listitem>
<listitem><para xml:lang="en">Const Objects used via <classname>RefPtr</classname>: If the
object should not be changed by the function, then make sure that
the object is const, even if the <classname>RefPtr</classname> is
already const. For instance, <code>const Glib::RefPtr<const
Gtk::FileFilter>& filter</code>.</para></listitem>
<listitem><para xml:lang="en">Wrapping <classname>GList*</classname> and
<classname>GSList*</classname> parameters: First, you need to discover
what objects are contained in the list's data field for each item,
usually by reading the documentation for the C function. The list can
then be wrapped by a <classname>std::vector</classname> type.
For instance, <code>std::vector<Glib::RefPtr<Gdk::Pixbuf>></code>.
You may need to define a Traits type to specify how the C
and C++ types should be converted.</para></listitem>
<listitem><para xml:lang="en">Wrapping <classname>GList*</classname> and
<classname>GSList*</classname> return types: You must discover whether
the caller should free the list and whether it should release the items
in the list, again by reading the documentation of the C function. With
this information you can choose the ownership (none, shallow or deep)
for the m4 conversion rule, which you should probably put directly into
the .hg file because the ownership depends on the
function rather than the type. For instance:</para>
<programlisting xml:lang="en"><code>#m4 _CONVERSION(`GSList*',`std::vector<Widget*>',`Glib::SListHandler<Widget*>::slist_to_vector($3, Glib::OWNERSHIP_SHALLOW)')</code></programlisting></listitem>
</itemizedlist>
</para>
</section>
<section xml:id="gmmproc-wrap-method-docs-only">
<title>_WRAP_METHOD_DOCS_ONLY</title>
<para>Esta macro es similar a <function>_WRAP_METHOD()</function>, pero sólo genera la documentación de un método de C++ que envuelve una función de C. Úsela cuando debe escribir el método a mano, pero quiere usar la documentación que se crearía si el método se generara.</para>
<para xml:lang="en"><function>_WRAP_METHOD_DOCS_ONLY(C function name)</function></para>
<para xml:lang="en">For instance, from <filename>recentinfo.hg</filename>:</para>
<programlisting xml:lang="en"><code>_WRAP_METHOD_DOCS_ONLY(gtk_recent_info_get_applications)
</code></programlisting>
<para xml:lang="en">There are some optional extra arguments:
<variablelist>
<varlistentry>
<term xml:lang="en">errthrow ["<exceptions>"]</term>
<listitem>
<para xml:lang="en">Excludes documentation of the last GError** parameter of
the C function.
The optional "<exceptions>" is a comma-separated list
of exceptions that can be thrown. It determines which @throws
Doxygen commands are added to the documentation. Default value
is <classname>Glib::Error</classname>. If you want a comma in
the description of an exception, precede it by a backslash. Example:
<code>errthrow "Glib::OptionError Hello\, world, Glib::ConvertError"</code></para>
</listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en">newin "<version>"</term>
<listitem>
<para xml:lang="en">Adds a @newin Doxygen command to the documentation, or replaces
the @newin command generated from the C documentation.</para>
</listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en">voidreturn</term>
<listitem>
<para xml:lang="en">Don't include a @return Doxygen command in the documentation.
Useful if the wrapped C function returns a value, but the corresponding
C++ method returns <type>void</type>.</para>
</listitem>
</varlistentry>
</variablelist>
</para>
</section>
<section xml:id="gmmproc-ignore">
<title>_IGNORE, _IGNORE_SIGNAL, _IGNORE_PROPERTY</title>
<para xml:lang="en"><command>gmmproc</command> will warn you on stdout about functions, signals,
properties and child properties that you have forgotten to wrap, helping to
ensure that you are wrapping the complete API. But if you don't want to wrap
some functions, signals, properties or child properties, or if you chose
to hand-code some methods then you can use the _IGNORE(), _IGNORE_SIGNAL()
or _IGNORE_PROPERTY() macro to make <command>gmmproc</command> stop complaining.
</para>
<para>
<literallayout class="normal"><function>_IGNORE(C function name 1, C function name 2, etc)
_IGNORE_SIGNAL(C signal name 1, C signal name 2, etc)
_IGNORE_PROPERTY(C property name 1, C property name 2, etc)</function></literallayout>
</para>
<para xml:lang="en">For instance, from <filename>flowbox.hg</filename>:</para>
<programlisting xml:lang="en"><code>_IGNORE(gtk_flow_box_set_filter_func, gtk_flow_box_set_sort_func)
_IGNORE_SIGNAL(activate-cursor-child, toggle-cursor-child, move-cursor)
</code></programlisting>
</section>
<section xml:id="gmmproc-wrap-signal">
<title>_WRAP_SIGNAL</title>
<para>Esta macro genera la señal de C++ con el estilo de libsigc++ para envolver una señal GObject de C. En realidad genera un método de acceso público, como <function>signal_clicked()</function>, que devuelve un objeto sustituto. <function>gmmproc</function> usa el archivo .defs para descubrir los tipos de parámetro de C y los archivos de conversión .m4 para descubrir conversiones de tipo adecuadas.</para>
<para><function>_WRAP_SIGNAL( C++ signal handler signature, C signal name)</function></para>
<para xml:lang="en">For instance, from <filename>button.hg</filename>:</para>
<programlisting xml:lang="en"><code>_WRAP_SIGNAL(void clicked(),"clicked")
</code></programlisting>
<para>Las señales generalmente tienen punteros de funciones en la estructura de GTK, con un valor de enum correspondiente y un <function>g_signal_new()</function> en el archivo .c.</para>
<para xml:lang="en">There are some optional extra arguments:
<variablelist>
<varlistentry>
<term xml:lang="en">no_default_handler</term>
<listitem>
<para xml:lang="en">Do not generate an <function>on_something()</function> virtual
method to allow easy overriding of the default signal handler.
Use this when adding a signal with a default signal handler
would break the ABI by increasing the size of the class's
virtual function table, and when adding a signal without a public
C default handler.</para>
</listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en">custom_default_handler</term>
<listitem>
<para xml:lang="en">Generate a declaration of the <function>on_something()</function>
virtual method in the <filename>.h</filename> file, but do not
generate a definition in the <filename>.cc</filename> file.
Use this when you must generate the definition by hand.</para>
</listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en">custom_c_callback</term>
<listitem>
<para xml:lang="en">Do not generate a C callback function for the signal.
Use this when you must generate the callback function by hand.</para>
</listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en">refreturn</term>
<listitem>
<para xml:lang="en">Do an extra <function>reference()</function> on the return value
of the <function>on_something()</function> virtual method, in
case the C function does not provide a reference.</para>
</listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en">deprecated ["<text>"]</term>
<listitem>
<para xml:lang="en">Puts the generated code in #ifdef blocks. Text about the
deprecation can be specified as an optional parameter.</para>
</listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en">newin "<version>"</term>
<listitem>
<para xml:lang="en">Adds a @newin Doxygen command to the documentation, or replaces
the @newin command generated from the C documentation.</para>
</listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en">ifdef <identifier></term>
<listitem>
<para xml:lang="en">Puts the generated code in #ifdef blocks.</para>
</listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en">exception_handler <method_name></term>
<listitem>
<para xml:lang="en">Allows to use custom exception handler instead of default one.
Exception might be rethrown by user-defined handler, and it will be
caught by default handler.</para>
</listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en">detail_name <parameter_name></term>
<listitem>
<para xml:lang="en">Adds a <type>const Glib::ustring&</type> parameter to the
<methodname>signal_something()</methodname> method. Use it, if the signal
accepts a detailed signal name, i.e. if the underlying C code registers
the signal with the <literal>G_SIGNAL_DETAILED</literal> flag.</para>
</listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en">two_signal_methods</term>
<listitem>
<para xml:lang="en">Used in conjunction with the <literal>detail_name</literal>
option to generate two <methodname>signal_something()</methodname>
methods, one without a parameter and one with a parameter without
a default value. With only the <literal>detail_name</literal> option
one method is generated, with a parameter with default value.
Use the <literal>two_signal_methods</literal> option, if it's
necessary in order to preserve ABI.</para>
</listitem>
</varlistentry>
</variablelist>
</para>
</section>
<section xml:id="gmmproc-wrap-property">
<title>_WRAP_PROPERTY</title>
<para>Esta macro genera el método de C++ que envuelve una propiedad GObject de C. Debe especificar el nombre de la propiedad y el tipo de C++ que quiere para la propiedad. <command>gmmproc</command> usa el archivo .defs para descubrir el tipo de C y el archivo de conversión .m4 para descubrir conversiones de tipo apropiadas.</para>
<para><function>_WRAP_PROPERTY(C property name, C++ type)</function></para>
<para xml:lang="en">For instance, from <filename>button.hg</filename>:</para>
<programlisting xml:lang="en"><code>_WRAP_PROPERTY("label", Glib::ustring)
</code></programlisting>
<para xml:lang="en">There are some optional extra arguments:
<variablelist>
<varlistentry>
<term xml:lang="en">deprecated ["<text>"]</term>
<listitem>
<para xml:lang="en">Puts the generated code in #ifdef blocks. Text about the
deprecation can be specified as an optional parameter.</para>
</listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en">newin "<version>"</term>
<listitem>
<para xml:lang="en">Adds a @newin Doxygen command to the documentation, or replaces
the @newin command generated from the C documentation.</para>
</listitem>
</varlistentry>
</variablelist>
</para>
</section>
<section xml:id="gmmproc-wrap-vfunc">
<title>_WRAP_VFUNC</title>
<para>Esta macro genera el método de C++ que envuelve una función virtual de C.</para>
<para><function>_WRAP_VFUNC( C++ method signature, C function name)</function></para>
<para xml:lang="en">For instance, from <filename>widget.hg</filename>:</para>
<programlisting xml:lang="en"><code>_WRAP_VFUNC(SizeRequestMode get_request_mode() const, get_request_mode)
</code></programlisting>
<para xml:lang="en">The C function (e.g. <function>get_request_mode</function>) is described
more fully in the <filename>*_vfuncs.defs</filename> file, and the
<filename>convert*.m4</filename> files contain the necessary conversion from
the C++ parameter type to the C parameter type. Conversions can also be
written in the .hg file. Virtual functions often require special conversions
that are best kept local to the .hg file where they are used.</para>
<para xml:lang="en">There are some optional extra arguments:
<variablelist>
<varlistentry>
<term xml:lang="en">refreturn</term>
<listitem>
<para xml:lang="en">Do an extra <function>reference()</function> on the return value
of the <function>something_vfunc()</function> function,
in case the virtual C function does not provide a reference.</para>
</listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en">refreturn_ctype</term>
<listitem>
<para xml:lang="en">Do an extra <function>reference()</function> on the return value
of an overridden <function>something_vfunc()</function> function
in the C callback function, in case the calling C function
expects it to provide a reference.</para>
</listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en">keep_return</term>
<listitem>
<para xml:lang="en">Keep a copy of the return value in the C callback function,
in case the calling C function does not expect to get its own
reference.</para>
</listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en">errthrow</term>
<listitem>
<para xml:lang="en">Use the last GError** parameter of the C virtual function (if
there is one) to throw an exception.</para>
</listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en">custom_vfunc</term>
<listitem>
<para xml:lang="en">Do not generate a definition of the vfunc in the
<filename>.cc</filename> file. Use this when you must generate
the vfunc by hand.</para>
</listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en">custom_vfunc_callback</term>
<listitem>
<para xml:lang="en">Do not generate a C callback function for the vfunc.
Use this when you must generate the callback function by hand.</para>
</listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en">ifdef <identifier></term>
<listitem>
<para xml:lang="en">Puts the generated code in #ifdef blocks.</para>
</listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en">slot_name <parameter_name></term>
<listitem>
<para xml:lang="en">Specifies the name of the slot parameter of the method, if it
has one. This enables <command>gmmproc</command> to generate code
to copy the slot and pass the copy on to the C function in its
final <literal>gpointer user_data</literal> parameter. The
<literal>slot_callback</literal> option must also be used to
specify the name of the glue callback function to also pass on to
the C function.</para>
</listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en">slot_callback <function_name></term>
<listitem>
<para xml:lang="en">Used in conjunction with the <literal>slot_name</literal>
option to specify the name of the glue callback function that
handles extracting the slot and then calling it. The address of
this callback is also passed on to the C function that the method
wraps.</para>
</listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en">no_slot_copy</term>
<listitem>
<para xml:lang="en">Tells <command>gmmproc</command> not to pass a copy of the slot
to the C function, if the method has one. Instead the slot itself
is passed. The slot parameter name and the glue callback function
must have been specified with the <literal>slot_name</literal> and
<literal>slot_callback</literal> options respectively.</para>
</listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en">return_value <value></term>
<listitem>
<para xml:lang="en">Defines a non-default return value.</para>
</listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en">err_return_value <value></term>
<listitem>
<para xml:lang="en">Defines a non-default return value, used only if the C++
<function>something_vfunc()</function> function throws an exception
which is propagated to the C callback function. If return_value is
specified, but err_return_value is not, then return_value is used
also when an exception is propagated.</para>
</listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en">exception_handler <method_name></term>
<listitem>
<para xml:lang="en">Allows to use custom exception handler instead of default one.
Exception might be rethrown by user-defined handler, and it will be
caught by default handler.</para>
</listitem>
</varlistentry>
</variablelist>
</para>
<para>Una regla para la cual puede haber excepciones: si la función virtual de C devuelve un puntero a un objeto derivado de <classname>GObject</classname>, es decir un objeto cuyas referencias se cuentan, entonces la función virtual de C++ deberá devolver un objeto <classname>Glib::RefPtr<></classname>. Se requiere uno de los argumentos adicionales <parameter>refreturn</parameter> o <parameter>refreturn_ctype</parameter>.</para>
</section>
</section>
<section xml:id="gmmproc-other-macros">
<title>Otras macros</title>
<section xml:id="gmmproc-implements-interface">
<title>_IMPLEMENTS_INTERFACE</title>
<para>Esta macro genera código de inicialización para la interfaz.</para>
<para><function>_IMPLEMENTS_INTERFACE(C++ interface name)</function></para>
<para xml:lang="en">For instance, from <filename>grid.hg</filename>:</para>
<programlisting xml:lang="en"><code>_IMPLEMENTS_INTERFACE(Orientable)
</code></programlisting>
<para xml:lang="en">There is one optional extra argument:
<variablelist>
<varlistentry>
<term xml:lang="en">ifdef <identifier></term>
<listitem>
<para xml:lang="en">Puts the generated code in #ifdef blocks.</para>
</listitem>
</varlistentry>
</variablelist>
</para>
</section>
<section xml:id="gmmproc-wrap-enum">
<title>_WRAP_ENUM</title>
<para>Esta macro genera una enum de C++ para envolver una enum de C. Debe especificar el nombre de C++ que quiere y el nombre de la enum de C subyacente.</para>
<para xml:lang="en">For instance, from <filename>enums.hg</filename>:</para>
<programlisting xml:lang="en"><code>_WRAP_ENUM(Orientation, GtkOrientation)
</code></programlisting>
<para xml:lang="en">There are some optional extra arguments:
<variablelist>
<varlistentry>
<term xml:lang="en">NO_GTYPE</term>
<listitem>
<para xml:lang="en">Use this option, if the enum is not a <classname>GType</classname>.
This is the case when there is no <function>*_get_type()</function>
function for the C enum, but be careful that you don't just need to
include an extra header for that function. You should also file a bug
against the C API, because all enums should be registered as GTypes.</para>
<para xml:lang="en">If you specify <literal>NO_GTYPE</literal>, don't use that enum as the
type in _WRAP_PROPERTY. It would cause a runtime error, when the generated
<methodname>property_*()</methodname> method is called.</para>
<para xml:lang="en">For example, from <filename>icontheme.hg</filename>:</para>
<programlisting xml:lang="en"><code>_WRAP_ENUM(IconLookupFlags, GtkIconLookupFlags, NO_GTYPE)
</code></programlisting>
</listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en">gtype_func <function_name></term>
<listitem>
<para xml:lang="en">Specifies the name of the <function>*_get_type()</function> function
for the C enum. Use this parameter if <command>gmmproc</command> can't
deduce the correct function name from the name of the C enum type.</para>
<para xml:lang="en">For example, from <filename>dbusproxy.hg</filename> in glibmm:</para>
<programlisting xml:lang="en"><code>_WRAP_ENUM(ProxyFlags, GDBusProxyFlags, gtype_func g_dbus_proxy_flags_get_type)
</code></programlisting>
</listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en">CONV_TO_INT</term>
<listitem>
<para xml:lang="en">"Convertible to int." Generates a plain enum (not an enum class)
within a class. Such an enum is scoped like an enum class, but unlike an
enum class, it can be implicitly converted to <type>int</type>.</para>
<para xml:lang="en">For example, from <filename>dialog.hg</filename>:</para>
<programlisting xml:lang="en"><code>_WRAP_ENUM(ResponseType, GtkResponseType, CONV_TO_INT)
</code></programlisting>
</listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en">s#<from>#<to>#</term>
<listitem>
<para xml:lang="en">Substitutes (part of) the name of one or more enum constants.
You can add any number of substitutions.</para>
<para xml:lang="en">For example, from <filename>iochannel.hg</filename> in glibmm:</para>
<programlisting xml:lang="en"><code>_WRAP_ENUM(SeekType, GSeekType, NO_GTYPE, s#^SEEK_#SEEK_TYPE_#)
</code></programlisting>
</listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en">deprecated ["<text>"]</term>
<listitem>
<para xml:lang="en">Puts the generated code in #ifdef blocks. Text about the
deprecation can be specified as an optional parameter.</para>
</listitem>
</varlistentry>
<varlistentry>
<term xml:lang="en">newin "<version>"</term>
<listitem>
<para xml:lang="en">Adds a @newin Doxygen command to the documentation, or replaces
the @newin command generated from the C documentation.</para>
</listitem>
</varlistentry>
</variablelist>
</para>
</section>
<section xml:id="gmmproc-wrap-enum-docs-only">
<title>_WRAP_ENUM_DOCS_ONLY</title>
<para xml:lang="en">This macro just generates a Doxygen documentation block for the enum.
This is useful for enums that can't be wrapped with
<function>_WRAP_ENUM()</function> because they are complexly defined (maybe
using C macros) but including the generated enum documentation is still
desired. It is used with the same syntax as
<function>_WRAP_ENUM()</function> and also processes the same options (though
NO_GTYPE, gtype_func <function_name> and CONV_TO_INT are ignored because
they make no difference when just generating the enum's documentation).
</para>
</section>
<section xml:id="gmmproc-wrap-gerror">
<title>_WRAP_GERROR</title>
<para xml:lang="en">This macro generates a C++ exception class, derived from <classname>Glib::Error</classname>, with
a <type>Code</type> enum and a <methodname>code()</methodname> method. You must specify the desired C++ name, the name
of the corresponding C enum, and the prefix for the C enum values.</para>
<para>Los métodos generados desde _WRAP_METHOD() podrán entonces lanzar esta excepción con la opción «errthrow».</para>
<para xml:lang="en">For instance, from <filename>pixbuf.hg</filename>:</para>
<programlisting xml:lang="en"><code>_WRAP_GERROR(PixbufError, GdkPixbufError, GDK_PIXBUF_ERROR)
</code></programlisting>
<para xml:lang="en">_WRAP_GERROR() accepts the same optional arguments as _WRAP_ENUM() (though
CONV_TO_INT is ignored because all exception class enums are plain enums within a class).
</para>
</section>
<section xml:id="gmmproc-member-set-get">
<title>_MEMBER_GET / _MEMBER_SET</title>
<para>Use estas macros si está envolviendo una estructura simple o un tipo en caja que proporciona acceso directo a sus miembros de datos, para crear métodos de acceso y modificación para estos.</para>
<para><function>_MEMBER_GET(C++ name, C name, C++ type, C type)</function></para>
<para><function>_MEMBER_SET(C++ name, C name, C++ type, C type)</function></para>
<para xml:lang="en">
For example, in <filename>rectangle.hg</filename>:
</para>
<programlisting xml:lang="en"><code>_MEMBER_GET(x, x, int, int)</code></programlisting>
</section>
<section xml:id="gmmproc-member-get-set-ptr">
<title>_MEMBER_GET_PTR / _MEMBER_SET_PTR</title>
<para>Use estas macros para proporcionar métodos de acceso y modificación para un miembro de datos que es de tipo puntero automáticamente. Para la función de acceso, creará dos métodos, uno constante y otro no constante.</para>
<para><function>_MEMBER_GET_PTR(C++ name, C name, C++ type, C type)</function></para>
<para><function>_MEMBER_SET_PTR(C++ name, C name, C++ type, C type)</function></para>
<para xml:lang="en">For example, for <classname>Pango::Analysis</classname> in <filename>item.hg</filename>:
</para>
<programlisting xml:lang="en"><code>// _MEMBER_GET_PTR(engine_lang, lang_engine, EngineLang*, PangoEngineLang*)
// It's just a comment. It's difficult to find a real-world example.
</code></programlisting>
</section>
<section xml:id="gmmproc-member-get-set-gobject">
<title>_MEMBER_GET_GOBJECT / _MEMBER_SET_GOBJECT</title>
<para>Use estas macros para proporcionar métodos de acceso y modificación para un miembro de datos que es de tipo <classname>GObject</classname> que debe referenciarse antes de devolverse.</para>
<para><function>_MEMBER_GET_GOBJECT(C++ name, C name, C++ type, C type)</function></para>
<para><function>_MEMBER_SET_GOBJECT(C++ name, C name, C++ type, C type)</function></para>
<para xml:lang="en">For example, in Pangomm, <filename>layoutline.hg</filename>:</para>
<programlisting xml:lang="en"><code>_MEMBER_GET_GOBJECT(layout, layout, Pango::Layout, PangoLayout*)
</code></programlisting>
</section>
</section>
<section xml:id="gmmproc-parameter-processing">
<title>Procesado de parámetros de gmmproc</title>
<para xml:lang="en"><command>gmmproc</command> allows processing the parameters in a method
signature for the macros that process method signatures (like
<function>_WRAP_METHOD()</function>, <function>_WRAP_CTOR()</function> and
<function>_WRAP_CREATE()</function>) in a variety of ways:
</para>
<section xml:id="gmmproc-parameter-reordering">
<title>Reordenación de parámetros</title>
<para xml:lang="en">
For all the macros that process method signatures, it is possible to
specify a different order for the C++ parameters than the existing order
in the C function, virtual function or signal. For example, say that the
following C function were being wrapped as a C++ method for the
<classname>Gtk::Widget</classname> class:</para>
<programlisting xml:lang="en"><code>void gtk_widget_set_device_events(GtkWidget* widget, GdkDevice* device,
GdkEventMask events);
</code></programlisting>
<para xml:lang="en">
However, changing the order of the C++ method's two parameters is
necessary. Something like the following would wrap the function as a C++
method with a different order for the two parameters:</para>
<programlisting xml:lang="en"><code>_WRAP_METHOD(void set_device_events(Gdk::EventMask events{events},
const Glib::RefPtr<const Gdk::Device>& device{device}),
gtk_widget_set_device_events)
</code></programlisting>
<para xml:lang="en">The <literal>{c_param_name}</literal> following the method parameter
names tells <command>gmmproc</command> to map the C++ parameter to the
specified C parameter within the <literal>{}</literal>. Since the C++
parameter names correspond to the C ones, the above could be re-written
as:</para>
<programlisting xml:lang="en"><code>_WRAP_METHOD(void set_device_events(Gdk::EventMask events{.},
const Glib::RefPtr<const Gdk::Device>& device{.}),
gtk_widget_set_device_events)
</code></programlisting>
<warning>
<para xml:lang="en">
Please note that when reordering parameters for a
<function>_WRAP_SIGNAL()</function> method signature, the C parameter
names would always be <literal>p0</literal>, <literal>p1</literal>,
etc. because the <filename>generate_extra_defs</filename> utility uses those
parameter names no matter what the C API's parameter names may be.
It's how the utility is written presently.
</para>
</warning>
</section>
<section xml:id="gmmproc-optional-parameter-processing">
<title>Procesado de parámetros opcionales</title>
<para xml:lang="en">
For all macros processing method signatures except
<function>_WRAP_SIGNAL()</function> and
<function>_WRAP_VFUNC()</function> it is also possible to make the
parameters optional so that extra C++ methods are generated without the
specified optional parameter. For example, say that the following
<function>*_new()</function> function were being wrapped as a constructor
in the <classname>Gtk::ToolButton</classname> class:</para>
<programlisting xml:lang="en"><code>GtkToolItem* gtk_tool_button_new(GtkWidget* icon_widget, const gchar* label);
</code></programlisting>
<para xml:lang="en">Also, say that the C API allowed NULL for the function's
<parameter>label</parameter> parameter so that that parameter is optional.
It would be possible to have <command>gmmproc</command> generate the
original constructor (with all the parameters) along with an additional
constructor without that optional parameter by appending a
<literal>{?}</literal> to the parameter name like so:</para>
<programlisting xml:lang="en"><code>_WRAP_CTOR(ToolButton(Widget& icon_widget, const Glib::ustring& label{?}),
gtk_tool_button_new)
</code></programlisting>
<para xml:lang="en">In this case, two constructors would be generated: One with the optional
parameter and one without it.
</para>
</section>
<section xml:id="gmmproc-output-parameter-processing">
<title>Procesado de parámetros de salida</title>
<para xml:lang="en">
With <function>_WRAP_METHOD()</function> it is also possible for the
return of the wrapped C function (if it has one) to be placed in an
output parameter of the C++ method instead of having the C++ method also
return a value like the C function does. To do that, simply include the
output parameter in the C++ method parameter list appending a
<literal>{OUT}</literal> to the output parameter name. For example, if
<function>gtk_widget_get_request_mode()</function> is declared as the
following:</para>
<programlisting xml:lang="en"><code>GtkSizeRequestMode gtk_widget_get_request_mode(GtkWidget* widget);
</code></programlisting>
<para xml:lang="en">And having the C++ method set an output parameter is desired instead of
returning a <type>SizeRequestMode</type>, something like the following
could be used:</para>
<programlisting xml:lang="en"><code>_WRAP_METHOD(void get_request_mode(SizeRequestMode& mode{OUT}) const,
gtk_widget_get_request_mode)
</code></programlisting>
<para xml:lang="en">The <literal>{OUT}</literal> appended to the name of the
<parameter>mode</parameter> output parameter tells
<command>gmmproc</command> to place the return of the C function in that
output parameter. In this case, however, a necessary initialization
macro like the following would also have to be specified:</para>
<programlisting xml:lang="en"><code>_INITIALIZATION(`SizeRequestMode&',`GtkSizeRequestMode',`$3 = (SizeRequestMode)($4)')
</code></programlisting>
<para xml:lang="en">Which could also be written as:</para>
<programlisting xml:lang="en"><code>_INITIALIZATION(`SizeRequestMode&',`GtkSizeRequestMode',`$3 = ($1)($4)')
</code></programlisting>
<para xml:lang="en">
<function>_WRAP_METHOD()</function> also supports setting C++ output
parameters from C output parameters if the C function being wrapped has
any. Suppose, for example, that we want to wrap the following C function
that returns a value in its C output parameter
<parameter>out_mime_type</parameter>:</para>
<programlisting xml:lang="en"><code>GInputStream* gdk_clipboard_read_finish(GdkClipboard* clipboard,
GAsyncResult* result, const char** out_mime_type, GError** error)
</code></programlisting>
<para xml:lang="en">To have <command>gmmproc</command> place the value returned in the C++
<parameter>out_mime_type</parameter> output parameter, something like the
following <function>_WRAP_METHOD()</function> macro could be used:
</para>
<programlisting xml:lang="en"><code><![CDATA[
_WRAP_METHOD(Glib::RefPtr<Gio::InputStream> read_finish(
const Glib::RefPtr<Gio::AsyncResult>& result,
Glib::ustring& out_mime_type{>>}), gdk_clipboard_read_finish, errthrow)
]]></code></programlisting>
<para xml:lang="en">The <literal>{>>}</literal> following the <parameter>out_mime_type</parameter>
parameter name indicates that the C++ output parameter should be set from
the value returned in the C parameter from the C function.
<command>gmmproc</command> will generate a declaration of a temporary
variable in which to store the value of the C output parameter and a
statement that sets the C++ output parameter from the temporary variable.
In this case it may be necessary to have an
<function>_INITIALIZATION()</function> describing how to set a
<classname>Glib::ustring&</classname> from a
<classname>const char**</classname> such as the following:</para>
<programlisting xml:lang="en"><code><![CDATA[
_INITIALIZATION(`Glib::ustring&',`const char*',`$3 = Glib::convert_const_gchar_ptr_to_ustring($4)')
]]></code></programlisting>
</section>
<section xml:id="gmmproc-string-parameter-processing">
<title xml:lang="en">String Parameter Processing</title>
<para xml:lang="en">
A string-valued input parameter in a C++ method is usually a
<type>const Glib::ustring&</type> or a <type>const std::string&</type>.
In C code it's a <type>const gchar*</type>. When an empty string is converted
to <type>const gchar*</type>, it can be converted either to <literal>nullptr</literal>
or to a pointer to an empty string (with <methodname>c_str()</methodname>).
Some parameters in some C functions accept a <literal>nullptr</literal>, and
interpret it in a special way. Other parameters must not be <literal>nullptr</literal>.
</para>
<para xml:lang="en">
The default conversion in <function>_WRAP_METHOD()</function> and similar
macros is
<itemizedlist>
<listitem><para xml:lang="en">for mandatory parameters (with or without default values):
empty string to empty string,</para></listitem>
<listitem><para xml:lang="en">for optional parameters (with appended <literal>{?}</literal>):
empty string to <literal>nullptr</literal>.</para></listitem>
</itemizedlist>
If the default conversion is not the best conversion, append <literal>{NULL}</literal>
to a mandatory parameter or <literal>{?!NULL}</literal> to an optional
parameter (<literal>!NULL</literal> = not <literal>NULL</literal>). If you
append both a C parameter name and <literal>NULL</literal>, separate them
with a space: <literal>{c_param_name NULL}</literal>.
</para>
</section>
</section>
<section xml:id="gmmproc-basic-types">
<title>Tipos básicos</title>
<para>Algunos de los tipos básicos que se usan en las API de C tienen alternativas mejores en C++. Por ejemplo, no hay necesidad de un tipo <type>gboolean</type> dado que C++ tiene el <type>bool</type>. La siguiente lista muestra algunos tipos comúnmente usados en API de C y en qué los puede convertir en una biblioteca envoltorio de C++</para>
<segmentedlist>
<?dbhtml list-presentation="table"?>
<segtitle>Tipo de C</segtitle>
<segtitle>Tipo de C++</segtitle>
<seglistitem><seg><type>gboolean</type></seg><seg><type>bool</type></seg></seglistitem>
<seglistitem><seg><type>gint</type></seg><seg><type>int</type></seg></seglistitem>
<seglistitem><seg><type>guint</type></seg><seg><type>guint</type></seg></seglistitem>
<seglistitem><seg><type>gdouble</type></seg><seg><type>double</type></seg></seglistitem>
<seglistitem><seg><type>gunichar</type></seg><seg><type>gunichar</type></seg></seglistitem>
<seglistitem><seg><type>gchar*</type></seg><seg xml:lang="en"><classname>Glib::ustring</classname> (or <classname>std::string</classname> for filenames)</seg></seglistitem>
</segmentedlist>
</section>
</section>
<section xml:id="sec-wrapping-hand-coded-files">
<title>Archivos de código fuente programados a mano</title>
<para xml:lang="en">You might want to include additional source files that will not be
generated by <command>gmmproc</command> from <filename>.hg</filename> and
<filename>.ccg</filename> files. You can simply place these in your
<filename>libsomething/libsomethingmm</filename> directory and mention them
in the <filename>meson.build</filename> in the
<varname>extra_h_files</varname> and <varname>extra_cc_files</varname>
variables.</para>
</section>
<section xml:id="sec-wrapping-initialization">
<title>Inicialización</title>
<para xml:lang="en">Your library must be initialized before it can be used, to register the
new types that it makes available. Also, the C library that you are wrapping
might have its own initialization function that you should call. You can do
this in an <function>init()</function> function that you can place in
hand-coded <filename>init.h</filename> and <filename>init.cc</filename>
files. This function should initialize your dependencies (such as the C
function, and <application>gtkmm</application>) and call your generated
<function>wrap_init()</function> function. For instance:</para>
<programlisting xml:lang="en"><code>void init()
{
Gtk::init_gtkmm_internals(); //Sets up the g type system and the Glib::wrap() table.
wrap_init(); //Tells the Glib::wrap() table about the libsomethingmm classes.
}
</code></programlisting>
<para><filename>generate_wrap_init.pl</filename> genera la implementación del método <function>wrap_init()</function> en <filename>wrap_init.cc</filename>, pero la declaración en <filename>wrap_init.h</filename> se programa a mano, por lo que necesitará ajustar <filename>wrap_init.h</filename> de manera tal que la función <function>wrap_init()</function> aparezca en el espacio de nombres de C++ correcto.</para>
</section>
<section xml:id="sec-wrapping-problems">
<title>Problemas en la API de C.</title>
<para>Es probable que encuentre algunos problemas en la biblioteca que está envolviendo, particularmente si es un proyecto nuevo. Aquí hay algunos problemas comunes, con soluciones.</para>
<section xml:id="wrapping-predeclare-structs">
<title>No se pueden predeclarar estructuras</title>
<para xml:lang="en">By convention, structs are declared in glib/GTK-style headers like so:</para>
<programlisting xml:lang="en"><code>typedef struct _ExampleWidget ExampleWidget;
struct _ExampleWidget
{
...
};
</code></programlisting>
<para>El «typedef» adicional le permite usar la estructura en una cabecera sin incluir su definición completa, simplemente predeclarándola repitiendo ese «typedef». Esto significa que no tiene que incluir la cabecera de la biblioteca de C en su cabecera de C++, por lo tanto la dejará fuera de su API pública. <command>gmmproc</command> asume que se usó esta técnica, por lo que verá errores de compilación si ese no es el caso.</para>
<para xml:lang="en">
This compiler error might look like this:</para>
<programlisting xml:lang="en"><code>example-widget.h:56: error: using typedef-name 'ExampleWidget' after 'struct'
../../libexample/libexamplemm/example-widget.h:34: error: 'ExampleWidget' has a previous declaration here
make[4]: *** [example-widget.lo] Error 1
</code></programlisting>
<para xml:lang="en">or this:</para>
<programlisting xml:lang="en"><code>example-widget.h:60: error: '_ExampleWidget ExampleWidget' redeclared as different kind of symbol
../../libexample/libexamplemm/example-widget.h:34: error: previous declaration of 'typedef struct _ExampleWidget ExampleWidget'
</code></programlisting>
<para>Esto es fácil de corregir en la biblioteca de C, así que envíe un parche al mantenedor pertinentes.</para>
</section>
<section xml:id="wrapping-no-properties">
<title>Falta de propiedades</title>
<para xml:lang="en">By convention, glib/GTK-style objects have <function>*_new()</function>
functions, such as <function>example_widget_new()</function> that do nothing
more than call <function>g_object_new()</function> and return the result.
The input parameters are supplied to <function>g_object_new()</function>
along with the names of the properties for which they are values. For
instance,</para>
<programlisting xml:lang="en"><code>GtkWidget* example_widget_new(int something, const char* thing)
{
return g_object_new (EXAMPLE_TYPE_WIDGET, "something", something, "thing", thing, NULL);
}
</code></programlisting>
<para>Esto le permite a los enlaces entre lenguajes implementar sus propios equivalentes (como los constructores de C++), sin usar la función <function>*_new()</function>. Esto a menudo es necesario para poder instanciar realmente un GType derivado, para añadir sus propios ganchos para gestores de señales y «vfuncs».</para>
<para>Como mínimo, la función <function>_new()</function> no debe usar ninguna API privada (funciones que sólo están en un archivo .c). Incluso cuando no hay funciones, a veces se pueden reimplementar 2 ó 3 líneas de código en una función <function>_new()</function> siempre que esas líneas de código usen una API que esté disponible.</para>
<para xml:lang="en">Another workaround is to add a <function>*_construct()</function> function
that the C++ constructor can call after instantiating its own type. For
instance,</para>
<programlisting xml:lang="en"><code>GtkWidget* example_widget_new(int something, const char* thing)
{
ExampleWidget* widget;
widget = g_object_new (EXAMPLE_TYPE_WIDGET, NULL);
example_widget_construct(widget, "something", something, "thing", thing);
}
void example_widget_construct(ExampleWidget* widget, int something, const char* thing)
{
//Do stuff that uses private API:
widget->priv->thing = thing;
do_something(something);
}
</code></programlisting>
<para>Incorporar propiedades, y asegurar que interactúan con otras propiedades correctamente, es relativamente difícil de corregir en la biblioteca de C, pero es posible, por lo que rellene un informe de error e intente enviar un parche al mantenedor correspondiente.</para>
</section>
</section>
<section xml:id="sec-wrapping-documentation">
<title>Documentación</title>
<para>En general, los proyectos de gtkmm usan Doxygen, que lee comentarios de C++ con un formato específico y genera documentación HTML. Puede escribir estos comentarios de Doxygen directamente en los archivos de cabecera.</para>
<section xml:id="wrapping-reusing-c-documentation">
<title>Reutilizar la documentación de C</title>
<para xml:lang="en">You might wish to reuse documentation that exists for the C library that
you are wrapping. GTK-style C libraries typically use gtk-doc or gi-docgen and therefore
have source code comments formatted for gtk-doc or gi-docgen and some extra documentation
in .sgml and .xml files. The docextract_to_xml.py script, from glibmm's
<filename>tools/defs_gen</filename> directory, can read these files and
generate an .xml file that <command>gmmproc</command> can use to generate
doxygen comments. <command>gmmproc</command> will even try to transform the
documentation to make it more appropriate for a C++ API.</para>
<para>Por ejemplo,</para>
<programlisting xml:lang="en"><code>./docextract_to_xml.py -s ~/checkout/gnome/gtk/gtk/ > gtk_docs.xml
</code></programlisting>
<para xml:lang="en">Because this automatic transformation is not always appropriate, you might
want to provide hand-written text for a particular method. You can do this
by copying the XML node for the function from your
<filename>something_docs.xml</filename> file to the
<filename>something_docs_override.xml</filename> file and changing the
contents. Alternatively you can write your own documentation in the
<filename>.hg</filename> file.</para>
</section>
<section xml:id="wrapping-documentation-build-structure">
<title>Estructura de construcción de la documentación</title>
<para xml:lang="en">If you copied the skeleton source tree in <application>mm-common</application> and substituted the
placeholder text, then you will already have suitable <filename>meson.build</filename>
and <filename>Doxyfile.in</filename> files in the <filename>doc/reference/</filename>
directory. You probably need to modify the <varname>tag_file_modules</varname>
variable in <filename>meson.build</filename>, though.
With the <application>mm-common</application> build setup, the list
of Doxygen input files is not defined in the Doxygen configuration file, but passed
along from <command>meson/ninja</command> to the standard input of <command>doxygen</command>.
</para>
</section>
</section>
</appendix>
</book>