GTK+ y pthread

Programa en GTK+ con una barra de progreso que cambia según el progreso de un thread. Este ejemplo es una versión modificada del ejemplo de progress bar del tutorial de GTK+ al que se le agregó un thread POSIX (pthread). El progress bar que se actualiza según el “progreso” generado por el otro thread.

/*
 * Ejemplo de progress bar usando POSIX Threads.
 *
 * Este ejemplo está basado en el ejemplo de progress bar del tutorial de GTK+
 * al que se le agregó un thread que va cambiando el porcentaje de manera
 * aleatoria.
 *
 * http://www.gtk.org/tutorial/sec-progressbars.html
 *
 * Modificado por Leandro Lucarella [luca en llucax.hn.org]
 * sáb may 21 16:32:22 ART 2005
 *
 */
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <gtk/gtk.h>
typedef struct _ProgressData {
  GtkWidget *window;
  GtkWidget *pbar;
  int timer;
  gboolean activity_mode;
  gdouble val;
  /* mutex para el valor que será accedido desde varios threads */
  pthread_mutex_t val_mutex;
} ProgressData;

/* indica si hay un thread andando (no lo protejo porque es trivial y lo peor
 * que puede pasar en los casos límite es que se lancen 2 threads, avanzando
 * más rápido el contador, o que no se lance un nuevo thread al apretar limpiar
 * y haya que apretarlo de nuevo, dos efectos que no son graves para este
 * ejemplo en particular) */
int running = 0;
/* Función que corre en un thread y va actualizando el contador */
static void* count_add(void* data) {
  ProgressData* pdata = (ProgressData*)data;
  running = 1; /* empezó a correr */
  pthread_mutex_lock(&(pdata->val_mutex));
  pdata->val = 0.0; /* reseteo contador */
  while (pdata->val < 1.0) {
    pdata->val += 0.05;
    if (pdata->val > 1.0) pdata->val = 1.0; /* error de redondeo */
    pthread_mutex_unlock(&(pdata->val_mutex));
    usleep(rand()%400000+100000); /* duerme entre 0.1 y 0.4 segundos */
    pthread_mutex_lock(&(pdata->val_mutex));
  }
  pthread_mutex_unlock(&(pdata->val_mutex));
  running = 0;
  return NULL;
}

/* Callback para resetear el contador */
static void count_reset( GtkWidget    *widget,
  ProgressData *pdata) {
  /* si no está corriendo, lo lanzo de nuevo */
  if (!running) {
    /* creo un nuevo thread y lo 'libero' */
    pthread_t thread;
    pthread_create(&thread, NULL, count_add, pdata);
    pthread_detach(thread);
  } else {/* si está corriendo, lo reseteo */
    pthread_mutex_lock(&(pdata->val_mutex));
    pdata->val = 0.0;
    pthread_mutex_unlock(&(pdata->val_mutex));
  }
}

/* Update the value of the progress bar so that we get
 * some movement */
static gboolean progress_timeout( gpointer data ) {
  ProgressData *pdata = (ProgressData *)data;
  if (pdata->activity_mode)
    gtk_progress_bar_pulse (GTK_PROGRESS_BAR (pdata->pbar));
  else {
    gdouble new_val;
    /* obtengo el valor lockeando para que no lo corrompa otro thread mientras
     * lo obtengo */
    pthread_mutex_lock(&(pdata->val_mutex));
    new_val = pdata->val;
    pthread_mutex_unlock(&(pdata->val_mutex));
    /* Set the new value */
    gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (pdata->pbar), new_val);
  }
  /* As this is a timeout function, return TRUE so that it
   * continues to get called */
   return TRUE;
}

/* Callback that toggles the text display within the progress bar trough */
static void toggle_show_text( GtkWidget* widget, ProgressData *pdata ) {
  const gchar *text;
  text = gtk_progress_bar_get_text (GTK_PROGRESS_BAR (pdata->pbar));
  if (text && *text)
    gtk_progress_bar_set_text (GTK_PROGRESS_BAR (pdata->pbar), "");
  else
    gtk_progress_bar_set_text (GTK_PROGRESS_BAR (pdata->pbar), "some text");
}

/* Callback that toggles the activity mode of the progress bar */
static void toggle_activity_mode( GtkWidget *widget, ProgressData *pdata ) {
  pdata->activity_mode = !pdata->activity_mode;
  if (pdata->activity_mode)
    gtk_progress_bar_pulse (GTK_PROGRESS_BAR (pdata->pbar));
  else
    gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (pdata->pbar), 0.0);
}

/* Callback that toggles the orientation of the progress bar */
static void toggle_orientation( GtkWidget *widget, ProgressData *pdata ) {
  switch (gtk_progress_bar_get_orientation (GTK_PROGRESS_BAR (pdata->pbar))) {
    case GTK_PROGRESS_LEFT_TO_RIGHT:
      gtk_progress_bar_set_orientation (GTK_PROGRESS_BAR (pdata->pbar),
      GTK_PROGRESS_RIGHT_TO_LEFT);
      break;
    case GTK_PROGRESS_RIGHT_TO_LEFT:
      gtk_progress_bar_set_orientation (GTK_PROGRESS_BAR (pdata->pbar),
      GTK_PROGRESS_LEFT_TO_RIGHT);
      break;
    default:;
      /* do nothing */
  }
}

