aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorThomas White <taw@bitwiz.me.uk>2019-03-28 15:05:10 +0100
committerThomas White <taw@bitwiz.me.uk>2019-03-28 15:05:10 +0100
commit6b60cafe0c2689531459f1cffd704da16ed2aec3 (patch)
treee41af753bb798e8b955434696c843c04dccf6bc5 /src
parent47764e46296e8c6921bbc00b95c05ff153699dc2 (diff)
Restore slideshow and clock
Diffstat (limited to 'src')
-rw-r--r--src/narrative_window.c245
-rw-r--r--src/pr_clock.c439
-rw-r--r--src/pr_clock.h37
-rw-r--r--src/slideshow.c222
-rw-r--r--src/slideshow.h79
5 files changed, 905 insertions, 117 deletions
diff --git a/src/narrative_window.c b/src/narrative_window.c
index c51d3a3..a6e71af 100644
--- a/src/narrative_window.c
+++ b/src/narrative_window.c
@@ -40,11 +40,10 @@
#include "narrative_window.h"
#include "slide_window.h"
#include "testcard.h"
-//#include "pr_clock.h"
+#include "pr_clock.h"
+#include "slideshow.h"
//#include "print.h"
//#include "stylesheet_editor.h"
-typedef struct _ss SCSlideshow; /* FIXME placeholder */
-typedef struct _pc PRClock; /* FIXME placeholder */
struct _narrative_window
{
@@ -109,25 +108,25 @@ static void update_titlebar(NarrativeWindow *nw)
static void update_toolbar(NarrativeWindow *nw)
{
-// int cur_para;
+ int cur_para, n_para;
- /* FIXME */
-// cur_para = sc_editor_get_cursor_para(nw->nv);
-// if ( cur_para == 0 ) {
-// gtk_widget_set_sensitive(GTK_WIDGET(nw->bfirst), FALSE);
-// gtk_widget_set_sensitive(GTK_WIDGET(nw->bprev), FALSE);
-// } else {
-// gtk_widget_set_sensitive(GTK_WIDGET(nw->bfirst), TRUE);
-// gtk_widget_set_sensitive(GTK_WIDGET(nw->bprev), TRUE);
-// }
-//
-// if ( cur_para == sc_editor_get_num_paras(nw->nv)-1 ) {
-// gtk_widget_set_sensitive(GTK_WIDGET(nw->bnext), FALSE);
-// gtk_widget_set_sensitive(GTK_WIDGET(nw->blast), FALSE);
-// } else {
-// gtk_widget_set_sensitive(GTK_WIDGET(nw->bnext), TRUE);
-// gtk_widget_set_sensitive(GTK_WIDGET(nw->blast), TRUE);
-// }
+ cur_para = gtk_narrative_view_get_cursor_para(GTK_NARRATIVE_VIEW(nw->nv));
+ if ( cur_para == 0 ) {
+ gtk_widget_set_sensitive(GTK_WIDGET(nw->bfirst), FALSE);
+ gtk_widget_set_sensitive(GTK_WIDGET(nw->bprev), FALSE);
+ } else {
+ gtk_widget_set_sensitive(GTK_WIDGET(nw->bfirst), TRUE);
+ gtk_widget_set_sensitive(GTK_WIDGET(nw->bprev), TRUE);
+ }
+
+ n_para = narrative_get_num_items(presentation_get_narrative(nw->p));
+ if ( cur_para == n_para - 1 ) {
+ gtk_widget_set_sensitive(GTK_WIDGET(nw->bnext), FALSE);
+ gtk_widget_set_sensitive(GTK_WIDGET(nw->blast), FALSE);
+ } else {
+ gtk_widget_set_sensitive(GTK_WIDGET(nw->bnext), TRUE);
+ gtk_widget_set_sensitive(GTK_WIDGET(nw->blast), TRUE);
+ }
}
@@ -318,87 +317,98 @@ static void add_slide_sig(GSimpleAction *action, GVariant *parameter,
static void first_para_sig(GSimpleAction *action, GVariant *parameter,
gpointer vp)
{
-// NarrativeWindow *nw = vp;
-// sc_editor_set_cursor_para(nw->nv, 0);
-// pr_clock_set_pos(nw->pr_clock, sc_editor_get_cursor_para(nw->nv),
-// sc_editor_get_num_paras(nw->nv));
-// update_toolbar(nw);
+ NarrativeWindow *nw = vp;
+ int n_paras = narrative_get_num_items(presentation_get_narrative(nw->p));
+ gtk_narrative_view_set_cursor_para(GTK_NARRATIVE_VIEW(nw->nv), 0);
+ pr_clock_set_pos(nw->pr_clock,
+ gtk_narrative_view_get_cursor_para(GTK_NARRATIVE_VIEW(nw->nv)),
+ n_paras);
+ update_toolbar(nw);
}
static void ss_prev_para(SCSlideshow *ss, void *vp)
{
-// NarrativeWindow *nw = vp;
-// if ( sc_editor_get_cursor_para(nw->nv) == 0 ) return;
-// sc_editor_set_cursor_para(nw->nv,
-// sc_editor_get_cursor_para(nw->nv)-1);
-// pr_clock_set_pos(nw->pr_clock, sc_editor_get_cursor_para(nw->nv),
-// sc_editor_get_num_paras(nw->nv));
-// update_toolbar(nw);
+ NarrativeWindow *nw = vp;
+ int n_paras = narrative_get_num_items(presentation_get_narrative(nw->p));
+ if ( gtk_narrative_view_get_cursor_para(GTK_NARRATIVE_VIEW(nw->nv)) == 0 ) return;
+ gtk_narrative_view_set_cursor_para(GTK_NARRATIVE_VIEW(nw->nv),
+ gtk_narrative_view_get_cursor_para(GTK_NARRATIVE_VIEW(nw->nv))-1);
+ pr_clock_set_pos(nw->pr_clock,
+ gtk_narrative_view_get_cursor_para(GTK_NARRATIVE_VIEW(nw->nv)),
+ n_paras);
+ update_toolbar(nw);
}
static void prev_para_sig(GSimpleAction *action, GVariant *parameter,
gpointer vp)
{
-// NarrativeWindow *nw = vp;
-// ss_prev_para(nw->show, nw);
+ NarrativeWindow *nw = vp;
+ ss_prev_para(nw->show, nw);
}
static void ss_next_para(SCSlideshow *ss, void *vp)
{
-// NarrativeWindow *nw = vp;
-// SCBlock *ns;
-//
-// sc_editor_set_cursor_para(nw->nv,
-// sc_editor_get_cursor_para(nw->nv)+1);
-//
-// /* If we only have one monitor, don't try to do paragraph counting */
-// if ( ss->single_monitor && !nw->show_no_slides ) {
-// int i, max;
-// max = sc_editor_get_num_paras(nw->nv);
-// for ( i=sc_editor_get_cursor_para(nw->nv); i<max; i++ ) {
-// SCBlock *ns;
-// sc_editor_set_cursor_para(nw->nv, i);
-// ns = sc_editor_get_cursor_bvp(nw->nv);
-// if ( ns != NULL ) break;
-// }
-// }
-//
-// pr_clock_set_pos(nw->pr_clock, sc_editor_get_cursor_para(nw->nv),
-// sc_editor_get_num_paras(nw->nv));
-// ns = sc_editor_get_cursor_bvp(nw->nv);
-// if ( ns != NULL ) {
-// sc_slideshow_set_slide(nw->show, ns);
-// }
-// update_toolbar(nw);
+ NarrativeWindow *nw = vp;
+ Slide *ns;
+ Narrative *narr;
+ GtkNarrativeView *nv;
+ int n_paras;
+
+ narr = presentation_get_narrative(nw->p);
+ n_paras = narrative_get_num_items(narr);
+ nv = GTK_NARRATIVE_VIEW(nw->nv);
+
+ gtk_narrative_view_set_cursor_para(nv, gtk_narrative_view_get_cursor_para(nv)+1);
+
+ /* If we only have one monitor, skip to next slide */
+ if ( ss->single_monitor && !nw->show_no_slides ) {
+ int i;
+ for ( i=gtk_narrative_view_get_cursor_para(nv); i<n_paras; i++ )
+ {
+ Slide *ns;
+ gtk_narrative_view_set_cursor_para(nv, i);
+ ns = narrative_get_slide(narr, i);
+ if ( ns != NULL ) break;
+ }
+ }
+
+ pr_clock_set_pos(nw->pr_clock, gtk_narrative_view_get_cursor_para(nv), n_paras);
+ ns = narrative_get_slide(narr, gtk_narrative_view_get_cursor_para(nv));
+ if ( ns != NULL ) {
+ sc_slideshow_set_slide(nw->show, ns);
+ }
+ update_toolbar(nw);
}
static void next_para_sig(GSimpleAction *action, GVariant *parameter,
gpointer vp)
{
-// NarrativeWindow *nw = vp;
-// ss_next_para(nw->show, nw);
+ NarrativeWindow *nw = vp;
+ ss_next_para(nw->show, nw);
}
static void last_para_sig(GSimpleAction *action, GVariant *parameter,
gpointer vp)
{
-// NarrativeWindow *nw = vp;
-// sc_editor_set_cursor_para(nw->nv, -1);
-// pr_clock_set_pos(nw->pr_clock, sc_editor_get_cursor_para(nw->nv),
-// sc_editor_get_num_paras(nw->nv));
-// update_toolbar(nw);
+ NarrativeWindow *nw = vp;
+ int n_paras = narrative_get_num_items(presentation_get_narrative(nw->p));
+ gtk_narrative_view_set_cursor_para(GTK_NARRATIVE_VIEW(nw->nv), -1);
+ pr_clock_set_pos(nw->pr_clock,
+ gtk_narrative_view_get_cursor_para(GTK_NARRATIVE_VIEW(nw->nv)),
+ n_paras);
+ update_toolbar(nw);
}
static void open_clock_sig(GSimpleAction *action, GVariant *parameter, gpointer vp)
{
- //NarrativeWindow *nw = vp;
-// nw->pr_clock = pr_clock_new();
+ NarrativeWindow *nw = vp;
+ nw->pr_clock = pr_clock_new();
}
@@ -547,7 +557,7 @@ static gboolean nw_key_press_sig(GtkWidget *da, GdkEventKey *event,
static gboolean ss_destroy_sig(GtkWidget *da, NarrativeWindow *nw)
{
nw->show = NULL;
- //sc_editor_set_para_highlight(nw->nv, 0); FIXME
+ gtk_narrative_view_set_para_highlight(GTK_NARRATIVE_VIEW(nw->nv), 0);
gtk_widget_set_sensitive(GTK_WIDGET(nw->bfirst), FALSE);
gtk_widget_set_sensitive(GTK_WIDGET(nw->bprev), FALSE);
@@ -561,74 +571,75 @@ static gboolean ss_destroy_sig(GtkWidget *da, NarrativeWindow *nw)
static void start_slideshow_here_sig(GSimpleAction *action, GVariant *parameter,
gpointer vp)
{
- //NarrativeWindow *nw = vp;
- //void *bvp;
+ NarrativeWindow *nw = vp;
+ Slide *slide;
- //if ( num_slides(nw->p) == 0 ) return;
+ if ( presentation_get_num_slides(nw->p) == 0 ) return;
- //bvp = sc_editor_get_cursor_bvp(nw->nv);
- //if ( bvp == NULL ) return;
+ slide = narrative_get_slide(presentation_get_narrative(nw->p),
+ gtk_narrative_view_get_cursor_para(GTK_NARRATIVE_VIEW(nw->nv)));
+ if ( slide == NULL ) return;
- //nw->show = sc_slideshow_new(nw->p, GTK_APPLICATION(nw->app));
- //if ( nw->show == NULL ) return;
+ nw->show = sc_slideshow_new(nw->p, GTK_APPLICATION(nw->app));
+ if ( nw->show == NULL ) return;
- //nw->show_no_slides = 0;
+ nw->show_no_slides = 0;
- //g_signal_connect(G_OBJECT(nw->show), "key-press-event",
- // G_CALLBACK(nw_key_press_sig), nw);
- //g_signal_connect(G_OBJECT(nw->show), "destroy",
- // G_CALLBACK(ss_destroy_sig), nw);
- //sc_slideshow_set_slide(nw->show, bvp);
- //sc_editor_set_para_highlight(nw->nv, 1);
- //gtk_widget_show_all(GTK_WIDGET(nw->show));
- //update_toolbar(nw);
+ g_signal_connect(G_OBJECT(nw->show), "key-press-event",
+ G_CALLBACK(nw_key_press_sig), nw);
+ g_signal_connect(G_OBJECT(nw->show), "destroy",
+ G_CALLBACK(ss_destroy_sig), nw);
+ sc_slideshow_set_slide(nw->show, slide);
+ gtk_narrative_view_set_para_highlight(GTK_NARRATIVE_VIEW(nw->nv), 1);
+ gtk_widget_show_all(GTK_WIDGET(nw->show));
+ update_toolbar(nw);
}
static void start_slideshow_noslides_sig(GSimpleAction *action, GVariant *parameter,
gpointer vp)
{
- //NarrativeWindow *nw = vp;
+ NarrativeWindow *nw = vp;
- //if ( num_slides(nw->p) == 0 ) return;
+ if ( presentation_get_num_slides(nw->p) == 0 ) return;
- //nw->show = sc_slideshow_new(nw->p, GTK_APPLICATION(nw->app));
- //if ( nw->show == NULL ) return;
+ nw->show = sc_slideshow_new(nw->p, GTK_APPLICATION(nw->app));
+ if ( nw->show == NULL ) return;
- //nw->show_no_slides = 1;
+ nw->show_no_slides = 1;
- //g_signal_connect(G_OBJECT(nw->show), "key-press-event",
- // G_CALLBACK(nw_key_press_sig), nw);
- //g_signal_connect(G_OBJECT(nw->show), "destroy",
- // G_CALLBACK(ss_destroy_sig), nw);
- //sc_slideshow_set_slide(nw->show, first_slide(nw->p));
- //sc_editor_set_para_highlight(nw->nv, 1);
- //sc_editor_set_cursor_para(nw->nv, 0);
- //update_toolbar(nw);
+ g_signal_connect(G_OBJECT(nw->show), "key-press-event",
+ G_CALLBACK(nw_key_press_sig), nw);
+ g_signal_connect(G_OBJECT(nw->show), "destroy",
+ G_CALLBACK(ss_destroy_sig), nw);
+ sc_slideshow_set_slide(nw->show, presentation_get_slide_by_number(nw->p, 0));
+ gtk_narrative_view_set_para_highlight(GTK_NARRATIVE_VIEW(nw->nv), 1);
+ gtk_narrative_view_set_cursor_para(GTK_NARRATIVE_VIEW(nw->nv), 0);
+ update_toolbar(nw);
}
static void start_slideshow_sig(GSimpleAction *action, GVariant *parameter,
gpointer vp)
{
-// NarrativeWindow *nw = vp;
-//
-// if ( num_slides(nw->p) == 0 ) return;
-//
-// nw->show = sc_slideshow_new(nw->p, GTK_APPLICATION(nw->app));
-// if ( nw->show == NULL ) return;
-//
-// nw->show_no_slides = 0;
-//
-// g_signal_connect(G_OBJECT(nw->show), "key-press-event",
-// G_CALLBACK(nw_key_press_sig), nw);
-// g_signal_connect(G_OBJECT(nw->show), "destroy",
-// G_CALLBACK(ss_destroy_sig), nw);
-// sc_slideshow_set_slide(nw->show, first_slide(nw->p));
-// sc_editor_set_para_highlight(nw->nv, 1);
-// sc_editor_set_cursor_para(nw->nv, 0);
-// gtk_widget_show_all(GTK_WIDGET(nw->show));
-// update_toolbar(nw);
+ NarrativeWindow *nw = vp;
+
+ if ( presentation_get_num_slides(nw->p) == 0 ) return;
+
+ nw->show = sc_slideshow_new(nw->p, GTK_APPLICATION(nw->app));
+ if ( nw->show == NULL ) return;
+
+ nw->show_no_slides = 0;
+
+ g_signal_connect(G_OBJECT(nw->show), "key-press-event",
+ G_CALLBACK(nw_key_press_sig), nw);
+ g_signal_connect(G_OBJECT(nw->show), "destroy",
+ G_CALLBACK(ss_destroy_sig), nw);
+ sc_slideshow_set_slide(nw->show, presentation_get_slide_by_number(nw->p, 0));
+ gtk_narrative_view_set_para_highlight(GTK_NARRATIVE_VIEW(nw->nv), 1);
+ gtk_narrative_view_set_cursor_para(GTK_NARRATIVE_VIEW(nw->nv), 0);
+ gtk_widget_show_all(GTK_WIDGET(nw->show));
+ update_toolbar(nw);
}
diff --git a/src/pr_clock.c b/src/pr_clock.c
new file mode 100644
index 0000000..aa1348e
--- /dev/null
+++ b/src/pr_clock.c
@@ -0,0 +1,439 @@
+/*
+ * pr_clock.c
+ *
+ * Copyright © 2013-2018 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 <libintl.h>
+#define _(x) gettext(x)
+
+#include "presentation.h"
+#include "pr_clock.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;
+ guint timer_id;
+
+ int running;
+ double time_allowed;
+ double time_elapsed;
+ int pos;
+ int end;
+ int pos_reached;
+
+ 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->end > 0 ) {
+ n->tf = (double)n->pos_reached / (n->end-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 pr_clock_set_pos(PRClock *n, int pos, int end)
+{
+ if ( n == NULL ) return;
+ n->pos = pos;
+ if ( n->pos > n->pos_reached ) {
+ n->pos_reached = pos;
+ }
+ n->end = end;
+ update_clock(n);
+}
+
+
+static gint close_clock_sig(GtkWidget *w, PRClock *n)
+{
+ g_source_remove(n->timer_id);
+ free(n);
+ return FALSE;
+}
+
+
+static gboolean clock_draw_sig(GtkWidget *da, cairo_t *cr, struct pr_clock *n)
+{
+ int width, height;
+ double s;
+ double ff;
+
+ width = gtk_widget_get_allocated_width(GTK_WIDGET(da));
+ height = gtk_widget_get_allocated_height(GTK_WIDGET(da));
+ s = width-20.0;
+
+ /* 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);
+ }
+
+ ff = (double)n->pos / (n->end-1);
+ if ( n->end == 1 ) ff = 0.0;
+ cairo_move_to(cr, 10.0+ff*s, 0.0);
+ cairo_line_to(cr, 10.0+ff*s, height);
+ cairo_set_line_width(cr, 2.0);
+ cairo_set_source_rgb(cr, 1.0, 0.0, 1.0);
+ cairo_stroke(cr);
+
+ if ( !n->running ) {
+ cairo_move_to(cr, 10.0, height*0.8);
+ cairo_set_font_size(cr, height*0.8);
+ cairo_select_font_face(cr, "sans-serif",
+ CAIRO_FONT_SLANT_NORMAL,
+ CAIRO_FONT_WEIGHT_BOLD);
+ cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
+ cairo_show_text(cr, _("Timer is NOT running!"));
+ }
+
+ 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;
+
+ 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 setpos_sig(GtkWidget *w, gpointer data)
+{
+ struct pr_clock *n = data;
+ n->pos_reached = n->pos;
+ update_clock(n);
+ return FALSE;
+}
+
+
+static gboolean start_sig(GtkWidget *w, gpointer data)
+{
+ struct pr_clock *n = data;
+
+ if ( n->running ) {
+ n->running = 0;
+ n->time_elapsed_at_start = n->time_elapsed;
+ 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"));
+ }
+
+ update_clock(n);
+
+ return FALSE;
+}
+
+
+PRClock *pr_clock_new()
+{
+ struct pr_clock *n;
+ GtkWidget *vbox;
+ GtkWidget *hbox;
+ GtkWidget *resetbutton;
+ GtkWidget *setposbutton;
+ GtkWidget *grid;
+ GtkWidget *label;
+
+ n = malloc(sizeof(struct pr_clock));
+ if ( n == NULL ) return NULL;
+ 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>"));
+ g_object_set(G_OBJECT(label), "halign", GTK_ALIGN_END, NULL);
+ 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);
+
+ setposbutton = gtk_button_new_with_label(_("Set position"));
+ gtk_box_pack_start(GTK_BOX(hbox), setposbutton, 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(clock_draw_sig), n);
+ g_signal_connect(G_OBJECT(n->window), "destroy",
+ G_CALLBACK(close_clock_sig), n); /* FIXME: Uniqueness */
+
+ 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(setposbutton), "clicked",
+ G_CALLBACK(setpos_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->pos = 0;
+ n->pos_reached = 0;
+ n->end = 0;
+ n->start = NULL;
+ update_clock(n);
+ n->timer_id = g_timeout_add_seconds(1, update_clock, n);
+
+ gtk_window_set_title(GTK_WINDOW(n->window), _("Presentation clock"));
+
+ gtk_widget_show_all(n->window);
+ return n;
+}
diff --git a/src/pr_clock.h b/src/pr_clock.h
new file mode 100644
index 0000000..97d2d0d
--- /dev/null
+++ b/src/pr_clock.h
@@ -0,0 +1,37 @@
+/*
+ * pr_clock.h
+ *
+ * Copyright © 2013-2018 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
+
+typedef struct pr_clock PRClock;
+
+extern PRClock *pr_clock_new(void);
+
+extern void pr_clock_set_pos(PRClock *n, int pos, int end);
+
+
+#endif /* CLOCK_H */
diff --git a/src/slideshow.c b/src/slideshow.c
new file mode 100644
index 0000000..1bd1930
--- /dev/null
+++ b/src/slideshow.c
@@ -0,0 +1,222 @@
+/*
+ * slideshow.c
+ *
+ * Copyright © 2013-2018 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 <gdk/gdkkeysyms.h>
+#include <libintl.h>
+#define _(x) gettext(x)
+
+#include <presentation.h>
+
+#include "slide_render_cairo.h"
+#include "slideshow.h"
+#include "colloquium.h"
+#include "pr_clock.h"
+
+G_DEFINE_TYPE_WITH_CODE(SCSlideshow, sc_slideshow, GTK_TYPE_WINDOW, NULL)
+
+
+static void sc_slideshow_init(SCSlideshow *ss)
+{
+}
+
+
+void sc_slideshow_class_init(SCSlideshowClass *klass)
+{
+}
+
+
+static void redraw(SCSlideshow *ss)
+{
+ gint w, h;
+ w = gtk_widget_get_allocated_width(GTK_WIDGET(ss->drawingarea));
+ h = gtk_widget_get_allocated_height(GTK_WIDGET(ss->drawingarea));
+ gtk_widget_queue_draw_area(ss->drawingarea, 0, 0, w, h);
+}
+
+
+static gint ssh_destroy_sig(GtkWidget *widget, SCSlideshow *ss)
+{
+ if ( ss->blank_cursor != NULL ) {
+ g_object_unref(ss->blank_cursor);
+ }
+ if ( ss->inhibit_cookie ) {
+ gtk_application_uninhibit(ss->app, ss->inhibit_cookie);
+ }
+ return FALSE;
+}
+
+
+static gboolean ss_draw_sig(GtkWidget *da, cairo_t *cr, SCSlideshow *ss)
+{
+ double dw, dh; /* Size of drawing area */
+ double lw, lh; /* Logical size of slide */
+ double sw, sh; /* Size of slide on screen */
+ double xoff, yoff;
+
+ dw = gtk_widget_get_allocated_width(GTK_WIDGET(da));
+ dh = gtk_widget_get_allocated_height(GTK_WIDGET(da));
+
+ /* Overall background */
+ cairo_rectangle(cr, 0.0, 0.0, dw, dh);
+ cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
+ cairo_fill(cr);
+
+ slide_get_logical_size(ss->cur_slide,
+ presentation_get_stylesheet(ss->p), &lw, &lh);
+
+ if ( lw/lh > (double)dw/dh ) {
+ /* Slide is too wide. Letterboxing top/bottom */
+ sw = dw;
+ sh = dw * lh/lw;
+ } else {
+ /* Letterboxing at sides */
+ sw = dh * lw/lh;
+ sh = dh;
+ }
+
+ xoff = (dw - sw)/2.0;
+ yoff = (dh - sh)/2.0;
+
+ if ( !ss->blank ) {
+
+ PangoContext *pc;
+ int n;
+ struct slide_pos sel;
+
+ cairo_save(cr);
+ cairo_translate(cr, xoff, yoff);
+ cairo_scale(cr, sw/lw, sh/lh);
+
+ sel.para = 0; sel.pos = 0; sel.trail = 0;
+ n = presentation_get_slide_number(ss->p, ss->cur_slide);
+ pc = pango_cairo_create_context(cr);
+
+ slide_render_cairo(ss->cur_slide, cr,
+ presentation_get_imagestore(ss->p),
+ presentation_get_stylesheet(ss->p),
+ n, pango_language_get_default(), pc,
+ NULL, sel, sel);
+
+ g_object_unref(pc);
+ cairo_restore(cr);
+
+ }
+
+ return FALSE;
+}
+
+
+static gboolean ss_realize_sig(GtkWidget *w, SCSlideshow *ss)
+{
+ if ( (ss->app == NULL) || colloquium_get_hidepointer(COLLOQUIUM(ss->app)) ) {
+
+ /* Hide the pointer */
+ GdkWindow *win;
+ win = gtk_widget_get_window(w);
+ ss->blank_cursor = gdk_cursor_new_for_display(gdk_display_get_default(),
+ GDK_BLANK_CURSOR);
+ gdk_window_set_cursor(GDK_WINDOW(win), ss->blank_cursor);
+
+ } else {
+ ss->blank_cursor = NULL;
+ }
+
+ return FALSE;
+}
+
+
+void sc_slideshow_set_slide(SCSlideshow *ss, Slide *ns)
+{
+ ss->cur_slide = ns;
+ redraw(ss);
+}
+
+
+SCSlideshow *sc_slideshow_new(Presentation *p, GtkApplication *app)
+{
+ GdkDisplay *display;
+ int n_monitors;
+ SCSlideshow *ss;
+
+ ss = g_object_new(SC_TYPE_SLIDESHOW, NULL);
+ if ( ss == NULL ) return NULL;
+
+ ss->blank = 0;
+ ss->p = p;
+ ss->cur_slide = NULL;
+ ss->blank_cursor = NULL;
+ ss->app = app;
+
+ ss->drawingarea = gtk_drawing_area_new();
+ gtk_container_add(GTK_CONTAINER(ss), ss->drawingarea);
+
+ gtk_widget_set_can_focus(GTK_WIDGET(ss->drawingarea), TRUE);
+ gtk_widget_add_events(GTK_WIDGET(ss->drawingarea),
+ GDK_KEY_PRESS_MASK);
+
+ g_signal_connect(G_OBJECT(ss), "destroy",
+ G_CALLBACK(ssh_destroy_sig), ss);
+ g_signal_connect(G_OBJECT(ss), "realize",
+ G_CALLBACK(ss_realize_sig), ss);
+ g_signal_connect(G_OBJECT(ss->drawingarea), "draw",
+ G_CALLBACK(ss_draw_sig), ss);
+
+ gtk_widget_grab_focus(GTK_WIDGET(ss->drawingarea));
+
+ display = gdk_display_get_default();
+ n_monitors = gdk_display_get_n_monitors(display);
+
+ GdkMonitor *mon_ss;
+ if ( n_monitors == 1 ) {
+ mon_ss = gdk_display_get_primary_monitor(display);
+ printf(_("Single monitor mode\n"));
+ ss->single_monitor = 1;
+ } else {
+ mon_ss = gdk_display_get_monitor(display, 1);
+ printf(_("Dual monitor mode\n"));
+ ss->single_monitor = 0;
+ }
+
+ /* Workaround because gtk_window_fullscreen_on_monitor doesn't work */
+ GdkRectangle rect;
+ gdk_monitor_get_geometry(mon_ss, &rect);
+ gtk_window_move(GTK_WINDOW(ss), rect.x, rect.y);
+ gtk_window_fullscreen(GTK_WINDOW(ss));
+
+ if ( app != NULL ) {
+ ss->inhibit_cookie = gtk_application_inhibit(app, GTK_WINDOW(ss),
+ GTK_APPLICATION_INHIBIT_IDLE,
+ _("Presentation slide show is running"));
+ }
+
+ return ss;
+}
+
diff --git a/src/slideshow.h b/src/slideshow.h
new file mode 100644
index 0000000..777b9f2
--- /dev/null
+++ b/src/slideshow.h
@@ -0,0 +1,79 @@
+/*
+ * slideshow.h
+ *
+ * Copyright © 2013-2019 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 SLIDESHOW_H
+#define SLIDESHOW_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gtk/gtk.h>
+
+#define SC_TYPE_SLIDESHOW (sc_slideshow_get_type())
+
+#define SC_SLIDESHOW(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \
+ SC_TYPE_SLIDESHOW, SCEditor))
+
+#define SC_IS_SLIDESHOW(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), \
+ SC_TYPE_SLIDESHOW))
+
+#define SC_SLIDESHOW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((obj), \
+ SC_TYPE_SLIDESHOW, SCEditorClass))
+
+#define SC_IS_SLIDESHOW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((obj), \
+ SC_TYPE_SLIDESHOW))
+
+#define SC_SLIDESHOW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), \
+ SC_TYPE_SLIDESHOW, SCSlideShowClass))
+
+struct _scslideshow
+{
+ GtkWindow parent_instance;
+
+ /* <private> */
+ Presentation *p;
+ Slide *cur_slide;
+ GtkWidget *drawingarea;
+ GdkCursor *blank_cursor;
+ int blank;
+ int xoff;
+ int yoff;
+ int single_monitor;
+ GtkApplication *app;
+ gint inhibit_cookie;
+};
+
+
+struct _scslideshowclass
+{
+ GtkWindowClass parent_class;
+};
+
+typedef struct _scslideshow SCSlideshow;
+typedef struct _scslideshowclass SCSlideshowClass;
+
+extern SCSlideshow *sc_slideshow_new(Presentation *p, GtkApplication *app);
+extern void sc_slideshow_set_slide(SCSlideshow *ss, Slide *ns);
+extern Slide *sc_slideshow_get_slide(SCSlideshow *ss);
+
+#endif /* SLIDESHOW_H */