Save the stylesheet
[colloquium.git] / src / narrative_window.c
index fc0e87b..3ff369c 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * narrative_window.c
  *
- * Copyright © 2014-2015 Thomas White <taw@bitwiz.org.uk>
+ * Copyright © 2014-2018 Thomas White <taw@bitwiz.org.uk>
  *
  * This file is part of Colloquium.
  *
@@ -30,6 +30,7 @@
 #include <string.h>
 #include <stdlib.h>
 
+#include "colloquium.h"
 #include "presentation.h"
 #include "narrative_window.h"
 #include "sc_editor.h"
@@ -38,6 +39,8 @@
 #include "testcard.h"
 #include "pr_clock.h"
 #include "print.h"
+#include "utils.h"
+#include "stylesheet_editor.h"
 
 
 struct _narrative_window
@@ -50,17 +53,35 @@ struct _narrative_window
        SCEditor *sceditor;
        GApplication *app;
        struct presentation *p;
-       SlideShow           *show;
-       SCBlock             *sel_slide;
+       SCBlock             *dummy_top;
+       SCSlideshow         *show;
+       int                  show_no_slides;
+       PRClock             *pr_clock;
 };
 
 
+static void show_error(NarrativeWindow *nw, const char *err)
+{
+       GtkWidget *mw;
+
+       mw = gtk_message_dialog_new(GTK_WINDOW(nw->window),
+                                   GTK_DIALOG_DESTROY_WITH_PARENT,
+                                   GTK_MESSAGE_ERROR,
+                                   GTK_BUTTONS_CLOSE, "%s", err);
+
+       g_signal_connect_swapped(mw, "response",
+                                G_CALLBACK(gtk_widget_destroy), mw);
+
+       gtk_widget_show(mw);
+}
+
+
 static void update_toolbar(NarrativeWindow *nw)
 {
-       int cur_slide_number;
+       int cur_para;
 
-       cur_slide_number = slide_number(nw->p, nw->sel_slide);
-       if ( cur_slide_number == 0 ) {
+       cur_para = sc_editor_get_cursor_para(nw->sceditor);
+       if ( cur_para == 0 ) {
                gtk_widget_set_sensitive(GTK_WIDGET(nw->bfirst), FALSE);
                gtk_widget_set_sensitive(GTK_WIDGET(nw->bprev), FALSE);
        } else {
@@ -68,7 +89,7 @@ static void update_toolbar(NarrativeWindow *nw)
                gtk_widget_set_sensitive(GTK_WIDGET(nw->bprev), TRUE);
        }
 
-       if ( cur_slide_number == num_slides(nw->p)-1 ) {
+       if ( cur_para == sc_editor_get_num_paras(nw->sceditor)-1 ) {
                gtk_widget_set_sensitive(GTK_WIDGET(nw->bnext), FALSE);
                gtk_widget_set_sensitive(GTK_WIDGET(nw->blast), FALSE);
        } else {
@@ -78,24 +99,61 @@ static void update_toolbar(NarrativeWindow *nw)
 }
 
 
-static gint saveas_response_sig(GtkWidget *d, gint response,
-                                NarrativeWindow *nw)
+struct saveas_info
 {
-       if ( response == GTK_RESPONSE_ACCEPT ) {
+       NarrativeWindow *nw;
+       GtkWidget *filechooser;
 
-               char *filename;
+       /* Radio buttons for how to save stylesheet */
+       GtkWidget *privatess;
+       GtkWidget *folderss;
+       GtkWidget *noss;
+};
 
-               filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(d));
 
-               if ( save_presentation(nw->p, filename) ) {
-                       //show_error(sw, "Failed to save presentation");
+static gint saveas_response_sig(GtkWidget *d, gint response,
+                                struct saveas_info *si)
+{
+       if ( response == 1 ) {  /* hard-coded number in Glade file */
+
+               GFile *file = gtk_file_chooser_get_file(GTK_FILE_CHOOSER(si->filechooser));
+               GFile *ssfile = NULL;
+
+               if ( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(si->privatess)) ) {
+                       gchar *ssuri;
+                       ssuri = g_file_get_uri(file);
+                       if ( ssuri != NULL ) {
+                               size_t l = strlen(ssuri);
+                               if ( ssuri[l-3] == '.' && ssuri[l-2] == 's' && ssuri[l-1] =='c' ) {
+                                       ssuri[l-1] = 's';
+                                       ssfile = g_file_new_for_uri(ssuri);
+                                       g_free(ssuri);
+                               }
+                       }
+               } else if ( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(si->folderss)) ) {
+                       GFile *parent;
+                       parent = g_file_get_parent(file);
+                       if ( parent != NULL ) {
+                               ssfile = g_file_get_child(parent, "stylesheet.ss");
+                               g_object_unref(parent);
+                       }
+               } else if ( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(si->noss)) ) {
+                       /* Do nothing */
+               } else {
+                       fprintf(stderr, _("Couldn't determine how to save stylesheet!\n"));
+               }
+
+               if ( save_presentation(si->nw->p, file, ssfile) ) {
+                       show_error(si->nw, _("Failed to save presentation"));
                }
 
-               g_free(filename);
+               g_object_unref(file);
+               g_object_unref(ssfile);
 
        }
 
        gtk_widget_destroy(d);
+       free(si);
 
        return 0;
 }
@@ -104,193 +162,266 @@ static gint saveas_response_sig(GtkWidget *d, gint response,
 static void saveas_sig(GSimpleAction *action, GVariant *parameter, gpointer vp)
 {
        GtkWidget *d;
+       GtkBuilder *builder;
        NarrativeWindow *nw = vp;
+       struct saveas_info *si;
 
-       d = gtk_file_chooser_dialog_new("Save Presentation",
-                                       GTK_WINDOW(nw->window),
-                                       GTK_FILE_CHOOSER_ACTION_SAVE,
-                                       "_Cancel", GTK_RESPONSE_CANCEL,
-                                       "_Open", GTK_RESPONSE_ACCEPT,
-                                       NULL);
-       gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(d),
-                                                      TRUE);
+       si = malloc(sizeof(struct saveas_info));
+       if ( si == NULL ) return;
 
-       g_signal_connect(G_OBJECT(d), "response",
-                        G_CALLBACK(saveas_response_sig), nw);
+       si->nw = nw;
+
+       builder = gtk_builder_new_from_resource("/uk/me/bitwiz/Colloquium/savepresentation.ui");
+       gtk_builder_add_callback_symbol(builder, "saveas_response_sig",
+                                       G_CALLBACK(saveas_response_sig));
+       gtk_builder_connect_signals(builder, si);
+       d = GTK_WIDGET(gtk_builder_get_object(builder, "savepresentation"));
+       si->filechooser = GTK_WIDGET(gtk_builder_get_object(builder, "filechooser"));
+       si->privatess = GTK_WIDGET(gtk_builder_get_object(builder, "privatess"));
+       si->folderss = GTK_WIDGET(gtk_builder_get_object(builder, "folderss"));
+       si->noss = GTK_WIDGET(gtk_builder_get_object(builder, "noss"));
+       gtk_window_set_transient_for(GTK_WINDOW(d), GTK_WINDOW(nw->window));
 
        gtk_widget_show_all(d);
 }
 
 
+static void about_sig(GSimpleAction *action, GVariant *parameter, gpointer vp)
+{
+       NarrativeWindow *nw = vp;
+       open_about_dialog(nw->window);
+}
+
+
 static void save_sig(GSimpleAction *action, GVariant *parameter, gpointer vp)
 {
        NarrativeWindow *nw = vp;
+       GFile *file;
 
-       if ( nw->p->filename == NULL ) {
+       if ( nw->p->uri == NULL ) {
                return saveas_sig(NULL, NULL, nw);
        }
 
-       save_presentation(nw->p, nw->p->filename);
+       file = g_file_new_for_uri(nw->p->uri);
+       save_presentation(nw->p, file, nw->p->stylesheet_from);
+       g_object_unref(file);
 }
 
 
-static void open_slidesorter_sig(GSimpleAction *action, GVariant *parameter,
-                                 gpointer vp)
+static void delete_slide_sig(GSimpleAction *action, GVariant *parameter,
+                              gpointer vp)
 {
-}
+       SCBlock *ns;
+       NarrativeWindow *nw = vp;
 
+       /* Get the SCBlock corresponding to the slide */
+       ns = sc_editor_get_cursor_bvp(nw->sceditor);
+       if ( ns == NULL ) {
+               fprintf(stderr, _("Not a slide!\n"));
+               return;
+       }
 
-static void delete_frame_sig(GSimpleAction *action, GVariant *parameter,
-                             gpointer vp)
-{
+       sc_block_delete(&nw->dummy_top, ns);
+
+       /* Full rerender */
+       sc_editor_set_scblock(nw->sceditor, nw->dummy_top);
+       nw->p->saved = 0;
+       update_titlebar(nw);
 }
 
 
-static void add_slide_sig(GSimpleAction *action, GVariant *parameter,
-                          gpointer vp)
+static gint load_ss_response_sig(GtkWidget *d, gint response,
+                                 NarrativeWindow *nw)
 {
-       SCBlock *nsblock;
-       NarrativeWindow *nw = vp;
+       if ( response == GTK_RESPONSE_ACCEPT ) {
 
-       /* Split the current paragraph */
-       nsblock = split_paragraph_at_cursor(nw->sceditor);
+               GFile *file;
+               Stylesheet *new_ss;
+
+               file = gtk_file_chooser_get_file(GTK_FILE_CHOOSER(d));
+
+               new_ss = stylesheet_load(file);
+               if ( new_ss != NULL ) {
+
+                       stylesheet_free(nw->p->stylesheet);
+                       nw->p->stylesheet = new_ss;
+                       sc_editor_set_stylesheet(nw->sceditor, new_ss);
+
+                       /* Full rerender */
+                       sc_editor_set_scblock(nw->sceditor, nw->dummy_top);
+
+               } else {
+                       fprintf(stderr, _("Failed to load\n"));
+               }
+
+               g_object_unref(file);
 
-       /* Link the new SCBlock in */
-       if ( nsblock != NULL ) {
-               sc_block_append(nsblock, "slide", NULL, NULL, NULL);
-       } else {
-               fprintf(stderr, "Failed to split paragraph\n");
        }
 
-       sc_editor_set_scblock(nw->sceditor,
-                             sc_editor_get_scblock(nw->sceditor));
+       gtk_widget_destroy(d);
+
+       return 0;
 }
 
 
-static void ss_end_show(SlideShow *ss, void *vp)
+static void stylesheet_changed_sig(GtkWidget *da, NarrativeWindow *nw)
 {
-       NarrativeWindow *nw = vp;
-       nw->show = NULL;
+       /* It might have changed (been created) since last time */
+       sc_editor_set_stylesheet(nw->sceditor, nw->p->stylesheet);
+
+       /* Full rerender, first block may have changed */
+       sc_editor_set_scblock(nw->sceditor, nw->dummy_top);
 }
 
 
-static void ss_next_slide(SlideShow *ss, void *vp)
+static void edit_ss_sig(GSimpleAction *action, GVariant *parameter,
+                        gpointer vp)
 {
        NarrativeWindow *nw = vp;
-       SCBlock *tt;
-
-       tt = next_slide(nw->p, nw->sel_slide);
-       if ( tt == NULL ) return;  /* Already on last slide */
-       nw->sel_slide = tt;
-       if ( slideshow_linked(nw->show) ) {
-               change_proj_slide(nw->show, nw->sel_slide);
-       } /* else leave the slideshow alone */
-       update_toolbar(nw);
+       StylesheetEditor *se;
+
+       se = stylesheet_editor_new(nw->p);
+       gtk_window_set_transient_for(GTK_WINDOW(se), GTK_WINDOW(nw->window));
+       g_signal_connect(G_OBJECT(se), "changed",
+                        G_CALLBACK(stylesheet_changed_sig), nw);
+       gtk_widget_show_all(GTK_WIDGET(se));
 }
 
 
-static void ss_prev_slide(SlideShow *ss, void *vp)
+static void load_ss_sig(GSimpleAction *action, GVariant *parameter,
+                        gpointer vp)
 {
+       //SCBlock *nsblock;
+       //SCBlock *templ;
        NarrativeWindow *nw = vp;
-       SCBlock *tt;
-
-       tt = prev_slide(nw->p, nw->sel_slide);
-       if ( tt == NULL ) return;  /* Already on first slide */
-       nw->sel_slide = tt;
-       if ( slideshow_linked(nw->show) ) {
-               change_proj_slide(nw->show, nw->sel_slide);
-       } /* else leave the slideshow alone */
-       update_toolbar(nw);
+       GtkWidget *d;
+
+       d = gtk_file_chooser_dialog_new(_("Load stylesheet"),
+                                       GTK_WINDOW(nw->window),
+                                       GTK_FILE_CHOOSER_ACTION_OPEN,
+                                       _("_Cancel"), GTK_RESPONSE_CANCEL,
+                                       _("_Open"), GTK_RESPONSE_ACCEPT,
+                                       NULL);
+
+       g_signal_connect(G_OBJECT(d), "response",
+                        G_CALLBACK(load_ss_response_sig), nw);
+
+       gtk_widget_show_all(d);
 }
 
 
-static void first_slide_sig(GSimpleAction *action, GVariant *parameter,
-                            gpointer vp)
+static void add_slide_sig(GSimpleAction *action, GVariant *parameter,
+                          gpointer vp)
 {
+       SCBlock *nsblock;
+       SCBlock *templ;
        NarrativeWindow *nw = vp;
-       SCBlock *tt;
-
-       tt = first_slide(nw->p);
-       if ( tt == NULL ) return;  /* Fail */
-       nw->sel_slide = tt;
-       if ( slideshow_linked(nw->show) ) {
-               change_proj_slide(nw->show, nw->sel_slide);
-       } /* else leave the slideshow alone */
-       update_toolbar(nw);
-}
 
+       sc_editor_ensure_cursor(nw->sceditor);
 
-static void prev_slide_sig(GSimpleAction *action, GVariant *parameter,
-                           gpointer vp)
-{
-       ss_prev_slide(NULL, vp);
-}
+       /* Split the current paragraph */
+       nsblock = split_paragraph_at_cursor(nw->sceditor);
 
+       /* FIXME: Template from JSON */
+       templ = sc_parse("\\slide{}");
 
-static void next_slide_sig(GSimpleAction *action, GVariant *parameter,
-                           gpointer vp)
-{
-       ss_next_slide(NULL, vp);
+       /* Link the new SCBlock in */
+       if ( nsblock != NULL ) {
+               sc_block_append_p(nsblock, templ);
+       } else {
+               fprintf(stderr, _("Failed to split paragraph\n"));
+       }
+
+       sc_editor_set_scblock(nw->sceditor, nw->dummy_top);
+       nw->p->saved = 0;
+       update_titlebar(nw);
 }
 
 
-static void last_slide_sig(GSimpleAction *action, GVariant *parameter,
+static void first_para_sig(GSimpleAction *action, GVariant *parameter,
                            gpointer vp)
 {
        NarrativeWindow *nw = vp;
-       SCBlock *tt;
-
-       tt = last_slide(nw->p);
-       if ( tt == NULL ) return;  /* Fail */
-       nw->sel_slide = tt;
-       if ( slideshow_linked(nw->show) ) {
-               change_proj_slide(nw->show, nw->sel_slide);
-       } /* else leave the slideshow alone */
+       sc_editor_set_cursor_para(nw->sceditor, 0);
+       pr_clock_set_pos(nw->pr_clock, sc_editor_get_cursor_para(nw->sceditor),
+                                      sc_editor_get_num_paras(nw->sceditor));
        update_toolbar(nw);
 }
 
 
-static void ss_changed_link(SlideShow *ss, void *vp)
+static void ss_prev_para(SCSlideshow *ss, void *vp)
 {
+       NarrativeWindow *nw = vp;
+       if ( sc_editor_get_cursor_para(nw->sceditor) == 0 ) return;
+       sc_editor_set_cursor_para(nw->sceditor,
+                                 sc_editor_get_cursor_para(nw->sceditor)-1);
+       pr_clock_set_pos(nw->pr_clock, sc_editor_get_cursor_para(nw->sceditor),
+                                      sc_editor_get_num_paras(nw->sceditor));
+       update_toolbar(nw);
 }
 
 
-static SCBlock *ss_cur_slide(SlideShow *ss, void *vp)
+static void prev_para_sig(GSimpleAction *action, GVariant *parameter,
+                          gpointer vp)
 {
        NarrativeWindow *nw = vp;
-       return nw->sel_slide;
+       ss_prev_para(nw->show, nw);
 }
 
 
-static void start_slideshow_sig(GSimpleAction *action, GVariant *parameter,
-                                gpointer vp)
+static void ss_next_para(SCSlideshow *ss, void *vp)
 {
        NarrativeWindow *nw = vp;
-       struct sscontrolfuncs ssc;
-
-       if ( num_slides(nw->p) == 0 ) return;
+       SCBlock *ns;
+
+       sc_editor_set_cursor_para(nw->sceditor,
+                                 sc_editor_get_cursor_para(nw->sceditor)+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->sceditor);
+               for ( i=sc_editor_get_cursor_para(nw->sceditor); i<max; i++ ) {
+                       SCBlock *ns;
+                       sc_editor_set_cursor_para(nw->sceditor, i);
+                       ns = sc_editor_get_cursor_bvp(nw->sceditor);
+                       if ( ns != NULL ) break;
+               }
+       }
 
-       ssc.next_slide = ss_next_slide;
-       ssc.prev_slide = ss_prev_slide;
-       ssc.current_slide = ss_cur_slide;
-       ssc.changed_link = ss_changed_link;
-       ssc.end_show = ss_end_show;
+       pr_clock_set_pos(nw->pr_clock, sc_editor_get_cursor_para(nw->sceditor),
+                                      sc_editor_get_num_paras(nw->sceditor));
+       ns = sc_editor_get_cursor_bvp(nw->sceditor);
+       if ( ns != NULL ) {
+               sc_slideshow_set_slide(nw->show, ns);
+       }
+       update_toolbar(nw);
+}
 
-       nw->sel_slide = first_slide(nw->p);
 
-       nw->show = try_start_slideshow(nw->p, ssc, nw);
+static void next_para_sig(GSimpleAction *action, GVariant *parameter,
+                          gpointer vp)
+{
+       NarrativeWindow *nw = vp;
+       ss_next_para(nw->show, nw);
 }
 
 
-static void open_notes_sig(GSimpleAction *action, GVariant *parameter, gpointer vp)
+static void last_para_sig(GSimpleAction *action, GVariant *parameter,
+                          gpointer vp)
 {
+       NarrativeWindow *nw = vp;
+       sc_editor_set_cursor_para(nw->sceditor, -1);
+       pr_clock_set_pos(nw->pr_clock, sc_editor_get_cursor_para(nw->sceditor),
+                                      sc_editor_get_num_paras(nw->sceditor));
+       update_toolbar(nw);
 }
 
 
 static void open_clock_sig(GSimpleAction *action, GVariant *parameter, gpointer vp)
 {
        NarrativeWindow *nw = vp;
-       open_clock(nw->p);
+       nw->pr_clock = pr_clock_new();
 }
 
 
@@ -318,57 +449,47 @@ static gint export_pdf_response_sig(GtkWidget *d, gint response,
 }
 
 
+static void print_sig(GSimpleAction *action, GVariant *parameter, gpointer vp)
+{
+       NarrativeWindow *nw = vp;
+       run_printing(nw->p, nw->window);
+}
+
+
 static void exportpdf_sig(GSimpleAction *action, GVariant *parameter,
                           gpointer vp)
 {
-       struct presentation *p = vp;
+       NarrativeWindow *nw = vp;
        GtkWidget *d;
 
-       d = gtk_file_chooser_dialog_new("Export PDF",
+       d = gtk_file_chooser_dialog_new(_("Export PDF"),
                                        NULL,
                                        GTK_FILE_CHOOSER_ACTION_SAVE,
-                                       "_Cancel", GTK_RESPONSE_CANCEL,
-                                       "_Export", GTK_RESPONSE_ACCEPT,
+                                       _("_Cancel"), GTK_RESPONSE_CANCEL,
+                                       _("_Export"), GTK_RESPONSE_ACCEPT,
                                        NULL);
        gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(d),
                                                       TRUE);
 
        g_signal_connect(G_OBJECT(d), "response",
-                        G_CALLBACK(export_pdf_response_sig), p);
+                        G_CALLBACK(export_pdf_response_sig), nw->p);
 
        gtk_widget_show_all(d);
 }
 
 
 
-GActionEntry nw_entries[] = {
-
-       { "save", save_sig, NULL, NULL, NULL },
-       { "saveas", saveas_sig, NULL, NULL, NULL },
-       { "sorter", open_slidesorter_sig, NULL, NULL, NULL },
-       { "deleteframe", delete_frame_sig, NULL, NULL, NULL },
-       { "slide", add_slide_sig, NULL, NULL, NULL },
-       { "startslideshow", start_slideshow_sig, NULL, NULL, NULL },
-       { "notes", open_notes_sig, NULL, NULL, NULL },
-       { "clock", open_clock_sig, NULL, NULL, NULL },
-       { "testcard", testcard_sig, NULL, NULL, NULL },
-       { "first", first_slide_sig, NULL, NULL, NULL },
-       { "prev", prev_slide_sig, NULL, NULL, NULL },
-       { "next", next_slide_sig, NULL, NULL, NULL },
-       { "last", last_slide_sig, NULL, NULL, NULL },
-};
-
-
-GActionEntry nw_entries_p[] = {
-       { "print", print_sig, NULL, NULL, NULL  },
-       { "exportpdf", exportpdf_sig, NULL, NULL, NULL  },
-};
+static gboolean nw_button_press_sig(GtkWidget *da, GdkEventButton *event,
+                                    NarrativeWindow *nw)
+{
+       return 0;
+}
 
 
-static gboolean button_press_sig(GtkWidget *da, GdkEventButton *event,
-                                 NarrativeWindow *nw)
+static void changed_sig(GtkWidget *da, NarrativeWindow *nw)
 {
-       return 0;
+       nw->p->saved = 0;
+       update_titlebar(nw);
 }
 
 
@@ -384,15 +505,15 @@ static void scroll_down(NarrativeWindow *nw)
 }
 
 
-static gboolean destroy_sig(GtkWidget *da, NarrativeWindow *nw)
+static gboolean nw_destroy_sig(GtkWidget *da, NarrativeWindow *nw)
 {
        g_application_release(nw->app);
        return FALSE;
 }
 
 
-static gboolean key_press_sig(GtkWidget *da, GdkEventKey *event,
-                              NarrativeWindow *nw)
+static gboolean nw_key_press_sig(GtkWidget *da, GdkEventKey *event,
+                                 NarrativeWindow *nw)
 {
        switch ( event->keyval ) {
 
@@ -406,34 +527,135 @@ static gboolean key_press_sig(GtkWidget *da, GdkEventKey *event,
 
                case GDK_KEY_Page_Up :
                if ( nw->show != NULL ) {
-                       ss_prev_slide(nw->show, nw);
+                       ss_prev_para(nw->show, nw);
                        return TRUE;
                }
                break;
 
                case GDK_KEY_Page_Down :
                if ( nw->show != NULL) {
-                       ss_next_slide(nw->show, nw);
+                       ss_next_para(nw->show, nw);
                        return TRUE;
                }
                break;
 
+               case GDK_KEY_Escape :
+               if ( nw->show != NULL ) {
+                       gtk_widget_destroy(GTK_WIDGET(nw->show));
+                       return TRUE;
+               }
+               break;
+
+               case GDK_KEY_F5 :
+               if ( nw->show != NULL ) {
+                       /* Trap F5 so that full rerender does NOT happen */
+                       return TRUE;
+               }
+
        }
 
        return FALSE;
 }
 
 
+static gboolean ss_destroy_sig(GtkWidget *da, NarrativeWindow *nw)
+{
+       nw->show = NULL;
+       sc_editor_set_para_highlight(nw->sceditor, 0);
+
+       gtk_widget_set_sensitive(GTK_WIDGET(nw->bfirst), FALSE);
+       gtk_widget_set_sensitive(GTK_WIDGET(nw->bprev), FALSE);
+       gtk_widget_set_sensitive(GTK_WIDGET(nw->bnext), FALSE);
+       gtk_widget_set_sensitive(GTK_WIDGET(nw->blast), FALSE);
+
+       return FALSE;
+}
+
+
+static void start_slideshow_here_sig(GSimpleAction *action, GVariant *parameter,
+                                     gpointer vp)
+{
+       NarrativeWindow *nw = vp;
+       void *bvp;
+
+       if ( num_slides(nw->p) == 0 ) return;
+
+       bvp = sc_editor_get_cursor_bvp(nw->sceditor);
+       if ( bvp == NULL ) 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, bvp);
+       sc_editor_set_para_highlight(nw->sceditor, 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;
+
+       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 = 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->sceditor, 1);
+       sc_editor_set_cursor_para(nw->sceditor, 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->sceditor, 1);
+       sc_editor_set_cursor_para(nw->sceditor, 0);
+       gtk_widget_show_all(GTK_WIDGET(nw->show));
+       update_toolbar(nw);
+}
+
+
 static void nw_update_titlebar(NarrativeWindow *nw)
 {
-       get_titlebar_string(nw->p);
+       char *tb = get_titlebar_string(nw->p);
 
        if ( nw->p->slidewindow != NULL ) {
 
                char *title;
 
-               title = malloc(strlen(nw->p->titlebar)+14);
-               sprintf(title, "%s - Colloquium", nw->p->titlebar);
+               title = malloc(strlen(tb)+14);
+               sprintf(title, "%s - Colloquium", tb);
                gtk_window_set_title(GTK_WINDOW(nw->window), title);
                free(title);
 
@@ -442,24 +664,14 @@ static void nw_update_titlebar(NarrativeWindow *nw)
 }
 
 
-static SCBlock *narrative_stylesheet()
-{
-       return sc_parse("\\stylesheet{"
-                       "\\ss[slide]{\\callback[sthumb]}"
-                       "}");
-}
-
-
 static int create_thumbnail(SCInterpreter *scin, SCBlock *bl,
                             double *w, double *h, void **bvp, void *vp)
 {
-       SCBlock *b;
-
-       *w = 320.0;
-       *h = 256.0;
-       b = sc_interp_get_macro_real_block(scin);
+       struct presentation *p = vp;
 
-       *bvp = b;
+       *w = 270.0*(p->slide_width / p->slide_height);
+       *h = 270.0;
+       *bvp = bl;
 
        return 1;
 }
@@ -470,15 +682,12 @@ static cairo_surface_t *render_thumbnail(int w, int h, void *bvp, void *vp)
        struct presentation *p = vp;
        SCBlock *scblocks = bvp;
        cairo_surface_t *surf;
-       SCBlock *stylesheets[2];
        struct frame *top;
+       int sn = slide_number(p, scblocks);
 
-       scblocks = sc_block_child(scblocks);
-       stylesheets[0] = p->stylesheet;
-       stylesheets[1] = NULL;
        /* FIXME: Cache like crazy here */
-       surf = render_sc(scblocks, w, h, 1024.0, 768.0, stylesheets, NULL,
-                        p->is, ISZ_THUMBNAIL, 0, &top, p->lang);
+       surf = render_sc(scblocks, w, h, p->slide_width, p->slide_height,
+                        p->stylesheet, NULL, p->is, sn, &top, p->lang);
        frame_free(top);
 
        return surf;
@@ -490,65 +699,107 @@ static int click_thumbnail(double x, double y, void *bvp, void *vp)
        struct presentation *p = vp;
        SCBlock *scblocks = bvp;
 
-       slide_window_open(p, scblocks, p->narrative_window->app);
+       if ( p->narrative_window->show != NULL ) {
+               sc_slideshow_set_slide(p->narrative_window->show, scblocks);
+       } else {
+               slide_window_open(p, scblocks, p->narrative_window->app);
+       }
 
        return 0;
 }
 
 
-NarrativeWindow *narrative_window_new(struct presentation *p, GApplication *app)
+GActionEntry nw_entries[] = {
+
+       { "about", about_sig, NULL, NULL, NULL },
+       { "save", save_sig, NULL, NULL, NULL },
+       { "saveas", saveas_sig, NULL, NULL, NULL },
+       { "deleteslide", delete_slide_sig, NULL, NULL, NULL },
+       { "slide", add_slide_sig, NULL, NULL, NULL },
+       { "loadstylesheet", load_ss_sig, NULL, NULL, NULL },
+       { "stylesheet", edit_ss_sig, NULL, NULL, NULL },
+       { "startslideshow", start_slideshow_sig, NULL, NULL, NULL },
+       { "startslideshowhere", start_slideshow_here_sig, NULL, NULL, NULL },
+       { "startslideshownoslides", start_slideshow_noslides_sig, NULL, NULL, NULL },
+       { "clock", open_clock_sig, NULL, NULL, NULL },
+       { "testcard", testcard_sig, NULL, NULL, NULL },
+       { "first", first_para_sig, NULL, NULL, NULL },
+       { "prev", prev_para_sig, NULL, NULL, NULL },
+       { "next", next_para_sig, NULL, NULL, NULL },
+       { "last", last_para_sig, NULL, NULL, NULL },
+       { "print", print_sig, NULL, NULL, NULL  },
+       { "exportpdf", exportpdf_sig, NULL, NULL, NULL  },
+};
+
+
+void update_titlebar(NarrativeWindow *nw)
+{
+       char *title;
+
+       title = get_titlebar_string(nw->p);
+       title = realloc(title, strlen(title)+16);
+       if ( title == NULL ) return;
+
+       strcat(title, " - Colloquium");
+       if ( !nw->p->saved ) {
+               strcat(title, " *");
+       }
+       gtk_window_set_title(GTK_WINDOW(nw->window), title);
+       free(title);
+}
+
+
+NarrativeWindow *narrative_window_new(struct presentation *p, GApplication *papp)
 {
        NarrativeWindow *nw;
        GtkWidget *vbox;
        GtkWidget *scroll;
        GtkWidget *toolbar;
        GtkToolItem *button;
-       SCBlock *stylesheets[3];
        SCCallbackList *cbl;
        GtkWidget *image;
+       Colloquium *app = COLLOQUIUM(papp);
 
        if ( p->narrative_window != NULL ) {
-               fprintf(stderr, "Narrative window is already open!\n");
+               fprintf(stderr, _("Narrative window is already open!\n"));
                return NULL;
        }
 
        nw = calloc(1, sizeof(NarrativeWindow));
        if ( nw == NULL ) return NULL;
 
-       nw->app = app;
+       nw->app = papp;
        nw->p = p;
 
        nw->window = gtk_application_window_new(GTK_APPLICATION(app));
        p->narrative_window = nw;
+       update_titlebar(nw);
 
        g_action_map_add_action_entries(G_ACTION_MAP(nw->window), nw_entries,
                                        G_N_ELEMENTS(nw_entries), nw);
-       g_action_map_add_action_entries(G_ACTION_MAP(nw->window), nw_entries_p,
-                                       G_N_ELEMENTS(nw_entries_p), p);
 
        nw_update_titlebar(nw);
 
        vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
        gtk_container_add(GTK_CONTAINER(nw->window), vbox);
 
-       if ( p->stylesheet != NULL ) {
-               stylesheets[0] = p->stylesheet;
-               stylesheets[1] = narrative_stylesheet();
-               stylesheets[2] = NULL;
-       } else {
-               stylesheets[0] = narrative_stylesheet();
-               stylesheets[1] = NULL;
-       }
-
+       /* If the presentation is completely empty, give ourselves at least
+        * something to work with */
        if ( nw->p->scblocks == NULL ) {
                nw->p->scblocks = sc_parse("");
        }
 
-       nw->sceditor = sc_editor_new(nw->p->scblocks, stylesheets, p->lang);
+       /* Put everything we have inside \presentation{}.
+        * SCEditor will start processing one level down */
+       nw->dummy_top = sc_block_new_parent(nw->p->scblocks, "presentation");
+
+       nw->sceditor = sc_editor_new(nw->dummy_top, p->stylesheet, p->lang,
+                                    colloquium_get_imagestore(app));
        cbl = sc_callback_list_new();
-       sc_callback_list_add_callback(cbl, "sthumb", create_thumbnail,
+       sc_callback_list_add_callback(cbl, "slide", create_thumbnail,
                                      render_thumbnail, click_thumbnail, p);
        sc_editor_set_callbacks(nw->sceditor, cbl);
+       sc_editor_set_imagestore(nw->sceditor, p->is);
 
        toolbar = gtk_toolbar_new();
        gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_ICONS);
@@ -557,7 +808,7 @@ NarrativeWindow *narrative_window_new(struct presentation *p, GApplication *app)
        /* Fullscreen */
        image = gtk_image_new_from_icon_name("view-fullscreen",
                                             GTK_ICON_SIZE_LARGE_TOOLBAR);
-       button = gtk_tool_button_new(image, "Start slideshow");
+       button = gtk_tool_button_new(image, _("Start slideshow"));
        gtk_actionable_set_action_name(GTK_ACTIONABLE(button),
                                       "win.startslideshow");
        gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(button));
@@ -566,9 +817,9 @@ NarrativeWindow *narrative_window_new(struct presentation *p, GApplication *app)
        gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(button));
 
        /* Add slide */
