Creando Widgets GTK

De Wiki do Ciclo ASIR do IES de Rodeira
Revisión feita o 12 de xuño de 2019 ás 09:32 por Xavi (conversa | contribucións)
Saltar á navegación Saltar á procura

Estructura dun Widget GTK+

GTK+ está programado en C, pero utilizando técnicas de orientación a objxectos. Un widget GTK+ non é máis que unha estructura que conten atributos (variables) e métodos (punteiros a funcións) representando as distintas sinais ás que vai a reaccionar o Widget.

O primeiro campo da estructura representando un Widget ten que ser a estructura da clase da que descende (herencia) de xeito que se poda facer un casting á clase pai.

En paralelo a estructura definindo o novo Widget, é necesario crear unha estructura para almacenar a información relativa a cada elemento (obxecto) creado. Dun xeito similar á definición da nova clase Widget, o primeiro campo desta estructura debe ser o obxecto pai.

Vexamos un exemplo coa clase GtkButton:

// Estructura definindo a nova clase
struct _GtkButtonClass
{
  GtkContainerClass parent_class; //Estructura que almacena a clase pai

  void (* pressed)  (GtkButton *button); // Sitio para os manexadores de sinal
  void (* released) (GtkButton *button);
  void (* clicked)  (GtkButton *button);
  void (* enter)    (GtkButton *button);
  void (* leave)    (GtkButton *button);
};

// Estructura definindo o novo obxecto
struct _GtkButton
{
  GtkContainer container; // Estructura que conten o obxecto pai

  GtkWidget *child;

  guint in_button : 1;
  guint button_down : 1;
};

Pódese observar claramente que o primeiro campo das estructuras _GtkButtonClass e _GtkButton permiten facer un cast á clase pai con total seguridade. Tamén podemos observar os atributos dos obxectos en _GtkButton e os punteiros ós tratamentos de sinal en _GtkButtonClass.

Creando un Novo Widget: GtkCalendarButton

Este widget consistirá nun botón que amosará unha data. O pulsar o botón aparecerá unha ventana emerxente cun calendario que permitirá elexir unha data que será a que se poña no botón. A clase pai do GtkCalendarButton será GtkVBoxClass e o obxecto pai GtkVBox, xa que o botón aparecerá nun contedor.

Ficheiro de Cabeceira

Descargar

/* GtkCalendarButton
 * Copyright (C) 2007 Francisco Javier Taboada Aguado
 *
 * Esta librería é software libre; poderás redistribuilo e/ou
 * modificalo baixo os termos da Licencia Xeral Pública GNU (GPL) tal
 * e como indica a Free Software Foundation; baixo a versión 2 
 * ou posteriores.
 *
 * Esta librería distribúese coa esperanza de que sexa útil, pero
 * SEN NINGUNHA GARANTIA; incluso sen a garantizar a súa adecuación
 * para un propósito concreto. Véxase a GPL para máis detalles.
 *
 * Deberías ter recibida unha copia da GPL xunto con esta librería,
 * se non fora así escribe a :
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */
#ifndef __GTK_CALENDAR_BUTTON_H__
#define __GTK_CALENDAR_BUTTON_H__


#include <gdk/gdk.h>
#include <gtk/gtkvbox.h>
#include <gtk/gtktogglebutton.h>
#include <gtk/gtkcalendar.h>

G_BEGIN_DECLS

// Declaración dos novos tipos
#define GTK_TYPE_CALENDAR_BUTTON         (gtk_calendar_button_get_type ())
#define GTK_CALENDAR_BUTTON(obj)         (G_TYPE_CHECK_INSTANCE_CAST ((obj), \\
                                          GTK_TYPE_CALENDAR_BUTTON, GtkCalendarButton))
#define GTK_CALENDAR_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), \\
                                          GTK_TYPE_CALENDAR_BUTTON, GtkCalendarButtonClass))
#define GTK_IS_CALENDAR_BUTTON(obj)               (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \\
                                                   GTK_TYPE_CALENDAR_BUTTON))
#define GTK_IS_CALENDAR_BUTTON_CLASS(klass)       (G_TYPE_CHECK_CLASS_TYPE ((klass), \\
                                                   GTK_TYPE_CALENDAR_BUTTON))
