/* -*- linux-c -*- Copyright (C) 2004 Tom Szilagyi 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., 675 Mass Ave, Cambridge, MA 02139, USA. $Id: loop_bar.c 1045 2008-12-25 13:10:34Z peterszilagyi $ */ #include #ifdef HAVE_LOOP #include #include "loop_bar.h" #define AQUALUNG_LOOP_BAR_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), AQUALUNG_TYPE_LOOP_BAR, AqualungLoopBarPrivate)) G_DEFINE_TYPE (AqualungLoopBar, aqualung_loop_bar, GTK_TYPE_DRAWING_AREA); typedef struct _AqualungLoopBarPrivate AqualungLoopBarPrivate; struct _AqualungLoopBarPrivate { float start; float end; gboolean start_dragged; gboolean end_dragged; gboolean start_hover; gboolean end_hover; gint prev_x; gint width; gint margin; }; enum { RANGE_CHANGED, LAST_SIGNAL }; static guint aqualung_loop_bar_signals[LAST_SIGNAL] = { 0 }; void _loop_bar_marshal_VOID__FLOAT_FLOAT(GClosure * closure, GValue * return_value, guint n_param_values, const GValue * param_values, gpointer invocation_hint, gpointer marshal_data) { typedef void (* GMarshalFunc_VOID__FLOAT_FLOAT) (gpointer data1, gfloat arg_1, gfloat arg_2, gpointer data2); GMarshalFunc_VOID__FLOAT_FLOAT callback; GCClosure * cc = (GCClosure *)closure; gpointer data1, data2; g_return_if_fail(n_param_values == 3); if (G_CCLOSURE_SWAP_DATA(closure)) { data1 = closure->data; data2 = g_value_peek_pointer(param_values + 0); } else { data1 = g_value_peek_pointer(param_values + 0); data2 = closure->data; } callback = (GMarshalFunc_VOID__FLOAT_FLOAT) (marshal_data ? marshal_data : cc->callback); callback(data1, g_value_get_float(param_values + 1), g_value_get_float(param_values + 2), data2); } void aqualung_loop_bar_draw_marker(GtkWidget * widget, cairo_t * cr, int x, int height, gboolean hover) { AqualungLoopBarPrivate * priv = AQUALUNG_LOOP_BAR_GET_PRIVATE(widget); GdkColor fg = widget->style->fg[hover ? GTK_STATE_PRELIGHT : GTK_STATE_NORMAL]; double fg_r = fg.red / 65535.0; double fg_g = fg.green / 65535.0; double fg_b = fg.blue / 65535.0; cairo_new_path(cr); cairo_move_to(cr, x - priv->width, 0); cairo_rel_line_to(cr, 2 * priv->width, 0); cairo_rel_line_to(cr, 0, height / 2); cairo_rel_line_to(cr, -priv->width, height / 2); cairo_rel_line_to(cr, -priv->width, -height / 2); cairo_rel_line_to(cr, 0, -height / 2); cairo_close_path(cr); cairo_set_source_rgb(cr, fg_r, fg_g, fg_b); cairo_fill_preserve(cr); cairo_set_source_rgb(cr, fg_r / 2, fg_g / 2, fg_b / 2); cairo_stroke(cr); } gboolean aqualung_loop_bar_expose(GtkWidget * widget, GdkEventExpose * event) { cairo_t * cr; int x_start; int x_end; int height; GdkColor bg = widget->style->bg[GTK_STATE_SELECTED]; GdkColor bg2 = widget->style->bg[GTK_STATE_ACTIVE]; double bg_r = bg.red / 65535.0; double bg_g = bg.green / 65535.0; double bg_b = bg.blue / 65535.0; double bg2_r = bg2.red / 65535.0; double bg2_g = bg2.green / 65535.0; double bg2_b = bg2.blue / 65535.0; AqualungLoopBarPrivate * priv = AQUALUNG_LOOP_BAR_GET_PRIVATE(widget); x_start = (widget->allocation.width - 2 * priv->margin) * priv->start + priv->margin; x_end = (widget->allocation.width - 2 * priv->margin) * priv->end + priv->margin; height = widget->allocation.height; cr = gdk_cairo_create(widget->window); cairo_rectangle(cr, event->area.x, event->area.y, event->area.width, event->area.height); cairo_clip(cr); cairo_set_line_width(cr, 1.0); cairo_new_path(cr); cairo_rectangle(cr, x_start, 0, x_end - x_start, height); cairo_set_source_rgb(cr, bg_r, bg_g, bg_b); cairo_fill_preserve(cr); aqualung_loop_bar_draw_marker(widget, cr, x_end, height, priv->end_hover); aqualung_loop_bar_draw_marker(widget, cr, x_start, height, priv->start_hover); cairo_new_path(cr); cairo_move_to(cr, 0, height); cairo_rel_line_to(cr, 0, -height); cairo_rel_line_to(cr, widget->allocation.width, 0); cairo_set_source_rgb(cr, bg2_r, bg2_g, bg2_b); cairo_stroke(cr); cairo_new_path(cr); cairo_move_to(cr, 0, height); cairo_rel_line_to(cr, widget->allocation.width, 0); cairo_rel_line_to(cr, 0, -height); cairo_set_source_rgb(cr, bg2_r * 2, bg2_g * 2, bg2_b * 2); cairo_stroke(cr); cairo_destroy(cr); return FALSE; } void aqualung_loop_bar_redraw_canvas(GtkWidget * widget) { GdkRegion *region; if (!widget->window) { return; } region = gdk_drawable_get_clip_region(widget->window); gdk_window_invalidate_region(widget->window, region, TRUE); gdk_window_process_updates(widget->window, TRUE); gdk_region_destroy(region); } void aqualung_loop_bar_update(GtkWidget * widget, int x) { AqualungLoopBarPrivate * priv = AQUALUNG_LOOP_BAR_GET_PRIVATE(widget); gboolean redraw = FALSE; if (priv->start_dragged || (!priv->end_dragged && x >= (widget->allocation.width - 2 * priv->margin) * priv->start + priv->margin - priv->width && x <= (widget->allocation.width - 2 * priv->margin) * priv->start + priv->margin + priv->width)) { if (priv->start_hover == FALSE) { priv->start_hover = TRUE; priv->end_hover = FALSE; redraw = TRUE; } } else { if (priv->start_hover == TRUE) { priv->start_hover = FALSE; redraw = TRUE; } } if (priv->end_dragged || (!priv->start_dragged && x >= (widget->allocation.width - 2 * priv->margin) * priv->end + priv->margin - priv->width && x <= (widget->allocation.width - 2 * priv->margin) * priv->end + priv->margin + priv->width)) { if (priv->end_hover == FALSE && priv->start_hover == FALSE) { priv->end_hover = TRUE; redraw = TRUE; } } else { if (priv->end_hover == TRUE) { priv->end_hover = FALSE; redraw = TRUE; } } if (priv->start_dragged) { priv->start += (float)(x - priv->prev_x) / (widget->allocation.width - 2 * priv->margin); priv->start = priv->start < 0 ? 0 : priv->start; priv->start = priv->start > priv->end ? priv->end : priv->start; priv->prev_x = x; redraw = TRUE; } else if (priv->end_dragged) { priv->end += (float)(x - priv->prev_x) / (widget->allocation.width - 2 * priv->margin); priv->end = priv->end < priv->start ? priv->start : priv->end; priv->end = priv->end > 1 ? 1 : priv->end; priv->prev_x = x; redraw = TRUE; } if (redraw) { aqualung_loop_bar_redraw_canvas(widget); } } gboolean aqualung_loop_bar_motion_notify(GtkWidget * widget, GdkEventMotion * event) { aqualung_loop_bar_update(widget, event->x); return FALSE; } gboolean aqualung_loop_bar_button_press(GtkWidget * widget, GdkEventButton * event) { AqualungLoopBarPrivate * priv = AQUALUNG_LOOP_BAR_GET_PRIVATE(widget); if (event->state & GDK_SHIFT_MASK) { /* SHIFT */ priv->start = 0.0f; priv->end = 1.0f; aqualung_loop_bar_redraw_canvas(widget); return TRUE; } if (event->button == 1) { priv->prev_x = event->x; if (event->x >= (widget->allocation.width - 2 * priv->margin) * priv->start + priv->margin - priv->width && event->x <= (widget->allocation.width - 2 * priv->margin) * priv->start + priv->margin + priv->width) { priv->start_dragged = TRUE; } else if (event->x >= (widget->allocation.width - 2 * priv->margin) * priv->end + priv->margin - priv->width && event->x <= (widget->allocation.width - 2 * priv->margin) * priv->end + priv->margin + priv->width) { priv->end_dragged = TRUE; } } return FALSE; } gboolean aqualung_loop_bar_button_release(GtkWidget * widget, GdkEventButton * event) { AqualungLoopBarPrivate * priv = AQUALUNG_LOOP_BAR_GET_PRIVATE(widget); priv->start_dragged = FALSE; priv->end_dragged = FALSE; aqualung_loop_bar_update(widget, event->x); g_signal_emit(widget, aqualung_loop_bar_signals[RANGE_CHANGED], 0, priv->start, priv->end); return FALSE; } gboolean aqualung_loop_bar_enter_notify(GtkWidget * widget, GdkEventCrossing * event) { aqualung_loop_bar_update(widget, event->x); return FALSE; } gboolean aqualung_loop_bar_leave_notify(GtkWidget * widget, GdkEventCrossing * event) { AqualungLoopBarPrivate * priv; if (event->y >= 0 && event->y < widget->allocation.height && event->x >= 0 && event->x < widget->allocation.width) { /* before each button press event we receive a leave * notify event, which should be neglected. */ return FALSE; } priv = AQUALUNG_LOOP_BAR_GET_PRIVATE(widget); priv->start_hover = priv->start_dragged; priv->end_hover = priv->end_dragged; aqualung_loop_bar_redraw_canvas(widget); return FALSE; } void aqualung_loop_bar_class_init(AqualungLoopBarClass * class) { GObjectClass * obj_class; GtkWidgetClass * widget_class; obj_class = G_OBJECT_CLASS(class); widget_class = GTK_WIDGET_CLASS(class); aqualung_loop_bar_signals[RANGE_CHANGED] = g_signal_new ( "range-changed", G_OBJECT_CLASS_TYPE(obj_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET(AqualungLoopBarClass, range_changed), NULL, NULL, _loop_bar_marshal_VOID__FLOAT_FLOAT, G_TYPE_NONE, 2, G_TYPE_FLOAT, G_TYPE_FLOAT); widget_class->expose_event = aqualung_loop_bar_expose; widget_class->button_press_event = aqualung_loop_bar_button_press; widget_class->button_release_event = aqualung_loop_bar_button_release; widget_class->motion_notify_event = aqualung_loop_bar_motion_notify; widget_class->enter_notify_event = aqualung_loop_bar_enter_notify; widget_class->leave_notify_event = aqualung_loop_bar_leave_notify; g_type_class_add_private(obj_class, sizeof(AqualungLoopBarPrivate)); } void aqualung_loop_bar_init(AqualungLoopBar * bar) { AqualungLoopBarPrivate * priv = AQUALUNG_LOOP_BAR_GET_PRIVATE(bar); priv->start = 0.0f; priv->end = 1.0f; priv->start_dragged = FALSE; priv->end_dragged = FALSE; priv->start_hover = FALSE; priv->end_hover = FALSE; priv->width = 4; priv->margin = 16; gtk_widget_add_events(GTK_WIDGET(bar), GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK); } GtkWidget * aqualung_loop_bar_new(float start, float end) { GtkWidget * widget; AqualungLoopBarPrivate * priv; widget = g_object_new(AQUALUNG_TYPE_LOOP_BAR, NULL); priv = AQUALUNG_LOOP_BAR_GET_PRIVATE(AQUALUNG_LOOP_BAR(widget)); priv->start = start; priv->end = end; return widget; } void aqualung_loop_bar_adjust_start(AqualungLoopBar * bar, float start) { AqualungLoopBarPrivate * priv = AQUALUNG_LOOP_BAR_GET_PRIVATE(bar); if (start < priv->end) { priv->start = start; aqualung_loop_bar_redraw_canvas(GTK_WIDGET(bar)); g_signal_emit(GTK_WIDGET(bar), aqualung_loop_bar_signals[RANGE_CHANGED], 0, priv->start, priv->end); } } void aqualung_loop_bar_adjust_end(AqualungLoopBar * bar, float end) { AqualungLoopBarPrivate * priv = AQUALUNG_LOOP_BAR_GET_PRIVATE(bar); if (end > priv->start) { priv->end = end; aqualung_loop_bar_redraw_canvas(GTK_WIDGET(bar)); g_signal_emit(GTK_WIDGET(bar), aqualung_loop_bar_signals[RANGE_CHANGED], 0, priv->start, priv->end); } } #endif /* HAVE_LOOP */ // vim: shiftwidth=8:tabstop=8:softtabstop=8 :