Manual
logo



Programación C++ con AGU desde 0

Cronómetro Double-Dutch

¿Quééeeeeee?
A partir de aquí, básicamente iré añadiendo programas que implican la utilización de determinados controles o aspectos. Voy a desarrollarlos progresivamente, poco a poco como buen aficionado, aunque lo suyo es primero realizar un diseño completo y luego proceder a su implementación, pero no es ésta una página profesional.

Controlando el tiempo

Problema

En Rope-Skipping, modalidad deportiva de la comba, existe una prueba [DDRS] de saltos con dos combas [Double Dutch] donde cuatro deportistas se relevan [Relay] en el salto tratando de realizar el mayor número de saltos posible [Speed]. Cada deportista tiene 45 segundos [bueno, el primero dispone 45 s., el resto tiene 45 s., pero en ellos están incluidos el tiempo del  intercambio de las posiciones.
Mi problema es que mi reloj no me permite realizar cuenta atrás de menos de 1 minuto  [un secretillo, maestrodenada es -soy- un profesor de educación física]. Podría cambiar de reloj, o aprovechar la ocasión para realizar un programilla [afortunadamente, trabajo en un centro TIC y dispongo de ordenadores con software libre, ¡qué lujazo!].

Pero, ¿cómo se controla el tiempo? Existen infinidad de programas que requieren realizar las operaciones en un tiempo determinado. Imagina por ejemplo un archivo de audio o vídeo, que esperará a que terminarás de editar un archivo de texto para enviar a la tarjeta de sonido la información del audio... sería imposible escuchar música a la par que escribíamos un texto.
Hay programas que requieren una realización ajustada al tiempo real... es el caso de nuestro cronómetro. Cada x tiempo, nuestro programa debe recuperar el control del ordenador.

¿control timer?

En otros lenguajes existe un control timer (invisible) que uno añade a sus ventanas, este control tiene asociada una función, la cual se ejecuta cada cierto tiempo. Tiempo que viene establecido en una propiedad Interval o similar.
En GTK no existen estos controles, no veremos en Glade un control timer que podamos añadir fácilmente a nuestra ventana.
Una vez conocido el procedimiento, resulta igualmente fácil.Para controlar el tiempo, tenemos que crear un objeto sigc::connection (como las señales de eventos), con la peculiaridad)que el slot al que va asociado es una función bool (que devuelve true o false), si devuelve true, el proceso se continua ejecutando, pero si devuelve false se termina el proceso y no se vuelve a ejecutar la función asociada. Esta "conexión" lleva un segundo argumento que es el tiempo, en milisegundos, en el que se suceden la ejecución de la función... bla, bla, bla... Vayamos a la práctica:
Crea un nuevo proyecto (o utiliza el anterior, de hecho...). Edita el  archivo .glade igual a anteriores programas: una label con nombre etiqueta, y un botón de nombre boton. En la etiqueta nos aparecerá el deportista que ha de saltar y el tiempo restante. La prueba comenzará al pulsar el botón (no al iniciar el programa) y, una vez pulsado, debemos de desactivarlo hasta que ha finalizado la prueba.
//main.cc
#include <libglademm/xml.h>
#include <gtkmm.h>
#include <iostream>


#ifdef ENABLE_NLS
#  include <libintl.h>
#endif

#include "cron.h"

/* For testing propose use the local (not installed) glade file */
/* #define GLADE_FILE PACKAGE_DATA_DIR"/comba/glade/comba.glade" */
#define GLADE_FILE "comba.glade"
  
int
main (int argc, char *argv[])
{
    Gtk::Main popgrama(argc, argv);
   
    //Load the Glade file and instiate its widgets:
    Glib::RefPtr<Gnome::Glade::Xml> refXml;
    try
    {
        refXml = Gnome::Glade::Xml::create(GLADE_FILE);
    }
    catch(const Gnome::Glade::XmlError& ex)
    {
        std::cerr << ex.what() << std::endl;
        return 1;
    }
    crono* ventana = 0;
        refXml->get_widget_derived("miventanica", ventana);
    if (ventana)
    {
        popgrama.run(*ventana);
    }
    delete ventana;
    return 0;
}
El archivo main.cc, no tiene nada que comentar. Si algo no comprendes es que te has saltado algún apartado o, lo que es peor, me explico fatal. Se detecta que he cambiado el nombre de los ficheros adjuntos (ya no son miventanica.h y miventanica.cc): cron.h y cron.cc. En cron.h debo encontrarme una clase heredada de Gtk::Window (por refXml->get_widget_derived) de nombre crono.

//crono.h
#ifndef _CRONO_COMBA_H
#define _CRONO_COMBA_H
#include <gtkmm.h>
#include <libglademm/xml.h>

class crono:public Gtk::Window
{
     public:
         crono(BaseObjectType* www, const Glib::RefPtr<Gnome::Glade::Xml>& refGlade);

        virtual ~crono();
     
    protected:
        int segundos;
        int veces;
         Glib::RefPtr<Gnome::Glade::Xml> refXml;
      
         Gtk::Button* boton;
         Gtk::Label* etiqueta;

