kopia lustrzana https://gitlab.com/sane-project/frontends
617 wiersze
16 KiB
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);
|
|
}
|