summaryrefslogtreecommitdiffstats
path: root/bin
diff options
context:
space:
mode:
Diffstat (limited to 'bin')
-rw-r--r--bin/CMakeLists.txt10
-rw-r--r--bin/common/ring-buffer.c70
-rw-r--r--bin/common/ring-buffer.h29
-rw-r--r--bin/gui/CMakeLists.txt41
-rw-r--r--bin/gui/config.h.in1
-rw-r--r--bin/gui/control.c662
-rw-r--r--bin/gui/control.glade635
-rw-r--r--bin/gui/egg-histogram-view.c524
-rw-r--r--bin/gui/egg-histogram-view.h66
-rw-r--r--bin/gui/egg-property-cell-renderer.c594
-rw-r--r--bin/gui/egg-property-cell-renderer.h55
-rw-r--r--bin/gui/egg-property-tree-view.c113
-rw-r--r--bin/gui/egg-property-tree-view.h54
-rw-r--r--bin/tools/CMakeLists.txt40
-rw-r--r--bin/tools/benchmark.c272
-rw-r--r--bin/tools/config.h.in1
-rw-r--r--bin/tools/gen-doc.c224
-rw-r--r--bin/tools/grab.c292
18 files changed, 3683 insertions, 0 deletions
diff --git a/bin/CMakeLists.txt b/bin/CMakeLists.txt
new file mode 100644
index 0000000..b8d065b
--- /dev/null
+++ b/bin/CMakeLists.txt
@@ -0,0 +1,10 @@
+cmake_minimum_required(VERSION 2.8)
+
+add_library(ringbuffer
+ ${CMAKE_CURRENT_SOURCE_DIR}/common/ring-buffer.c)
+
+include_directories(${CMAKE_CURRENT_SOURCE_DIR}/common)
+
+add_subdirectory(gui)
+add_subdirectory(tools)
+
diff --git a/bin/common/ring-buffer.c b/bin/common/ring-buffer.c
new file mode 100644
index 0000000..039024f
--- /dev/null
+++ b/bin/common/ring-buffer.c
@@ -0,0 +1,70 @@
+
+#include <math.h>
+#include "ring-buffer.h"
+
+RingBuffer *
+ring_buffer_new (gsize block_size,
+ gsize n_blocks)
+{
+ RingBuffer *buffer;
+
+ buffer = g_new0 (RingBuffer, 1);
+ buffer->block_size = block_size;
+ buffer->n_blocks_total = n_blocks;
+ buffer->n_blocks_used = 0;
+ buffer->current_index = 0;
+ buffer->data = g_malloc0_n (n_blocks, block_size);
+
+ return buffer;
+}
+
+void
+ring_buffer_free (RingBuffer *buffer)
+{
+ g_free (buffer->data);
+ g_free (buffer);
+}
+
+void
+ring_buffer_reset (RingBuffer *buffer)
+{
+ buffer->n_blocks_used = 0;
+ buffer->current_index = 0;
+}
+
+gsize
+ring_buffer_get_block_size (RingBuffer *buffer)
+{
+ return buffer->block_size;
+}
+
+gpointer
+ring_buffer_get_current_pointer (RingBuffer *buffer)
+{
+ return buffer->data + (buffer->current_index % buffer->n_blocks_total) * buffer->block_size;
+}
+
+gpointer
+ring_buffer_get_pointer (RingBuffer *buffer,
+ guint index)
+{
+ g_assert (index < buffer->n_blocks_total);
+ return buffer->data + ((buffer->current_index - buffer->n_blocks_used + index) % buffer->n_blocks_total) * buffer->block_size;
+}
+
+guint
+ring_buffer_get_num_blocks (RingBuffer *buffer)
+{
+ return buffer->n_blocks_used;
+}
+
+void
+ring_buffer_proceed (RingBuffer *buffer)
+{
+ buffer->current_index++;
+
+ if (buffer->n_blocks_used < buffer->n_blocks_total)
+ buffer->n_blocks_used++;
+ else
+ buffer->current_index = buffer->current_index % buffer->n_blocks_total;
+}
diff --git a/bin/common/ring-buffer.h b/bin/common/ring-buffer.h
new file mode 100644
index 0000000..a3758cb
--- /dev/null
+++ b/bin/common/ring-buffer.h
@@ -0,0 +1,29 @@
+#ifndef RING_BUFFER_H
+#define RING_BUFFER_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef struct {
+ guchar *data;
+ gsize block_size;
+ guint n_blocks_total;
+ guint n_blocks_used;
+ guint current_index;
+} RingBuffer;
+
+RingBuffer * ring_buffer_new (gsize block_size,
+ gsize n_blocks);
+void ring_buffer_free (RingBuffer *buffer);
+void ring_buffer_reset (RingBuffer *buffer);
+gsize ring_buffer_get_block_size (RingBuffer *buffer);
+gpointer ring_buffer_get_current_pointer (RingBuffer *buffer);
+gpointer ring_buffer_get_pointer (RingBuffer *buffer,
+ guint index);
+guint ring_buffer_get_num_blocks (RingBuffer *buffer);
+void ring_buffer_proceed (RingBuffer *buffer);
+
+G_END_DECLS
+
+#endif
diff --git a/bin/gui/CMakeLists.txt b/bin/gui/CMakeLists.txt
new file mode 100644
index 0000000..f64bd02
--- /dev/null
+++ b/bin/gui/CMakeLists.txt
@@ -0,0 +1,41 @@
+cmake_minimum_required(VERSION 2.8)
+
+add_definitions("--std=c99 -Wall")
+
+# --- Find packages and libraries ---------------------------------------------
+find_package(PkgConfig)
+
+pkg_check_modules(GTK2 gtk+-2.0>=2.22)
+pkg_check_modules(GTHREAD2 gthread-2.0)
+
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in
+ ${CMAKE_CURRENT_BINARY_DIR}/config.h)
+
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/control.glade
+ ${CMAKE_CURRENT_BINARY_DIR})
+
+# --- Build targets -----------------------------------------------------------
+include_directories(${CMAKE_CURRENT_BINARY_DIR})
+
+if (GTK2_FOUND)
+ include_directories(${GTK2_INCLUDE_DIRS})
+
+ add_executable(control
+ control.c
+ egg-property-cell-renderer.c
+ egg-property-tree-view.c
+ egg-histogram-view.c)
+
+ target_link_libraries(control
+ m
+ uca
+ ringbuffer
+ ${GTK2_LIBRARIES}
+ ${GTHREAD2_LIBRARIES})
+
+ install(TARGETS control
+ RUNTIME DESTINATION bin)
+
+ install(FILES control.glade
+ DESTINATION share/libuca)
+endif()
diff --git a/bin/gui/config.h.in b/bin/gui/config.h.in
new file mode 100644
index 0000000..e7de9c6
--- /dev/null
+++ b/bin/gui/config.h.in
@@ -0,0 +1 @@
+#define CONTROL_GLADE_PATH "${CMAKE_INSTALL_PREFIX}/share/libuca/control.glade"
diff --git a/bin/gui/control.c b/bin/gui/control.c
new file mode 100644
index 0000000..c177f07
--- /dev/null
+++ b/bin/gui/control.c
@@ -0,0 +1,662 @@
+/* Copyright (C) 2011, 2012 Matthias Vogelgesang <matthias.vogelgesang@kit.edu>
+ (Karlsruhe Institute of Technology)
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by the
+ Free Software Foundation; either version 2.1 of the License, or (at your
+ option) any later version.
+
+ This library 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 Lesser General Public License for more
+ details.
+
+ You should have received a copy of the GNU Lesser General Public License along
+ with this library; if not, write to the Free Software Foundation, Inc., 51
+ Franklin St, Fifth Floor, Boston, MA 02110, USA */
+
+#include <glib/gprintf.h>
+#include <gtk/gtk.h>
+#include <gdk/gdk.h>
+#include <gdk/gdkkeysyms.h>
+#include <math.h>
+
+#include "config.h"
+#include "ring-buffer.h"
+#include "uca-camera.h"
+#include "uca-plugin-manager.h"
+#include "egg-property-tree-view.h"
+#include "egg-histogram-view.h"
+
+typedef enum {
+ IDLE,
+ RUNNING,
+ RECORDING
+} State;
+
+typedef struct {
+ UcaCamera *camera;
+ GtkWidget *main_window;
+ GdkPixbuf *pixbuf;
+ GtkWidget *image;
+ GtkWidget *start_button;
+ GtkWidget *stop_button;
+ GtkWidget *record_button;
+ GtkWidget *download_button;
+ GtkComboBox *zoom_box;
+
+ GtkDialog *download_dialog;
+ GtkProgressBar *download_progressbar;
+ GtkWidget *download_close_button;
+ GtkAdjustment *download_adjustment;
+
+ GtkWidget *histogram_view;
+ GtkToggleButton *histogram_button;
+ GtkAdjustment *frame_slider;
+
+ RingBuffer *buffer;
+ guchar *pixels;
+ gint display_width, display_height;
+ gdouble zoom_factor;
+ State state;
+ gboolean data_in_camram;
+
+ gint timestamp;
+ gint width, height;
+ gint pixel_size;
+} ThreadData;
+
+static UcaPluginManager *plugin_manager;
+static gsize mem_size = 2048;
+
+static void
+convert_grayscale_to_rgb (ThreadData *data, gpointer buffer)
+{
+ gdouble min;
+ gdouble max;
+ gdouble factor;
+ guint8 *output;
+ gint i = 0;
+ gint stride;
+
+ egg_histogram_get_visible_range (EGG_HISTOGRAM_VIEW (data->histogram_view), &min, &max);
+ factor = 255.0 / (max - min);
+ output = data->pixels;
+ stride = (gint) 1 / data->zoom_factor;
+
+ if (data->pixel_size == 1) {
+ guint8 *input = (guint8 *) buffer;
+
+ for (gint y = 0; y < data->display_height; y++) {
+ gint offset = y * stride * data->width;
+
+ for (gint x = 0; x < data->display_width; x++, offset += stride) {
+ gdouble dval = (input[offset] - min) * factor;
+ guchar val = (guchar) CLAMP(dval, 0.0, 255.0);
+
+ output[i++] = val;
+ output[i++] = val;
+ output[i++] = val;
+ }
+ }
+ }
+ else if (data->pixel_size == 2) {
+ guint16 *input = (guint16 *) buffer;
+
+ for (gint y = 0; y < data->display_height; y++) {
+ gint offset = y * stride * data->width;
+
+ for (gint x = 0; x < data->display_width; x++, offset += stride) {
+ gdouble dval = (input[offset] - min) * factor;
+ guchar val = (guchar) CLAMP(dval, 0.0, 255.0);
+
+ output[i++] = val;
+ output[i++] = val;
+ output[i++] = val;
+ }
+ }
+ }
+}
+
+static void
+update_pixbuf (ThreadData *data)
+{
+ gdk_flush ();
+ gtk_image_set_from_pixbuf (GTK_IMAGE (data->image), data->pixbuf);
+ gtk_widget_queue_draw_area (data->image, 0, 0, data->display_width, data->display_height);
+
+ if (gtk_toggle_button_get_active (data->histogram_button))
+ gtk_widget_queue_draw (data->histogram_view);
+}
+
+static void
+update_pixbuf_dimensions (ThreadData *data)
+{
+ if (data->pixbuf != NULL)
+ g_object_unref (data->pixbuf);
+
+ data->display_width = (gint) data->width * data->zoom_factor;
+ data->display_height = (gint) data->height * data->zoom_factor;
+ data->pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, data->display_width, data->display_height);
+ data->pixels = gdk_pixbuf_get_pixels (data->pixbuf);
+ gtk_image_set_from_pixbuf (GTK_IMAGE (data->image), data->pixbuf);
+}
+
+static void
+print_and_free_error (GError **error)
+{
+ g_printerr ("%s\n", (*error)->message);
+ g_error_free (*error);
+ *error = NULL;
+}
+
+static gpointer
+preview_frames (void *args)
+{
+ ThreadData *data = (ThreadData *) args;
+ gint counter = 0;
+ GError *error = NULL;;
+
+ while (data->state == RUNNING) {
+ gpointer buffer;
+
+ buffer = ring_buffer_get_current_pointer (data->buffer);
+ uca_camera_trigger (data->camera, &error);
+ uca_camera_grab (data->camera, buffer, &error);
+
+ if (error == NULL) {
+ convert_grayscale_to_rgb (data, buffer);
+
+ gdk_threads_enter ();
+ update_pixbuf (data);
+ gdk_threads_leave ();
+
+ counter++;
+ }
+ else
+ print_and_free_error (&error);
+ }
+ return NULL;
+}
+
+static gpointer
+record_frames (gpointer args)
+{
+ ThreadData *data;
+ gpointer buffer;
+ guint n_frames = 0;
+ GError *error = NULL;
+
+ data = (ThreadData *) args;
+ ring_buffer_reset (data->buffer);
+
+ while (data->state == RECORDING) {
+ buffer = ring_buffer_get_current_pointer (data->buffer);
+ uca_camera_grab (data->camera, buffer, NULL);
+
+ if (error == NULL) {
+ ring_buffer_proceed (data->buffer);
+ n_frames++;
+ }
+ else
+ print_and_free_error (&error);
+ }
+
+ n_frames = ring_buffer_get_num_blocks (data->buffer);
+
+ gdk_threads_enter ();
+ gtk_adjustment_set_upper (data->frame_slider, n_frames - 1);
+ gtk_adjustment_set_value (data->frame_slider, n_frames - 1);
+ gdk_threads_leave ();
+
+ return NULL;
+}
+
+gboolean
+on_delete_event (GtkWidget *widget, GdkEvent *event, gpointer data)
+{
+ return FALSE;
+}
+
+void
+on_destroy (GtkWidget *widget, ThreadData *data)
+{
+ data->state = IDLE;
+ g_object_unref (data->camera);
+ ring_buffer_free (data->buffer);
+
+ gtk_main_quit ();
+}
+
+static void
+set_tool_button_state (ThreadData *data)
+{
+ gtk_widget_set_sensitive (data->start_button,
+ data->state == IDLE);
+ gtk_widget_set_sensitive (data->stop_button,
+ data->state == RUNNING || data->state == RECORDING);
+ gtk_widget_set_sensitive (data->record_button,
+ data->state == IDLE);
+ gtk_widget_set_sensitive (data->download_button,
+ data->data_in_camram);
+ gtk_widget_set_sensitive (GTK_WIDGET (data->zoom_box),
+ data->state == IDLE);
+}
+
+static void
+update_current_frame (ThreadData *data)
+{
+ gpointer buffer;
+ gint index;
+
+ index = (gint) gtk_adjustment_get_value (data->frame_slider);
+ buffer = ring_buffer_get_pointer (data->buffer, index);
+ convert_grayscale_to_rgb (data, buffer);
+ update_pixbuf (data);
+}
+
+static void
+on_frame_slider_changed (GtkAdjustment *adjustment, ThreadData *data)
+{
+ if (data->state == IDLE)
+ update_current_frame (data);
+}
+
+static void
+on_start_button_clicked (GtkWidget *widget, ThreadData *data)
+{
+ GError *error = NULL;
+
+ uca_camera_start_recording (data->camera, &error);
+
+ if (error != NULL) {
+ g_printerr ("Failed to start recording: %s\n", error->message);
+ return;
+ }
+
+ data->state = RUNNING;
+ set_tool_button_state (data);
+
+ if (!g_thread_create (preview_frames, data, FALSE, &error)) {
+ g_printerr ("Failed to create thread: %s\n", error->message);
+ data->state = IDLE;
+ set_tool_button_state (data);
+ }
+}
+
+static void
+on_stop_button_clicked (GtkWidget *widget, ThreadData *data)
+{
+ GError *error = NULL;
+
+ g_object_get (data->camera, "has-camram-recording", &data->data_in_camram, NULL);
+ data->state = IDLE;
+ set_tool_button_state (data);
+ uca_camera_stop_recording (data->camera, &error);
+
+ if (error != NULL)
+ g_printerr ("Failed to stop: %s\n", error->message);
+
+}
+
+static void
+on_record_button_clicked (GtkWidget *widget, ThreadData *data)
+{
+ GError *error = NULL;
+
+ uca_camera_start_recording (data->camera, &error);
+
+ if (error != NULL) {
+ g_printerr ("Failed to start recording: %s\n", error->message);
+ }
+
+ data->timestamp = (int) time (0);
+ data->state = RECORDING;
+ set_tool_button_state (data);
+
+ if (!g_thread_create (record_frames, data, FALSE, &error)) {
+ g_printerr ("Failed to create thread: %s\n", error->message);
+ data->state = IDLE;
+ set_tool_button_state (data);
+ }
+}
+
+static gpointer
+download_frames (ThreadData *data)
+{
+ gpointer buffer;
+ guint n_frames;
+ guint current_frame = 1;
+ GError *error = NULL;
+
+ g_object_get (data->camera, "recorded-frames", &n_frames, NULL);
+ gdk_threads_enter ();
+ gtk_widget_set_sensitive (data->download_close_button, FALSE);
+ gtk_adjustment_set_upper (data->download_adjustment, n_frames);
+ gdk_threads_leave ();
+
+ uca_camera_start_readout (data->camera, &error);
+
+ if (error != NULL) {
+ g_printerr ("Failed to start read out of camera memory: %s\n", error->message);
+ return NULL;
+ }
+
+ ring_buffer_reset (data->buffer);
+
+ while (error == NULL) {
+ buffer = ring_buffer_get_current_pointer (data->buffer);
+ uca_camera_grab (data->camera, buffer, &error);
+ ring_buffer_proceed (data->buffer);
+ gdk_threads_enter ();
+ gtk_adjustment_set_value (data->download_adjustment, current_frame++);
+ gdk_threads_leave ();
+ }
+
+ if (error->code == UCA_CAMERA_ERROR_END_OF_STREAM) {
+ guint n_frames = ring_buffer_get_num_blocks (data->buffer);
+
+ gtk_adjustment_set_upper (data->frame_slider, n_frames - 1);
+ gtk_adjustment_set_value (data->frame_slider, n_frames - 1);
+ }
+ else
+ g_printerr ("Error while reading out frames: %s\n", error->message);
+
+ g_error_free (error);
+ error = NULL;
+
+ uca_camera_stop_readout (data->camera, &error);
+
+ if (error != NULL)
+ g_printerr ("Failed to stop reading out of camera memory: %s\n", error->message);
+
+ gdk_threads_enter ();
+ gtk_widget_set_sensitive (data->download_close_button, TRUE);
+ gdk_threads_leave ();
+
+ return NULL;
+}
+
+static void
+on_download_button_clicked (GtkWidget *widget, ThreadData *data)
+{
+ GError *error = NULL;
+
+ if (!g_thread_create ((GThreadFunc) download_frames, data, FALSE, &error)) {
+ g_printerr ("Failed to create thread: %s\n", error->message);
+ }
+
+ gtk_widget_set_sensitive (data->main_window, FALSE);
+ gtk_window_set_modal (GTK_WINDOW (data->download_dialog), TRUE);
+ gtk_dialog_run (data->download_dialog);
+ gtk_widget_hide (GTK_WIDGET (data->download_dialog));
+ gtk_window_set_modal (GTK_WINDOW (data->download_dialog), FALSE);
+ gtk_widget_set_sensitive (data->main_window, TRUE);
+ gtk_window_set_modal (GTK_WINDOW (data->download_dialog), TRUE);
+}
+
+static void
+on_histogram_changed (EggHistogramView *view, ThreadData *data)
+{
+ if (data->state == IDLE)
+ update_current_frame (data);
+}
+
+static void
+on_zoom_changed (GtkComboBox *widget, ThreadData *data)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gdouble factor;
+
+ enum {
+ DISPLAY_COLUMN,
+ FACTOR_COLUMN
+ };
+
+ model = gtk_combo_box_get_model (widget);
+ gtk_combo_box_get_active_iter (widget, &iter);
+ gtk_tree_model_get (model, &iter, FACTOR_COLUMN, &factor, -1);
+
+ data->zoom_factor = factor;
+ update_pixbuf_dimensions (data);
+}
+
+static void
+on_roi_width_changed (GObject *object, GParamSpec *pspec, ThreadData *data)
+{
+ g_object_get (object, "roi-width", &data->width, NULL);
+ update_pixbuf_dimensions (data);
+}
+
+static void
+on_roi_height_changed (GObject *object, GParamSpec *pspec, ThreadData *data)
+{
+ g_object_get (object, "roi-height", &data->height, NULL);
+ update_pixbuf_dimensions (data);
+}
+
+static void
+create_main_window (GtkBuilder *builder, const gchar* camera_name)
+{
+ static ThreadData td;
+ UcaCamera *camera;
+ GtkWidget *window;
+ GtkWidget *image;
+ GtkWidget *histogram_view;
+ GtkWidget *property_tree_view;
+ GdkPixbuf *pixbuf;
+ GtkBox *histogram_box;
+ GtkContainer *property_window;
+ GtkAdjustment *max_bin_adjustment;
+ RingBuffer *ring_buffer;
+ gsize image_size;
+ guint n_frames;
+ guint bits_per_sample;
+ guint pixel_size;
+ guint width, height;
+ GError *error = NULL;
+
+ camera = uca_plugin_manager_get_camera (plugin_manager, camera_name, &error, NULL);
+
+ if ((camera == NULL) || (error != NULL)) {
+ g_error ("%s\n", error->message);
+ gtk_main_quit ();
+ }
+
+ g_object_get (camera,
+ "roi-width", &width,
+ "roi-height", &height,
+ "sensor-bitdepth", &bits_per_sample,
+ NULL);
+
+ g_signal_connect (camera, "notify::roi-width", (GCallback) on_roi_width_changed, &td);
+ g_signal_connect (camera, "notify::roi-height", (GCallback) on_roi_height_changed, &td);
+
+ histogram_view = egg_histogram_view_new ();
+ property_tree_view = egg_property_tree_view_new (G_OBJECT (camera));
+ property_window = GTK_CONTAINER (gtk_builder_get_object (builder, "property-window"));
+ image = GTK_WIDGET (gtk_builder_get_object (builder, "image"));
+ histogram_box = GTK_BOX (gtk_builder_get_object (builder, "histogram-box"));
+ window = GTK_WIDGET (gtk_builder_get_object (builder, "window"));
+ max_bin_adjustment = GTK_ADJUSTMENT (gtk_builder_get_object (builder, "max-bin-value-adjustment"));
+
+ td.zoom_box = GTK_COMBO_BOX (gtk_builder_get_object (builder, "zoom-box"));
+ td.start_button = GTK_WIDGET (gtk_builder_get_object (builder, "start-button"));
+ td.stop_button = GTK_WIDGET (gtk_builder_get_object (builder, "stop-button"));
+ td.record_button = GTK_WIDGET (gtk_builder_get_object (builder, "record-button"));
+ td.download_button = GTK_WIDGET (gtk_builder_get_object (builder, "download-button"));
+ td.histogram_button = GTK_TOGGLE_BUTTON (gtk_builder_get_object (builder, "histogram-checkbutton"));
+ td.frame_slider = GTK_ADJUSTMENT (gtk_builder_get_object (builder, "frames-adjustment"));
+
+ td.download_dialog = GTK_DIALOG (gtk_builder_get_object (builder, "download-dialog"));
+ td.download_adjustment = GTK_ADJUSTMENT (gtk_builder_get_object (builder, "download-adjustment"));
+ td.download_close_button = GTK_WIDGET (gtk_builder_get_object (builder, "download-close-button"));
+
+ /* Set initial data */
+ pixel_size = bits_per_sample > 8 ? 2 : 1;
+ image_size = pixel_size * width * height;
+ n_frames = mem_size * 1024 * 1024 / image_size;
+ ring_buffer = ring_buffer_new (image_size, n_frames);
+
+ egg_histogram_view_set_data (EGG_HISTOGRAM_VIEW (histogram_view),
+ ring_buffer_get_current_pointer (ring_buffer),
+ width * height, bits_per_sample, 256);
+
+ pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, width, height);
+ gtk_image_set_from_pixbuf (GTK_IMAGE (image), pixbuf);
+
+ gtk_adjustment_set_value (max_bin_adjustment, pow (2, bits_per_sample) - 1);
+
+ g_message ("Allocated memory for %d frames", n_frames);
+
+ td.pixel_size = pixel_size;
+ td.image = image;
+ td.pixbuf = NULL;
+ td.pixels = NULL;
+ td.buffer = ring_buffer;
+ td.state = IDLE;
+ td.camera = camera;
+ td.width = td.display_width = width;
+ td.height = td.display_height = height;
+ td.zoom_factor = 1.0;
+ td.histogram_view = histogram_view;
+ td.data_in_camram = FALSE;
+ td.main_window = window;
+
+ update_pixbuf_dimensions (&td);
+ set_tool_button_state (&td);
+
+ /* Hook up signals */
+ g_object_bind_property (gtk_builder_get_object (builder, "min-bin-value-adjustment"), "value",
+ td.histogram_view, "minimum-bin-value",
+ G_BINDING_DEFAULT);
+
+ g_object_bind_property (max_bin_adjustment, "value",
+ td.histogram_view, "maximum-bin-value",
+ G_BINDING_DEFAULT);
+
+ g_signal_connect (td.frame_slider, "value-changed", G_CALLBACK (on_frame_slider_changed), &td);
+ g_signal_connect (td.start_button, "clicked", G_CALLBACK (on_start_button_clicked), &td);
+ g_signal_connect (td.stop_button, "clicked", G_CALLBACK (on_stop_button_clicked), &td);
+ g_signal_connect (td.record_button, "clicked", G_CALLBACK (on_record_button_clicked), &td);
+ g_signal_connect (td.download_button, "clicked", G_CALLBACK (on_download_button_clicked), &td);
+ g_signal_connect (td.zoom_box, "changed", G_CALLBACK (on_zoom_changed), &td);
+ g_signal_connect (histogram_view, "changed", G_CALLBACK (on_histogram_changed), &td);
+ g_signal_connect (window, "destroy", G_CALLBACK (on_destroy), &td);
+
+ /* Layout */
+ gtk_container_add (property_window, property_tree_view);
+ gtk_box_pack_start (histogram_box, td.histogram_view, TRUE, TRUE, 6);
+
+ gtk_widget_show_all (window);
+}
+
+static void
+on_button_proceed_clicked (GtkWidget *widget, gpointer data)
+{
+ GtkBuilder *builder = GTK_BUILDER (data);
+ GtkWidget *choice_window = GTK_WIDGET (gtk_builder_get_object (builder, "choice-window"));
+ GtkTreeView *treeview = GTK_TREE_VIEW (gtk_builder_get_object (builder, "treeview-cameras"));
+ GtkListStore *list_store = GTK_LIST_STORE (gtk_builder_get_object (builder, "camera-types"));
+
+ GtkTreeSelection *selection = gtk_tree_view_get_selection (treeview);
+ GList *selected_rows = gtk_tree_selection_get_selected_rows (selection, NULL);
+ GtkTreeIter iter;
+
+ gtk_widget_destroy (choice_window);
+ gboolean valid = gtk_tree_model_get_iter (GTK_TREE_MODEL (list_store), &iter, selected_rows->data);
+
+ if (valid) {
+ gchar *data;
+ gtk_tree_model_get (GTK_TREE_MODEL (list_store), &iter, 0, &data, -1);
+ create_main_window (builder, data);
+ g_free (data);
+ }
+
+ g_list_foreach (selected_rows, (GFunc) gtk_tree_path_free, NULL);
+ g_list_free (selected_rows);
+}
+
+static void
+on_treeview_keypress (GtkWidget *widget, GdkEventKey *event, gpointer data)
+{
+ if (event->keyval == GDK_KEY_Return)
+ gtk_widget_grab_focus (GTK_WIDGET (data));
+}
+
+static void
+create_choice_window (GtkBuilder *builder)
+{
+ GList *camera_types = uca_plugin_manager_get_available_cameras (plugin_manager);
+
+ GtkWidget *choice_window = GTK_WIDGET (gtk_builder_get_object (builder, "choice-window"));
+ GtkTreeView *treeview = GTK_TREE_VIEW (gtk_builder_get_object (builder, "treeview-cameras"));
+ GtkListStore *list_store = GTK_LIST_STORE (gtk_builder_get_object (builder, "camera-types"));
+ GtkButton *proceed_button = GTK_BUTTON (gtk_builder_get_object (builder, "proceed-button"));
+ GtkTreeIter iter;
+
+ for (GList *it = g_list_first (camera_types); it != NULL; it = g_list_next (it)) {
+ gtk_list_store_append (list_store, &iter);
+ gtk_list_store_set (list_store, &iter, 0, g_strdup ((gchar *) it->data), -1);
+ }
+
+ gboolean valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (list_store), &iter);
+
+ if (valid) {
+ GtkTreeSelection *selection = gtk_tree_view_get_selection (treeview);
+ gtk_tree_selection_unselect_all (selection);
+ gtk_tree_selection_select_path (selection, gtk_tree_model_get_path (GTK_TREE_MODEL (list_store), &iter));
+ }
+
+ g_signal_connect (proceed_button, "clicked", G_CALLBACK (on_button_proceed_clicked), builder);
+ g_signal_connect (treeview, "key-press-event", G_CALLBACK (on_treeview_keypress), proceed_button);
+ gtk_widget_show_all (GTK_WIDGET (choice_window));
+
+ g_list_foreach (camera_types, (GFunc) g_free, NULL);
+ g_list_free (camera_types);
+}
+
+int
+main (int argc, char *argv[])
+{
+ GtkBuilder *builder;
+ GOptionContext *context;
+ GError *error = NULL;
+
+ static GOptionEntry entries[] =
+ {
+ { "mem-size", 'm', 0, G_OPTION_ARG_INT, &mem_size, "Memory in megabytes to allocate for frame storage", "M" },
+ { NULL }
+ };
+
+ context = g_option_context_new ("- control libuca cameras");
+ g_option_context_add_main_entries (context, entries, NULL);
+ g_option_context_add_group (context, gtk_get_option_group (TRUE));
+ if (!g_option_context_parse (context, &argc, &argv, &error)) {
+ g_print ("Option parsing failed: %s\n", error->message);
+ return 1;
+ }
+
+ g_thread_init (NULL);
+ gdk_threads_init ();
+ gtk_init (&argc, &argv);
+
+ builder = gtk_builder_new ();
+
+ if (!gtk_builder_add_from_file (builder, CONTROL_GLADE_PATH, &error)) {
+ g_print ("Could not load UI file: %s\n", error->message);
+ return 1;
+ }
+
+ plugin_manager = uca_plugin_manager_new ();
+ create_choice_window (builder);
+ gtk_builder_connect_signals (builder, NULL);
+
+ gdk_threads_enter ();
+ gtk_main ();
+ gdk_threads_leave ();
+
+ g_object_unref (plugin_manager);
+ return 0;
+}
diff --git a/bin/gui/control.glade b/bin/gui/control.glade
new file mode 100644
index 0000000..eec9dde
--- /dev/null
+++ b/bin/gui/control.glade
@@ -0,0 +1,635 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk+" version="2.20"/>
+ <!-- interface-naming-policy project-wide -->
+ <object class="GtkListStore" id="camera-types">
+ <columns>
+ <!-- column-name name -->
+ <column type="gchararray"/>
+ </columns>
+ </object>
+ <object class="GtkListStore" id="camera-properties">
+ <columns>
+ <!-- column-name PropertyName -->
+ <column type="gchararray"/>
+ <!-- column-name PropertyValue -->
+ <column type="gchararray"/>
+ <!-- column-name writeable -->
+ <column type="gboolean"/>
+ </columns>
+ </object>
+ <object class="GtkWindow" id="window">
+ <property name="title" translatable="yes">Camera Control</property>
+ <property name="default_width">1024</property>
+ <property name="default_height">768</property>
+ <signal name="delete_event" handler="on_delete_event"/>
+ <child>
+ <object class="GtkVBox" id="vbox1">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkMenuBar" id="menubar1">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkMenuItem" id="menuitem1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_File</property>
+ <property name="use_underline">True</property>
+ <child type="submenu">
+ <object class="GtkMenu" id="menu_file">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkImageMenuItem" id="imagemenuitem1">
+ <property name="label">gtk-new</property>
+ <property name="visible">True</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="imagemenuitem2">
+ <property name="label">gtk-open</property>
+ <property name="visible">True</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="imagemenuitem3">
+ <property name="label">gtk-save</property>
+ <property name="visible">True</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="imagemenuitem4">
+ <property name="label">gtk-save-as</property>
+ <property name="visible">True</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparatorMenuItem" id="separatormenuitem1">
+ <property name="visible">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="imagemenuitem_quit">
+ <property name="label">gtk-quit</property>
+ <property name="visible">True</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ <signal name="activate" handler="gtk_main_quit"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="menuitem4">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Help</property>
+ <property name="use_underline">True</property>
+ <child type="submenu">
+ <object class="GtkMenu" id="menu_help">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkImageMenuItem" id="imagemenuitem_about">
+ <property name="label">gtk-about</property>
+ <property name="visible">True</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolbar" id="toolbar">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkToolButton" id="start-button">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Run</property>
+ <property name="use_underline">True</property>
+ <property name="stock_id">gtk-media-play</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolButton" id="record-button">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Record</property>
+ <property name="use_underline">True</property>
+ <property name="stock_id">gtk-media-record</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolButton" id="stop-button">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Stop</property>
+ <property name="use_underline">True</property>
+ <property name="stock_id">gtk-media-stop</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolButton" id="download-button">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Download</property>
+ <property name="use_underline">True</property>
+ <property name="icon_name">network-receive</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHPaned" id="hpaned1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="border_width">6</property>
+ <child>
+ <object class="GtkVPaned" id="vpaned1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="width_request">640</property>
+ <property name="height_request">480</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">automatic</property>
+ <property name="vscrollbar_policy">automatic</property>
+ <child>
+ <object class="GtkViewport" id="viewport1">
+ <property name="visible">True</property>
+ <property name="resize_mode">queue</property>
+ <child>
+ <object class="GtkImage" id="image">
+ <property name="visible">True</property>
+ <property name="stock">gtk-missing-image</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="resize">True</property>
+ <property name="shrink">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkNotebook" id="notebook1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <object class="GtkHBox" id="histogram-box">
+ <property name="visible">True</property>
+ <property name="border_width">10</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkTable" id="table1">
+ <property name="visible">True</property>
+ <property name="border_width">6</property>
+ <property name="n_rows">3</property>
+ <property name="n_columns">2</property>
+ <property name="column_spacing">6</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">Minimum Value:</property>
+ </object>
+ <packing>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ <property name="x_padding">6</property>
+ <property name="y_padding">6</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spinbutton1">
+ <property name="width_request">100</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">•</property>
+ <property name="adjustment">min-bin-value-adjustment</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="x_options">GTK_EXPAND</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">Maximum Value:</property>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ <property name="x_padding">6</property>
+ <property name="y_padding">6</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spinbutton2">
+ <property name="width_request">100</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">•</property>
+ <property name="adjustment">max-bin-value-adjustment</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_EXPAND</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="histogram-checkbutton">
+ <property name="label" translatable="yes">Live Update</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="border_width">6</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options"></property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Histogram</property>
+ </object>
+ <packing>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkTable" id="table2">
+ <property name="visible">True</property>
+ <property name="border_width">10</property>
+ <property name="n_rows">2</property>
+ <property name="n_columns">2</property>
+ <property name="column_spacing">6</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <object class="GtkHScale" id="hscale1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="adjustment">frames-adjustment</property>
+ <property name="digits">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label5">
+ <property name="visible">True</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">Frame:</property>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label6">
+ <property name="visible">True</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">Zoom:</property>
+ </object>
+ <packing>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="zoom-box">
+ <property name="visible">True</property>
+ <property name="model">zoom-values</property>
+ <child>
+ <object class="GtkCellRendererText" id="cellrenderertext2"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Preview</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="resize">True</property>
+ <property name="shrink">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="resize">False</property>
+ <property name="shrink">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="property-window">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">automatic</property>
+ <property name="vscrollbar_policy">automatic</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="resize">True</property>
+ <property name="shrink">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <object class="GtkListStore" id="zoom-values">
+ <columns>
+ <!-- column-name display -->
+ <column type="gchararray"/>
+ <!-- column-name factor -->
+ <column type="gdouble"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">100 %</col>
+ <col id="1">1</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">50 %</col>
+ <col id="1">0.5</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">25 %</col>
+ <col id="1">0.25</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkWindow" id="choice-window">
+ <property name="border_width">6</property>
+ <child>
+ <object class="GtkVBox" id="vbox3">
+ <property name="visible">True</property>
+ <property name="spacing">2</property>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow3">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">automatic</property>
+ <property name="vscrollbar_policy">automatic</property>
+ <child>
+ <object class="GtkTreeView" id="treeview-cameras">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="model">camera-types</property>
+ <property name="headers_clickable">False</property>
+ <property name="search_column">0</property>
+ <child>
+ <object class="GtkTreeViewColumn" id="treeviewcolumn1">
+ <property name="title">Choose camera</property>
+ <child>
+ <object class="GtkCellRendererText" id="cellrenderertext1"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHButtonBox" id="hbuttonbox1">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="cancel-button">
+ <property name="label">gtk-quit</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ <signal name="clicked" handler="gtk_main_quit"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="proceed-button">
+ <property name="label">gtk-ok</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="padding">6</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <object class="GtkAdjustment" id="min-bin-value-adjustment">
+ <property name="upper">65535</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="max-bin-value-adjustment">
+ <property name="value">256</property>
+ <property name="upper">65535</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="frames-adjustment">
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkDialog" id="download-dialog">
+ <property name="border_width">5</property>
+ <property name="type_hint">normal</property>
+ <child internal-child="vbox">
+ <object class="GtkVBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="spacing">2</property>
+ <child>
+ <object class="GtkVBox" id="vbox2">
+ <property name="visible">True</property>
+ <property name="border_width">10</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label7">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Downloading Frames …</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkProgressBar" id="download-progressbar">
+ <property name="visible">True</property>
+ <property name="adjustment">download-adjustment</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child internal-child="action_area">
+ <object class="GtkHButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="download-close-button">
+ <property name="label">gtk-close</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="0">download-close-button</action-widget>
+ </action-widgets>
+ </object>
+ <object class="GtkAdjustment" id="download-adjustment">
+ <property name="upper">100</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ <property name="page_size">10</property>
+ </object>
+</interface>
diff --git a/bin/gui/egg-histogram-view.c b/bin/gui/egg-histogram-view.c
new file mode 100644
index 0000000..812af7a
--- /dev/null
+++ b/bin/gui/egg-histogram-view.c
@@ -0,0 +1,524 @@
+/* Copyright (C) 2011, 2012 Matthias Vogelgesang <matthias.vogelgesang@kit.edu>
+ (Karlsruhe Institute of Technology)
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by the
+ Free Software Foundation; either version 2.1 of the License, or (at your
+ option) any later version.
+
+ This library 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 Lesser General Public License for more
+ details.
+
+ You should have received a copy of the GNU Lesser General Public License along
+ with this library; if not, write to the Free Software Foundation, Inc., 51
+ Franklin St, Fifth Floor, Boston, MA 02110, USA */
+
+#include <math.h>
+#include "egg-histogram-view.h"
+
+G_DEFINE_TYPE (EggHistogramView, egg_histogram_view, GTK_TYPE_DRAWING_AREA)
+
+#define EGG_HISTOGRAM_VIEW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), EGG_TYPE_HISTOGRAM_VIEW, EggHistogramViewPrivate))
+
+#define MIN_WIDTH 128
+#define MIN_HEIGHT 128
+#define BORDER 2
+
+struct _EggHistogramViewPrivate
+{
+ GdkCursorType cursor_type;
+ gboolean grabbing;
+
+ /* This could be moved into a real histogram class */
+ guint n_bins;
+ gint *bins;
+ gint *grabbed;
+ gint min_border; /* threshold set in screen units */
+ gint max_border;
+
+ gdouble min_value; /* lowest value of the first bin */
+ gdouble max_value; /* highest value of the last bin */
+ gdouble range;
+
+ gpointer data;
+ gint n_elements;
+ gint n_bits;
+};
+
+enum
+{
+ PROP_0,
+ PROP_MINIMUM_BIN_VALUE,
+ PROP_MAXIMUM_BIN_VALUE,
+ N_PROPERTIES
+};
+
+enum
+{
+ CHANGED,
+ LAST_SIGNAL
+};
+
+static GParamSpec *egg_histogram_view_properties[N_PROPERTIES] = { NULL, };
+
+static guint egg_histogram_view_signals[LAST_SIGNAL] = { 0 };
+
+
+GtkWidget *
+egg_histogram_view_new (void)
+{
+ EggHistogramView *view;
+
+ view = EGG_HISTOGRAM_VIEW (g_object_new (EGG_TYPE_HISTOGRAM_VIEW, NULL));
+ return GTK_WIDGET (view);
+}
+
+void
+egg_histogram_view_set_data (EggHistogramView *view,
+ gpointer data,
+ guint n_elements,
+ guint n_bits,
+ guint n_bins)
+{
+ EggHistogramViewPrivate *priv;
+
+ g_return_if_fail (EGG_IS_HISTOGRAM_VIEW (view));
+ priv = view->priv;
+
+ if (priv->bins != NULL)
+ g_free (priv->bins);
+
+ priv->data = data;
+ priv->bins = g_malloc0 (n_bins * sizeof (guint));
+ priv->n_bins = n_bins;
+ priv->n_bits = n_bits;
+ priv->n_elements = n_elements;
+
+ priv->min_value = 0.0;
+ priv->max_value = (gint) pow(2, n_bits) - 1;
+
+ priv->min_border = 0;
+ priv->max_border = 256;
+ priv->range = priv->max_value - priv->min_value;
+}
+
+void
+egg_histogram_get_visible_range (EggHistogramView *view, gdouble *min, gdouble *max)
+{
+ EggHistogramViewPrivate *priv;
+ GtkAllocation allocation;
+ gdouble width;
+
+ g_return_if_fail (EGG_IS_HISTOGRAM_VIEW (view));
+
+ gtk_widget_get_allocation (GTK_WIDGET (view), &allocation);
+ width = (gdouble) allocation.width - 2 * BORDER;
+ priv = view->priv;
+
+ *min = (priv->min_border - 2) / width * priv->range;
+ *max = (priv->max_border - 2) / width * priv->range;
+}
+
+static void
+set_max_border (EggHistogramView *view)
+{
+ GtkAllocation allocation;
+
+ g_return_if_fail (EGG_IS_HISTOGRAM_VIEW (view));
+ gtk_widget_get_allocation (GTK_WIDGET (view), &allocation);
+ view->priv->max_border = allocation.width - 2 * BORDER;
+}
+
+static void
+compute_histogram (EggHistogramViewPrivate *priv)
+{
+ guint n_bins = priv->n_bins - 1;
+
+ for (guint i = 0; i < priv->n_bins; i++)
+ priv->bins[i] = 0;
+
+ if (priv->n_bits == 8) {
+ guint8 *data = (guint8 *) priv->data;
+
+ for (guint i = 0; i < priv->n_elements; i++) {
+ guint8 v = data[i];
+
+ if (v >= priv->min_value && v <= priv->max_value) {
+ guint index = (guint) round (((gdouble) v) / priv->max_value * n_bins);
+ priv->bins[index]++;
+ }
+ }
+ }
+ else {
+ guint16 *data = (guint16 *) priv->data;
+
+ for (guint i = 0; i < priv->n_elements; i++) {
+ guint16 v = data[i];
+
+ if (v >= priv->min_value && v <= priv->max_value) {
+ guint index = (guint) floor (((gdouble ) v) / priv->max_value * n_bins);
+ priv->bins[index]++;
+ }
+ }
+ }
+}
+
+static void
+set_cursor_type (EggHistogramView *view, GdkCursorType cursor_type)
+{
+ if (cursor_type != view->priv->cursor_type) {
+ GdkCursor *cursor = gdk_cursor_new (cursor_type);
+
+ gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET(view)), cursor);
+ gdk_cursor_unref (cursor);
+ view->priv->cursor_type = cursor_type;
+ }
+}
+
+static void
+egg_histogram_view_size_request (GtkWidget *widget,
+ GtkRequisition *requisition)
+{
+ requisition->width = MIN_WIDTH;
+ requisition->height = MIN_HEIGHT;
+}
+
+static void
+draw_bins (EggHistogramViewPrivate *priv,
+ cairo_t *cr,
+ gint width,
+ gint height)
+{
+ gdouble skip = ((gdouble) width) / priv->n_bins;
+ gdouble x = BORDER;
+ gdouble ys = height + BORDER - 1;
+ gint max_value = 0;
+
+ for (guint i = 0; i < priv->n_bins; i++) {
+ if (priv->bins[i] > max_value)
+ max_value = priv->bins[i];
+ }
+
+ if (max_value == 0)
+ return;
+
+ for (guint i = 0; i < priv->n_bins && x < (width - BORDER); i++, x += skip) {
+ if (priv->bins[i] == 0)
+ continue;
+
+ gint y = (gint) ((height - 2) * priv->bins[i]) / max_value;
+ cairo_move_to (cr, round (x), ys);
+ cairo_line_to (cr, round (x), ys - y);
+ cairo_stroke (cr);
+ }
+}
+
+static gboolean
+egg_histogram_view_expose (GtkWidget *widget,
+ GdkEventExpose *event)
+{
+ EggHistogramViewPrivate *priv;
+ GtkAllocation allocation;
+ GtkStyle *style;
+ cairo_t *cr;
+ gint width, height;
+
+ priv = EGG_HISTOGRAM_VIEW_GET_PRIVATE (widget);
+ cr = gdk_cairo_create (gtk_widget_get_window (widget));
+
+ gdk_cairo_region (cr, event->region);
+ cairo_clip (cr);
+
+ style = gtk_widget_get_style (widget);
+ gdk_cairo_set_source_color (cr, &style->base[GTK_STATE_NORMAL]);
+ cairo_paint (cr);
+
+ /* Draw the background */
+ gdk_cairo_set_source_color (cr, &style->base[GTK_STATE_NORMAL]);
+ cairo_paint (cr);
+
+ gtk_widget_get_allocation (widget, &allocation);
+ width = allocation.width - 2 * BORDER;
+ height = allocation.height - 2 * BORDER;
+
+ gdk_cairo_set_source_color (cr, &style->dark[GTK_STATE_NORMAL]);
+ cairo_set_line_width (cr, 1.0);
+ cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
+ cairo_translate (cr, 0.5, 0.5);
+ cairo_rectangle (cr, BORDER, BORDER, width - 1, height - 1);
+ cairo_stroke (cr);
+
+ if (priv->bins == NULL)
+ goto cleanup;
+
+ compute_histogram (priv);
+
+ /* Draw border areas */
+ gdk_cairo_set_source_color (cr, &style->dark[GTK_STATE_NORMAL]);
+
+ cairo_rectangle (cr, BORDER, BORDER, priv->min_border + 0.5, height - 1);
+ cairo_fill (cr);
+
+ cairo_rectangle (cr, priv->max_border + 0.5, BORDER, width - priv->max_border + 0.5, height - 1);
+ cairo_fill (cr);
+
+ /* Draw spikes */
+ gdk_cairo_set_source_color (cr, &style->black);
+ draw_bins (priv, cr, width, height);
+
+cleanup:
+ cairo_destroy (cr);
+ return FALSE;
+}
+
+static void
+egg_histogram_view_finalize (GObject *object)
+{
+ EggHistogramViewPrivate *priv;
+
+ priv = EGG_HISTOGRAM_VIEW_GET_PRIVATE (object);
+
+ if (priv->bins)
+ g_free (priv->bins);
+
+ G_OBJECT_CLASS (egg_histogram_view_parent_class)->finalize (object);
+}
+
+static void
+egg_histogram_view_dispose (GObject *object)
+{
+ G_OBJECT_CLASS (egg_histogram_view_parent_class)->dispose (object);
+}
+
+static void
+egg_histogram_view_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ EggHistogramViewPrivate *priv;
+
+ g_return_if_fail (EGG_IS_HISTOGRAM_VIEW (object));
+ priv = EGG_HISTOGRAM_VIEW_GET_PRIVATE (object);
+
+ switch (property_id) {
+ case PROP_MINIMUM_BIN_VALUE:
+ {
+ gdouble v = g_value_get_double (value);
+
+ if (v > priv->max_value)
+ g_warning ("Minimum value `%f' larger than maximum value `%f'",
+ v, priv->max_value);
+ else {
+ priv->min_value = v;
+ priv->range = priv->max_value - v;
+ priv->min_border = 0;
+ }
+ }
+ break;
+
+ case PROP_MAXIMUM_BIN_VALUE:
+ {
+ gdouble v = g_value_get_double (value);
+
+ if (v < priv->min_value)
+ g_warning ("Maximum value `%f' larger than minimum value `%f'",
+ v, priv->min_value);
+ else {
+ priv->max_value = v;
+ priv->range = v - priv->min_value;
+ set_max_border (EGG_HISTOGRAM_VIEW (object));
+ }
+ }
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ return;
+ }
+}
+
+static void
+egg_histogram_view_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ EggHistogramViewPrivate *priv;
+
+ g_return_if_fail (EGG_IS_HISTOGRAM_VIEW (object));
+ priv = EGG_HISTOGRAM_VIEW_GET_PRIVATE (object);
+
+ switch (property_id) {
+ case PROP_MINIMUM_BIN_VALUE:
+ g_value_set_double (value, priv->min_value);
+ break;
+
+ case PROP_MAXIMUM_BIN_VALUE:
+ g_value_set_double (value, priv->max_value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ return;
+ }
+}
+
+static gboolean
+is_on_border (EggHistogramViewPrivate *priv,
+ gint x)
+{
+ gint d1 = (priv->min_border + BORDER) - x;
+ gint d2 = (priv->max_border + BORDER) - x;
+ return ABS (d1) < 6 || ABS (d2) < 6;
+}
+
+static gint *
+get_grabbed_border (EggHistogramViewPrivate *priv,
+ gint x)
+{
+ gint d1 = (priv->min_border + BORDER) - x;
+ gint d2 = (priv->max_border + BORDER) - x;
+
+ if (ABS (d1) < 6)
+ return &priv->min_border;
+ else if (ABS (d2) < 6)
+ return &priv->max_border;
+
+ return NULL;
+}
+
+static gboolean
+egg_histogram_view_motion_notify (GtkWidget *widget,
+ GdkEventMotion *event)
+{
+ EggHistogramView *view;
+ EggHistogramViewPrivate *priv;
+
+ view = EGG_HISTOGRAM_VIEW (widget);
+ priv = view->priv;
+
+ if (priv->grabbing) {
+ GtkAllocation allocation;
+
+ gtk_widget_get_allocation (widget, &allocation);
+
+ if ((event->x + BORDER > 0) && (event->x + BORDER < allocation.width)) {
+ *priv->grabbed = event->x;
+ gtk_widget_queue_draw (widget);
+ }
+ }
+ else {
+ if (is_on_border (priv, event->x))
+ set_cursor_type (view, GDK_FLEUR);
+ else
+ set_cursor_type (view, GDK_ARROW);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+egg_histogram_view_button_release (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ EggHistogramView *view;
+
+ view = EGG_HISTOGRAM_VIEW (widget);
+ set_cursor_type (view, GDK_ARROW);
+ view->priv->grabbing = FALSE;
+ g_signal_emit (widget, egg_histogram_view_signals[CHANGED], 0);
+
+ return TRUE;
+}
+
+static gboolean
+egg_histogram_view_button_press (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ EggHistogramView *view;
+ EggHistogramViewPrivate *priv;
+
+ view = EGG_HISTOGRAM_VIEW (widget);
+ priv = view->priv;
+
+ if (is_on_border (priv, event->x)) {
+ priv->grabbing = TRUE;
+ priv->grabbed = get_grabbed_border (priv, event->x);
+ set_cursor_type (view, GDK_FLEUR);
+ }
+
+ return TRUE;
+}
+
+static void
+egg_histogram_view_class_init (EggHistogramViewClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->set_property = egg_histogram_view_set_property;
+ object_class->get_property = egg_histogram_view_get_property;
+ object_class->dispose = egg_histogram_view_dispose;
+ object_class->finalize = egg_histogram_view_finalize;
+
+ widget_class->size_request = egg_histogram_view_size_request;
+ widget_class->expose_event = egg_histogram_view_expose;
+ widget_class->button_press_event = egg_histogram_view_button_press;
+ widget_class->button_release_event = egg_histogram_view_button_release;
+ widget_class->motion_notify_event = egg_histogram_view_motion_notify;
+
+ egg_histogram_view_properties[PROP_MINIMUM_BIN_VALUE] =
+ g_param_spec_double ("minimum-bin-value",
+ "Smallest possible bin value",
+ "Smallest possible bin value, everything below is discarded.",
+ 0.0, G_MAXDOUBLE, 0.0,
+ G_PARAM_READWRITE);
+
+ egg_histogram_view_properties[PROP_MAXIMUM_BIN_VALUE] =
+ g_param_spec_double ("maximum-bin-value",
+ "Largest possible bin value",
+ "Largest possible bin value, everything above is discarded.",
+ 0.0, G_MAXDOUBLE, 256.0,
+ G_PARAM_READWRITE);
+
+ g_object_class_install_property (object_class, PROP_MINIMUM_BIN_VALUE, egg_histogram_view_properties[PROP_MINIMUM_BIN_VALUE]);
+ g_object_class_install_property (object_class, PROP_MAXIMUM_BIN_VALUE, egg_histogram_view_properties[PROP_MAXIMUM_BIN_VALUE]);
+
+ egg_histogram_view_signals[CHANGED] =
+ g_signal_new ("changed",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE,
+ G_STRUCT_OFFSET (EggHistogramViewClass, changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ g_type_class_add_private (klass, sizeof (EggHistogramViewPrivate));
+}
+
+static void
+egg_histogram_view_init (EggHistogramView *view)
+{
+ EggHistogramViewPrivate *priv;
+
+ view->priv = priv = EGG_HISTOGRAM_VIEW_GET_PRIVATE (view);
+
+ priv->bins = NULL;
+ priv->data = NULL;
+ priv->n_bins = 0;
+ priv->n_elements = 0;
+ priv->min_value = priv->min_border = 0;
+ priv->max_value = priv->max_border = 256;
+
+ priv->cursor_type = GDK_ARROW;
+ priv->grabbing = FALSE;
+
+ gtk_widget_add_events (GTK_WIDGET (view),
+ GDK_BUTTON_PRESS_MASK |
+ GDK_BUTTON_RELEASE_MASK |
+ GDK_BUTTON1_MOTION_MASK |
+ GDK_POINTER_MOTION_MASK);
+}
diff --git a/bin/gui/egg-histogram-view.h b/bin/gui/egg-histogram-view.h
new file mode 100644
index 0000000..7a62fca
--- /dev/null
+++ b/bin/gui/egg-histogram-view.h
@@ -0,0 +1,66 @@
+/* Copyright (C) 2011, 2012 Matthias Vogelgesang <matthias.vogelgesang@kit.edu>
+ (Karlsruhe Institute of Technology)
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by the
+ Free Software Foundation; either version 2.1 of the License, or (at your
+ option) any later version.
+
+ This library 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 Lesser General Public License for more
+ details.
+
+ You should have received a copy of the GNU Lesser General Public License along
+ with this library; if not, write to the Free Software Foundation, Inc., 51
+ Franklin St, Fifth Floor, Boston, MA 02110, USA */
+
+#ifndef EGG_HISTOGRAM_VIEW_H
+#define EGG_HISTOGRAM_VIEW_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define EGG_TYPE_HISTOGRAM_VIEW (egg_histogram_view_get_type())
+#define EGG_HISTOGRAM_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), EGG_TYPE_HISTOGRAM_VIEW, EggHistogramView))
+#define EGG_IS_HISTOGRAM_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), EGG_TYPE_HISTOGRAM_VIEW))
+#define EGG_HISTOGRAM_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), EGG_TYPE_HISTOGRAM_VIEW, EggHistogramViewClass))
+#define EGG_IS_HISTOGRAM_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), EGG_TYPE_HISTOGRAM_VIEW))
+#define EGG_HISTOGRAM_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), EGG_TYPE_HISTOGRAM_VIEW, EggHistogramViewClass))
+
+typedef struct _EggHistogramView EggHistogramView;
+typedef struct _EggHistogramViewClass EggHistogramViewClass;
+typedef struct _EggHistogramViewPrivate EggHistogramViewPrivate;
+
+struct _EggHistogramView
+{
+ GtkDrawingArea parent_instance;
+
+ /*< private >*/
+ EggHistogramViewPrivate *priv;
+};
+
+struct _EggHistogramViewClass
+{
+ GtkDrawingAreaClass parent_class;
+
+ /* signals */
+ void (* changed) (EggHistogramView *view);
+};
+
+GType egg_histogram_view_get_type (void);
+GtkWidget * egg_histogram_view_new (void);
+void egg_histogram_view_set_data (EggHistogramView *view,
+ gpointer data,
+ guint n_elements,
+ guint n_bits,
+ guint n_bins);
+void egg_histogram_get_visible_range
+ (EggHistogramView *view,
+ gdouble *min,
+ gdouble *max);
+
+G_END_DECLS
+
+#endif
diff --git a/bin/gui/egg-property-cell-renderer.c b/bin/gui/egg-property-cell-renderer.c
new file mode 100644
index 0000000..9df5cc3
--- /dev/null
+++ b/bin/gui/egg-property-cell-renderer.c
@@ -0,0 +1,594 @@
+/* Copyright (C) 2011, 2012 Matthias Vogelgesang <matthias.vogelgesang@kit.edu>
+ (Karlsruhe Institute of Technology)
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by the
+ Free Software Foundation; either version 2.1 of the License, or (at your
+ option) any later version.
+
+ This library 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 Lesser General Public License for more
+ details.
+
+ You should have received a copy of the GNU Lesser General Public License along
+ with this library; if not, write to the Free Software Foundation, Inc., 51
+ Franklin St, Fifth Floor, Boston, MA 02110, USA */
+
+#include <stdlib.h>
+#include "egg-property-cell-renderer.h"
+
+G_DEFINE_TYPE (EggPropertyCellRenderer, egg_property_cell_renderer, GTK_TYPE_CELL_RENDERER)
+
+#define EGG_PROPERTY_CELL_RENDERER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), EGG_TYPE_PROPERTY_CELL_RENDERER, EggPropertyCellRendererPrivate))
+
+
+struct _EggPropertyCellRendererPrivate
+{
+ GObject *object;
+ GtkListStore *list_store;
+ GtkCellRenderer *renderer;
+ GtkCellRenderer *text_renderer;
+ GtkCellRenderer *spin_renderer;
+ GtkCellRenderer *toggle_renderer;
+ GtkCellRenderer *combo_renderer;
+ GHashTable *combo_models;
+};
+
+enum
+{
+ PROP_0,
+ PROP_PROP_NAME,
+ N_PROPERTIES
+};
+
+enum
+{
+ COMBO_COLUMN_VALUE_NAME,
+ COMBO_COLUMN_VALUE,
+ N_COMBO_COLUMNS
+};
+
+static GParamSpec *egg_property_cell_renderer_properties[N_PROPERTIES] = { NULL, };
+
+GtkCellRenderer *
+egg_property_cell_renderer_new (GObject *object,
+ GtkListStore *list_store)
+{
+ EggPropertyCellRenderer *renderer;
+
+ renderer = EGG_PROPERTY_CELL_RENDERER (g_object_new (EGG_TYPE_PROPERTY_CELL_RENDERER, NULL));
+ renderer->priv->object = object;
+ renderer->priv->list_store = list_store;
+ return GTK_CELL_RENDERER (renderer);
+}
+
+static GParamSpec *
+get_pspec_from_object (GObject *object, const gchar *prop_name)
+{
+ GObjectClass *oclass = G_OBJECT_GET_CLASS (object);
+ return g_object_class_find_property (oclass, prop_name);
+}
+
+static void
+get_string_double_repr (GObject *object, const gchar *prop_name, gchar **text, gdouble *number)
+{
+ GParamSpec *pspec;
+ GValue from = { 0 };
+ GValue to_string = { 0 };
+ GValue to_double = { 0 };
+
+ pspec = get_pspec_from_object (object, prop_name);
+ g_value_init (&from, pspec->value_type);
+ g_value_init (&to_string, G_TYPE_STRING);
+ g_value_init (&to_double, G_TYPE_DOUBLE);
+ g_object_get_property (object, prop_name, &from);
+
+ if (g_value_transform (&from, &to_string))
+ *text = g_strdup (g_value_get_string (&to_string));
+ else
+ g_warning ("Could not convert from %s gchar*\n", g_type_name (pspec->value_type));
+
+ if (g_value_transform (&from, &to_double))
+ *number = g_value_get_double (&to_double);
+ else
+ g_warning ("Could not convert from %s to gdouble\n", g_type_name (pspec->value_type));
+}
+
+static void
+clear_adjustment (GObject *object)
+{
+ GtkAdjustment *adjustment;
+
+ g_object_get (object,
+ "adjustment", &adjustment,
+ NULL);
+
+ if (adjustment)
+ g_object_unref (adjustment);
+
+ g_object_set (object,
+ "adjustment", NULL,
+ NULL);
+}
+
+static void
+egg_property_cell_renderer_set_renderer (EggPropertyCellRenderer *renderer,
+ const gchar *prop_name)
+{
+ EggPropertyCellRendererPrivate *priv;
+ GParamSpec *pspec;
+ gchar *text = NULL;
+ gdouble number;
+
+ priv = EGG_PROPERTY_CELL_RENDERER_GET_PRIVATE (renderer);
+ pspec = get_pspec_from_object (priv->object, prop_name);
+
+ /*
+ * Set this renderers mode, so that any actions can be forwarded to our
+ * child renderers.
+ */
+ switch (pspec->value_type) {
+ /* toggle renderers */
+ case G_TYPE_BOOLEAN:
+ priv->renderer = priv->toggle_renderer;
+ g_object_set (renderer, "mode", GTK_CELL_RENDERER_MODE_ACTIVATABLE, NULL);
+ break;
+
+ /* spin renderers */
+ case G_TYPE_FLOAT:
+ case G_TYPE_DOUBLE:
+ priv->renderer = priv->spin_renderer;
+ g_object_set (renderer, "mode", GTK_CELL_RENDERER_MODE_EDITABLE, NULL);
+ g_object_set (priv->renderer, "digits", 5, NULL);
+ break;
+
+ case G_TYPE_INT:
+ case G_TYPE_UINT:
+ case G_TYPE_LONG:
+ case G_TYPE_ULONG:
+ case G_TYPE_INT64:
+ case G_TYPE_UINT64:
+ priv->renderer = priv->spin_renderer;
+ g_object_set (renderer, "mode", GTK_CELL_RENDERER_MODE_EDITABLE, NULL);
+ g_object_set (priv->renderer, "digits", 0, NULL);
+ break;
+
+ /* text renderers */
+ case G_TYPE_POINTER:
+ case G_TYPE_STRING:
+ priv->renderer = priv->text_renderer;
+ g_object_set (renderer, "mode", GTK_CELL_RENDERER_MODE_EDITABLE, NULL);
+ break;
+
+ /* combo renderers */
+ default:
+ if (G_TYPE_IS_ENUM (pspec->value_type)) {
+ priv->renderer = priv->combo_renderer;
+ g_object_set (renderer, "mode", GTK_CELL_RENDERER_MODE_EDITABLE, NULL);
+ }
+ break;
+ }
+
+ /*
+ * Set the content from the objects property.
+ */
+ switch (pspec->value_type) {
+ case G_TYPE_BOOLEAN:
+ {
+ gboolean val;
+
+ g_object_get (priv->object, prop_name, &val, NULL);
+ g_object_set (priv->renderer,
+ "active", val,
+ "activatable", pspec->flags & G_PARAM_WRITABLE ? TRUE : FALSE,
+ NULL);
+ break;
+ }
+
+ case G_TYPE_INT:
+ case G_TYPE_UINT:
+ case G_TYPE_LONG:
+ case G_TYPE_ULONG:
+ case G_TYPE_INT64:
+ case G_TYPE_UINT64:
+ case G_TYPE_FLOAT:
+ case G_TYPE_DOUBLE:
+ get_string_double_repr (priv->object, prop_name, &text, &number);
+ break;
+
+ case G_TYPE_STRING:
+ g_object_get (priv->object, prop_name, &text, NULL);
+ break;
+
+ case G_TYPE_POINTER:
+ {
+ gpointer val;
+
+ g_object_get (priv->object, prop_name, &val, NULL);
+ text = g_strdup_printf ("0x%x", GPOINTER_TO_INT (val));
+ }
+ break;
+
+ default:
+ if (G_TYPE_IS_ENUM (pspec->value_type)) {
+ GParamSpecEnum *pspec_enum;
+ GEnumClass *enum_class;
+ GtkTreeModel *combo_model;
+ GtkTreeIter iter;
+ gint value;
+
+ g_object_get (priv->object, prop_name, &value, NULL);
+
+ pspec_enum = G_PARAM_SPEC_ENUM (pspec);
+ enum_class = pspec_enum->enum_class;
+ combo_model = g_hash_table_lookup (priv->combo_models, prop_name);
+
+ if (combo_model == NULL) {
+ combo_model = GTK_TREE_MODEL (gtk_list_store_new (N_COMBO_COLUMNS, G_TYPE_STRING, G_TYPE_INT));
+ g_hash_table_insert (priv->combo_models, g_strdup (prop_name), combo_model);
+
+ for (guint i = 0; i < enum_class->n_values; i++) {
+ gtk_list_store_append (GTK_LIST_STORE (combo_model), &iter);
+ gtk_list_store_set (GTK_LIST_STORE (combo_model), &iter,
+ COMBO_COLUMN_VALUE_NAME, enum_class->values[i].value_name,
+ COMBO_COLUMN_VALUE, enum_class->values[i].value,
+ -1);
+ }
+ }
+
+
+ for (guint i = 0; i < enum_class->n_values; i++) {
+ if (enum_class->values[i].value == value)
+ text = g_strdup (enum_class->values[i].value_name);
+ }
+
+ g_object_set (priv->renderer,
+ "model", combo_model,
+ "text-column", 0,
+ NULL);
+ }
+ break;
+ }
+
+ if (pspec->flags & G_PARAM_WRITABLE) {
+ if (GTK_IS_CELL_RENDERER_TOGGLE (priv->renderer))
+ g_object_set (priv->renderer, "mode", GTK_CELL_RENDERER_MODE_ACTIVATABLE, NULL);
+ else
+ g_object_set (priv->renderer, "foreground", "#000000", NULL);
+
+ if (GTK_IS_CELL_RENDERER_TEXT (priv->renderer)) {
+ g_object_set (priv->renderer,
+ "editable", TRUE,
+ "mode", GTK_CELL_RENDERER_MODE_EDITABLE,
+ NULL);
+ }
+
+ if (GTK_IS_CELL_RENDERER_SPIN (priv->renderer)) {
+ GtkObject *adjustment = NULL;
+
+#define gtk_typed_adjustment_new(type, pspec, val, step_inc, page_inc) \
+ gtk_adjustment_new (val, ((type *) pspec)->minimum, ((type *) pspec)->maximum, step_inc, page_inc, 0)
+
+ switch (pspec->value_type) {
+ case G_TYPE_INT:
+ adjustment = gtk_typed_adjustment_new (GParamSpecInt, pspec, number, 1, 10);
+ break;
+ case G_TYPE_UINT:
+ adjustment = gtk_typed_adjustment_new (GParamSpecUInt, pspec, number, 1, 10);
+ break;
+ case G_TYPE_LONG:
+ adjustment = gtk_typed_adjustment_new (GParamSpecLong, pspec, number, 1, 10);
+ break;
+ case G_TYPE_ULONG:
+ adjustment = gtk_typed_adjustment_new (GParamSpecULong, pspec, number, 1, 10);
+ break;
+ case G_TYPE_INT64:
+ adjustment = gtk_typed_adjustment_new (GParamSpecInt64, pspec, number, 1, 10);
+ break;
+ case G_TYPE_UINT64:
+ adjustment = gtk_typed_adjustment_new (GParamSpecUInt64, pspec, number, 1, 10);
+ break;
+ case G_TYPE_FLOAT:
+ adjustment = gtk_typed_adjustment_new (GParamSpecFloat, pspec, number, 0.05, 10);
+ break;
+ case G_TYPE_DOUBLE:
+ adjustment = gtk_typed_adjustment_new (GParamSpecDouble, pspec, number, 0.05, 10);
+ break;
+ }
+
+ clear_adjustment (G_OBJECT (priv->renderer));
+ g_object_set (priv->renderer, "adjustment", adjustment, NULL);
+ }
+ }
+ else {
+ g_object_set (priv->renderer, "mode", GTK_CELL_RENDERER_MODE_INERT, NULL);
+
+ if (!GTK_IS_CELL_RENDERER_TOGGLE (priv->renderer))
+ g_object_set (priv->renderer, "foreground", "#aaaaaa", NULL);
+ }
+
+ if (text != NULL) {
+ g_object_set (priv->renderer, "text", text, NULL);
+ g_free (text);
+ }
+}
+
+static gchar *
+get_prop_name_from_tree_model (GtkTreeModel *model, const gchar *path)
+{
+ GtkTreeIter iter;
+ gchar *prop_name = NULL;
+
+ /* TODO: don't assume column 0 to contain the prop name */
+ if (gtk_tree_model_get_iter_from_string (model, &iter, path))
+ gtk_tree_model_get (model, &iter, 0, &prop_name, -1);
+
+ return prop_name;
+}
+
+static void
+egg_property_cell_renderer_toggle_cb (GtkCellRendererToggle *renderer,
+ gchar *path,
+ gpointer user_data)
+{
+ EggPropertyCellRendererPrivate *priv;
+ gchar *prop_name;
+
+ priv = (EggPropertyCellRendererPrivate *) user_data;
+ prop_name = get_prop_name_from_tree_model (GTK_TREE_MODEL (priv->list_store), path);
+
+ if (prop_name != NULL) {
+ gboolean activated;
+
+ g_object_get (priv->object, prop_name, &activated, NULL);
+ g_object_set (priv->object, prop_name, !activated, NULL);
+ g_free (prop_name);
+ }
+}
+
+static void
+egg_property_cell_renderer_text_edited_cb (GtkCellRendererText *renderer,
+ gchar *path,
+ gchar *new_text,
+ gpointer user_data)
+{
+ EggPropertyCellRendererPrivate *priv;
+ gchar *prop_name;
+
+ priv = (EggPropertyCellRendererPrivate *) user_data;
+ prop_name = get_prop_name_from_tree_model (GTK_TREE_MODEL (priv->list_store), path);
+
+ if (prop_name != NULL) {
+ g_object_set (priv->object, prop_name, new_text, NULL);
+ g_free (prop_name);
+ }
+}
+
+static void
+egg_property_cell_renderer_spin_edited_cb (GtkCellRendererText *renderer,
+ gchar *path,
+ gchar *new_text,
+ gpointer user_data)
+{
+ EggPropertyCellRendererPrivate *priv;
+ gchar *prop_name;
+
+ priv = (EggPropertyCellRendererPrivate *) user_data;
+ prop_name = get_prop_name_from_tree_model (GTK_TREE_MODEL (priv->list_store), path);
+
+ if (prop_name != NULL) {
+ GParamSpec *pspec;
+ GValue from = { 0 };
+ GValue to = { 0 };
+
+ pspec = get_pspec_from_object (priv->object, prop_name);
+
+ g_value_init (&from, G_TYPE_DOUBLE);
+ g_value_init (&to, pspec->value_type);
+ g_value_set_double (&from, strtod (new_text, NULL));
+
+ if (g_value_transform (&from, &to))
+ g_object_set_property (priv->object, prop_name, &to);
+ else
+ g_warning ("Could not transform %s to %s\n",
+ g_value_get_string (&from), g_type_name (pspec->value_type));
+
+ g_free (prop_name);
+ }
+}
+
+static void
+egg_property_cell_renderer_changed_cb (GtkCellRendererCombo *combo,
+ gchar *path,
+ GtkTreeIter *new_iter,
+ gpointer user_data)
+{
+ EggPropertyCellRendererPrivate *priv;
+ gchar *prop_name;
+
+ priv = (EggPropertyCellRendererPrivate *) user_data;
+ prop_name = get_prop_name_from_tree_model (GTK_TREE_MODEL (priv->list_store), path);
+
+ if (prop_name != NULL) {
+ GtkTreeModel *combo_model;
+ gchar *value_name;
+ gint value;
+
+ combo_model = g_hash_table_lookup (priv->combo_models, prop_name);
+
+ gtk_tree_model_get (combo_model, new_iter,
+ COMBO_COLUMN_VALUE_NAME, &value_name,
+ COMBO_COLUMN_VALUE, &value,
+ -1);
+
+ g_object_set (priv->object, prop_name, value, NULL);
+ g_free (value_name);
+ g_free (prop_name);
+ }
+}
+
+static void
+egg_property_cell_renderer_get_size (GtkCellRenderer *cell,
+ GtkWidget *widget,
+ GdkRectangle *cell_area,
+ gint *x_offset,
+ gint *y_offset,
+ gint *width,
+ gint *height)
+{
+
+ EggPropertyCellRendererPrivate *priv = EGG_PROPERTY_CELL_RENDERER_GET_PRIVATE (cell);
+ gtk_cell_renderer_get_size (priv->renderer, widget, cell_area, x_offset, y_offset, width, height);
+}
+
+static void
+egg_property_cell_renderer_render (GtkCellRenderer *cell,
+ GdkDrawable *window,
+ GtkWidget *widget,
+ GdkRectangle *background_area,
+ GdkRectangle *cell_area,
+ GdkRectangle *expose_area,
+ GtkCellRendererState flags)
+{
+ EggPropertyCellRendererPrivate *priv = EGG_PROPERTY_CELL_RENDERER_GET_PRIVATE (cell);
+ gtk_cell_renderer_render (priv->renderer, window, widget, background_area, cell_area, expose_area, flags);
+}
+
+static gboolean
+egg_property_cell_renderer_activate (GtkCellRenderer *cell,
+ GdkEvent *event,
+ GtkWidget *widget,
+ const gchar *path,
+ GdkRectangle *background_area,
+ GdkRectangle *cell_area,
+ GtkCellRendererState flags)
+{
+ EggPropertyCellRendererPrivate *priv = EGG_PROPERTY_CELL_RENDERER_GET_PRIVATE (cell);
+ return gtk_cell_renderer_activate (priv->renderer, event, widget, path, background_area, cell_area, flags);
+}
+
+static GtkCellEditable *
+egg_property_cell_renderer_start_editing (GtkCellRenderer *cell,
+ GdkEvent *event,
+ GtkWidget *widget,
+ const gchar *path,
+ GdkRectangle *background_area,
+ GdkRectangle *cell_area,
+ GtkCellRendererState flags)
+{
+ EggPropertyCellRendererPrivate *priv = EGG_PROPERTY_CELL_RENDERER_GET_PRIVATE (cell);
+ return gtk_cell_renderer_start_editing (priv->renderer, event, widget, path, background_area, cell_area, flags);
+}
+
+static void
+egg_property_cell_renderer_dispose (GObject *object)
+{
+ EggPropertyCellRenderer *renderer = EGG_PROPERTY_CELL_RENDERER (object);
+ g_hash_table_destroy (renderer->priv->combo_models);
+}
+
+static void
+egg_property_cell_renderer_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ g_return_if_fail (EGG_IS_PROPERTY_CELL_RENDERER (object));
+ EggPropertyCellRenderer *renderer = EGG_PROPERTY_CELL_RENDERER (object);
+
+ switch (property_id) {
+ case PROP_PROP_NAME:
+ egg_property_cell_renderer_set_renderer (renderer, g_value_get_string (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ return;
+ }
+}
+
+static void
+egg_property_cell_renderer_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ g_return_if_fail (EGG_IS_PROPERTY_CELL_RENDERER (object));
+
+ switch (property_id) {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ return;
+ }
+}
+
+static void
+egg_property_cell_renderer_class_init (EggPropertyCellRendererClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GtkCellRendererClass *cellrenderer_class = GTK_CELL_RENDERER_CLASS (klass);
+
+ gobject_class->set_property = egg_property_cell_renderer_set_property;
+ gobject_class->get_property = egg_property_cell_renderer_get_property;
+ gobject_class->dispose = egg_property_cell_renderer_dispose;
+
+ cellrenderer_class->render = egg_property_cell_renderer_render;
+ cellrenderer_class->get_size = egg_property_cell_renderer_get_size;
+ cellrenderer_class->activate = egg_property_cell_renderer_activate;
+ cellrenderer_class->start_editing = egg_property_cell_renderer_start_editing;
+
+ egg_property_cell_renderer_properties[PROP_PROP_NAME] =
+ g_param_spec_string("prop-name",
+ "Property name", "Property name", "",
+ G_PARAM_READWRITE);
+
+ g_object_class_install_property(gobject_class, PROP_PROP_NAME, egg_property_cell_renderer_properties[PROP_PROP_NAME]);
+
+ g_type_class_add_private (klass, sizeof (EggPropertyCellRendererPrivate));
+}
+
+static void
+egg_property_cell_renderer_init (EggPropertyCellRenderer *renderer)
+{
+ EggPropertyCellRendererPrivate *priv;
+
+ renderer->priv = priv = EGG_PROPERTY_CELL_RENDERER_GET_PRIVATE (renderer);
+
+ priv->text_renderer = gtk_cell_renderer_text_new ();
+ priv->spin_renderer = gtk_cell_renderer_spin_new ();
+ priv->toggle_renderer = gtk_cell_renderer_toggle_new ();
+ priv->combo_renderer = gtk_cell_renderer_combo_new ();
+ priv->renderer = priv->text_renderer;
+ priv->combo_models = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
+
+ g_object_set (priv->text_renderer,
+ "editable", TRUE,
+ NULL);
+
+ g_object_set (priv->spin_renderer,
+ "editable", TRUE,
+ NULL);
+
+ g_object_set (priv->toggle_renderer,
+ "xalign", 0.0f,
+ "activatable", TRUE,
+ "mode", GTK_CELL_RENDERER_MODE_ACTIVATABLE,
+ NULL);
+
+ g_object_set (priv->combo_renderer,
+ "has-entry", FALSE,
+ NULL);
+
+ g_signal_connect (priv->spin_renderer, "edited",
+ G_CALLBACK (egg_property_cell_renderer_spin_edited_cb), priv);
+
+ g_signal_connect (priv->text_renderer, "edited",
+ G_CALLBACK (egg_property_cell_renderer_text_edited_cb), NULL);
+
+ g_signal_connect (priv->toggle_renderer, "toggled",
+ G_CALLBACK (egg_property_cell_renderer_toggle_cb), priv);
+
+ g_signal_connect (priv->combo_renderer, "changed",
+ G_CALLBACK (egg_property_cell_renderer_changed_cb), priv);
+}
diff --git a/bin/gui/egg-property-cell-renderer.h b/bin/gui/egg-property-cell-renderer.h
new file mode 100644
index 0000000..d4dbe02
--- /dev/null
+++ b/bin/gui/egg-property-cell-renderer.h
@@ -0,0 +1,55 @@
+/* Copyright (C) 2011, 2012 Matthias Vogelgesang <matthias.vogelgesang@kit.edu>
+ (Karlsruhe Institute of Technology)
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by the
+ Free Software Foundation; either version 2.1 of the License, or (at your
+ option) any later version.
+
+ This library 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 Lesser General Public License for more
+ details.
+
+ You should have received a copy of the GNU Lesser General Public License along
+ with this library; if not, write to the Free Software Foundation, Inc., 51
+ Franklin St, Fifth Floor, Boston, MA 02110, USA */
+
+#ifndef EGG_PROPERTY_CELL_RENDERER_H
+#define EGG_PROPERTY_CELL_RENDERER_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define EGG_TYPE_PROPERTY_CELL_RENDERER (egg_property_cell_renderer_get_type())
+#define EGG_PROPERTY_CELL_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), EGG_TYPE_PROPERTY_CELL_RENDERER, EggPropertyCellRenderer))
+#define EGG_IS_PROPERTY_CELL_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), EGG_TYPE_PROPERTY_CELL_RENDERER))
+#define EGG_PROPERTY_CELL_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), EGG_TYPE_PROPERTY_CELL_RENDERER, EggPropertyCellRendererClass))
+#define EGG_IS_PROPERTY_CELL_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), EGG_TYPE_PROPERTY_CELL_RENDERER))
+#define EGG_PROPERTY_CELL_RENDERER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), EGG_TYPE_PROPERTY_CELL_RENDERER, EggPropertyCellRendererClass))
+
+typedef struct _EggPropertyCellRenderer EggPropertyCellRenderer;
+typedef struct _EggPropertyCellRendererClass EggPropertyCellRendererClass;
+typedef struct _EggPropertyCellRendererPrivate EggPropertyCellRendererPrivate;
+
+struct _EggPropertyCellRenderer
+{
+ GtkCellRenderer parent_instance;
+
+ /*< private >*/
+ EggPropertyCellRendererPrivate *priv;
+};
+
+struct _EggPropertyCellRendererClass
+{
+ GtkCellRendererClass parent_class;
+};
+
+GType egg_property_cell_renderer_get_type (void);
+GtkCellRenderer* egg_property_cell_renderer_new (GObject *object,
+ GtkListStore *list_store);
+
+G_END_DECLS
+
+#endif
diff --git a/bin/gui/egg-property-tree-view.c b/bin/gui/egg-property-tree-view.c
new file mode 100644
index 0000000..52d1e10
--- /dev/null
+++ b/bin/gui/egg-property-tree-view.c
@@ -0,0 +1,113 @@
+/* Copyright (C) 2011, 2012 Matthias Vogelgesang <matthias.vogelgesang@kit.edu>
+ (Karlsruhe Institute of Technology)
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by the
+ Free Software Foundation; either version 2.1 of the License, or (at your
+ option) any later version.
+
+ This library 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 Lesser General Public License for more
+ details.
+
+ You should have received a copy of the GNU Lesser General Public License along
+ with this library; if not, write to the Free Software Foundation, Inc., 51
+ Franklin St, Fifth Floor, Boston, MA 02110, USA */
+
+
+#include "egg-property-tree-view.h"
+#include "egg-property-cell-renderer.h"
+
+G_DEFINE_TYPE (EggPropertyTreeView, egg_property_tree_view, GTK_TYPE_TREE_VIEW)
+
+#define EGG_PROPERTY_TREE_VIEW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), EGG_TYPE_PROPERTY_TREE_VIEW, EggPropertyTreeViewPrivate))
+
+struct _EggPropertyTreeViewPrivate
+{
+ GtkListStore *list_store;
+};
+
+enum
+{
+ COLUMN_PROP_NAME,
+ COLUMN_PROP_ROW,
+ COLUMN_PROP_ADJUSTMENT,
+ N_COLUMNS
+};
+
+static void
+egg_property_tree_view_populate_model_with_properties (GtkListStore *model, GObject *object)
+{
+ GParamSpec **pspecs;
+ GObjectClass *oclass;
+ guint n_properties;
+ GtkTreeIter iter;
+
+ oclass = G_OBJECT_GET_CLASS (object);
+ pspecs = g_object_class_list_properties (oclass, &n_properties);
+
+ for (guint i = 0; i < n_properties; i++) {
+ if (pspecs[i]->flags & G_PARAM_READABLE) {
+ GtkObject *adjustment;
+
+ adjustment = gtk_adjustment_new (5, 0, 1000, 1, 10, 0);
+
+ gtk_list_store_append (model, &iter);
+ gtk_list_store_set (model, &iter,
+ COLUMN_PROP_NAME, pspecs[i]->name,
+ COLUMN_PROP_ROW, FALSE,
+ COLUMN_PROP_ADJUSTMENT, adjustment,
+ -1);
+ }
+ }
+
+ g_free (pspecs);
+}
+
+GtkWidget *
+egg_property_tree_view_new (GObject *object)
+{
+ EggPropertyTreeView *property_tree_view;
+ GtkTreeView *tree_view;
+ GtkTreeViewColumn *prop_column, *value_column;
+ GtkCellRenderer *prop_renderer, *value_renderer;
+ GtkListStore *list_store;
+
+ property_tree_view = EGG_PROPERTY_TREE_VIEW (g_object_new (EGG_TYPE_PROPERTY_TREE_VIEW, NULL));
+ list_store = property_tree_view->priv->list_store;
+ tree_view = GTK_TREE_VIEW (property_tree_view);
+
+ egg_property_tree_view_populate_model_with_properties (list_store, object);
+ gtk_tree_view_set_model (tree_view, GTK_TREE_MODEL (list_store));
+
+ prop_renderer = gtk_cell_renderer_text_new ();
+ prop_column = gtk_tree_view_column_new_with_attributes ("Property", prop_renderer,
+ "text", COLUMN_PROP_NAME,
+ NULL);
+
+ value_renderer = egg_property_cell_renderer_new (object, list_store);
+ value_column = gtk_tree_view_column_new_with_attributes ("Value", value_renderer,
+ "prop-name", COLUMN_PROP_NAME,
+ NULL);
+
+ gtk_tree_view_append_column (tree_view, prop_column);
+ gtk_tree_view_append_column (tree_view, value_column);
+
+ return GTK_WIDGET (tree_view);
+}
+
+static void
+egg_property_tree_view_class_init (EggPropertyTreeViewClass *klass)
+{
+ g_type_class_add_private (klass, sizeof (EggPropertyTreeViewPrivate));
+}
+
+static void
+egg_property_tree_view_init (EggPropertyTreeView *tree_view)
+{
+ EggPropertyTreeViewPrivate *priv = EGG_PROPERTY_TREE_VIEW_GET_PRIVATE (tree_view);
+
+ tree_view->priv = priv = EGG_PROPERTY_TREE_VIEW_GET_PRIVATE (tree_view);
+ priv->list_store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_BOOLEAN, GTK_TYPE_ADJUSTMENT);
+}
diff --git a/bin/gui/egg-property-tree-view.h b/bin/gui/egg-property-tree-view.h
new file mode 100644
index 0000000..e8fd0fe
--- /dev/null
+++ b/bin/gui/egg-property-tree-view.h
@@ -0,0 +1,54 @@
+/* Copyright (C) 2011, 2012 Matthias Vogelgesang <matthias.vogelgesang@kit.edu>
+ (Karlsruhe Institute of Technology)
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by the
+ Free Software Foundation; either version 2.1 of the License, or (at your
+ option) any later version.
+
+ This library 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 Lesser General Public License for more
+ details.
+
+ You should have received a copy of the GNU Lesser General Public License along
+ with this library; if not, write to the Free Software Foundation, Inc., 51
+ Franklin St, Fifth Floor, Boston, MA 02110, USA */
+
+#ifndef EGG_PROPERTY_TREE_VIEW_H
+#define EGG_PROPERTY_TREE_VIEW_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define EGG_TYPE_PROPERTY_TREE_VIEW (egg_property_tree_view_get_type())
+#define EGG_PROPERTY_TREE_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), EGG_TYPE_PROPERTY_TREE_VIEW, EggPropertyTreeView))
+#define EGG_IS_PROPERTY_TREE_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), EGG_TYPE_PROPERTY_TREE_VIEW))
+#define EGG_PROPERTY_TREE_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), EGG_TYPE_PROPERTY_TREE_VIEW, EggPropertyTreeViewClass))
+#define EGG_IS_PROPERTY_TREE_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), EGG_TYPE_PROPERTY_TREE_VIEW))
+#define EGG_PROPERTY_TREE_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), EGG_TYPE_PROPERTY_TREE_VIEW, EggPropertyTreeViewClass))
+
+typedef struct _EggPropertyTreeView EggPropertyTreeView;
+typedef struct _EggPropertyTreeViewClass EggPropertyTreeViewClass;
+typedef struct _EggPropertyTreeViewPrivate EggPropertyTreeViewPrivate;
+
+struct _EggPropertyTreeView
+{
+ GtkTreeView parent_instance;
+
+ /*< private >*/
+ EggPropertyTreeViewPrivate *priv;
+};
+
+struct _EggPropertyTreeViewClass
+{
+ GtkTreeViewClass parent_class;
+};
+
+GType egg_property_tree_view_get_type (void) G_GNUC_CONST;
+GtkWidget* egg_property_tree_view_new (GObject *object);
+
+G_END_DECLS
+
+#endif
diff --git a/bin/tools/CMakeLists.txt b/bin/tools/CMakeLists.txt
new file mode 100644
index 0000000..a52bb97
--- /dev/null
+++ b/bin/tools/CMakeLists.txt
@@ -0,0 +1,40 @@
+cmake_minimum_required(VERSION 2.8)
+
+add_definitions("--std=c99 -Wall")
+
+# --- Find packages and libraries ---------------------------------------------
+find_package(TIFF)
+
+# --- Build targets -----------------------------------------------------------
+include_directories(
+ ${CMAKE_CURRENT_BINARY_DIR}
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ )
+
+set(libs uca)
+
+if (TIFF_FOUND)
+ set(HAVE_LIBTIFF "1")
+ list(APPEND libs ${TIFF_LIBRARIES})
+ include_directories(${TIFF_INCLUDE_DIRS})
+endif ()
+
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in
+ ${CMAKE_CURRENT_BINARY_DIR}/config.h)
+
+# --- gen-doc
+add_executable(gen-doc gen-doc.c)
+target_link_libraries(gen-doc ${libs})
+
+# --- uca-grab
+add_executable(uca-grab grab.c)
+target_link_libraries(uca-grab
+ ringbuffer
+ ${libs})
+
+# --- uca-benchmark
+add_executable(benchmark benchmark.c)
+target_link_libraries(benchmark ${libs})
+
+install(TARGETS uca-grab
+ RUNTIME DESTINATION bin)
diff --git a/bin/tools/benchmark.c b/bin/tools/benchmark.c
new file mode 100644
index 0000000..0939d78
--- /dev/null
+++ b/bin/tools/benchmark.c
@@ -0,0 +1,272 @@
+/* Copyright (C) 2011, 2012 Matthias Vogelgesang <matthias.vogelgesang@kit.edu>
+ (Karlsruhe Institute of Technology)
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by the
+ Free Software Foundation; either version 2.1 of the License, or (at your
+ option) any later version.
+
+ This library 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 Lesser General Public License for more
+ details.
+
+ You should have received a copy of the GNU Lesser General Public License along
+ with this library; if not, write to the Free Software Foundation, Inc., 51
+ Franklin St, Fifth Floor, Boston, MA 02110, USA */
+
+#include <glib-object.h>
+#include <signal.h>
+#include <string.h>
+#include <stdlib.h>
+#include "uca-camera.h"
+#include "uca-plugin-manager.h"
+
+typedef void (*GrabFrameFunc) (UcaCamera *camera, gpointer buffer, guint n_frames);
+
+static UcaCamera *camera = NULL;
+
+static void
+sigint_handler(int signal)
+{
+ g_print ("Closing down libuca\n");
+ uca_camera_stop_recording (camera, NULL);
+ g_object_unref (camera);
+ exit (signal);
+}
+
+static void
+print_usage (void)
+{
+ GList *types;
+ UcaPluginManager *manager;
+
+ manager = uca_plugin_manager_new ();
+ g_print ("Usage: benchmark [ ");
+ types = uca_plugin_manager_get_available_cameras (manager);
+
+ if (types == NULL) {
+ g_print ("] -- no camera plugin found\n");
+ return;
+ }
+
+ for (GList *it = g_list_first (types); it != NULL; it = g_list_next (it)) {
+ gchar *name = (gchar *) it->data;
+ if (g_list_next (it) == NULL)
+ g_print ("%s ]\n", name);
+ else
+ g_print ("%s, ", name);
+ }
+}
+
+static void
+log_handler (const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer user)
+{
+ gsize n_written;
+ GError *error = NULL;
+ GIOChannel *channel = user;
+
+#if GLIB_CHECK_VERSION(2, 26, 0)
+ GTimeZone *tz;
+ GDateTime *date_time;
+ gchar *new_message;
+
+ tz = g_time_zone_new_local ();
+ date_time = g_date_time_new_now (tz);
+
+ new_message = g_strdup_printf ("[%s] %s\n",
+ g_date_time_format (date_time, "%FT%H:%M:%S%z"), message);
+
+ g_time_zone_unref (tz);
+ g_date_time_unref (date_time);
+
+ g_io_channel_write_chars (channel, new_message, strlen (new_message), &n_written, &error);
+ g_assert_no_error (error);
+ g_free (new_message);
+#else
+ g_io_channel_write_chars (channel, message, strlen (message), &n_written, &error);
+ g_assert_no_error (error);
+#endif
+
+ g_io_channel_flush (channel, &error);
+ g_assert_no_error (error);
+}
+
+static void
+grab_frames_sync (UcaCamera *camera, gpointer buffer, guint n_frames)
+{
+ GError *error = NULL;
+
+ uca_camera_start_recording (camera, &error);
+
+ for (guint i = 0; i < n_frames; i++) {
+ if (!uca_camera_grab (camera, buffer, &error))
+ g_warning ("Data stream ended");
+
+ if (error != NULL) {
+ g_warning ("Error grabbing frame %02i/%i: `%s'", i, n_frames, error->message);
+ g_error_free (error);
+ error = NULL;
+ }
+ }
+
+ uca_camera_stop_recording (camera, &error);
+}
+
+static void
+grab_callback (gpointer data, gpointer user_data)
+{
+ static GStaticMutex mutex = G_STATIC_MUTEX_INIT;
+ guint *n_acquired_frames = user_data;
+
+ g_static_mutex_lock (&mutex);
+ *n_acquired_frames += 1;
+ g_static_mutex_unlock (&mutex);
+}
+
+static void
+grab_frames_async (UcaCamera *camera, gpointer buffer, guint n_frames)
+{
+ GError *error = NULL;
+ guint n_acquired_frames = 0;
+
+ uca_camera_set_grab_func (camera, grab_callback, &n_acquired_frames);
+ uca_camera_start_recording (camera, &error);
+
+ /*
+ * Behold! Spinlooping is probably a bad idea but nowadays single core
+ * machines are relatively rare.
+ */
+ while (n_acquired_frames < n_frames)
+ ;
+
+ uca_camera_stop_recording (camera, &error);
+}
+
+static void
+benchmark_method (UcaCamera *camera, gpointer buffer, GrabFrameFunc func, guint n_runs, guint n_frames, guint n_bytes)
+{
+ GTimer *timer;
+ gdouble fps;
+ gdouble bandwidth;
+ gdouble total_time = 0.0;
+ GError *error = NULL;
+
+ g_print ("%-10i%-10i", n_frames, n_runs);
+ timer = g_timer_new ();
+ g_assert_no_error (error);
+
+ for (guint run = 0; run < n_runs; run++) {
+ g_message ("Start run %i of %i", run, n_runs);
+ g_timer_start (timer);
+
+ func (camera, buffer, n_frames);
+
+ g_timer_stop (timer);
+ total_time += g_timer_elapsed (timer, NULL);
+ }
+
+ g_assert_no_error (error);
+
+ fps = n_runs * n_frames / total_time;
+ bandwidth = n_bytes * fps / 1024 / 1024;
+ g_print ("%-16.2f%-16.2f\n", fps, bandwidth);
+
+ g_timer_destroy (timer);
+}
+
+static void
+benchmark (UcaCamera *camera)
+{
+ const guint n_runs = 3;
+ const guint n_frames = 100;
+
+ guint sensor_width;
+ guint sensor_height;
+ guint roi_width;
+ guint roi_height;
+ guint bits;
+ guint n_bytes_per_pixel;
+ guint n_bytes;
+ gdouble exposure = 0.00001;
+ gpointer buffer;
+
+ g_object_set (G_OBJECT (camera),
+ "exposure-time", exposure,
+ NULL);
+
+ g_object_get (G_OBJECT (camera),
+ "sensor-width", &sensor_width,
+ "sensor-height", &sensor_height,
+ "sensor-bitdepth", &bits,
+ "roi-width", &roi_width,
+ "roi-height", &roi_height,
+ "exposure-time", &exposure,
+ NULL);
+
+ g_print ("# --- General information ---\n");
+ g_print ("# Sensor size: %ix%i\n", sensor_width, sensor_height);
+ g_print ("# ROI size: %ix%i\n", roi_width, roi_height);
+ g_print ("# Exposure time: %fs\n", exposure);
+ g_print ("# Bits: %i\n", bits);
+
+ /* Synchronous frame acquisition */
+ g_print ("# %-10s%-10s%-10s%-16s%-16s\n", "type", "n_frames", "n_runs", "frames/s", "MiB/s");
+ g_print (" %-10s", "sync");
+
+ g_message ("Start synchronous benchmark");
+
+ n_bytes_per_pixel = bits > 8 ? 2 : 1;
+ n_bytes = roi_width * roi_height * n_bytes_per_pixel;
+ buffer = g_malloc0(n_bytes);
+
+ benchmark_method (camera, buffer, grab_frames_sync, n_runs, n_frames, n_bytes);
+
+ /* Asynchronous frame acquisition */
+ g_object_set (G_OBJECT(camera),
+ "transfer-asynchronously", TRUE,
+ NULL);
+
+ g_message ("Start asynchronous benchmark");
+ g_print (" %-10s", "async");
+
+ benchmark_method (camera, buffer, grab_frames_async, n_runs, n_frames, n_bytes);
+
+ g_free (buffer);
+}
+
+int
+main (int argc, char *argv[])
+{
+ UcaPluginManager *manager;
+ GIOChannel *log_channel;
+ GError *error = NULL;
+
+ (void) signal (SIGINT, sigint_handler);
+ g_type_init();
+
+ if (argc < 2) {
+ print_usage();
+ return 1;
+ }
+
+ log_channel = g_io_channel_new_file ("error.log", "a+", &error);
+ g_assert_no_error (error);
+ g_log_set_handler (NULL, G_LOG_LEVEL_MASK, log_handler, log_channel);
+
+ manager = uca_plugin_manager_new ();
+ camera = uca_plugin_manager_get_camera (manager, argv[1], &error, NULL);
+
+ if (camera == NULL) {
+ g_error ("Initialization: %s", error->message);
+ return 1;
+ }
+
+ benchmark (camera);
+
+ g_object_unref (camera);
+ g_io_channel_shutdown (log_channel, TRUE, &error);
+ g_assert_no_error (error);
+
+ return 0;
+}
diff --git a/bin/tools/config.h.in b/bin/tools/config.h.in
new file mode 100644
index 0000000..aea5237
--- /dev/null
+++ b/bin/tools/config.h.in
@@ -0,0 +1 @@
+#cmakedefine HAVE_LIBTIFF
diff --git a/bin/tools/gen-doc.c b/bin/tools/gen-doc.c
new file mode 100644
index 0000000..d27bdd8
--- /dev/null
+++ b/bin/tools/gen-doc.c
@@ -0,0 +1,224 @@
+/* Copyright (C) 2012 Matthias Vogelgesang <matthias.vogelgesang@kit.edu>
+ (Karlsruhe Institute of Technology)
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by the
+ Free Software Foundation; either version 2.1 of the License, or (at your
+ option) any later version.
+
+ This library 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 Lesser General Public License for more
+ details.
+
+ You should have received a copy of the GNU Lesser General Public License along
+ with this library; if not, write to the Free Software Foundation, Inc., 51
+ Franklin St, Fifth Floor, Boston, MA 02110, USA */
+
+#include <glib-object.h>
+#include "uca-plugin-manager.h"
+#include "uca-camera.h"
+
+
+static void
+print_usage (void)
+{
+ GList *types;
+ UcaPluginManager *manager;
+
+ manager = uca_plugin_manager_new ();
+ g_print ("Usage: gen-doc [ ");
+ types = uca_plugin_manager_get_available_cameras (manager);
+
+ if (types == NULL) {
+ g_print ("] -- no camera plugin found\n");
+ return;
+ }
+
+ for (GList *it = g_list_first (types); it != NULL; it = g_list_next (it)) {
+ gchar *name = (gchar *) it->data;
+ if (g_list_next (it) == NULL)
+ g_print ("%s ]\n", name);
+ else
+ g_print ("%s, ", name);
+ }
+}
+
+static const gchar *
+get_flags_description (GParamSpec *pspec)
+{
+ static const gchar *descriptions[] = {
+ "",
+ "Read-only",
+ "Write-only",
+ "Read / Write",
+ ""
+ };
+
+ if (pspec->flags >= 3)
+ return descriptions[3];
+
+ return descriptions[pspec->flags];
+}
+
+static void
+print_property_toc (GParamSpec **pspecs, guint n_props)
+{
+ g_print ("<h2>Properties</h2><ul id=\"toc\">");
+
+ for (guint i = 0; i < n_props; i++) {
+ GParamSpec *pspec = pspecs[i];
+ const gchar *name = g_param_spec_get_name (pspec);
+
+ g_print ("<li><code><a href=#%s>\"%s\"</a></code></li>", name, name);
+ }
+
+ g_print ("</ul>");
+}
+
+static void
+print_value_info (GParamSpec *pspec)
+{
+ gchar *default_value = NULL;
+ GString *range = g_string_new("");
+
+#define MAKE_RANGE(spec_type, fmt) \
+ { \
+ spec_type *spec = (spec_type *) pspec; \
+ g_string_printf (range, \
+ fmt" &#8804; <em>%s</em> &#8804; "fmt, \
+ spec->minimum, \
+ g_param_spec_get_name (pspec), \
+ spec->maximum); \
+ default_value = g_strdup_printf (fmt, spec->default_value); \
+ }
+
+ switch (pspec->value_type) {
+ case G_TYPE_BOOLEAN:
+ {
+ GParamSpecBoolean *spec = (GParamSpecBoolean *) pspec;
+ default_value = spec->default_value ? g_strdup ("<code>TRUE</code>") : g_strdup ("<code>FALSE</code>");
+ }
+ break;
+
+ case G_TYPE_UINT:
+ MAKE_RANGE (GParamSpecUInt, "%i");
+ break;
+
+ case G_TYPE_FLOAT:
+ MAKE_RANGE (GParamSpecFloat, "%.1e");
+ break;
+
+ case G_TYPE_DOUBLE:
+ MAKE_RANGE (GParamSpecDouble, "%.1e");
+ break;
+ }
+
+#undef MAKE_RANGE
+
+ if (g_type_is_a (pspec->value_type, G_TYPE_ENUM)) {
+ GParamSpecEnum *spec = (GParamSpecEnum *) pspec;
+
+ if (spec->enum_class->n_values > 0) {
+ g_string_printf (range, "<table><tr><th>Enum name</th><th>Value</th>");
+
+ for (guint i = 0; i < spec->enum_class->n_values; i++) {
+ GEnumValue *v = &spec->enum_class->values[i];
+ g_string_append_printf (range,
+ "<tr><td><code>%s</code></td><td>%i</td></tr>",
+ v->value_name, v->value);
+ }
+
+ g_string_append_printf (range, "</table>");
+ }
+ }
+
+ if (range->len > 0)
+ g_print ("<p>Possible values: %s</p>", range->str);
+
+ if (default_value != NULL) {
+ g_print ("<p>Default value: %s</p>", default_value);
+ g_free (default_value);
+ }
+
+ g_string_free (range, TRUE);
+}
+
+static void
+print_property_descriptions (GParamSpec **pspecs, guint n_props)
+{
+ g_print ("<h2>Details</h2><dl>");
+
+ for (guint i = 0; i < n_props; i++) {
+ GParamSpec *pspec = pspecs[i];
+ const gchar *name = g_param_spec_get_name (pspec);
+
+ g_print ("<dt id=\"%s\"><a href=\"#toc\">%s</a></dt>\n", name, name);
+ g_print ("<dd>");
+ g_print ("<pre><code class=\"prop-type\">\"%s\" : %s : %s</code></pre>\n",
+ name,
+ g_type_name (pspec->value_type),
+ get_flags_description (pspec));
+ g_print ("<p>%s</p>\n", g_param_spec_get_blurb (pspec));
+ print_value_info (pspec);
+ g_print ("</dd>");
+ }
+
+ g_print ("</dl>");
+}
+
+static void
+print_properties (UcaCamera *camera)
+{
+ GObjectClass *oclass;
+ GParamSpec **pspecs;
+ guint n_props;
+
+ oclass = G_OBJECT_GET_CLASS (camera);
+ pspecs = g_object_class_list_properties (oclass, &n_props);
+
+ print_property_toc (pspecs, n_props);
+ print_property_descriptions (pspecs, n_props);
+
+ g_free (pspecs);
+}
+
+static const gchar *html_header = "<html><head>\
+<link rel=\"stylesheet\" href=\"style.css\" type=\"text/css\" />\
+<link href='http://fonts.googleapis.com/css?family=Droid+Sans:400,700|Droid+Serif:400,400italic|Inconsolata' rel='stylesheet' type='text/css'>\
+<title>%s &mdash; properties</title></head><body>";
+static const gchar *html_footer = "</body></html>";
+
+int main(int argc, char *argv[])
+{
+ UcaPluginManager *manager;
+ UcaCamera *camera;
+ gchar *name;
+ GError *error = NULL;
+
+ g_type_init();
+ manager = uca_plugin_manager_new ();
+
+ if (argc < 2) {
+ name = g_strdup ("Basic camera");
+ camera = g_object_new (UCA_TYPE_CAMERA, NULL);
+ }
+ else {
+ name = argv[1];
+ camera = uca_plugin_manager_get_camera (manager, name, &error, NULL);
+ }
+
+ if (camera == NULL) {
+ g_print("Error during initialization: %s\n", error->message);
+ print_usage();
+ return 1;
+ }
+
+ g_print (html_header, name);
+ g_print ("<div id=\"header\"><h1 class=\"title\">Property documentation of %s</h1>", name);
+ print_properties (camera);
+ g_print ("%s\n", html_footer);
+
+ g_object_unref (camera);
+ g_object_unref (manager);
+}
diff --git a/bin/tools/grab.c b/bin/tools/grab.c
new file mode 100644
index 0000000..972f000
--- /dev/null
+++ b/bin/tools/grab.c
@@ -0,0 +1,292 @@
+/* Copyright (C) 2011, 2012 Matthias Vogelgesang <matthias.vogelgesang@kit.edu>
+ (Karlsruhe Institute of Technology)
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by the
+ Free Software Foundation; either version 2.1 of the License, or (at your
+ option) any later version.
+
+ This library 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 Lesser General Public License for more
+ details.
+
+ You should have received a copy of the GNU Lesser General Public License along
+ with this library; if not, write to the Free Software Foundation, Inc., 51
+ Franklin St, Fifth Floor, Boston, MA 02110, USA */
+
+#include "config.h"
+
+#include <glib-object.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "uca-plugin-manager.h"
+#include "uca-camera.h"
+#include "ring-buffer.h"
+
+#ifdef HAVE_LIBTIFF
+#include <tiffio.h>
+#endif
+
+
+typedef struct {
+ gint n_frames;
+ gdouble duration;
+ gchar *filename;
+} Options;
+
+
+static gchar *
+get_camera_list (void)
+{
+ GList *types;
+ GString *str;
+ UcaPluginManager *manager;
+
+ manager = uca_plugin_manager_new ();
+ types = uca_plugin_manager_get_available_cameras (manager);
+ str = g_string_new ("[ ");
+
+ if (types != NULL) {
+ for (GList *it = g_list_first (types); it != NULL; it = g_list_next (it)) {
+ gchar *name = (gchar *) it->data;
+
+ if (g_list_next (it) == NULL)
+ g_string_append_printf (str, "%s ]", name);
+ else
+ g_string_append_printf (str, "%s, ", name);
+ }
+ }
+ else {
+ g_string_append (str, "]");
+ }
+
+ g_object_unref (manager);
+ return g_string_free (str, FALSE);
+}
+
+static guint
+get_bytes_per_pixel (guint bits_per_pixel)
+{
+ return bits_per_pixel > 8 ? 2 : 1;
+}
+
+#ifdef HAVE_LIBTIFF
+static void
+write_tiff (RingBuffer *buffer,
+ Options *opts,
+ guint width,
+ guint height,
+ guint bits_per_pixel)
+{
+ TIFF *tif;
+ guint32 rows_per_strip;
+ guint n_frames;
+ guint bits_per_sample;
+ gsize bytes_per_pixel;
+
+ if (opts->filename)
+ tif = TIFFOpen (opts->filename, "w");
+ else
+ tif = TIFFOpen ("frames.tif", "w");
+
+ n_frames = ring_buffer_get_num_blocks (buffer);
+ rows_per_strip = TIFFDefaultStripSize (tif, (guint32) - 1);
+ bytes_per_pixel = get_bytes_per_pixel (bits_per_pixel);
+ bits_per_sample = bits_per_pixel > 8 ? 16 : 8;
+
+ /* Write multi page TIFF file */
+ TIFFSetField (tif, TIFFTAG_SUBFILETYPE, FILETYPE_PAGE);
+
+ for (guint i = 0; i < n_frames; i++) {
+ gpointer data;
+ gsize offset = 0;
+
+ data = ring_buffer_get_pointer (buffer, i);
+
+ TIFFSetField (tif, TIFFTAG_IMAGEWIDTH, width);
+ TIFFSetField (tif, TIFFTAG_IMAGELENGTH, height);
+ TIFFSetField (tif, TIFFTAG_BITSPERSAMPLE, bits_per_sample);
+ TIFFSetField (tif, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_UINT);
+ TIFFSetField (tif, TIFFTAG_SAMPLESPERPIXEL, 1);
+ TIFFSetField (tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
+ TIFFSetField (tif, TIFFTAG_ROWSPERSTRIP, rows_per_strip);
+ TIFFSetField (tif, TIFFTAG_PAGENUMBER, i, n_frames);
+
+ for (guint y = 0; y < height; y++, offset += width * bytes_per_pixel)
+ TIFFWriteScanline (tif, data + offset, y, 0);
+
+ TIFFWriteDirectory (tif);
+ }
+
+ TIFFClose (tif);
+}
+#else
+static void
+write_raw (RingBuffer *buffer,
+ Options *opts)
+{
+ guint n_frames;
+ gsize size;
+
+ size = ring_buffer_get_block_size (buffer);
+ n_frames = ring_buffer_get_num_blocks (buffer);
+
+ for (gint i = 0; i < n_frames; i++) {
+ FILE *fp;
+ gchar *filename;
+ gpointer data;
+
+ if (opts->filename)
+ filename = g_strdup_printf ("%s-%08i.raw", opts->filename, i);
+ else
+ filename = g_strdup_printf ("frame-%08i.raw", i);
+
+ fp = fopen(filename, "wb");
+ data = ring_buffer_get_pointer (buffer, i);
+
+ fwrite (data, size, 1, fp);
+ fclose (fp);
+ g_free (filename);
+ }
+}
+#endif
+
+static GError *
+record_frames (UcaCamera *camera, Options *opts)
+{
+ guint roi_width;
+ guint roi_height;
+ guint bits;
+ guint pixel_size;
+ gsize size;
+ gint n_frames;
+ guint n_allocated;
+ GTimer *timer;
+ RingBuffer *buffer;
+ GError *error = NULL;
+ gdouble last_printed;
+
+ g_object_get (G_OBJECT (camera),
+ "roi-width", &roi_width,
+ "roi-height", &roi_height,
+ "sensor-bitdepth", &bits,
+ NULL);
+
+ pixel_size = get_bytes_per_pixel (bits);
+ size = roi_width * roi_height * pixel_size;
+ n_allocated = opts->n_frames > 0 ? opts->n_frames : 256;
+ buffer = ring_buffer_new (size, n_allocated);
+ timer = g_timer_new();
+
+ g_print("Start recording: %ix%i at %i bits/pixel\n",
+ roi_width, roi_height, bits);
+
+ uca_camera_start_recording(camera, &error);
+
+ if (error != NULL)
+ return error;
+
+ n_frames = 0;
+ g_timer_start(timer);
+ last_printed = 0.0;
+
+ while (1) {
+ gdouble elapsed;
+
+ uca_camera_grab (camera, ring_buffer_get_current_pointer (buffer), &error);
+ ring_buffer_proceed (buffer);
+
+ if (error != NULL)
+ return error;
+
+ n_frames++;
+ elapsed = g_timer_elapsed (timer, NULL);
+
+ if (n_frames == opts->n_frames || (opts->duration > 0.0 && elapsed >= opts->duration))
+ break;
+
+ if (elapsed - last_printed >= 1.0) {
+ g_print ("Recorded %i frames at %.2f frames/s\n",
+ n_frames, n_frames / elapsed);
+ last_printed = elapsed;
+ }
+ }
+
+ g_print ("Stop recording: %3.2f frames/s\n",
+ n_frames / g_timer_elapsed (timer, NULL));
+
+ uca_camera_stop_recording (camera, &error);
+
+#ifdef HAVE_LIBTIFF
+ write_tiff (buffer, opts, roi_width, roi_height, bits);
+#else
+ write_raw (buffer, opts);
+#endif
+
+ ring_buffer_free (buffer);
+ g_timer_destroy (timer);
+
+ return error;
+}
+
+int
+main (int argc, char *argv[])
+{
+ GOptionContext *context;
+ UcaPluginManager *manager;
+ UcaCamera *camera;
+ gchar *cam_list;
+ GError *error = NULL;
+
+ static Options opts = {
+ .n_frames = -1,
+ .duration = -1.0,
+ .filename = NULL
+ };
+
+ static GOptionEntry entries[] = {
+ { "num-frames", 'n', 0, G_OPTION_ARG_INT, &opts.n_frames, "Number of frames to acquire", "N" },
+ { "duration", 'd', 0, G_OPTION_ARG_DOUBLE, &opts.duration, "Duration in seconds", NULL },
+ { "output", 'o', 0, G_OPTION_ARG_STRING, &opts.filename, "Output file name", "FILE" },
+ { NULL }
+ };
+
+ g_type_init();
+
+ cam_list = get_camera_list ();
+ context = g_option_context_new (cam_list);
+ g_option_context_add_main_entries (context, entries, NULL);
+ g_free (cam_list);
+
+ if (!g_option_context_parse (context, &argc, &argv, &error)) {
+ g_print ("Failed parsing arguments: %s\n", error->message);
+ exit (1);
+ }
+
+ if (argc < 2) {
+ g_print ("%s\n", g_option_context_get_help (context, TRUE, NULL));
+ exit (0);
+ }
+
+ if (opts.n_frames < 0 && opts.duration < 0.0) {
+ g_print ("You must specify at least one of --num-frames and --output.\n");
+ exit (1);
+ }
+
+ manager = uca_plugin_manager_new ();
+ camera = uca_plugin_manager_get_camera (manager, argv[1], &error, NULL);
+
+ if (camera == NULL) {
+ g_print ("Error during initialization: %s\n", error->message);
+ exit (1);
+ }
+
+ error = record_frames (camera, &opts);
+
+ if (error != NULL)
+ g_print ("Error: %s\n", error->message);
+
+ g_object_unref (camera);
+ return error != NULL ? 1 : 0;
+}