Blob Blame History Raw
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#include <config.h>

#include <glib/gi18n.h>

#include <glibtop.h>
#include <glibtop/cpu.h>
#include <glibtop/mem.h>
#include <glibtop/swap.h>
#include <glibtop/netload.h>
#include <glibtop/netlist.h>

#include "application.h"
#include "load-graph.h"
#include "util.h"
#include "legacy/gsm_color_button.h"


void LoadGraph::clear_background()
{
    if (background) {
        cairo_surface_destroy (background);
        background = NULL;
    }
}


unsigned LoadGraph::num_bars() const
{
    unsigned n;

    // keep 100 % num_bars == 0
    switch (static_cast<int>(draw_height / (fontsize + 14)))
    {
        case 0:
        case 1:
            n = 1;
            break;
        case 2:
        case 3:
            n = 2;
            break;
        case 4:
            n = 4;
            break;
        default:
            n = 5;
    }

    return n;
}



#define FRAME_WIDTH 4
void draw_background(LoadGraph *graph) {
    GtkAllocation allocation;
    cairo_t *cr;
    guint i;
    unsigned num_bars;
    char *caption;
    PangoLayout* layout;
    PangoFontDescription* font_desc;
    PangoRectangle extents;
    cairo_surface_t *surface;
    GdkRGBA fg;
    GdkRGBA fg_grid;
    double const border_alpha = 0.7;
    double const grid_alpha = border_alpha / 2.0;

    num_bars = graph->num_bars();
    graph->graph_dely = (graph->draw_height - 15) / num_bars; /* round to int to avoid AA blur */
    graph->real_draw_height = graph->graph_dely * num_bars;
    graph->graph_delx = (graph->draw_width - 2.0 - graph->indent) / (LoadGraph::NUM_POINTS - 3);
    graph->graph_buffer_offset = (int) (1.5 * graph->graph_delx) + FRAME_WIDTH ;

    gtk_widget_get_allocation (GTK_WIDGET (graph->disp), &allocation);
    surface = gdk_window_create_similar_surface (gtk_widget_get_window (GTK_WIDGET (graph->disp)),
                                                           CAIRO_CONTENT_COLOR_ALPHA,
                                                           allocation.width,
                                                           allocation.height);
    cr = cairo_create (surface);

    GtkStyleContext *context = gtk_widget_get_style_context (GTK_WIDGET (GsmApplication::get()->stack));
    
    gtk_style_context_get_color (context, GTK_STATE_FLAG_NORMAL, &fg);

    cairo_paint_with_alpha (cr, 0.0);
    layout = pango_cairo_create_layout (cr);
    gtk_style_context_get (context, GTK_STATE_FLAG_NORMAL, GTK_STYLE_PROPERTY_FONT, &font_desc, NULL);
    pango_font_description_set_size (font_desc, 0.8 * graph->fontsize * PANGO_SCALE);
    pango_layout_set_font_description (layout, font_desc);
    pango_font_description_free (font_desc);

    /* draw frame */
    cairo_translate (cr, FRAME_WIDTH, FRAME_WIDTH);

    /* Draw background rectangle */
    /* When a user uses a dark theme, the hard-coded
     * white background in GSM is a lone white on the
     * display, which makes the user unhappy. To fix
     * this, here we offer the user a chance to set
     * his favorite background color. */
    gtk_style_context_save (context);

    /* Here we specify the name of the class. Now in
     * the theme's CSS we can specify the own colors
     * for this class. */
    gtk_style_context_add_class (context, "loadgraph");

    /* And in case the user does not care, we add
     * classes that usually have a white background. */
    gtk_style_context_add_class (context, GTK_STYLE_CLASS_PAPER);
    gtk_style_context_add_class (context, GTK_STYLE_CLASS_ENTRY);

    /* And, as a bonus, the user can choose the color of the grid. */
    gtk_style_context_get_color (context, GTK_STATE_FLAG_NORMAL, &fg_grid);

    /* Why not use the new features of the
     * GTK instead of cairo_rectangle ?! :) */
    gtk_render_background (context, cr, graph->indent, 0.0,
            graph->draw_width - graph->rmargin - graph->indent,
            graph->real_draw_height);

    gtk_style_context_restore (context);

    cairo_set_line_width (cr, 1.0);
    
    for (i = 0; i <= num_bars; ++i) {
        double y;

        if (i == 0)
            y = 0.5 + graph->fontsize / 2.0;
        else if (i == num_bars)
            y = i * graph->graph_dely + 0.5;
        else
            y = i * graph->graph_dely + graph->fontsize / 2.0;

        gdk_cairo_set_source_rgba (cr, &fg);
        if (graph->type == LOAD_GRAPH_NET) {
            // operation orders matters so it's 0 if i == num_bars
            guint64 rate = graph->net.max - (i * graph->net.max / num_bars);
            const std::string captionstr(procman::format_network_rate(rate));
            caption = g_strdup(captionstr.c_str());
        } else {
            // operation orders matters so it's 0 if i == num_bars
            caption = g_strdup_printf("%d %%", 100 - i * (100 / num_bars));
        }
        pango_layout_set_alignment (layout, PANGO_ALIGN_LEFT);
        pango_layout_set_text (layout, caption, -1);
        pango_layout_get_extents (layout, NULL, &extents);
        cairo_move_to (cr, graph->draw_width - graph->indent - 23,
                       y - 1.0 * extents.height / PANGO_SCALE / 2);
        pango_cairo_show_layout (cr, layout);
        g_free(caption);

        if (i==0 || i==num_bars)
            fg_grid.alpha = border_alpha;
        else
            fg_grid.alpha = grid_alpha;

        gdk_cairo_set_source_rgba (cr, &fg_grid);
        cairo_move_to (cr, graph->indent, i * graph->graph_dely + 0.5);
        cairo_line_to (cr, graph->draw_width - graph->rmargin + 0.5 + 4, i * graph->graph_dely + 0.5);
        cairo_stroke (cr);
    }
    

    const unsigned total_seconds = graph->speed * (LoadGraph::NUM_POINTS - 2) / 1000;

    for (unsigned int i = 0; i < 7; i++) {
        double x = (i) * (graph->draw_width - graph->rmargin - graph->indent) / 6;

        if (i==0 || i==6)
            fg_grid.alpha = border_alpha;
        else
            fg_grid.alpha = grid_alpha;

        gdk_cairo_set_source_rgba (cr, &fg_grid);
        cairo_move_to (cr, (ceil(x) + 0.5) + graph->indent, 0.5);
        cairo_line_to (cr, (ceil(x) + 0.5) + graph->indent, graph->real_draw_height + 4.5);
        cairo_stroke(cr);
        unsigned seconds = total_seconds - i * total_seconds / 6;
        const char* format;
        if (i == 0)
            format = dngettext(GETTEXT_PACKAGE, "%u second", "%u seconds", seconds);
        else
            format = "%u";
        caption = g_strdup_printf(format, seconds);
        pango_layout_set_text (layout, caption, -1);
        pango_layout_get_extents (layout, NULL, &extents);
        cairo_move_to (cr,
                       (ceil(x) + 0.5 + graph->indent) - (1.0 * extents.width / PANGO_SCALE / 2),
                       graph->draw_height - 1.0 * extents.height / PANGO_SCALE);
        gdk_cairo_set_source_rgba (cr, &fg);
        pango_cairo_show_layout (cr, layout);
        g_free (caption);
    }
    g_object_unref(layout);
    cairo_stroke (cr);
    cairo_destroy (cr);
    graph->background = surface;
}