#define GTK_CALENDAR_BUTTON_GET_CLASS(obj)        (G_TYPE_INSTANCE_GET_CLASS ((obj), \\
                                                   GTK_TYPE_CALENDAR_BUTTON, GtkCalendarButtonClass))


typedef struct _GtkCalendarButton       GtkCalendarButton;
typedef struct _GtkCalendarButtonClass  GtkCalendarButtonClass;

// Estructura para o obxecto
struct _GtkCalendarButton
{
  GtkVBox ct;

  GtkButton *bt;
  GtkCalendar *cal;
  GtkWindow *win;
};

// Estructura para a clase
struct _GtkCalendarButtonClass
{
  GtkVBoxClass parent_class;

  void (* calendar_show)  (GtkCalendarButton *cbutton);  // Sitio para poñer os handlers
  void (* calendar_hide) (GtkCalendarButton *cbutton);    // Sitio para poñer os handlers
  void (* calendar_select) (GtkCalendarButton *cbutton); // Sitio para poñer os handlers

  /* Padding for future expansion */
  void (*_gtk_reserved1) (void);
  void (*_gtk_reserved2) (void);
  void (*_gtk_reserved3) (void);
  void (*_gtk_reserved4) (void);
};

// Tipo de datos para almacenar as datas
typedef struct tagGtkCBDate {
  int dia;
  int mes;
  int ano;
} GtkCBDate;


// Funcións de uso externo
GType      gtk_calendar_button_get_type          (void) G_GNUC_CONST;
GtkWidget* gtk_calendar_button_new               (void);
GtkWidget *gtk_calendar_button_new_with_date (GtkCBDate *date);
GtkCBDate *gtk_calendar_button_get_date(GtkCalendarButton *cb,GtkCBDate *date);
void gtk_calendar_button_set_date(GtkCalendarButton *cb,GtkCBDate *date);

G_END_DECLS

#endif /* __GTK_CALENDAR_BUTTON_H__ */

Implementación

Descargar

/* GtkCalendarButton
 * Copyright (C) 2007 Francisco Javier Taboada Aguado
 *
 * Esta librería é software libre; poderás redistribuilo e/ou
 * modificalo baixo os termos da Licencia Xeral Pública GNU (GPL) tal
 * e como indica a Free Software Foundation; baixo a versión 2 
 * ou posteriores.
 *
 * Esta librería distribúese coa esperanza de que sexa útil, pero
 * SEN NINGUNHA GARANTIA; incluso sen a garantizar a súa adecuación
 * para un propósito concreto. Véxase a GPL para máis detalles.
 *
 * Deberías ter recibida unha copia da GPL xunto con esta librería,
 * se non fora así escribe a :
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#include <gtk/gtklabel.h>
#include <gtk/gtkmain.h>
#include <gtk/gtksignal.h>
#include <gtk/gtkprivate.h>
#include <gtk/gtkmarshal.h>

#include "gtkcalendarbutton.h"

// Sinais que emite o widget
enum {
  GTKCALENDARBUTTONSHOW_SIGNAL,
  GTKCALENDARBUTTONHIDE_SIGNAL,
  GTKCALENDARBUTTONSELECT_SIGNAL,
  LAST_SIGNAL
};

static void gtk_calendar_button_class_init (GtkCalendarButtonClass *class);
static void gtk_calendar_button_init (GtkCalendarButton *calendar_button);

static void gtk_calendar_button_pressed       (GtkButton            *button);
static void gtk_calendar_button_released      (GtkButton            *button);
static void gtk_calendar_button_clicked       (GtkButton            *button);
static gboolean gtk_calendar_button_press_event( GtkWidget  *widget,  GtkCalendarButton *calendar_button );
static gboolean gtk_calendar_button_day_selected( GtkWidget  *widget,  GtkCalendarButton *calendar_button );

char *parseDate(GtkCBDate *date,char *buffer);

// Táboa para garda-los sinais.
static guint calendar_button_signals[LAST_SIGNAL] = { 0 };

// Definimos GtkCalendarButton como heredada de VBOX.
G_DEFINE_TYPE (GtkCalendarButton, gtk_calendar_button, GTK_TYPE_VBOX)

/* Inicialización da clase.
 *
 * Aquí sobreescribimos métodos da clase e creamos os sinais.
 */
