Dessiner un graphique avec le Caire dans GTK3

image

Un tableau de type float agit comme entrée. Le programme organise l'affichage, l'étirement, le défilement du graphique.

Le style d'écriture est C avec des classes (sans gtkmm). Il s'est avéré pas parfait, avec des abstractions fluides. En particulier, les fonctions de rappel dégradent l'encapsulation, une partie importante des variables doit être déplacée vers la section publique.
Fondamentalement, les fonctions de rappel peuvent être placées dans un fichier avec le reste des fonctions de la classe, que j'ai appelé graphic_parameters. Dans GTK, chaque type de widget a ses propres signaux, certains d'entre eux sont hérités. Par exemple, GtkEventBox a un signal «bouton-pression-événement», mais n'a pas «configure-event» requis pour répondre au redimensionnement du widget, car GtkEventBox prend toujours la taille du contenu. Et la taille du contenu est définie à la main. Vous pouvez utiliser le conteneur GtkFrame.

cairo_surface_t  *surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
cairo_t  *cr = cairo_create(surface);

Dans cairo_t, des lignes, des étiquettes sont créées qui sont affichées par la fonction cairo_stroke. Lors du profilage, il s'est avéré que cairo_stroke prend beaucoup de temps processeur, donc il devrait être utilisé aussi peu que possible, et le temps d'exécution pour des fonctions
comme cairo_move_to, cairo_line_to est assez petit. Après cairo_stroke, le contenu de cairo_t est effacé et l'appel à nouveau de cairo_stroke (cr) ne produira rien. Vous pouvez utiliser
cairo_stroke_preserve pour enregistrer le contenu et cairo_save / cairo_restore, mais je ne les ai pas utilisés.

Si les dimensions sont modifiées (en étirant avec la souris, le signal configure_event_cb), alors pour chaque dessin il faut supprimer et recréer cairo_surface_t et cairo_t. Si vous rembobinez le calendrier, il n'est pas nécessaire de recréer

    cairo_set_source_rgb(cr,0.8,0.8,0.8);
    cairo_paint(cr);

Ensuite, cairo_surface_t est traduit en une image

void gtk_image_set_from_surface (GtkImage *image, cairo_surface_t *surface);

Cette image est ensuite insérée comme suit

eventbox=gtk_event_box_new();
    g_signal_connect(eventbox,"button-press-event", G_CALLBACK(eventbox_press_cb), this);
    GtkAdjustment *adj_h=gtk_adjustment_new(0,0,100,1,5,10);
    GtkAdjustment *adj_v=gtk_adjustment_new(0,0,100,1,5,10);
    GtkWidget *viewport=gtk_viewport_new(adj_h, adj_v);
    scrolledwindow=gtk_scrolled_window_new(adj_h, adj_v);
    g_object_set(scrolledwindow, "hscrollbar-policy", GTK_POLICY_EXTERNAL, "vscrollbar-policy", GTK_POLICY_EXTERNAL, NULL);
    gtk_container_add(GTK_CONTAINER(viewport), scrolledwindow);
    gtk_widget_set_events(scrolledwindow, GDK_SCROLL_MASK); 
    g_signal_connect(scrolledwindow,"scroll-event",G_CALLBACK(eventbox_scroll_cb), this);
    GtkWidget *box=gtk_box_new(GTK_ORIENTATION_VERTICAL,0);
    adj=gtk_adjustment_new(0,0,110,1,5,10);
    g_signal_connect(adj,"value-changed", G_CALLBACK(adj_changed_cb), this);
    scrollbar=gtk_scrollbar_new(GTK_ORIENTATION_HORIZONTAL,adj);
    gtk_box_pack_end(GTK_BOX(box),scrollbar, FALSE,FALSE,0);
    image_from_surface=gtk_image_new_from_surface(surface);
    gtk_container_add(GTK_CONTAINER(scrolledwindow),image_from_surface);
    gtk_box_pack_start(GTK_BOX(box),viewport, TRUE,TRUE,0);
    gtk_container_add(GTK_CONTAINER(eventbox),box);

J'ai supprimé les préfixes, c'est-à-dire que, par exemple, scrolledwindow est de type GtkScrolledWindow.
Ordre des pièces jointes brièvement image-> scrolledwindow-> viewport-> box-> eventbox -> (Frame)
Si vous supprimez les scrolledwindow-> viewport containers, le graphique augmentera mais ne diminuera pas. boîte ajoute le défilement. Vous pouvez remarquer qu'il y en a 3, mais 2 d'entre eux ne sont pas utilisés et ne sont nécessaires que pour initialiser les conteneurs nécessaires. Dans les widgets conteneurs où 1 widget enfant tient, la fonction gtk_container_add est utilisée pour l'insertion. g_object_set définit des propriétés supplémentaires, en particulier l'absence de barres de défilement pour le widget scrolledwindow
. Vous pouvez également définir des propriétés via GValue

    GValue val = G_VALUE_INIT;
    g_value_init(&val, G_TYPE_BOOLEAN);
    g_value_set_boolean(&val, TRUE);
    gtk_container_child_set_property(GTK_CONTAINER(data->notebook), gr, "tab-expand", &val);

