[gnome-disk-utility/ata-smart-ui-rework: 17/17] Include a graph in the ATA SMART dialog



commit a578a4127fb882101fa2fe564a7dde6d734cdd22
Author: David Zeuthen <davidz redhat com>
Date:   Tue Jun 23 00:39:55 2009 -0400

    Include a graph in the ATA SMART dialog
    
    This is still work in progress, see
    
    http://people.freedesktop.org/~david/new-ata-smart-dialog-wip.png
    
    One can observe that I hacked on the DriveSetSpindownTimeout()
    yesterday using this disk as a guinea pig ;-)

 src/gdu-gtk/Makefile.am            |    2 +
 src/gdu-gtk/gdu-ata-smart-dialog.c |  445 +++++++++++++++++++++----
 src/gdu-gtk/gdu-graph.c            |  647 ++++++++++++++++++++++++++++++++++++
 src/gdu-gtk/gdu-graph.h            |   93 +++++
 src/gdu-gtk/gdu-gtk-types.h        |    1 +
 src/gdu-gtk/gdu-gtk.h              |    1 +
 src/gdu/gdu-device.c               |    2 +-
 7 files changed, 1120 insertions(+), 71 deletions(-)
---
diff --git a/src/gdu-gtk/Makefile.am b/src/gdu-gtk/Makefile.am
index 8e1aeaf..b122ac5 100644
--- a/src/gdu-gtk/Makefile.am
+++ b/src/gdu-gtk/Makefile.am
@@ -10,6 +10,7 @@ libgdu_gtkinclude_HEADERS =              						\
 	gdu-gtk-types.h									\
 	gdu-time-label.h								\
 	gdu-ata-smart-dialog.h								\
+	gdu-graph.h									\
 	gdu-ata-smart-attribute-dialog.h						\
 	$(NULL)
 
@@ -18,6 +19,7 @@ libgdu_gtk_la_SOURCES =                 	        	       			\
 	gdu-gtk-types.h									\
 	gdu-time-label.h			gdu-time-label.c			\
 	gdu-ata-smart-dialog.h			gdu-ata-smart-dialog.c			\
+	gdu-graph.h				gdu-graph.c				\
 	gdu-ata-smart-attribute-dialog.h	gdu-ata-smart-attribute-dialog.c	\
 	$(NULL)
 
diff --git a/src/gdu-gtk/gdu-ata-smart-dialog.c b/src/gdu-gtk/gdu-ata-smart-dialog.c
index 8bcdfc0..e091f79 100644
--- a/src/gdu-gtk/gdu-ata-smart-dialog.c
+++ b/src/gdu-gtk/gdu-ata-smart-dialog.c
@@ -23,6 +23,7 @@
 #include <glib/gi18n.h>
 
 #include "gdu-time-label.h"
+#include "gdu-graph.h"
 #include "gdu-ata-smart-dialog.h"
 #include "gdu-ata-smart-attribute-dialog.h"
 
@@ -43,6 +44,10 @@ struct GduAtaSmartDialogPrivate
 
         GtkWidget *tree_view;
         GtkListStore *attr_list_store;
+
+        GtkWidget *graph;
+
+        GList *historical_data;
 };
 
 enum
@@ -75,6 +80,10 @@ G_DEFINE_TYPE (GduAtaSmartDialog, gdu_ata_smart_dialog, GTK_TYPE_DIALOG)
 static void update_dialog (GduAtaSmartDialog *dialog);
 static void device_changed (GduDevice *device, gpointer user_data);
 
+static gchar *pretty_to_string (guint64                  pretty_value,
+                                GduAtaSmartAttributeUnit pretty_unit,
+                                gboolean                 long_string);
+
 static void
 gdu_ata_smart_dialog_finalize (GObject *object)
 {
@@ -84,6 +93,11 @@ gdu_ata_smart_dialog_finalize (GObject *object)
         g_object_unref (dialog->priv->device);
         g_object_unref (dialog->priv->attr_list_store);
 
+        if (dialog->priv->historical_data != NULL) {
+                g_list_foreach (dialog->priv->historical_data, (GFunc) g_object_unref, NULL);
+                g_list_free (dialog->priv->historical_data);
+        }
+
         if (G_OBJECT_CLASS (gdu_ata_smart_dialog_parent_class)->finalize != NULL)
                 G_OBJECT_CLASS (gdu_ata_smart_dialog_parent_class)->finalize (object);
 }
@@ -125,43 +139,223 @@ gdu_ata_smart_dialog_set_property (GObject      *object,
 }
 
 static void
-on_bar_clicked (GtkButton *button,
-                gpointer   user_data)
+selection_changed (GtkTreeSelection *tree_selection,
+                   gpointer          user_data)
 {
         GduAtaSmartDialog *dialog = GDU_ATA_SMART_DIALOG (user_data);
-        GtkWidget *attr_dialog;
-        GtkTreeSelection *tree_selection;
-        GtkTreeModel *tree_model;
         GtkTreeIter iter;
-        gchar *selected_attr_name;
-
-        selected_attr_name = NULL;
-        tree_selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (dialog->priv->tree_view));
-        if (gtk_tree_selection_get_selected (tree_selection, &tree_model, &iter)) {
-                gtk_tree_model_get (tree_model, &iter,
-                                    ATTR_NAME_COLUMN,
-                                    &selected_attr_name,
-                                    -1);
-        }
+        gchar *attr_name;
+        guint n;
+
+        attr_name = NULL;
 
