Manual Completo de Programación con Glade 3

1. Introducción a Glade 3

Glade es una herramienta de diseño de interfaces gráficas (GUI) para GTK+ que permite crear interfaces de usuario de forma visual. Glade 3 es la versión estable y ampliamente utilizada en el desarrollo de aplicaciones GTK.

1.1 Características principales

1.2 Instalación

Dependiendo de tu distribución Linux:

Debian/Ubuntu:

sudo apt-get install glade

Fedora:

sudo dnf install glade

Arch Linux:

sudo pacman -S glade

Nota: Asegúrate de tener instaladas las bibliotecas GTK+ y las dependencias de desarrollo.

2. La interfaz de Glade 3

2.1 Componentes principales

2.2 Creando un primer proyecto

  1. Abre Glade 3
  2. Selecciona "File" > "New"
  3. Arrastra un "GtkWindow" desde la paleta al área de trabajo
  4. Guarda el proyecto como "mi_interfaz.glade"

2.3 Estructura de un archivo .glade

El archivo generado es XML que describe la interfaz:

<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <requires lib="gtk+" version="3.20"/>
  <object class="GtkWindow" id="ventana_principal">
    <property name="can_focus">False</property>
    <property name="title" translatable="yes">Mi Ventana</property>
    <property name="default_width">400</property>
    <property name="default_height">300</property>
  </object>
</interface>

3. Widgets Básicos

3.1 Contenedores

GtkBox (Caja vertical/horizontal)

<object class="GtkBox" id="caja_principal">
  <property name="visible">True</property>
  <property name="can_focus">False</property>
  <property name="orientation">vertical</property>
  <property name="spacing">5</property>
</object>

GtkGrid (Cuadrícula)

<object class="GtkGrid" id="grid_principal">
  <property name="visible">True</property>
  <property name="can_focus">False</property>
  <property name="row_spacing">5</property>
  <property name="column_spacing">5</property>
</object>

3.2 Widgets de control

GtkButton (Botón)

<object class="GtkButton" id="btn_aceptar">
  <property name="label" translatable="yes">Aceptar</property>
  <property name="visible">True</property>
  <property name="can_focus">True</property>
  <property name="receives_default">True</property>
</object>

GtkEntry (Campo de texto)

<object class="GtkEntry" id="entrada_texto">
  <property name="visible">True</property>
  <property name="can_focus">True</property>
  <property name="placeholder_text" translatable="yes">Escribe algo...</property>
</object>

GtkLabel (Etiqueta)

<object class="GtkLabel" id="etiqueta_info">
  <property name="visible">True</property>
  <property name="can_focus">False</property>
  <property name="label" translatable="yes">Información importante:</property>
  <property name="halign">start</property>
</object>

3.3 Widgets de selección

GtkComboBox (Lista desplegable)

<object class="GtkComboBox" id="combo_opciones">
  <property name="visible">True</property>
  <property name="can_focus">False</property>
  <property name="model">liststore1</property>
  <child>
    <object class="GtkCellRendererText" id="renderer_texto"/>
    <attributes>
      <attribute name="text">0</attribute>
    </attributes>
  </child>
</object>

<object class="GtkListStore" id="liststore1">
  <columns>
    <column type="gchararray"/>
  </columns>
  <data>
    <row>
      <col id="0" translatable="yes">Opción 1</col>
    </row>
    <row>
      <col id="0" translatable="yes">Opción 2</col>
    </row>
  </data>
</object>

GtkCheckButton (Casilla de verificación)

<object class="GtkCheckButton" id="check_activo">
  <property name="label" translatable="yes">Activar función</property>
  <property name="visible">True</property>
  <property name="can_focus">True</property>
  <property name="receives_default">False</property>
</object>

4. Diseño de Interfaces

4.1 Layouts y organización

Ejemplo de ventana con caja vertical y botones

<object class="GtkWindow" id="ventana_principal">
  <property name="title" translatable="yes">Ejemplo</property>
  <child>
    <object class="GtkBox" id="caja_principal">
      <property name="orientation">vertical</property>
      <property name="spacing">10</property>
      <child>
        <object class="GtkLabel" id="etiqueta">
          <property name="label" translatable="yes">Ejemplo de diseño</property>
        </object>
      </child>
      <child>
        <object class="GtkEntry" id="entrada"/>
      </child>
      <child>
        <object class="GtkButton" id="btn_aceptar">
          <property name="label" translatable="yes">Aceptar</property>
        </object>
      </child>
    </object>
  </child>