static void gtk_calendar_button_class_init (GtkCalendarButtonClass *class)
{
   GObjectClass *gobject_class;

   gobject_class = G_OBJECT_CLASS (class);
/* Poderíamos acceder ós métodos das clases pais para modifica-lo seu comportamento.

  
  GtkWidgetClass *widget_class;
  GtkContainerClass *container_class;
  GtkVBoxClass *vbox_class;
  
  widget_class = (GtkWidgetClass*) class;
  container_class = (GtkContainerClass*) class;
  vbox_class = (GtkVBoxClass*) class;

  gobject_class->set_property = gtk_calendar_button_set_property;
  gobject_class->get_property = gtk_calendar_button_get_property;

  widget_class->expose_event = gtk_calendar_button_expose;
  widget_class->mnemonic_activate = gtk_calendar_button_mnemonic_activate;
*/
  

  class->calendar_show = NULL;
  class->calendar_hide = NULL;  
  class->calendar_select = NULL;

  calendar_button_signals[GTKCALENDARBUTTONSHOW_SIGNAL] =
    g_signal_new ("calendar_show",
		  G_OBJECT_CLASS_TYPE (gobject_class),
		  G_SIGNAL_RUN_FIRST,
		  G_STRUCT_OFFSET (GtkCalendarButtonClass, calendar_show),
		  NULL, NULL,
		  gtk_marshal_VOID__VOID,
		  G_TYPE_NONE, 0);

 calendar_button_signals[GTKCALENDARBUTTONHIDE_SIGNAL] =
    g_signal_new ("calendar_hide",
		  G_OBJECT_CLASS_TYPE (gobject_class),
		  G_SIGNAL_RUN_FIRST,
		  G_STRUCT_OFFSET (GtkCalendarButtonClass, calendar_hide),
		  NULL, NULL,
		  gtk_marshal_VOID__VOID,
		  G_TYPE_NONE, 0);
 calendar_button_signals[GTKCALENDARBUTTONSELECT_SIGNAL] =
    g_signal_new ("calendar_select",
		  G_OBJECT_CLASS_TYPE (gobject_class),
		  G_SIGNAL_RUN_LAST,
		  G_STRUCT_OFFSET (GtkCalendarButtonClass, calendar_select),
		  g_signal_accumulator_true_handled, NULL,
		  gtk_marshal_BOOLEAN__POINTER,
		  G_TYPE_BOOLEAN, 1,GTK_TYPE_POINTER);
}

/* Auxiliar
 * A partir dunha data numérica devolve unha representación alfabética
 * TODO: Internacionalización.
 */
char *parseDate(GtkCBDate *date,char *buffer)
{
    char *meses[]={"Xaneiro","Febreiro","Marzo","Abril","Maio","Xuño","Xullo","Agosto",
                   "Setembro","Outubre","Novembro","Decembro"};

    snprintf(buffer,255,"%d de %s de %d",date->dia,meses[(date->mes)-1],date->ano);
    return buffer;
}
/***************/

/* Inicialización do Obxecto
 *
 * Aquí creamos os atributos do obxecto.
 *
 */
static void gtk_calendar_button_init (GtkCalendarButton *calendar_button)
{
  int dia,mes,ano;
  GtkCBDate date;
  char buffer[256];
  
  // Creamos unha ventana popup e incrustamos un calendario nela
  calendar_button->win=(GtkWindow *)gtk_window_new(GTK_WINDOW_POPUP);
  gtk_window_set_position(calendar_button->win,GTK_WIN_POS_MOUSE);
  calendar_button->cal=(GtkCalendar *)gtk_calendar_new();
  gtk_container_add(GTK_CONTAINER(calendar_button->win),GTK_WIDGET(calendar_button->cal));

  // Creamos o botón e o poñemos no widget (é da clase VBox..)
  calendar_button->bt=(GtkButton *)gtk_button_new();
  gtk_container_add(GTK_CONTAINER(calendar_button),GTK_WIDGET(calendar_button->bt));

  // Conectamos as sinais de clicked do botón e de seleccionar unha data no calendario con doble click.
  g_signal_connect(G_OBJECT(calendar_button->bt),"clicked",
                   G_CALLBACK(gtk_calendar_button_press_event),(gpointer)calendar_button);

  g_signal_connect(G_OBJECT(calendar_button->cal),"day_selected_double_click",
                   G_CALLBACK(gtk_calendar_button_day_selected),(gpointer)calendar_button);

  // Ocultamos o calendario, e poñemos a data de hoxe como etiqueta do botón
  gtk_widget_hide(GTK_WIDGET(calendar_button->win));
  gtk_calendar_get_date(calendar_button->cal,&date.ano,&date.mes,&date.dia);
  date.mes++;
  gtk_button_set_label(calendar_button->bt,parseDate(&date,buffer));

  // Facemos visible o botón.
  gtk_widget_show_all(GTK_WIDGET(calendar_button->bt));
}