-        if (selected_attr_name == NULL) {
-                g_warning ("No attribute selected");
+        if (dialog->priv->historical_data == NULL)
                 goto out;
+
+        if (!gtk_tree_selection_get_selected (tree_selection,
+                                              NULL,
+                                              &iter))
+                goto out;
+
+        gtk_tree_model_get (GTK_TREE_MODEL (dialog->priv->attr_list_store),
+                            &iter,
+                            ATTR_NAME_COLUMN,
+                            &attr_name,
+                            -1);
+
+        g_debug ("selected %s", attr_name);
+
+        GArray *cur_points;
+        GArray *raw_points;
+        GArray *band_points;
+        GList *l;
+        guint64 now;
+        cur_points = g_array_new (FALSE, FALSE, sizeof (GduGraphPoint));
+        raw_points = g_array_new (FALSE, FALSE, sizeof (GduGraphPoint));
+        band_points = g_array_new (FALSE, FALSE, sizeof (GduGraphPoint));
+
+        guint64 raw_min;
+        guint64 raw_max;
+        GduAtaSmartAttributeUnit raw_unit;
+        raw_min = G_MAXUINT64;
+        raw_max = 0;
+        for (l = dialog->priv->historical_data; l != NULL; l = l->next) {
+                GduAtaSmartHistoricalData *data = GDU_ATA_SMART_HISTORICAL_DATA (l->data);
+                GduAtaSmartAttribute *attr;
+
+                attr = gdu_ata_smart_historical_data_get_attribute (data, attr_name);
+                if (attr != NULL) {
+                        guint64 raw;
+
+                        raw = gdu_ata_smart_attribute_get_pretty_value (attr);
+                        raw_unit = gdu_ata_smart_attribute_get_pretty_unit (attr);
+                        if (raw < raw_min)
+                                raw_min = raw;
+                        if (raw > raw_max)
+                                raw_max = raw;
+                        g_object_unref (attr);
+                }
+        }
+
+        guint64 time_factor;
+        switch (raw_unit) {
+        case GDU_ATA_SMART_ATTRIBUTE_UNIT_MSECONDS:
+                if (raw_min > 1000 * 60 * 60 * 24) {
+                        time_factor = 1000 * 60 * 60 * 24;
+                } else if (raw_min > 1000 * 60 * 60) {
+                        time_factor = 1000 * 60 * 60;
+                } else if (raw_min > 1000 * 60) {
+                        time_factor = 1000 * 60;
+                } else if (raw_min > 1000) {
+                        time_factor = 1000;
+                } else {
+                        time_factor = 1;
+                }
+
+                if (raw_max - raw_min < 5 * time_factor) {
+                        raw_min -= (raw_min % time_factor);
+                        raw_max = raw_min + 5 * time_factor;
+                }
+                break;
+        case GDU_ATA_SMART_ATTRIBUTE_UNIT_MKELVIN:
+                if (raw_max - raw_min < 5000) {
+                        raw_min -= (raw_min % 1000);
+                        raw_max = raw_min + 5000;
+                }
+                break;
+        case GDU_ATA_SMART_ATTRIBUTE_UNIT_SECTORS:
+        case GDU_ATA_SMART_ATTRIBUTE_UNIT_NONE:
+        case GDU_ATA_SMART_ATTRIBUTE_UNIT_UNKNOWN:
+                if (raw_min - raw_max < 5) {
+                        raw_max = raw_min + 5;
+                }
+                break;
+        }
+
+
+        gchar **y_axis_left;
+        y_axis_left = g_new0 (gchar *, 6);
+        for (n = 0; n < 5; n++) {
+                guint64 raw_marker_value;
+                gchar *s;
+
+                raw_marker_value = raw_min + n * ((gdouble) (raw_max - raw_min)) / (5 - 1);
+
+                s = pretty_to_string (raw_marker_value, raw_unit, FALSE);
+                y_axis_left[n] = s;
+        }
+        y_axis_left[n] = NULL;
+        gdu_graph_set_y_markers_left (GDU_GRAPH (dialog->priv->graph), (const gchar* const *) y_axis_left);
+        g_strfreev (y_axis_left);
+
+        guint64 tolerance;
+        guint64 timespan;
+
+        timespan = 5 * 24 * 60 * 60;
+        tolerance = 2 * 60 * 60;
+
+        guint64 last_age;
+        now = (guint64) time (NULL);
+        last_age = timespan;
+
+        /* oldest points first */
+        for (l = dialog->priv->historical_data; l != NULL; l = l->next) {
+                GduAtaSmartHistoricalData *data = GDU_ATA_SMART_HISTORICAL_DATA (l->data);
+                GduAtaSmartAttribute *attr;
+                guint64 time_collected;
+                GduGraphPoint point;
+                guint64 age;
+                gboolean use_point;
+
+                memset (&point, '\0', sizeof (GduGraphPoint));
+
+                time_collected = gdu_ata_smart_historical_data_get_time_collected (data);
+                age = now - time_collected;
+
+                g_debug ("age = %d", (gint) age);
+
+                /* skip old points, except if the following point is not too old */
+                use_point = FALSE;
+                if (age < timespan) {
+                        use_point = TRUE;
+                } else {
+                        if (l->next != NULL) {
+                                GduAtaSmartHistoricalData *next_data = GDU_ATA_SMART_HISTORICAL_DATA (l->next->data);
+                                guint64 next_age;
+                                next_age = now - gdu_ata_smart_historical_data_get_time_collected (next_data);
+                                if (next_age < timespan) {
+                                        use_point = TRUE;
+                                }
+                        }
+                }
+
+                if (use_point) {
+
+                        point.x = 1.0f - ((gfloat) age) / ((gfloat) timespan);
+
+                        attr = gdu_ata_smart_historical_data_get_attribute (data, attr_name);
+                        if (attr != NULL) {
+                                guint current;
+                                guint64 raw;
+
+                                current = gdu_ata_smart_attribute_get_current (attr);
+                                raw = gdu_ata_smart_attribute_get_pretty_value (attr);
+
+                                point.y = current / 255.0f;
+                                g_array_append_val (cur_points, point);
+
+                                point.y = ((gfloat) (raw - raw_min)) / ((gfloat) (raw_max - raw_min));
+                                g_array_append_val (raw_points, point);
+
+                                g_object_unref (attr);
+                        }
+
+                        /* draw a band if there's a discontinuity; e.g. no samples for an hour or more */
+                        if (last_age - age >= tolerance) {
+                                guint64 band_start;
+                                guint64 band_end;
+
+                                band_start = last_age - tolerance;
+                                band_end = age + tolerance;
+
+                                point.x = 1.0f - band_start / ((gfloat) timespan);
+                                point.y = 0;
+                                g_array_append_val (band_points, point);
+
+                                point.x = 1.0f - band_end / ((gfloat) timespan);
+                                point.y = 0;
+                                g_array_append_val (band_points, point);
+                        }
+                }
+
+                last_age = age;
         }
 
-        /* Make the attributes dialog transient for the same window as
-         * this dialog - we do this so the user can open several attr
-         * windows and keep them visible while allowing to close this
-         * window (this is useful when monitoring a system)
-         */
-        attr_dialog = gdu_ata_smart_attribute_dialog_new (GTK_WINDOW (dialog), //NULL,//TODO:gtk_window_get_transient_for (GTK_WINDOW (dialog)),
-                                                          dialog->priv->device,
-                                                          selected_attr_name);
+        GdkColor cur_color = { 0, 0x8c00, 0xb000, 0xd700};
+        GdkColor raw_color = { 0, 0xfc00, 0xaf00, 0x3e00};
+        GdkColor band_color = { 0, 0x4000, 0x4000, 0x4000};
+
+        gdu_graph_set_curve (GDU_GRAPH (dialog->priv->graph),
+                             "current",
+                             &cur_color,
+                             cur_points);
+
+        gdu_graph_set_curve (GDU_GRAPH (dialog->priv->graph),
+                             "raw",
+                             &raw_color,
+                             raw_points);
 
-        gtk_widget_show_all (attr_dialog);
+        gdu_graph_set_band (GDU_GRAPH (dialog->priv->graph),
+                            "discontinuity",
+                            &band_color,
+                            band_points);
+
+        g_array_unref (cur_points);
+        g_array_unref (raw_points);
+        g_array_unref (band_points);
 
  out:
-        ;
+        g_free (attr_name);
 }
 
 static void
@@ -171,20 +365,38 @@ gdu_ata_smart_dialog_constructed (GObject *object)
         GtkWidget *content_area;
         GtkWidget *align;
         GtkWidget *vbox;
+        GtkWidget *hbox;
         GtkWidget *table;
         GtkWidget *label;
         GtkWidget *tree_view;
         GtkWidget *scrolled_window;
+        GtkWidget *graph;
         GtkCellRenderer *renderer;
         GtkTreeViewColumn *column;
         gint row;
+        GduPresentable *drive;
+        GduPool *pool;
+        gchar *title;
+        gchar *drive_name;
+        GtkTreeSelection *selection;
 
         gtk_dialog_set_has_separator (GTK_DIALOG (dialog), FALSE);
 
+        pool = gdu_device_get_pool (dialog->priv->device);
+        drive = gdu_pool_get_drive_by_device (pool, dialog->priv->device);
+        drive_name = gdu_presentable_get_name (drive);
+        /* Translators: %s is the drive name */
+        title = g_strdup_printf (_("ATA SMART data for %s"), drive_name);
+        gtk_window_set_title (GTK_WINDOW (dialog), title);
+        g_object_unref (pool);
+        g_object_unref (drive);
+        g_free (title);
+        g_free (drive_name);
+
         content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
