Add nknob, modified/simlified version of phatknob

This commit is contained in:
Nick Lanham 2012-02-21 20:38:32 +01:00
parent fedf1a87a6
commit 771fd5b528
3 changed files with 654 additions and 0 deletions

View File

@ -16,6 +16,9 @@ drmr_ui.so: drmr_ui.c drmr_hydrogen.c
htest: drmr_hydrogen.c
$(CC) -D_TEST_HYDROGEN_PARSER -Wall -fPIC -DPIC drmr_hydrogen.c `pkg-config --cflags --libs sndfile samplerate` -lexpat -lm -o htest
knobt: nknob.c
$(CC) -D_TEST_N_KNOB -DINSTALL_DIR=\"$(INSTALL_DIR)\" -Wall -fPIC -DPIC nknob.c `pkg-config --cflags --libs gtk+-2.0 ` -lm -o knobt
install: $(BUNDLE)
mkdir -p $(INSTALL_DIR)
rm -rf $(INSTALL_DIR)/$(BUNDLE)

572
nknob.c Normal file
View File

@ -0,0 +1,572 @@
/* nknob.c
* LV2 DrMr plugin
* Copyright 2012 Nick Lanham <nick@afternight.org>
*
* NKnob - A simplified version of phatknob that just is a new gui
* over a GtkRange (i.e. it can be used exactly like a
* GtkRange from the outside)
*
* From PhatKnob code:
* Most of this code comes from gAlan 0.2.0, copyright (C) 1999
* Tony Garnock-Jones, with modifications by Sean Bolton,
* copyright (c) 2004. (gtkdial.c rolls over in its grave.)
*
* Phatised by Loki Davison.
*
* GNU Public License v3. source code is available at
* <http://github.com/nicklan/drmr>
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <gtk/gtkmain.h>
#include <gtk/gtksignal.h>
#include "nknob.h"
#define SCROLL_DELAY_LENGTH 100
#define KNOB_SIZE 50
enum {
STATE_IDLE,
STATE_PRESSED,
STATE_DRAGGING,
STATE_SCROLL,
};
/* properties */
enum
{
PROP_0, /* oops, no props any more */
};
static void n_knob_class_init (NKnobClass *klass);
static void n_knob_init (NKnob *knob);
static void n_knob_destroy (GtkObject *object);
static void n_knob_realize (GtkWidget *widget);
static void n_knob_size_request (GtkWidget *widget,
GtkRequisition *requisition);
static gint n_knob_expose (GtkWidget *widget,
GdkEventExpose *event);
static gint n_knob_button_press (GtkWidget *widget,
GdkEventButton *event);
static gint n_knob_button_release (GtkWidget *widget,
GdkEventButton *event);
static gint n_knob_motion_notify (GtkWidget *widget,
GdkEventMotion *event);
static gint n_knob_scroll (GtkWidget *widget,
GdkEventScroll *event);
static void n_knob_update_mouse (NKnob *knob,
gint x,
gint y,
gboolean absolute);
static void n_knob_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec);
static void n_knob_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec);
GError *gerror;
/* global pixbufs for less mem usage */
GdkPixbuf **pixbuf = NULL;
/* Local data */
G_DEFINE_TYPE (NKnob, n_knob, GTK_TYPE_RANGE);
static void n_knob_class_init (NKnobClass *klass) {
GtkObjectClass *object_class;
GtkWidgetClass *widget_class;
GObjectClass *g_object_class;
object_class = GTK_OBJECT_CLASS(klass);
widget_class = GTK_WIDGET_CLASS(klass);
g_object_class = G_OBJECT_CLASS(klass);
g_object_class->set_property = n_knob_set_property;
g_object_class->get_property = n_knob_get_property;
object_class->destroy = n_knob_destroy;
widget_class->realize = n_knob_realize;
widget_class->expose_event = n_knob_expose;
widget_class->size_request = n_knob_size_request;
widget_class->button_press_event = n_knob_button_press;
widget_class->button_release_event = n_knob_button_release;
widget_class->motion_notify_event = n_knob_motion_notify;
widget_class->scroll_event = n_knob_scroll;
}
static void n_knob_init (NKnob *knob) {
knob->state = STATE_IDLE;
knob->saved_x = knob->saved_y = 0;
knob->size = KNOB_SIZE;
knob->pixbuf = NULL;
}
/**
* n_knob_new:
* @adjustment: a #GtkAdjustment or NULL
*
* Creates a new #NKnob with the supplied
* #GtkAdjustment. if adjustment is NULL or
* invalid, a new #GtkAdjustment will be created
* with the default values of:
* 0.0, 0.0, 10.0, 0.1, 0.1, 0.2
*
* Returns: a newly created #NKnob
*
*/
GtkWidget *n_knob_new(GtkAdjustment *adjustment) {
return g_object_new (N_TYPE_KNOB,
"adjustment",
adjustment,
NULL);
}
/**
* n_knob_new_with_range:
* @value: the initial value the new knob should have
* @lower: the lowest value the new knob will allow
* @upper: the highest value the new knob will allow
* @step: increment added or subtracted when mouse scrolling
*
* Creates a new #NKnob. The knob will create a new
* #GtkAdjustment from @value, @lower, @upper, and @step. If these
* parameters represent a bogus configuration, the program will
* terminate.
*
* Returns: a newly created #NKnob
*
*/
GtkWidget* n_knob_new_with_range (double value, double lower,
double upper, double step) {
GtkAdjustment* adj;
adj = (GtkAdjustment*) gtk_adjustment_new (value, lower, upper, step, step, 0);
return n_knob_new (adj);
}
static void n_knob_destroy(GtkObject *object) {
NKnob *knob;
g_return_if_fail(object != NULL);
g_return_if_fail(N_IS_KNOB(object));
knob = N_KNOB(object);
if (knob->mask) {
gdk_bitmap_unref(knob->mask);
knob->mask = NULL;
}
if (knob->mask_gc) {
gdk_gc_unref(knob->mask_gc);
knob->mask_gc = NULL;
}
if (knob->red_gc) {
gdk_gc_unref(knob->red_gc);
knob->red_gc = NULL;
}
if (GTK_OBJECT_CLASS(n_knob_parent_class)->destroy)
(*GTK_OBJECT_CLASS(n_knob_parent_class)->destroy)(object);
}
static void n_knob_realize(GtkWidget *widget) {
NKnob *knob;
extern GdkPixbuf **pixbuf;
int i=0;
g_return_if_fail(widget != NULL);
g_return_if_fail(N_IS_KNOB(widget));
if (GTK_WIDGET_CLASS(n_knob_parent_class)->realize)
(*GTK_WIDGET_CLASS(n_knob_parent_class)->realize)(widget);
knob = N_KNOB(widget);
/* FIXME keeps khagan from drawing knob */
if(widget->allocation.height > 1)
{
knob->size = widget->allocation.height;
}
/* init first pixbuf */
if(pixbuf == NULL){
pixbuf = g_malloc0(sizeof(GdkPixbuf *));
}
/* check for fitting pixbuf or NULL */
while(pixbuf[i] != NULL && gdk_pixbuf_get_height(pixbuf[i]) != knob->size){
i++;
}
/* if NULL realloc pixbuf pointer array one bigger
* malloc new pixbuf with new size
* set local pixbuf pointer to global
* set last pixbuf pointer to NULL */
if(pixbuf[i] == NULL){
pixbuf[i] = gdk_pixbuf_new_from_file_at_size(INSTALL_DIR"/drmr.lv2/knob.png",
52*knob->size,knob->size,&gerror);
knob->pixbuf = pixbuf[i];
pixbuf=g_realloc(pixbuf,sizeof(GdkPixbuf *) * (i+2));
pixbuf[i+1] = NULL;
} else { /* if not NULL set fitting pixbuf */
knob->pixbuf = pixbuf[i];
}
}
static void n_knob_size_request (GtkWidget *widget, GtkRequisition *requisition) {
NKnob *knob;
knob = N_KNOB(widget);
requisition->width = knob->size;
requisition->height = knob->size;
}
static inline gdouble get_zero_one_value(NKnob *knob) {
GtkAdjustment* adj = gtk_range_get_adjustment(GTK_RANGE(knob));
return (adj->value - adj->lower)/(adj->upper - adj->lower);
}
static inline gdouble get_adj_value(NKnob *knob, gdouble val) {
GtkAdjustment* adj = gtk_range_get_adjustment(GTK_RANGE(knob));
return val*(adj->upper - adj->lower) + adj->lower;
}
static gint n_knob_expose(GtkWidget *widget, GdkEventExpose *event)
{
NKnob *knob;
int dx;
g_return_val_if_fail(widget != NULL, FALSE);
g_return_val_if_fail(N_IS_KNOB(widget), FALSE);
g_return_val_if_fail(event != NULL, FALSE);
if (event->count > 0)
return FALSE;
knob = N_KNOB(widget);
dx = (int)(51 * get_zero_one_value(knob)) * knob->size;
gdk_pixbuf_render_to_drawable_alpha( knob->pixbuf, widget->window,
dx, 0, widget->allocation.x, widget->allocation.y,
knob->size, knob->size, GDK_PIXBUF_ALPHA_FULL, 0, 0,0,0 );
// gdk_draw_pixbuf(widget->window, knob->mask_gc, knob->pixbuf,
// dx, 0, 0, 0, knob->size, knob->size,GDK_RGB_DITHER_NONE,0,0);
return FALSE;
}
static gint n_knob_button_press(GtkWidget *widget, GdkEventButton *event) {
NKnob *knob;
g_return_val_if_fail(widget != NULL, FALSE);
g_return_val_if_fail(N_IS_KNOB(widget), FALSE);
g_return_val_if_fail(event != NULL, FALSE);
knob = N_KNOB(widget);
switch (knob->state) {
case STATE_IDLE:
switch (event->button) {
case 1:
case 2:
gtk_grab_add(widget);
knob->state = STATE_PRESSED;
knob->saved_x = event->x;
knob->saved_y = event->y;
break;
default:
break;
}
break;
default:
break;
}
return FALSE;
}
static gint n_knob_button_release(GtkWidget *widget, GdkEventButton *event) {
NKnob *knob;
GtkRange *range;
GtkAdjustment *adj;
g_return_val_if_fail(widget != NULL, FALSE);
g_return_val_if_fail(N_IS_KNOB(widget), FALSE);
g_return_val_if_fail(event != NULL, FALSE);
knob = N_KNOB(widget);
range = GTK_RANGE(knob);
adj = gtk_range_get_adjustment(range);
switch (knob->state) {
case STATE_PRESSED:
gtk_grab_remove(widget);
knob->state = STATE_IDLE;
switch (event->button) {
case 1:
gtk_range_set_value(range,
gtk_range_get_value(range)+
gtk_adjustment_get_page_increment(adj));
break;
case 3:
gtk_range_set_value(range,
gtk_range_get_value(range)-
gtk_adjustment_get_page_increment(adj));
break;
default:
break;
}
break;
case STATE_DRAGGING:
gtk_grab_remove(widget);
knob->state = STATE_IDLE;
break;
default:
break;
}
return FALSE;
}
static gint n_knob_motion_notify(GtkWidget *widget, GdkEventMotion *event) {
NKnob *knob;
GdkModifierType mods;
gint x, y;
g_return_val_if_fail(widget != NULL, FALSE);
g_return_val_if_fail(N_IS_KNOB(widget), FALSE);
g_return_val_if_fail(event != NULL, FALSE);
knob = N_KNOB(widget);
x = event->x;
y = event->y;
if (event->is_hint || (event->window != widget->window))
gdk_window_get_pointer(widget->window, &x, &y, &mods);
switch (knob->state) {
case STATE_PRESSED:
knob->state = STATE_DRAGGING;
/* fall through */
case STATE_DRAGGING:
if (mods & GDK_BUTTON1_MASK) {
n_knob_update_mouse(knob, x-widget->allocation.x, y-widget->allocation.y , TRUE);
return TRUE;
} else if (mods & GDK_BUTTON3_MASK) {
n_knob_update_mouse(knob, x-widget->allocation.x, y-widget->allocation.y , FALSE);
return TRUE;
}
break;
default:
break;
}
return FALSE;
}
static gint n_knob_scroll (GtkWidget *widget, GdkEventScroll *event) {
NKnob *knob;
GtkRange *range;
GtkAdjustment *adj;
gdouble oldval,newval;
gboolean handled;
GtkScrollType type;
knob = N_KNOB(widget);
range = GTK_RANGE(widget);
adj = gtk_range_get_adjustment(range);
gtk_widget_grab_focus (widget);
knob->state = STATE_SCROLL;
oldval = gtk_range_get_value(range);
switch (event->direction) {
case GDK_SCROLL_UP:
newval = oldval +
gtk_adjustment_get_step_increment(adj);
type = GTK_SCROLL_STEP_UP;
break;
case GDK_SCROLL_DOWN:
newval = oldval -
gtk_adjustment_get_step_increment(adj);
type = GTK_SCROLL_STEP_DOWN;
default: // only handle those for now
break;
}
gtk_range_set_value(range,newval);
newval = gtk_range_get_value(range);
if (newval != oldval)
g_signal_emit_by_name (range, "change-value", type, gtk_range_get_value(range), &handled);
knob->state = STATE_IDLE;
return TRUE;
}
static void n_knob_update_mouse(NKnob *knob, gint x, gint y,
gboolean absolute) {
gdouble old_adj_val,old_value, new_value, dv, dh;
gdouble angle,handled;
g_return_if_fail(knob != NULL);
g_return_if_fail(N_IS_KNOB(knob));
old_adj_val = gtk_range_get_value(GTK_RANGE(knob));
old_value = get_zero_one_value(knob);
angle = atan2(-y + (knob->size>>1), x - (knob->size>>1));
if (absolute) {
angle /= G_PI;
if (angle < -0.5)
angle += 2;
new_value = -(2.0/3.0) * (angle - 1.25); /* map [1.25pi, -0.25pi] onto [0, 1] */
} else {
dv = knob->saved_y - y; /* inverted cartesian graphics coordinate system */
dh = x - knob->saved_x;
knob->saved_x = x;
knob->saved_y = y;
if (x >= 0 && x <= knob->size)
dh = 0; /* dead zone */
else {
angle = cos(angle);
dh *= angle * angle;
}
new_value = old_value +
dv * 0.1 + /* "step" == 0.1 */
dh / 200.0f;
}
new_value = get_adj_value(knob,new_value);
gtk_range_set_value(GTK_RANGE(knob),new_value);
new_value = gtk_range_get_value(GTK_RANGE(knob));
if (new_value != old_adj_val) {
g_signal_emit_by_name (knob, "change-value", GTK_SCROLL_JUMP,
new_value, &handled);
}
}
static void
n_knob_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
switch (prop_id) {
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
n_knob_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
switch (prop_id) {
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
#ifdef _TEST_N_KNOB
static gboolean timeout_func(gpointer data) {
GtkRange *range = GTK_RANGE(data);
GtkAdjustment *adj = gtk_range_get_adjustment(range);
gdouble val = gtk_range_get_value(range);
if (val == adj->upper)
val = adj->lower;
else
val += gtk_adjustment_get_step_increment(adj);
gtk_range_set_value(range,val);
return TRUE;
}
static gboolean changed_callback(GtkRange* range, GtkScrollType type, gdouble value, gpointer data) {
printf("Changed to: %f\n",value);
return FALSE;
}
int main(int argc, char* argv[]) {
GtkWidget* window;
GtkWidget* knob;
GtkWidget* vbox;
gtk_init (&argc, &argv);
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (window), "NKnob Test");
gtk_window_set_position (GTK_WINDOW (window), GTK_WIN_POS_CENTER);
g_signal_connect (G_OBJECT (window), "delete-event",
G_CALLBACK (gtk_main_quit), NULL);
vbox = gtk_vbox_new (FALSE, 0);
gtk_container_add (GTK_CONTAINER (window), vbox);
/* knob */
knob = n_knob_new_with_range (-90, -90, 0, 1);
gtk_box_pack_start (GTK_BOX (vbox), knob, TRUE, FALSE, 0);
g_signal_connect (G_OBJECT (knob), "change-value",
G_CALLBACK (changed_callback), NULL);
gtk_widget_show_all (window);
if (0)
g_timeout_add(5,timeout_func,knob);
gtk_main ( );
return 0;
}
#endif

79
nknob.h Normal file
View File

@ -0,0 +1,79 @@
/* nknob.h
* LV2 DrMr plugin
* Copyright 2012 Nick Lanham <nick@afternight.org>
*
* NKnob - A simplified version of phatknob that just is a new gui
* over a GtkRange (i.e. it can be used exactly like a
* GtkRange from the outside)
*
* From PhatKnob code:
* Most of this code comes from gAlan 0.2.0, copyright (C) 1999
* Tony Garnock-Jones, with modifications by Sean Bolton,
* copyright (c) 2004. (gtkdial.c rolls over in its grave.)
*
* Phatised by Loki Davison.
*
* GNU Public License v3. source code is available at
* <http://github.com/nicklan/drmr>
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#ifndef __NKNOB_H__
#define __NKNOB_H__
#include <gdk/gdk.h>
#include <gtk/gtkrange.h>
G_BEGIN_DECLS
#define N_KNOB(obj) GTK_CHECK_CAST(obj, n_knob_get_type(), NKnob)
#define N_KNOB_CLASS(klass) GTK_CHECK_CLASS_CAST(klass, n_knob_get_type(), NKnobClass)
#define N_IS_KNOB(obj) GTK_CHECK_TYPE(obj, n_knob_get_type())
#define N_TYPE_KNOB (n_knob_get_type ( ))
typedef struct _NKnob NKnob;
typedef struct _NKnobClass NKnobClass;
struct _NKnob {
GtkRange range;
/* State of widget (to do with user interaction) */
guint8 state;
gint saved_x, saved_y;
/* size of the widget */
gint size;
/* Pixmap for knob */
GdkPixbuf *pixbuf;
GdkBitmap *mask;
GdkGC *mask_gc;
GdkGC *red_gc;
};
struct _NKnobClass {
GtkRangeClass parent_class;
};
GType n_knob_get_type ( );
GtkWidget* n_knob_new (GtkAdjustment* adjustment);
GtkWidget* n_knob_new_with_range (double value,
double lower,
double upper,
double step);
G_END_DECLS
#endif