</object>

4.2 Alineación y expansión

Propiedades importantes:

4.3 Diseño responsive

Consejos para interfaces que se adaptan:

5. Señales y Eventos

5.1 Concepto de señales en GTK

Las señales son el mecanismo de GTK para manejar eventos (clic de botón, cambio de texto, etc.).

5.2 Configurar señales en Glade

  1. Selecciona un widget en Glade
  2. Ve a la pestaña "Señales" en el editor de propiedades
  3. Haz clic en "+" para añadir una nueva señal
  4. Selecciona la señal (ej. "clicked" para un botón)
  5. Asigna un nombre al manejador (ej. "on_btn_aceptar_clicked")

5.3 Señales comunes

Widget Señal Descripción
GtkButton clicked Cuando se hace clic en el botón
GtkEntry changed Cuando cambia el texto
GtkWindow delete-event Cuando se intenta cerrar la ventana
GtkComboBox changed Cuando se cambia la selección

5.4 Ejemplo de conexión de señales en código

Python:

import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk

class MiAplicacion:
    def __init__(self):
        # Cargar la interfaz
        self.builder = Gtk.Builder()
        self.builder.add_from_file("interfaz.glade")
        
        # Conectar señales
        self.builder.connect_signals({
            "on_btn_aceptar_clicked": self.on_aceptar_clicked,
            "on_ventana_principal_destroy": Gtk.main_quit
        })
        
        # Mostrar ventana
        self.ventana = self.builder.get_object("ventana_principal")
        self.ventana.show_all()
    
    def on_aceptar_clicked(self, widget):
        print("Botón aceptar clickeado")

if __name__ == "__main__":
    app = MiAplicacion()
    Gtk.main()

C:

#include <gtk/gtk.h>

// Manejador de la señal clicked
void on_btn_aceptar_clicked(GtkButton *button, gpointer user_data) {
    g_print("Botón aceptar clickeado\n");
}

int main(int argc, char *argv[]) {
    GtkBuilder *builder;
    GtkWidget *window;
    
    gtk_init(&argc, &argv);
    
    builder = gtk_builder_new();
    gtk_builder_add_from_file(builder, "interfaz.glade", NULL);
    
    // Conectar señales
    gtk_builder_connect_signals(builder, NULL);
    
    window = GTK_WIDGET(gtk_builder_get_object(builder, "ventana_principal"));
    gtk_widget_show(window);
    
    gtk_main();
    
    return 0;
}

6. Integración con Código

6.1 Flujo de trabajo típico

  1. Diseñar la interfaz en Glade
  2. Guardar el archivo .glade
  3. Cargar el archivo en tu aplicación
  4. Conectar las señales a funciones manejadoras
  5. Ejecutar el bucle principal de GTK

6.2 Cargar interfaz en diferentes lenguajes

Python:

from gi.repository import Gtk

builder = Gtk.Builder()
builder.add_from_file("interfaz.glade")

C:

GtkBuilder *builder = gtk_builder_new();
gtk_builder_add_from_file(builder, "interfaz.glade", NULL);

Vala:

var builder = new Gtk.Builder();
builder.add_from_file("interfaz.glade");

6.3 Acceder a widgets desde el código

Python:

entrada = builder.get_object("entrada_texto")
texto = entrada.get_text()

C:

GtkEntry *entrada = GTK_ENTRY(gtk_builder_get_object(builder, "entrada_texto"));
const gchar *texto = gtk_entry_get_text(entrada);

6.4 Actualizar la interfaz desde código

Cambiar texto de una etiqueta (Python):

etiqueta = builder.get_object("etiqueta_status")
etiqueta.set_text("Estado: Conectado")

Deshabilitar un botón (C):

GtkButton *btn = GTK_BUTTON(gtk_builder_get_object(builder, "btn_enviar"));
gtk_widget_set_sensitive(GTK_WIDGET(btn), FALSE);

7. Widgets Avanzados

7.1 GtkTreeView (Vista de árbol/listado)