/* Crea un novo obxecto gtk_calendar_button
 *
 */
GtkWidget*gtk_calendar_button_new (void)
{
   return g_object_new (GTK_TYPE_CALENDAR_BUTTON, NULL);
}

/* Crea un novo obxecto gtk_calendar_button coa data indicada
 *
 */
GtkWidget *gtk_calendar_button_new_with_date (GtkCBDate *date)
{
    GtkWidget *w;

    w=gtk_calendar_button_new();
    gtk_calendar_button_set_date(GTK_CALENDAR_BUTTON(w),date);
    return w;
}
/* O usuario fixo un doble click nunha data. Cambiamos a etiqueta do botón e ocultamos o calendario
 *
 */
static gboolean gtk_calendar_button_day_selected( GtkWidget  *widget,  GtkCalendarButton *calendar_button ) 
{
   GtkCBDate date;
   char buffer[256];
   gboolean ret;

   // Recuperamos a data
   gtk_calendar_get_date(calendar_button->cal,&date.ano,&date.mes,&date.dia);
   date.mes++;
   // Sinal de seleccionada a data.
   g_signal_emit (G_OBJECT(calendar_button),calendar_button_signals[GTKCALENDARBUTTONSELECT_SIGNAL], 0,&date,&ret);
   if (!ret) // Se devolve TRUE, non fago nada
   {   
       gtk_button_set_label(calendar_button->bt,parseDate(&date,buffer));
       // Sinal de ocultar
       g_signal_emit (G_OBJECT(calendar_button),calendar_button_signals[GTKCALENDARBUTTONHIDE_SIGNAL], 0);
       gtk_widget_hide(GTK_WIDGET(calendar_button->win));
   }
   return TRUE;
}

/* O usuario fixo click no botón de abrir o calendario
 *
 */
static gboolean gtk_calendar_button_press_event( GtkWidget  *widget,  GtkCalendarButton *calendar_button ) {
   // Sinal de Amosar
   g_signal_emit (G_OBJECT(calendar_button),calendar_button_signals[GTKCALENDARBUTTONHIDE_SIGNAL], 0);
   gtk_widget_show_all(GTK_WIDGET(calendar_button->win));
   return TRUE;
}

/* Devolve a data do botón
 *
 */
GtkCBDate *gtk_calendar_button_get_date(GtkCalendarButton *cb,GtkCBDate *date)
{
    gtk_calendar_get_date(cb->cal,&(date->ano),&(date->mes),&(date->dia));
    date->mes++;
    return date;
}

/* Pon unha data no botón
 *
 */
void gtk_calendar_button_set_date(GtkCalendarButton *cb,GtkCBDate *date)
{
    char buffer[256];

    gtk_calendar_select_month(cb->cal,(date->mes)-1,date->ano);
    gtk_calendar_select_day(cb->cal,date->dia);
    gtk_button_set_label(cb->bt,parseDate(date,buffer));
}

#define __GTK_CALENDAR_BUTTON_C__

Proba

Descargar

