Commit 26490a2d authored by Matthias Clasen's avatar Matthias Clasen

Bug 565656 – Add marks to scales


        Bug 565656 – Add marks to scales

        * gtk/gtkrange.[hc]: Add internal api to define 'stop values'
        that have a little resistance when dragging the slider over it.

        * gtk/gtk.symbols:
        * gtk/gtkscale.[hc] (gtk_scale_add_mark): New function to add
        a 'mark' to a scale, which will draws a tick, plus optionally
        some text, and makes the value a stop value.
        (gtk_scale_clear_values): Removes all marks.

        * tests/testscale.c: Test for marks on scales
        * tests/Makefile.am: Integrate it


svn path=/trunk/; revision=22149
parent b6b52376
2009-01-20 Matthias Clasen <mclasen@redhat.com>
Bug 565656 – Add marks to scales
* gtk/gtkrange.[hc]: Add internal api to define 'stop values'
that have a little resistance when dragging the slider over it.
* gtk/gtk.symbols:
* gtk/gtkscale.[hc] (gtk_scale_add_mark): New function to add
a 'mark' to a scale, which will draws a tick, plus optionally
some text, and makes the value a stop value.
(gtk_scale_clear_values): Removes all marks.
* tests/testscale.c: Test for marks on scales
* tests/Makefile.am: Integrate it
2009-01-19 Matthias Clasen <mclasen@redhat.com>
* gtk/gtkentry.c:
......
2009-01-20 Matthias Clasen <mclasen@redhat.com>
* gtk/gtk-sections.txt: Add new scale api
2009-01-19 Matthias Clasen <mclasen@redhat.com>
* gdk/tmpl/cursors.sgml: Document GDK_BLANK_CURSOR.
......
......@@ -3229,6 +3229,8 @@ gtk_scale_get_draw_value
gtk_scale_get_value_pos
gtk_scale_get_layout
gtk_scale_get_layout_offsets
gtk_scale_add_mark
gtk_scale_clear_marks
<SUBSECTION Standard>
GTK_SCALE
GTK_IS_SCALE
......
......@@ -3460,6 +3460,8 @@ gtk_scale_get_value_pos
gtk_scale_set_digits
gtk_scale_set_draw_value
gtk_scale_set_value_pos
gtk_scale_add_mark
gtk_scale_clear_marks
#endif
#endif
......
......@@ -117,6 +117,11 @@ struct _GtkRangeLayout
GQuark slider_detail_quark;
GQuark stepper_detail_quark;
gdouble *marks;
gint *mark_pos;
gint n_marks;
gboolean recalc_marks;
};
......@@ -175,6 +180,7 @@ static gboolean gtk_range_scroll (GtkRange *range,
static gboolean gtk_range_update_mouse_location (GtkRange *range);
static void gtk_range_calc_layout (GtkRange *range,
gdouble adjustment_value);
static void gtk_range_calc_marks (GtkRange *range);
static void gtk_range_get_props (GtkRange *range,
gint *slider_width,
gint *stepper_size,
......@@ -1216,6 +1222,13 @@ gtk_range_destroy (GtkObject *object)
range->adjustment = NULL;
}
if (range->layout->n_marks)
{
g_free (range->layout->marks);
g_free (range->layout->mark_pos);
range->layout->n_marks = 0;
}
GTK_OBJECT_CLASS (gtk_range_parent_class)->destroy (object);
}
......@@ -1255,6 +1268,8 @@ gtk_range_size_allocate (GtkWidget *widget,
widget->allocation = *allocation;
range->layout->recalc_marks = TRUE;
range->need_recalc = TRUE;
gtk_range_calc_layout (range, range->adjustment->value);
......@@ -1520,6 +1535,7 @@ gtk_range_expose (GtkWidget *widget,
expose_area.x -= widget->allocation.x;
expose_area.y -= widget->allocation.y;
gtk_range_calc_marks (range);
gtk_range_calc_layout (range, range->adjustment->value);
sensitive = GTK_WIDGET_IS_SENSITIVE (widget);
......@@ -2101,6 +2117,10 @@ update_slider_position (GtkRange *range,
gint c;
gdouble new_value;
gboolean handled;
gdouble next_value;
gdouble mark_value;
gdouble mark_delta;
gint i;
if (range->orientation == GTK_ORIENTATION_VERTICAL)
delta = mouse_y - range->slide_initial_coordinate;
......@@ -2110,7 +2130,23 @@ update_slider_position (GtkRange *range,
c = range->slide_initial_slider_position + delta;
new_value = coord_to_value (range, c);
next_value = coord_to_value (range, c + 1);
mark_delta = fabs (next_value - new_value);
for (i = 0; i < range->layout->n_marks; i++)
{
mark_value = range->layout->marks[i];
if (fabs (range->adjustment->value - mark_value) < 3 * mark_delta)
{
if (fabs (new_value - mark_value) < (range->slider_end - range->slider_start) * 0.5 * mark_delta)
{
new_value = mark_value;
break;
}
}
}
g_signal_emit (range, signals[CHANGE_VALUE], 0, GTK_SCROLL_JUMP, new_value,
&handled);
}
......@@ -2340,6 +2376,7 @@ gtk_range_adjustment_changed (GtkAdjustment *adjustment,
/* create a copy of the layout */
GtkRangeLayout layout = *range->layout;
range->layout->recalc_marks = TRUE;
range->need_recalc = TRUE;
gtk_range_calc_layout (range, range->adjustment->value);
......@@ -2408,6 +2445,26 @@ gtk_range_style_set (GtkWidget *widget,
GTK_WIDGET_CLASS (gtk_range_parent_class)->style_set (widget, previous_style);
}
static void
apply_marks (GtkRange *range,
gdouble oldval,
gdouble *newval)
{
gint i;
gdouble mark;
for (i = 0; i < range->layout->n_marks; i++)
{
mark = range->layout->marks[i];
if ((oldval < mark && mark < *newval) ||
(oldval > mark && mark > *newval))
{
*newval = mark;
return;
}
}
}
static void
step_back (GtkRange *range)
{
......@@ -2415,6 +2472,7 @@ step_back (GtkRange *range)
gboolean handled;
newval = range->adjustment->value - range->adjustment->step_increment;
apply_marks (range, range->adjustment->value, &newval);
g_signal_emit (range, signals[CHANGE_VALUE], 0,
GTK_SCROLL_STEP_BACKWARD, newval, &handled);
}
......@@ -2426,6 +2484,7 @@ step_forward (GtkRange *range)
gboolean handled;
newval = range->adjustment->value + range->adjustment->step_increment;
apply_marks (range, range->adjustment->value, &newval);
g_signal_emit (range, signals[CHANGE_VALUE], 0,
GTK_SCROLL_STEP_FORWARD, newval, &handled);
}
......@@ -2438,6 +2497,7 @@ page_back (GtkRange *range)
gboolean handled;
newval = range->adjustment->value - range->adjustment->page_increment;
apply_marks (range, range->adjustment->value, &newval);
g_signal_emit (range, signals[CHANGE_VALUE], 0,
GTK_SCROLL_PAGE_BACKWARD, newval, &handled);
}
......@@ -2449,6 +2509,7 @@ page_forward (GtkRange *range)
gboolean handled;
newval = range->adjustment->value + range->adjustment->page_increment;
apply_marks (range, range->adjustment->value, &newval);
g_signal_emit (range, signals[CHANGE_VALUE], 0,
GTK_SCROLL_PAGE_FORWARD, newval, &handled);
}
......@@ -3337,6 +3398,29 @@ get_area (GtkRange *range,
return NULL;
}
static void
gtk_range_calc_marks (GtkRange *range)
{
gint i;
if (!range->layout->recalc_marks)
return;
range->layout->recalc_marks = FALSE;
for (i = 0; i < range->layout->n_marks; i++)
{
range->need_recalc = TRUE;
gtk_range_calc_layout (range, range->layout->marks[i]);
if (range->orientation == GTK_ORIENTATION_HORIZONTAL)
range->layout->mark_pos[i] = range->layout->slider.x + range->layout->slider.width / 2;
else
range->layout->mark_pos[i] = range->layout->slider.y + range->layout->slider.height / 2;
}
range->need_recalc = TRUE;
}
static gboolean
gtk_range_real_change_value (GtkRange *range,
GtkScrollType scroll,
......@@ -3511,5 +3595,38 @@ gtk_range_remove_update_timer (GtkRange *range)
}
}
void
_gtk_range_set_stop_values (GtkRange *range,
gdouble *values,
gint n_values)
{
gint i;
g_free (range->layout->marks);
range->layout->marks = g_new (gdouble, n_values);
g_free (range->layout->mark_pos);
range->layout->mark_pos = g_new (gint, n_values);
range->layout->n_marks = n_values;
for (i = 0; i < n_values; i++)
range->layout->marks[i] = values[i];
range->layout->recalc_marks = TRUE;
}
gint
_gtk_range_get_stop_positions (GtkRange *range,
gint **values)
{
gtk_range_calc_marks (range);
if (values)
*values = g_memdup (range->layout->mark_pos, range->layout->n_marks * sizeof (gint));
return range->layout->n_marks;
}
#define __GTK_RANGE_C__
#include "gtkaliasdef.c"
......@@ -178,6 +178,12 @@ gdouble gtk_range_get_fill_level (GtkRange *range
gdouble _gtk_range_get_wheel_delta (GtkRange *range,
GdkScrollDirection direction);
void _gtk_range_set_stop_values (GtkRange *range,
gdouble *values,
gint n_values);
gint _gtk_range_get_stop_positions (GtkRange *range,
gint **values);
G_END_DECLS
......
......@@ -32,6 +32,8 @@
#include "gdk/gdkkeysyms.h"
#include "gtkscale.h"
#include "gtkiconfactory.h"
#include "gtkicontheme.h"
#include "gtkmarshalers.h"
#include "gtkbindings.h"
#include "gtkprivate.h"
......@@ -49,9 +51,19 @@
typedef struct _GtkScalePrivate GtkScalePrivate;
typedef struct _GtkScaleMark GtkScaleMark;
struct _GtkScaleMark
{
gdouble value;
const gchar *markup;
GtkPositionType position;
};
struct _GtkScalePrivate
{
PangoLayout *layout;
GSList *marks;
};
enum {
......@@ -76,10 +88,20 @@ static void gtk_scale_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec);
static void gtk_scale_size_request (GtkWidget *widget,
GtkRequisition *requisition);
static void gtk_scale_style_set (GtkWidget *widget,
GtkStyle *previous);
static void gtk_scale_get_range_border (GtkRange *range,
GtkBorder *border);
static void gtk_scale_get_mark_label_size (GtkScale *scale,
GtkPositionType position,
gint *count1,
gint *width1,
gint *height1,
gint *count2,
gint *width2,
gint *height2);
static void gtk_scale_finalize (GObject *object);
static void gtk_scale_screen_changed (GtkWidget *widget,
GdkScreen *old_screen);
......@@ -132,6 +154,7 @@ gtk_scale_class_init (GtkScaleClass *class)
widget_class->style_set = gtk_scale_style_set;
widget_class->screen_changed = gtk_scale_screen_changed;
widget_class->expose_event = gtk_scale_expose;
widget_class->size_request = gtk_scale_size_request;
range_class->slider_detail = "Xscale";
range_class->get_range_border = gtk_scale_get_range_border;
......@@ -647,12 +670,14 @@ static void
gtk_scale_get_range_border (GtkRange *range,
GtkBorder *border)
{
GtkScalePrivate *priv;
GtkWidget *widget;
GtkScale *scale;
gint w, h;
widget = GTK_WIDGET (range);
scale = GTK_SCALE (range);
priv = GTK_SCALE_GET_PRIVATE (scale);
_gtk_scale_get_value_size (scale, &w, &h);
......@@ -682,6 +707,36 @@ gtk_scale_get_range_border (GtkRange *range,
break;
}
}
if (priv->marks)
{
gint slider_width;
gint value_spacing;
gint n1, w1, h1, n2, w2, h2;
gtk_widget_style_get (widget,
"slider-width", &slider_width,
"value-spacing", &value_spacing,
NULL);
if (GTK_RANGE (scale)->orientation == GTK_ORIENTATION_HORIZONTAL)
{
gtk_scale_get_mark_label_size (scale, GTK_POS_TOP, &n1, &w1, &h1, &n2, &w2, &h2);
if (n1 > 0)
border->top += h1 + value_spacing + slider_width / 2;
if (n2 > 0)
border->bottom += h2 + value_spacing + slider_width / 2;
}
else
{
gtk_scale_get_mark_label_size (scale, GTK_POS_LEFT, &n1, &w1, &h1, &n2, &w2, &h2);
if (n1 > 0)
border->left += w1 + value_spacing + slider_width / 2;
if (n2 > 0)
border->right += w2 + value_spacing + slider_width / 2;
}
}
}
/* FIXME this could actually be static at the moment. */
......@@ -738,6 +793,63 @@ _gtk_scale_get_value_size (GtkScale *scale,
}
static void
gtk_scale_get_mark_label_size (GtkScale *scale,
GtkPositionType position,
gint *count1,
gint *width1,
gint *height1,
gint *count2,
gint *width2,
gint *height2)
{
GtkScalePrivate *priv = GTK_SCALE_GET_PRIVATE (scale);
PangoLayout *layout;
PangoRectangle logical_rect;
GSList *m;
gint w, h;
*count1 = *count2 = 0;
*width1 = *width2 = 0;
*height1 = *height2 = 0;
layout = gtk_widget_create_pango_layout (GTK_WIDGET (scale), NULL);
for (m = priv->marks; m; m = m->next)
{
GtkScaleMark *mark = m->data;
if (mark->markup)
{
pango_layout_set_markup (layout, mark->markup, -1);
pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
w = logical_rect.width;
h = logical_rect.height;
}
else
{
w = 0;
h = 0;
}
if (mark->position == position)
{
(*count1)++;
*width1 = MAX (*width1, w);
*height1 = MAX (*height1, h);
}
else
{
(*count2)++;
*width2 = MAX (*width2, w);
*height2 = MAX (*height2, h);
}
}
g_object_unref (layout);
}
static void
gtk_scale_style_set (GtkWidget *widget,
GtkStyle *previous)
......@@ -765,31 +877,160 @@ gtk_scale_screen_changed (GtkWidget *widget,
_gtk_scale_clear_layout (GTK_SCALE (widget));
}
static void
gtk_scale_size_request (GtkWidget *widget,
GtkRequisition *requisition)
{
GtkRange *range = GTK_RANGE (widget);
gint n1, w1, h1, n2, w2, h2;
gint slider_length;
GTK_WIDGET_CLASS (gtk_scale_parent_class)->size_request (widget, requisition);
gtk_widget_style_get (widget, "slider-length", &slider_length, NULL);
if (range->orientation == GTK_ORIENTATION_HORIZONTAL)
{
gtk_scale_get_mark_label_size (GTK_SCALE (widget), GTK_POS_TOP, &n1, &w1, &h1, &n2, &w2, &h2);
w1 = (n1 - 1) * w1 + MAX (w1, slider_length);
w2 = (n2 - 1) * w2 + MAX (w2, slider_length);
requisition->width = MAX (requisition->width, MAX (w1, w2));
}
else
{
gtk_scale_get_mark_label_size (GTK_SCALE (widget), GTK_POS_LEFT, &n1, &w1, &h1, &n2, &w2, &h2);
h1 = (n1 - 1) * h1 + MAX (h1, slider_length);
h2 = (n2 - 1) * h1 + MAX (h2, slider_length);
requisition->height = MAX (requisition->height, MAX (h1, h2));
}
}
static gboolean
gtk_scale_expose (GtkWidget *widget,
GdkEventExpose *event)
{
GtkScale *scale = GTK_SCALE (widget);
GtkScalePrivate *priv = GTK_SCALE_GET_PRIVATE (scale);
GtkRange *range = GTK_RANGE (scale);
GtkStateType state_type;
gint n_marks;
gint *marks;
gint focus_padding;
gint slider_width;
gint value_spacing;
gtk_widget_style_get (widget,
"focus-padding", &focus_padding,
"slider-width", &slider_width,
"value-spacing", &value_spacing,
NULL);
/* We need to chain up _first_ so the various geometry members of
* GtkRange struct are updated.
*/
GTK_WIDGET_CLASS (gtk_scale_parent_class)->expose_event (widget, event);
state_type = GTK_STATE_NORMAL;
if (!GTK_WIDGET_IS_SENSITIVE (widget))
state_type = GTK_STATE_INSENSITIVE;
if (priv->marks)
{
gint i;
gint x1, x2, x3, y1, y2, y3;
PangoLayout *layout;
PangoRectangle logical_rect;
GSList *m;
n_marks = _gtk_range_get_stop_positions (range, &marks);
layout = gtk_widget_create_pango_layout (widget, NULL);
for (m = priv->marks, i = 0; m; m = m->next, i++)
{
GtkScaleMark *mark = m->data;
if (range->orientation == GTK_ORIENTATION_HORIZONTAL)
{
x1 = widget->allocation.x + marks[i];
if (mark->position == GTK_POS_TOP)
{
y1 = widget->allocation.y + range->range_rect.y;
y2 = y1 - slider_width / 2;
}
else
{
y1 = widget->allocation.y + range->range_rect.y + range->range_rect.height;
y2 = y1 + slider_width / 2;
}
gtk_paint_vline (widget->style, widget->window, state_type,
NULL, widget, "scale-mark", y1, y2, x1);
if (mark->markup)
{
pango_layout_set_markup (layout, mark->markup, -1);
pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
x3 = x1 - logical_rect.width / 2;
if (mark->position == GTK_POS_TOP)
y3 = y2 - value_spacing - logical_rect.height;
else
y3 = y2 + value_spacing;
gtk_paint_layout (widget->style, widget->window, state_type,
FALSE, NULL, widget, "scale-mark",
x3, y3, layout);
}
}
else
{
if (mark->position == GTK_POS_LEFT)
{
x1 = widget->allocation.x + range->range_rect.x;
x2 = widget->allocation.x + range->range_rect.x - slider_width / 2;
}
else
{
x1 = widget->allocation.x + range->range_rect.x + range->range_rect.width;
x2 = widget->allocation.x + range->range_rect.x + range->range_rect.width + slider_width / 2;
}
y1 = widget->allocation.y + marks[i];
gtk_paint_hline (widget->style, widget->window, state_type,
NULL, widget, "range-mark", x1, x2, y1);
if (mark->markup)
{
pango_layout_set_markup (layout, mark->markup, -1);
pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
if (mark->position == GTK_POS_LEFT)
x3 = x2 - value_spacing - logical_rect.width;
else
x3 = x2 + value_spacing;
y3 = y1 - logical_rect.height / 2;
gtk_paint_layout (widget->style, widget->window, state_type,
FALSE, NULL, widget, "scale-mark",
x3, y3, layout);
}
}
}
g_object_unref (layout);
g_free (marks);
}
if (scale->draw_value)
{
GtkRange *range = GTK_RANGE (scale);
PangoLayout *layout;
gint x, y;
GtkStateType state_type;
layout = gtk_scale_get_layout (scale);
gtk_scale_get_layout_offsets (scale, &x, &y);
state_type = GTK_STATE_NORMAL;
if (!GTK_WIDGET_IS_SENSITIVE (scale))
state_type = GTK_STATE_INSENSITIVE;
gtk_paint_layout (widget->style,
widget->window,
state_type,
......@@ -932,6 +1173,7 @@ gtk_scale_finalize (GObject *object)
GtkScale *scale = GTK_SCALE (object);
_gtk_scale_clear_layout (scale);
gtk_scale_clear_marks (scale);
G_OBJECT_CLASS (gtk_scale_parent_class)->finalize (object);
}
......@@ -1024,5 +1266,91 @@ _gtk_scale_clear_layout (GtkScale *scale)
}
}
static void
gtk_scale_mark_free (GtkScaleMark *mark)
{
g_free (mark->markup);
g_free (mark);
}
/**
* gtk_scale_clear_marks:
* @scale: a #GtkScale
*
* Removes any marks that have been added with gtk_scale_add_mark().
*
* Since: 2.16
*/
void
gtk_scale_clear_marks (GtkScale *scale)
{
GtkScalePrivate *priv = GTK_SCALE_GET_PRIVATE (scale);
g_return_if_fail (GTK_IS_SCALE (scale));
g_slist_foreach (priv->marks, (GFunc)gtk_scale_mark_free, NULL);
g_slist_free (priv->marks);
priv->marks = NULL;
_gtk_range_set_stop_values (GTK_RANGE (scale), NULL, 0);
}
/**
* gtk_scale_add_mark:
* @scale: a #GtkScale
* @value: the value at which the mark is placed, must be between
* the lower and upper limits of the scales' adjustment
* @position: where to draw the mark. For a horizontal scale, #GTK_POS_TOP
* is drawn above the scale, anything else below. For a vertical scale,
* #GTK_POS_LEFT is drawn to the left of the scale, anything else to the
* right.
* @markup: Text to be shown at the mark, using <link linkend="PangoMarkupFormat">Pango markup</link>, or %NULL
*
*
* Adds a mark at @value.
*
* A mark is indicated visually by drawing a tick mark next to the scale,
* and GTK+ makes it easy for the user to position the scale exactly at the
* marks value.
*
* If @markup is not %NULL, text is shown next to the tick mark.
*
* To remove marks from a scale, use gtk_scale_clear_marks().
*
* Since: 2.16
*/
void
gtk_scale_add_mark (GtkScale *scale,
gdouble value,
GtkPositionType position,
const gchar *markup)
{
GtkScalePrivate *priv = GTK_SCALE_GET_PRIVATE (scale);
GtkScaleMark *mark;
GSList *m;
gdouble *values;
gint n, i;
mark = g_new (GtkScaleMark, 1);
mark->value = value;
mark->markup = g_strdup (markup);
mark->position = position;
priv->marks = g_slist_prepend (priv->marks, mark);
n = g_slist_length (priv->marks);
values = g_new (gdouble, n);
for (m = priv->marks, i = 0; m; m = m->next, i++)
{
mark = m->data;
values[i] = mark->value;
}
_gtk_range_set_stop_values (GTK_RANGE (scale), values, n);
g_free (values);
}
#define __GTK_SCALE_C__
#include "gtkaliasdef.c"
......@@ -71,9 +71,9 @@ struct _GtkScaleClass
gint *y);
/* Padding for future expansion */
void (*_gtk_reserved1) (void);
void (*_gtk_reserved2) (void);
void (*_gtk_reserved3) (void);
void (*_gtk_reserved4) (void);
};
GType gtk_scale_get_type (void) G_GNUC_CONST;
......@@ -92,6 +92,12 @@ void gtk_scale_get_layout_offsets (GtkScale *scale,
gint *x,
gint *y);
void gtk_scale_add_mark (GtkScale *scale,
gdouble value,
GtkPositionType position,
const gchar *markup);
void gtk_scale_clear_marks (GtkScale *scale);
/* internal API */
void _gtk_scale_clear_layout (GtkScale *scale);
void _gtk_scale_get_value_size (GtkScale *scale,
......
......@@ -60,6 +60,7 @@ noinst_PROGRAMS = $(TEST_PROGS) \
testrecentchooser \