/* Redraws the backing buffer for the load graph and updates the window */
void
load_graph_queue_draw (LoadGraph *graph)
{
    /* repaint */
    gtk_widget_queue_draw (GTK_WIDGET (graph->disp));
}

static int load_graph_update (gpointer user_data); // predeclare load_graph_update so we can compile ;)

static gboolean
load_graph_configure (GtkWidget *widget,
                      GdkEventConfigure *event,
                      gpointer data_ptr)
{
    GtkAllocation allocation;
    LoadGraph * const graph = static_cast<LoadGraph*>(data_ptr);

    gtk_widget_get_allocation (widget, &allocation);
    graph->draw_width = allocation.width - 2 * FRAME_WIDTH;
    graph->draw_height = allocation.height - 2 * FRAME_WIDTH;

    graph->clear_background();

    load_graph_queue_draw (graph);

    return TRUE;
}

static void force_refresh (LoadGraph * const graph)
{
    graph->clear_background();
    load_graph_queue_draw (graph);
}

static void
load_graph_style_updated (GtkWidget *widget,
                          gpointer data_ptr)
{
    LoadGraph * const graph = static_cast<LoadGraph*>(data_ptr);
    force_refresh (graph);
}

static gboolean
load_graph_state_changed (GtkWidget *widget,
                      GtkStateFlags *flags,
                      gpointer data_ptr)
{
    LoadGraph * const graph = static_cast<LoadGraph*>(data_ptr);
    force_refresh (graph);
    return TRUE;
}

