TD 6 : déroulement d’un programme événementiel

Considérons le programme suivant :

#include <sstream>
 
#include <gtkmm/drawingarea.h>
#include <gtkmm/main.h>
#include <gtkmm/window.h>
#include <gtkmm/box.h>
#include <gtkmm/button.h>
 
#include <gdkmm/colormap.h>
#include <gdkmm/pixbuf.h>
#include <gdkmm/window.h>
 
#include <cstdlib>
 
class Mobile
{
public:
    Mobile() : rectangle(100,100,10,10),vitesse(1) {}
 
    void Initialiser(Glib::RefPtr<Gdk::Window> _window,Gdk::Color &couleur)
    {
        window=_window;
        gc=Gdk::GC::create(window);
        gc->set_foreground(couleur);
    }
 
    void Deplacer()
    {
        int const deltaX = rand()%(vitesse*2+1)-vitesse;
        int const deltaY = rand()%(vitesse*2+1)-vitesse;
        rectangle.set_x(std::min(300,std::max(0,(rectangle.get_x()+deltaX))));
        rectangle.set_y(std::min(300,std::max(0,(rectangle.get_y()+deltaY))));
    }
 
    bool Touche(int x,int y)
    {
        return
            x>=rectangle.get_x() &&
            y>=rectangle.get_y() &&
            x <rectangle.get_x()+rectangle.get_width() &&
            y <rectangle.get_y()+rectangle.get_height();
    }
    void Dessiner()
    {
        window->draw_rectangle(gc, true,
                               rectangle.get_x()    ,rectangle.get_y(),
                               rectangle.get_width(),rectangle.get_height());
    }
 
    void PlusVite()
    {
        vitesse++;
    }
 
    void MoinsVite()
    {
        vitesse=std::max(1,vitesse-1);
    }
 
private:
    Glib::RefPtr<Gdk::GC> gc;
    Glib::RefPtr<Gdk::Window> window;
    // position et dimensions du mobile
    Gdk::Rectangle rectangle;
    int vitesse;
};
 
class ZoneDeJeu : public Gtk::DrawingArea
{
public:
    ZoneDeJeu() : noir("black"), rouge("red"),score(0)
    {
        set_flags(Gtk::CAN_FOCUS);
        get_default_colormap()->alloc_color(noir);
        get_default_colormap()->alloc_color(rouge);
        Glib::signal_timeout().connect(sigc::mem_fun(*this, &ZoneDeJeu::MiseAJour),20);
        set_size_request(300, 300);
        set_events(Gdk::BUTTON_PRESS_MASK);
    }
 
    void PlusVite()
    {
        mobile.PlusVite();
    }
 
    void MoinsVite()
    {
        mobile.MoinsVite();
    }
 
private:
    Gdk::Color noir, rouge;
    Glib::RefPtr<Gdk::GC> gcTexte;
    Mobile mobile;
    int score;
 
    void on_realize()
    {
        Gtk::DrawingArea::on_realize();
        mobile.Initialiser(get_window(),noir);
        gcTexte=Gdk::GC::create(get_window());
    }
 
    bool on_button_press_event(GdkEventButton *event)
    {
        if(mobile.Touche((int)event->x,(int)event->y))
        {
            score++;
        }
        return true;
    }
 
    // On surcharge cette méthode virtuelle de la classe parent.
    // Celle ci est appelée lorsque la fenêtre doit être redessinée.
    bool on_expose_event(GdkEventExpose*)
    {
        get_window()->set_background(rouge);
        get_window()->clear();
        Dessiner();
        return true;
    }
 
    void Dessiner()
    {
        get_window()->clear();
        mobile.Dessiner();
        std::ostringstream text;
        text << "score: " << score;
        get_window()->draw_layout(gcTexte, 10,10, create_pango_layout(text.str()));
    }
 
    bool MiseAJour()
    {
        grab_focus();
        mobile.Deplacer();
        // dessiner avec double-buffering géré automatiquement par le widget
        // (pour eviter le scintillement)
        Gdk::Rectangle rect(0,0,get_width(),get_height());
        get_window()->begin_paint_rect(rect);
        Dessiner();
        get_window()->end_paint ();
        return true;
    }
 
};
 
// La fenetre principale
class MaFenetre : public Gtk::Window
{
public:
    MaFenetre() :
        boutonPlusVite ("plus vite"),
        boutonMoinsVite("moins vite")
    {
        set_border_width(10);
        add(boiteVerticale);
        boiteVerticale.pack_start(zoneDeJeu);
        boiteVerticale.add(boiteHorizontale);
        boiteHorizontale.add(boutonPlusVite);
        boiteHorizontale.add(boutonMoinsVite);
        boutonPlusVite .signal_clicked().connect(sigc::mem_fun(zoneDeJeu, &ZoneDeJeu::PlusVite ));
        boutonMoinsVite.signal_clicked().connect(sigc::mem_fun(zoneDeJeu, &ZoneDeJeu::MoinsVite));
        show_all();
    }
 
private:
    Gtk::Button boutonPlusVite;
    Gtk::Button boutonMoinsVite;
    ZoneDeJeu zoneDeJeu;
    Gtk::VBox boiteVerticale;
    Gtk::HBox boiteHorizontale;
};
 
int main(int argc, char** argv)
{
    Gtk::Main kit(argc, argv);
    MaFenetre fenetre;
    Gtk::Main::run(fenetre);
 
    return 0;
}

L'exécution de ce programme est rythmée par les appels réguliers à la fonction ZoneDeJeu::MiseAJour.

Gestion des évènements clavier

Pour recevoir des évènements claviers dans un Gtk::DrawingArea, il est nécessaire d'appeler set_flags(Gtk::CAN_FOCUS) et ensuite grab_focus(). À quelle classe sont associées ces méthodes? Essayez de trouver à quoi elle servent.

Comme pour tous les évènements dans gtkmm, les évènements claviers peuvent être gérés de deux manières : soit en surchargeant une fonction virtuelle, soit en se connectant sur un signal. En vous inspirant des évènements button_press (clic souris) et signal_clicked (appui sur un widget Gtk::Button) écrivez les deux manières de gérer un évènement clavier.

Pause

Nous souhaitons pouvoir faire une pause dans ce jeu en appuyant sur la barre d'espace. Pour cela nous ne toucherons pas au timeout : la fonction MiseAJour continuera d'être appelée régulièrement. Comment peut-on faire ? écrivez le code nécessaire.

Début et fin de partie

Nous souhaitons qu'au démarrage rien ne soit affiché à l'écran mis à part le message Appuyez sur espace pour commencer la partie. De plus, lorsque le score atteint 10, nous souhaitons afficher uniquement Partie terminée, appuyez sur espace pour recommencer. Réfléchissez à comment implémenter cela. Faites un diagramme avec les différentes phases du programme et la gestion des événements.

Gestion de plusieurs mobiles

En utilisant la classe std::vector, faites en sorte que le programme affiche et déplace plusieurs mobiles simultanément.

Affichage d'images

Dans le corrigé, vous trouverez un exemple montrant comment on peut utiliser des images (classe Gdk::Pixbuf). Notez que pour de meilleurs performances, il peut être intéressant de passer par un Gdk::Pixmap intermédiaire.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

*

Vous pouvez utiliser ces balises et attributs HTML : <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>