/* GtkCalendarButton
 * Copyright (C) 2007 Francisco Javier Taboada Aguado
 *
 * Esta librería é software libre; poderás redistribuilo e/ou
 * modificalo baixo os termos da Licencia Xeral Pública GNU (GPL) tal
 * e como indica a Free Software Foundation; baixo a versión 2 
 * ou posteriores.
 *
 * Esta librería distribúese coa esperanza de que sexa útil, pero
 * SEN NINGUNHA GARANTIA; incluso sen a garantizar a súa adecuación
 * para un propósito concreto. Véxase a GPL para máis detalles.
 *
 * Deberías ter recibida unha copia da GPL xunto con esta librería,
 * se non fora así escribe a :
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#include <stdio.h>
#include <stdlib.h>
#include <gtk/gtk.h>
#include "gtkcalendarbutton.h"

gboolean seleccionada(GtkCalendarButton *cb,GtkCBDate *d)
{
    printf("Seleccionada %d/%d/%d!!!\n",d->dia,d->mes,d->ano);
    return FALSE;
}

int main( int   argc,char *argv[])
{
  GtkWidget *window;
  GtkWidget *cb;
  
  gtk_init (&argc, &argv);

  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title (GTK_WINDOW (window), "Dial");
  g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (exit), NULL);
  
  gtk_container_set_border_width (GTK_CONTAINER (window), 10);

  cb=gtk_calendar_button_new();
  gtk_container_add (GTK_CONTAINER (window), cb);
  g_signal_connect(G_OBJECT(cb),"calendar_select",G_CALLBACK(seleccionada),NULL);

  gtk_widget_show_all (window);
  
  gtk_main ();
  
  return 0;
}

Habilitando o Widget no Glade

Esta característica so está dispoñible a partir do Glade-3.

Introducción

A partir do Glade-3 é posible incorporar novos Widgets á paleta e manexalos co interface engadindo "plugins". Un "plugin" consiste nun ficheiro de descrición, unha librería dinámica e as iconas para os novos widgets na paleta e no árbol de widgets.

A meirande parte das propiedades dos widgets poden manexarse dun xeito automático, pero pode ser necesario código para algunhas porpiedades avanzadas. Todo esto se especifica no ficheiro de descrición onde se poden especificar valores por defecto, propiedades non visibles, funcións específicas a chamar... etc.

O ficheiro de descrición tamén se utiliza para agrupar widgets en categorías que se amosarán na paleta de glade-3.

O Ficheiro de Descrición (Catálogo)

O ficheiro de descrición é un ficheiro XML coa seguinte estructura:

<glade-catalog name="nome_do_catalogo" library="nome_libraría_dinámica">

  <init-function>función de inicialización</init-function>

  <glade-widget-classes>

    ... definición dos widgets

  </glade-widget-classes>

  ... definición dos grupos

</glade-catalog>

O Tag glade-catalog

Propiedades Obrigatorias
  • name: E unha cadea de caracteres identificando o catálogo.
  • library: E o nome da librería que se instalará en $prefix/lib/glade-3/modules
Propiedades Opcionais
  • depends: Utilízase para indicar a herencia do widget poñendo o 'name' do catálogo necesario. Normalmente indicarase depends="gtk+".
  • domain: Dominio para o soporte multilinguaxe GTK.
  • book: Espacio de nomes para a documentación mediante gtk-doc.
  • fixed: Para permitir que o widget sexa manexado co administrador de deseño GtkFixed.

O Tag init-function

Utilízase para dar un puntode entrada global o plugin. Se chamará antes que se cree calquera widget do catálogo.

Definición de Clases de Widgets

<glade-widget-class name="nome_da_clase" generic-name="nome_xenerico_obxecto" title="Nome_na_paleta">

  ... Funcións de soporte do widget

  <properties>

    ... definición de propidades

  </properties>

  <children>

    ... descrición dos fillos

  </children>
</glade-widget-class>

Definición de Grupos

Instalación

  • Copiar o catálogo a /usr/share/glade3/catalogs/
  • Copiar o icono a /usr/share/glade3/pixmaps/22x22/
  • Copiar a librería a /usr/lib/glade3/modules/

O catálogo para un widget como o do exemplo, é moi sinxelo, e ademáis non precisa de libraría de soporte xa que é moi simple.

<?xml version="1.0" encoding="UTF-8"?>
<glade-catalog name="gtkcalendarbutton" library="gtkcalendarbutton" domain="glade3" book="gtkcalendarbutton">

	<glade-widget-classes>
		<glade-widget-class name="GtkCalendarButton"  generic-name="gtkcalendarbutton" title="Calendar">
		</glade-widget-class>
	</glade-widget-classes>
  
	<glade-widget-group name="Custom" title="Widget Personais">
    <glade-widget-class-ref name="GtkCalendarButton"/>
  </glade-widget-group>

</glade-catalog>

Referencias

Este tutorial é básicamente unha traducción do tutorial de gtk.org