static gboolean
load_graph_draw (GtkWidget *widget,
                 cairo_t * cr,
                 gpointer data_ptr)
{
    LoadGraph * const graph = static_cast<LoadGraph*>(data_ptr);

    guint i;
    gint j;
    gdouble sample_width, x_offset;

    /* Number of pixels wide for one graph point */
    sample_width = (float)(graph->draw_width - graph->rmargin - graph->indent) / (float)LoadGraph::NUM_POINTS;
    /* General offset */
    x_offset = graph->draw_width - graph->rmargin;

    /* Subframe offset */
    x_offset += graph->rmargin - ((sample_width / graph->frames_per_unit) * graph->render_counter);

    /* draw the graph */

    if (graph->background == NULL) {
        draw_background(graph);
    }
    cairo_set_source_surface (cr, graph->background, 0, 0);
    cairo_paint (cr);

    cairo_set_line_width (cr, 1);
    cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
    cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
    cairo_rectangle (cr, graph->indent + FRAME_WIDTH + 1, FRAME_WIDTH - 1,
                     graph->draw_width - graph->rmargin - graph->indent - 1,
                     graph->real_draw_height + FRAME_WIDTH - 1);
    cairo_clip(cr);

    bool drawStacked = graph->type == LOAD_GRAPH_CPU && GsmApplication::get()->config.draw_stacked;
    bool drawSmooth = graph->type != LOAD_GRAPH_CPU || GsmApplication::get()->config.draw_smooth;
    for (j = graph->n-1; j >= 0; j--) {
        gdk_cairo_set_source_rgba (cr, &(graph->colors [j]));
        if (drawStacked) {
            cairo_move_to (cr, x_offset, graph->real_draw_height + 3.5f);
        } else {
            cairo_move_to (cr, x_offset, (1.0f - graph->data[0][j]) * graph->real_draw_height + 3.5f);
        }
        for (i = 1; i < LoadGraph::NUM_POINTS; ++i) {
            if (graph->data[i][j] == -1.0f)
                continue;
            if (drawSmooth) {
              cairo_curve_to (cr,
                              x_offset - ((i - 0.5f) * graph->graph_delx),
                              (1.0 - graph->data[i-1][j]) * graph->real_draw_height + 3.5,
                              x_offset - ((i - 0.5f) * graph->graph_delx),
                              (1.0 - graph->data[i][j]) * graph->real_draw_height + 3.5,
                              x_offset - (i * graph->graph_delx),
                              (1.0 - graph->data[i][j]) * graph->real_draw_height + 3.5);
            } else {
              cairo_line_to (cr, x_offset - (i * graph->graph_delx),
                              (1.0 - graph->data[i][j]) * graph->real_draw_height + 3.5);
            }

        }
        if (drawStacked) {
            cairo_rel_line_to (cr, 0, graph->real_draw_height + 3.5f);
            //cairo_stroke_preserve(cr);
            //cairo_close_path(cr);
            cairo_fill(cr);
        } else {
            cairo_stroke (cr);
        }
    }

    return TRUE;
}

void
load_graph_reset (LoadGraph *graph)
{
    std::fill(graph->data_block.begin(), graph->data_block.end(), -1.0);
}

