changeset 344:9cf1a58b0234

Beginnings of the editor.
author Matti Hamalainen <ccr@tnsp.org>
date Mon, 15 Oct 2012 22:48:41 +0300
parents 6ecc7eb6d158
children cac13f180169
files Makefile Makefile.gen config.mak.in dmeditor.c gtktimeline.c gtktimeline.h gtkwaveform.c gtkwaveform.h
diffstat 8 files changed, 842 insertions(+), 10 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Mon Oct 15 21:27:24 2012 +0300
+++ b/Makefile	Mon Oct 15 22:48:41 2012 +0300
@@ -5,6 +5,8 @@
 SDL_LDFLAGS=`sdl-config --static-libs`
 TREMOR_CFLAGS=-I/usr/local/lib/
 TREMOR_LDFLAGS=/usr/local/lib/libvorbisidec.a /usr/lib/i386-linux-gnu/libogg.a
+GTK_CFLAGS=`pkg-config --cflags gtk+-2.0 glib-2.0 cairo`
+GTK_LDFLAGS=`pkg-config --libs gtk+-2.0 glib-2.0 cairo`
 
 RANLIB=ranlib
 
--- a/Makefile.gen	Mon Oct 15 21:27:24 2012 +0300
+++ b/Makefile.gen	Mon Oct 15 22:48:41 2012 +0300
@@ -11,6 +11,19 @@
 DM_CFLAGS += -I$(DMLIB)
 endif
 
+
+###
+### Editor
+###
+ifeq ($(DM_BUILD_EDITOR),yes)
+ifneq ($(DEMO_BIN),)
+BINARIES += ed_$(DEMO_BIN)
+endif
+endif
+
+EDITOR_OBJS = gtktimeline.o gtkwaveform.o dmeditor.o
+
+
 ###
 ### Form additional compilation defines based on settings
 ###
@@ -212,8 +225,7 @@
 DMLIB_A=$(OBJPATH)dmlib.a
 DMLIB_OBJS += dmfile.o dmlib.o dmlerp.o dmstring.o \
 	dmargs.o dmvecmat.o dmperlin.o dmimage.o \
-	dmwav.o	dmengine.o dmsimple.o \
-	dmtimeline.o dmtimelinew.o dmq3d.o
+	dmwav.o	dmengine.o dmtimeline.o dmtimelinew.o dmq3d.o
 
 ifeq ($(DM_BUILD_TESTS),yes)
 BINARIES += vecmattest fptest
@@ -226,6 +238,11 @@
 ###
 ### Generic rules
 ###
+$(OBJPATH)gtk%.o: $(DMLIB)gtk%.c $(DMLIB)gtk%.h
+	@echo " CC $+"
+	@$(CC) $(CFLAGS) -c -o $@ $< $(DM_CFLAGS) $(GTK_CFLAGS)
+
+
 $(OBJPATH)%.d: $(DMLIB)%.c
 	@echo > $@
 	@grep '#\s*include\s*\"' $< | sed 's/#\s*include\s\s*"\(.*\)"/\1/' | while read i; do if test -e "$$i"; then echo "$$i" >> $@; fi; done
@@ -294,10 +311,6 @@
 ###
 ### Tests and binaries
 ###
-$(BINPATH)%$(EXEEXT): $(OBJPATH)%.o $(DMLIB_A)
-	@echo " LINK $+"
-	@$(CC) -o $@ $(filter %.o %.a,$+) $(DM_LDFLAGS) $(SDL_LDFLAGS)
-
 $(BINPATH)vview$(EXEEXT): $(OBJPATH)vview.o $(DMLIB_A)
 	@echo " LINK $+"
 	@$(CC) -o $@ $(filter %.o %.a,$+) $(DM_LDFLAGS) $(SDL_LDFLAGS) -lSDL_ttf
@@ -350,6 +363,18 @@
 	@echo " LINK $+"
 	@$(CC) -o $@ $(filter %.o %.a,$+) $(DM_LDFLAGS) $(SDL_LDFLAGS)
 