-        gtk_dialog_add_button (GTK_DIALOG (dialog),
-                               GTK_STOCK_CLOSE,
-                               GTK_RESPONSE_CLOSE);
+        //gtk_dialog_add_button (GTK_DIALOG (dialog),
+        //                       GTK_STOCK_CLOSE,
+        //                       GTK_RESPONSE_CLOSE);
 
         align = gtk_alignment_new (0.5, 0.5, 1.0, 1.0);
         gtk_alignment_set_padding (GTK_ALIGNMENT (align), 12, 12, 12, 12);
@@ -193,10 +405,37 @@ gdu_ata_smart_dialog_constructed (GObject *object)
         vbox = gtk_vbox_new (FALSE, 6);
         gtk_container_add (GTK_CONTAINER (align), vbox);
 
+        hbox = gtk_hbox_new (FALSE, 6);
+        gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
+
         table = gtk_table_new (4, 2, FALSE);
         gtk_table_set_col_spacings (GTK_TABLE (table), 12);
         gtk_table_set_row_spacings (GTK_TABLE (table), 4);
-        gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0);
+        gtk_box_pack_start (GTK_BOX (hbox), table, FALSE, FALSE, 0);
+
+        graph = gdu_graph_new ();
+        dialog->priv->graph = graph;
+        gtk_widget_set_size_request (graph, 480, 180);
+        gtk_box_pack_start (GTK_BOX (hbox), graph, TRUE, TRUE, 0);
+
+        const gchar *time_axis[7];
+        time_axis[0] = C_("ATA SMART graph label", "five days ago");
+        time_axis[1] = C_("ATA SMART graph label", "four days ago");
+        time_axis[2] = C_("ATA SMART graph label", "three days ago");
+        time_axis[3] = C_("ATA SMART graph label", "two days ago");
+        time_axis[4] = C_("ATA SMART graph label", "one day ago");
+        time_axis[5] = C_("ATA SMART graph label", "now");
+        time_axis[6] = NULL;
+        gdu_graph_set_x_markers (GDU_GRAPH (graph), time_axis);
+
+        const gchar *y_axis_right[6];
+        y_axis_right[0] = C_("ATA SMART graph label", "0");
+        y_axis_right[1] = C_("ATA SMART graph label", "64");
+        y_axis_right[2] = C_("ATA SMART graph label", "128");
+        y_axis_right[3] = C_("ATA SMART graph label", "192");
+        y_axis_right[4] = C_("ATA SMART graph label", "255");
+        y_axis_right[5] = NULL;
+        gdu_graph_set_y_markers_right (GDU_GRAPH (graph), y_axis_right);
 
         row = 0;
 
@@ -205,14 +444,14 @@ gdu_ata_smart_dialog_constructed (GObject *object)
         gtk_misc_set_alignment (GTK_MISC (label), 1.0, 0.5);
         gtk_label_set_markup_with_mnemonic (GTK_LABEL (label), _("<b>Powered On:</b>"));
         gtk_table_attach (GTK_TABLE (table), label, 0, 1, row, row + 1,
-                          GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
+                          GTK_FILL, GTK_FILL, 0, 0);
 
         label = gtk_label_new (NULL);
         gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
         dialog->priv->power_on_hours_label = label;
 
         gtk_table_attach (GTK_TABLE (table), label, 1, 2, row, row + 1,
-                          GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
+                          GTK_FILL, GTK_FILL, 0, 0);
 
         row++;
 
@@ -221,14 +460,14 @@ gdu_ata_smart_dialog_constructed (GObject *object)
         gtk_misc_set_alignment (GTK_MISC (label), 1.0, 0.5);
         gtk_label_set_markup_with_mnemonic (GTK_LABEL (label), _("<b>Temperature:</b>"));
         gtk_table_attach (GTK_TABLE (table), label, 0, 1, row, row + 1,
-                          GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
+                          GTK_FILL, GTK_FILL, 0, 0);
 
         label = gtk_label_new (NULL);
         gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
         dialog->priv->temperature_label = label;
 
         gtk_table_attach (GTK_TABLE (table), label, 1, 2, row, row + 1,
-                          GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
+                          GTK_FILL, GTK_FILL, 0, 0);
 
         row++;
 
@@ -237,14 +476,14 @@ gdu_ata_smart_dialog_constructed (GObject *object)
         gtk_misc_set_alignment (GTK_MISC (label), 1.0, 0.5);
         gtk_label_set_markup_with_mnemonic (GTK_LABEL (label), _("<b>Last Test:</b>"));
         gtk_table_attach (GTK_TABLE (table), label, 0, 1, row, row + 1,
-                          GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
+                          GTK_FILL, GTK_FILL, 0, 0);
 
         label = gtk_label_new (NULL);
         gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
         dialog->priv->last_self_test_result_label = label;
 
         gtk_table_attach (GTK_TABLE (table), label, 1, 2, row, row + 1,
-                          GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
+                          GTK_FILL, GTK_FILL, 0, 0);
 
         row++;
 
@@ -253,14 +492,14 @@ gdu_ata_smart_dialog_constructed (GObject *object)
         gtk_misc_set_alignment (GTK_MISC (label), 1.0, 0.5);
         gtk_label_set_markup_with_mnemonic (GTK_LABEL (label), _("<b>Updated:</b>"));
         gtk_table_attach (GTK_TABLE (table), label, 0, 1, row, row + 1,
-                          GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
+                          GTK_FILL, GTK_FILL, 0, 0);
 
         label = gdu_time_label_new (NULL);
         gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
         dialog->priv->updated_label = label;
 
         gtk_table_attach (GTK_TABLE (table), label, 1, 2, row, row + 1,
-                          GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
+                          GTK_FILL, GTK_FILL, 0, 0);
 
         row++;
 
@@ -269,14 +508,14 @@ gdu_ata_smart_dialog_constructed (GObject *object)
         gtk_misc_set_alignment (GTK_MISC (label), 1.0, 0.5);
         gtk_label_set_markup_with_mnemonic (GTK_LABEL (label), _("<b>Sectors:</b>"));
         gtk_table_attach (GTK_TABLE (table), label, 0, 1, row, row + 1,
-                          GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
+                          GTK_FILL, GTK_FILL, 0, 0);
 
         label = gtk_label_new (NULL);
         gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
         dialog->priv->sectors_label = label;
 
         gtk_table_attach (GTK_TABLE (table), label, 1, 2, row, row + 1,
-                          GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
+                          GTK_FILL, GTK_FILL, 0, 0);
 
         row++;
 
@@ -285,14 +524,14 @@ gdu_ata_smart_dialog_constructed (GObject *object)
         gtk_misc_set_alignment (GTK_MISC (label), 1.0, 0.5);
         gtk_label_set_markup_with_mnemonic (GTK_LABEL (label), _("<b>Attributes:</b>"));
         gtk_table_attach (GTK_TABLE (table), label, 0, 1, row, row + 1,
-                          GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
+                          GTK_FILL, GTK_FILL, 0, 0);
 
         label = gtk_label_new (NULL);
         gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
         dialog->priv->attributes_label = label;
 
         gtk_table_attach (GTK_TABLE (table), label, 1, 2, row, row + 1,
-                          GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
+                          GTK_FILL, GTK_FILL, 0, 0);
 
         row++;
 