static void
get_load (LoadGraph *graph)
{
    guint i;
    glibtop_cpu cpu;

    glibtop_get_cpu (&cpu);

#undef NOW
#undef LAST
#define NOW  (graph->cpu.times[graph->cpu.now])
#define LAST (graph->cpu.times[graph->cpu.now ^ 1])

    if (graph->n == 1) {
        NOW[0][CPU_TOTAL] = cpu.total;
        NOW[0][CPU_USED] = cpu.user + cpu.nice + cpu.sys;
    } else {
        for (i = 0; i < graph->n; i++) {
            NOW[i][CPU_TOTAL] = cpu.xcpu_total[i];
            NOW[i][CPU_USED] = cpu.xcpu_user[i] + cpu.xcpu_nice[i]
                + cpu.xcpu_sys[i];
        }
    }

    // on the first call, LAST is 0
    // which means data is set to the average load since boot
    // that value has no meaning, we just want all the
    // graphs to be aligned, so the CPU graph needs to start
    // immediately
    bool drawStacked = graph->type == LOAD_GRAPH_CPU && GsmApplication::get()->config.draw_stacked;

    for (i = 0; i < graph->n; i++) {
        float load;
        float total, used;
        gchar *text;

        total = NOW[i][CPU_TOTAL] - LAST[i][CPU_TOTAL];
        used  = NOW[i][CPU_USED]  - LAST[i][CPU_USED];

        load = used / MAX(total, 1.0f);
        graph->data[0][i] = load;
        if (drawStacked) {
            graph->data[0][i] /= graph->n;
            if (i > 0) {
                graph->data[0][i] += graph->data[0][i-1];
            }
        }

        /* Update label */
        text = g_strdup_printf("%.1f%%", load * 100.0f);
        gtk_label_set_text(GTK_LABEL(graph->labels.cpu[i]), text);
        g_free(text);
    }

    graph->cpu.now ^= 1;

#undef NOW
#undef LAST
}


namespace
{

    void set_memory_label_and_picker(GtkLabel* label, GsmColorButton* picker,
                                     guint64 used, guint64 total, double percent)
    {
        char* used_text;
        char* total_text;
        char* text;

        used_text = g_format_size_full(used, G_FORMAT_SIZE_IEC_UNITS);
        total_text = g_format_size_full(total, G_FORMAT_SIZE_IEC_UNITS);
        if (total == 0) {
            text = g_strdup(_("not available"));
        } else {
            // xgettext: 540MiB (53 %) of 1.0 GiB
            text = g_strdup_printf(_("%s (%.1f%%) of %s"), used_text, 100.0 * percent, total_text);
        }
        gtk_label_set_text(label, text);
        g_free(used_text);
        g_free(total_text);
        g_free(text);

        if (picker)
            gsm_color_button_set_fraction(picker, percent);
    }
}

static void
get_memory (LoadGraph *graph)
{
    float mempercent, swappercent;

    glibtop_mem mem;
    glibtop_swap swap;

    glibtop_get_mem (&mem);
    glibtop_get_swap (&swap);

    /* There's no swap on LiveCD : 0.0f is better than NaN :) */
    swappercent = (swap.total ? (float)swap.used / (float)swap.total : 0.0f);
    mempercent  = (float)mem.user  / (float)mem.total;
    set_memory_label_and_picker(GTK_LABEL(graph->labels.memory),
                                GSM_COLOR_BUTTON(graph->mem_color_picker),
                                mem.user, mem.total, mempercent);

    set_memory_label_and_picker(GTK_LABEL(graph->labels.swap),
                                GSM_COLOR_BUTTON(graph->swap_color_picker),
                                swap.used, swap.total, swappercent);
    
    gtk_widget_set_sensitive (GTK_WIDGET (graph->swap_color_picker), swap.total > 0);
    
    graph->data[0][0] = mempercent;
    graph->data[0][1] = swap.total>0 ? swappercent : -1.0;
}

/* Nice Numbers for Graph Labels after Paul Heckbert
   nicenum: find a "nice" number approximately equal to x.
   Round the number if round=1, take ceiling if round=0    */