+###
+### Editor targets
+###
+$(OBJPATH)dmeditor.o: $(DMLIB)dmeditor.c
+	@echo " CC $+"
+	@$(CC) $(CFLAGS) -c -o $@ $< $(DM_CFLAGS) $(GTK_CFLAGS)
+
+
+$(BINPATH)ed_$(DEMO_BIN)$(EXEEXT): $(OBJPATH)$(DEMO_BIN).o $(addprefix $(OBJPATH),$(EDITOR_OBJS)) $(addprefix $(OBJPATH),$(DEMO_OBJS)) $(DMLIB_A)
+	@echo " LINK $+"
+	@$(CC) -o $@ $(filter %.o %.a,$+) $(DM_LDFLAGS) $(SDL_LDFLAGS) $(GTK_LDFLAGS)
+
 
 ###
 ### Special targets
--- a/config.mak.in	Mon Oct 15 21:27:24 2012 +0300
+++ b/config.mak.in	Mon Oct 15 22:48:41 2012 +0300
@@ -1,10 +1,10 @@
 # Build engine tests and tools? (Please notice that what
 # actually gets built it also affected by other options)
-DM_BUILD_TESTS=no
+DM_BUILD_TESTS=yes
 DM_BUILD_TOOLS=yes
 
-# Enable OpenGL specifics (does not mean that OpenGL is automatically used, tho)
-DM_USE_OPENGL=no
+# Build Gtk+2 based demo editor?
+DM_BUILD_EDITOR=yes
 
 # Build with runtime asserts?
 DM_USE_ASSERTS=yes
@@ -41,7 +41,7 @@
 JSS_LIGHT=no
 
 # Build with extra debugging code? (disabled by JSS_LIGHT=yes)
-JSS_DEBUG=yes
+JSS_DEBUG=no
 
 
 ### Support loading of XM-format module files