@@ -301,14 +540,14 @@ gdu_ata_smart_dialog_constructed (GObject *object)
         gtk_misc_set_alignment (GTK_MISC (label), 1.0, 0.5);
         gtk_label_set_markup_with_mnemonic (GTK_LABEL (label), _("<b>Assessment:</b>"));
         gtk_table_attach (GTK_TABLE (table), label, 0, 1, row, row + 1,
-                          GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
+                          GTK_FILL, GTK_FILL, 0, 0);
 
         label = gtk_label_new (NULL);
         gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
         dialog->priv->assessment_label = label;
 
         gtk_table_attach (GTK_TABLE (table), label, 1, 2, row, row + 1,
-                          GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
+                          GTK_FILL, GTK_FILL, 0, 0);
 
         row++;
 
@@ -338,6 +577,12 @@ gdu_ata_smart_dialog_constructed (GObject *object)
                                               GTK_SORT_ASCENDING);
         dialog->priv->tree_view = tree_view;
 
+        selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view));
+        g_signal_connect (selection,
+                          "changed",
+                          G_CALLBACK (selection_changed),
+                          dialog);
+
         column = gtk_tree_view_column_new ();
         gtk_tree_view_column_set_title (column, "ID");
         renderer = gtk_cell_renderer_text_new ();
@@ -441,11 +686,6 @@ gdu_ata_smart_dialog_constructed (GObject *object)
 
 	gtk_box_pack_start (GTK_BOX (vbox), scrolled_window, TRUE, TRUE, 0);
 
-        GtkWidget *button;
-        button = gtk_button_new_with_mnemonic ("_Bar");
-        g_signal_connect (button, "clicked", G_CALLBACK (on_bar_clicked), dialog);
-	gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
-
         gtk_window_set_default_size (GTK_WINDOW (dialog), 500, 500);
 
         update_dialog (dialog);
