sane-project-frontends/libgtk/gtk4ruler.c

617 wiersze
16 KiB
C

#include <gtk/gtk.h>
#include <string.h>
#include <math.h>
#include <stdio.h>
#include "gtk4ruler.h"
#define ROUND(x) ((int) round(x))
#define GTK_PARAM_READWRITE G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB
#define DEFAULT_RULER_FONT_SCALE PANGO_SCALE_X_SMALL
#define MINIMUM_INCR 5
#define IMMEDIATE_REDRAW_THRESHOLD 20
enum {
PROP_0,
PROP_ORIENTATION,
PROP_UNIT,
PROP_LOWER,
PROP_UPPER,
PROP_POSITION,
PROP_MAX_SIZE
};
typedef struct _Gtk4RulerMetric Gtk4RulerMetric;
struct _Gtk4Ruler
{
GtkBox parent_instance;
GtkOrientation orientation;
Gtk4RulerMetricUnit unit;
gdouble lower;
gdouble upper;
gdouble position;
gdouble max_size;
GdkWindow *input_window;
cairo_surface_t *backing_store;
gboolean backing_store_valid;
GdkRectangle last_pos_rect;
guint pos_redraw_idle_id;
PangoLayout *layout;
gdouble font_scale;
GList *track_widgets;
};
typedef struct _Gtk4Ruler Gtk4Ruler;
struct _Gtk4RulerClass
{
GtkBoxClass parent_class;
};
typedef struct _Gtk4RulerClass Gtk4RulerClass;
static GType gtk4_ruler_get_type (void);
G_DEFINE_TYPE (Gtk4Ruler, gtk4_ruler, GTK_TYPE_BOX)
struct _Gtk4RulerMetric
{
gdouble ruler_scale[16];
gint subdivide[5];
};
Gtk4RulerMetric ruler_metric;
// Ruler metric for general use.
static const Gtk4RulerMetric ruler_metric_general = {
{ 1, 2, 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000, 25000, 50000, 100000 },
{ 1, 5, 10, 50, 100 }
};
// Ruler metric for inch scales.
static const Gtk4RulerMetric ruler_metric_inches = {
{ 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768 },
{ 1, 2, 4, 8, 16 }
};
#if 0
static const Gtk4RulerMetric ruler_metric_feet =
{
/* 3 feet = 1 yard; 6 feet = 1 fathom */
{ 1, 3, 6, 12, 36, 72, 100, 250, 500, 1000, 2500, 5000, 10000, 25000, 50000, 100000 },
/* 1 foot = 12 inches, so let's divide up to 12, */
{ 1, 3, 6, 12,
/* then divide the inch by 2. */
24 }
};
static const Gtk4RulerMetric ruler_metric_yards =
{
/* 1 fathom = 2 yards. Should we go back to base-10 digits? */
{ 1, 2, 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000, 25000, 50000, 100000 },
/* 1 yard = 3 feet = 36 inches. */
{ 1, 3, 6, 12, 36 }
};
#endif
static void
gtk4_ruler_snapshot (GtkWidget *widget,
GtkSnapshot *snapshot)
{
Gtk4Ruler *ruler = GTK4_RULER (widget);;
GtkStyleContext *context = gtk_widget_get_style_context (widget);
GtkAllocation allocation;
gtk_widget_get_allocation (widget, &allocation);
gtk_render_background (context, cr, 0, 0, allocation.width, allocation.height);
gtk_render_frame (context, cr, 0, 0, allocation.width, allocation.height);
gtk3_ruler_draw_ticks (ruler);
cairo_set_source_surface(cr, priv->backing_store, 0, 0);
cairo_paint(cr);
gtk3_ruler_draw_pos (ruler, cr);
gtk_snapshot_push_blur (snapshot, box->radius);
GTK_WIDGET_CLASS (gtk4_ruler_parent_class)->snapshot (widget, snapshot);
gtk_snapshot_pop (snapshot);
}
static void
gtk4_ruler_init (Gtk4Ruler *ruler) {
gtk_widget_set_has_window (GTK_WIDGET (ruler), FALSE);
ruler->orientation = GTK_ORIENTATION_HORIZONTAL;
ruler->unit = GTK4_RULER_METRIC_INCHES;
ruler->lower = 0;
ruler->upper = 0;
ruler->position = 0;
ruler->max_size = 0;
ruler->backing_store = NULL;
ruler->backing_store_valid = FALSE;
ruler->last_pos_rect.x = 0;
ruler->last_pos_rect.y = 0;
ruler->last_pos_rect.width = 0;
ruler->last_pos_rect.height = 0;
ruler->pos_redraw_idle_id = 0;
ruler->font_scale = DEFAULT_RULER_FONT_SCALE;
ruler->track_widgets = NULL;
}
static void
gtk4_ruler_class_init (Gtk4RulerClass *klass)
{
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
gtk_widget_class_set_css_name (widget_class, "ruler-widget");
object_class->dispose = gtk4_ruler_dispose;
object_class->set_property = gtk4_ruler_set_property;
object_class->get_property = gtk4_ruler_get_property;
widget_class->realize = gtk4_ruler_realize;
widget_class->unrealize = gtk4_ruler_unrealize;
widget_class->map = gtk4_ruler_map;
widget_class->unmap = gtk4_ruler_unmap;
widget_class->size_allocate = gtk4_ruler_size_allocate;
widget_class->get_preferred_width = gtk4_ruler_get_preferred_width;
widget_class->get_preferred_height = gtk4_ruler_get_preferred_height;
widget_class->style_updated = gtk4_ruler_style_updated;
widget_class->snapshot = gtk4_ruler_snapshot;
widget_class->motion_notify_event = gtk4_ruler_motion_notify;
g_object_class_install_property (object_class,
PROP_ORIENTATION,
g_param_spec_enum ("orientation",
("Orientation"),
("The orientation of the ruler"),
GTK_TYPE_ORIENTATION,
GTK_ORIENTATION_HORIZONTAL,
GTK_PARAM_READWRITE));
/* FIXME: Should probably use g_param_spec_enum */
g_object_class_install_property (object_class,
PROP_UNIT,
g_param_spec_string ("unit",
("Unit"),
("Unit of the ruler"),
"px",
GTK_PARAM_READWRITE));
g_object_class_install_property (object_class,
PROP_LOWER,
g_param_spec_double ("lower",
("Lower"),
("Lower limit of ruler"),
-G_MAXDOUBLE,
G_MAXDOUBLE,
0.0,
GTK_PARAM_READWRITE));
g_object_class_install_property (object_class,
PROP_UPPER,
g_param_spec_double ("upper",
("Upper"),
("Upper limit of ruler"),
-G_MAXDOUBLE,
G_MAXDOUBLE,
0.0,
GTK_PARAM_READWRITE));
g_object_class_install_property (object_class,
PROP_POSITION,
g_param_spec_double ("position",
("Position"),
("Position of mark on the ruler"),
-G_MAXDOUBLE,
G_MAXDOUBLE,
0.0,
GTK_PARAM_READWRITE));
g_object_class_install_property (object_class,
PROP_MAX_SIZE,
g_param_spec_double ("max-size",
("Max Size"),
("Maximum size of the ruler"),
-G_MAXDOUBLE,
G_MAXDOUBLE,
0.0,
GTK_PARAM_READWRITE));
gtk_widget_class_install_style_property (widget_class,
g_param_spec_double ("font-scale",
NULL, NULL,
0.0,
G_MAXDOUBLE,
DEFAULT_RULER_FONT_SCALE,
G_PARAM_READABLE));
}
static void
gtk4_ruler_dispose (GObject *object)
{
Gtk4Ruler *ruler = GTK4_RULER(object);
if (ruler == NULL || ruler->track_widgets == NULL || g_list_length (ruler->track_widgets) == 0) { return; }
while (ruler->track_widgets != NULL) {
GtkWidget *widget = ruler->track_widgets->data;
if (!GTK_IS_WIDGET (widget))break;
ruler->track_widgets = g_list_remove (ruler->track_widgets, widget);
gtk4_ruler_remove_track_widget(ruler, widget);
}
if (ruler->pos_redraw_idle_id) {
g_source_remove(ruler->pos_redraw_idle_id);
ruler->pos_redraw_idle_id = 0;
}
ruler->track_widgets = NULL;
G_OBJECT_CLASS (parent_class)->dispose(object);
}
/**
* gtk4_ruler_set_range:
* @ruler: the Gtk4Ruler
* @lower: the lower limit of the ruler
* @upper: the upper limit of the ruler
* @max_size: the maximum size of the ruler used when calculating the space to
* leave for the text
*
* This sets the range of the ruler.
*/
void
gtk4_ruler_set_range (Gtk4Ruler *ruler,
gdouble lower,
gdouble upper,
gdouble max_size)
{
g_return_if_fail (GTK4_IS_RULER (ruler));
g_object_freeze_notify (G_OBJECT (ruler));
if (ruler->lower != lower)
{
ruler->lower = lower;
g_object_notify (G_OBJECT (ruler), "lower");
}
if (ruler->upper != upper)
{
ruler->upper = upper;
g_object_notify (G_OBJECT (ruler), "upper");
}
if (ruler->max_size != max_size)
{
ruler->max_size = max_size;
g_object_notify (G_OBJECT (ruler), "max-size");
}
g_object_thaw_notify (G_OBJECT (ruler));
ruler->backing_store_valid = FALSE;
gtk_widget_queue_draw (GTK_WIDGET (ruler));
}
/**
* gtk4_ruler_get_range:
* @ruler: a #Gtk4Ruler
* @lower: (allow-none): location to store lower limit of the ruler, or %NULL
* @upper: (allow-none): location to store upper limit of the ruler, or %NULL
* @max_size: location to store the maximum size of the ruler used when calculating
* the space to leave for the text, or %NULL.
*
* Retrieves values indicating the range and current position of a #Gtk4Ruler.
* See gtk4_ruler_set_range().
**/
void
gtk4_ruler_get_range (Gtk4Ruler *ruler,
gdouble *lower,
gdouble *upper,
gdouble *max_size)
{
g_return_if_fail (GTK4_IS_RULER (ruler));
if (lower)
*lower = ruler->lower;
if (upper)
*upper = ruler->upper;
if (max_size)
*max_size = ruler->max_size;
}
static void
gtk4_ruler_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
Gtk4Ruler *ruler = GTK4_RULER (object);
switch (prop_id)
{
case PROP_ORIENTATION:
ruler->orientation = (GtkOrientation)g_value_get_enum (value);
gtk_widget_queue_resize (GTK_WIDGET (ruler));
break;
case PROP_UNIT:
gtk4_ruler_set_unit (ruler, (Gtk4RulerMetricUnit)g_value_get_int (value));
break;
case PROP_LOWER:
gtk4_ruler_set_range (ruler,
g_value_get_double (value),
ruler->upper,
ruler->max_size);
break;
case PROP_UPPER:
gtk4_ruler_set_range (ruler,
ruler->lower,
g_value_get_int (value),
ruler->max_size);
break;
case PROP_POSITION:
gtk4_ruler_set_position (ruler, g_value_get_double (value));
break;
case PROP_MAX_SIZE:
gtk4ruler_set_range (ruler,
ruler->lower,
ruler->upper,
g_value_get_double (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk4_ruler_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
Gtk4Ruler *ruler = GTK4_RULER (object);
switch (prop_id)
{
case PROP_ORIENTATION:
g_value_set_enum (value, ruler->orientation);
break;
case PROP_UNIT:
g_value_set_int (value, ruler->unit);
break;
case PROP_LOWER:
g_value_set_double (value, ruler->lower);
break;
case PROP_UPPER:
g_value_set_double (value, ruler->upper);
break;
case PROP_POSITION:
g_value_set_double (value, ruler->position);
break;
case PROP_MAX_SIZE:
g_value_set_double (value, ruler->max_size);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk4_ruler_realize (GtkWidget *widget)
{
Gtk4Ruler *ruler = GTK4_RULER (widget);
GtkAllocation allocation;
GdkWindowAttr attributes;
gint attributes_mask;
GTK_WIDGET_CLASS (gtk4_ruler_parent_class)->realize (widget);
gtk_widget_get_allocation (widget, &allocation);
attributes.window_type = GDK_WINDOW_CHILD;
attributes.x = allocation.x;
attributes.y = allocation.y;
attributes.width = allocation.width;
attributes.height = allocation.height;
attributes.wclass = GDK_INPUT_ONLY;
attributes.event_mask = (gtk_widget_get_events (widget) |
GDK_EXPOSURE_MASK |
GDK_POINTER_MOTION_MASK |
GDK_POINTER_MOTION_HINT_MASK);
attributes_mask = GDK_WA_X | GDK_WA_Y;
ruler->input_window = gdk_window_new (gtk_widget_get_parent_window (widget),
&attributes, attributes_mask);
gdk_window_set_user_data (ruler->input_window, ruler);
gtk4_ruler_make_pixmap (ruler);
}
static void
gtk4_ruler_unrealize(GtkWidget *widget)
{
Gtk4Ruler *ruler = GTK4_RULER (widget);
if (ruler->backing_store)
{
cairo_surface_destroy (ruler->backing_store);
ruler->backing_store = NULL;
}
ruler->backing_store_valid = FALSE;
if (ruler->layout)
{
g_object_unref (ruler->layout);
ruler->layout = NULL;
}
if (ruler->input_window)
{
gdk_window_destroy (ruler->input_window);
ruler->input_window = NULL;
}
GTK_WIDGET_CLASS (gtk4_ruler_parent_class)->unrealize (widget);
}
static void
gtk4_ruler_map (GtkWidget *widget)
{
Gtk4Ruler *ruler = GTK4_RULER (widget);
GTK_WIDGET_CLASS (gtk4_ruler_parent_class)->map (widget);
if (ruler->input_window)
gdk_window_show (ruler->input_window);
}
static void
gtk4_ruler_unmap (GtkWidget *widget)
{
Gtk4Ruler *ruler = GTK4_RULER (widget);
if (ruler->input_window)
gdk_window_hide (ruler->input_window);
GTK_WIDGET_CLASS (gtk4_ruler_parent_class)->unmap (widget);
}
static void
gtk4_ruler_size_allocate (GtkWidget *widget,
GtkAllocation *allocation)
{
Gtk4Ruler *ruler = GTK4_RULER(widget);
GtkAllocation widget_allocation;
gboolean resized;
gtk_widget_get_allocation (widget, &widget_allocation);
resized = (widget_allocation.width != allocation->width ||
widget_allocation.height != allocation->height);
GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation);
if (gtk_widget_get_realized (widget))
{
gdk_window_move_resize (ruler->input_window,
allocation->x, allocation->y,
allocation->width, allocation->height);
if (resized)
gtk4_ruler_make_pixmap (ruler);
}
}
static void
gtk4_ruler_size_request (GtkWidget *widget,
GtkRequisition *requisition)
{
Gtk4Ruler *ruler = GTK4_RULER (widget);
PangoLayout *layout;
PangoRectangle ink_rect;
gint size;
layout = gtk4_ruler_get_layout (widget, "0123456789");
pango_layout_get_pixel_extents (layout, &ink_rect, NULL);
size = 2 + ink_rect.height * 1.7;
GtkStyleContext *context = gtk_widget_get_style_context (widget);
GtkBorder border;
GtkStateFlags state_flags = gtk_style_context_get_state (context);
gtk_style_context_get_border (context, state_flags, &border);
requisition->width = border.left + border.right;
requisition->height = border.top + border.bottom;
if (ruler->orientation == GTK_ORIENTATION_HORIZONTAL)
{
requisition->width += 1;
requisition->height += size;
}
else
{
requisition->width += size;
requisition->height += 1;
}
}
static void
gtk4_ruler_style_updated (GtkWidget *widget)
{
Gtk4Ruler *ruler = GTK4_RULER (widget);
GTK_WIDGET_CLASS (gtk4_ruler_parent_class)->style_updated (widget);
gtk_widget_style_get (widget,
"font-scale", &ruler->font_scale,
NULL);
if (ruler->layout)
{
g_object_unref (ruler->layout);
ruler->layout = NULL;
}
}
static void
gtk4_ruler_get_preferred_width (GtkWidget *widget,
gint *minimum_width,
gint *natural_width)
{
GtkRequisition requisition;
gtk4_ruler_size_request (widget, &requisition);
*minimum_width = *natural_width = requisition.width;
}
static void
gtk4_ruler_get_preferred_height (GtkWidget *widget,
gint *minimum_height,
gint *natural_height)
{
GtkRequisition requisition;
gtk4_ruler_size_request(widget, &requisition);
*minimum_height = *natural_height = requisition.height;
}
GtkWidget *
gtk4_ruller_new () {
return g_object_new (gtk4_ruler_get_type (),
"orientation", GTK_ORIENTATION_VERTICAL,
"spacing", 32,
NULL);
}