/* * Sweep, a sound wave editor. * * db_ruler, modified from hruler in GTK+ 1.2.x * by Conrad Parker 2002 for Sweep. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* * GTK - The GIMP Toolkit * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald */ /* * Modified by the GTK+ Team and others 1997-1999. See the AUTHORS * file for a list of people on the GTK+ Team. See the ChangeLog * files for a list of changes. These files are distributed with * GTK+ at ftp://ftp.gtk.org/pub/gtk/. */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include "db_ruler.h" #include #include "print.h" #define RULER_WIDTH 32 #define RULER_HEIGHT 14 #define MINIMUM_INCR 2 #define MAXIMUM_SUBDIVIDE 3 #define MAXIMUM_SCALES 21 #define ROUND(x) ((int) ((x) + 0.5)) static void db_ruler_class_init (DbRulerClass *klass); static void db_ruler_init (DbRuler *db_ruler); static void db_ruler_realize (GtkWidget * widget); static gint db_ruler_button_press (GtkWidget * widget, GdkEventButton * event); static gint db_ruler_button_release (GtkWidget * widget, GdkEventButton * event); static gint db_ruler_motion_notify (GtkWidget *widget, GdkEventMotion *event); static gint db_ruler_leave_notify (GtkWidget * widget, GdkEventCrossing * event); static gboolean db_ruler_scroll_event (GtkWidget * widget, GdkEventScroll *event); static void db_ruler_draw_ticks (GtkRuler *ruler); static void db_ruler_draw_pos (GtkRuler *ruler); static GtkWidgetClass * parent_class = NULL; enum { CHANGED_SIGNAL, LAST_SIGNAL }; static gint db_ruler_signals[LAST_SIGNAL] = { 0 }; GType db_ruler_get_type (void) { static GType db_ruler_type = 0; if (!db_ruler_type) { static const GTypeInfo db_ruler_info = { sizeof (DbRulerClass), NULL, /* base_init */ NULL, /* base_finalize */ (GClassInitFunc) db_ruler_class_init, NULL, /* class_finalize */ NULL, /* class_data */ sizeof (DbRuler), 0, /* n_preallocs */ (GInstanceInitFunc) db_ruler_init, }; db_ruler_type = g_type_register_static (GTK_TYPE_RULER, "DbRuler", &db_ruler_info, 0); } return db_ruler_type; } static void db_ruler_class_init (DbRulerClass *klass) { GtkObjectClass *object_class; GtkWidgetClass *widget_class; GtkRulerClass *ruler_class; object_class = (GtkObjectClass *) klass; widget_class = (GtkWidgetClass*) klass; ruler_class = (GtkRulerClass*) klass; widget_class->realize = db_ruler_realize; widget_class->button_press_event = db_ruler_button_press; widget_class->motion_notify_event = db_ruler_motion_notify; widget_class->button_release_event = db_ruler_button_release; widget_class->leave_notify_event = db_ruler_leave_notify; widget_class->scroll_event = db_ruler_scroll_event; ruler_class->draw_ticks = db_ruler_draw_ticks; ruler_class->draw_pos = db_ruler_draw_pos; parent_class = gtk_type_class (gtk_widget_get_type ()); db_ruler_signals[CHANGED_SIGNAL] = g_signal_new ("changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (DbRulerClass, changed), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); klass->changed = NULL; } static gfloat ruler_scale[MAXIMUM_SCALES] = { 0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 3, 6, 12, 15, 30, 60, 300, 600, 1800, 3600, 18000, 36000 }; static gint subdivide[MAXIMUM_SUBDIVIDE] = { 1, 5, 20 }; static void db_ruler_init (DbRuler *db_ruler) { GtkWidget *widget; widget = GTK_WIDGET (db_ruler); widget->requisition.width = widget->style->xthickness * 2 + RULER_WIDTH; widget->requisition.height = widget->style->ythickness * 2 + 1; DB_RULER(db_ruler)->dragging = FALSE; } static void db_ruler_realize (GtkWidget * widget) { GtkRuler * ruler; GdkWindowAttr attributes; gint attributes_mask; GdkVisual * visual; g_return_if_fail (widget != NULL); g_return_if_fail (GTK_IS_DB_RULER (widget)); GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED); ruler = GTK_RULER(widget); attributes.x = widget->allocation.x; attributes.y = widget->allocation.y; attributes.width = widget->allocation.width; attributes.height = widget->allocation.height; attributes.wclass = GDK_INPUT_OUTPUT; attributes.window_type = GDK_WINDOW_CHILD; attributes.event_mask = gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_SCROLL_MASK; attributes.visual = gtk_widget_get_visual (widget); attributes.colormap = gtk_widget_get_colormap (widget); attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP; widget->window = gdk_window_new (widget->parent->window, &attributes, attributes_mask); widget->style = gtk_style_attach (widget->style, widget->window); gtk_style_set_background(widget->style, widget->window, GTK_STATE_NORMAL); gdk_window_set_user_data(widget->window, widget); visual = gdk_window_get_visual (widget->window); if (ruler->backing_store != NULL) { gdk_pixmap_unref (ruler->backing_store); } ruler->backing_store = gdk_pixmap_new (widget->window, widget->allocation.width, widget->allocation.height, visual->depth); ruler->non_gr_exp_gc = gdk_gc_new (widget->window); gdk_gc_copy (ruler->non_gr_exp_gc, widget->style->fg_gc[GTK_STATE_NORMAL]); } GtkWidget* db_ruler_new (void) { return GTK_WIDGET (g_object_new (db_ruler_get_type (), NULL)); } static gint db_ruler_motion_notify (GtkWidget *widget, GdkEventMotion *event) { GtkRuler *ruler; gfloat delta; GdkModifierType state; gint y, ydelta; g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (GTK_IS_DB_RULER (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE); ruler = GTK_RULER (widget); if (event->is_hint) gdk_window_get_pointer (widget->window, NULL, &y, &state); else y = event->y; if (DB_RULER(widget)->dragging && (state & GDK_BUTTON1_MASK)) { ydelta = DB_RULER(widget)->y - y; delta = (((ruler->lower - ruler->upper) * ydelta) / widget->allocation.height); gtk_ruler_set_range (ruler, ruler->lower + delta, ruler->upper + delta, (ruler->upper - ruler->lower)/2.0, 2.0); g_signal_emit_by_name (ruler, "changed"); gtk_ruler_draw_ticks (ruler); } DB_RULER(widget)->y = y; ruler->position = ((ruler->lower - ruler->upper) * y) / widget->allocation.height - ruler->lower; /* Make sure the ruler has been allocated already */ if (ruler->backing_store != NULL) gtk_ruler_draw_pos (ruler); return FALSE; } static gint db_ruler_button_press (GtkWidget * widget, GdkEventButton * event) { int y; gdk_window_get_pointer (event->window, NULL, &y, NULL); switch (event->button) { case 1: DB_RULER(widget)->y = y; DB_RULER(widget)->dragging = TRUE; break; default: break; } return TRUE; } static gint db_ruler_button_release (GtkWidget * widget, GdkEventButton * event) { DB_RULER(widget)->dragging = FALSE; return TRUE; } static gboolean db_ruler_scroll_event (GtkWidget *widget, GdkEventScroll *event) { GtkRuler * ruler = GTK_RULER(widget); float delta; if (event->direction == GDK_SCROLL_UP) { /* mouse wheel scroll up */ delta = ruler->upper - ruler->lower; gtk_ruler_set_range (ruler, ruler->lower + delta/8, ruler->upper - delta/8, (ruler->upper - ruler->lower)/2.0, 2.0); g_signal_emit_by_name (ruler, "changed"); return TRUE; } else if (event->direction == GDK_SCROLL_DOWN) { /* mouse wheel scroll down */ delta = ruler->upper - ruler->lower; gtk_ruler_set_range (ruler, ruler->lower - delta/8, ruler->upper + delta/8, (ruler->upper - ruler->lower)/2.0, 2.0); g_signal_emit_by_name (ruler, "changed"); return TRUE; } return FALSE; /* redundant? */ } static gint db_ruler_leave_notify (GtkWidget * widget, GdkEventCrossing * event) { DB_RULER(widget)->dragging = FALSE; return TRUE; } static void db_ruler_draw_ticks (GtkRuler *ruler) { GtkWidget *widget; GdkGC *gc, *bg_gc; gint i; gint width, height; gint xthickness; gint ythickness; gint length, ideal_length; gfloat lower, upper; /* Upper and lower limits, in ruler units */ gfloat increment, abs_increment; /* Number of pixels per unit */ gint scale; /* Number of units per major unit */ gfloat subd_incr; gfloat start, end, cur; gchar unit_str[32]; gint digit_height; gint digit_offset; gint text_height; gint pos; PangoLayout *layout; PangoRectangle logical_rect, ink_rect; g_return_if_fail (ruler != NULL); g_return_if_fail (GTK_IS_DB_RULER (ruler)); if (!GTK_WIDGET_DRAWABLE (ruler)) return; widget = GTK_WIDGET (ruler); gc = widget->style->fg_gc[GTK_STATE_NORMAL]; bg_gc = widget->style->bg_gc[GTK_STATE_NORMAL]; xthickness = widget->style->xthickness; ythickness = widget->style->ythickness; width = widget->allocation.width - ( xthickness * 2) ; /* height = widget->allocation.height - ythickness * 2;*/ height = widget->allocation.height; layout = gtk_widget_create_pango_layout (widget, "012456789dBinf-"); pango_layout_get_extents (layout, &ink_rect, &logical_rect); digit_height = PANGO_PIXELS (ink_rect.height) + 2; digit_offset = ink_rect.y; gtk_paint_box (widget->style, ruler->backing_store, GTK_STATE_NORMAL, GTK_SHADOW_OUT, NULL, widget, "db_ruler", 0, 0, widget->allocation.width, widget->allocation.height); gdk_draw_line (ruler->backing_store, gc, width + xthickness, ythickness, width + xthickness, height - ythickness); upper = ruler->upper / ruler->metric->pixels_per_unit; lower = ruler->lower / ruler->metric->pixels_per_unit; // upper = ruler->upper; // lower = ruler->lower; if ((upper - lower) == 0) return; increment = (gfloat) height / (upper - lower); abs_increment = (gfloat) fabs((double)increment); // * strlen (unit_str) text_height = digit_height + 1; for (scale = 0; scale < MAXIMUM_SCALES; scale++) /* if (ruler->metric->ruler_scale[scale] * fabs(increment) > 2 * text_height) break;*/ if (ruler_scale[scale] * abs_increment > 2 * digit_height) break; if (scale == MAXIMUM_SCALES) scale = MAXIMUM_SCALES - 1; /* drawing starts here */ pango_layout_set_text (layout, "dB", 2); pango_layout_get_extents (layout, NULL, &logical_rect); gtk_paint_layout (widget->style, ruler->backing_store, GTK_WIDGET_STATE (widget), FALSE, NULL, widget, "vruler", 2, (digit_height/2), layout); length = 0; for (i = MAXIMUM_SUBDIVIDE - 1; i >= 0; i--) { subd_incr = (gfloat) ruler_scale[scale] / (gfloat) subdivide[i]; if (subd_incr * fabs(increment) <= MINIMUM_INCR) continue; /* Calculate the length of the tickmarks. Make sure that * this length increases for each set of ticks */ ideal_length = 8 / (i + 1) - 1; if (ideal_length > ++length) length = ideal_length; if (lower < upper) { start = floor (lower / subd_incr) * subd_incr; end = ceil (upper / subd_incr) * subd_incr; } else { start = floor (upper / subd_incr) * subd_incr; end = ceil (lower / subd_incr) * subd_incr; } for (cur = start; cur <= end; cur += subd_incr) { pos = height - ROUND ((cur - lower) * increment); gdk_draw_line (ruler->backing_store, gc, width + xthickness, pos, width - length + xthickness, pos); /* draw label */ if (i == 0 && cur < upper && cur > lower) { float a_cur = fabs(cur), db_cur; /* ensure inf. stays as 'inf.', not nearby large values */ if (a_cur < subd_incr/2) a_cur = 0.0; db_cur = 20 * log10 (a_cur); if (db_cur > -10.0) { snprintf (unit_str, sizeof (unit_str), "%1.1f", db_cur); } else { snprintf (unit_str, sizeof (unit_str), "%2.0f", db_cur); } pango_layout_set_text (layout, unit_str, -1); gtk_paint_layout (widget->style, ruler->backing_store, GTK_WIDGET_STATE (widget), FALSE, NULL, widget, "vruler", 2, pos - (digit_height), layout); } } } } static void db_ruler_draw_pos (GtkRuler *ruler) { GtkWidget *widget; GdkGC *gc; int i; gint x, y; gint width, height; gint bs_width, bs_height; gint xthickness; gint ythickness; gfloat increment; g_return_if_fail (ruler != NULL); g_return_if_fail (GTK_IS_DB_RULER (ruler)); if (GTK_WIDGET_DRAWABLE (ruler)) { widget = GTK_WIDGET (ruler); gc = widget->style->fg_gc[GTK_STATE_NORMAL]; xthickness = widget->style->xthickness; ythickness = widget->style->ythickness; width = widget->allocation.width - xthickness * 2; height = widget->allocation.height; bs_height = 7; bs_width = 4; if ((bs_width > 0) && (bs_height > 0)) { /* If a backing store exists, restore the ruler */ if (ruler->backing_store && ruler->non_gr_exp_gc) gdk_draw_drawable (ruler->widget.window, ruler->non_gr_exp_gc, ruler->backing_store, ruler->xsrc, ruler->ysrc, ruler->xsrc, ruler->ysrc, bs_width, bs_height); increment = (gfloat) height / (ruler->upper - ruler->lower); x = (width - bs_width) + xthickness - 2; y = DB_RULER(ruler)->y; for (i = 0; i < bs_width; i++) gdk_draw_line (widget->window, gc, x + i, y + i, x + i, y + bs_height - 1 - i); ruler->xsrc = x; ruler->ysrc = y; } } }