Zeichnen eines Diagramms mit Kairo in GTK3

Bild

Ein Array vom Typ float fungiert als Eingabe. Das Programm organisiert das Anzeigen, Strecken und Scrollen des Diagramms.

Der Schreibstil ist C mit Klassen (ohne gtkmm). Es stellte sich als nicht perfekt heraus, mit fließenden Abstraktionen. Insbesondere verschlechtern Rückruffunktionen die Kapselung, ein erheblicher Teil der Variablen muss in den öffentlichen Bereich verschoben werden.
Grundsätzlich können Rückruffunktionen zusammen mit den übrigen Funktionen der Klasse, die ich als graphische_Parameter bezeichnet habe, in eine Datei eingefügt werden. In GTK hat jeder Widget-Typ seine eigenen Signale, von denen einige vererbt werden. Zum Beispiel hat GtkEventBox ein "Tastendruck-Ereignis" -Signal, aber nicht das "Konfigurationsereignis", das erforderlich ist, um auf die Größenänderung des Widgets zu reagieren, da GtkEventBox immer die Größe des Inhalts annimmt. Und die Größe des Inhalts wird von Hand eingestellt. Sie können den GtkFrame-Container verwenden.

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

In cairo_t werden Linien und Beschriftungen erstellt, die von der Funktion cairo_stroke angezeigt werden. Bei der Profilerstellung stellte sich heraus, dass cairo_stroke viel Prozessorzeit benötigt, daher sollte es so wenig wie möglich verwendet werden, und die Ausführungszeit für Funktionen
wie cairo_move_to, cairo_line_to ist recht gering. Nach cairo_stroke wird der Inhalt von cairo_t gelöscht und der erneute Aufruf von cairo_stroke (cr) gibt nichts aus. Sie können
cairo_stroke_preserve verwenden, um den Inhalt und cairo_save / cairo_restore zu speichern, aber ich habe sie nicht verwendet.

Wenn die Abmessungen geändert werden (durch Strecken mit der Maus das Signal configure_event_cb), müssen für jede Zeichnung cairo_surface_t und cairo_t gelöscht und neu erstellt werden. Wenn Sie den Zeitplan zurückspulen, müssen Sie ihn nicht neu erstellen

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

Als nächstes wird cairo_surface_t in ein Bild übersetzt

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

Dieses Bild wird dann wie folgt eingefügt

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);

Ich habe die Präfixe entfernt, dh das gescrollte Fenster ist vom Typ GtkScrolledWindow.
Anhangsreihenfolge kurz Bild-> Bildlauffenster-> Ansichtsfenster-> Feld-> Ereignisfeld -> (Rahmen)
Wenn Sie das Bildlauffenster-> Ansichtsfenster-Container entfernen, wird das Diagramm nur vergrößert, aber nicht verkleinert. Feld fügt Bildlauf hinzu. Möglicherweise stellen Sie fest, dass es 3 gibt, von denen jedoch 2 nicht verwendet werden und nur zum Initialisieren der erforderlichen Container benötigt werden. In Container-Widgets, in die 1 untergeordnetes Widget passt, wird die Funktion gtk_container_add zum Einfügen verwendet. g_object_set legt zusätzliche Eigenschaften fest, insbesondere das Fehlen von Bildlaufleisten für das Bildlauffenster-Widget
. Sie können Eigenschaften auch über GValue festlegen

    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);

Bildlaufmechanismus: Das gesamte Segment wird durch 100 geteilt, und die Zeitplanänderung wird in der Rückruffunktion berechnet. 100 wird aus den Zahlen gtk_adjustment_new (0,0,110,1,5,10) als 100 = 110-10 genommen.

Weiter zur Parametrisierung.

Um Text zu parametrisieren, verwenden wir die Pango-Bibliothek, um Beschriftungen zu parametrisieren. Sie können die Größe des Texts in Pixel für eine bestimmte Schriftart und ihre topografische Größe berechnen und in die Kairo-Ebene exportieren.

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;
}

Wie Sie sehen können, zählt Pango die Größen in eigenen Einheiten. Ich habe eine separate Klasse
für den Text und seine Parameter hervorgehoben.

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);
};

Die Diagrammparameter bilden eine separate Klasse:

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);
};

Diese Klasse tritt der Hauptanwendungsklasse bei.
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();
};

Das heißt, die class_parameters-Klasse wird beim Start der Anwendung erstellt und der Inhalt wird nach Bedarf initialisiert, indem nach NULL, 0 gesucht wird.

Die Hauptschwierigkeit bestand darin, alle Transformationen zu debuggen. Segfaults traten dreimal auf: 2-mal verpasste Rückgabe FALSE in den Rückruffunktionen und die Prüfung zum Verlassen des Arrays in der Redraw-Funktion wurde nicht gesetzt. Qt hat eine vorgefertigte QCustomPlot-Klasse zum Plotten, es hat viel mehr Möglichkeiten.

Link zum Github


All Articles