/* Clean up allocated memory and remove the timer */
static void destroy_progress( GtkWidget *widget, ProgressData *pdata) {
  g_source_remove (pdata->timer);
  pdata->timer = 0;
  pdata->window = NULL;
  g_free (pdata);
  pthread_mutex_destroy(&(pdata->val_mutex));
  gtk_main_quit ();
}

int main( int argc, char *argv[]) {
  pthread_t thread;
  ProgressData *pdata;
  GtkWidget *align;
  GtkWidget *separator;
  GtkWidget *table;
  GtkWidget *button;
  GtkWidget *check;
  GtkWidget *vbox;
  gtk_init (&argc, &argv);
  /* Allocate memory for the data that is passed to the callbacks */
  pdata = g_malloc (sizeof (ProgressData));
  /* Inicializo mutex */
  pthread_mutex_init(&(pdata->val_mutex), NULL);
  pdata->window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  gtk_window_set_resizable (GTK_WINDOW (pdata->window), TRUE);
  /* Conecto señal */
  g_signal_connect (G_OBJECT (pdata->window), "destroy",
                    G_CALLBACK (destroy_progress), (gpointer) pdata);
  gtk_window_set_title (GTK_WINDOW (pdata->window), "GtkProgressBar");
  gtk_container_set_border_width (GTK_CONTAINER (pdata->window), 0);
  vbox = gtk_vbox_new (FALSE, 5);
  gtk_container_set_border_width (GTK_CONTAINER (vbox), 10);
  gtk_container_add (GTK_CONTAINER (pdata->window), vbox);
  gtk_widget_show (vbox);
  /* Create a centering alignment object */
  align = gtk_alignment_new (0.5, 0.5, 0, 0);
  gtk_box_pack_start (GTK_BOX (vbox), align, FALSE, FALSE, 5);
  gtk_widget_show (align);
  /* Create the GtkProgressBar */
  pdata->pbar = gtk_progress_bar_new ();
  pdata->activity_mode = FALSE;
  gtk_container_add (GTK_CONTAINER (align), pdata->pbar);
  gtk_widget_show (pdata->pbar);
  /* Add a timer callback to update the value of the progress bar */
  pdata->timer = g_timeout_add (100, progress_timeout, pdata);
  separator = gtk_hseparator_new ();
  gtk_box_pack_start (GTK_BOX (vbox), separator, FALSE, FALSE, 0);
  gtk_widget_show (separator);
  /* rows, columns, homogeneous */
  table = gtk_table_new (2, 3, FALSE);
  gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, TRUE, 0);
  gtk_widget_show (table);
  /* Add a check button to select displaying of the trough text */
  check = gtk_check_button_new_with_label ("Show text");
  gtk_table_attach (GTK_TABLE (table), check, 0, 1, 0, 1,
  GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 5, 5);
  g_signal_connect (G_OBJECT (check), "clicked",
                    G_CALLBACK (toggle_show_text), (gpointer) pdata);
  gtk_widget_show (check);
  /* Add a check button to toggle activity mode */
  check = gtk_check_button_new_with_label ("Activity mode");
  gtk_table_attach (GTK_TABLE (table), check, 0, 1, 1, 2,
  GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL,
  5, 5);
  g_signal_connect (G_OBJECT (check), "clicked",
                    G_CALLBACK (toggle_activity_mode), (gpointer) pdata);
  gtk_widget_show (check);
  /* Add a check button to toggle orientation */
  check = gtk_check_button_new_with_label ("Right to Left");
  gtk_table_attach (GTK_TABLE (table), check, 0, 1, 2, 3,
  GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 5, 5);
  g_signal_connect (G_OBJECT (check), "clicked",
                    G_CALLBACK (toggle_orientation), (gpointer) pdata);
  gtk_widget_show (check);
  /* Agrego botón para resetear contador */
  button = gtk_button_new_from_stock ("gtk-clear");
  g_signal_connect (G_OBJECT (button), "clicked",
                   G_CALLBACK (count_reset), pdata);
  gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
  gtk_widget_show (button);
  /* Add a button to exit the program */
  button = gtk_button_new_from_stock ("gtk-quit");
  g_signal_connect_swapped (G_OBJECT (button), "clicked",
                           G_CALLBACK (gtk_widget_destroy),  G_OBJECT (pdata->window));
  gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
  /* This makes it so the button is the default. */
  GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
  /* This grabs this button to be the default button. Simply hitting
   * the "Enter" key will cause this button to activate. */
  gtk_widget_grab_default (button);
  gtk_widget_show (button);
  gtk_widget_show (pdata->window);
  /* Creo thread y lo dejo en libertad (para no tener que hacer un join
   * después */
  pthread_create(&thread, NULL, count_add, pdata);
  pthread_detach(thread);
  gtk_main ();
  return 0; /* no debería ejecutarse nunca */
}

Creamos el archivo progressbar.c con el siguiente contenido

Para compilar utilizamos el siguiente Makefile

CC = gcc

CFLAGS = -Wall			 	\
	-DG_DISABLE_DEPRECATED 	 	\
	-DGDK_DISABLE_DEPRECATED 	\
	-DGDK_PIXBUF_DISABLE_DEPRECATED \
	-DGTK_DISABLE_DEPRECATED

progressbar: progressbar.c
	$(CC) progressbar.c -o progressbar $(CFLAGS) `pkg-config gtk+-2.0 --cflags --libs` -lpthread

clean:
	rm -f *.o progressbar