<object class="GtkTreeView" id="treeview_archivos">
  <property name="visible">True</property>
  <property name="can_focus">True</property>
  <property name="model">liststore_archivos</property>
  
  <child>
    <object class="GtkTreeViewColumn" id="treeviewcolumn_nombre">
      <property name="title" translatable="yes">Nombre</property>
      <child>
        <object class="GtkCellRendererText" id="cellrenderertext_nombre"/>
        <attributes>
          <attribute name="text">0</attribute>
        </attributes>
      </child>
    </object>
  </child>
  
  <child>
    <object class="GtkTreeViewColumn" id="treeviewcolumn_tamano">
      <property name="title" translatable="yes">Tamaño</property>
      <child>
        <object class="GtkCellRendererText" id="cellrenderertext_tamano"/>
        <attributes>
          <attribute name="text">1</attribute>
        </attributes>
      </child>
    </object>
  </child>
</object>

<object class="GtkListStore" id="liststore_archivos">
  <columns>
    <column type="gchararray"/> <!-- Nombre -->
    <column type="gchararray"/> <!-- Tamaño -->
  </columns>
</object>

7.2 GtkNotebook (Pestañas)

<object class="GtkNotebook" id="notebook_principal">
  <property name="visible">True</property>
  <property name="can_focus">True</property>
  
  <child>
    <object class="GtkLabel" id="label_pestana1">
      <property name="visible">True</property>
      <property name="label" translatable="yes">Primera pestaña</property>
    </object>
    <packing>
      <property name="tab_expand">False</property>
      <property name="tab_fill">True</property>
    </packing>
  </child>
  
  <child>
    <object class="GtkBox" id="contenido_pestana1">
      <property name="visible">True</property>
      <property name="orientation">vertical</property>
      <!-- Contenido de la pestaña -->
    </object>
  </child>
  
  <!-- Más pestañas... -->
</object>

7.3 GtkDialog (Cuadros de diálogo)

<object class="GtkDialog" id="dialogo_mensaje">
  <property name="title" translatable="yes">Mensaje</property>
  <property name="modal">True</property>
  <property name="destroy_with_parent">True</property>
  
  <child internal-child="content_area">
    <object class="GtkBox">
      <property name="orientation">vertical</property>
      <property name="spacing">10</property>
      
      <child>
        <object class="GtkLabel" id="label_mensaje">
          <property name="label" translatable="yes">¿Está seguro?</property>
        </object>
      </child>
    </object>
  </child>
  
  <child internal-child="action_area">
    <object class="GtkButtonBox">
      <child>
        <object class="GtkButton" id="btn_aceptar">
          <property name="label" translatable="yes">Aceptar</property>
          <property name="can_default">True</property>
        </object>
      </child>
      
      <child>
        <object class="GtkButton" id="btn_cancelar">
          <property name="label" translatable="yes">Cancelar</property>
        </object>
      </child>
    </object>
  </child>
</object>

8. Personalización y Temas

8.1 CSS en GTK 3

GTK+ 3 permite estilizar widgets usando CSS:

Archivo de estilo (estilos.css):

/* Estilo para botones */
button {
    padding: 5px 10px;
    border-radius: 3px;
}

/* Estilo para el botón aceptar */
#btn_aceptar {
    background-color: #4CAF50;
    color: white;
}

/* Estilo para la ventana principal */
#ventana_principal {
    background-color: #f5f5f5;
}

Aplicar estilos en Python:

css_provider = Gtk.CssProvider()
css_provider.load_from_path("estilos.css")

screen = Gdk.Screen.get_default()
style_context = Gtk.StyleContext()
style_context.add_provider_for_screen(
    screen, css_provider, 
    Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
)

8.2 Clases de estilo

Puedes asignar clases CSS a widgets en Glade:

  1. Selecciona el widget
  2. Ve a la pestaña "Common"
  3. Añade clases en "CSS classes" (separadas por espacios)

Ejemplo CSS para clases:

.boton-destacado {
    font-weight: bold;
    border: 2px solid #3498db;
}

.etiqueta-error {
    color: #e74c3c;
}

9. Mejores Prácticas

9.1 Organización de proyectos

9.2 Internacionalización

Para hacer tu aplicación traducible:

Marcar texto traducible en Glade:

  1. Selecciona la propiedad (ej. "label" de un GtkLabel)
  2. Marca la casilla "Translatable"
  3. Proporciona un comentario para el traductor si es necesario

Generar archivo .pot:

xgettext --from-code=UTF-8 -o messages.pot *.glade

9.3 Accesibilidad

10. Ejemplo Completo

10.1 Editor de texto simple

Interfaz (editor.glade):