        sigc::connection conexion;

        bool tiempo();

        // señales
        virtual void pulsa_contador();
 
};
#endif
En la declaración de la clase se observa el objeto sigc::connection (conexion) y la función bool tiempo(). También hemos incorporado una variable entera segundos, donde vamos a controlar el número de segundos y la variable veces que utilizaremos para ver cuántas veces se ha realizado la cuenta (el número de sujetos que ha saltado). Todo el meollo del programa está en crono.cc
//cron.cc

#include <gtkmm.h>
#include <iostream>
#include <stdlib.h>
#include <libglademm/xml.h>
#include "cron.h"
//constructor
crono::crono(BaseObjectType* www, const Glib::RefPtr<Gnome::Glade::Xml>& refGlade):Gtk::Window(www),refXml(refGlade)
{
        segundos=45;
        veces=1;
        refXml->get_widget("boton", boton);
     
        boton->signal_clicked().connect(sigc::mem_fun(*this,&crono::pulsa_contador));
    
        refXml->get_widget("etiqueta", etiqueta);

}

// destructor
crono::~crono(){std::cout<<"Eso es todo amigo"<<std::endl; conexion.disconnect();}
 
// función para manipular la señal
void crono::pulsa_contador(){
    sigc::connection con = Glib::signal_timeout().connect((sigc::slot<bool>)sigc::mem_fun(*this,&crono::tiempo),1000);
    conexion=con;
    char h[40];
    sprintf(h,"Sujeto 1-> 45:00 segundos");
    etiqueta->set_text(h);
    boton->set_sensitive(false);
}

bool crono::tiempo(){
    char h[40];
    if (veces<=4){
        if (segundos>1){
            sprintf (h, "Sujeto %i-> %i:00 segundos",veces, --segundos);
           
        }
        else{
            if(veces!=4)sprintf(h,"CAMBIO");else sprintf(h,"Pulsa abajo para comenzar la prueba");
            segundos=45;
            veces++;
       
        }
        etiqueta->set_text(h);
        return true;
    }
    else{
       
        boton->set_sensitive();
        veces=1;
        return false;
    }
}
En el constructor: se inicializa segundos a 45 (duración de la prueba para cada deportista) y veces.
En el destructor: se llama al método disconnect(), por si el programa termina precipitadamente y no concluye la cuenta atrás.
En crono::pulsa_contador():
sigc::connection con = Glib::signal_timeout().connect((sigc::slot<bool>)sigc::mem_fun(*this,&crono::tiempo),1000);Cuando se pulsa el botón, se crea un sigc::connection, el slot se obtiene como siempre con sigc::mem_fun, pero se declara del tipo bool, (sigc::slot<bool>), como segundo argumento hemos puesto 1000, que indica 1000 milisegundos (1 segundo). Es decir, que cada segundo se ejecutará la función crono::tiempo().
conexion=con;con es una variable local. Es decir, que una vez fuera de la función no podemos acceder a ella. Como crea una conexión que no se destruye al terminar la función, asignamos su valor a la variable conexion de la clase, para poder deshacer la misma si fuera necesario.
boton->set_sensitive(false);
Inhabilita el botón.

En crono::tiempo():
bool crono::tiempo(){Observamos que es una función booleana, esto es devuelve verdadero o falso.
if (veces<=4){
       //código cronometro.
    }
    else{
       
        boton->set_sensitive();
        veces=1;
        return false;
    }
Existe una estructura if que ve si el valor de veces es menor o igual a 4. Si no es así, es que el proceso se ha completado. Por lo que habilita el botón. Observa que no lleva argumentos la función, esto es así, porque es una función definida con valores por omisión, esto es, que si no introduces el argumento, la función asigna uno por defecto. En este caso, true, que activa el botón. Luego, vuelve a devolver a veces el valor de inicio y devuelve false, que provoca la destrucción del objeto sigc::connection, como el método disconnect.
if (segundos>1){
            sprintf (h, "Sujeto %i-> %i:00 segundos",veces, --segundos);
           
        }
        else{
            if(veces!=4)sprintf(h,"CAMBIO");else sprintf(h,"Pulsa abajo para comenzar la prueba");
            segundos=45;
            veces++;
       
        }
        etiqueta->set_text(h);
        return true;
Dentro del if anterior, nos encontramos con este if anidado (dentro del otro), donde si los segundos son mayores a 1 (de 45 a 2) se resta 1 segundo y se crea el mensaje (de 44 s. a 1). Si en vez de --segundos, hubiesemos escrito segundos--, primero se crearía el mensaje y luego se realizaría la resta (de 45 a 2). La primera vez que se ejecuta la función ya ha transcurrido 1 segundo, luego el primer mensaje a escribir 44 segundos. Si segundos es igual a 1, es que ya se ha realizado la cuenta, el próximo valor sería 0 segundos, pero ahí nosotros debemos avisar que cambien, salvo que sea el último, en cuyo caso debemos dejar la etiqueta como estaba (esto también podía hacerse junto a la habilitación del botón.
La función devuelve true, con ello, continúa el timeout establecido.