static double
nicenum (double x, int round)
{
    int expv;				/* exponent of x */
    double f;				/* fractional part of x */
    double nf;				/* nice, rounded fraction */

    expv = floor(log10(x));
    f = x/pow(10.0, expv);		/* between 1 and 10 */
    if (round) {
        if (f < 1.5)
            nf = 1.0;
        else if (f < 3.0)
            nf = 2.0;
        else if (f < 7.0)
            nf = 5.0;
        else
            nf = 10.0;
    } else {
        if (f <= 1.0)
            nf = 1.0;
        else if (f <= 2.0)
            nf = 2.0;
        else if (f <= 5.0)
            nf = 5.0;
        else
            nf = 10.0;
    }
    return nf * pow(10.0, expv);
}

static void
net_scale (LoadGraph *graph, guint64 din, guint64 dout)
{
    graph->data[0][0] = 1.0f * din / graph->net.max;
    graph->data[0][1] = 1.0f * dout / graph->net.max;

    guint64 dmax = std::max(din, dout);
    graph->net.values[graph->net.cur] = dmax;
    graph->net.cur = (graph->net.cur + 1) % LoadGraph::NUM_POINTS;

    guint64 new_max;
    // both way, new_max is the greatest value
    if (dmax >= graph->net.max)
        new_max = dmax;
    else
        new_max = *std::max_element(&graph->net.values[0],
                                    &graph->net.values[LoadGraph::NUM_POINTS]);

    //
    // Round network maximum
    //

    const guint64 bak_max(new_max);

    if (GsmApplication::get()->config.network_in_bits) {
        // nice number is for the ticks
        unsigned ticks = graph->num_bars();

        // gets messy at low values due to division by 8
        guint64 bit_max = std::max( new_max*8, G_GUINT64_CONSTANT(10000) );

        // our tick size leads to max
        double d = nicenum(bit_max/ticks, 0);
        bit_max = ticks * d;
        new_max = bit_max / 8;

        procman_debug("bak*8 %" G_GUINT64_FORMAT ", ticks %d, d %f"
                      ", bit_max %" G_GUINT64_FORMAT ", new_max %" G_GUINT64_FORMAT,
                      bak_max*8, ticks, d, bit_max, new_max );
    } else {
        // round up to get some extra space
        // yes, it can overflow
        new_max = 1.1 * new_max;
        // make sure max is not 0 to avoid / 0
        // default to 1 KiB
        new_max = std::max(new_max, G_GUINT64_CONSTANT(1024));

        // decompose new_max = coef10 * 2**(base10 * 10)
        // where coef10 and base10 are integers and coef10 < 2**10
        //
        // e.g: ceil(100.5 KiB) = 101 KiB = 101 * 2**(1 * 10)
        //      where base10 = 1, coef10 = 101, pow2 = 16

        guint64 pow2 = std::floor(log2(new_max));
        guint64 base10 = pow2 / 10.0;
        guint64 coef10 = std::ceil(new_max / double(G_GUINT64_CONSTANT(1) << (base10 * 10)));
        g_assert(new_max <= (coef10 * (G_GUINT64_CONSTANT(1) << (base10 * 10))));

        // then decompose coef10 = x * 10**factor10
        // where factor10 is integer and x < 10
        // so we new_max has only 1 significant digit

        guint64 factor10 = std::pow(10.0, std::floor(std::log10(coef10)));
        coef10 = std::ceil(coef10 / double(factor10)) * factor10;

        new_max = coef10 * (G_GUINT64_CONSTANT(1) << guint64(base10 * 10));
        procman_debug("bak %" G_GUINT64_FORMAT " new_max %" G_GUINT64_FORMAT
                      "pow2 %" G_GUINT64_FORMAT " coef10 %" G_GUINT64_FORMAT,
                      bak_max, new_max, pow2, coef10);
    }

    if (bak_max > new_max) {
        procman_debug("overflow detected: bak=%" G_GUINT64_FORMAT
                      " new=%" G_GUINT64_FORMAT,
                      bak_max, new_max);
        new_max = bak_max;
    }

    // if max is the same or has decreased but not so much, don't
    // do anything to avoid rescaling
    if ((0.8 * graph->net.max) < new_max && new_max <= graph->net.max)
        return;

    const double scale = 1.0f * graph->net.max / new_max;

    for (size_t i = 0; i < LoadGraph::NUM_POINTS; i++) {
        if (graph->data[i][0] >= 0.0f) {
            graph->data[i][0] *= scale;
            graph->data[i][1] *= scale;
        }
    }

    procman_debug("rescale dmax = %" G_GUINT64_FORMAT
                  " max = %" G_GUINT64_FORMAT
                  " new_max = %" G_GUINT64_FORMAT,
                  dmax, graph->net.max, new_max);

    graph->net.max = new_max;

    // force the graph background to be redrawn now that scale has changed
    graph->clear_background();
}