<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <requires lib="gtk+" version="3.20"/>
  
  <object class="GtkWindow" id="ventana_principal">
    <property name="title" translatable="yes">Editor de Texto</property>
    <property name="default_width">600</property>
    <property name="default_height">400</property>
    <signal name="destroy" handler="on_ventana_principal_destroy"/>
    
    <child>
      <object class="GtkBox" id="caja_principal">
        <property name="orientation">vertical</property>
        
        <child>
          <object class="GtkMenuBar" id="menubar">
            <child>
              <object class="GtkMenuItem" id="menuitem_archivo">
                <property name="label" translatable="yes">_Archivo</property>
                <property name="use_underline">True</property>
                
                <child>
                  <object class="GtkMenu" id="menu_archivo">
                    <child>
                      <object class="GtkMenuItem" id="menuitem_abrir">
                        <property name="label" translatable="yes">_Abrir</property>
                        <property name="use_underline">True</property>
                        <signal name="activate" handler="on_menuitem_abrir_activate"/>
                      </object>
                    </child>
                    
                    <child>
                      <object class="GtkMenuItem" id="menuitem_guardar">
                        <property name="label" translatable="yes">_Guardar</property>
                        <property name="use_underline">True</property>
                        <signal name="activate" handler="on_menuitem_guardar_activate"/>
                      </object>
                    </child>
                    
                    <child>
                      <object class="GtkSeparatorMenuItem"/>
                    </child>
                    
                    <child>
                      <object class="GtkMenuItem" id="menuitem_salir">
                        <property name="label" translatable="yes">_Salir</property>
                        <property name="use_underline">True</property>
                        <signal name="activate" handler="on_menuitem_salir_activate"/>
                      </object>
                    </child>
                  </object>
                </child>
              </object>
            </child>
          </object>
        </child>
        
        <child>
          <object class="GtkScrolledWindow" id="scrolledwindow">
            <property name="hexpand">True</property>
            <property name="vexpand">True</property>
            
            <child>
              <object class="GtkTextView" id="textview">
                <property name="wrap_mode">word</property>
              </object>
            </child>
          </object>
        </child>
        
        <child>
          <object class="GtkStatusbar" id="statusbar"/>
        </child>
      </object>
    </child>
  </object>
</interface>

Código Python (editor.py):

import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gio

class EditorTexto:
    def __init__(self):
        self.builder = Gtk.Builder()
        self.builder.add_from_file("editor.glade")
        self.builder.connect_signals(self)
        
        self.window = self.builder.get_object("ventana_principal")
        self.textview = self.builder.get_object("textview")
        self.statusbar = self.builder.get_object("statusbar")
        
        self.window.show_all()
    
    def on_ventana_principal_destroy(self, widget):
        Gtk.main_quit()
    
    def on_menuitem_abrir_activate(self, widget):
        dialog = Gtk.FileChooserDialog(
            title="Abrir archivo",
            parent=self.window,
            action=Gtk.FileChooserAction.OPEN
        )
        dialog.add_buttons(
            Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
            Gtk.STOCK_OPEN, Gtk.ResponseType.OK
        )
        
        response = dialog.run()
        if response == Gtk.ResponseType.OK:
            try:
                with open(dialog.get_filename(), 'r') as f:
                    buffer = self.textview.get_buffer()
                    buffer.set_text(f.read())
            except Exception as e:
                print("Error al abrir:", e)
        
        dialog.destroy()
    
    def on_menuitem_guardar_activate(self, widget):
        dialog = Gtk.FileChooserDialog(
            title="Guardar archivo",
            parent=self.window,
            action=Gtk.FileChooserAction.SAVE
        )
        dialog.add_buttons(
            Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
            Gtk.STOCK_SAVE, Gtk.ResponseType.OK
        )
        
        response = dialog.run()
        if response == Gtk.ResponseType.OK:
            try:
                buffer = self.textview.get_buffer()
                start, end = buffer.get_bounds()
                text = buffer.get_text(start, end, False)
                
                with open(dialog.get_filename(), 'w') as f:
                    f.write(text)
            except Exception as e:
                print("Error al guardar:", e)
        
        dialog.destroy()
    
    def on_menuitem_salir_activate(self, widget):
        self.window.destroy()

if __name__ == "__main__":
    app = EditorTexto()
    Gtk.main()

11. Recursos y Referencias

11.1 Documentación oficial

11.2 Tutoriales

11.3 Comunidades