/* * Copyright (c) 2011- Osmo Antero. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License (GPL3), or 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 Library General Public License 3 for more details. * * You should have received a copy of the GNU Library General Public * License 3 along with this program; if not, see /usr/share/common-licenses/GPL file * or . */ #include "levelbar.h" #include // round() // A simple level bar widget. // By Osmo Antero. // // Sample call: // GtkWidget *lb = level_bar_new(); // gtk_widget_show(lb); // // Set value [0.0, 1.0]. // level_bar_set_fraction(LEVEL_BAR(lb), 0.8); // // Show %-value [0 - 100%] or plain value [0 - 1.0] on the level bar. See BAR_VALUE enum. // level_bar_set_value_type(LEVEL_BAR(lb), VALUE_PRECENT); // struct _LevelBarPrivate { gdouble fraction; guint bar_height; enum BAR_VALUE bar_value; enum BAR_SHAPE bar_shape; }; static void level_bar_get_preferred_width(GtkWidget *widget, gint *minimum, gint *natural); static void level_bar_get_preferred_height (GtkWidget *widget,gint *minimum, gint *natural); static void level_bar_real_update(LevelBar *progress); static gboolean level_bar_draw(GtkWidget *widget, cairo_t *cr); static void level_bar_finalize(GObject *object); #if !GLIB_CHECK_VERSION(2,38,0) // pkg-config --modversion glib-2.0 // GLIB is older than than 2.38 G_DEFINE_TYPE(LevelBar, level_bar, GTK_TYPE_WIDGET); #else // GLIB is 2.38 or newer G_DEFINE_TYPE_WITH_PRIVATE(LevelBar, level_bar, GTK_TYPE_WIDGET); #endif static void level_bar_class_init(LevelBarClass *class) { GObjectClass *gobject_class; GtkWidgetClass *widget_class; gobject_class = G_OBJECT_CLASS (class); widget_class = (GtkWidgetClass *)class; gobject_class->set_property = NULL; gobject_class->get_property = NULL; gobject_class->finalize = level_bar_finalize; widget_class->draw = level_bar_draw; widget_class->get_preferred_width = level_bar_get_preferred_width; widget_class->get_preferred_height = level_bar_get_preferred_height; #if !GLIB_CHECK_VERSION(2,38,0) // GLIB is older than than 2.38 // Will be deprecated 2.58 g_type_class_add_private(class, sizeof (LevelBarPrivate)); #endif } static void level_bar_init(LevelBar *pbar) { LevelBarPrivate *priv; #if !GLIB_CHECK_VERSION(2,38,0) // pkg-config --modversion glib-2.0 // GLIB is older than than 2.38 pbar->priv = G_TYPE_INSTANCE_GET_PRIVATE(pbar, TYPE_LEVEL_BAR, LevelBarPrivate); #else // GLIB is 2.38 or newer pbar->priv = level_bar_get_instance_private(pbar); #endif priv = pbar->priv; priv->fraction = 0.0; priv->bar_height = 8; priv->bar_value = VALUE_NONE; priv->bar_shape = SHAPE_CIRCLE; // pulsing line with circle at end. gtk_widget_set_has_window(GTK_WIDGET (pbar), FALSE); } GtkWidget *level_bar_new(void) { GtkWidget *pbar; pbar = g_object_new(TYPE_LEVEL_BAR, NULL); return pbar; } static void level_bar_real_update (LevelBar *pbar) { GtkWidget *widget; g_return_if_fail (IS_LEVEL_BAR (pbar)); LevelBarPrivate __attribute__ ((unused)) *priv = pbar->priv; widget = GTK_WIDGET(pbar); gtk_widget_queue_draw(widget); } static void level_bar_finalize (GObject *object) { G_OBJECT_CLASS(level_bar_parent_class)->finalize (object); } static void level_bar_get_preferred_width (GtkWidget *widget,gint *minimum, gint *natural) { *minimum = 50; *natural = 160; } static void level_bar_get_preferred_height(GtkWidget *widget, gint *minimum, gint *natural) { *minimum = 6; *natural = 8; } static gboolean level_bar_draw(GtkWidget *widget, cairo_t *cr) { // Draw level bar and optional text LevelBar *pbar = LEVEL_BAR (widget); LevelBarPrivate *priv = pbar->priv; GtkStyleContext *context; int width, height; context = gtk_widget_get_style_context(widget); width = gtk_widget_get_allocated_width(widget); height = gtk_widget_get_allocated_height(widget); // Bar thickness gdouble bar_height = MIN(height , priv->bar_height); // Vertical pos gdouble y = (height - bar_height)/2; // Pulse width gdouble w = priv->fraction/(1.00/width); // Debug: // LOG_DEBUG("width=%d height=%d bar_height=%2.1f y=%2.1f w=%2.1f fraction=%2.1f\n", width, height, bar_height, y, w, priv->fraction); gtk_style_context_save(context); gtk_render_background(context, cr, 0, 0, width, height); gtk_render_frame(context, cr, 0, 0, width, height); // Render level bar with current theme and color. // Progressbar style gtk_style_context_add_class(context, GTK_STYLE_CLASS_PROGRESSBAR); if (priv->fraction > 0.001) { switch (priv->bar_shape) { case SHAPE_LINE: // Render a single line if (priv->bar_value == VALUE_NONE) { // No value (text) shown. Draw a line on the middle. gtk_render_line(context, cr, 0, y + (bar_height / 2), w, y + (bar_height / 2)); } else { // Draw a line under text. gtk_render_line(context, cr, 0, y + (bar_height ), w, y + (bar_height )); } break; case SHAPE_LINE2: // Render two horizontal lines + close the end. gtk_render_line(context, cr, 0, y-1 , w, y-1); gtk_render_line(context, cr, 0, y + (bar_height ), w, y + (bar_height )); gtk_render_line(context, cr, w, y - 1, w, y + (bar_height )); break; case SHAPE_CIRCLE: // Draw a line on the middle + circle at the end. gtk_render_line(context, cr, 0, y + (bar_height / 2), w, y + (bar_height / 2)); gtk_style_context_set_state(context, GTK_STATE_FLAG_CHECKED); gtk_render_option(context, cr, w, y, bar_height+1, bar_height+1); break; default: // case SHAPE_LEVELBAR: // EDIT: gtk_render_activity() does not work in GTK 3.14+ // gtk_style_context_set_state(context, GTK_STATE_FLAG_ACTIVE); // gtk_render_activity(context, cr, 0, y, w, bar_height); // Render a filled frame (this is a typical levelbar). gtk_render_frame(context, cr, 0, y, w, bar_height); break; } } gtk_style_context_restore(context); cairo_select_font_face(cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); cairo_set_font_size(cr, priv->bar_height); GdkRGBA color; // Check: // $ pkg-config --modversion gtk+-3.0 // #if GTK_CHECK_VERSION(3,16,0) gtk_style_context_get_color(context, GTK_STATE_FLAG_NORMAL, &color); #else gtk_style_context_get_border_color(context, GTK_STATE_NORMAL, &color); #endif gdk_cairo_set_source_rgba(cr, &color); color.alpha = 0.9; // Calculate total width of scale cairo_text_extents_t extents; cairo_text_extents(cr, "0.0", &extents); gint total_w = 9 * (extents.x_advance + extents.width); // Debug: // g_print("Bar width=%d total_w=%d bearing=%3.1f advance=%3.1f char.width=%3.1f\n", width, total_w, // extents.x_bearing, extents.x_advance, extents.width); // Draw values gboolean draw_all = (total_w - extents.width) < width; // Show normalized value [0 - 1.0]? if (priv->bar_value == VALUE_0_1) { // Value: 0.1 0.2 0.3 0.4...0.9 gint i = 0; for (i=0; i < 10; i++) { gchar *s = NULL; // Draw all or draw only each second value? if (draw_all || (i % 2 == 0)) s = g_strdup_printf("%2.1f", (gdouble)i/10.0); if (!s) continue; cairo_text_extents_t extents; cairo_text_extents(cr, s, &extents); gdouble xx = (width/10) * i; gdouble yy = (height/2)-(extents.height/2 + extents.y_bearing) + 0.2; cairo_move_to(cr, xx, yy); cairo_show_text(cr, s); g_free(s); } // Show percentage value? } else if (priv->bar_value == VALUE_PERCENT) { // Value: 10% . 20% . 30% . 40% . 50% ... 90% gint i = 0; for (i=0; i < 10; i++) { gchar *s = NULL; if (i % 2 == 0) s = g_strdup_printf("%2.0f%%", (gdouble)i*10.0); else s = g_strdup_printf("%3s", "."); cairo_text_extents_t extents; cairo_text_extents(cr, s, &extents); gdouble xx = (width/10) * i; gdouble yy = (height/2)-(extents.height/2 + extents.y_bearing); cairo_move_to(cr, xx, yy); cairo_show_text(cr, s); g_free(s); } } #if 0 // Commented out by moma 30.sep.2012. // Set text if (priv->text) { cairo_select_font_face(cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); cairo_set_font_size(cr, 0.5*height); cairo_text_extents_t extents; cairo_text_extents(cr, priv->text, &extents); // Ref: https://www.cairographics.org/manual/cairo-cairo-scaled-font-t.html#cairo-text-extents-t gdouble xx = width-(extents.width + extents.x_bearing)-2; gdouble yy = height/2-(extents.height/2 + extents.y_bearing); GdkRGBA color; gtk_style_context_get_border_color(context, GTK_STATE_NORMAL, &color); gdk_cairo_set_source_rgba(cr, &color); color.alpha = 0.9; cairo_move_to(cr, xx, yy); cairo_show_text(cr, priv->text); } #endif return FALSE; } void level_bar_set_fraction(LevelBar *pbar, gdouble fraction) { // Set fraction [0.0, 1.0] LevelBarPrivate* priv; g_return_if_fail (IS_LEVEL_BAR (pbar)); priv = pbar->priv; priv->fraction = CLAMP(fraction, 0.0, 1.0); level_bar_real_update (pbar); } gdouble level_bar_get_fraction(LevelBar *pbar) { // Get fraction g_return_val_if_fail(IS_LEVEL_BAR (pbar), 0); return pbar->priv->fraction; } void level_bar_set_bar_height(LevelBar *pbar, guint height) { // Set bar height (thickness). Normally 8 pixels. g_return_if_fail(IS_LEVEL_BAR (pbar)); LevelBarPrivate* priv = pbar->priv; priv->bar_height = height; // Redraw level_bar_real_update(pbar); } guint level_bar_get_bar_height(LevelBar *pbar) { // Get bar thickness g_return_val_if_fail(IS_LEVEL_BAR(pbar), 0); return pbar->priv->bar_height; } void level_bar_set_value_type(LevelBar *pbar, enum BAR_VALUE bar_value) { // Set BAR_VALUE g_return_if_fail(IS_LEVEL_BAR(pbar)); LevelBarPrivate* priv = pbar->priv; priv->bar_value = bar_value; // Redraw level_bar_real_update(pbar); } enum BAR_VALUE level_bar_get_scale(LevelBar *pbar) { // Get BAR_VALUE g_return_val_if_fail(IS_LEVEL_BAR(pbar), VALUE_NONE); LevelBarPrivate* priv = pbar->priv; return priv->bar_value; } void level_bar_set_shape(LevelBar *pbar, enum BAR_SHAPE bar_shape) { // Set BAR_SHAPE g_return_if_fail(IS_LEVEL_BAR(pbar)); LevelBarPrivate* priv = pbar->priv; priv->bar_shape = bar_shape; // Redraw level_bar_real_update(pbar); } enum BAR_SHAPE level_bar_get_shape(LevelBar *pbar) { // Get BAR_SHAPE g_return_val_if_fail(IS_LEVEL_BAR(pbar), SHAPE_LEVELBAR); LevelBarPrivate* priv = pbar->priv; return priv->bar_shape; }