static void
get_net (LoadGraph *graph)
{
    glibtop_netlist netlist;
    char **ifnames;
    guint32 i;
    guint64 in = 0, out = 0;
    GTimeVal time;
    guint64 din, dout;
    gboolean first = true;
    ifnames = glibtop_get_netlist(&netlist);

    for (i = 0; i < netlist.number; ++i)
    {
        glibtop_netload netload;
        glibtop_get_netload (&netload, ifnames[i]);

        if (netload.if_flags & (1 << GLIBTOP_IF_FLAGS_LOOPBACK))
            continue;

        /* Skip interfaces without any IPv4/IPv6 address (or
           those with only a LINK ipv6 addr) However we need to
           be able to exclude these while still keeping the
           value so when they get online (with NetworkManager
           for example) we don't get a suddent peak.  Once we're
           able to get this, ignoring down interfaces will be
           possible too.  */
        if (not (netload.flags & (1 << GLIBTOP_NETLOAD_ADDRESS6)
                 and netload.scope6 != GLIBTOP_IF_IN6_SCOPE_LINK)
            and not (netload.flags & (1 << GLIBTOP_NETLOAD_ADDRESS)))
            continue;

        /* Don't skip interfaces that are down (GLIBTOP_IF_FLAGS_UP)
           to avoid spikes when they are brought up */

        in  += netload.bytes_in;
        out += netload.bytes_out;
    }

    g_strfreev(ifnames);

    g_get_current_time (&time);

    if (in >= graph->net.last_in && out >= graph->net.last_out && graph->net.time.tv_sec != 0) {
        float dtime;
        dtime = time.tv_sec - graph->net.time.tv_sec +
                (double) (time.tv_usec - graph->net.time.tv_usec) / G_USEC_PER_SEC;
        din   = static_cast<guint64>((in  - graph->net.last_in)  / dtime);
        dout  = static_cast<guint64>((out - graph->net.last_out) / dtime);
    } else {
        /* Don't calc anything if new data is less than old (interface
           removed, counters reset, ...) or if it is the first time */
        din  = 0;
        dout = 0;
    }

    first = first && (graph->net.time.tv_sec==0);
    graph->net.last_in  = in;
    graph->net.last_out = out;
    graph->net.time     = time;

    if (!first)
        net_scale(graph, din, dout);

    gtk_label_set_text (GTK_LABEL (graph->labels.net_in), procman::format_network_rate(din).c_str());
    gtk_label_set_text (GTK_LABEL (graph->labels.net_in_total), procman::format_network(in).c_str());

    gtk_label_set_text (GTK_LABEL (graph->labels.net_out), procman::format_network_rate(dout).c_str());
    gtk_label_set_text (GTK_LABEL (graph->labels.net_out_total), procman::format_network(out).c_str());
}


/* Updates the load graph when the timeout expires */
static gboolean
load_graph_update (gpointer user_data)
{
    LoadGraph * const graph = static_cast<LoadGraph*>(user_data);

    if (graph->render_counter == graph->frames_per_unit - 1) {
        std::rotate(&graph->data[0],
                    &graph->data[LoadGraph::NUM_POINTS - 1],
                    &graph->data[LoadGraph::NUM_POINTS]);

        switch (graph->type) {
            case LOAD_GRAPH_CPU:
                get_load(graph);
                break;
            case LOAD_GRAPH_MEM:
                get_memory(graph);
                break;
            case LOAD_GRAPH_NET:
                get_net(graph);
                break;
            default:
                g_assert_not_reached();
        }
    }

    if (graph->draw)
        load_graph_queue_draw (graph);

    graph->render_counter++;

    if (graph->render_counter >= graph->frames_per_unit)
        graph->render_counter = 0;

    return TRUE;
}



