aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas White <taw@bitwiz.org.uk>2013-09-15 18:10:00 +0200
committerThomas White <taw@bitwiz.org.uk>2013-09-15 18:10:00 +0200
commitb687096cd64902c4530fef9c9cae8a0494013137 (patch)
tree5f3172401f4df30fc156eebe74b28ac164a7dc4a
parent7f82ae3627592facfcdb84b187e03cafa654ac13 (diff)
Add the Presentation Clock
-rw-r--r--Makefile.am4
-rw-r--r--data/colloquium.ui1
-rw-r--r--src/mainwindow.c11
-rw-r--r--src/pr_clock.c405
-rw-r--r--src/pr_clock.h38
-rw-r--r--src/presentation.h1
-rw-r--r--src/slideshow.c5
7 files changed, 463 insertions, 2 deletions
diff --git a/Makefile.am b/Makefile.am
index a956a3c..859ae33 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -14,13 +14,13 @@ src_colloquium_SOURCES = src/colloquium.c src/render.c \
src/mainwindow.c src/presentation.c \
src/stylesheet.c src/loadsave.c src/frame.c \
src/slideshow.c src/wrap.c src/storycode.c \
- src/imagestore.c src/notes.c
+ src/imagestore.c src/notes.c src/pr_clock.c
INCLUDES = -Iharfatum/src
EXTRA_DIST += src/presentation.h src/render.h src/wrap.h \
src/stylesheet.h src/loadsave.h src/slideshow.h src/storycode.h \
- src/imagestore.h src/notes.h
+ src/imagestore.h src/notes.h src/pr_clock.h
src/default_stylesheet.o: src/default_stylesheet.sty
ld -r -b binary -o src/default_stylesheet.o src/default_stylesheet.sty
diff --git a/data/colloquium.ui b/data/colloquium.ui
index afd8702..0817b64 100644
--- a/data/colloquium.ui
+++ b/data/colloquium.ui
@@ -33,6 +33,7 @@
<menu name="tools" action="ToolsAction">
<menuitem name="slideshow" action="TSlideshowAction" />
<menuitem name="notes" action="NotesAction" />
+ <menuitem name="clock" action="ClockAction" />
<menuitem name="preferences" action="PrefsAction" />
</menu>
diff --git a/src/mainwindow.c b/src/mainwindow.c
index e3a5329..c3468dd 100644
--- a/src/mainwindow.c
+++ b/src/mainwindow.c
@@ -40,6 +40,7 @@
#include "slideshow.h"
#include "wrap.h"
#include "notes.h"
+#include "pr_clock.h"
/* Update a slide, once it's been edited in some way. */
@@ -325,6 +326,7 @@ static gint open_response_sig(GtkWidget *d, gint response,
rerender_slide(p);
update_toolbar(p);
update_style_menus(p);
+ if ( p->slideshow != NULL ) end_slideshow(p);
} else {
@@ -714,6 +716,13 @@ static gint open_notes_sig(GtkWidget *widget, struct presentation *p)
}
+static gint open_clock_sig(GtkWidget *widget, struct presentation *p)
+{
+ open_clock(p);
+ return FALSE;
+}
+
+
static void add_menu_bar(struct presentation *p, GtkWidget *vbox)
{
GError *error = NULL;
@@ -766,6 +775,8 @@ static void add_menu_bar(struct presentation *p, GtkWidget *vbox)
"F5", NULL, G_CALLBACK(start_slideshow_sig) },
{ "NotesAction", NULL, "_Open slide notes",
"F8", NULL, G_CALLBACK(open_notes_sig) },
+ { "ClockAction", NULL, "_Open presentation clock",
+ "F9", NULL, G_CALLBACK(open_clock_sig) },
{ "PrefsAction", GTK_STOCK_PREFERENCES, "_Preferences",
NULL, NULL, NULL },
diff --git a/src/pr_clock.c b/src/pr_clock.c
new file mode 100644
index 0000000..2526366
--- /dev/null
+++ b/src/pr_clock.c
@@ -0,0 +1,405 @@
+/*
+ * pr_clock.c
+ *
+ * Copyright © 2013 Thomas White <taw@bitwiz.org.uk>
+ *
+ * This file is part of Colloquium.
+ *
+ * Colloquium is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <gtk/gtk.h>
+
+#include "presentation.h"
+
+
+struct pr_clock
+{
+ int open;
+
+ GtkWidget *window;
+ GtkWidget *entry;
+ GtkWidget *startbutton;
+ GtkWidget *da;
+ GtkWidget *wallclock;
+ GtkWidget *elapsed;
+ GtkWidget *remaining;
+ GtkWidget *status;
+ GTimeZone *tz;
+
+ GDateTime *start;
+ double time_elapsed_at_start;
+
+ int running;
+ double time_allowed;
+ double time_elapsed;
+ int slide_reached;
+ int last_slide;
+ int cur_slide;
+
+ double t;
+ double tf;
+};
+
+
+static char *format_span(int n)
+{
+ char tmp[32];
+ int hours, mins, sec;
+ char *s;
+
+ if ( n < 0 ) {
+ s = "-";
+ n = -n;
+ } else {
+ s = "";
+ }
+
+ sec = n % 60;
+ mins = ((n-sec) % (60*60))/60;
+ hours = (n-sec-mins) / (60*60);
+
+ snprintf(tmp, 31, "%s%i:%02i:%02i", s, hours, mins, sec);
+
+ return strdup(tmp);
+}
+
+
+static char *format_span_nice(int n)
+{
+ char tmp[64];
+ int hours, mins, sec;
+ char *s;
+
+ if ( n < 0 ) {
+ s = "behind";
+ n = -n;
+ } else {
+ s = "ahead";
+ }
+
+ sec = n % 60;
+ mins = ((n-sec) % (60*60))/60;
+ hours = (n-sec-mins) / (60*60);
+
+ if ( n < 60 ) {
+ snprintf(tmp, 63, "%i seconds %s", n, s);
+ return strdup(tmp);
+ }
+
+ if ( n < 60*60 ) {
+ snprintf(tmp, 63, "%i min %i seconds %s", mins, sec, s);
+ return strdup(tmp);
+ }
+
+ snprintf(tmp, 63, "%i hours, %i min, %i seconds %s",
+ hours, mins, sec, s);
+ return strdup(tmp);
+}
+
+
+static gboolean update_clock(gpointer data)
+{
+ struct pr_clock *n = data;
+ gchar *d;
+ GDateTime *dt;
+ GTimeSpan sp;
+ double time_remaining;
+ double delta;
+ gint w, h;
+ char *tmp;
+
+ if ( !n->open ) {
+ g_date_time_unref(n->start);
+ g_time_zone_unref(n->tz);
+ free(n);
+ return FALSE;
+ }
+
+ dt = g_date_time_new_now(n->tz);
+
+ if ( n->running ) {
+
+ sp = g_date_time_difference(dt, n->start);
+ n->time_elapsed = n->time_elapsed_at_start +
+ sp / G_TIME_SPAN_SECOND;
+
+ time_remaining = n->time_allowed - n->time_elapsed;
+
+ tmp = format_span(n->time_elapsed);
+ gtk_label_set_text(GTK_LABEL(n->elapsed), tmp);
+ free(tmp);
+
+ tmp = format_span(time_remaining);
+ gtk_label_set_text(GTK_LABEL(n->remaining), tmp);
+ free(tmp);
+
+ } else {
+
+ n->time_elapsed = n->time_elapsed_at_start;
+
+ time_remaining = n->time_allowed - n->time_elapsed;
+
+ tmp = format_span(n->time_elapsed);
+ gtk_label_set_text(GTK_LABEL(n->elapsed), tmp);
+ free(tmp);
+
+ tmp = format_span(time_remaining);
+ gtk_label_set_text(GTK_LABEL(n->remaining), tmp);
+ free(tmp);
+
+ }
+
+ d = g_date_time_format(dt, "%H:%M:%S");
+ g_date_time_unref(dt);
+
+ gtk_label_set_text(GTK_LABEL(n->wallclock), d);
+ free(d);
+
+ n->t = n->time_elapsed / n->time_allowed;
+
+ if ( n->time_allowed == 0.0 ) n->t = 0.0;
+ if ( n->time_elapsed > n->time_allowed ) n->t = 1.0;
+
+ if ( n->last_slide > 0 ) {
+ n->tf = (double)n->slide_reached / (n->last_slide-1);
+ } else {
+ n->tf = 0.0;
+ }
+
+ delta = (n->tf - n->t)*n->time_allowed;
+ tmp = format_span_nice(delta);
+ gtk_label_set_text(GTK_LABEL(n->status), tmp);
+ free(tmp);
+
+ w = gtk_widget_get_allocated_width(GTK_WIDGET(n->da));
+ h = gtk_widget_get_allocated_height(GTK_WIDGET(n->da));
+ gtk_widget_queue_draw_area(n->da, 0, 0, w, h);
+
+ return TRUE;
+}
+
+
+void notify_clock_slide_changed(struct presentation *p, struct slide *np)
+{
+ struct pr_clock *n = p->clock;
+ int sr;
+
+ if ( n == NULL ) return;
+
+ sr = slide_number(p, np);
+ n->cur_slide = sr;
+ n->last_slide = p->num_slides;
+
+ if ( sr > n->slide_reached ) n->slide_reached = sr;
+
+ update_clock(n);
+}
+
+
+static gint close_clock_sig(GtkWidget *w, struct presentation *p)
+{
+ p->clock->open = 0;
+ p->clock = NULL;
+ return FALSE;
+}
+
+
+static gboolean draw_sig(GtkWidget *da, cairo_t *cr, struct pr_clock *n)
+{
+ int width, height;
+ double s;
+
+ width = gtk_widget_get_allocated_width(GTK_WIDGET(da));
+ height = gtk_widget_get_allocated_height(GTK_WIDGET(da));
+ s = width-20;
+
+ /* Overall background */
+ cairo_rectangle(cr, 10.0, 0.0, s, height);
+ cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
+ cairo_fill(cr);
+
+ cairo_rectangle(cr, 10.0, 0.0, s*n->t, height);
+ cairo_set_source_rgb(cr, 0.0, 0.0, 1.0);
+ cairo_fill(cr);
+
+ if ( n->tf > n->t ) {
+ cairo_rectangle(cr, 10.0+s*n->t, 0.0, (n->tf - n->t)*s, height);
+ cairo_set_source_rgb(cr, 0.0, 1.0, 0.0);
+ cairo_fill(cr);
+ } else {
+ cairo_rectangle(cr, 10.0+s*n->t, 0.0, (n->tf - n->t)*s, height);
+ cairo_set_source_rgb(cr, 1.0, 0.0, 0.0);
+ cairo_fill(cr);
+ }
+
+ return FALSE;
+}
+
+
+static void set_sig(GtkEditable *w, struct pr_clock *n)
+{
+ const gchar *t;
+ char *check;
+
+ t = gtk_entry_get_text(GTK_ENTRY(n->entry));
+ n->time_allowed = 60.0 * strtod(t, &check);
+ if ( check == t ) {
+ fprintf(stderr, "Invalid time '%s'\n", t);
+ n->time_allowed = 0.0;
+ }
+
+ update_clock(n);
+}
+
+
+static gboolean reset_sig(GtkWidget *w, gpointer data)
+{
+ struct pr_clock *n = data;
+
+ n->time_elapsed = 0;
+ n->time_elapsed_at_start = 0;
+ n->slide_reached = n->cur_slide;
+
+ if ( n->start != NULL ) {
+ g_date_time_unref(n->start);
+ }
+
+ n->start = g_date_time_new_now(n->tz);
+
+ update_clock(n);
+
+ return FALSE;
+}
+
+
+static gboolean start_sig(GtkWidget *w, gpointer data)
+{
+ struct pr_clock *n = data;
+
+ if ( n->running ) {
+ n->running = 0;
+ gtk_label_set_text(GTK_LABEL(gtk_bin_get_child(GTK_BIN(w))),
+ "Start");
+ } else {
+ n->time_elapsed_at_start = n->time_elapsed;
+ if ( n->start != NULL ) {
+ g_date_time_unref(n->start);
+ }
+ n->start = g_date_time_new_now(n->tz);
+ n->running = 1;
+ gtk_label_set_text(GTK_LABEL(gtk_bin_get_child(GTK_BIN(w))),
+ "Stop");
+ }
+
+ return FALSE;
+}
+
+
+void open_clock(struct presentation *p)
+{
+ struct pr_clock *n;
+ GtkWidget *vbox;
+ GtkWidget *hbox;
+ GtkWidget *resetbutton;
+ GtkWidget *grid;
+ GtkWidget *label;
+
+ if ( p->clock != NULL ) return; /* Already open */
+
+ n = malloc(sizeof(struct pr_clock));
+ if ( n == NULL ) return;
+ p->clock = n;
+ n->open = 1;
+
+ n->tz = g_time_zone_new_local();
+
+ n->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_default_size(GTK_WINDOW(n->window), 600, 150);
+
+ vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+ gtk_container_add(GTK_CONTAINER(n->window), vbox);
+
+ hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 10);
+
+ label = gtk_label_new("Length (mins):");
+ gtk_label_set_markup(GTK_LABEL(label), "<b>Length (mins):</b>");
+ gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.5);
+ gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 10);
+
+ n->entry = gtk_entry_new();
+ gtk_box_pack_start(GTK_BOX(hbox), n->entry, TRUE, TRUE, 0);
+
+ n->startbutton = gtk_button_new_with_label("Start");
+ gtk_box_pack_start(GTK_BOX(hbox), n->startbutton, TRUE, TRUE, 10);
+
+ resetbutton = gtk_button_new_with_label("Reset");
+ gtk_box_pack_start(GTK_BOX(hbox), resetbutton, TRUE, TRUE, 10);
+
+ n->da = gtk_drawing_area_new();
+ gtk_box_pack_start(GTK_BOX(vbox), n->da, TRUE, TRUE, 0);
+ g_signal_connect(G_OBJECT(n->da), "draw", G_CALLBACK(draw_sig), n);
+ g_signal_connect(G_OBJECT(n->window), "destroy",
+ G_CALLBACK(close_clock_sig), p);
+
+ grid = gtk_grid_new();
+ gtk_grid_set_row_spacing(GTK_GRID(grid), 10);
+ gtk_grid_set_column_homogeneous(GTK_GRID(grid), TRUE);
+ gtk_box_pack_start(GTK_BOX(vbox), grid, FALSE, FALSE, 10);
+ label = gtk_label_new("Time elapsed");
+ gtk_label_set_markup(GTK_LABEL(label), "<b>Time elapsed</b>");
+ gtk_grid_attach(GTK_GRID(grid), label, 0, 0, 1, 1);
+ label = gtk_label_new("Time remaining");
+ gtk_label_set_markup(GTK_LABEL(label), "<b>Time remaining</b>");
+ gtk_grid_attach(GTK_GRID(grid), label, 1, 0, 1, 1);
+ n->status = gtk_label_new("<status>");
+ gtk_grid_attach(GTK_GRID(grid), n->status, 2, 0, 1, 1);
+ n->elapsed = gtk_label_new("<elapsed>");
+ gtk_grid_attach(GTK_GRID(grid), n->elapsed, 0, 1, 1, 1);
+ n->remaining = gtk_label_new("<remaining>");
+ gtk_grid_attach(GTK_GRID(grid), n->remaining, 1, 1, 1, 1);
+ n->wallclock = gtk_label_new("<wall clock>");
+ gtk_grid_attach(GTK_GRID(grid), n->wallclock, 2, 1, 1, 1);
+
+ g_signal_connect(G_OBJECT(n->startbutton), "clicked",
+ G_CALLBACK(start_sig), n);
+ g_signal_connect(G_OBJECT(resetbutton), "clicked",
+ G_CALLBACK(reset_sig), n);
+ g_signal_connect(G_OBJECT(n->entry), "changed",
+ G_CALLBACK(set_sig), n);
+
+ n->running = 0;
+ n->time_allowed = 0;
+ n->time_elapsed = 0;
+ n->time_elapsed_at_start = 0;
+ n->slide_reached = 0;
+ n->last_slide = 0;
+ n->start = NULL;
+ update_clock(n);
+ g_timeout_add_seconds(1, update_clock, n);
+
+ gtk_window_set_title(GTK_WINDOW(n->window), "Presentation clock");
+
+ gtk_widget_show_all(n->window);
+}
diff --git a/src/pr_clock.h b/src/pr_clock.h
new file mode 100644
index 0000000..edebcff
--- /dev/null
+++ b/src/pr_clock.h
@@ -0,0 +1,38 @@
+/*
+ * pr_clock.h
+ *
+ * Copyright © 2013 Thomas White <taw@bitwiz.org.uk>
+ *
+ * This file is part of Colloquium.
+ *
+ * Colloquium is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef CLOCK_H
+#define CLOCK_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+struct pr_clock;
+
+extern void open_clock(struct presentation *p);
+
+extern void notify_clock_slide_changed(struct presentation *p,
+ struct slide *np);
+
+
+#endif /* CLOCK_H */
diff --git a/src/presentation.h b/src/presentation.h
index aed2ddf..84c79b0 100644
--- a/src/presentation.h
+++ b/src/presentation.h
@@ -126,6 +126,7 @@ struct presentation
ImageStore *is;
struct notes *notes;
+ struct pr_clock *clock;
/* Pointers to the current "editing" and "projection" slides */
struct slide *cur_edit_slide;
diff --git a/src/slideshow.c b/src/slideshow.c
index 6594be8..4847077 100644
--- a/src/slideshow.c
+++ b/src/slideshow.c
@@ -34,6 +34,7 @@
#include "presentation.h"
#include "mainwindow.h"
#include "render.h"
+#include "pr_clock.h"
/* Force a redraw of the slideshow */
@@ -103,6 +104,8 @@ void change_proj_slide(struct presentation *p, struct slide *np)
p->cur_proj_slide = np;
+ notify_clock_slide_changed(p, np);
+
/* The slide is already rendered, because the editor always gets there
* first, so we only need to do this: */
redraw_slideshow(p);
@@ -266,4 +269,6 @@ void try_start_slideshow(struct presentation *p)
//if ( p->prefs->open_notes ) open_notes(p); FIXME!
p->cur_proj_slide = p->cur_edit_slide;
+
+ notify_clock_slide_changed(p, p->cur_proj_slide);
}