-       image = gtk_image_new_from_icon_name("add",
+       image = gtk_image_new_from_icon_name("list-add",
                                             GTK_ICON_SIZE_LARGE_TOOLBAR);
-       button = gtk_tool_button_new(image, "Add slide");
+       button = gtk_tool_button_new(image, _("Add slide"));
        gtk_actionable_set_action_name(GTK_ACTIONABLE(button),
                                       "win.slide");
        gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(button));
@@ -576,36 +827,34 @@ NarrativeWindow *narrative_window_new(struct presentation *p, GApplication *app)
        button = gtk_separator_tool_item_new();
        gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(button));
 
-       /* Change slide.  FIXME: LTR vs RTL */
-       image = gtk_image_new_from_icon_name("gtk-goto-first-ltr",
+       image = gtk_image_new_from_icon_name("go-top",
                                             GTK_ICON_SIZE_LARGE_TOOLBAR);
-       nw->bfirst = gtk_tool_button_new(image, "First slide");
+       nw->bfirst = gtk_tool_button_new(image, _("First slide"));
        gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(nw->bfirst));
        gtk_actionable_set_action_name(GTK_ACTIONABLE(nw->bfirst),
                                       "win.first");
 
-       image = gtk_image_new_from_icon_name("gtk-go-back-ltr",
+       image = gtk_image_new_from_icon_name("go-up",
                                             GTK_ICON_SIZE_LARGE_TOOLBAR);
-       nw->bprev = gtk_tool_button_new(image, "Previous slide");
+       nw->bprev = gtk_tool_button_new(image, _("Previous slide"));
        gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(nw->bprev));
        gtk_actionable_set_action_name(GTK_ACTIONABLE(nw->bprev),
                                       "win.prev");
 
