summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/window.ui314
-rw-r--r--src/gui_app_window.c165
2 files changed, 340 insertions, 139 deletions
diff --git a/data/window.ui b/data/window.ui
index 9eefa36..2ca8457 100644
--- a/data/window.ui
+++ b/data/window.ui
@@ -25,183 +25,255 @@
<placeholder/>
</child>
<child>
- <object class="GtkBox">
+ <object class="GtkOverlay">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
- <object class="GtkDrawingArea" id="video">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <signal name="draw" handler="video_draw_cb" swapped="no"/>
- <signal name="size-allocate" handler="video_size_allocate_cb" swapped="no"/>
- </object>
- <packing>
- <property name="expand">True</property>
- <property name="fill">True</property>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="orientation">vertical</property>
- <property name="spacing">5</property>
- <child>
- <object class="GtkButton" id="start_stop_button">
- <property name="label">Start</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">True</property>
- <signal name="clicked" handler="start_stop_button_clicked_cb" swapped="no"/>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkComboBoxText" id="resolution_combo_box">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <signal name="changed" handler="resolution_combo_box_changed_cb" swapped="no"/>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">1</property>
- </packing>
- </child>
<child>
- <object class="GtkLabel">
+ <object class="GtkDrawingArea" id="video">
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="label" translatable="yes">Exposure (ms)</property>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">2</property>
- </packing>
- </child>
- <child>
- <object class="GtkSpinButton">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="input_purpose">digits</property>
- <property name="adjustment">exposure_adj</property>
- <property name="update_policy">if-valid</property>
+ <signal name="draw" handler="video_draw_cb" swapped="no"/>
+ <signal name="size-allocate" handler="video_size_allocate_cb" swapped="no"/>
</object>
<packing>
- <property name="expand">False</property>
+ <property name="expand">True</property>
<property name="fill">True</property>
- <property name="position">3</property>
- </packing>
- </child>
- <child>
- <object class="GtkLabel">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="label" translatable="yes">Gain</property>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">4</property>
- </packing>
- </child>
- <child>
- <object class="GtkSpinButton">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="input_purpose">digits</property>
- <property name="adjustment">gain_adj</property>
- <property name="digits">2</property>
- <property name="update_policy">if-valid</property>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">5</property>
+ <property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkButton" id="start_stop_button">
+ <property name="label">Start</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <signal name="clicked" handler="start_stop_button_clicked_cb" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBoxText" id="resolution_combo_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <signal name="changed" handler="resolution_combo_box_changed_cb" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Exposure (ms)</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="input_purpose">digits</property>
+ <property name="adjustment">exposure_adj</property>
+ <property name="update_policy">if-valid</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Gain</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="input_purpose">digits</property>
+ <property name="adjustment">gain_adj</property>
+ <property name="digits">2</property>
+ <property name="update_policy">if-valid</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkCheckButton" id="wb_button">
+ <property name="label" translatable="yes">White Balance</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="wb_button_toggled_cb" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="wb_cal_button">
+ <property name="label" translatable="yes">Cal.</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <signal name="clicked" handler="wb_cal_button_clicked_cb" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">6</property>
+ </packing>
+ </child>
<child>
- <object class="GtkCheckButton" id="wb_button">
- <property name="label" translatable="yes">White Balance</property>
+ <object class="GtkCheckButton" id="rotate_button">
+ <property name="label" translatable="yes">Rotate</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
- <signal name="toggled" handler="wb_button_toggled_cb" swapped="no"/>
+ <signal name="toggled" handler="rotate_button_toggled_cb" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
- <property name="position">0</property>
+ <property name="position">7</property>
</packing>
</child>
<child>
- <object class="GtkButton" id="wb_cal_button">
- <property name="label" translatable="yes">Cal.</property>
+ <object class="GtkButton" id="save_button">
+ <property name="label" translatable="yes">Save image</property>
<property name="visible">True</property>
+ <property name="sensitive">False</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
- <signal name="clicked" handler="wb_cal_button_clicked_cb" swapped="no"/>
+ <signal name="clicked" handler="save_button_clicked_cb" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
- <property name="position">1</property>
+ <property name="position">8</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="savepp_button">
+ <property name="label" translatable="yes">Save image (n+1)</property>
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <signal name="clicked" handler="savepp_button_clicked_cb" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">9</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
- <property name="position">6</property>
+ <property name="position">1</property>
</packing>
</child>
- <child>
- <object class="GtkCheckButton" id="rotate_button">
- <property name="label" translatable="yes">Rotate</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">False</property>
- <property name="draw_indicator">True</property>
- <signal name="toggled" handler="rotate_button_toggled_cb" swapped="no"/>
+ </object>
+ <packing>
+ <property name="index">-1</property>
+ </packing>
+ </child>
+ <child type="overlay">
+ <object class="GtkInfoBar" id="info_bar">
+ <property name="name">info_bar</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="no_show_all">True</property>
+ <property name="valign">end</property>
+ <property name="revealed">False</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox">
+ <property name="can_focus">False</property>
+ <property name="spacing">6</property>
+ <property name="layout_style">end</property>
+ <child>
+ <placeholder/>
+ </child>
</object>
<packing>
<property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">7</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
</packing>
</child>
- <child>
- <object class="GtkButton" id="save_button">
- <property name="label" translatable="yes">Save image</property>
- <property name="visible">True</property>
- <property name="sensitive">False</property>
- <property name="can_focus">True</property>
- <property name="receives_default">True</property>
- <signal name="clicked" handler="save_button_clicked_cb" swapped="no"/>
+ <child internal-child="content_area">
+ <object class="GtkBox">
+ <property name="can_focus">False</property>
+ <property name="spacing">16</property>
+ <child>
+ <object class="GtkLabel" id="info_bar_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">label</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
</object>
<packing>
<property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">8</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
</packing>
</child>
</object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">1</property>
- </packing>
</child>
</object>
</child>
diff --git a/src/gui_app_window.c b/src/gui_app_window.c
index 1068556..efde847 100644
--- a/src/gui_app_window.c
+++ b/src/gui_app_window.c
@@ -24,6 +24,7 @@
#include "utils.h"
+#include <errno.h>
#include <math.h>
#include <stdbool.h>
@@ -45,6 +46,9 @@ struct _GuiAppWindowPrivate {
GtkButton *wb_cal_button;
GtkCheckButton *rotate_button;
GtkButton *save_button;
+ GtkButton *savepp_button;
+ GtkInfoBar *info_bar;
+ GtkLabel *info_bar_label;
GdkRectangle allocation;
cairo_matrix_t transform;
cairo_surface_t *surface;
@@ -59,6 +63,9 @@ struct _GuiAppWindowPrivate {
struct device *device;
bool started;
gulong video_ready_handler_id;
+ char *save_filename;
+ const char *save_type;
+ guint info_bar_timeout_id;
};
G_DEFINE_TYPE_WITH_PRIVATE(GuiAppWindow, gui_app_window,
@@ -163,6 +170,8 @@ video_ready_cb(GuiApp *app, gpointer user_data)
image->height, image->stride);
gtk_widget_queue_draw(GTK_WIDGET(priv->video));
gtk_widget_set_sensitive(GTK_WIDGET(priv->save_button), TRUE);
+ if (priv->save_filename)
+ gtk_widget_set_sensitive(GTK_WIDGET(priv->savepp_button), TRUE);
}
}
}
@@ -216,6 +225,7 @@ video_stop(GuiAppWindowPrivate *priv)
gtk_widget_queue_draw(GTK_WIDGET(priv->video));
gtk_button_set_label(priv->start_stop_button, "Start");
gtk_widget_set_sensitive(GTK_WIDGET(priv->save_button), FALSE);
+ gtk_widget_set_sensitive(GTK_WIDGET(priv->savepp_button), FALSE);
}
static gboolean
@@ -251,6 +261,15 @@ destroy_cb(GtkWidget *widget, gpointer user_data)
{
GuiAppWindow *win = GUI_APP_WINDOW(widget);
GuiAppWindowPrivate *priv = gui_app_window_get_instance_private(win);
+ if (priv->info_bar_timeout_id) {
+ g_source_remove(priv->info_bar_timeout_id);
+ priv->info_bar_timeout_id = 0;
+ }
+ if (priv->save_filename) {
+ g_free(priv->save_filename);
+ priv->save_filename = NULL;
+ priv->save_type = NULL;
+ }
if (priv->started)
video_stop(priv);
if (priv->device) {
@@ -340,6 +359,56 @@ rotate_button_toggled_cb(GtkToggleButton *button, gpointer user_data)
video_size_update(priv);
}
+static gboolean
+info_bar_hide(gpointer user_data)
+{
+ GuiAppWindowPrivate *priv = user_data;
+ gtk_info_bar_set_revealed(priv->info_bar, FALSE);
+ priv->info_bar_timeout_id = 0;
+ return G_SOURCE_REMOVE;
+}
+
+static GdkPixbuf *
+save_get_pixbuf(GuiAppWindowPrivate *priv)
+{
+ GdkPixbuf *pixbuf = gdk_pixbuf_get_from_surface(priv->surface, 0, 0,
+ priv->width, priv->height);
+ if (priv->rotate && pixbuf) {
+ GdkPixbuf *rotated = gdk_pixbuf_rotate_simple(pixbuf, 180);
+ g_object_unref(pixbuf);
+ pixbuf = rotated;
+ }
+ return pixbuf;
+}
+
+/* Save an image, take ownership of filename. */
+static void
+save_image(GuiAppWindow *win, GuiAppWindowPrivate *priv, char *filename,
+ const char *type, GdkPixbuf *pixbuf)
+{
+ GError *error = NULL;
+ /* Save image. */
+ if (!gdk_pixbuf_save(pixbuf, filename, type, &error, NULL)) {
+ utils_dialog_error(GTK_WINDOW(win), "can not save image: %s", error->message);
+ g_error_free(error);
+ } else {
+ /* Keep information to save +1. */
+ if (priv->save_filename)
+ g_free(priv->save_filename);
+ priv->save_filename = filename;
+ priv->save_type = type;
+ gtk_widget_set_sensitive(GTK_WIDGET(priv->savepp_button), TRUE);
+ /* Display message. */
+ char *save_message = g_strdup_printf("Saved image \"%s\"", filename);
+ gtk_label_set_text(priv->info_bar_label, save_message);
+ g_free(save_message);
+ gtk_info_bar_set_revealed(priv->info_bar, TRUE);
+ if (priv->info_bar_timeout_id)
+ g_source_remove(priv->info_bar_timeout_id);
+ priv->info_bar_timeout_id = g_timeout_add(3000, info_bar_hide, priv);
+ }
+}
+
static void
save_button_clicked_cb(GtkButton *button, gpointer user_data)
{
@@ -347,13 +416,7 @@ save_button_clicked_cb(GtkButton *button, gpointer user_data)
GUI_APP_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(button)));
GuiAppWindowPrivate *priv = gui_app_window_get_instance_private(win);
if (priv->surface) {
- GdkPixbuf *pixbuf = gdk_pixbuf_get_from_surface(priv->surface, 0, 0,
- priv->width, priv->height);
- if (priv->rotate && pixbuf) {
- GdkPixbuf *rotated = gdk_pixbuf_rotate_simple(pixbuf, 180);
- g_object_unref(pixbuf);
- pixbuf = rotated;
- }
+ GdkPixbuf *pixbuf = save_get_pixbuf(priv);
if (pixbuf) {
GtkWidget *dialog = gtk_file_chooser_dialog_new("Save image",
GTK_WINDOW(win),
@@ -385,17 +448,10 @@ save_button_clicked_cb(GtkButton *button, gpointer user_data)
type = "png";
else if (filter == jpg_filter)
type = "jpeg";
- if (type) {
- GError *error = NULL;
- if (!gdk_pixbuf_save(pixbuf, filename, type, &error,
- NULL)) {
- utils_dialog_error(GTK_WINDOW(win),
- "can not save image: %s",
- error->message);
- g_error_free(error);
- }
- }
- g_free(filename);
+ if (type)
+ save_image(win, priv, filename, type, pixbuf);
+ else
+ g_free(filename);
}
}
gtk_widget_destroy(dialog);
@@ -405,6 +461,68 @@ save_button_clicked_cb(GtkButton *button, gpointer user_data)
}
static void
+savepp_button_clicked_cb(GtkButton *button, gpointer user_data)
+{
+ GuiAppWindow *win =
+ GUI_APP_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(button)));
+ GuiAppWindowPrivate *priv = gui_app_window_get_instance_private(win);
+ if (priv->surface && priv->save_filename) {
+ GdkPixbuf *pixbuf = save_get_pixbuf(priv);
+ if (pixbuf) {
+ /* Compute the new filename:
+ * - if there is a number, increment it, using the same number of
+ * digits,
+ * - else, add "_2". */
+ char *dirname = g_path_get_dirname(priv->save_filename);
+ char *basename = g_path_get_basename(priv->save_filename);
+ char **parts = g_strsplit(basename, ".", 2);
+ char *root = parts[0];
+ const char *extension = parts[1];
+ char *root_end = root + strlen(root);
+ char *numbers = root_end;
+ while (numbers != root && g_ascii_isdigit(numbers[-1]))
+ numbers--;
+ unsigned long n = 2;
+ const char *sep = "_";
+ int width = 1;
+ if (numbers != root_end) {
+ char *tail;
+ errno = 0;
+ /* n is unsigned so that it can be incremented even if value
+ * is maximum. */
+ long v = strtol(numbers, &tail, 10);
+ if (tail == root_end && errno == 0) {
+ n = (unsigned long)v + 1;
+ *numbers = '\0';
+ sep = "";
+ width = root_end - numbers;
+ }
+ }
+ char *new_basename = g_strdup_printf("%s%s%0*lu%s%s",
+ root, sep, width, n,
+ extension ? "." : "",
+ extension ? extension : "");
+ char *new_filename = g_build_filename(dirname, new_basename,
+ NULL);
+ /* Test for file existence. */
+ if (g_file_test(new_filename, G_FILE_TEST_EXISTS)) {
+ utils_dialog_error(GTK_WINDOW(win),
+ "Will not overwrite %s: file exists.", new_filename);
+ g_free(new_filename);
+ } else {
+ /* Save image. */
+ save_image(win, priv, new_filename, priv->save_type, pixbuf);
+ }
+ /* Cleanup. */
+ g_free(dirname);
+ g_free(basename);
+ g_strfreev(parts);
+ g_object_unref(pixbuf);
+ }
+ }
+}
+
+static void
gui_app_window_init(GuiAppWindow *win)
{
gtk_widget_init_template(GTK_WIDGET(win));
@@ -424,6 +542,9 @@ gui_app_window_init(GuiAppWindow *win)
priv->device = NULL;
priv->started = false;
priv->video_ready_handler_id = 0;
+ priv->save_filename = NULL;
+ priv->save_type = NULL;
+ priv->info_bar_timeout_id = 0;
}
static void
@@ -449,6 +570,12 @@ gui_app_window_class_init(GuiAppWindowClass *class)
GuiAppWindow, rotate_button);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS(class),
GuiAppWindow, save_button);
+ gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS(class),
+ GuiAppWindow, savepp_button);
+ gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS(class),
+ GuiAppWindow, info_bar);
+ gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS(class),
+ GuiAppWindow, info_bar_label);
gtk_widget_class_bind_template_callback(GTK_WIDGET_CLASS(class),
destroy_cb);
gtk_widget_class_bind_template_callback(GTK_WIDGET_CLASS(class),
@@ -471,6 +598,8 @@ gui_app_window_class_init(GuiAppWindowClass *class)
rotate_button_toggled_cb);
gtk_widget_class_bind_template_callback(GTK_WIDGET_CLASS(class),
save_button_clicked_cb);
+ gtk_widget_class_bind_template_callback(GTK_WIDGET_CLASS(class),
+ savepp_button_clicked_cb);
}
GuiAppWindow *