LoadGraph::~LoadGraph()
{
    load_graph_stop(this);

    if (timer_index)
        g_source_remove(timer_index);

    clear_background();
}



static gboolean
load_graph_destroy (GtkWidget *widget, gpointer data_ptr)
{
    LoadGraph * const graph = static_cast<LoadGraph*>(data_ptr);

    delete graph;

    return FALSE;
}


LoadGraph::LoadGraph(guint type)
    : fontsize(8.0),
      rmargin(7 * fontsize),
      indent(24.0),
      n(0),
      type(type),
      speed(0),
      draw_width(0),
      draw_height(0),
      render_counter(0),
      frames_per_unit(10), // this will be changed but needs initialising
      graph_dely(0),
      real_draw_height(0),
      graph_delx(0.0),
      graph_buffer_offset(0),
      colors(),
      data_block(),
      main_widget(NULL),
      disp(NULL),
      background(NULL),
      timer_index(0),
      draw(FALSE),
      labels(),
      mem_color_picker(NULL),
      swap_color_picker(NULL),
      cpu(),
      net()
{
    LoadGraph * const graph = this;

    // FIXME:
    // on configure, graph->frames_per_unit = graph->draw_width/(LoadGraph::NUM_POINTS);
    // knock FRAMES down to 5 until cairo gets faster

    switch (type) {
        case LOAD_GRAPH_CPU:
            memset(&cpu, 0, sizeof cpu);
            n = GsmApplication::get()->config.num_cpus;

            for(guint i = 0; i < G_N_ELEMENTS(labels.cpu); ++i)
                labels.cpu[i] = GTK_LABEL (gtk_label_new(NULL));

            break;

        case LOAD_GRAPH_MEM:
            n = 2;
            labels.memory = GTK_LABEL (gtk_label_new(NULL));
            gtk_widget_set_valign (GTK_WIDGET (labels.memory), GTK_ALIGN_CENTER);
            gtk_widget_set_halign (GTK_WIDGET (labels.memory), GTK_ALIGN_START);
            gtk_widget_show (GTK_WIDGET (labels.memory));
            labels.swap = GTK_LABEL (gtk_label_new(NULL));
            gtk_widget_set_valign (GTK_WIDGET (labels.swap), GTK_ALIGN_CENTER);
            gtk_widget_set_halign (GTK_WIDGET (labels.swap), GTK_ALIGN_START);
            gtk_widget_show (GTK_WIDGET (labels.swap));
            break;

        case LOAD_GRAPH_NET:
            memset(&net, 0, sizeof net);
            n = 2;
            net.max = 1;
            labels.net_in = GTK_LABEL (gtk_label_new(NULL));
            gtk_label_set_width_chars(labels.net_in, 10);
            gtk_widget_set_valign (GTK_WIDGET (labels.net_in), GTK_ALIGN_CENTER);
            gtk_widget_set_halign (GTK_WIDGET (labels.net_in), GTK_ALIGN_END);
            gtk_widget_show (GTK_WIDGET (labels.net_in));

            labels.net_in_total = GTK_LABEL (gtk_label_new(NULL));
            gtk_widget_set_valign (GTK_WIDGET (labels.net_in_total), GTK_ALIGN_CENTER);
            gtk_widget_set_halign (GTK_WIDGET (labels.net_in_total), GTK_ALIGN_END);
            gtk_label_set_width_chars(labels.net_in_total, 10);
            gtk_widget_show (GTK_WIDGET (labels.net_in_total));

            labels.net_out = GTK_LABEL (gtk_label_new(NULL));
            gtk_widget_set_valign (GTK_WIDGET (labels.net_out), GTK_ALIGN_CENTER);
            gtk_widget_set_halign (GTK_WIDGET (labels.net_out), GTK_ALIGN_END);
            gtk_label_set_width_chars(labels.net_out, 10);
            gtk_widget_show (GTK_WIDGET (labels.net_out));

            labels.net_out_total = GTK_LABEL (gtk_label_new(NULL));
            gtk_widget_set_valign (GTK_WIDGET (labels.net_out_total), GTK_ALIGN_CENTER);
            gtk_widget_set_halign (GTK_WIDGET (labels.net_out), GTK_ALIGN_END);
            gtk_label_set_width_chars(labels.net_out_total, 10);
            gtk_widget_show (GTK_WIDGET (labels.net_out_total));

            break;
    }

    speed  = GsmApplication::get()->config.graph_update_interval;

    colors.resize(n);

    switch (type) {
        case LOAD_GRAPH_CPU:
            memcpy(&colors[0], GsmApplication::get()->config.cpu_color,
                   n * sizeof colors[0]);
            break;
        case LOAD_GRAPH_MEM:
            colors[0] = GsmApplication::get()->config.mem_color;
            colors[1] = GsmApplication::get()->config.swap_color;
            mem_color_picker = gsm_color_button_new (&colors[0],
                                                        GSMCP_TYPE_PIE);
            swap_color_picker = gsm_color_button_new (&colors[1],
                                                         GSMCP_TYPE_PIE);
            break;
        case LOAD_GRAPH_NET:
            colors[0] = GsmApplication::get()->config.net_in_color;
            colors[1] = GsmApplication::get()->config.net_out_color;
            break;
    }

    timer_index = 0;
    render_counter = (frames_per_unit - 1);
    draw = FALSE;

    main_widget = GTK_BOX (gtk_box_new (GTK_ORIENTATION_VERTICAL, 6));
    gtk_widget_set_size_request(GTK_WIDGET (main_widget), -1, LoadGraph::GRAPH_MIN_HEIGHT);
    gtk_widget_show (GTK_WIDGET (main_widget));

    disp = GTK_DRAWING_AREA (gtk_drawing_area_new ());
    gtk_widget_show (GTK_WIDGET (disp));
    g_signal_connect (G_OBJECT (disp), "draw",
                      G_CALLBACK (load_graph_draw), graph);
    g_signal_connect (G_OBJECT(disp), "configure_event",
                      G_CALLBACK (load_graph_configure), graph);
    g_signal_connect (G_OBJECT(disp), "destroy",
                      G_CALLBACK (load_graph_destroy), graph);
    g_signal_connect (G_OBJECT(disp), "state-flags-changed",
                      G_CALLBACK (load_graph_state_changed), graph);
    g_signal_connect (G_OBJECT(disp), "style-updated",
                      G_CALLBACK (load_graph_style_updated), graph);

    gtk_widget_set_events (GTK_WIDGET (disp), GDK_EXPOSURE_MASK);

    gtk_box_pack_start (main_widget, GTK_WIDGET (disp), TRUE, TRUE, 0);


    /* Allocate data in a contiguous block */
    data_block = std::vector<double>(n * LoadGraph::NUM_POINTS, -1.0);

    for (guint i = 0; i < LoadGraph::NUM_POINTS; ++i)
        data[i] = &data_block[0] + i * n;

    gtk_widget_show_all (GTK_WIDGET (main_widget));
}