Mécanisme de défilement: le segment entier est divisé par 100 et le changement d'horaire est calculé dans la fonction de rappel. 100 est tiré des nombres gtk_adjustment_new (0,0,110,1,5,10) comme 100 = 110-10.

De plus, sur le paramétrage.

Pour paramétrer du texte, nous utilisons la bibliothèque pango pour paramétrer les étiquettes. Il vous permet de calculer la taille du texte en pixels pour une police donnée et sa taille topographique et de l'exporter vers la couche du Caire.

PangoLayout* get_width_height_of_text(char *text, char *font, float size, float *w, float *h)
{
    GdkScreen *screen = gdk_screen_get_default();
    PangoContext *context = gdk_pango_context_get_for_screen (screen);
    PangoLayout *layout = pango_layout_new (context);
    if(g_utf8_validate(text,-1,0))
    {
        pango_layout_set_text(layout,text,-1);
        PangoFontDescription *desc=pango_font_description_new();
        pango_font_description_set_family(desc,font);
        pango_font_description_set_size(desc,size*1024);
        pango_layout_set_font_description (layout, desc);
        int width=0,height=0;
        pango_layout_get_size(layout, &width, &height);
        *w=(float) width/1024;
        *h=(float) height/1024;
        pango_font_description_free(desc);
    }
    else
    {
        printf("      UTF8\n");
    }
    return layout;
}

Comme vous pouvez le voir, pango compte les tailles dans ses propres unités. J'ai mis en évidence une classe distincte
pour le texte et ses paramètres.

class text_layout
{
    private:
    int fontsize;
    public:
    GString *text;
    GString *font;
    PangoLayout *layout;
    int width;
    int height;
    text_layout(char *text, char *font, int fontsize);
    void change_text_font_fontsize(char *new_text, char *new_font, int new_fontsize);
    ~text_layout();
    text_layout(float num, char *font, int fontsize);
};

Les paramètres du graphique forment une classe distincte:

class graphic_parameters
{
    private:
     text_layout y_text=text_layout(" y","Liberation Serif", 14);
     text_layout x_text=text_layout(" x","Liberation Serif", 14);
     text_layout *number=0; ///   
     float max=0;
     float min=0;
     text_layout *max_=0;
     text_layout *min_=0;

    GtkAdjustment *adj;
    GtkWidget *scrollbar;
     float gap_x=25; ///    
     float gap_y=5; ///    

    void create_axes_and_xy_labels(void);

    public:
    cairo_t *cr;
    float *massiv=0; ///  
    int len=0; /// 
    int count_in_display=0; ///   
    float multiplier_x=6;
    int offset=0;
    float x_null=0;
    float y_null=0;
    int pos=0;///  
    float margin=16;

    int callback_width;  /// 
    int callback_height;
    int widget_width;
    int widget_height;
    int scroll_height=0;
    GtkWidget *eventbox;
    GtkWidget *scrolledwindow;
    GtkWidget *image_from_surface;
    cairo_surface_t *surface;

    graphic_parameters(int width, int height);
    ~graphic_parameters();
    void resize_graphic(int new_width, int new_height);
    void create_one_dimensional_graphic(float *massiv, int size);
    void update_graphic(int offset);
    void change_graphic_adj(void);
    void create_vertical_line(void);
};

Cette classe rejoint la classe d'application principale.
class externals
{
    public:
    graphic_parameters *param;
    externals();
};

class appdata : public externals
{
    public:
    char *glade_name=(char*)"window.glade";
    GtkApplication *app;
    GtkWidget *win;
    GtkNotebook *notebook;
    GtkMenuBar *menubar;
    appdata();
};

En d'autres termes, la classe graphic_parameters est créée au démarrage de l'application et le contenu est initialisé si nécessaire en vérifiant NULL, 0.

La principale difficulté était de déboguer toutes les transformations. Segfaults s'est produit 3 fois: 2 fois manqué retour FAUX dans les fonctions de rappel et n'a pas défini la vérification pour quitter le tableau dans la fonction de redessin. Qt a une classe QCustomPlot prête à l'emploi pour le traçage, il a beaucoup plus de possibilités.

Lien vers github


All Articles