From b687096cd64902c4530fef9c9cae8a0494013137 Mon Sep 17 00:00:00 2001 From: Thomas White Date: Sun, 15 Sep 2013 18:10:00 +0200 Subject: Add the Presentation Clock --- src/mainwindow.c | 11 ++ src/pr_clock.c | 405 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/pr_clock.h | 38 +++++ src/presentation.h | 1 + src/slideshow.c | 5 + 5 files changed, 460 insertions(+) create mode 100644 src/pr_clock.c create mode 100644 src/pr_clock.h (limited to 'src') 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 + * + * 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 . + * + */ + + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include + +#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), "Length (mins):"); + 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), "Time elapsed"); + gtk_grid_attach(GTK_GRID(grid), label, 0, 0, 1, 1); + label = gtk_label_new("Time remaining"); + gtk_label_set_markup(GTK_LABEL(label), "Time remaining"); + gtk_grid_attach(GTK_GRID(grid), label, 1, 0, 1, 1); + n->status = gtk_label_new(""); + gtk_grid_attach(GTK_GRID(grid), n->status, 2, 0, 1, 1); + n->elapsed = gtk_label_new(""); + gtk_grid_attach(GTK_GRID(grid), n->elapsed, 0, 1, 1, 1); + n->remaining = gtk_label_new(""); + gtk_grid_attach(GTK_GRID(grid), n->remaining, 1, 1, 1, 1); + n->wallclock = gtk_label_new(""); + 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 + * + * 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 . + * + */ + +#ifndef CLOCK_H +#define CLOCK_H + +#ifdef HAVE_CONFIG_H +#include +#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); } -- cgit v1.2.3