-       image = gtk_image_new_from_icon_name("gtk-go-forward-ltr",
+       image = gtk_image_new_from_icon_name("go-down",
                                             GTK_ICON_SIZE_LARGE_TOOLBAR);
-       nw->bnext = gtk_tool_button_new(image, "Next slide");
+       nw->bnext = gtk_tool_button_new(image, _("Next slide"));
        gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(nw->bnext));
        gtk_actionable_set_action_name(GTK_ACTIONABLE(nw->bnext),
                                       "win.next");
 
-       image = gtk_image_new_from_icon_name("gtk-goto-last-ltr",
+       image = gtk_image_new_from_icon_name("go-bottom",
                                             GTK_ICON_SIZE_LARGE_TOOLBAR);
-       nw->blast = gtk_tool_button_new(image, "Last slide");
+       nw->blast = gtk_tool_button_new(image, _("Last slide"));
        gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(nw->blast));
        gtk_actionable_set_action_name(GTK_ACTIONABLE(nw->blast),
                                       "win.last");
 
-       nw->sel_slide = NULL;
        update_toolbar(nw);
 
        scroll = gtk_scrolled_window_new(NULL, NULL);
@@ -619,18 +868,21 @@ NarrativeWindow *narrative_window_new(struct presentation *p, GApplication *app)
        sc_editor_set_top_frame_editable(nw->sceditor, 1);
 
        g_signal_connect(G_OBJECT(nw->sceditor), "button-press-event",
-                        G_CALLBACK(button_press_sig), nw);
+                        G_CALLBACK(nw_button_press_sig), nw);
+       g_signal_connect(G_OBJECT(nw->sceditor), "changed",
+                        G_CALLBACK(changed_sig), nw);
        g_signal_connect(G_OBJECT(nw->sceditor), "key-press-event",
-                        G_CALLBACK(key_press_sig), nw);
+                        G_CALLBACK(nw_key_press_sig), nw);
        g_signal_connect(G_OBJECT(nw->window), "destroy",
-                        G_CALLBACK(destroy_sig), nw);
+                        G_CALLBACK(nw_destroy_sig), nw);
 
        gtk_window_set_default_size(GTK_WINDOW(nw->window), 768, 768);
        gtk_box_pack_start(GTK_BOX(vbox), scroll, TRUE, TRUE, 0);
+       gtk_container_set_focus_child(GTK_CONTAINER(nw->window),
+                                     GTK_WIDGET(nw->sceditor));
 
        gtk_widget_show_all(nw->window);
-       nw->app = app;
-       g_application_hold(app);
+       g_application_hold(papp);
 
        return nw;
 }