/*================================================================== * samview.c - sample view widget * * Swami * Copyright (C) 1999-2003 Josh Green * * 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 or point your web browser to http://www.gnu.org. * * To contact the author of this program: * Email: Josh Green * Swami homepage: http://swami.sourceforge.net *==================================================================*/ #include #include #include #include #include #include "samview.h" #define SAMVIEW_DEFAULT_SIZEX 100 #define SAMVIEW_DEFAULT_SIZEY 40 #define ZOOM_PRIORITY -1 /* priority of glib timeout (g_timeout_add_full) */ #define ZOOM_SAFEZONE 2 /* # of pixels no-zoom on each side of zoom point */ /* The following constants define the zoom curve, which consists of two slopes. One in the time domain (amount of time between each zoom update) and the other is the amount of zoom per time interval, each slope is calculated using the # of pixels from the safe zone update_time_interval_in_ms = ZOOM_TIME_SLOPE * x + ZOOM_TIME_OFS zoom_amount_per_time_interval = ZOOM_AMT_SLOPE * x + ZOOM_AMT_OFS where x is the # of pixels from the safe zone (i.e. further = faster updates and more zoom per update, resulting in faster zoom) */ #define ZOOM_TIME_OFS 160 /* longest zoom time, 1st pixel out of safezone */ #define ZOOM_TIME_SLOPE -20.0 /* # of ms to add to OFS per pixel */ #define ZOOM_TIME_MIN 40 /* minimum timout in milliseconds */ #define ZOOM_AMT_OFS 1.0 /* initial amount of zoom per update = 1X */ #define ZOOM_AMT_SLOPE -0.001 /* amt added to zoom amt per pixel */ #define ZOOM_AMT_MIN 0.05 /* max zoom amount 1/AMT (multiplier) */ /* scrolling during a selection uses these defines. The amount of pixels to scroll in view is kept in a variable, this variable is added to by the increase equation, which relies on the rate of movement of the mouse in the y axis. The decrease equation is related to time only, and is a steady linear decrease which works against the increase function. D = vertical distance of mouse cursor since last event T = time in milliseconds since last event scroll amount increase in pixels = SCROLL_SLOPE * D / T + SCROLL_OFS */ #define SCROLL_OFS 1.0 /* scroll amount increase offset */ #define SCROLL_SLOPE 4.0 /* scroll amount increase multiplier */ #define SCROLL_MAX 400.0 /* max scroll amount (in pixels) */ /* scroll amount decrease in pixels = SCROLL_DSLOPE * T + SCROLL_DOFS */ #define SCROLL_DOFS 0.0 /* scroll amount decrease offset */ #define SCROLL_DSLOPE (-30.0/200.0) /* scroll amount decrease devisor */ #define SCROLL_PRIORITY -1 /* priority of scroll timeout (g_timeout_add_full) */ #define SCROLL_TINTERVAL 120 /* timer interval in milliseconds */ /* converts x pixel position within view to sample position or -1 if outside sample */ #define VIEW_X2SAMPOS(sampos, x, sv) G_STMT_START { \ sampos = (int)((float)x / (((GtkWidget *)sv)->allocation.width - 1) \ * (sv->size - 1)) + sv->start; \ } G_STMT_END /* converts a sample position to view xpos or -1 if not in view */ #define VIEW_SAM2XPOS(x, sampos, sv) G_STMT_START { \ x = (int)((float)(sampos - sv->start) / (sv->size - 1) \ * (((GtkWidget *)sv)->allocation.width - 1)); \ } G_STMT_END /* compares two integers and swaps them if the second is less than the first */ #define CMPSWAP(f, s) G_STMT_START { \ gint _temp; \ if (s < f) { \ _temp = f; \ f = s; \ s = _temp; \ } \ } G_STMT_END /* Forward declarations */ static void samview_class_init (SamViewClass * klass); static void samview_init (SamView * samview); static void samview_destroy (GtkObject * object); static void samview_size_request (GtkWidget * widget, GtkRequisition * requisition); static void samview_size_allocate (GtkWidget * widget, GtkAllocation * allocation); static void samview_realize (GtkWidget * widget); static gint samview_expose (GtkWidget * widget, GdkEventExpose * event); static void samview_adjustment_changed (GtkAdjustment * adj, SamView * samview); static void samview_adjustment_value_changed (GtkAdjustment * adj, SamView * samview); static void samview_redraw (SamView * samview); static void samview_draw (GtkWidget * widget); static gint samview_button_press (GtkWidget * widget, GdkEventButton * event); static gint samview_button_release (GtkWidget * widget, GdkEventButton * event); static gint samview_motion_notify (GtkWidget * widget, GdkEventMotion * event); static gboolean samview_scroll_timeout (gpointer data); static gboolean samview_zoom_timeout (gpointer data); /* data */ static GtkWidgetClass *parent_class = NULL; enum { VIEW_CHANGE, SELECTION_CHANGE, DUMMY_SIGNAL }; static gint samview_signals[DUMMY_SIGNAL] = { 0 }; /* functions */ guint samview_get_type (void) { static guint samview_type = 0; if (!samview_type) { GtkTypeInfo samview_info = { "SamView", sizeof (SamView), sizeof (SamViewClass), (GtkClassInitFunc) samview_class_init, (GtkObjectInitFunc) samview_init, (GtkArgSetFunc) NULL, (GtkArgGetFunc) NULL, }; samview_type = gtk_type_unique (gtk_widget_get_type (), &samview_info); } return samview_type; } static void samview_class_init (SamViewClass * klass) { GtkObjectClass *object_class; GtkWidgetClass *widget_class; object_class = (GtkObjectClass *) klass; widget_class = (GtkWidgetClass *) klass; parent_class = gtk_type_class (gtk_widget_get_type ()); samview_signals[VIEW_CHANGE] = gtk_signal_new ("view_change", GTK_RUN_FIRST, object_class->type, GTK_SIGNAL_OFFSET (SamViewClass, view_change), gtk_marshal_NONE__NONE, GTK_TYPE_NONE, 0); samview_signals[SELECTION_CHANGE] = gtk_signal_new ("selection_change", GTK_RUN_FIRST, object_class->type, GTK_SIGNAL_OFFSET (SamViewClass, selection_change), gtk_marshal_NONE__NONE, GTK_TYPE_NONE, 0); gtk_object_class_add_signals (object_class, samview_signals, DUMMY_SIGNAL); klass->view_change = NULL; klass->selection_change = NULL; object_class->destroy = samview_destroy; widget_class->size_request = samview_size_request; widget_class->size_allocate = samview_size_allocate; widget_class->realize = samview_realize; widget_class->expose_event = samview_expose; widget_class->button_press_event = samview_button_press; widget_class->button_release_event = samview_button_release; widget_class->motion_notify_event = samview_motion_notify; } static void samview_init (SamView * samview) { samview->samdata = NULL; samview->length = 0; samview->start = 0; samview->size = 0; samview->select_start = -1; samview->select_actvbegin = -1; samview->select_scroll = 0; samview->markers = NULL; samview->sel_marker = NULL; samview->adj = NULL; samview->zoom_btn = 0; samview->samview_pm = NULL; samview->freeze_count = 0; samview->bg_gc = NULL; samview->sam_gc = NULL; samview->cline_gc = NULL; samview->realpos_gc = NULL; samview->selmark_gc = NULL; samview->select_gc = NULL; samview->bg_clr.red = 0; samview->bg_clr.green = 0; samview->bg_clr.blue = 0; samview->sam_clr.red = 0; samview->sam_clr.green = 65535; samview->sam_clr.blue = 65535; samview->cline_clr.red = 65535; samview->cline_clr.green = 65535; samview->cline_clr.blue = 65535; samview->realpos_clr.red = 32768; samview->realpos_clr.green = 32768; samview->realpos_clr.blue = 32768; samview->selmark_clr.red = 65535; samview->selmark_clr.green = 65535; samview->selmark_clr.blue = 65535; samview->select_clr.red = 65535; samview->select_clr.green = 65535; samview->select_clr.blue = 65535; } GtkWidget * samview_new (void) { SamView *samview; samview = gtk_type_new (samview_get_type ()); samview->adj = (GtkAdjustment *) gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0); gtk_object_ref (GTK_OBJECT (samview->adj)); gtk_signal_connect (GTK_OBJECT (samview->adj), "changed", (GtkSignalFunc) samview_adjustment_changed, samview); gtk_signal_connect (GTK_OBJECT (samview->adj), "value_changed", (GtkSignalFunc) samview_adjustment_value_changed, samview); return GTK_WIDGET (samview); } void samview_freeze (SamView * samview) { g_return_if_fail (samview != NULL); g_return_if_fail (IS_SAMVIEW (samview)); samview->freeze_count++; } void samview_thaw (SamView * samview) { g_return_if_fail (samview != NULL); g_return_if_fail (IS_SAMVIEW (samview)); g_return_if_fail (samview->freeze_count > 0); samview->freeze_count--; } void samview_set_data (SamView * samview, gint16 * data, gint length) { gint w; g_return_if_fail (samview != NULL); g_return_if_fail (IS_SAMVIEW (samview)); samview->samdata = data; samview->length = length; samview->select_start = -1; samview->select_end = -1; w = GTK_WIDGET (samview)->allocation.width; if (length > w) w = length; samview->adj->lower = 0.0; samview->adj->upper = (float) (w); samview->adj->value = 0.0; samview->adj->step_increment = (float) w / 30.0; samview->adj->page_increment = (float) w / 10.0; samview->adj->page_size = (float) w; gtk_adjustment_changed (samview->adj); gtk_adjustment_value_changed (samview->adj); gtk_signal_emit (GTK_OBJECT (samview), samview_signals[VIEW_CHANGE]); } void samview_set_view (SamView * samview, gint start, gint size) { g_return_if_fail (samview != NULL); g_return_if_fail (IS_SAMVIEW (samview)); samview->adj->value = (float) start; samview->adj->step_increment = (float) size / 30.0; samview->adj->page_increment = (float) size / 10.0; samview->adj->page_size = (float) size; gtk_adjustment_changed (samview->adj); gtk_adjustment_value_changed (samview->adj); gtk_signal_emit (GTK_OBJECT (samview), samview_signals[VIEW_CHANGE]); } void samview_set_position (SamView * samview, gint start) { g_return_if_fail (samview != NULL); g_return_if_fail (IS_SAMVIEW (samview)); samview->adj->value = (float) start; gtk_adjustment_value_changed (samview->adj); gtk_signal_emit (GTK_OBJECT (samview), samview_signals[VIEW_CHANGE]); } /* calculates the sample offset given an x pixel position */ gint samview_calc_sample_pos (SamView * samview, gint xpos) { gint pos; g_return_val_if_fail (samview != NULL, -1); g_return_val_if_fail (IS_SAMVIEW (samview), -1); VIEW_X2SAMPOS (pos, xpos, samview); return (CLAMP (pos, 0, samview->length - 1)); } gint samview_calc_xpos (SamView * samview, gint pos) { gint xpos; g_return_val_if_fail (samview != NULL, -1); g_return_val_if_fail (IS_SAMVIEW (samview), -1); if (samview->size <= 1 || pos < 0) return (-1); VIEW_SAM2XPOS (xpos, pos, samview); if (xpos < 0 || xpos > ((GtkWidget *) samview)->allocation.width - 1) return (-1); return (xpos); } void samview_new_marker (SamView * samview, GdkColor * color) { SamViewMark *marker; g_return_if_fail (samview != NULL); g_return_if_fail (IS_SAMVIEW (samview)); marker = g_new (SamViewMark, 1); marker->pos = -1; marker->xpos = -1; memcpy (&marker->color, color, sizeof (GdkColor)); marker->gc = NULL; samview->markers = g_slist_append (samview->markers, marker); } void samview_set_marker (SamView * samview, guint marknum, gint loc) { SamViewMark *marker; g_return_if_fail (samview != NULL); g_return_if_fail (IS_SAMVIEW (samview)); marker = g_slist_nth_data (samview->markers, marknum); if (!marker) return; marker->pos = loc; samview_redraw (samview); } SamViewMark * samview_get_nth_mark (SamView * samview, guint marknum) { g_return_val_if_fail (samview != NULL, NULL); g_return_val_if_fail (IS_SAMVIEW (samview), NULL); return (g_slist_nth_data (samview->markers, marknum)); } gint samview_get_mark_index (SamView * samview, SamViewMark * mark) { g_return_val_if_fail (samview != NULL, 0); g_return_val_if_fail (IS_SAMVIEW (samview), 0); return (g_slist_index (samview->markers, mark)); } void samview_select_marker (SamView * samview, guint marknum) { SamViewMark *marker; g_return_if_fail (samview != NULL); g_return_if_fail (IS_SAMVIEW (samview)); marker = g_slist_nth_data (samview->markers, marknum); if (!marker) return; samview->sel_marker = marker; samview_redraw (samview); } void samview_unselect_marker (SamView * samview) { g_return_if_fail (samview != NULL); g_return_if_fail (IS_SAMVIEW (samview)); samview->sel_marker = NULL; samview_redraw (samview); } /* sets the selected marker to the given x position in pixels */ gint samview_set_selected_marker_xpos (SamView * samview, gint * xpos) { gint vheight, vwidth; gint pos; g_return_val_if_fail (samview != NULL, -1); g_return_val_if_fail (IS_SAMVIEW (samview), -1); /* make sure there is a marker selected */ if (!samview->sel_marker) return (-1); vheight = GTK_WIDGET (samview)->allocation.height - 1; vwidth = GTK_WIDGET (samview)->allocation.width - 1; /* calculate corresponding sample position and clamp to limits */ VIEW_X2SAMPOS (pos, *xpos, samview); samview->sel_marker->pos = CLAMP (pos, 0, samview->length - 1); /* if sample pos out of range, then recalculate correct xpos */ if (samview->sel_marker->pos != pos) { pos = samview->sel_marker->pos; VIEW_SAM2XPOS (*xpos, pos, samview); } /* clear old marker (GC function is XOR) */ gdk_draw_line (samview->samview_pm, samview->selmark_gc, samview->sel_marker->xpos, 0, samview->sel_marker->xpos, vheight); gdk_draw_pixmap (GTK_WIDGET (samview)->window, GTK_WIDGET (samview)->style->fg_gc[GTK_STATE_SELECTED], samview->samview_pm, samview->sel_marker->xpos, 0, samview->sel_marker->xpos, 0, 1, vheight); samview->sel_marker->xpos = *xpos; /* draw marker */ gdk_draw_line (samview->samview_pm, samview->selmark_gc, *xpos, 0, *xpos, vheight); gdk_draw_pixmap (GTK_WIDGET (samview)->window, GTK_WIDGET (samview)->style->fg_gc[GTK_STATE_SELECTED], samview->samview_pm, *xpos, 0, *xpos, 0, 1, vheight); return (pos); } void samview_set_selection (SamView * samview, gint start, gint end) { g_return_if_fail (samview != NULL); g_return_if_fail (IS_SAMVIEW (samview)); if (start <= end) { samview->select_start = start; samview->select_end = end; } else { samview->select_start = end; samview->select_end = start; } samview_redraw (samview); gtk_signal_emit (GTK_OBJECT (samview), samview_signals[SELECTION_CHANGE]); } static void samview_destroy (GtkObject * object) { SamView *samview; GSList *p; g_return_if_fail (object != NULL); g_return_if_fail (IS_SAMVIEW (object)); samview = SAMVIEW (object); if (samview->markers) { p = samview->markers; while (p) { gdk_gc_unref (((SamViewMark *) (p->data))->gc); g_free (p->data); p = g_slist_next (p); } g_slist_free (samview->markers); } if (samview->samview_pm) gdk_pixmap_unref (samview->samview_pm); if (samview->sam_gc) { gdk_gc_unref (samview->sam_gc); gdk_gc_unref (samview->cline_gc); gdk_gc_unref (samview->realpos_gc); } if (samview->selmark_gc) gdk_gc_unref (samview->selmark_gc); if (GTK_OBJECT_CLASS (parent_class)->destroy) (*GTK_OBJECT_CLASS (parent_class)->destroy) (object); } static void samview_size_request (GtkWidget * widget, GtkRequisition * requisition) { requisition->width = SAMVIEW_DEFAULT_SIZEX; requisition->height = SAMVIEW_DEFAULT_SIZEY; } static void samview_size_allocate (GtkWidget * widget, GtkAllocation * allocation) { SamView *samview; g_return_if_fail (widget != NULL); g_return_if_fail (IS_SAMVIEW (widget)); g_return_if_fail (allocation != NULL); widget->allocation = *allocation; samview = SAMVIEW (widget); if (GTK_WIDGET_REALIZED (widget)) { if (samview->samview_pm) { gdk_pixmap_unref (samview->samview_pm); samview->samview_pm = NULL; } gdk_window_move_resize (widget->window, allocation->x, allocation->y, allocation->width, allocation->height); } gtk_signal_emit (GTK_OBJECT (samview), samview_signals[VIEW_CHANGE]); } static void samview_realize (GtkWidget * widget) { SamView *samview; GdkWindowAttr attributes; gint attributes_mask; GdkColormap *cmap; g_return_if_fail (widget != NULL); g_return_if_fail (IS_SAMVIEW (widget)); GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED); samview = SAMVIEW (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; 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 (gtk_widget_get_parent_window (widget), &attributes, attributes_mask); gdk_window_set_user_data (widget->window, widget); widget->style = gtk_style_attach (widget->style, widget->window); gtk_style_set_background (widget->style, widget->window, GTK_STATE_ACTIVE); cmap = gdk_window_get_colormap (widget->window); gdk_colormap_alloc_color (cmap, &samview->bg_clr, FALSE, TRUE); gdk_colormap_alloc_color (cmap, &samview->sam_clr, FALSE, TRUE); gdk_colormap_alloc_color (cmap, &samview->cline_clr, FALSE, TRUE); gdk_colormap_alloc_color (cmap, &samview->realpos_clr, FALSE, TRUE); gdk_colormap_alloc_color (cmap, &samview->selmark_clr, FALSE, TRUE); gdk_colormap_alloc_color (cmap, &samview->select_clr, FALSE, TRUE); samview->bg_gc = gdk_gc_new (widget->window); gdk_gc_set_foreground (samview->bg_gc, &samview->bg_clr); samview->sam_gc = gdk_gc_new (widget->window); gdk_gc_set_foreground (samview->sam_gc, &samview->sam_clr); samview->cline_gc = gdk_gc_new (widget->window); gdk_gc_set_function (samview->cline_gc, GDK_XOR); gdk_gc_set_foreground (samview->cline_gc, &samview->cline_clr); samview->realpos_gc = gdk_gc_new (widget->window); gdk_gc_set_function (samview->realpos_gc, GDK_XOR); gdk_gc_set_foreground (samview->realpos_gc, &samview->realpos_clr); samview->selmark_gc = gdk_gc_new (widget->window); gdk_gc_set_function (samview->selmark_gc, GDK_XOR); gdk_gc_set_foreground (samview->selmark_gc, &samview->selmark_clr); samview->select_gc = gdk_gc_new (widget->window); gdk_gc_set_function (samview->select_gc, GDK_XOR); gdk_gc_set_foreground (samview->select_gc, &samview->select_clr); } static gint samview_expose (GtkWidget * widget, GdkEventExpose * event) { SamView *samview; g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (IS_SAMVIEW (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE); samview = SAMVIEW (widget); if (!samview->samview_pm) { samview_draw (widget); } gdk_draw_pixmap (widget->window, widget->style->fg_gc[GTK_WIDGET_STATE (widget)], samview->samview_pm, event->area.x, event->area.y, event->area.x, event->area.y, event->area.width, event->area.height); return (FALSE); } #if 0 /* fast compared to what? */ static void samview_fast_draw_selection (SamView * samview, gint newxbegin, gint newxend, gboolean newsel) { GtkWidget *widget; gint x[4]; gboolean overlap = FALSE; gint areas = 0; if (!newsel) { if (samview->select_start == -1) return; areas = 1; VIEW_SAM2XPOS (x[0], samview->select_start, samview); VIEW_SAM2XPOS (x[1], samview->select_end, samview); } else if (samview->select_start == -1) { if (!newsel) return; areas = 1; x[0] = newxbegin; x[1] = newxend; } else { areas = 2; x[0] = newxbegin; x[1] = newxend; VIEW_SAM2XPOS (x[2], samview->select_start, samview); VIEW_SAM2XPOS (x[3], samview->select_end, samview); /* check if old and new selections overlap */ if ((x[0] >= x[2] && x[0] <= x[3]) || (x[1] >= x[2] && x[1] <= x[3]) || (x[2] >= x[0] && x[2] <= x[1]) || (x[3] >= x[0] && x[3] <= x[1])) overlap = TRUE; /* sort x positions */ CMPSWAP (x[0], x[3]); CMPSWAP (x[0], x[2]); CMPSWAP (x[0], x[1]); CMPSWAP (x[1], x[3]); CMPSWAP (x[1], x[2]); CMPSWAP (x[2], x[3]); if (overlap) { x[1]--; x[2]++; } } widget = GTK_WIDGET (samview); if (areas > 0 && x[0] <= x[1]) { gdk_draw_rectangle (samview->samview_pm, samview->select_gc, TRUE, x[0], 0, x[1] - x[0] + 1, widget->allocation.height); } if (areas > 1 && x[2] <= x[3]) { gdk_draw_rectangle (samview->samview_pm, samview->select_gc, TRUE, x[2], 0, x[3] - x[2] + 1, widget->allocation.height); } samview->select_start = newbegin; samview->select_end = newend; gtk_signal_emit (GTK_OBJECT (samview), samview_signals[SELECTION_CHANGE]); } #endif static void samview_adjustment_changed (GtkAdjustment * adj, SamView * samview) { samview->start = adj->value; samview->size = adj->page_size; samview_redraw (samview); gtk_signal_emit (GTK_OBJECT (samview), samview_signals[VIEW_CHANGE]); } static void samview_adjustment_value_changed (GtkAdjustment * adj, SamView * samview) { samview->start = adj->value; samview_redraw (samview); gtk_signal_emit (GTK_OBJECT (samview), samview_signals[VIEW_CHANGE]); } static void samview_redraw (SamView * samview) { if (samview->freeze_count) return; samview_draw (GTK_WIDGET (samview)); gtk_widget_queue_draw (GTK_WIDGET (samview)); } static void samview_draw (GtkWidget * widget) { SamView *samview; gint vwidth, vheight; /* view physical (pixel) width and height */ gint vmid; /* vertical middle point */ gint x, y, lasty, i; float xmul, ymul; /* Number of samples per pixel for x and y */ SamViewMark *mark; GdkColormap *cmap; GdkGC *gc; GtkStyle *style; GSList *p; g_return_if_fail (widget != NULL); g_return_if_fail (IS_SAMVIEW (widget)); if (!GTK_WIDGET_REALIZED (widget)) return; samview = SAMVIEW (widget); if (!samview->samview_pm) { samview->samview_pm = gdk_pixmap_new (widget->window, widget->allocation.width, widget->allocation.height, -1); } vwidth = widget->allocation.width; /* view width in pixels */ vheight = widget->allocation.height; /* view height */ style = gtk_widget_get_style (widget); /* blank out sample view */ gdk_draw_rectangle (samview->samview_pm, samview->bg_gc, TRUE, 0, 0, vwidth, vheight); if (!samview->samdata) return; /* if zoom active: draw "snap" line and original sample line (if any) */ if (samview->zoom_btn != 0) { gint xpos; /* current zoom x position */ gdk_draw_line (samview->samview_pm, samview->cline_gc, samview->zoom_pos, 0, samview->zoom_pos, vheight - 1); VIEW_SAM2XPOS (xpos, samview->zoom_sampos, samview); /* if original zoom point has moved more than 2 pixels */ if (abs (xpos - samview->zoom_pos) > 2) { /* draw original sample "snap" line */ gdk_draw_line (samview->samview_pm, samview->realpos_gc, xpos, 0, xpos, vheight - 1); } } /* draw marker lines */ p = samview->markers; while (p) { /* loop through markers */ mark = (SamViewMark *) (p->data); p = g_slist_next (p); /* if marker is disabled (pos == -1) or not in view, then skip */ if (mark->pos == -1 || mark->pos < samview->start || mark->pos >= samview->start + samview->size) { mark->xpos = -1; continue; } /* calculate the x position (in pixels) of the marker */ VIEW_SAM2XPOS (x, mark->pos, samview); mark->xpos = x; if (!mark->gc) { /* create graphics context if needed */ cmap = gdk_window_get_colormap (widget->window); mark->gc = gdk_gc_new (widget->window); gdk_colormap_alloc_color (cmap, &mark->color, FALSE, TRUE); gdk_gc_set_foreground (mark->gc, &mark->color); } /* if marker is selected, draw it highlighted, else use its own color */ if (mark != samview->sel_marker) gc = mark->gc; else gc = samview->selmark_gc; /* draw marker */ gdk_draw_line (samview->samview_pm, gc, x, 0, x, vheight - 1); } /* mid point in pixels from top */ vmid = vheight / 2; /* draw center line */ gdk_draw_line (samview->samview_pm, samview->cline_gc, 0, vmid, vwidth - 1, vmid); /* calc samples per pixel ratio */ xmul = (float) (samview->size - 1) / (vwidth - 1); ymul = (float) (vheight - 1) / 65535; /* pixels per amplitude ratio */ for (x = 0; x < vwidth; x++) { i = samview->start + (int) (x * xmul); /* this sample index */ if (i >= samview->length) break; /* index beyond sample data? */ /* convert sample to unsigned (^ 0x8000) and calc pixel amplitude */ y = vheight - 1 - (int) (((guint16) (samview->samdata[i]) ^ 0x8000) * ymul); if (x > 0) { /* first sample point doesn't draw line */ gdk_draw_line (samview->samview_pm, samview->sam_gc, x - 1, lasty, x, y); } lasty = y; } /* if there is a selection and it should be drawn.. */ if (samview->select_start != -1) { gint s, e, sx, ex; /* calculate virtual x positions of start and end of selection */ VIEW_SAM2XPOS (sx, samview->select_start, samview); VIEW_SAM2XPOS (ex, samview->select_end, samview); /* determine area of selection within view */ s = MAX (0, sx); e = MIN (vwidth - 1, ex); /* s > e if selection is entirely outside of view */ if (s <= e) { /* draw selection if not outside of view */ gdk_draw_rectangle (samview->samview_pm, samview->select_gc, TRUE, s, 0, e - s + 1, vheight); } } } static gint samview_button_press (GtkWidget * widget, GdkEventButton * event) { SamView *samview; gint s, e; gboolean ctrl; g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (IS_SAMVIEW (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE); samview = SAMVIEW (widget); /* if a zoom is in progress, return */ if (samview->zoom_btn != 0) return (FALSE); if (event->button != 1 && event->button != 3) return (FALSE); ctrl = (event->state & GDK_CONTROL_MASK) != 0; /* zoom if SHIFT is held down on keyboard */ if (event->state & GDK_SHIFT_MASK) { samview->zoom_btn = event->button; samview->zoom_pos = event->x; VIEW_X2SAMPOS (samview->zoom_sampos, event->x, samview); samview->zoom_ofs = 0; samview->zoom_dir = 2; samview->zoom_toactv = FALSE; gtk_grab_add (widget); /* grab all mouse events */ samview_redraw (SAMVIEW (widget)); } else if ((event->button == 1 && !ctrl) || (ctrl && samview->select_start == -1)) { /* selection operation? */ /* store selection start sample position */ VIEW_X2SAMPOS (samview->select_actvbegin, event->x, samview); /* invalidate previous selection (if any) */ if (samview->select_start != -1) { samview->select_start = samview->select_end = -1; samview_redraw (SAMVIEW (widget)); gtk_signal_emit (GTK_OBJECT (samview), samview_signals[SELECTION_CHANGE]); } gtk_grab_add (widget); /* grab all mouse events */ } else if (ctrl) { /* add to selection? */ if (event->button == 1) s = samview->select_end; else s = samview->select_start; samview->select_actvbegin = s; VIEW_X2SAMPOS (e, event->x, samview); CMPSWAP (s, e); samview->select_start = s; samview->select_end = e; samview_redraw (SAMVIEW (widget)); gtk_signal_emit (GTK_OBJECT (samview), samview_signals[SELECTION_CHANGE]); gtk_grab_add (widget); /* grab all mouse events */ } return (FALSE); } static gint samview_button_release (GtkWidget * widget, GdkEventButton * event) { SamView *samview; g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (IS_SAMVIEW (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE); samview = SAMVIEW (widget); /* zoom is done? */ if (event->button == samview->zoom_btn) { samview->zoom_btn = 0; if (samview->zoom_toactv) { /* disable any active timeout */ g_source_remove (samview->zoom_toid); samview->zoom_toactv = FALSE; } gtk_grab_remove (widget); /* no more mouse event grabbing */ samview_redraw (SAMVIEW (widget)); /* redraw sample view */ } /* selection done? */ if (samview->select_actvbegin != -1) { samview->select_actvbegin = -1; /* selection no longer active */ gtk_grab_remove (widget); /* no more mouse event grabbing */ if (samview->select_scroll) { samview->select_scroll = 0; g_source_remove (samview->select_toid); } } return (FALSE); } static gint samview_motion_notify (GtkWidget * widget, GdkEventMotion * event) { SamView *samview; gint x, y; gint s; gint t, d, a; float plus, minus; GdkModifierType mods; g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (IS_SAMVIEW (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE); samview = SAMVIEW (widget); x = event->x; y = event->y; if (event->is_hint || (event->window != widget->window)) gdk_window_get_pointer (widget->window, &x, &y, &mods); if (samview->zoom_btn == 0) { /* selection in progress? */ if (samview->select_actvbegin == -1) return (FALSE); /* scroll?? */ if ((x < 0 && samview->start != 0) || (x >= widget->allocation.width && samview->start + samview->size < samview->length - 1)) { if (samview->select_scroll) { /* scroll in progress? */ d = ABS (y - samview->select_scrolly); t = event->time - samview->select_scrolltime; minus = SCROLL_DSLOPE * t + SCROLL_DOFS; samview->select_scrollamt += minus; if (samview->select_scrollamt < 0.0) samview->select_scrollamt = 0.0; plus = SCROLL_SLOPE * d / t + SCROLL_OFS; samview->select_scrollamt += plus; if (samview->select_scrollamt > SCROLL_MAX) samview->select_scrollamt = SCROLL_MAX; a = (x < 0) ? 1 : 2; /* direction of scroll 1,2 = left,right */ /* probably won't happen, but just in case scroll direction switches without being disabled first */ if (a != samview->select_scroll) { samview->select_scroll = a; /* change direction */ samview->select_scrollamt = 0.0; /* reset counters */ samview->select_scrollaccum = 0; } a = (float) samview->select_scrollamt / (widget->allocation.width - 1) * (samview->size - 1); samview->select_scrollaccum += a; // printf ("%d %2.2f %2.2f %2.2f\n", event->time, // samview->select_scrollamt, plus, minus); } else { /* ?: scroll not in progress, so initiate it */ samview->select_scroll = (x < 0) ? 1 : 2; samview->select_scrollamt = 0.0; samview->select_scrollaccum = 0; samview->select_toid = g_timeout_add_full (SCROLL_PRIORITY, SCROLL_TINTERVAL, samview_scroll_timeout, (gpointer) samview, NULL); } samview->select_scrolltime = event->time; samview->select_scrolly = y; } else samview->select_scroll = 0; /* ?: no scroll */ x = CLAMP (x, 0, widget->allocation.width - 1); /* clamp x to view */ /* calculate new start point of selection in samples */ samview->select_start = CLAMP (samview->select_actvbegin, 0, samview->length - 1); /* calculate new end point of selection in samples */ VIEW_X2SAMPOS (s, x, samview); samview->select_end = CLAMP (s, 0, samview->length - 1); /* swap start/end if in reverse */ CMPSWAP (samview->select_start, samview->select_end); samview_redraw (SAMVIEW (widget)); gtk_signal_emit (GTK_OBJECT (samview), samview_signals[SELECTION_CHANGE]); return (FALSE); } if (samview->zoom_dir == 2) { /* un/zoom not determined yet? */ if (x > samview->zoom_pos + ZOOM_SAFEZONE) { samview->zoom_dir = 0; } else if (x < samview->zoom_pos - ZOOM_SAFEZONE) { samview->zoom_dir = 1; } else return (FALSE); if (samview->zoom_btn == 3) samview->zoom_dir ^= 1; } samview->zoom_ofs = x - samview->zoom_pos; if (abs (samview->zoom_ofs) > ZOOM_SAFEZONE) { /* safe zone? */ /* calculate zoom/unzoom interval timeout (in milliseconds) */ samview->zoom_totime = (float) (abs (samview->zoom_ofs) - ZOOM_SAFEZONE) * ZOOM_TIME_SLOPE + ZOOM_TIME_OFS; /* make sure interval isn't too fast */ if (samview->zoom_totime < ZOOM_TIME_MIN) samview->zoom_totime = ZOOM_TIME_MIN; /* calculate ZOOM amount */ samview->zoom_amount = (float) (abs (samview->zoom_ofs) - ZOOM_SAFEZONE) * ZOOM_AMT_SLOPE + ZOOM_AMT_OFS; /* make sure un/zoom amount isn't too much */ if (samview->zoom_amount < ZOOM_AMT_MIN) samview->zoom_amount = ZOOM_AMT_MIN; /* If UNZOOM, UNZOOM = 1 / ZOOM */ if ((samview->zoom_dir == 0 && samview->zoom_ofs < 0) || (samview->zoom_dir == 1 && samview->zoom_ofs > 0)) samview->zoom_amount = 1.0 / samview->zoom_amount; if (!samview->zoom_toactv) { /* start timeout, if not actv */ samview->zoom_toid = g_timeout_add_full (ZOOM_PRIORITY, samview->zoom_totime, samview_zoom_timeout, (gpointer) samview, NULL); samview->zoom_toactv = TRUE; } } else if (samview->zoom_toactv) { /* ?: Yes, safe ZONE */ g_source_remove (samview->zoom_toid); /* disable timeout function */ samview->zoom_toactv = FALSE; } return (FALSE); } static gboolean samview_scroll_timeout (gpointer data) { SamView *samview; gint a; samview = SAMVIEW (data); a = samview->select_scrollaccum; if (!a || !samview->select_scroll) return (TRUE); if (samview->select_scroll == 1) { a = samview->start - a; if (a < 0) a = 0; } else { a = samview->start + a; if (a + samview->size >= samview->length) a = samview->length - samview->size - 1; } samview->select_scrollaccum = 0; if (samview->start == a) return (TRUE); samview_freeze (samview); samview_set_position (samview, a); samview_thaw (samview); return (TRUE); } static gboolean samview_zoom_timeout (gpointer data) { SamView *samview; gint vwidth; gint newsize; gint newstart; samview = SAMVIEW (data); vwidth = GTK_WIDGET (samview)->allocation.width; /* calculate new view size (in samples) i.e. zoom in or out */ newsize = samview->size * samview->zoom_amount; /* make sure newsize is within bounds */ if (newsize > samview->length) newsize = samview->length; if (newsize < vwidth) newsize = vwidth; /* at least 1 sample per pixel */ /* calculate new start position, keeping zoom point stationary */ newstart = (int) ((float) samview->zoom_pos / (vwidth - 1) * newsize); newstart = samview->zoom_sampos - newstart; /* make sure newstart is within bounds */ if (newstart < 0) newstart = 0; else if (newstart + newsize > samview->length) newstart = samview->length - newsize; if (newstart != samview->start || newsize != samview->size) samview_set_view (samview, newstart, newsize); samview->zoom_toid = g_timeout_add (samview->zoom_totime, samview_zoom_timeout, (gpointer) samview); samview->zoom_toactv = TRUE; return (FALSE); /* don't destroy timeout */ }