void
load_graph_start (LoadGraph *graph)
{
     if (!graph->timer_index) {

        load_graph_update(graph);

        graph->timer_index = g_timeout_add (graph->speed / graph->frames_per_unit,
                                            load_graph_update,
                                            graph);
    }

    graph->draw = TRUE;
}

void
load_graph_stop (LoadGraph *graph)
{
    /* don't draw anymore, but continue to poll */
    graph->draw = FALSE;
}

void
load_graph_change_speed (LoadGraph *graph,
                         guint new_speed)
{
    if (graph->speed == new_speed)
        return;

    graph->speed = new_speed;

    if (graph->timer_index) {
        g_source_remove (graph->timer_index);
        graph->timer_index = g_timeout_add (graph->speed / graph->frames_per_unit,
                                            load_graph_update,
                                            graph);
    }

    graph->clear_background();
}


LoadGraphLabels*
load_graph_get_labels (LoadGraph *graph)
{
    return &graph->labels;
}

GtkBox*
load_graph_get_widget (LoadGraph *graph)
{
    return graph->main_widget;
}

GsmColorButton*
load_graph_get_mem_color_picker(LoadGraph *graph)
{
    return graph->mem_color_picker;
}

GsmColorButton*
load_graph_get_swap_color_picker(LoadGraph *graph)
{
    return graph->swap_color_picker;
}