@@ -501,7 +741,9 @@ gdu_ata_smart_dialog_new (GtkWindow *parent,
 /* ---------------------------------------------------------------------------------------------------- */
 
 static gchar *
-pretty_to_string (guint64 pretty_value, GduAtaSmartAttributeUnit pretty_unit)
+pretty_to_string (guint64                  pretty_value,
+                  GduAtaSmartAttributeUnit pretty_unit,
+                  gboolean                 long_string)
 {
         gchar *ret;
         gdouble celcius;
@@ -510,30 +752,56 @@ pretty_to_string (guint64 pretty_value, GduAtaSmartAttributeUnit pretty_unit)
         switch (pretty_unit) {
 
         case GDU_ATA_SMART_ATTRIBUTE_UNIT_MSECONDS:
-                if (pretty_value > 1000 * 60 * 60 * 24) {
-                        ret = g_strdup_printf (_("%.3g days"), pretty_value / 1000.0 / 60.0 / 60.0 / 24.0);
-                } else if (pretty_value > 1000 * 60 * 60) {
-                        ret = g_strdup_printf (_("%.3g hours"), pretty_value / 1000.0 / 60.0 / 60.0);
-                } else if (pretty_value > 1000 * 60) {
-                        ret = g_strdup_printf (_("%.3g mins"), pretty_value / 1000.0 / 60.0);
-                } else if (pretty_value > 1000) {
-                        ret = g_strdup_printf (_("%.3g secs"), pretty_value / 1000.0);
+                if (long_string) {
+                        if (pretty_value > 1000 * 60 * 60 * 24) {
+                                ret = g_strdup_printf (_("%.3f days"), pretty_value / 1000.0 / 60.0 / 60.0 / 24.0);
+                        } else if (pretty_value > 1000 * 60 * 60) {
+                                ret = g_strdup_printf (_("%.3f hours"), pretty_value / 1000.0 / 60.0 / 60.0);
+                        } else if (pretty_value > 1000 * 60) {
+                                ret = g_strdup_printf (_("%.3f minutes"), pretty_value / 1000.0 / 60.0);
+                        } else if (pretty_value > 1000) {
+                                ret = g_strdup_printf (_("%.3f seconds"), pretty_value / 1000.0);
+                        } else {
+                                ret = g_strdup_printf (_("%" G_GUINT64_FORMAT " msec"), pretty_value);
+                        }
                 } else {
-                        ret = g_strdup_printf (_("%" G_GUINT64_FORMAT " msec"), pretty_value);
+                        if (pretty_value > 1000 * 60 * 60 * 24) {
+                                ret = g_strdup_printf (_("%.0f d"), pretty_value / 1000.0 / 60.0 / 60.0 / 24.0);
+                        } else if (pretty_value > 1000 * 60 * 60) {
+                                ret = g_strdup_printf (_("%.0f h"), pretty_value / 1000.0 / 60.0 / 60.0);
+                        } else if (pretty_value > 1000 * 60) {
+                                ret = g_strdup_printf (_("%.0f m"), pretty_value / 1000.0 / 60.0);
+                        } else if (pretty_value > 1000) {
+                                ret = g_strdup_printf (_("%.0f s"), pretty_value / 1000.0);
+                        } else {
+                                ret = g_strdup_printf (_("%" G_GUINT64_FORMAT " msec"), pretty_value);
+                        }
                 }
                 break;
 
         case GDU_ATA_SMART_ATTRIBUTE_UNIT_SECTORS:
-                if (pretty_value == 1)
-                        ret = g_strdup (_("1 Sector"));
-                else
-                        ret = g_strdup_printf (_("%" G_GUINT64_FORMAT " Sectors"), pretty_value);
+                if (long_string) {
+                        if (pretty_value == 1)
+                                ret = g_strdup (_("1 Sector"));
+                        else
+                                ret = g_strdup_printf (_("%" G_GUINT64_FORMAT " Sectors"), pretty_value);
+                } else {
+                        ret = g_strdup_printf (_("%" G_GUINT64_FORMAT), pretty_value);
+                }
                 break;
 
         case GDU_ATA_SMART_ATTRIBUTE_UNIT_MKELVIN:
-                celcius = pretty_value / 1000.0 - 273.15;
-                fahrenheit = 9.0 * celcius / 5.0 + 32.0;
-                ret = g_strdup_printf (_("%.3g\302\260 C / %.3g\302\260 F"), celcius, fahrenheit);
+                if (long_string) {
+                        celcius = pretty_value / 1000.0 - 273.15;
+                        fahrenheit = 9.0 * celcius / 5.0 + 32.0;
+                        ret = g_strdup_printf (_("%.3f\302\260 C / %.3f\302\260 F"), celcius, fahrenheit);
+                } else {
+                        /* We could choose kelvin here to treat C and F camps equally. But
+                         * that would be lame.
+                         */
+                        celcius = pretty_value / 1000.0 - 273.15;
+                        ret = g_strdup_printf (_("%.0f\302\260 C"), celcius);
+                }
                 break;
 
         default:
@@ -549,6 +817,33 @@ pretty_to_string (guint64 pretty_value, GduAtaSmartAttributeUnit pretty_unit)
 /* ---------------------------------------------------------------------------------------------------- */
 
 static void
+get_historical_data_cb (GduDevice *device,
+                        GList     *smart_data,
+                        GError    *error,
+                        gpointer   user_data)
+{
+        GduAtaSmartDialog *dialog = GDU_ATA_SMART_DIALOG (user_data);
+
+        if (error != NULL) {
+                g_warning ("Error getting historical data: %s", error->message);
+                g_error_free (error);
+        } else {
+                if (dialog->priv->historical_data != NULL) {
+                        g_list_foreach (dialog->priv->historical_data, (GFunc) g_object_unref, NULL);
+                        g_list_free (dialog->priv->historical_data);
+                }
+                dialog->priv->historical_data = smart_data;
+
+                g_debug ("got historical data (%d elems)", g_list_length (smart_data));
+
+                update_dialog (dialog);
+        }
+
+        g_object_unref (dialog);
+}
+
+
+static void
 update_dialog (GduAtaSmartDialog *dialog)
 {
         gchar *assessment_text;
@@ -627,13 +922,13 @@ update_dialog (GduAtaSmartDialog *dialog)
         if (power_on_msec == 0) {
                 powered_on_text = g_strdup (_("Unknown"));
         } else {
-                powered_on_text = pretty_to_string (power_on_msec, GDU_ATA_SMART_ATTRIBUTE_UNIT_MSECONDS);
+                powered_on_text = pretty_to_string (power_on_msec, GDU_ATA_SMART_ATTRIBUTE_UNIT_MSECONDS, TRUE);
         }
 
         if (temperature_mkelvin == 0) {
                 temperature_text = g_strdup (_("Unknown"));
         } else {
-                temperature_text = pretty_to_string (temperature_mkelvin, GDU_ATA_SMART_ATTRIBUTE_UNIT_MKELVIN);
+                temperature_text = pretty_to_string (temperature_mkelvin, GDU_ATA_SMART_ATTRIBUTE_UNIT_MKELVIN, TRUE);
         }
 
         dialog->priv->last_updated = updated.tv_sec = gdu_device_drive_ata_smart_get_time_collected (dialog->priv->device);
@@ -757,7 +1052,7 @@ update_dialog (GduAtaSmartDialog *dialog)
 
                 pretty_value = gdu_ata_smart_attribute_get_pretty_value (a);
                 pretty_unit = gdu_ata_smart_attribute_get_pretty_unit (a);
-                pretty_str = pretty_to_string (pretty_value, pretty_unit);
+                pretty_str = pretty_to_string (pretty_value, pretty_unit, TRUE);
 
                 is_good = gdu_ata_smart_attribute_get_good (a);
                 is_good_valid = gdu_ata_smart_attribute_get_good_valid (a);
@@ -823,6 +1118,16 @@ update_dialog (GduAtaSmartDialog *dialog)
         g_free (powered_on_text);
         g_free (temperature_text);
         g_free (selftest_text);
+
+        /* TODO: also fetch new data if current data is out of date */
+        if (dialog->priv->historical_data == NULL) {
+                gdu_device_drive_ata_smart_get_historical_data (dialog->priv->device,
+                                                                0, /* since */
+                                                                0, /* until */
+                                                                0, /* spacing */
+                                                                get_historical_data_cb,
+                                                                g_object_ref (dialog));
+        }
 }
 
 static void
diff --git a/src/gdu-gtk/gdu-graph.c b/src/gdu-gtk/gdu-graph.c
new file mode 100644
index 0000000..f0b0cf9
--- /dev/null
+++ b/src/gdu-gtk/gdu-graph.c
@@ -0,0 +1,647 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+/* gdu-graph.c
+ *
+ * Copyright (C) 2009 David Zeuthen
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include <string.h>
+#include <math.h>
+
+#include "gdu-graph.h"
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+        gchar    *id;
+        GdkColor *color;
+        GArray   *points;
+} Curve;
+
+static Curve *
+curve_new (const gchar *curve_id,
+           GdkColor    *color,
+           GArray      *points)
+{
+        Curve *c;
+
+        c = g_new0 (Curve, 1);
+        c->id = g_strdup (curve_id);
+        c->color = gdk_color_copy (color);
+        c->points = g_array_ref (points);
+
+        return c;
+}
+
+static void
+curve_free (Curve *c)
+{
+        g_free (c->id);
+        gdk_color_free (c->color);
+        g_array_unref (c->points);
+        g_free (c);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+struct GduGraphPrivate
+{
+        guint foo;
+
+        gchar **x_markers;
+        gchar **y_markers_left;
+        gchar **y_markers_right;
+
+        GPtrArray *curves;
+        GPtrArray *bands;
+};
+
+G_DEFINE_TYPE (GduGraph, gdu_graph, GTK_TYPE_DRAWING_AREA)
+
+static gboolean gdu_graph_expose_event (GtkWidget      *widget,
+                                        GdkEventExpose *event);
+
+enum
+{
+        PROP_0,
+        PROP_X_MARKERS,
+        PROP_Y_MARKERS_LEFT,
+        PROP_Y_MARKERS_RIGHT,
+};
+
+static void
+gdu_graph_set_property (GObject      *object,
+                        guint         prop_id,
+                        const GValue *value,
+                        GParamSpec   *pspec)
+{
+        GduGraph *graph = GDU_GRAPH (object);
+
+        switch (prop_id) {
+        case PROP_X_MARKERS:
+                gdu_graph_set_x_markers (graph, g_value_get_boxed (value));
+                break;
+
+        case PROP_Y_MARKERS_LEFT:
+                gdu_graph_set_y_markers_left (graph, g_value_get_boxed (value));
+                break;
+
+        case PROP_Y_MARKERS_RIGHT:
+                gdu_graph_set_y_markers_right (graph, g_value_get_boxed (value));
+                break;
+
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                break;
+        }
+}
+
+static void
+gdu_graph_get_property (GObject     *object,
+                        guint        prop_id,
+                        GValue      *value,
+                        GParamSpec  *pspec)
+{
+        GduGraph *graph = GDU_GRAPH (object);
+
+        switch (prop_id) {
+        case PROP_X_MARKERS:
+                g_value_set_boxed (value, graph->priv->x_markers);
+                break;
+
+        case PROP_Y_MARKERS_LEFT:
+                g_value_set_boxed (value, graph->priv->y_markers_left);
+                break;
+
+        case PROP_Y_MARKERS_RIGHT:
+                g_value_set_boxed (value, graph->priv->y_markers_right);
+                break;
+
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                break;
+    }
+}
+
+static void
+gdu_graph_finalize (GObject *object)
+{
+        GduGraph *graph = GDU_GRAPH (object);
+
+        g_strfreev (graph->priv->x_markers);
+        g_strfreev (graph->priv->y_markers_left);
+        g_strfreev (graph->priv->y_markers_right);
+
+        g_ptr_array_unref (graph->priv->curves);
+        g_ptr_array_unref (graph->priv->bands);
+
+        if (G_OBJECT_CLASS (gdu_graph_parent_class)->finalize != NULL)
+                G_OBJECT_CLASS (gdu_graph_parent_class)->finalize (object);
+}
+
+static void
+gdu_graph_class_init (GduGraphClass *klass)
+{
+        GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+        GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+        gobject_class->finalize     = gdu_graph_finalize;
+        gobject_class->set_property = gdu_graph_set_property;
+        gobject_class->get_property = gdu_graph_get_property;
+
+        widget_class->expose_event  = gdu_graph_expose_event;
+
+        g_type_class_add_private (klass, sizeof (GduGraphPrivate));
+
+        g_object_class_install_property (gobject_class,
+                                         PROP_X_MARKERS,
+                                         g_param_spec_boxed ("x-markers",
+                                                             _("X Markers"),
+                                                             _("Markers to print on the X axis"),
+                                                             G_TYPE_STRV,
+                                                             G_PARAM_READABLE |
+                                                             G_PARAM_WRITABLE |
+                                                             G_PARAM_CONSTRUCT));
+
+        g_object_class_install_property (gobject_class,
+                                         PROP_Y_MARKERS_LEFT,
+                                         g_param_spec_boxed ("y-markers-left",
+                                                             _("Left Y Markers"),
+                                                             _("Markers to print on the left Y axis"),
+                                                             G_TYPE_STRV,
+                                                             G_PARAM_READABLE |
+                                                             G_PARAM_WRITABLE |
+                                                             G_PARAM_CONSTRUCT));
+
+        g_object_class_install_property (gobject_class,
+                                         PROP_Y_MARKERS_RIGHT,
+                                         g_param_spec_boxed ("y-markers-right",
+                                                             _("Right Y Markers"),
+                                                             _("Markers to print on the right Y axis"),
+                                                             G_TYPE_STRV,
+                                                             G_PARAM_READABLE |
+                                                             G_PARAM_WRITABLE |
+                                                             G_PARAM_CONSTRUCT));
+}
+
+static void
+gdu_graph_init (GduGraph *graph)
+{
+        graph->priv = G_TYPE_INSTANCE_GET_PRIVATE (graph, GDU_TYPE_GRAPH, GduGraphPrivate);
+        graph->priv->curves = g_ptr_array_new_with_free_func ((GDestroyNotify) curve_free);
+        graph->priv->bands  = g_ptr_array_new_with_free_func ((GDestroyNotify) curve_free);
+}
+
+GtkWidget *
+gdu_graph_new (void)
+{
+        return GTK_WIDGET (g_object_new (GDU_TYPE_GRAPH, NULL));
+}
+
+gchar **
+gdu_graph_get_x_markers (GduGraph *graph)
+{
+        g_return_val_if_fail (GDU_IS_GRAPH (graph), NULL);
+        return g_strdupv (graph->priv->x_markers);
+}
+
+gchar **
+gdu_graph_get_y_markers_left (GduGraph *graph)
+{
+        g_return_val_if_fail (GDU_IS_GRAPH (graph), NULL);
+        return g_strdupv (graph->priv->y_markers_left);
+}
+
+gchar **
+gdu_graph_get_y_markers_right (GduGraph *graph)
+{
+        g_return_val_if_fail (GDU_IS_GRAPH (graph), NULL);
+        return g_strdupv (graph->priv->y_markers_right);
+}
+
+void
+gdu_graph_set_x_markers (GduGraph           *graph,
+                         const gchar* const *markers)
+{
+        g_return_if_fail (GDU_IS_GRAPH (graph));
+        g_strfreev (graph->priv->x_markers);
+        graph->priv->x_markers = g_strdupv ((gchar **) markers);
+        if (GTK_WIDGET (graph)->window != NULL)
+                gdk_window_invalidate_rect (GTK_WIDGET (graph)->window, NULL, TRUE);
+}
+
+void
+gdu_graph_set_y_markers_left (GduGraph           *graph,
+                              const gchar* const *markers)
+{
+        g_return_if_fail (GDU_IS_GRAPH (graph));
+        g_strfreev (graph->priv->y_markers_left);
+        graph->priv->y_markers_left = g_strdupv ((gchar **) markers);
+        if (GTK_WIDGET (graph)->window != NULL)
+                gdk_window_invalidate_rect (GTK_WIDGET (graph)->window, NULL, TRUE);
+}
+
+void
+gdu_graph_set_y_markers_right (GduGraph           *graph,
+                               const gchar* const *markers)
+{
+        g_return_if_fail (GDU_IS_GRAPH (graph));
+        g_strfreev (graph->priv->y_markers_right);
+        graph->priv->y_markers_right = g_strdupv ((gchar **) markers);
+        if (GTK_WIDGET (graph)->window != NULL)
+                gdk_window_invalidate_rect (GTK_WIDGET (graph)->window, NULL, TRUE);
+}
+
+static gdouble
+measure_width (cairo_t     *cr,
+               const gchar *s)
+{
+        cairo_text_extents_t te;
+        cairo_select_font_face (cr,
+                                "sans",
+                                CAIRO_FONT_SLANT_NORMAL,
+                                CAIRO_FONT_WEIGHT_NORMAL);
+        cairo_set_font_size (cr, 8.0);
+        cairo_text_extents (cr, s, &te);
+        return te.width;
+}
+
+static gboolean
+gdu_graph_expose_event (GtkWidget      *widget,
+                        GdkEventExpose *event)
+{
+        GduGraph *graph = GDU_GRAPH (widget);
+        guint n_x_markers;
+        guint n_y_markers_left;
+        guint n_y_markers_right;
+        cairo_t *cr;
+        gdouble width, height;
+        guint n;
+
+        n_x_markers = graph->priv->x_markers != NULL ? g_strv_length (graph->priv->x_markers) : 0;
+        n_y_markers_left = graph->priv->y_markers_left != NULL ? g_strv_length (graph->priv->y_markers_left) : 0;
+        n_y_markers_right = graph->priv->y_markers_right != NULL ? g_strv_length (graph->priv->y_markers_right) : 0;
+
+        width = widget->allocation.width;
+        height = widget->allocation.height;
+
+        cr = gdk_cairo_create (widget->window);
+        cairo_rectangle (cr,
+                         event->area.x, event->area.y,
+                         event->area.width, event->area.height);
+        cairo_clip (cr);
+
+        double gx, gy, gw, gh;
+        gx = 0;
+        gy = 10;
+        gw = width - 10;
+        gh = height - gy - 30;
+
+        guint twidth;
+
+        /* measure text on the left y-axis */
+        guint y_left_max_width;
+        y_left_max_width = 0;
+        for (n = 0; n < n_y_markers_left; n++) {
+                twidth = ceil (measure_width (cr, graph->priv->y_markers_left[n]));
+                if (twidth > y_left_max_width)
+                        y_left_max_width = twidth;
+        }
+        /* include half width of first xmarker label */
+        if (n_x_markers > 0) {
+                twidth = ceil (measure_width (cr, graph->priv->x_markers[0]));
+                if (twidth/2 > y_left_max_width)
+                        y_left_max_width = twidth/2;
+        }
+        y_left_max_width += 6; /* padding */
+        gx += y_left_max_width;
+        gw -= y_left_max_width;
+
+        /* measure text on the right y-axis */
+        guint y_right_max_width;
+        y_right_max_width = 0;
+        for (n = 0; n < n_y_markers_right; n++) {
+                twidth = ceil (measure_width (cr, graph->priv->y_markers_right[n]));
+                if (twidth/2 > y_right_max_width)
+                        y_right_max_width = twidth/2;
+        }
+        /* include half width of last xmarker label */
+        if (n_x_markers > 0) {
+                twidth = ceil (measure_width (cr, graph->priv->x_markers[n_x_markers - 1]));
+                y_right_max_width += twidth/2;
+        }
+        y_right_max_width += 6; /* padding */
+        gw -= y_right_max_width;
+
+        /* draw the box to draw in */
+        cairo_set_source_rgb (cr, 1, 1, 1);
+        cairo_rectangle (cr, gx, gy, gw, gh);
+        cairo_set_line_width (cr, 0.0);
+	cairo_fill (cr);
+
+        /* draw markers on the left y-axis */
+        for (n = 0; n < n_y_markers_left; n++) {
+                double pos;
+
+                pos = ceil (gy + gh / (n_y_markers_left - 1) * n);
+
+                const char *s;
+                s = graph->priv->y_markers_left[n_y_markers_left - 1 - n];
+
+                cairo_text_extents_t te;
+                cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
+                cairo_select_font_face (cr, "sans",
+                                        CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
+                cairo_set_font_size (cr, 8.0);
+                cairo_text_extents (cr, s, &te);
+                cairo_move_to (cr,
+                               gx/2.0 - 3 - te.width/2  - te.x_bearing,
+                               pos - te.height/2 - te.y_bearing);
+
+                cairo_show_text (cr, s);
+
+                cairo_set_line_width (cr, 1.0);
+                double dashes[1] = {2.0};
+                cairo_set_dash (cr, dashes, 1, 0.0);
+                cairo_move_to (cr,
+                               gx - 0.5,
+                               pos - 0.5);
+                cairo_line_to (cr,
+                               gx - 0.5 + gw,
+                               pos - 0.5);
+                cairo_stroke (cr);
+        }
+
+        /* draw markers on the right y-axis */
+        for (n = 0; n < n_y_markers_right; n++) {
+                double pos;
+
+                pos = ceil (gy + gh / (n_y_markers_right - 1) * n);
+
+                const char *s;
+                s = graph->priv->y_markers_right[n_y_markers_right - 1 - n];
+
+                cairo_text_extents_t te;
+                cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
+                cairo_select_font_face (cr, "sans",
+                                        CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
+                cairo_set_font_size (cr, 8.0);
+                cairo_text_extents (cr, s, &te);
+                cairo_move_to (cr,
+                               gx + gw + y_right_max_width/2.0 + 3 - te.width/2  - te.x_bearing,
+                               pos - te.height/2 - te.y_bearing);
+
+                cairo_show_text (cr, s);
+        }
+
+        guint64 t_left;
+        guint64 t_right;
+        GTimeVal now;
+
+        g_get_current_time (&now);
+        /*
+        switch (0) {
+        default:
+        case 0:
+                t_left = now.tv_sec - 6 * 60 * 60;
+                break;
+        case 1:
+                t_left = now.tv_sec - 24 * 60 * 60;
+                break;
+        case 2:
+                t_left = now.tv_sec - 3 * 24 * 60 * 60;
+                break;
+        case 3:
+                t_left = now.tv_sec - 12 * 24 * 60 * 60;
+                break;
+        case 4:
+                t_left = now.tv_sec - 36 * 24 * 60 * 60;
+                break;
+        case 5:
+                t_left = now.tv_sec - 96 * 24 * 60 * 60;
+                break;
+        }
+        t_right = now.tv_sec;
+        */
+        t_left = now.tv_sec - 6 * 24 * 60 * 60;
+        t_right = now.tv_sec;
+
+        /* draw time markers on x-axis */
+        for (n = 0; n < n_x_markers; n++) {
+                double pos;
+                guint64 val;
+                const gchar *s;
+                cairo_text_extents_t te;
+                double dashes[1] = {2.0};
+
+                s = graph->priv->x_markers[n];
+
+                pos = ceil (gx + gw / (n_x_markers - 1) * n);
+                val = t_left + (t_right - t_left) * n / (n_x_markers - 1);
+
+                cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
+                cairo_select_font_face (cr, "sans",
+                                        CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
+                cairo_set_font_size (cr, 8.0);
+                cairo_text_extents (cr, s, &te);
+                cairo_move_to (cr,
+                               pos - te.width/2  - te.x_bearing,
+                               height - 30.0/2  - te.height/2 - te.y_bearing); /* TODO */
+
+                cairo_show_text (cr, s);
+
+                cairo_set_line_width (cr, 1.0);
+                cairo_set_dash (cr, dashes, 1, 0.0);
+                cairo_move_to (cr,
+                               pos - 0.5,
+                               gy - 0.5);
+                cairo_line_to (cr,
+                               pos - 0.5,
+                               gy - 0.5 + gh);
+                cairo_stroke (cr);
+        }
+
+        /* clip to the graph area */
+        cairo_rectangle (cr, gx, gy, gw, gh);
+        cairo_clip (cr);
+
+        /* draw all bands */
+        for (n = 0; n < graph->priv->bands->len; n++) {
+                Curve *c = (Curve *) graph->priv->bands->pdata[n];
+                guint m;
+
+                cairo_new_path (cr);
+                cairo_set_line_width (cr, 0.0);
+                cairo_set_source_rgba (cr, 0.5, 0.5, 0.5, 0.5);
+
+                for (m = 0; m < c->points->len; m+= 2) {
+                        GduGraphPoint *point;
+                        gdouble x0, x1;
+                        gdouble x, width;
+                        cairo_pattern_t *pat;
+
+                        point = &g_array_index (c->points, GduGraphPoint, m);
+                        x0 = gx + gw * point->x;
+
+                        point = &g_array_index (c->points, GduGraphPoint, m + 1);
+                        x1 = gx + gw * point->x;
+
+                        x = x0;
+                        if (x1 < x0)
+                                x = x1;
+                        width = fabs (x1 - x0);
+
+                        g_debug ("band: %f to %f", x0, x1);
+
+                        pat = cairo_pattern_create_linear (x, gy,
+                                                           x + width, gy);
+                        cairo_pattern_add_color_stop_rgba (pat, 0.00, 1.00, 1.00, 1.00, 0.00);
+                        cairo_pattern_add_color_stop_rgba (pat, 0.35, 0.85, 0.85, 0.85, 0.50);
+                        cairo_pattern_add_color_stop_rgba (pat, 0.65, 0.85, 0.85, 0.85, 0.50);
+                        cairo_pattern_add_color_stop_rgba (pat, 1.00, 1.00, 1.00, 1.00, 0.00);
+                        cairo_set_source (cr, pat);
+                        cairo_pattern_destroy (pat);
+
+                        cairo_rectangle (cr, x, gy, width, gh);
+                        cairo_fill (cr);
+                }
+        }
+
+        /* draw all curves */
+        for (n = 0; n < graph->priv->curves->len; n++) {
+                Curve *c = (Curve *) graph->priv->curves->pdata[n];
+                guint m;
+
+                cairo_new_path (cr);
+                cairo_set_dash (cr, NULL, 0, 0.0);
+                cairo_set_line_width (cr, 1.0);
+                gdk_cairo_set_source_color (cr, c->color);
+
+                for (m = 0; m < c->points->len; m++) {
+                        GduGraphPoint *point;
+                        gdouble x, y;
+
+                        point = &g_array_index (c->points, GduGraphPoint, m);
+
+                        x = gx + gw * point->x;
+                        y = gy + gh * (1.0f - point->y);
+
+                        if (y < gy + 1.0)
+                                y = gy;
+
+                        if (y > gy + gh - 1.0)
+                                y = gy + gh - 1.0;
+
+                        cairo_line_to (cr, x, y);
+                }
+                cairo_stroke (cr);
+        }
+
+        /* propagate event further */
+        return FALSE;
+}
+
+gboolean
+gdu_graph_remove_curve (GduGraph           *graph,
+                        const gchar        *curve_id)
+{
+        guint n;
+        gboolean found;
+
+        found = FALSE;
+        for (n = 0; n < graph->priv->curves->len; n++) {
+                Curve *c = (Curve *) graph->priv->curves->pdata[n];
+                if (g_strcmp0 (curve_id, c->id) == 0) {
+                        g_ptr_array_remove_index (graph->priv->curves, n);
+                        found = TRUE;
+                        break;
+                }
+        }
+
+        return found;
+}
+
+void
+gdu_graph_set_curve (GduGraph           *graph,
+                     const gchar        *curve_id,
+                     GdkColor           *color,
+                     GArray             *points)
+{
+        g_return_if_fail (GDU_IS_GRAPH (graph));
+        g_return_if_fail (curve_id != NULL);
+        g_return_if_fail (color != NULL);
+
+        if (points == NULL) {
+                gdu_graph_remove_curve (graph, curve_id);
+        } else {
+                gdu_graph_remove_curve (graph, curve_id);
+                g_ptr_array_add (graph->priv->curves,
+                                 curve_new (curve_id,
+                                            color,
+                                            points));
+        }
+
+        if (GTK_WIDGET (graph)->window != NULL)
+                gdk_window_invalidate_rect (GTK_WIDGET (graph)->window, NULL, TRUE);
+}
+
+gboolean
+gdu_graph_remove_band (GduGraph           *graph,
+                       const gchar        *band_id)
+{
+        guint n;
+        gboolean found;
+
+        found = FALSE;
+        for (n = 0; n < graph->priv->bands->len; n++) {
+                Curve *c = (Curve *) graph->priv->bands->pdata[n];
+                if (g_strcmp0 (band_id, c->id) == 0) {
+                        g_ptr_array_remove_index (graph->priv->bands, n);
+                        found = TRUE;
+                        break;
+                }
+        }
+
+        return found;
+}
+
+void
+gdu_graph_set_band (GduGraph           *graph,
+                    const gchar        *band_id,
+                    GdkColor           *color,
+                    GArray             *points)
+{
+        g_return_if_fail (GDU_IS_GRAPH (graph));
+        g_return_if_fail (band_id != NULL);
+        g_return_if_fail (color != NULL);
+
+        if (points == NULL) {
+                gdu_graph_remove_band (graph, band_id);
+        } else {
+                gdu_graph_remove_band (graph, band_id);
+                g_ptr_array_add (graph->priv->bands,
+                                 curve_new (band_id,
+                                            color,
+                                            points));
+        }
+
+        if (GTK_WIDGET (graph)->window != NULL)
+                gdk_window_invalidate_rect (GTK_WIDGET (graph)->window, NULL, TRUE);
+}
diff --git a/src/gdu-gtk/gdu-graph.h b/src/gdu-gtk/gdu-graph.h
new file mode 100644
index 0000000..92fe0f0
--- /dev/null
+++ b/src/gdu-gtk/gdu-graph.h
@@ -0,0 +1,93 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+/* gdu-graph.h
+ *
+ * Copyright (C) 2007 David Zeuthen
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#if !defined (__GDU_GTK_INSIDE_GDU_GTK_H) && !defined (GDU_GTK_COMPILATION)
+#error "Only <gdu-gtk/gdu-gtk.h> can be included directly, this file may disappear or change contents."
+#endif
+
+#ifndef GDU_GRAPH_H
+#define GDU_GRAPH_H
+
+#include <gdu-gtk/gdu-gtk-types.h>
+
+#define GDU_TYPE_GRAPH             (gdu_graph_get_type ())
+#define GDU_GRAPH(obj)             (G_TYPE_CHECK_INSTANCE_CAST ((obj), GDU_TYPE_GRAPH, GduGraph))
+#define GDU_GRAPH_CLASS(obj)       (G_TYPE_CHECK_CLASS_CAST ((obj), GDU_GRAPH,  GduGraphClass))
+#define GDU_IS_GRAPH(obj)          (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GDU_TYPE_GRAPH))
+#define GDU_IS_GRAPH_CLASS(obj)    (G_TYPE_CHECK_CLASS_TYPE ((obj), GDU_TYPE_GRAPH))
+#define GDU_GRAPH_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), GDU_TYPE_GRAPH, GduGraphClass))
+
+typedef struct GduGraphClass       GduGraphClass;
+typedef struct GduGraphPrivate     GduGraphPrivate;
+
+struct GduGraph
+{
+        GtkDrawingArea parent;
+
+        /* private */
+        GduGraphPrivate *priv;
+};
+
+struct GduGraphClass
+{
+        GtkDrawingAreaClass parent_class;
+};
+
+
+GType       gdu_graph_get_type            (void);
+GtkWidget  *gdu_graph_new                 (void);
+gchar     **gdu_graph_get_x_markers       (GduGraph           *graph);
+gchar     **gdu_graph_get_y_markers_left  (GduGraph           *graph);
+gchar     **gdu_graph_get_y_markers_right (GduGraph           *graph);
+void        gdu_graph_set_x_markers       (GduGraph           *graph,
+                                           const gchar* const *markers);
+void        gdu_graph_set_y_markers_left  (GduGraph           *graph,
+                                           const gchar* const *markers);
+void        gdu_graph_set_y_markers_right (GduGraph           *graph,
+                                           const gchar* const *markers);
+
+typedef struct GduGraphPoint GduGraphPoint;
+
+struct GduGraphPoint
+{
+        gfloat x;
+        gfloat y;
+        gpointer data;
+};
+
+gboolean    gdu_graph_remove_curve        (GduGraph           *graph,
+                                           const gchar        *curve_id);
+
+void        gdu_graph_set_curve           (GduGraph           *graph,
+                                           const gchar        *curve_id,
+                                           GdkColor           *color,
+                                           GArray             *points);
+
+gboolean    gdu_graph_remove_band         (GduGraph           *graph,
+                                           const gchar        *band_id);
+
+void        gdu_graph_set_band            (GduGraph           *graph,
+                                           const gchar        *band_id,
+                                           GdkColor           *color,
+                                           GArray             *points);
+
+
+#endif /* GDU_GRAPH_H */
diff --git a/src/gdu-gtk/gdu-gtk-types.h b/src/gdu-gtk/gdu-gtk-types.h
index 658d312..efab698 100644
--- a/src/gdu-gtk/gdu-gtk-types.h
+++ b/src/gdu-gtk/gdu-gtk-types.h
@@ -32,6 +32,7 @@
 
 G_BEGIN_DECLS
 
+typedef struct GduGraph                    GduGraph;
 typedef struct GduTimeLabel                GduTimeLabel;
 typedef struct GduAtaSmartDialog           GduAtaSmartDialog;
 typedef struct GduAtaSmartAttributeDialog  GduAtaSmartAttributeDialog;
diff --git a/src/gdu-gtk/gdu-gtk.h b/src/gdu-gtk/gdu-gtk.h
index bab7ad3..b530209 100644
--- a/src/gdu-gtk/gdu-gtk.h
+++ b/src/gdu-gtk/gdu-gtk.h
@@ -28,6 +28,7 @@
 
 #define __GDU_GTK_INSIDE_GDU_GTK_H
 #include <gdu-gtk/gdu-gtk-types.h>
+#include <gdu-gtk/gdu-graph.h>
 #include <gdu-gtk/gdu-time-label.h>
 #include <gdu-gtk/gdu-ata-smart-dialog.h>
 #include <gdu-gtk/gdu-ata-smart-attribute-dialog.h>
diff --git a/src/gdu/gdu-device.c b/src/gdu/gdu-device.c
index 82eeec0..b1794b9 100644
--- a/src/gdu/gdu-device.c
+++ b/src/gdu/gdu-device.c
@@ -2406,7 +2406,7 @@ op_ata_smart_historical_data_cb (DBusGProxy *proxy, GPtrArray *historical_data,
         if (historical_data != NULL && error == NULL)
                 ret = op_ata_smart_historical_data_compute_ret (historical_data);
 
-        if (data->callback == NULL)
+        if (data->callback != NULL)
                 data->callback (data->device, ret, error, data->user_data);
 
         g_object_unref (data->device);



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]