@@ -74,3 +74,4 @@
 ### Locking/thread support
 ###
 JSS_SUP_THREADS=yes
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dmeditor.c	Mon Oct 15 22:48:41 2012 +0300
@@ -0,0 +1,283 @@
+#include <SDL.h>
+#include "dmengine.h"
+#include "gtktimeline.h"
+#include "gtkwaveform.h"
+
+
+static void set_value(GtkWidget * widget, gpointer data)
+{
+    GdkRegion *region;
+
+    GtkRange *range = (GtkRange *) widget;
+    GtkTimeline *timeline = (GtkTimeline *) data;
+
+    gint offs = gtk_range_get_value(range);
+
+    gtk_timeline_set_offs(timeline, offs * 1000);
+/*
+    region = gdk_drawable_get_clip_region(timeline->window);
+    gdk_window_invalidate_region(timeline->window, region, TRUE);
+    gdk_window_process_updates(timeline->window, TRUE);
+*/
+}
+
+
+static void engineAudioCallback(void *userdata, Uint8 * stream, int len)
+{
+    (void) userdata;
+
+    if (engine.paused)
+    {
+        memset(stream, 0, len);
+    }
+    else
+#ifdef DM_USE_JSS
+    {
+        if (engine.dev != NULL)
+            jvmRenderAudio(engine.dev, stream,
+                           len / jvmGetSampleSize(engine.dev));
+    }
+#endif
+#ifdef DM_USE_TREMOR
+    if (engine.audioPos + len >= engine.audio->rdataSize)
+    {
+        engine.exitFlag = TRUE;
+    }
+    else
+    {
+        memcpy(stream, engine.audio->rdata + engine.audioPos, len);
+        engine.audioPos += len;
+    }
+#endif
+}
+
+
+int engineOpenResources()
+{
+    int err;
+
+    if ((err =
+         dmres_init(engine.optPackFilename, engine.optDataPath,
+                    engine.optResFlags, engineClassifier)) != DMERR_OK)
+        dmError("Could not initialize resource manager: %d, %s.\n", err,
+                dmErrorStr(err));
+    return err;
+}
+
+
+int engineLoadResources()
+{
+    int err, loaded, total;
+    err = dmres_preload(TRUE, &loaded, &total);
+
+    while ((err = dmres_preload(FALSE, &loaded, &total)) == DMERR_PROGRESS)
+    {
+        // Show a nice progress bar while loading
+        if (total > 0 && (loaded % 2) == 0)
+        {
+/*
+            if ((err = engineShowProgress(loaded, total)) != DMERR_OK)
+                return err;
+*/
+        }
+    }
+}
+
+int engineReloadResources()
+{
+    int err;
+
+    dmres_close();
+    if ((err = engineOpenResources()) != DMERR_OK)
+        return err;
+    if ((err = engineLoadResources()) != DMERR_OK)
+        return err;
+
+    return DMERR_OK;
+}
+
+
+BOOL engineInitializeVideo()
+{
+    return TRUE;
+}
+
+
+int main(int argc, char **argv)
+{
+    int err;
+    BOOL initSDL = FALSE;
+
+    memset(&frame, 0, sizeof(frame));
+    memset(&engine, 0, sizeof(engine));
+
+    // Pre-initialization
+    if ((err = demoPreInit()) != DMERR_OK)
+        goto error_exit;
+
+    gtk_init(&argc, &argv);
+
+    // Initialize resource subsystem
+    dmPrint(1, "Initializing resources subsystem.\n");
+    if ((err = engineOpenResources()) != DMERR_OK)
+        goto error_exit;
+
+    // Initialize SDL components
+    dmPrint(1, "Initializing libSDL.\n");
+    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER) != 0)
+    {
+        dmError("Could not initialize SDL: %s\n", SDL_GetError());
+        goto error_exit;
+    }
+    initSDL = TRUE;
+
+    // Initialize audio parts
+    if (engine.optAfmt.freq == 0 && engine.optAfmt.channels == 0)
+    {
+        // Defaults, if none seem to be set
+        engine.optAfmt.freq = 44100;
+        engine.optAfmt.format = AUDIO_S16SYS;
+        engine.optAfmt.channels = 2;
+        engine.optAfmt.samples = engine.optAfmt.freq / 16;
+    }
+
+#ifdef DM_USE_JSS
+    jssInit();
+
+    switch (engine.optAfmt.format)
+    {
+        case AUDIO_S16SYS:
+            engine.jss_format = JSS_AUDIO_S16;
+            break;
+        case AUDIO_U16SYS:
+            engine.jss_format = JSS_AUDIO_U16;
+            break;
+        case AUDIO_S8:
+            engine.jss_format = JSS_AUDIO_S8;
+            break;
+        case AUDIO_U8:
+            engine.jss_format = JSS_AUDIO_U8;
+            break;
+    }
+
+    dmPrint(1, "Initializing miniJSS mixer with fmt=%d, chn=%d, freq=%d\n",
+            engine.jss_format, engine.optAfmt.channels, engine.optAfmt.freq);
+
+    if ((engine.dev =
+         jvmInit(engine.jss_format, engine.optAfmt.channels,
+                 engine.optAfmt.freq, JMIX_AUTO)) == NULL)
+    {
+        dmError("jvmInit() returned NULL, voi perkele.\n");
+        goto error_exit;
+    }
+
+    if ((engine.plr = jmpInit(engine.dev)) == NULL)
+    {
+        dmError("jmpInit() returned NULL\n");
+        goto error_exit;
+    }
+#endif
+
+    // Initialize SDL audio
+    dmPrint(1, "Trying to init SDL audio with: fmt=%d, chn=%d, freq=%d\n",
+            engine.optAfmt.format, engine.optAfmt.channels,
+            engine.optAfmt.freq);
+
+    engine.optAfmt.callback = engineAudioCallback;
+
+    if (SDL_OpenAudio(&engine.optAfmt, NULL) < 0)
+    {
+        dmError("Couldn't open SDL audio: %s\n", SDL_GetError());
+        goto error_exit;
+    }
+
+    // Initialize SDL video
+    if (engine.demoInitPreVideo != NULL &&
+        (err = engine.demoInitPreVideo()) != DMERR_OK)
+    {
+        dmError("demoInitPreVideo() failed, %d: %s\n", err, dmErrorStr(err));
+        goto error_exit;
+    }
+
+    if (!engineInitializeVideo())
+        goto error_exit;
+
+    if (engine.demoInitPostVideo != NULL &&
+        (err = engine.demoInitPostVideo()) != DMERR_OK)
+    {
+        dmError("demoInitPostVideo() failed, %d: %s\n", err, dmErrorStr(err));
+        goto error_exit;
+    }
+
+    // Load resources
+    dmPrint(1, "Loading resources, please wait...\n");
+    if ((err = engineLoadResources()) != DMERR_OK)
+    {
+        dmError("Error loading resources, %d: %s.\n", err, dmErrorStr(err));
+        goto error_exit;
+    }
+
+    // Final initializations
+    if ((err = engine.demoInit()) != DMERR_OK)
+        goto error_exit;
+
+
+    // Initialize GUI
+    GtkWidget *window;
+    GtkWidget *timeline;
+    GtkWidget *fixed;
+    GtkWidget *scale;
+
+    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+    gtk_window_set_title(GTK_WINDOW(window), "TIMELINE widget");
+    gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
+    gtk_window_set_default_size(GTK_WINDOW(window), 200, 180);
+    g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(gtk_main_quit), NULL);
+    fixed = gtk_fixed_new();
+    gtk_container_add(GTK_CONTAINER(window), fixed);
+    timeline = gtk_timeline_new();
+    gtk_fixed_put(GTK_FIXED(fixed), timeline, 30, 40);
+    scale = gtk_vscale_new_with_range(0.0, 100.0, 1.0);
+    gtk_range_set_inverted(GTK_RANGE(scale), TRUE);
+    gtk_scale_set_value_pos(GTK_SCALE(scale), GTK_POS_TOP);
+    gtk_widget_set_size_request(scale, 50, 120);
+    gtk_fixed_put(GTK_FIXED(fixed), scale, 130, 20);
+    g_signal_connect(G_OBJECT(scale), "value_changed", G_CALLBACK(set_value),(gpointer) timeline);
+
+    gtk_widget_show(timeline);
+    gtk_widget_show(fixed);
+    gtk_widget_show_all(window);
+
+    gtk_main();
+
+
+error_exit:
+
+    dmPrint(1, "Shutting down.\n");
+    SDL_ShowCursor(SDL_ENABLE);
+
+    if (engine.screen)
+        SDL_FreeSurface(engine.screen);
+
+    SDL_LockAudio();
+    SDL_PauseAudio(1);
+#ifdef DM_USE_JSS
+    jmpClose(engine.plr);
+    jvmClose(engine.dev);
+    jssClose();
+#endif
+    SDL_UnlockAudio();
+
+    dmres_close();
+
+    if (engine.demoShutdown != NULL)
+        engine.demoShutdown();
+
+    if (initSDL)
+        SDL_Quit();
+
+    if (engine.demoQuit != NULL)
+        engine.demoQuit();
+
+    return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gtktimeline.c	Mon Oct 15 22:48:41 2012 +0300
@@ -0,0 +1,212 @@
+#include "gtktimeline.h"
+
+
+static void gtk_timeline_class_init(GtkTimelineClass *klass);
+static void gtk_timeline_init(GtkTimeline *timeline);
+static void gtk_timeline_size_request(GtkWidget *widget, GtkRequisition *requisition);
+static void gtk_timeline_size_allocate(GtkWidget *widget, GtkAllocation *allocation);
+static void gtk_timeline_realize(GtkWidget *widget);
+static gboolean gtk_timeline_expose(GtkWidget *widget, GdkEventExpose *event);
+static void gtk_timeline_paint(GtkWidget *widget);
+static void gtk_timeline_destroy(GtkObject *object);
+
+
+GtkType gtk_timeline_get_type(void)
+{
+    static GtkType gtk_timeline_type = 0;
+
+    if (!gtk_timeline_type)
+    {
+        static const GtkTypeInfo gtk_timeline_info =
+        {
+            "GtkTimeline",
+            sizeof(GtkTimeline),
+            sizeof(GtkTimelineClass),
+            (GtkClassInitFunc) gtk_timeline_class_init,
+            (GtkObjectInitFunc) gtk_timeline_init,
+            NULL,
+            NULL,
+            (GtkClassInitFunc) NULL
+        };
+        gtk_timeline_type = gtk_type_unique(GTK_TYPE_WIDGET, &gtk_timeline_info);
+    }
+    return gtk_timeline_type;
+}
+
+
+void gtk_timeline_set_zoom(GtkTimeline *timeline, gfloat zoom)
+{
+    timeline->zoom = zoom;
+    gtk_timeline_paint(GTK_WIDGET(timeline));
+}
+
+
+void gtk_timeline_set_time(GtkTimeline *timeline, gint time)
+{
+    timeline->time = time;
+    gtk_timeline_paint(GTK_WIDGET(timeline));
+}
+
+
+void gtk_timeline_set_offs(GtkTimeline *timeline, gint offs)
+{
+    timeline->offs = offs;
+    gtk_timeline_paint(GTK_WIDGET(timeline));
+}
+
+
+GtkWidget * gtk_timeline_new()
+{
+    return GTK_WIDGET(gtk_type_new(gtk_timeline_get_type()));
+}
+
+
+static void gtk_timeline_class_init(GtkTimelineClass *klass)
+{
+    GtkWidgetClass *widget_class = (GtkWidgetClass *) klass;
+    GtkObjectClass *object_class = (GtkObjectClass *) klass;
+
+    widget_class->realize = gtk_timeline_realize;
+    widget_class->size_request = gtk_timeline_size_request;
+    widget_class->size_allocate = gtk_timeline_size_allocate;
+    widget_class->expose_event = gtk_timeline_expose;
+
+    object_class->destroy = gtk_timeline_destroy;
+}
+
+
+static void gtk_timeline_init(GtkTimeline *timeline)
+{
+    timeline->time = 0;
+    timeline->offs = 0;
+    timeline->zoom = 1.0f;
+    timeline->tl   = NULL;
+}
+
+
+static void gtk_timeline_size_request(GtkWidget *widget, GtkRequisition *requisition)
+{
+    g_return_if_fail(widget != NULL);
+    g_return_if_fail(GTK_IS_TIMELINE(widget));
+    g_return_if_fail(requisition != NULL);
+
+    requisition->width = 200;
+    requisition->height = 60;
+}
+
+
+static void gtk_timeline_size_allocate(GtkWidget *widget,
+    GtkAllocation *allocation)
+{
+    g_return_if_fail(widget != NULL);
+    g_return_if_fail(GTK_IS_TIMELINE(widget));
+    g_return_if_fail(allocation != NULL);
+
+    widget->allocation = *allocation;
+
+    if (GTK_WIDGET_REALIZED(widget))
+    {
+        gdk_window_move_resize(
+            widget->window,
+            allocation->x, allocation->y,
+            allocation->width, allocation->height);
+    }
+}
+
+
+static void gtk_timeline_realize(GtkWidget *widget)
+{
+    GdkWindowAttr attributes;
+    guint attributes_mask;
+
+    g_return_if_fail(widget != NULL);
+    g_return_if_fail(GTK_IS_TIMELINE(widget));
+
+    GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED);
+
+    attributes.window_type = GDK_WINDOW_CHILD;
+    attributes.x = widget->allocation.x;
+    attributes.y = widget->allocation.y;
+    attributes.width = 200;
+    attributes.height = 60;
+
+    attributes.wclass = GDK_INPUT_OUTPUT;
+    attributes.event_mask = gtk_widget_get_events(widget) | GDK_EXPOSURE_MASK;
+
+    attributes_mask = GDK_WA_X | GDK_WA_Y;
+
+    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_NORMAL);
+}
+
+
+static gboolean gtk_timeline_expose(GtkWidget *widget, GdkEventExpose *event)
+{
+    g_return_val_if_fail(widget != NULL, FALSE);
+    g_return_val_if_fail(GTK_IS_TIMELINE(widget), FALSE);
+    g_return_val_if_fail(event != NULL, FALSE);
+
+    gtk_timeline_paint(widget);
+
+    return FALSE;
+}
+
+
+static void gtk_timeline_paint(GtkWidget *widget)
+{
+#if 0
+    gint i;
+    cairo_t *cr;
+
+    cr = gdk_cairo_create(widget->window);
+
+    cairo_translate(cr, 0, 7);
+
+    cairo_set_source_rgb(cr, 0, 0, 0);
+    cairo_paint(cr);
+
+    gint pos = GTK_TIMELINE(widget)->sel;
+    gint rect = pos / 5;
+
+    cairo_set_source_rgb(cr, 0.2, 0.4, 0);
+
+    for (i = 1; i <= 20; i++)
+    {
+        if (i > 20 - rect)
+            cairo_set_source_rgb(cr, 0.6, 1.0, 0);
+        else
+            cairo_set_source_rgb(cr, 0.2, 0.4, 0);
+
+        cairo_rectangle(cr,  8, i*4, 30, 3);
+        cairo_rectangle(cr, 42, i*4, 30, 3);
+        cairo_fill(cr);
+    }
+
+    cairo_destroy(cr);
+#endif
+}
+
+
+static void gtk_timeline_destroy(GtkObject *object)
+{
+    GtkTimeline *timeline;
+    GtkTimelineClass *klass;
+
+    g_return_if_fail(object != NULL);
+    g_return_if_fail(GTK_IS_TIMELINE(object));
+
+    timeline = GTK_TIMELINE(object);
+
+    klass = gtk_type_class(gtk_widget_get_type());
+
+    if (GTK_OBJECT_CLASS(klass)->destroy)
+    {
+        (* GTK_OBJECT_CLASS(klass)->destroy) (object);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gtktimeline.h	Mon Oct 15 22:48:41 2012 +0300
@@ -0,0 +1,45 @@
+#ifndef GTKTIMELINE_H
+#define GTKTIMELINE_H
+
+#include <gtk/gtk.h>
+#include <cairo.h>
+
+#include "dmtimeline.h"
+
+G_BEGIN_DECLS
+
+
+#define GTK_TIMELINE(obj) GTK_CHECK_CAST(obj, gtk_timeline_get_type (), GtkTimeline)
+#define GTK_TIMELINE_CLASS(klass) GTK_CHECK_CLASS_CAST(klass, gtk_timeline_get_type(), GtkTimelineClass)
+#define GTK_IS_TIMELINE(obj) GTK_CHECK_TYPE(obj, gtk_timeline_get_type())
+
+
+typedef struct _GtkTimeline GtkTimeline;
+typedef struct _GtkTimelineClass GtkTimelineClass;
+
+
+struct _GtkTimeline
+{
+    GtkWidget widget;
+    DMTimeline *tl;
+    gint time, offs;
+    gfloat zoom;
+};
+
+struct _GtkTimelineClass
+{
+    GtkWidgetClass parent_class;
+};
+
+
+GtkType      gtk_timeline_get_type(void);
+void         gtk_timeline_set_timeline(GtkTimeline *timeline, DMTimeline *tl);
+void         gtk_timeline_set_zoom(GtkTimeline *timeline, gfloat zoom);
+void         gtk_timeline_set_time(GtkTimeline *timeline, gint time);
+void         gtk_timeline_set_offs(GtkTimeline *timeline, gint offs);
+GtkWidget *  gtk_timeline_new();
+
+
+G_END_DECLS
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gtkwaveform.c	Mon Oct 15 22:48:41 2012 +0300
@@ -0,0 +1,221 @@
+#include "gtkwaveform.h"
+
+
+static void gtk_waveform_class_init(GtkWaveformClass *klass);
+static void gtk_waveform_init(GtkWaveform *waveform);
+static void gtk_waveform_size_request(GtkWidget *widget, GtkRequisition *requisition);
+static void gtk_waveform_size_allocate(GtkWidget *widget, GtkAllocation *allocation);
+static void gtk_waveform_realize(GtkWidget *widget);
+static gboolean gtk_waveform_expose(GtkWidget *widget, GdkEventExpose *event);
+static void gtk_waveform_paint(GtkWidget *widget);
+static void gtk_waveform_destroy(GtkObject *object);
+
+
+GtkType gtk_waveform_get_type(void)
+{
+    static GtkType gtk_waveform_type = 0;
+
+    if (!gtk_waveform_type)
+    {
+        static const GtkTypeInfo gtk_waveform_info =
+        {
+            "GtkWaveform",
+            sizeof(GtkWaveform),
+            sizeof(GtkWaveformClass),
+            (GtkClassInitFunc) gtk_waveform_class_init,
+            (GtkObjectInitFunc) gtk_waveform_init,
+            NULL,
+            NULL,
+            (GtkClassInitFunc) NULL
+        };
+        gtk_waveform_type = gtk_type_unique(GTK_TYPE_WIDGET, &gtk_waveform_info);
+    }
+    return gtk_waveform_type;
+}
+
+
+void gtk_waveform_set_wave_data(GtkWaveform *waveform, gint16 *data, gint len, gint freq)
+{
+    waveform->data = data;
+    waveform->len  = len;
+    waveform->freq = freq;
+    gtk_waveform_paint(GTK_WIDGET(waveform));
+}
+
+
+void gtk_waveform_set_zoom(GtkWaveform *waveform, gfloat zoom)
+{
+    waveform->zoom = zoom;
+    gtk_waveform_paint(GTK_WIDGET(waveform));
+}
+
+
+void gtk_waveform_set_time(GtkWaveform *waveform, gint time)
+{
+    waveform->time = time;
+    gtk_waveform_paint(GTK_WIDGET(waveform));
+}
+
+
+void gtk_waveform_set_offs(GtkWaveform *waveform, gint offs)
+{
+    waveform->offs = offs;
+    gtk_waveform_paint(GTK_WIDGET(waveform));
+}
+
+
+GtkWidget * gtk_waveform_new()
+{
+    return GTK_WIDGET(gtk_type_new(gtk_waveform_get_type()));
+}
+
+
+static void gtk_waveform_class_init(GtkWaveformClass *klass)
+{
+    GtkWidgetClass *widget_class = (GtkWidgetClass *) klass;
+    GtkObjectClass *object_class = (GtkObjectClass *) klass;
+
+    widget_class->realize = gtk_waveform_realize;
+    widget_class->size_request = gtk_waveform_size_request;
+    widget_class->size_allocate = gtk_waveform_size_allocate;
+    widget_class->expose_event = gtk_waveform_expose;
+
+    object_class->destroy = gtk_waveform_destroy;
+}
+
+
+static void gtk_waveform_init(GtkWaveform *waveform)
+{
+    waveform->time = 0;
+    waveform->offs = 0;
+    waveform->zoom = 1.0f;
+    waveform->data = NULL;
+    waveform->freq = 1;
+}
+
+
+static void gtk_waveform_size_request(GtkWidget *widget, GtkRequisition *requisition)
+{
+    g_return_if_fail(widget != NULL);
+    g_return_if_fail(GTK_IS_WAVEFORM(widget));
+    g_return_if_fail(requisition != NULL);
+
+    requisition->width = 200;
+    requisition->height = 60;
+}
+
+
+static void gtk_waveform_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
+{
+    g_return_if_fail(widget != NULL);
+    g_return_if_fail(GTK_IS_WAVEFORM(widget));
+    g_return_if_fail(allocation != NULL);
+
+    widget->allocation = *allocation;
+
+    if (GTK_WIDGET_REALIZED(widget))
+    {
+        gdk_window_move_resize(
+            widget->window,
+            allocation->x, allocation->y,
+            allocation->width, allocation->height);
+    }
+}
+
+
+static void gtk_waveform_realize(GtkWidget *widget)
+{
+    GdkWindowAttr attributes;
+    guint attributes_mask;
+
+    g_return_if_fail(widget != NULL);
+    g_return_if_fail(GTK_IS_WAVEFORM(widget));
+
+    GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED);
+
+    attributes.window_type = GDK_WINDOW_CHILD;
+    attributes.x = widget->allocation.x;
+    attributes.y = widget->allocation.y;
+    attributes.width = 80;
+    attributes.height = 100;
+
+    attributes.wclass = GDK_INPUT_OUTPUT;
+    attributes.event_mask = gtk_widget_get_events(widget) | GDK_EXPOSURE_MASK;
+
+    attributes_mask = GDK_WA_X | GDK_WA_Y;
+
+    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_NORMAL);
+}
+
+
+static gboolean gtk_waveform_expose(GtkWidget *widget, GdkEventExpose *event)
+{
+    g_return_val_if_fail(widget != NULL, FALSE);
+    g_return_val_if_fail(GTK_IS_WAVEFORM(widget), FALSE);
+    g_return_val_if_fail(event != NULL, FALSE);
+
+    gtk_waveform_paint(widget);
+
+    return FALSE;
+}
+
+
+static void gtk_waveform_paint(GtkWidget *widget)
+{
+#if 0
+    gint i;
+    cairo_t *cr;
+
+    cr = gdk_cairo_create(widget->window);
+
+    cairo_translate(cr, 0, 7);
+
+    cairo_set_source_rgb(cr, 0, 0, 0);
+    cairo_paint(cr);
+
+    gint pos = GTK_WAVEFORM(widget)->sel;
+    gint rect = pos / 5;
+
+    cairo_set_source_rgb(cr, 0.2, 0.4, 0);
+
+    for (i = 1; i <= 20; i++)
+    {
+        if (i > 20 - rect)
+            cairo_set_source_rgb(cr, 0.6, 1.0, 0);
+        else
+            cairo_set_source_rgb(cr, 0.2, 0.4, 0);
+
+        cairo_rectangle(cr,  8, i*4, 30, 3);
+        cairo_rectangle(cr, 42, i*4, 30, 3);
+        cairo_fill(cr);
+    }
+
+    cairo_destroy(cr);
+#endif
+}
+
+
+static void gtk_waveform_destroy(GtkObject *object)
+{
+    GtkWaveform *waveform;
+    GtkWaveformClass *klass;
+
+    g_return_if_fail(object != NULL);
+    g_return_if_fail(GTK_IS_WAVEFORM(object));
+
+    waveform = GTK_WAVEFORM(object);
+
+    klass = gtk_type_class(gtk_widget_get_type());
+
+    if (GTK_OBJECT_CLASS(klass)->destroy)
+    {
+        (* GTK_OBJECT_CLASS(klass)->destroy) (object);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gtkwaveform.h	Mon Oct 15 22:48:41 2012 +0300
@@ -0,0 +1,43 @@
+#ifndef GTKWAVEFORM_H
+#define GTKWAVEFORM_H
+
+#include <gtk/gtk.h>
+#include <cairo.h>
+
+G_BEGIN_DECLS
+
+
+#define GTK_WAVEFORM(obj) GTK_CHECK_CAST(obj, gtk_waveform_get_type (), GtkWaveform)
+#define GTK_WAVEFORM_CLASS(klass) GTK_CHECK_CLASS_CAST(klass, gtk_waveform_get_type(), GtkWaveformClass)
+#define GTK_IS_WAVEFORM(obj) GTK_CHECK_TYPE(obj, gtk_waveform_get_type())
+
+
+typedef struct _GtkWaveform GtkWaveform;
+typedef struct _GtkWaveformClass GtkWaveformClass;
+
+
+struct _GtkWaveform
+{
+    GtkWidget widget;
+    gint16 *data;
+    gint len, time, offs, freq;
+    gfloat zoom;
+};
+
+struct _GtkWaveformClass
+{
+    GtkWidgetClass parent_class;
+};
+
+
+GtkType      gtk_waveform_get_type(void);
+void         gtk_waveform_set_wave_data(GtkWaveform *waveform, gint16 *data, gint len, gint freq);
+void         gtk_waveform_set_zoom(GtkWaveform *waveform, gfloat zoom);
+void         gtk_waveform_set_time(GtkWaveform *waveform, gint time);
+void         gtk_waveform_set_offs(GtkWaveform *waveform, gint offs);
+GtkWidget *  gtk_waveform_new();
+
+
+G_END_DECLS
+
+#endif