diff options
Diffstat (limited to 'src-old')
36 files changed, 12812 insertions, 0 deletions
diff --git a/src-old/colloquium.c b/src-old/colloquium.c new file mode 100644 index 0000000..02ffa0a --- /dev/null +++ b/src-old/colloquium.c @@ -0,0 +1,476 @@ +/* + * colloquium.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 <gtk/gtk.h> +#include <getopt.h> +#include <string.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <glib.h> +#include <glib/gstdio.h> + +#include "colloquium.h" +#include "presentation.h" +#include "narrative_window.h" +#include "utils.h" + + +struct _colloquium +{ + GtkApplication parent_instance; + GtkBuilder *builder; + char *mydir; + int first_run; + char *imagestore; + int hidepointer; +}; + + +typedef GtkApplicationClass ColloquiumClass; + + +G_DEFINE_TYPE(Colloquium, colloquium, GTK_TYPE_APPLICATION) + + +static void colloquium_activate(GApplication *papp) +{ + Colloquium *app = COLLOQUIUM(papp); + if ( !app->first_run ) { + struct presentation *p; + p = new_presentation(app->imagestore); + narrative_window_new(p, papp); + } +} + + +static void new_sig(GSimpleAction *action, GVariant *parameter, gpointer vp) +{ + GApplication *app = vp; + g_application_activate(app); +} + + +static void open_intro_doc(Colloquium *app) +{ + GFile *file = g_file_new_for_uri("resource:///uk/me/bitwiz/Colloquium/demo.sc"); + g_application_open(G_APPLICATION(app), &file, 1, ""); + g_object_unref(file); +} + + +static void intro_sig(GSimpleAction *action, GVariant *parameter, gpointer vp) +{ + GApplication *app = vp; + open_intro_doc(COLLOQUIUM(app)); +} + + +void open_about_dialog(GtkWidget *parent) +{ + GtkWidget *window; + + const gchar *authors[] = { + "Thomas White <taw@bitwiz.org.uk>", + NULL + }; + + window = gtk_about_dialog_new(); + gtk_window_set_transient_for(GTK_WINDOW(window), GTK_WINDOW(parent)); + + gtk_about_dialog_set_program_name(GTK_ABOUT_DIALOG(window), + "Colloquium"); + gtk_about_dialog_set_logo_icon_name(GTK_ABOUT_DIALOG(window), + "colloquium"); + gtk_about_dialog_set_version(GTK_ABOUT_DIALOG(window), + PACKAGE_VERSION); + gtk_about_dialog_set_copyright(GTK_ABOUT_DIALOG(window), + "© 2017-2018 Thomas White <taw@bitwiz.me.uk>"); + gtk_about_dialog_set_comments(GTK_ABOUT_DIALOG(window), + /* Description of the program */ + _("Narrative-based presentation system")); + gtk_about_dialog_set_license_type(GTK_ABOUT_DIALOG(window), GTK_LICENSE_GPL_3_0); + gtk_about_dialog_set_website(GTK_ABOUT_DIALOG(window), + "https://www.bitwiz.me.uk/"); + gtk_about_dialog_set_website_label(GTK_ABOUT_DIALOG(window), + "https://www.bitwiz.me.uk/"); + gtk_about_dialog_set_authors(GTK_ABOUT_DIALOG(window), authors); + gtk_about_dialog_set_translator_credits(GTK_ABOUT_DIALOG(window), + _("translator-credits")); + + g_signal_connect(window, "response", G_CALLBACK(gtk_widget_destroy), + NULL); + + gtk_widget_show_all(window); +} + + +static void quit_sig(GSimpleAction *action, GVariant *parameter, gpointer vp) +{ + GApplication *app = vp; + g_application_quit(app); +} + + +static GFile **gslist_to_array(GSList *item, int *n) +{ + int i = 0; + int len = g_slist_length(item); + GFile **files = malloc(len * sizeof(GFile *)); + + if ( files == NULL ) return NULL; + + while ( item != NULL ) { + if ( i == len ) { + fprintf(stderr, "WTF? Too many files\n"); + break; + } + files[i++] = item->data; + item = item->next; + } + + *n = len; + return files; +} + + +static gint open_response_sig(GtkWidget *d, gint response, GApplication *papp) +{ + if ( response == GTK_RESPONSE_ACCEPT ) { + + GSList *files; + int n_files = 0; + GFile **files_array; + int i; + + files = gtk_file_chooser_get_files(GTK_FILE_CHOOSER(d)); + files_array = gslist_to_array(files, &n_files); + if ( files_array == NULL ) { + fprintf(stderr, "Failed to convert file list\n"); + return 0; + } + g_slist_free(files); + g_application_open(papp, files_array, n_files, ""); + + for ( i=0; i<n_files; i++ ) { + g_object_unref(files_array[i]); + } + + } + + gtk_widget_destroy(d); + + return 0; +} + + +static void open_sig(GSimpleAction *action, GVariant *parameter, gpointer vp) +{ + GtkWidget *d; + GApplication *app = vp; + + d = gtk_file_chooser_dialog_new(_("Open Presentation"), + gtk_application_get_active_window(GTK_APPLICATION(app)), + GTK_FILE_CHOOSER_ACTION_OPEN, + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_Open"), GTK_RESPONSE_ACCEPT, + NULL); + + gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(d), TRUE); + + g_signal_connect(G_OBJECT(d), "response", + G_CALLBACK(open_response_sig), app); + + gtk_widget_show_all(d); +} + + +GActionEntry app_entries[] = { + + { "new", new_sig, NULL, NULL, NULL }, + { "open", open_sig, NULL, NULL, NULL }, + { "intro", intro_sig, NULL, NULL, NULL }, + { "quit", quit_sig, NULL, NULL, NULL }, +}; + + +static void colloquium_open(GApplication *papp, GFile **files, gint n_files, + const gchar *hint) +{ + int i; + Colloquium *app = COLLOQUIUM(papp); + + for ( i = 0; i<n_files; i++ ) { + struct presentation *p; + p = new_presentation(app->imagestore); + if ( load_presentation(p, files[i]) == 0 ) { + narrative_window_new(p, papp); + } else { + char *uri = g_file_get_uri(files[i]); + fprintf(stderr, _("Failed to load presentation '%s'\n"), + uri); + g_free(uri); + } + } +} + + +static void colloquium_finalize(GObject *object) +{ + G_OBJECT_CLASS(colloquium_parent_class)->finalize(object); +} + + +static void create_config(const char *filename) +{ + + FILE *fh; + fh = fopen(filename, "w"); + if ( fh == NULL ) { + fprintf(stderr, _("Failed to create config\n")); + return; + } + + fprintf(fh, "imagestore: %s\n", + g_get_user_special_dir(G_USER_DIRECTORY_PICTURES)); + fprintf(fh, "hidepointer: no\n"); + + fclose(fh); +} + + +static int yesno(const char *a) +{ + if ( a == NULL ) return 0; + + if ( strcmp(a, "1") == 0 ) return 1; + if ( strcasecmp(a, "yes") == 0 ) return 1; + if ( strcasecmp(a, "true") == 0 ) return 1; + + if ( strcasecmp(a, "0") == 0 ) return 0; + if ( strcasecmp(a, "no") == 0 ) return 0; + if ( strcasecmp(a, "false") == 0 ) return 0; + + fprintf(stderr, "Don't understand '%s', assuming false\n", a); + return 0; +} + + +static void read_config(const char *filename, Colloquium *app) +{ + FILE *fh; + char line[1024]; + + fh = fopen(filename, "r"); + if ( fh == NULL ) { + fprintf(stderr, _("Failed to open config %s\n"), filename); + return; + } + + do { + + if ( fgets(line, 1024, fh) == NULL ) break; + chomp(line); + + if ( strncmp(line, "imagestore: ", 11) == 0 ) { + app->imagestore = strdup(line+12); + } + + if ( strncmp(line, "hidepointer: ", 12) == 0 ) { + app->hidepointer = yesno(line+13); + } + } while ( !feof(fh) ); + + fclose(fh); +} + + +const char *colloquium_get_imagestore(Colloquium *app) +{ + return app->imagestore; +} + + +int colloquium_get_hidepointer(Colloquium *app) +{ + return app->hidepointer; +} + + +static void colloquium_startup(GApplication *papp) +{ + Colloquium *app = COLLOQUIUM(papp); + const char *configdir; + char *tmp; + + G_APPLICATION_CLASS(colloquium_parent_class)->startup(papp); + + g_action_map_add_action_entries(G_ACTION_MAP(app), app_entries, + G_N_ELEMENTS(app_entries), app); + + app->builder = gtk_builder_new_from_resource("/uk/me/bitwiz/Colloquium/menus.ui"); + gtk_builder_add_from_resource(app->builder, "/uk/me/bitwiz/Colloquium/windows.ui", NULL); + gtk_application_set_menubar(GTK_APPLICATION(app), + G_MENU_MODEL(gtk_builder_get_object(app->builder, "menubar"))); + + if ( gtk_application_prefers_app_menu(GTK_APPLICATION(app)) ) { + /* Set the application menu only if it will be shown by the + * desktop environment. All the entries are already in the + * normal menus, so don't let GTK create a fallback menu in the + * menu bar. */ + GMenuModel *mmodel = G_MENU_MODEL(gtk_builder_get_object(app->builder, "app-menu")); + gtk_application_set_app_menu(GTK_APPLICATION(app), mmodel); + } + + configdir = g_get_user_config_dir(); + app->mydir = malloc(strlen(configdir)+14); + strcpy(app->mydir, configdir); + strcat(app->mydir, "/colloquium"); + + if ( !g_file_test(app->mydir, G_FILE_TEST_IS_DIR) ) { + + /* Folder not created yet */ + open_intro_doc(app); + app->first_run = 1; + + if ( g_mkdir(app->mydir, S_IRUSR | S_IWUSR | S_IXUSR) ) { + fprintf(stderr, _("Failed to create config folder\n")); + } + } + + /* Read config file */ + tmp = malloc(strlen(app->mydir)+32); + if ( tmp != NULL ) { + + tmp[0] = '\0'; + strcat(tmp, app->mydir); + strcat(tmp, "/config"); + + /* Create default config file if it doesn't exist */ + if ( !g_file_test(tmp, G_FILE_TEST_EXISTS) ) { + create_config(tmp); + } + + read_config(tmp, app); + free(tmp); + } +} + + +static void colloquium_shutdown(GApplication *app) +{ + G_APPLICATION_CLASS(colloquium_parent_class)->shutdown(app); +} + + +static void colloquium_class_init(ColloquiumClass *class) +{ + GApplicationClass *app_class = G_APPLICATION_CLASS(class); + GObjectClass *object_class = G_OBJECT_CLASS(class); + + app_class->startup = colloquium_startup; + app_class->shutdown = colloquium_shutdown; + app_class->activate = colloquium_activate; + app_class->open = colloquium_open; + + object_class->finalize = colloquium_finalize; +} + + +static void colloquium_init(Colloquium *app) +{ + app->imagestore = NULL; + app->hidepointer = 0; +} + + +static Colloquium *colloquium_new() +{ + Colloquium *app; + + g_set_application_name("Colloquium"); + app = g_object_new(colloquium_get_type(), + "application-id", "uk.org.bitwiz.Colloquium", + "flags", G_APPLICATION_HANDLES_OPEN, + "register-session", TRUE, + NULL); + + app->first_run = 0; /* Will be updated at "startup" if appropriate */ + + return app; +} + + +static void show_help(const char *s) +{ + printf(_("Syntax: %s [options] [<file.sc>]\n\n"), s); + printf(_("Narrative-based presentation system.\n\n" + " -h, --help Display this help message.\n")); +} + + +int main(int argc, char *argv[]) +{ + int c; + int status; + Colloquium *app; + + /* Long options */ + const struct option longopts[] = { + {"help", 0, NULL, 'h'}, + {0, 0, NULL, 0} + }; + + /* Short options */ + while ((c = getopt_long(argc, argv, "h", longopts, NULL)) != -1) { + + switch (c) + { + case 'h' : + show_help(argv[0]); + return 0; + + case 0 : + break; + + default : + return 1; + } + + } + +#if !GLIB_CHECK_VERSION(2,36,0) + g_type_init(); +#endif + + bindtextdomain("colloquium", LOCALEDIR); + textdomain("colloquium"); + + app = colloquium_new(); + status = g_application_run(G_APPLICATION(app), argc, argv); + g_object_unref(app); + return status; +} diff --git a/src-old/colloquium.h b/src-old/colloquium.h new file mode 100644 index 0000000..89f600f --- /dev/null +++ b/src-old/colloquium.h @@ -0,0 +1,45 @@ +/* + * colloquium.h + * + * Copyright © 2014-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 COLLOQUIUM_H +#define COLLOQUIUM_H + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <glib-object.h> + + +typedef struct _colloquium Colloquium; + +#define COLLOQUIUM(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + GTK_TYPE_APPLICATION, Colloquium)) + + +extern const char *colloquium_get_imagestore(Colloquium *app); +extern int colloquium_get_hidepointer(Colloquium *app); + +extern void open_about_dialog(GtkWidget *parent); + + +#endif /* COLLOQUIUM_H */ diff --git a/src-old/debugger.c b/src-old/debugger.c new file mode 100644 index 0000000..f15478a --- /dev/null +++ b/src-old/debugger.c @@ -0,0 +1,229 @@ +/* + * print.c + * + * Copyright © 2017-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 <gtk/gtk.h> +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +#include "presentation.h" +#include "narrative_window.h" +#include "render.h" +#include "frame.h" +#include "utils.h" + + +#define MAX_DEBUG_RUNS (1024) + +struct debugwindow +{ + GtkWidget *window; + GtkWidget *drawingarea; + struct frame *fr; + guint timeout; +}; + + +static void plot_hr(cairo_t *cr, double *ypos, double width) +{ + cairo_move_to(cr, 10.0, *ypos+5.5); + cairo_line_to(cr, width-20.0, *ypos+5.5); + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); + cairo_set_line_width(cr, 1.0); + cairo_stroke(cr); + *ypos += 10.0; +} + + +static void plot_text(cairo_t *cr, double *ypos, PangoFontDescription *fontdesc, + const char *tmp) +{ + PangoLayout *layout; + PangoRectangle ext; + + cairo_move_to(cr, 10.0, *ypos); + + layout = pango_cairo_create_layout(cr); + pango_layout_set_text(layout, tmp, -1); + pango_layout_set_font_description(layout, fontdesc); + + pango_layout_get_extents(layout, NULL, &ext); + *ypos += ext.height/PANGO_SCALE; + + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); + pango_cairo_show_layout(cr, layout); + + g_object_unref(layout); +} + + +static const char *str_type(enum para_type t) +{ + switch ( t ) { + + /* Text paragraph */ + case PARA_TYPE_TEXT : return "text"; + + /* Callback paragraph */ + case PARA_TYPE_CALLBACK : return "callback"; + + /* Unknown paragraph type */ + default : return "unknown"; + } +} + +static void debug_text_para(Paragraph *para, cairo_t *cr, double *ypos, + PangoFontDescription *fontdesc) +{ + int i, nrun; + char tmp[256]; + + nrun = para_debug_num_runs(para); + /* How many text runs */ + snprintf(tmp, 255, " %i runs", nrun); + plot_text(cr, ypos, fontdesc, tmp); + + for ( i=0; i<nrun; i++ ) { + SCBlock *scblock; + if ( para_debug_run_info(para, i, &scblock) ) { + /* Failed to get debug info for paragraph */ + plot_text(cr, ypos, fontdesc, "Error"); + } else { + snprintf(tmp, 255, " Run %i: SCBlock %p", i, scblock); + plot_text(cr, ypos, fontdesc, tmp); + } + } + + snprintf(tmp, 255, "Newline at end: %p", + para_debug_get_newline_at_end(para)); + plot_text(cr, ypos, fontdesc, tmp); +} + + +static gboolean dbg_draw_sig(GtkWidget *da, cairo_t *cr, struct debugwindow *dbgw) +{ + int width, height; + char tmp[256]; + PangoFontDescription *fontdesc; + int i; + double ypos = 10.0; + + /* Background */ + width = gtk_widget_get_allocated_width(GTK_WIDGET(da)); + height = gtk_widget_get_allocated_height(GTK_WIDGET(da)); + cairo_set_source_rgba(cr, 1.0, 0.8, 0.8, 1.0); + cairo_rectangle(cr, 0.0, 0.0, width, height); + cairo_fill(cr); + + if ( dbgw->fr == NULL ) return FALSE; + + fontdesc = pango_font_description_new(); + pango_font_description_set_family_static(fontdesc, "Serif"); + pango_font_description_set_style(fontdesc, PANGO_STYLE_ITALIC); + pango_font_description_set_absolute_size(fontdesc, 15*PANGO_SCALE); + + snprintf(tmp, 255, "Frame %p has %i paragraphs", dbgw->fr, dbgw->fr->n_paras); + plot_text(cr, &ypos, fontdesc, tmp); + + for ( i=0; i<dbgw->fr->n_paras; i++ ) { + + enum para_type t = para_type(dbgw->fr->paras[i]); + SCBlock *scblock = para_scblock(dbgw->fr->paras[i]); + + plot_hr(cr, &ypos, width); + snprintf(tmp, 255, "Paragraph %i: type %s", i, str_type(t)); + plot_text(cr, &ypos, fontdesc, tmp); + + snprintf(tmp, 255, "SCBlock %p", scblock); + plot_text(cr, &ypos, fontdesc, tmp); + + if ( t == PARA_TYPE_TEXT ) { + debug_text_para(dbgw->fr->paras[i], cr, &ypos, fontdesc); + } + + plot_text(cr, &ypos, fontdesc, ""); + + } + + pango_font_description_free(fontdesc); + + return FALSE; +} + + +static gboolean queue_redraw(void *vp) +{ + struct debugwindow *dbgw = vp; + gint w, h; + w = gtk_widget_get_allocated_width(GTK_WIDGET(dbgw->drawingarea)); + h = gtk_widget_get_allocated_height(GTK_WIDGET(dbgw->drawingarea)); + gtk_widget_queue_draw_area(GTK_WIDGET(dbgw->drawingarea), 0, 0, w, h); + return TRUE; +} + + +static gboolean close_sig(GtkWidget *widget, GdkEvent *event, struct debugwindow *dbgw) +{ + g_source_remove(dbgw->timeout); + free(dbgw); + return FALSE; +} + + +void open_debugger(struct frame *fr) +{ + struct debugwindow *dbgw; + GtkWidget *scroll; + + if ( fr == NULL ) return; + + dbgw = calloc(1, sizeof(struct debugwindow)); + if ( dbgw == NULL ) return; + + dbgw->fr = fr; + + dbgw->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_role(GTK_WINDOW(dbgw->window), "debugger"); + gtk_window_set_title(GTK_WINDOW(dbgw->window), "Colloquium debugger"); + + scroll = gtk_scrolled_window_new(NULL, NULL); + gtk_container_add(GTK_CONTAINER(dbgw->window), scroll); + + dbgw->drawingarea = gtk_drawing_area_new(); + gtk_container_add(GTK_CONTAINER(scroll), dbgw->drawingarea); + gtk_widget_set_size_request(dbgw->drawingarea, 100, 8000); + + g_signal_connect(G_OBJECT(dbgw->drawingarea), "draw", + G_CALLBACK(dbg_draw_sig), dbgw); + + g_signal_connect(G_OBJECT(dbgw->window), "delete-event", + G_CALLBACK(close_sig), dbgw); + + dbgw->timeout = g_timeout_add(1000, queue_redraw, dbgw); + + gtk_widget_show_all(dbgw->window); +} diff --git a/src-old/debugger.h b/src-old/debugger.h new file mode 100644 index 0000000..d6191bf --- /dev/null +++ b/src-old/debugger.h @@ -0,0 +1,32 @@ +/* + * debugger.h + * + * Copyright © 2017-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 DEBUGGER_H +#define DEBUGGER_H + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +extern void open_debugger(struct frame *fr); + +#endif /* DEBUGGER_H */ diff --git a/src-old/frame.c b/src-old/frame.c new file mode 100644 index 0000000..08b8fd9 --- /dev/null +++ b/src-old/frame.c @@ -0,0 +1,1820 @@ +/* + * frame.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 <assert.h> +#include <stdlib.h> +#include <string.h> +#include <math.h> +#include <gdk-pixbuf/gdk-pixbuf.h> + +#include "sc_parse.h" +#include "frame.h" +#include "imagestore.h" +#include "utils.h" + +struct text_run +{ + SCBlock *scblock; + char *real_text; /* Usually NULL */ + PangoFontDescription *fontdesc; + double col[4]; +}; + +struct _paragraph +{ + enum para_type type; + double height; + float space[4]; + SCBlock *newline_at_end; + int empty; + + /* For PARA_TYPE_TEXT */ + int n_runs; + struct text_run *runs; + PangoLayout *layout; + PangoAlignment alignment; + + /* For anything other than PARA_TYPE_TEXT + * (for text paragraphs, these things are in the runs) */ + SCBlock *scblock; + + /* For PARA_TYPE_IMAGE */ + char *filename; + double image_w; + double image_h; + int image_real_w; + int image_real_h; + + /* For PARA_TYPE_CALLBACK */ + double cb_w; + double cb_h; + SCCallbackDrawFunc draw_func; + SCCallbackClickFunc click_func; + void *bvp; + void *vp; +}; + + +/* Returns the height of the paragraph including all spacing, padding etc */ +double paragraph_height(Paragraph *para) +{ + return para->height + para->space[2] + para->space[3]; +} + + +static int alloc_ro(struct frame *fr) +{ + struct frame **new_ro; + + new_ro = realloc(fr->children, + fr->max_children*sizeof(struct frame *)); + if ( new_ro == NULL ) return 1; + + fr->children = new_ro; + + return 0; +} + + +struct frame *frame_new() +{ + struct frame *n; + + n = calloc(1, sizeof(struct frame)); + if ( n == NULL ) return NULL; + + n->children = NULL; + n->max_children = 32; + if ( alloc_ro(n) ) { + fprintf(stderr, "Couldn't allocate children\n"); + free(n); + return NULL; + } + n->num_children = 0; + + n->scblocks = NULL; + n->n_paras = 0; + n->paras = NULL; + + return n; +} + + +static void free_paragraph(Paragraph *para) +{ + int i; + + for ( i=0; i<para->n_runs; i++ ) { + pango_font_description_free(para->runs[i].fontdesc); + free(para->runs[i].real_text); /* free(NULL) is OK */ + } + free(para->runs); + if ( para->layout != NULL ) g_object_unref(para->layout); + free(para); +} + + +void frame_free(struct frame *fr) +{ + int i; + + if ( fr == NULL ) return; + + /* Free paragraphs */ + if ( fr->paras != NULL ) { + for ( i=0; i<fr->n_paras; i++ ) { + free_paragraph(fr->paras[i]); + } + free(fr->paras); + } + + /* Free all children */ + for ( i=0; i<fr->num_children; i++ ) { + frame_free(fr->children[i]); + } + free(fr->children); + + free(fr); +} + + +struct frame *add_subframe(struct frame *fr) +{ + struct frame *n; + + n = frame_new(); + if ( n == NULL ) return NULL; + + if ( fr->num_children == fr->max_children ) { + fr->max_children += 32; + if ( alloc_ro(fr) ) return NULL; + } + + fr->children[fr->num_children++] = n; + + return n; +} + + +void show_frame_hierarchy(struct frame *fr, const char *t) +{ + int i; + char tn[1024]; + + strcpy(tn, t); + strcat(tn, " "); + + printf("%s%p (%.2f x %.2f)\n", t, fr, fr->w, fr->h); + + for ( i=0; i<fr->num_children; i++ ) { + show_frame_hierarchy(fr->children[i], tn); + } + +} + + +struct frame *find_frame_with_scblocks(struct frame *fr, SCBlock *scblocks) +{ + int i; + + if ( fr->scblocks == scblocks ) return fr; + + for ( i=0; i<fr->num_children; i++ ) { + struct frame *tt; + tt = find_frame_with_scblocks(fr->children[i], scblocks); + if ( tt != NULL ) return tt; + } + + return NULL; +} + + +static const char *text_for_run(const struct text_run *run) +{ + if ( run == NULL ) { + fprintf(stderr, "NULL run passed to text_for_run\n"); + return 0; + } + + if ( run->scblock == NULL ) { + fprintf(stderr, "NULL scblock in text_for_run\n"); + return 0; + } + + if ( run->real_text != NULL ) { + return run->real_text; + } + + return sc_block_contents(run->scblock); +} + + +static size_t run_text_len(const struct text_run *run) +{ + if ( run == NULL ) { + fprintf(stderr, "NULL run passed to run_text_len\n"); + return 0; + } + + if ( run->scblock == NULL ) { + fprintf(stderr, "NULL scblock in run_text_len\n"); + return 0; + } + + if ( run->real_text != NULL ) { + return strlen(run->real_text); + } + + if ( sc_block_contents(run->scblock) == NULL ) { + fprintf(stderr, "NULL scblock contents in run_text_len\n"); + return 0; + } + + return strlen(sc_block_contents(run->scblock)); +} + + +void wrap_paragraph(Paragraph *para, PangoContext *pc, double w, + size_t sel_start, size_t sel_end) +{ + size_t total_len = 0; + int i; + char *text; + PangoAttrList *attrs; + PangoRectangle rect; + size_t pos = 0; + + w -= para->space[0] + para->space[1]; + + if ( para->type == PARA_TYPE_IMAGE ) { + if ( para->image_w < 0.0 ) { + para->image_w = w; + para->image_h = w*((float)para->image_real_h/para->image_real_w); + } + para->height = para->image_h; + return; + } + + if ( para->type != PARA_TYPE_TEXT ) return; + + for ( i=0; i<para->n_runs; i++ ) { + total_len += run_text_len(¶->runs[i]); + } + + /* Allocate the complete text */ + text = malloc(total_len+1); + if ( text == NULL ) { + fprintf(stderr, "Couldn't allocate combined text (%lli)\n", + (long long int)total_len); + return; + } + + /* Allocate the attributes */ + attrs = pango_attr_list_new(); + + /* Put all of the text together */ + text[0] = '\0'; + for ( i=0; i<para->n_runs; i++ ) { + + PangoAttribute *attr; + const char *run_text; + size_t run_len; + guint16 r, g, b; + + run_text = text_for_run(¶->runs[i]); + run_len = strlen(run_text); + + attr = pango_attr_font_desc_new(para->runs[i].fontdesc); + attr->start_index = pos; + attr->end_index = pos + run_len; + pango_attr_list_insert(attrs, attr); + + r = para->runs[i].col[0] * 65535; + g = para->runs[i].col[1] * 65535; + b = para->runs[i].col[2] * 65535; + attr = pango_attr_foreground_new(r, g, b); + attr->start_index = pos; + attr->end_index = pos + run_len; + pango_attr_list_insert(attrs, attr); + + pos += run_len; + strncat(text, run_text, run_len); + + } + + /* Add attributes for selected text */ + if ( sel_start > 0 || sel_end > 0 ) { + PangoAttribute *attr; + attr = pango_attr_background_new(42919, 58853, 65535); + attr->start_index = sel_start; + attr->end_index = sel_end; + pango_attr_list_insert(attrs, attr); + } + + if ( para->layout == NULL ) { + para->layout = pango_layout_new(pc); + } + pango_layout_set_width(para->layout, pango_units_from_double(w)); + pango_layout_set_text(para->layout, text, total_len); + pango_layout_set_alignment(para->layout, para->alignment); + pango_layout_set_attributes(para->layout, attrs); + free(text); + pango_attr_list_unref(attrs); + + pango_layout_get_extents(para->layout, NULL, &rect); + para->height = pango_units_to_double(rect.height); +} + +static SCBlock *get_newline_at_end(Paragraph *para) +{ + return para->newline_at_end; +} + + +SCBlock *para_debug_get_newline_at_end(Paragraph *para) +{ + return get_newline_at_end(para); +} + + +void set_newline_at_end(Paragraph *para, SCBlock *bl) +{ + para->newline_at_end = bl; +} + + +void add_run(Paragraph *para, SCBlock *scblock, + PangoFontDescription *fdesc, double col[4], const char *real_text) +{ + struct text_run *runs_new; + + runs_new = realloc(para->runs, + (para->n_runs+1)*sizeof(struct text_run)); + if ( runs_new == NULL ) { + fprintf(stderr, "Failed to add run.\n"); + return; + } + + para->runs = runs_new; + para->runs[para->n_runs].scblock = scblock; + if ( real_text != NULL ) { + para->runs[para->n_runs].real_text = strdup(real_text); + } else { + para->runs[para->n_runs].real_text = NULL; + } + para->runs[para->n_runs].fontdesc = pango_font_description_copy(fdesc); + para->runs[para->n_runs].col[0] = col[0]; + para->runs[para->n_runs].col[1] = col[1]; + para->runs[para->n_runs].col[2] = col[2]; + para->runs[para->n_runs].col[3] = col[3]; + para->empty = 0; + para->n_runs++; +} + + +Paragraph *create_paragraph(struct frame *fr, SCBlock *bl) +{ + Paragraph **paras_new; + Paragraph *pnew; + + paras_new = realloc(fr->paras, (fr->n_paras+1)*sizeof(Paragraph *)); + if ( paras_new == NULL ) return NULL; + + pnew = calloc(1, sizeof(struct _paragraph)); + if ( pnew == NULL ) return NULL; + + fr->paras = paras_new; + fr->paras[fr->n_paras++] = pnew; + + /* For now, assume the paragraph is going to be for text. + * However, this can easily be changed */ + pnew->type = PARA_TYPE_TEXT; + pnew->scblock = bl; + pnew->n_runs = 0; + pnew->runs = NULL; + pnew->layout = NULL; + pnew->height = 0.0; + pnew->alignment = PANGO_ALIGN_LEFT; + pnew->empty = 1; + + return pnew; +} + + +/* Create a new paragraph in 'fr' just after paragraph 'pos' */ +Paragraph *insert_paragraph(struct frame *fr, int pos) +{ + Paragraph **paras_new; + Paragraph *pnew; + int i; + + if ( pos >= fr->n_paras ) { + fprintf(stderr, "insert_paragraph(): pos too high!\n"); + return NULL; + } + + paras_new = realloc(fr->paras, (fr->n_paras+1)*sizeof(Paragraph *)); + if ( paras_new == NULL ) return NULL; + + pnew = calloc(1, sizeof(struct _paragraph)); + if ( pnew == NULL ) return NULL; + + fr->paras = paras_new; + fr->n_paras++; + + for ( i=fr->n_paras-1; i>pos; i-- ) { + fr->paras[i] = fr->paras[i-1]; + } + fr->paras[pos+1] = pnew; + + return pnew; +} + + +Paragraph *add_callback_para(struct frame *fr, SCBlock *bl, double w, double h, + SCCallbackDrawFunc draw_func, + SCCallbackClickFunc click_func, void *bvp, + void *vp) +{ + Paragraph *pnew; + + if ( (fr->n_paras > 0) && (fr->paras[fr->n_paras-1]->empty) ) { + pnew = fr->paras[fr->n_paras-1]; + } else { + pnew = create_paragraph(fr, bl); + if ( pnew == NULL ) { + fprintf(stderr, "Failed to add callback paragraph\n"); + return NULL; + } + } + + pnew->type = PARA_TYPE_CALLBACK; + pnew->scblock = bl; + pnew->cb_w = w; + pnew->cb_h = h; + pnew->draw_func = draw_func; + pnew->click_func = click_func; + pnew->bvp = bvp; + pnew->vp = vp; + pnew->height = h; + pnew->empty = 0; + + return pnew; +} + + +void add_image_para(struct frame *fr, SCBlock *scblock, + const char *filename, + ImageStore *is, double w, double h, int editable) +{ + Paragraph *pnew; + int wi, hi; + + if ( is == NULL ) { + fprintf(stderr, "Adding image without ImageStore!\n"); + return; + } + + if ( (fr->n_paras > 0) && (fr->paras[fr->n_paras-1]->empty) ) { + pnew = fr->paras[fr->n_paras-1]; + } else { + pnew = create_paragraph(fr, scblock); + if ( pnew == NULL ) { + fprintf(stderr, "Failed to add image paragraph\n"); + return; + } + } + + if ( imagestore_get_size(is, filename, &wi, &hi) ) { + fprintf(stderr, _("Couldn't determine size of image '%s'\n"), + filename); + wi = 100; + hi = 100; + } + + pnew->type = PARA_TYPE_IMAGE; + pnew->scblock = scblock; + pnew->filename = strdup(filename); + pnew->image_w = w; + pnew->image_h = h; + pnew->image_real_w = wi; + pnew->image_real_h = hi; + pnew->height = h; + pnew->space[0] = 0.0; + pnew->space[1] = 0.0; + pnew->space[2] = 0.0; + pnew->space[3] = 0.0; + pnew->empty = 0; +} + + +double total_height(struct frame *fr) +{ + int i; + double t = 0.0; + for ( i=0; i<fr->n_paras; i++ ) { + t += paragraph_height(fr->paras[i]); + } + return t; +} + + +Paragraph *last_para(struct frame *fr) +{ + if ( fr == NULL ) return NULL; + if ( fr->paras == NULL ) return NULL; + return fr->paras[fr->n_paras-1]; +} + + +static void render_from_surf(cairo_surface_t *surf, cairo_t *cr, + double w, double h, int border) +{ + double x, y; + int sw, sh; + + x = 0.0; y = 0.0; + cairo_user_to_device(cr, &x, &y); + x = rint(x); y = rint(y); + cairo_device_to_user(cr, &x, &y); + + sw = cairo_image_surface_get_width(surf); + sh = cairo_image_surface_get_height(surf); + + cairo_save(cr); + cairo_scale(cr, w/sw, h/sh); + cairo_new_path(cr); + cairo_rectangle(cr, x, y, sw, sh); + cairo_set_source_surface(cr, surf, 0.0, 0.0); + cairo_pattern_t *patt = cairo_get_source(cr); + cairo_pattern_set_extend(patt, CAIRO_EXTEND_PAD); + cairo_pattern_set_filter(patt, CAIRO_FILTER_BEST); + cairo_fill(cr); + cairo_restore(cr); + + if ( border ) { + cairo_new_path(cr); + cairo_rectangle(cr, x+0.5, y+0.5, w, h); + cairo_set_source_rgb(cr, 0.0, 0.0, 0.0); + cairo_set_line_width(cr, 1.0); + cairo_stroke(cr); + } +} + + +void render_paragraph(cairo_t *cr, Paragraph *para, ImageStore *is) +{ + cairo_surface_t *surf; + cairo_surface_type_t type; + double w, h; + + cairo_translate(cr, para->space[0], para->space[2]); + + type = cairo_surface_get_type(cairo_get_target(cr)); + + switch ( para->type ) { + + case PARA_TYPE_TEXT : + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); + pango_cairo_update_layout(cr, para->layout); + pango_cairo_show_layout(cr, para->layout); + cairo_fill(cr); + break; + + case PARA_TYPE_IMAGE : + w = para->image_w; + h = para->image_h; + cairo_user_to_device_distance(cr, &w, &h); + surf = lookup_image(is, para->filename, w); + if ( surf != NULL ) { + render_from_surf(surf, cr, para->image_w, para->image_h, 0); + } else { + printf("surf = NULL!\n"); + } + break; + + case PARA_TYPE_CALLBACK : + w = para->cb_w; + h = para->cb_h; + cairo_user_to_device_distance(cr, &w, &h); + if ( type == CAIRO_SURFACE_TYPE_PDF ) { + w *= 6; h *= 6; + } + surf = para->draw_func(w, h, + para->bvp, para->vp); + render_from_surf(surf, cr, para->cb_w, para->cb_h, 1); + cairo_surface_destroy(surf); /* FIXME: Cache like crazy */ + break; + + } +} + + +static size_t end_offset_of_para(struct frame *fr, int pn) +{ + int i; + size_t total = 0; + for ( i=0; i<fr->paras[pn]->n_runs; i++ ) { + total += run_text_len(&fr->paras[pn]->runs[i]); + } + return total; +} + + +/* Local x,y in paragraph -> text offset */ +static size_t text_para_pos(Paragraph *para, double x, double y, int *ptrail) +{ + int idx; + pango_layout_xy_to_index(para->layout, pango_units_from_double(x), + pango_units_from_double(y), &idx, ptrail); + return idx; +} + + +void show_edit_pos(struct edit_pos a) +{ + printf("para %i, pos %li, trail %i\n", a.para, (long int)a.pos, a.trail); +} + + +int positions_equal(struct edit_pos a, struct edit_pos b) +{ + if ( a.para != b.para ) return 0; + if ( a.pos != b.pos ) return 0; + if ( a.trail != b.trail ) return 0; + return 1; +} + + +void sort_positions(struct edit_pos *a, struct edit_pos *b) +{ + if ( a->para > b->para ) { + size_t tpos; + int tpara, ttrail; + tpara = b->para; tpos = b->pos; ttrail = b->trail; + b->para = a->para; b->pos = a->pos; b->trail = a->trail; + a->para = tpara; a->pos = tpos; a->trail = ttrail; + } + + if ( (a->para == b->para) && (a->pos > b->pos) ) + { + size_t tpos = b->pos; + int ttrail = b->trail; + b->pos = a->pos; b->trail = a->trail; + a->pos = tpos; a->trail = ttrail; + } +} + + +static PangoFontDescription *last_font_desc_and_col(struct frame *fr, int pn, double *col) +{ + int i; + + for ( i=pn-1; i>=0; i-- ) { + if ( fr->paras[i]->type != PARA_TYPE_TEXT ) continue; + if ( fr->paras[i]->n_runs == 0 ) continue; + col[0] = fr->paras[i]->runs[fr->paras[i]->n_runs-1].col[0]; + col[1] = fr->paras[i]->runs[fr->paras[i]->n_runs-1].col[1]; + col[2] = fr->paras[i]->runs[fr->paras[i]->n_runs-1].col[2]; + col[3] = fr->paras[i]->runs[fr->paras[i]->n_runs-1].col[3]; + return fr->paras[i]->runs[fr->paras[i]->n_runs-1].fontdesc; + } + + /* No previous text at all, so use the default text style + * (which is valid for new text in the frame, so this is OK) */ + col[0] = fr->col[0]; + col[1] = fr->col[1]; + col[2] = fr->col[2]; + col[3] = fr->col[3]; + return fr->fontdesc; +} + + +void ensure_run(struct frame *fr, struct edit_pos cpos) +{ + SCBlock *bl; + Paragraph *para; + PangoFontDescription *fontdesc; + double col[4]; + + para = fr->paras[cpos.para]; + + if ( para->n_runs > 0 ) return; + + if ( para->type != PARA_TYPE_TEXT ) return; + + if ( para->scblock != NULL ) { + + bl = sc_block_prepend(para->scblock, fr->scblocks); + if ( bl == NULL ) { + fprintf(stderr, "Couldn't prepend block\n"); + return; + } + sc_block_set_contents(bl, strdup("")); + + } else { + + /* If the paragraph's SCBlock is NULL, it means this paragraph + * is right at the end of the document. The last thing in the + * document is something like \newpara. */ + bl = sc_block_append_inside(fr->scblocks, NULL, NULL, strdup("")); + + } + + para->scblock = bl; + + fontdesc = last_font_desc_and_col(fr, cpos.para, col); + add_run(para, bl, fontdesc, col, NULL); + wrap_paragraph(para, NULL, fr->w - fr->pad_l - fr->pad_r, 0, 0); +} + + +int find_cursor(struct frame *fr, double x, double y, struct edit_pos *pos) +{ + double pad; + int i; + + if ( fr == NULL ) { + fprintf(stderr, "Cursor frame is NULL.\n"); + return 1; + } + + pad = fr->pad_t; + + for ( i=0; i<fr->n_paras; i++ ) { + double npos = pad + paragraph_height(fr->paras[i]); + if ( npos > y ) { + pos->para = i; + if ( fr->paras[i]->type == PARA_TYPE_TEXT ) { + pos->pos = text_para_pos(fr->paras[i], + x - fr->pad_l - fr->paras[i]->space[0], + y - pad - fr->paras[i]->space[2], + &pos->trail); + } else { + pos->pos = 0; + } + return 0; + } + pad = npos; + } + + if ( fr->n_paras == 0 ) { + printf("No paragraphs in frame.\n"); + return 1; + } + + /* Pretend it's in the last paragraph */ + pad -= fr->paras[fr->n_paras-1]->height; + pos->para = fr->n_paras - 1; + pos->pos = text_para_pos(fr->paras[fr->n_paras-1], + x - fr->pad_l, y - pad, &pos->trail); + return 0; +} + + +int get_para_highlight(struct frame *fr, int cursor_para, + double *cx, double *cy, double *cw, double *ch) +{ + Paragraph *para; + int i; + double py = 0.0; + + if ( fr == NULL ) { + fprintf(stderr, "Cursor frame is NULL.\n"); + return 1; + } + + if ( cursor_para >= fr->n_paras ) { + fprintf(stderr, "Highlight paragraph number is too high!\n"); + return 1; + } + + para = fr->paras[cursor_para]; + for ( i=0; i<cursor_para; i++ ) { + py += paragraph_height(fr->paras[i]); + } + + *cx = fr->pad_l; + *cy = fr->pad_t + py; + *cw = fr->w - fr->pad_l - fr->pad_r; + *ch = paragraph_height(para); + return 0; +} + + +int get_cursor_pos(struct frame *fr, int cursor_para, int cursor_pos, + double *cx, double *cy, double *ch) +{ + Paragraph *para; + PangoRectangle rect; + int i; + double py = 0.0; + + if ( fr == NULL ) { + fprintf(stderr, "Cursor frame is NULL.\n"); + return 1; + } + + if ( cursor_para >= fr->n_paras ) { + fprintf(stderr, "Cursor paragraph number is too high!\n"); + return 1; + } + + para = fr->paras[cursor_para]; + for ( i=0; i<cursor_para; i++ ) { + py += paragraph_height(fr->paras[i]); + } + + if ( para->type != PARA_TYPE_TEXT ) { + return 1; + } + + pango_layout_get_cursor_pos(para->layout, cursor_pos, &rect, NULL); + + *cx = pango_units_to_double(rect.x) + fr->pad_l + para->space[0]; + *cy = pango_units_to_double(rect.y) + fr->pad_t + py + para->space[2]; + *ch = pango_units_to_double(rect.height); + return 0; +} + + +//void cursor_moveh(struct frame *fr, int *cpara, size_t *cpos, int *ctrail, +void cursor_moveh(struct frame *fr, struct edit_pos *cp, signed int dir) +{ + Paragraph *para = fr->paras[cp->para]; + int np = cp->pos; + + pango_layout_move_cursor_visually(para->layout, 1, cp->pos, cp->trail, + dir, &np, &cp->trail); + if ( np == -1 ) { + if ( cp->para > 0 ) { + size_t end_offs; + cp->para--; + end_offs = end_offset_of_para(fr, cp->para); + if ( end_offs > 0 ) { + cp->pos = end_offs - 1; + cp->trail = 1; + } else { + /* Jumping into an empty paragraph */ + cp->pos = 0; + cp->trail = 0; + } + return; + } else { + /* Can't move any further */ + return; + } + } + + if ( np == G_MAXINT ) { + if ( cp->para < fr->n_paras-1 ) { + cp->para++; + cp->pos = 0; + cp->trail = 0; + return; + } else { + /* Can't move any further */ + return; + } + } + + cp->pos = np; +} + + +void check_callback_click(struct frame *fr, int para) +{ + Paragraph *p = fr->paras[para]; + if ( p->type == PARA_TYPE_CALLBACK ) { + p->click_func(0.0, 0.0, p->bvp, p->vp); + } +} + + +static int get_paragraph_offset(Paragraph *para, int nrun) +{ + int i; + size_t t = 0; + + for ( i=0; i<nrun; i++ ) { + struct text_run *run = ¶->runs[i]; + t += run_text_len(run); + } + return t; +} + + +static int which_run(Paragraph *para, size_t offs) +{ + int i; + size_t t = 0; + + for ( i=0; i<para->n_runs; i++ ) { + struct text_run *run = ¶->runs[i]; + t += run_text_len(run); + if ( t > offs ) return i; + } + + /* Maybe offs points exactly to the end of the last run? */ + if ( t == offs ) return para->n_runs-1; + + return para->n_runs; +} + + +size_t pos_trail_to_offset(Paragraph *para, size_t offs, int trail) +{ + glong char_offs; + size_t run_offs; + const char *run_text; + struct text_run *run; + int nrun; + char *ptr; + size_t para_offset_of_run; + + nrun = which_run(para, offs); + + if ( nrun == para->n_runs ) { + fprintf(stderr, "pos_trail_to_offset: Offset too high\n"); + return 0; + } + + if ( para->n_runs == 0 ) { + return 0; + } + + run = ¶->runs[nrun]; + + if ( para->type != PARA_TYPE_TEXT ) return 0; + + if ( run == NULL ) { + fprintf(stderr, "pos_trail_to_offset: No run\n"); + return 0; + } + + if ( run->scblock == NULL ) { + fprintf(stderr, "pos_trail_to_offset: SCBlock = NULL?\n"); + return 0; + } + + /* Get the text for the run */ + run_text = text_for_run(run); + if ( run_text == NULL ) { + fprintf(stderr, "pos_trail_to_offset: No contents " + "(%p name=%s, options=%s)\n", + run->scblock, sc_block_name(run->scblock), + sc_block_options(run->scblock)); + return 0; + } + + + /* Turn the paragraph offset into a run offset */ + para_offset_of_run = get_paragraph_offset(para, nrun); + run_offs = offs - para_offset_of_run; + + char_offs = g_utf8_pointer_to_offset(run_text, run_text+run_offs); + char_offs += trail; + + if ( char_offs > g_utf8_strlen(run_text, -1) ) { + printf("Offset outside string! '%s'\n" + "char_offs %li offs %li len %li\n", + run_text, (long int)char_offs, (long int)offs, + (long int)g_utf8_strlen(run_text, -1)); + } + + ptr = g_utf8_offset_to_pointer(run_text, char_offs); + return ptr - run_text + para_offset_of_run; +} + + +int position_editable(struct frame *fr, struct edit_pos cp) +{ + Paragraph *para; + int run; + size_t paraoffs; + + if ( fr == NULL ) { + fprintf(stderr, "Frame is NULL.\n"); + return 0; + } + + if ( cp.para >= fr->n_paras ) { + fprintf(stderr, "Paragraph number is too high!\n"); + return 0; + } + + para = fr->paras[cp.para]; + + if ( para->type != PARA_TYPE_TEXT ) { + fprintf(stderr, "Paragraph is not text.\n"); + return 0; + } + + paraoffs = pos_trail_to_offset(para, cp.pos, cp.trail); + run = which_run(para, paraoffs); + if ( run == para->n_runs ) { + fprintf(stderr, "Couldn't find run!\n"); + return 0; + } + + if ( para->runs[run].real_text != NULL ) return 0; + + return 1; +} + + +void insert_text_in_paragraph(Paragraph *para, size_t offs, const char *t) +{ + int nrun; + + /* Find which run we are in */ + nrun = which_run(para, offs); + if ( nrun == para->n_runs ) { + fprintf(stderr, "Couldn't find run to insert into.\n"); + return; + } + + if ( para->n_runs == 0 ) { + printf("No runs in paragraph?\n"); + } else { + struct text_run *run; + size_t run_offs; + run = ¶->runs[nrun]; + run_offs = offs - get_paragraph_offset(para, nrun); + sc_insert_text(run->scblock, run_offs, t); + } +} + + +static SCBlock *pos_to_scblock(struct frame *fr, struct edit_pos p, + enum para_type *type) +{ + int run; + size_t paraoffs; + Paragraph *para; + + para = fr->paras[p.para]; + if ( type != NULL ) { + *type = para->type; + } + + if ( para->type != PARA_TYPE_TEXT ) { + return para->scblock; + } + + paraoffs = pos_trail_to_offset(para, p.pos, p.trail); + + run = which_run(para, paraoffs); + assert(run < para->n_runs); + + return para->runs[run].scblock; +} + + +static size_t pos_to_offset(struct frame *fr, struct edit_pos p) +{ + int run; + size_t paraoffs; + Paragraph *para; + + para = fr->paras[p.para]; + if ( para->type != PARA_TYPE_TEXT ) { + return 0; + } + + /* Offset of this position into the paragraph */ + paraoffs = pos_trail_to_offset(para, p.pos, p.trail); + + run = which_run(para, paraoffs); + assert(run < para->n_runs); + + /* Offset of this position into the run + * (and therefore into the SCBlock) */ + return paraoffs - get_paragraph_offset(para, run); +} + + +static int pos_to_run_number(struct frame *fr, struct edit_pos p) +{ + int run; + size_t paraoffs; + Paragraph *para; + + para = fr->paras[p.para]; + if ( para->type != PARA_TYPE_TEXT ) { + return 0; + } + + paraoffs = pos_trail_to_offset(para, p.pos, p.trail); + + run = which_run(para, paraoffs); + assert(run < para->n_runs); + + return run; +} + + +static void delete_run(Paragraph *para, int nrun) +{ + printf("deleting run %i of %i from para %p\n", nrun, para->n_runs, para); + memmove(¶->runs[nrun], ¶->runs[nrun+1], + (para->n_runs-nrun-1)*sizeof(struct text_run)); + para->n_runs--; +} + + +static Paragraph *scan_runs_for_scblock(struct frame *fr, int pn1, int pn2, + SCBlock *bl, int *run) +{ + int i; + + for ( i=pn1; i<=pn2; i++ ) { + + int j; + + /* Non-text paragraph - just one thing to check */ + if ( fr->paras[i]->scblock == bl ) { + *run = 0; + return fr->paras[i]; + } + + /* Check all runs */ + for ( j=0; j<fr->paras[i]->n_runs; j++ ) { + if ( fr->paras[i]->runs[j].scblock == bl ) { + *run = j; + return fr->paras[i]; + } + } + } + return NULL; +} + + +static Paragraph *find_run_for_scblock_next(struct frame *fr, int pn1, int pn2, + SCBlock *bl, int *run) +{ + if ( sc_block_child(bl) != NULL ) { + Paragraph *para; + para = find_run_for_scblock_next(fr, pn1, pn2, + sc_block_child(bl), run); + if ( para != NULL ) return para; + } + + do { + Paragraph *para; + para = scan_runs_for_scblock(fr, pn1, pn2, bl, run); + if ( para != NULL ) return para; + bl = sc_block_next(bl); + } while ( bl != NULL ); + + return NULL; +} + + +/* Find the run which contains the text from "bl", + * taking into account that it might be a child block, for example: + * {some text} + * \italic <---- bl points here + * {more text} <---- but this block is referenced by the run + * {final text} + */ +static Paragraph *find_run_for_scblock(struct frame *fr, int pn1, int pn2, + SCBlock *bl, int *run) +{ + Paragraph *para; + + show_sc_block(bl, "searching "); + para = scan_runs_for_scblock(fr, pn1, pn2, bl, run); + if ( para != NULL ) return para; + + if ( sc_block_child(bl) != NULL ) { + para = find_run_for_scblock_next(fr, pn1, pn2, sc_block_child(bl), run); + if ( para != NULL ) return para; + } + + return NULL; +} + + +static int paragraph_number(struct frame *fr, Paragraph *p, int *err) +{ + int i; + for ( i=0; i<fr->n_paras; i++ ) { + if ( fr->paras[i] == p ) return i; + } + fprintf(stderr, "Couldn't find paragraph %p\n", p); + *err = 1; + return 0; +} + + +static int find_para(struct frame *fr, Paragraph *para) +{ + int i; + + for ( i=0; i<fr->n_paras; i++ ) { + if ( fr->paras[i] == para ) return i; + } + + return fr->n_paras; +} + + +static void delete_paragraph(struct frame *fr, Paragraph *para, int *pnp) +{ + int pn = find_para(fr, para); + if ( pn == fr->n_paras ) { + fprintf(stderr, "Couldn't find paragraph to delete (%p)\n", para); + return; + } + + printf("deleting paragraph %i (%p)\n", pn, para); + + memmove(&fr->paras[pn], &fr->paras[pn+1], + (fr->n_paras-pn-1)*sizeof(Paragraph *)); + fr->n_paras--; + + if ( (pnp != NULL) && (*pnp > pn) ) { + (*pnp)--; + } +} + + +static void delete_run_for_scblock(struct frame *fr, + Paragraph *p1, Paragraph *p2, SCBlock *bl, + int *pnp) +{ + int pn1, pn2; + int err = 0; + Paragraph *para; + int run; + + pn1 = paragraph_number(fr, p1, &err); + pn2 = paragraph_number(fr, p2, &err); + if ( err ) return; + + para = find_run_for_scblock(fr, pn1, pn2, bl, &run); + if ( para == NULL ) { + fprintf(stderr, "Couldn't find block %p between paragraphs %p and %p\n", + bl, p1, p2); + return; + } + + if ( (run==0) && (para->scblock == bl ) ) { + delete_paragraph(fr, para, pnp); + } else { + delete_run(para, run); + } +} + + +static signed int merge_paragraph_runs(Paragraph *p1, Paragraph *p2) +{ + struct text_run *runs_new; + int i, spos; + + /* All the runs from p2 get added to p1 */ + runs_new = realloc(p1->runs, + (p1->n_runs+p2->n_runs)*sizeof(struct text_run)); + if ( runs_new == NULL ) { + fprintf(stderr, "Failed to allocate merged runs.\n"); + return -1; + } + p1->runs = runs_new; + + spos = p1->n_runs; + + /* The end of the united paragraph should now be the end of the + * second one */ + set_newline_at_end(p1, get_newline_at_end(p2)); + + for ( i=0; i<p2->n_runs; i++ ) { + p1->runs[p1->n_runs] = p2->runs[i]; + p1->n_runs++; + } + free(p2->runs); + free(p2); + + return spos; +} + + +static void merge_paragraphs_by_newpara(struct frame *fr, SCBlock *np) +{ + int i; + Paragraph *p1; + Paragraph *p2; + + for ( i=0; i<fr->n_paras-1; i++ ) { + if ( fr->paras[i]->newline_at_end == np ) { + + int j; + signed int spos; + + p1 = fr->paras[i]; + p2 = fr->paras[i+1]; + + printf("-------------------------------\n"); + show_para(p1); + printf("---x--------x------------------\n"); + show_para(p2); + spos = merge_paragraph_runs(p1, p2); + if ( spos < 0 ) { + fprintf(stderr, "Failed to merge paragraphs\n"); + return; + } + printf("-------------------------------\n"); + show_para(p1); + + for ( j=i+1; j<fr->n_paras-1; j++ ) { + fr->paras[j] = fr->paras[j+1]; + } + fr->n_paras--; + + return; + + } + } + + fprintf(stderr, "Couldn't find paragraphs to merge by newpara\n"); +} + + +static int find_block_inside(SCBlock *needle, SCBlock *bl) +{ + if ( needle == bl ) return 1; + + if ( sc_block_child(bl) != NULL ) { + if ( find_block_inside(needle, sc_block_child(bl)) ) return 1; + } + + if ( sc_block_next(bl) != NULL ) { + if ( find_block_inside(needle, sc_block_next(bl)) ) return 1; + } + + return 0; +} + + +/* Return true if "top" either IS "child", or contains "child" somewhere + * underneath, even if via a macro expansion */ +static int block_is_under(SCBlock *needle, SCBlock *top) +{ + if ( needle == top ) return 1; + + if ( sc_block_child(top) != NULL ) { + if ( find_block_inside(needle, sc_block_child(top)) ) return 1; + } + + /* Do not look at top->next here */ + + return 0; +} + + +void delete_text_from_frame(struct frame *fr, struct edit_pos p1, struct edit_pos p2, + double wrapw) +{ + int i; + SCBlock *p1scblock, *p2scblock; + enum para_type type1, type2; + size_t p2offs; + SCBlock *scblock; + int wrap_end; + + sort_positions(&p1, &p2); + + /* To make sure there are no nasty surprises ahead, run through the + * paragraphs we're about to touch, and make sure they all have at least + * an empty dummy run */ + for ( i=p1.para; i<=p2.para; i++ ) { + struct edit_pos ep; + ep.para = i; + ep.pos = 0; + ep.trail = 0; + ensure_run(fr, ep); + } + + if ( !position_editable(fr, p1) || !position_editable(fr, p2) ) { + fprintf(stderr, "Delete outside editable region\n"); + return; + } + + /* Find SC positions for start and end */ + p1scblock = pos_to_scblock(fr, p1, &type1); + p2scblock = pos_to_scblock(fr, p2, &type2); + p2offs = pos_to_offset(fr, p2); + wrap_end = p2.para; + + printf("SCBlocks %p to %p\n", p1scblock, p2scblock); + if ( p1scblock == NULL ) { + fprintf(stderr, "Starting block NULL. Not deleting.\n"); + return; + } + if ( p2scblock == NULL ) { + fprintf(stderr, "Ending block NULL. Not deleting.\n"); + return; + } + //show_sc_blocks(p1scblock); + + if ( (p1scblock == p2scblock) && (type1 == PARA_TYPE_TEXT) ) { + + size_t p1offs; + printf("Simple case, one SCBlock\n"); + + assert(type1 == type2); + + /* Remove the text and update the run length */ + p1offs = pos_to_offset(fr, p1); + scblock_delete_text(p1scblock, p1offs, p2offs); + + wrap_paragraph(fr->paras[p1.para], NULL, wrapw, 0, 0); + + return; + } + + /* Starting point for iteration over blocks in middle of range. + * Record this now, because p1scblock might be about to get deleted */ + scblock = sc_block_next(p1scblock); + + /* First SCBlock in range: delete whole thing or second half */ + printf("First block %p\n", p1scblock); + if ( type1 == PARA_TYPE_TEXT ) { + + size_t p1offs = pos_to_offset(fr, p1); + int p1run = pos_to_run_number(fr, p1); + printf(" offs %li\n", (long int)p1offs); + if ( p1offs != 0 ) { + printf("Partial delete\n"); + printf("contents '%s'\n", sc_block_contents(p1scblock)); + printf("from offs %li\n", (long int)p1offs); + scblock_delete_text(p1scblock, p1offs, -1); + } else { + printf("Deleting the whole text SCBlock\n"); + sc_block_delete(&fr->scblocks, p1scblock); + delete_run(fr->paras[p1.para], p1run); + } + + } else { + printf("Deleting the whole non-text SCBlock\n"); + sc_block_delete(&fr->scblocks, p1scblock); + } + + /* Delete all the complete SCBlocks in the middle of the range */ + if ( !block_is_under(p2scblock, scblock) ) { + do { + + SCBlock *next; + + /* For each SC block in middle of range: */ + printf("Deleting %p\n", scblock); + if ( scblock == NULL ) { + fprintf(stderr, "nothing?\n"); + break; + } + printf("name is '%s'\n", sc_block_name(scblock)); + + if ( (sc_block_name(scblock) != NULL) + && (strcmp(sc_block_name(scblock), "newpara") == 0) ) + { + /* Deleting newpara block, merge the paragraphs */ + merge_paragraphs_by_newpara(fr, scblock); + p2.para--; + } + + next = sc_block_next(scblock); + delete_run_for_scblock(fr, fr->paras[p1.para], + fr->paras[p2.para], scblock, + &p2.para); + sc_block_delete(&fr->scblocks, scblock); + + scblock = next; + + } while ( !block_is_under(p2scblock, scblock) ); + } + + /* Last SCBlock in range: delete whole thing or first half */ + printf("Last block %p (%s)\n", p2scblock, sc_block_name(p2scblock)); + if ( type2 == PARA_TYPE_TEXT ) { + size_t len; + printf(" offs %li\n", (long int)p2offs); + if ( sc_block_contents(p2scblock) != NULL ) { + len = strlen(sc_block_contents(p2scblock)); + } else { + len = 0; + } + printf(" len %li\n", (long int)len); + if ( (len > 0) && (p2offs == len) ) { + printf("Deleting the whole text SCBlock\n"); + printf("deleting block %p\n", p2scblock); + show_sc_block(p2scblock, ""); + sc_block_delete(&fr->scblocks, p2scblock); + delete_run_for_scblock(fr, fr->paras[p1.para], + fr->paras[p2.para], p2scblock, + NULL); + } else if ( p2offs > 0 ) { + printf("Partial delete\n"); + printf("contents '%s'\n", sc_block_contents(p2scblock)); + printf("up to offs %li\n", (long int)p2offs); + scblock_delete_text(p2scblock, 0, p2offs); + } /* else do nothing */ + } else { + printf("Deleting the whole non-text SCBlock\n"); + sc_block_delete(&fr->scblocks, p2scblock); + } + + /* If any paragraphs have been deleted, this will wrap too many + * paragraphs, but it doesn't matter as long as we don't wrap + * past the end of the frame's contents. */ + for ( i=p1.para; i<=wrap_end; i++ ) { + if ( i >= fr->n_paras ) break; + printf("Wrapping para %i (%p)\n", i, fr->paras[i]); + wrap_paragraph(fr->paras[i], NULL, wrapw, 0, 0); + } + printf("All done.\n"); +} + + +void show_para(Paragraph *p) +{ + printf("Paragraph %p\n", p); + + if ( p->type == PARA_TYPE_TEXT ) { + + int i; + + printf("%i runs:\n", p->n_runs); + for ( i=0; i<p->n_runs; i++ ) { + printf(" Run %2i: SCBlock %p %s '%s'\n", + i, p->runs[i].scblock, + pango_font_description_to_string(p->runs[i].fontdesc), + sc_block_contents(p->runs[i].scblock)); + } + + } else if ( p->type == PARA_TYPE_IMAGE ) { + printf(" Image: %s\n", p->filename); + } else { + printf(" Other paragraph type\n"); + } +} + + +static SCBlock *split_text_paragraph(struct frame *fr, int pn, size_t pos, + PangoContext *pc) +{ + Paragraph *pnew; + int i; + SCBlock *nnp; + size_t run_offs; + int run; + Paragraph *para = fr->paras[pn]; + struct text_run *rr; + + pnew = insert_paragraph(fr, pn); + if ( pnew == NULL ) { + fprintf(stderr, "Failed to insert paragraph\n"); + return NULL; + } + + /* Determine which run the cursor is in */ + run = which_run(para, pos); + + /* Create the new (second) paragraph */ + pnew->type = PARA_TYPE_TEXT; + pnew->n_runs = 0; + pnew->runs = NULL; + for ( i=0; i<4; i++ ) pnew->space[i] = para->space[i]; + + rr = ¶->runs[run]; + run_offs = pos - get_paragraph_offset(para, run); + printf("split at run %i\n", run); + + /* Easy case: splitting at a run boundary */ + if ( run_offs == run_text_len(rr) ) { + + /* Even easier case: splitting at the end of the paragraph */ + if ( run == para->n_runs-1 ) { + + SCBlock *np; + SCBlock *end; + + printf("Simple new para\n"); + + if ( get_newline_at_end(para) == NULL ) { + + /* The current paragraph doesn't have + * a \newpara yet */ + + np = sc_block_append(rr->scblock, + strdup("newpara"), NULL, + NULL, NULL); + set_newline_at_end(para, np); + + } else { + + SCBlock *op; + + /* If the current paragraph did have \newpara, + * then the new one needs one too */ + np = sc_block_append(rr->scblock, + strdup("newpara"), + NULL, NULL, NULL); + op = get_newline_at_end(para); + set_newline_at_end(para, np); + set_newline_at_end(pnew, op); + + + } + + /* Add an empty run + SCBlock to type into */ + end = sc_block_append(np, NULL, NULL, strdup(""), NULL); + + pnew->n_runs = 0; + add_run(pnew, end, rr->fontdesc, rr->col, NULL); + pnew->scblock = end; + + wrap_paragraph(pnew, pc, fr->w - fr->pad_l - fr->pad_r, 0, 0); + + return end; + + } + + } else { + + /* Split the run (and SCBlock) into two */ + double col[4] = {0,0,0,0}; + struct text_run *rn; + + printf("Splitting run %i. Before:\n", run); + show_para(para); + + add_run(para, NULL, NULL, col, NULL); + /* -2 here because add_run increased para->n_runs by 1 */ + memmove(¶->runs[run+2], ¶->runs[run+1], + (para->n_runs - run - 2)*sizeof(struct text_run)); + + rr = ¶->runs[run]; /* Because add_run realloced the runs */ + rn = ¶->runs[run+1]; + + rn->scblock = sc_block_split(rr->scblock, run_offs); + + rn->fontdesc = pango_font_description_copy(rr->fontdesc); + rn->col[0] = rr->col[0]; + rn->col[1] = rr->col[1]; + rn->col[2] = rr->col[2]; + rn->col[3] = rr->col[3]; + + printf("After:\n"); + show_para(para); + + } + + /* All later runs just get moved to the new paragraph */ + for ( i=run+1; i<para->n_runs; i++ ) { + double col[4] = {0,0,0,0}; + printf("Moving run %i to pos %i\n", i, pnew->n_runs); + add_run(pnew, NULL, NULL, col, NULL); + pnew->runs[pnew->n_runs-1] = para->runs[i]; + } + pnew->scblock = pnew->runs[0].scblock; + + /* Truncate the first paragraph at the appropriate position */ + para->n_runs = run+1; + + printf("Final paragraphs:\n"); + printf("First:\n"); + show_para(para); + printf("Second:\n"); + show_para(pnew); + + /* Add a \newpara after the end of the first paragraph's SC */ + nnp = sc_block_append(rr->scblock, strdup("newpara"), NULL, NULL, NULL); + set_newline_at_end(pnew, get_newline_at_end(para)); + set_newline_at_end(para, nnp); + + wrap_paragraph(para, pc, fr->w - fr->pad_l - fr->pad_r, 0, 0); + wrap_paragraph(pnew, pc, fr->w - fr->pad_l - fr->pad_r, 0, 0); + + return sc_block_next(nnp); +} + + +SCBlock *split_paragraph(struct frame *fr, int pn, size_t pos, PangoContext *pc) +{ + Paragraph *para = fr->paras[pn]; + + if ( para->type == PARA_TYPE_TEXT ) { + return split_text_paragraph(fr, pn, pos, pc); + } else { + /* Other types can't be split */ + return NULL; + } +} + + +SCBlock *block_at_cursor(struct frame *fr, int pn, size_t pos) +{ + Paragraph *para = fr->paras[pn]; + + if ( para->type != PARA_TYPE_CALLBACK ) return NULL; + + return para->scblock; +} + + +int get_sc_pos(struct frame *fr, int pn, size_t pos, + SCBlock **bl, size_t *ppos) +{ + Paragraph *para = fr->paras[pn]; + int nrun; + struct text_run *run; + + nrun = which_run(para, pos); + if ( nrun == para->n_runs ) { + fprintf(stderr, "Couldn't find run to insert into.\n"); + return 1; + } + run = ¶->runs[nrun]; + + *ppos = pos - get_paragraph_offset(para, nrun); + *bl = run->scblock; + + return 0; +} + + +void set_para_spacing(Paragraph *para, float space[4]) +{ + if ( para == NULL ) return; + para->space[0] = space[0]; + para->space[1] = space[1]; + para->space[2] = space[2]; + para->space[3] = space[3]; +} + + +void set_para_alignment(Paragraph *para, PangoAlignment align) +{ + if ( para == NULL ) return; + para->alignment = align; +} + + +void *get_para_bvp(Paragraph *para) +{ + if ( para->type != PARA_TYPE_CALLBACK ) return NULL; + return para->bvp; +} + + +SCBlock *para_scblock(Paragraph *para) +{ + return para->scblock; +} + + +enum para_type para_type(Paragraph *para) +{ + return para->type; +} + + +int para_debug_num_runs(Paragraph *para) +{ + if ( para->type != PARA_TYPE_TEXT ) return 0; + return para->n_runs; +} + + +int para_debug_run_info(Paragraph *para, int i, SCBlock **scblock) +{ + if ( para->type != PARA_TYPE_TEXT ) return 1; + if ( i >= para->n_runs ) return 1; + + *scblock = para->runs[i].scblock; + return 0; +} diff --git a/src-old/frame.h b/src-old/frame.h new file mode 100644 index 0000000..d0525f8 --- /dev/null +++ b/src-old/frame.h @@ -0,0 +1,194 @@ +/* + * frame.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 FRAME_H +#define FRAME_H + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pango/pango.h> +#include <cairo.h> + + +typedef enum +{ + GRAD_NONE, + GRAD_HORIZ, + GRAD_VERT +} GradientType; + +enum para_type +{ + PARA_TYPE_TEXT, + PARA_TYPE_IMAGE, + PARA_TYPE_CALLBACK +}; + +typedef struct _paragraph Paragraph; + +#include "sc_parse.h" +#include "sc_interp.h" +#include "imagestore.h" + + +struct edit_pos +{ + int para; /* Paragraph number */ + size_t pos; /* Byte position within paragraph + * Yes, really. See pango_layout_xy_to_index */ + int trail; +}; + + +struct frame +{ + struct frame **children; + int num_children; + int max_children; + + SCBlock *scblocks; + + Paragraph **paras; + int n_paras; + int max_paras; + + /* The font which will be used by default for this frame */ + PangoFontDescription *fontdesc; + double col[4]; + + /* The rectangle allocated to this frame, determined by the renderer */ + double x; + double y; + double w; + double h; + + double pad_t; + double pad_b; + double pad_l; + double pad_r; + + /* Background properties for this frame */ + double bgcol[4]; + double bgcol2[4]; + GradientType grad; + + /* True if this frame should be deleted on the next mouse click */ + int empty; + + /* True if the aspect ratio of this frame should be maintained */ + int is_image; + + /* True if this frame can be resized and moved */ + int resizable; +}; + + +extern struct frame *frame_new(void); +extern void frame_free(struct frame *fr); +extern struct frame *add_subframe(struct frame *fr); +extern struct frame *find_frame_with_scblocks(struct frame *top, + SCBlock *scblocks); + +extern double total_height(struct frame *fr); + +extern Paragraph *last_para(struct frame *fr); +extern void show_para(Paragraph *p); +extern void set_para_spacing(Paragraph *para, float space[4]); +extern void set_para_alignment(Paragraph *para, PangoAlignment align); + +extern double paragraph_height(Paragraph *para); +extern void render_paragraph(cairo_t *cr, Paragraph *para, ImageStore *is); + +extern void set_newline_at_end(Paragraph *para, SCBlock *bl); +extern void show_edit_pos(struct edit_pos a); + +extern void add_run(Paragraph *para, SCBlock *scblock, + PangoFontDescription *fdesc, double col[4], + const char *real_text); + +extern Paragraph *insert_paragraph(struct frame *fr, int pos); + +extern Paragraph *add_callback_para(struct frame *fr, SCBlock *scblock, + double w, double h, + SCCallbackDrawFunc draw_func, + SCCallbackClickFunc click_func, void *bvp, + void *vp); + +extern void add_image_para(struct frame *fr, SCBlock *scblock, + const char *filename, + ImageStore *is, double w, double h, int editable); + +extern void wrap_paragraph(Paragraph *para, PangoContext *pc, double w, + size_t sel_start, size_t sel_end); + +extern int find_cursor(struct frame *fr, double x, double y, + struct edit_pos *pos); + +extern void sort_positions(struct edit_pos *a, struct edit_pos *b); + +extern void ensure_run(struct frame *fr, struct edit_pos cpos); + +extern int positions_equal(struct edit_pos a, struct edit_pos b); + +extern int get_para_highlight(struct frame *fr, int cursor_para, + double *cx, double *cy, double *cw, double *ch); + +extern int position_editable(struct frame *fr, struct edit_pos cp); + +extern int get_cursor_pos(struct frame *fr, int cursor_para, int cursor_pos, + double *cx, double *cy, double *ch); + +extern void cursor_moveh(struct frame *fr, struct edit_pos *cp, signed int dir); + +extern void check_callback_click(struct frame *fr, int para); + +extern size_t pos_trail_to_offset(Paragraph *para, size_t offs, int trail); + +extern void insert_text_in_paragraph(Paragraph *para, size_t offs, + const char *t); + +extern void delete_text_from_frame(struct frame *fr, struct edit_pos p1, struct edit_pos p2, + double wrap_w); + +extern SCBlock *split_paragraph(struct frame *fr, int pn, size_t pos, + PangoContext *pc); +extern SCBlock *block_at_cursor(struct frame *fr, int para, size_t pos); + +extern void show_frame_hierarchy(struct frame *fr, const char *t); + +extern int get_sc_pos(struct frame *fr, int pn, size_t pos, + SCBlock **bl, size_t *ppos); + +extern void *get_para_bvp(Paragraph *para); + +extern Paragraph *create_paragraph(struct frame *fr, SCBlock *bl); + +extern enum para_type para_type(Paragraph *para); +extern SCBlock *para_scblock(Paragraph *para); + +extern SCBlock *para_debug_get_newline_at_end(Paragraph *para); +extern int para_debug_num_runs(Paragraph *para); +extern int para_debug_run_info(Paragraph *para, int i, SCBlock **scblock); + +#endif /* FRAME_H */ diff --git a/src-old/imagestore.c b/src-old/imagestore.c new file mode 100644 index 0000000..61ebcfb --- /dev/null +++ b/src-old/imagestore.c @@ -0,0 +1,369 @@ +/* + * imagestore.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 <libgen.h> +#include <cairo.h> +#include <glib.h> +#include <gio/gio.h> +#include <gdk/gdk.h> +#include <gdk-pixbuf/gdk-pixbuf.h> + +#include "imagestore.h" +#include "utils.h" + +#define MAX_SIZES (32) + +struct image_record +{ + char *filename; + cairo_surface_t *surf[MAX_SIZES]; + int w[MAX_SIZES]; + int last_used[MAX_SIZES]; + int n_sizes; + int h_last_used; + int full_width; + int full_height; +}; + + +struct _imagestore +{ + int n_images; + struct image_record *images; + int max_images; + GFile *pparent; + GFile *iparent; + const char *storename; +}; + + +static int alloc_images(ImageStore *is, int new_max_images) +{ + struct image_record *images_new; + + images_new = realloc(is->images, + sizeof(struct image_record)*new_max_images); + if ( images_new == NULL ) return 1; + + is->images = images_new; + is->max_images = new_max_images; + return 0; +} + + +ImageStore *imagestore_new(const char *storename) +{ + ImageStore *is; + + is = calloc(1, sizeof(ImageStore)); + if ( is == NULL ) return NULL; + + is->images = NULL; + is->pparent = NULL; + if ( storename != NULL ) { + is->iparent = g_file_new_for_uri(storename); + } else { + is->iparent = NULL; + } + is->n_images = 0; + is->max_images = 0; + if ( alloc_images(is, 32) ) { + free(is); + return NULL; + } + + return is; +} + + +void imagestore_set_parent(ImageStore *is, GFile *parent) +{ + if ( is->pparent != NULL ) { + g_object_unref(is->pparent); + } + is->pparent = parent; +} + + +void imagestore_destroy(ImageStore *is) +{ + int i; + + for ( i=0; i<is->n_images; i++ ) { + int j; + free(is->images[i].filename); + for ( j=0; j<is->images[i].n_sizes; j++ ) { + if ( is->images[i].surf[j] != NULL ) { + g_object_unref(is->images[i].surf[j]); + } + } + } + g_object_unref(is->pparent); + g_object_unref(is->iparent); + free(is->images); + free(is); +} + + +static cairo_surface_t *pixbuf_to_surface(GdkPixbuf *t) +{ + cairo_surface_t *surf; + cairo_t *cr; + int w, h; + + if ( t == NULL ) return NULL; + + w = gdk_pixbuf_get_width(t); + h = gdk_pixbuf_get_height(t); + + surf = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w, h); + if ( surf == NULL ) return NULL; + + cr = cairo_create(surf); + + gdk_cairo_set_source_pixbuf(cr, t, 0, 0); + cairo_pattern_t *patt = cairo_get_source(cr); + cairo_pattern_set_extend(patt, CAIRO_EXTEND_PAD); + cairo_pattern_set_filter(patt, CAIRO_FILTER_BEST); + cairo_paint(cr); + + cairo_destroy(cr); + + return surf; +} + + +static int try_load(GFile *file, GdkPixbuf **pixbuf, gint w, gint h) +{ + GFileInputStream *stream; + GError *error = NULL; + + stream = g_file_read(file, NULL, &error); + if ( stream != NULL ) { + GError *pberr = NULL; + *pixbuf = gdk_pixbuf_new_from_stream_at_scale(G_INPUT_STREAM(stream), + w, h, TRUE, + NULL, &pberr); + g_object_unref(stream); + g_object_unref(file); + return 1; + } + + return 0; +} + + +static GdkPixbuf *load_image(const char *uri, GFile *pparent, GFile *iparent, + gint w, gint h) +{ + GFile *file; + GdkPixbuf *pixbuf; + + /* Literal pathname */ + file = g_file_new_for_path(uri); + if ( try_load(file, &pixbuf, w, h) ) return pixbuf; + + /* Try the file prefixed with the directory the presentation is in */ + if ( pparent != NULL ) { + file = g_file_get_child(pparent, uri); + if ( try_load(file, &pixbuf, w, h) ) return pixbuf; + } + + /* Try prefixing with imagestore folder */ + if ( iparent != NULL ) { + file = g_file_get_child(iparent, uri); + if ( try_load(file, &pixbuf, w, h) ) return pixbuf; + } + + return NULL; +} + + +static struct image_record *add_image_record(ImageStore *is, + const char *filename) +{ + int idx; + + if ( is->n_images == is->max_images ) { + if ( alloc_images(is, is->max_images+32) ) { + fprintf(stderr, "Couldn't allocate memory for image " + "records.\n"); + return NULL; + } + } + + idx = is->n_images++; + + is->images[idx].n_sizes = 0; + is->images[idx].filename = strdup(filename); + is->images[idx].h_last_used = 0; + + return &is->images[idx]; +} + + + +int imagestore_get_size(ImageStore *is, const char *filename, + int *w, int *h) +{ + GdkPixbuf *pixbuf; + struct image_record *ir; + int i; + + for ( i=0; i<is->n_images; i++ ) { + if ( strcmp(is->images[i].filename, filename) == 0 ) { + *w = is->images[i].full_width; + *h = is->images[i].full_height; + return 0; + } + } + + pixbuf = load_image(filename, is->pparent, is->iparent, -1, -1); + if ( pixbuf == NULL ) return 1; + + + *w = gdk_pixbuf_get_width(pixbuf); + *h = gdk_pixbuf_get_height(pixbuf); + + ir = add_image_record(is, filename); + if ( ir != NULL ) { + ir->full_width = *w; + ir->full_height = *h; + } /* otherwise can't cache, too bad */ + + g_object_unref(pixbuf); + + return 0; +} + + +static int find_earliest(struct image_record *im) +{ + int i, earliest, l; + + earliest = im->h_last_used; + l = im->n_sizes; + + for ( i=0; i<im->n_sizes; i++ ) { + if ( im->last_used[i] < earliest ) { + earliest = im->last_used[i]; + l = i; + } + } + + assert(l != im->n_sizes); + + cairo_surface_destroy(im->surf[l]); + + return l; +} + + +static cairo_surface_t *add_image_size(struct image_record *im, + const char *filename, + GFile *pparent, GFile *iparent, + int w) +{ + cairo_surface_t *surf; + GdkPixbuf *t; + int pos; + + t = load_image(filename, pparent, iparent, w, -1); + if ( t == NULL ) return NULL; + surf = pixbuf_to_surface(t); + g_object_unref(t); + + /* Add surface to list */ + if ( im->n_sizes == MAX_SIZES ) { + pos = find_earliest(im); + } else { + pos = im->n_sizes++; + } + + im->w[pos] = w; + im->surf[pos] = surf; + im->last_used[pos] = im->h_last_used++; + + return surf; +} + + +static cairo_surface_t *add_new_image(ImageStore *is, const char *filename, int w) +{ + struct image_record *ir; + ir = add_image_record(is, filename); + if ( ir == NULL ) return NULL; + return add_image_size(ir, filename, is->pparent, is->iparent, w); +} + + +void show_imagestore(ImageStore *is) +{ + int i; + + printf("Store %p contains %i records.\n", is, is->n_images); + + for ( i=0; i<is->n_images; i++ ) { + + printf("%s :\n", is->images[i].filename); + printf("\n"); + + } +} + + +cairo_surface_t *lookup_image(ImageStore *is, const char *filename, int w) +{ + int i, j; + int found = 0; + cairo_surface_t *surf; + + for ( i=0; i<is->n_images; i++ ) { + if ( strcmp(is->images[i].filename, filename) == 0 ) { + found = 1; + break; + } + } + if ( !found ) { + return add_new_image(is, filename, w); + } + + for ( j=0; j<is->images[i].n_sizes; j++ ) { + if ( is->images[i].w[j] == w ) { + is->images[i].last_used[j] = is->images[i].h_last_used++; + return is->images[i].surf[j]; + } + } + + /* We don't have this size yet */ + surf = add_image_size(&is->images[i], filename, is->pparent, + is->iparent, w); + return surf; +} diff --git a/src-old/imagestore.h b/src-old/imagestore.h new file mode 100644 index 0000000..0d4df24 --- /dev/null +++ b/src-old/imagestore.h @@ -0,0 +1,43 @@ +/* + * imagestore.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 IMAGESTORE_H +#define IMAGESTORE_H + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <cairo.h> +#include <gio/gio.h> + +typedef struct _imagestore ImageStore; + +extern ImageStore *imagestore_new(const char *storename); +extern void imagestore_destroy(ImageStore *is); +extern void imagestore_set_parent(ImageStore *is, GFile *parent); +extern void show_imagestore(ImageStore *is); + +extern cairo_surface_t *lookup_image(ImageStore *is, const char *uri, int w); +extern int imagestore_get_size(ImageStore *is, const char *uri, int *w, int *h); + +#endif /* IMAGESTORE_H */ diff --git a/src-old/narrative_window.c b/src-old/narrative_window.c new file mode 100644 index 0000000..e0c59ef --- /dev/null +++ b/src-old/narrative_window.c @@ -0,0 +1,927 @@ +/* + * narrative_window.c + * + * Copyright © 2014-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 <gtk/gtk.h> +#include <assert.h> +#include <string.h> +#include <stdlib.h> + +#include "colloquium.h" +#include "presentation.h" +#include "narrative_window.h" +#include "sc_editor.h" +#include "sc_parse.h" +#include "render.h" +#include "testcard.h" +#include "pr_clock.h" +#include "print.h" +#include "utils.h" +#include "stylesheet_editor.h" + + +struct _narrative_window +{ + GtkWidget *window; + GtkToolItem *bfirst; + GtkToolItem *bprev; + GtkToolItem *bnext; + GtkToolItem *blast; + SCEditor *sceditor; + GApplication *app; + struct presentation *p; + SCBlock *dummy_top; + SCSlideshow *show; + int show_no_slides; + PRClock *pr_clock; + SlideWindow *slidewindows[16]; + int n_slidewindows; +}; + + +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_para; + + 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 { + 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->sceditor)-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); + } +} + + +struct saveas_info +{ + NarrativeWindow *nw; + + /* Radio buttons for how to save stylesheet */ + GtkWidget *privatess; + GtkWidget *folderss; + GtkWidget *noss; +}; + + +static gint saveas_response_sig(GtkWidget *d, gint response, + struct saveas_info *si) +{ + if ( response == GTK_RESPONSE_ACCEPT ) { + + GFile *file = gtk_file_chooser_get_file(GTK_FILE_CHOOSER(d)); + 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 where to save stylesheet!\n"); + } + + if ( save_presentation(si->nw->p, file, ssfile) ) { + show_error(si->nw, _("Failed to save presentation")); + } + + /* save_presentation keeps a reference to both of these */ + g_object_unref(file); + if ( ssfile != NULL ) g_object_unref(ssfile); + + } + + gtk_widget_destroy(d); + free(si); + + return 0; +} + + +static void saveas_sig(GSimpleAction *action, GVariant *parameter, gpointer vp) +{ + GtkWidget *d; + GtkWidget *box; + NarrativeWindow *nw = vp; + struct saveas_info *si; + + si = malloc(sizeof(struct saveas_info)); + if ( si == NULL ) return; + + si->nw = nw; + + d = gtk_file_chooser_dialog_new(_("Save presentation"), + GTK_WINDOW(nw->window), + GTK_FILE_CHOOSER_ACTION_SAVE, + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_Save"), GTK_RESPONSE_ACCEPT, + NULL); + gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(d), + TRUE); + + box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8); + gtk_file_chooser_set_extra_widget(GTK_FILE_CHOOSER(d), box); + si->privatess = gtk_radio_button_new_with_label(NULL, + _("Create/update private stylesheet for this presentation")); + gtk_box_pack_start(GTK_BOX(box), si->privatess, FALSE, FALSE, 0); + si->folderss = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(si->privatess), + _("Create/update default stylesheet for presentations in folder")); + gtk_box_pack_start(GTK_BOX(box), si->folderss, FALSE, FALSE, 0); + si->noss = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(si->privatess), + _("Don't save stylesheet at all")); + gtk_box_pack_start(GTK_BOX(box), si->noss, FALSE, FALSE, 0); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(si->privatess), TRUE); + + g_signal_connect(G_OBJECT(d), "response", + G_CALLBACK(saveas_response_sig), si); + + 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; + + if ( nw->p->file == NULL ) { + return saveas_sig(NULL, NULL, nw); + } + + save_presentation(nw->p, nw->p->file, nw->p->stylesheet_from); +} + + +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; + } + + 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 gint load_ss_response_sig(GtkWidget *d, gint response, + NarrativeWindow *nw) +{ + if ( response == GTK_RESPONSE_ACCEPT ) { + + 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 stylesheet\n")); + } + + g_object_unref(file); + + } + + gtk_widget_destroy(d); + + return 0; +} + + +static void stylesheet_changed_sig(GtkWidget *da, NarrativeWindow *nw) +{ + int i; + + /* 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); + + /* Full rerender of all slide windows */ + for ( i=0; i<nw->n_slidewindows; i++ ) { + slide_window_update(nw->slidewindows[i]); + } +} + + +static void edit_ss_sig(GSimpleAction *action, GVariant *parameter, + gpointer vp) +{ + NarrativeWindow *nw = vp; + 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 load_ss_sig(GSimpleAction *action, GVariant *parameter, + gpointer vp) +{ + //SCBlock *nsblock; + //SCBlock *templ; + NarrativeWindow *nw = vp; + 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 add_slide_sig(GSimpleAction *action, GVariant *parameter, + gpointer vp) +{ + SCBlock *nsblock; + SCBlock *templ; + NarrativeWindow *nw = vp; + + sc_editor_ensure_cursor(nw->sceditor); + + /* Split the current paragraph */ + nsblock = split_paragraph_at_cursor(nw->sceditor); + + /* FIXME: Template from JSON */ + templ = sc_parse("\\slide{}"); + + /* 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 first_para_sig(GSimpleAction *action, GVariant *parameter, + gpointer vp) +{ + NarrativeWindow *nw = vp; + 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_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 void prev_para_sig(GSimpleAction *action, GVariant *parameter, + gpointer vp) +{ + 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->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; + } + } + + 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); +} + + +static void next_para_sig(GSimpleAction *action, GVariant *parameter, + gpointer vp) +{ + 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->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; + nw->pr_clock = pr_clock_new(); +} + + +static void testcard_sig(GSimpleAction *action, GVariant *parameter, + gpointer vp) +{ + NarrativeWindow *nw = vp; + show_testcard(nw->p); +} + + +static gint export_pdf_response_sig(GtkWidget *d, gint response, + struct presentation *p) +{ + if ( response == GTK_RESPONSE_ACCEPT ) { + char *filename; + filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(d)); + export_pdf(p, filename); + g_free(filename); + } + + gtk_widget_destroy(d); + + return 0; +} + + +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) +{ + NarrativeWindow *nw = vp; + GtkWidget *d; + + d = gtk_file_chooser_dialog_new(_("Export PDF"), + NULL, + GTK_FILE_CHOOSER_ACTION_SAVE, + _("_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), nw->p); + + gtk_widget_show_all(d); +} + + + +static gboolean nw_button_press_sig(GtkWidget *da, GdkEventButton *event, + NarrativeWindow *nw) +{ + return 0; +} + + +static void changed_sig(GtkWidget *da, NarrativeWindow *nw) +{ + nw->p->saved = 0; + update_titlebar(nw); +} + + +static void scroll_down(NarrativeWindow *nw) +{ + gdouble inc, val; + GtkAdjustment *vadj; + + vadj = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(nw->sceditor)); + inc = gtk_adjustment_get_step_increment(GTK_ADJUSTMENT(vadj)); + val = gtk_adjustment_get_value(GTK_ADJUSTMENT(vadj)); + gtk_adjustment_set_value(GTK_ADJUSTMENT(vadj), inc+val); +} + + +static gboolean nw_destroy_sig(GtkWidget *da, NarrativeWindow *nw) +{ + g_application_release(nw->app); + return FALSE; +} + + +static gboolean nw_key_press_sig(GtkWidget *da, GdkEventKey *event, + NarrativeWindow *nw) +{ + switch ( event->keyval ) { + + case GDK_KEY_B : + case GDK_KEY_b : + if ( nw->show != NULL ) { + scroll_down(nw); + return TRUE; + } + break; + + case GDK_KEY_Page_Up : + if ( nw->show != NULL ) { + ss_prev_para(nw->show, nw); + return TRUE; + } + break; + + case GDK_KEY_Page_Down : + if ( nw->show != NULL) { + 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 int create_thumbnail(SCInterpreter *scin, SCBlock *bl, + double *w, double *h, void **bvp, void *vp) +{ + struct presentation *p = vp; + + *w = 270.0*(p->slide_width / p->slide_height); + *h = 270.0; + *bvp = bl; + + return 1; +} + + +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; + struct frame *top; + int sn = slide_number(p, scblocks); + + /* FIXME: Cache like crazy here */ + 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; +} + + +static int click_thumbnail(double x, double y, void *bvp, void *vp) +{ + struct presentation *p = vp; + SCBlock *scblocks = bvp; + NarrativeWindow *nw = p->narrative_window; + + if ( p->narrative_window->show != NULL ) { + sc_slideshow_set_slide(nw->show, scblocks); + } else { + if ( nw->n_slidewindows >= 16 ) { + show_error(nw, _("Too many open slide windows")); + } else { + nw->slidewindows[nw->n_slidewindows++] = slide_window_open(p, scblocks, + p->narrative_window->app); + } + } + + return 0; +} + + +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; + char *title_new; + + title = get_titlebar_string(nw->p); + title_new = realloc(title, strlen(title)+16); + if ( title_new == NULL ) { + free(title); + return; + } else { + title = title_new; + } + + strcat(title, " - Colloquium"); + if ( !nw->p->saved ) { + strcat(title, " *"); + } + gtk_window_set_title(GTK_WINDOW(nw->window), title); + + /* FIXME: Update all slide windows belonging to this NW */ + + free(title); +} + + +void narrative_window_sw_closed(NarrativeWindow *nw, SlideWindow *sw) +{ + int i; + int found = 0; + + for ( i=0; i<nw->n_slidewindows; i++ ) { + if ( nw->slidewindows[i] == sw ) { + + int j; + for ( j=i; j<nw->n_slidewindows-1; j++ ) { + nw->slidewindows[j] = nw->slidewindows[j+1]; + } + nw->n_slidewindows--; + found = 1; + } + } + + if ( !found ) { + fprintf(stderr, "Couldn't find slide window in narrative record\n"); + } +} + + +NarrativeWindow *narrative_window_new(struct presentation *p, GApplication *papp) +{ + NarrativeWindow *nw; + GtkWidget *vbox; + GtkWidget *scroll; + GtkWidget *toolbar; + GtkToolItem *button; + SCCallbackList *cbl; + GtkWidget *image; + Colloquium *app = COLLOQUIUM(papp); + + if ( p->narrative_window != NULL ) { + fprintf(stderr, "Narrative window is already open!\n"); + return NULL; + } + + nw = calloc(1, sizeof(NarrativeWindow)); + if ( nw == NULL ) return NULL; + + nw->app = papp; + nw->p = p; + nw->n_slidewindows = 0; + + 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); + + vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); + gtk_container_add(GTK_CONTAINER(nw->window), vbox); + + /* If the presentation is completely empty, give ourselves at least + * something to work with */ + if ( nw->p->scblocks == NULL ) { + nw->p->scblocks = sc_parse(""); + } + + /* 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, "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); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(toolbar), FALSE, FALSE, 0); + + /* Fullscreen */ + image = gtk_image_new_from_icon_name("view-fullscreen", + GTK_ICON_SIZE_LARGE_TOOLBAR); + 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)); + + button = gtk_separator_tool_item_new(); + gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(button)); + + /* Add slide */ + image = gtk_image_new_from_icon_name("list-add", + GTK_ICON_SIZE_LARGE_TOOLBAR); + 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)); + + button = gtk_separator_tool_item_new(); + gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(button)); + + image = gtk_image_new_from_icon_name("go-top", + GTK_ICON_SIZE_LARGE_TOOLBAR); + 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("go-up", + GTK_ICON_SIZE_LARGE_TOOLBAR); + 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("go-down", + GTK_ICON_SIZE_LARGE_TOOLBAR); + 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("go-bottom", + GTK_ICON_SIZE_LARGE_TOOLBAR); + 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"); + + update_toolbar(nw); + + scroll = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), + GTK_POLICY_NEVER, GTK_POLICY_ALWAYS); + gtk_container_add(GTK_CONTAINER(scroll), GTK_WIDGET(nw->sceditor)); + + sc_editor_set_flow(nw->sceditor, 1); + sc_editor_set_background(nw->sceditor, 0.9, 0.9, 0.9); + sc_editor_set_min_border(nw->sceditor, 0.0); + sc_editor_set_top_frame_editable(nw->sceditor, 1); + + g_signal_connect(G_OBJECT(nw->sceditor), "button-press-event", + 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(nw_key_press_sig), nw); + g_signal_connect(G_OBJECT(nw->window), "destroy", + 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); + g_application_hold(papp); + + return nw; +} diff --git a/src-old/narrative_window.h b/src-old/narrative_window.h new file mode 100644 index 0000000..24b4a4b --- /dev/null +++ b/src-old/narrative_window.h @@ -0,0 +1,42 @@ +/* + * narrative_window.h + * + * Copyright © 2014-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 NARRATIVE_WINDOW_H +#define NARRATIVE_WINDOW_H + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + + +typedef struct _narrative_window NarrativeWindow; + +#include "slide_window.h" + +extern NarrativeWindow *narrative_window_new(struct presentation *p, + GApplication *app); + +extern void update_titlebar(NarrativeWindow *nw); + +extern void narrative_window_sw_closed(NarrativeWindow *nw, SlideWindow *sw); + +#endif /* NARRATIVE_WINDOW_H */ diff --git a/src-old/pr_clock.c b/src-old/pr_clock.c new file mode 100644 index 0000000..8085c89 --- /dev/null +++ b/src-old/pr_clock.c @@ -0,0 +1,438 @@ +/* + * 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 "presentation.h" +#include "pr_clock.h" +#include "utils.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-old/pr_clock.h b/src-old/pr_clock.h new file mode 100644 index 0000000..97d2d0d --- /dev/null +++ b/src-old/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-old/presentation.c b/src-old/presentation.c new file mode 100644 index 0000000..d7d9c08 --- /dev/null +++ b/src-old/presentation.c @@ -0,0 +1,362 @@ +/* + * presentation.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 "presentation.h" +#include "slide_window.h" +#include "frame.h" +#include "imagestore.h" +#include "render.h" +#include "sc_interp.h" +#include "utils.h" + + +void free_presentation(struct presentation *p) +{ + /* FIXME: Loads of stuff leaks here */ + g_object_unref(p->file); + g_object_unref(p->stylesheet_from); + imagestore_destroy(p->is); + free(p); +} + + +char *get_titlebar_string(struct presentation *p) +{ + if ( p == NULL || p->file == NULL ) { + return strdup(_("(untitled)")); + } else { + char *bn = g_file_get_basename(p->file); + return bn; + } +} + + +static void find_and_load_stylesheet(struct presentation *p, GFile *file) +{ + GFile *ssfile; + + if ( file != NULL ) { + + /* First choice: /same/directory/<presentation>.ss */ + gchar *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); + p->stylesheet = stylesheet_load(ssfile); + p->stylesheet_from = ssfile; + g_free(ssuri); + } + } + + /* Second choice: /same/directory/stylesheet.ss */ + if ( p->stylesheet == NULL ) { + GFile *parent = g_file_get_parent(file); + if ( parent != NULL ) { + ssfile = g_file_get_child(parent, "stylesheet.ss"); + if ( ssfile != NULL ) { + p->stylesheet = stylesheet_load(ssfile); + p->stylesheet_from = ssfile; + } + g_object_unref(parent); + } + } + + } + + /* Third choice: <cwd>/stylesheet.ss */ + if ( p->stylesheet == NULL ) { + ssfile = g_file_new_for_path("./stylesheet.ss"); + p->stylesheet = stylesheet_load(ssfile); + p->stylesheet_from = ssfile; + } + + /* Fourth choice: internal default stylesheet */ + if ( p->stylesheet == NULL ) { + ssfile = g_file_new_for_uri("resource:///uk/me/bitwiz/Colloquium/default.ss"); + p->stylesheet = stylesheet_load(ssfile); + p->stylesheet_from = NULL; + g_object_unref(ssfile); + } + + /* Last resort is NULL stylesheet and SCInterpreter's defaults */ + /* We keep a reference to the GFile */ +} + + +struct presentation *new_presentation(const char *imagestore) +{ + struct presentation *new; + + new = calloc(1, sizeof(struct presentation)); + if ( new == NULL ) return NULL; + + new->file = NULL; + new->stylesheet_from = NULL; + + new->scblocks = NULL; + + /* Default slide size */ + new->slide_width = 1024.0; + new->slide_height = 768.0; + + new->completely_empty = 1; + new->saved = 1; + new->stylesheet = NULL; + new->is = imagestore_new(imagestore); + + new->lang = pango_language_get_default(); + + find_and_load_stylesheet(new, NULL); + + return new; +} + + +int save_presentation(struct presentation *p, GFile *file, GFile *ssfile) +{ + GFileOutputStream *fh; + int r; + int sr; + GError *error = NULL; + + fh = g_file_replace(file, NULL, FALSE, G_FILE_CREATE_NONE, NULL, &error); + if ( fh == NULL ) { + fprintf(stderr, _("Open failed: %s\n"), error->message); + return 1; + } + r = save_sc_block(G_OUTPUT_STREAM(fh), p->scblocks); + if ( r ) { + fprintf(stderr, _("Couldn't save presentation\n")); + } + g_object_unref(fh); + + if ( ssfile != NULL ) { + char *uri = g_file_get_uri(ssfile); + printf(_("Saving stylesheet to %s\n"), uri); + g_free(uri); + sr = stylesheet_save(p->stylesheet, ssfile); + if ( sr ) { + fprintf(stderr, _("Couldn't save stylesheet\n")); + } + if ( p->stylesheet_from != ssfile ) { + if ( p->stylesheet_from != NULL ) { + g_object_unref(p->stylesheet_from); + } + p->stylesheet_from = ssfile; + g_object_ref(p->stylesheet_from); + } + } else { + fprintf(stderr, _("Not saving the stylesheet\n")); + sr = 0; + } + + if ( r || sr ) return 1; + + imagestore_set_parent(p->is, g_file_get_parent(file)); + + if ( p->file != file ) { + if ( p->file != NULL ) g_object_unref(p->file); + p->file = file; + g_object_ref(p->file); + } + p->saved = 1; + update_titlebar(p->narrative_window); + return 0; +} + + +int slide_number(struct presentation *p, SCBlock *sl) +{ + SCBlock *bl = p->scblocks; + int n = 0; + + while ( bl != NULL ) { + if ( safe_strcmp(sc_block_name(bl), "slide") == 0 ) { + n++; + if ( bl == sl ) return n; + } + bl = sc_block_next(bl); + } + + return 0; +} + + +int num_slides(struct presentation *p) +{ + SCBlock *bl = p->scblocks; + int n = 0; + + while ( bl != NULL ) { + if ( safe_strcmp(sc_block_name(bl), "slide") == 0 ) { + n++; + } + bl = sc_block_next(bl); + } + + return n; +} + + +SCBlock *first_slide(struct presentation *p) +{ + SCBlock *bl = p->scblocks; + + while ( bl != NULL ) { + if ( safe_strcmp(sc_block_name(bl), "slide") == 0 ) { + return bl; + } + bl = sc_block_next(bl); + } + + fprintf(stderr, "Couldn't find first slide!\n"); + return NULL; +} + + +SCBlock *last_slide(struct presentation *p) +{ + SCBlock *bl = p->scblocks; + SCBlock *l = NULL; + + while ( bl != NULL ) { + if ( safe_strcmp(sc_block_name(bl), "slide") == 0 ) { + l = bl; + } + bl = sc_block_next(bl); + } + + if ( l == NULL ) { + fprintf(stderr, "Couldn't find last slide!\n"); + } + return l; +} + + +SCBlock *next_slide(struct presentation *p, SCBlock *sl) +{ + SCBlock *bl = sl; + int found = 0; + + while ( bl != NULL ) { + if ( safe_strcmp(sc_block_name(bl), "slide") == 0 ) { + if ( found ) return bl; + } + if ( bl == sl ) { + found = 1; + } + bl = sc_block_next(bl); + } + + fprintf(stderr, "Couldn't find next slide!\n"); + return NULL; +} + + +SCBlock *prev_slide(struct presentation *p, SCBlock *sl) +{ + SCBlock *bl = p->scblocks; + SCBlock *l = NULL; + + while ( bl != NULL ) { + if ( bl == sl ) { + if ( l == NULL ) return sl; /* Already on first slide */ + return l; + } + if ( safe_strcmp(sc_block_name(bl), "slide") == 0 ) { + l = bl; + } + bl = sc_block_next(bl); + } + + fprintf(stderr, "Couldn't find prev slide!\n"); + return NULL; +} + + +static void set_slide_size_from_stylesheet(struct presentation *p) +{ + char *result; + + result = stylesheet_lookup(p->stylesheet, "$.slide", "size"); + if ( result != NULL ) { + float v[2]; + if ( parse_double(result, v) == 0 ) { + p->slide_width = v[0]; + p->slide_height = v[1]; + } + } +} + + +int load_presentation(struct presentation *p, GFile *file) +{ + int r = 0; + char *everything; + + assert(p->completely_empty); + + if ( !g_file_load_contents(file, NULL, &everything, NULL, NULL, NULL) ) { + fprintf(stderr, _("Failed to load '%s'\n"), g_file_get_uri(file)); + return 1; + } + + p->scblocks = sc_parse(everything); + g_free(everything); + + p->lang = pango_language_get_default(); + + if ( p->scblocks == NULL ) r = 1; + + if ( r ) { + p->completely_empty = 1; + fprintf(stderr, _("Parse error.\n")); + return r; /* Error */ + } + + p->stylesheet = NULL; + + find_and_load_stylesheet(p, file); + + set_slide_size_from_stylesheet(p); + + assert(p->file == NULL); + p->file = file; + g_object_ref(file); + + imagestore_set_parent(p->is, g_file_get_parent(file)); + + return 0; +} + diff --git a/src-old/presentation.h b/src-old/presentation.h new file mode 100644 index 0000000..b288d8e --- /dev/null +++ b/src-old/presentation.h @@ -0,0 +1,83 @@ +/* + * presentation.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 PRESENTATION_H +#define PRESENTATION_H + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +struct presentation; + +#include "imagestore.h" +#include "sc_parse.h" +#include "slideshow.h" +#include "narrative_window.h" +#include "slide_window.h" +#include "stylesheet.h" + +struct menu_pl; + +struct presentation +{ + GFile *file; + GFile *stylesheet_from; + int completely_empty; + int saved; + PangoLanguage *lang; + + ImageStore *is; + + NarrativeWindow *narrative_window; + + struct pr_clock *clock; + + /* This is the "native" size of the slide. It only exists to give + * font size some meaning in the context of a somewhat arbitrary DPI */ + double slide_width; + double slide_height; + + SCBlock *scblocks; + Stylesheet *stylesheet; +}; + + +extern struct presentation *new_presentation(const char *imagestore); +extern void free_presentation(struct presentation *p); + +extern char *get_titlebar_string(struct presentation *p); + +extern int slide_number(struct presentation *p, SCBlock *sl); +extern int num_slides(struct presentation *p); +extern SCBlock *first_slide(struct presentation *p); +extern SCBlock *last_slide(struct presentation *p); +extern SCBlock *next_slide(struct presentation *p, SCBlock *sl); +extern SCBlock *prev_slide(struct presentation *p, SCBlock *sl); + +extern int load_presentation(struct presentation *p, GFile *file); +extern int save_presentation(struct presentation *p, GFile *file, GFile *ssfile); + +#define UNUSED __attribute__((unused)) + + +#endif /* PRESENTATION_H */ diff --git a/src-old/print.c b/src-old/print.c new file mode 100644 index 0000000..43c967e --- /dev/null +++ b/src-old/print.c @@ -0,0 +1,314 @@ +/* + * print.c + * + * Copyright © 2016-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 <gtk/gtk.h> +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +#include "presentation.h" +#include "narrative_window.h" +#include "render.h" +#include "utils.h" + + +static GtkPrintSettings *print_settings = NULL; + +struct print_stuff +{ + struct presentation *p; + + /* Printing config */ + GtkWidget *combo; + int slides_only; + + /* When printing slides only */ + SCBlock *slide; + + /* When printing narrative */ + int nar_line; + struct frame *top; + int start_paras[256]; + int slide_number; +}; + + +static void print_widget_apply(GtkPrintOperation *op, GtkWidget *widget, + void *vp) +{ + const char *id; + struct print_stuff *ps = vp; + + id = gtk_combo_box_get_active_id(GTK_COMBO_BOX(ps->combo)); + if ( strcmp(id, "slides") == 0 ) { + ps->slides_only = 1; + } else { + ps->slides_only = 0; + } +} + + +static GObject *print_widget(GtkPrintOperation *op, void *vp) +{ + GtkWidget *vbox; + GtkWidget *cbox; + struct print_stuff *ps = vp; + + vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10); + gtk_container_set_border_width(GTK_CONTAINER(vbox), 10); + + /* What do you want to print? */ + cbox = gtk_combo_box_text_new(); + gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(cbox), "slides", + _("Print the slides only")); + gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(cbox), "narrative", + _("Print the narrative")); + gtk_box_pack_start(GTK_BOX(vbox), cbox, FALSE, FALSE, 10); + gtk_combo_box_set_active(GTK_COMBO_BOX(cbox), 1); + ps->combo = cbox; + + gtk_widget_show_all(vbox); + return G_OBJECT(vbox); + +} + + +static void print_slide_only(GtkPrintOperation *op, GtkPrintContext *ctx, + struct print_stuff *ps, int page) +{ + cairo_t *cr; + PangoContext *pc; + double w, h; + struct frame *top; + const double sw = ps->p->slide_width; + const double sh = ps->p->slide_height; + double slide_width, slide_height; + + cr = gtk_print_context_get_cairo_context(ctx); + pc = gtk_print_context_create_pango_context(ctx); + w = gtk_print_context_get_width(ctx); + h = gtk_print_context_get_height(ctx); + + cairo_rectangle(cr, 0.0, 0.0, w, h); + cairo_set_source_rgb(cr, 1.0, 1.0, 1.0); + cairo_fill(cr); + + if ( sw/sh > w/h ) { + /* Slide is too wide. Letterboxing top/bottom */ + slide_width = w; + slide_height = w * sh/sw; + } else { + /* Letterboxing at sides */ + slide_width = h * sw/sh; + slide_height = h; + } + + printf("%f x %f ---> %f x %f\n", w, h, slide_width, slide_height); + + top = interp_and_shape(ps->slide, ps->p->stylesheet, NULL, + ps->p->is, page+1, pc, sw, sh, ps->p->lang); + + recursive_wrap(top, pc); + + cairo_scale(cr, slide_width/sw, slide_width/sw); + + recursive_draw(top, cr, ps->p->is, + 0.0, ps->p->slide_height); + + ps->slide = next_slide(ps->p, ps->slide); +} + + +static int print_create_thumbnail(SCInterpreter *scin, SCBlock *bl, + double *w, double *h, void **bvp, void *vp) +{ + struct print_stuff *ps = vp; + struct presentation *p = ps->p; + SCBlock *b; + + *w = 270.0*(p->slide_width / p->slide_height); + *h = 270.0; + b = bl; + + *bvp = b; + + return 1; +} + + +static cairo_surface_t *print_render_thumbnail(int w, int h, void *bvp, void *vp) +{ + struct print_stuff *ps = vp; + struct presentation *p = ps->p; + SCBlock *scblocks = bvp; + cairo_surface_t *surf; + struct frame *top; + + surf = render_sc(scblocks, w, h, p->slide_width, p->slide_height, + p->stylesheet, NULL, p->is, ps->slide_number++, + &top, p->lang); + frame_free(top); + + return surf; +} + + +static void begin_narrative_print(GtkPrintOperation *op, GtkPrintContext *ctx, + struct print_stuff *ps) +{ + SCCallbackList *cbl; + PangoContext *pc; + int i, n_pages; + double h, page_height; + SCBlock *dummy_top; + + cbl = sc_callback_list_new(); + ps->slide_number = 1; + sc_callback_list_add_callback(cbl, "slide", print_create_thumbnail, + print_render_thumbnail, NULL, ps); + + pc = gtk_print_context_create_pango_context(ctx); + + dummy_top = sc_block_new_parent(ps->p->scblocks, "presentation"); + ps->top = interp_and_shape(dummy_top, ps->p->stylesheet, cbl, + ps->p->is, 0, pc, + gtk_print_context_get_width(ctx), + gtk_print_context_get_height(ctx), + ps->p->lang); + recursive_wrap(ps->top, pc); + + /* Count pages */ + page_height = gtk_print_context_get_height(ctx); + h = 0.0; + n_pages = 1; + ps->start_paras[0] = 0; + for ( i=0; i<ps->top->n_paras; i++ ) { + if ( h + paragraph_height(ps->top->paras[i]) > page_height ) { + /* Paragraph does not fit on page */ + ps->start_paras[n_pages] = i; + n_pages++; + h = 0.0; + } + h += paragraph_height(ps->top->paras[i]); + } + gtk_print_operation_set_n_pages(op, n_pages); + g_object_unref(pc); +} + + +static void print_narrative(GtkPrintOperation *op, GtkPrintContext *ctx, + struct print_stuff *ps, gint page) +{ + int i; + double h, page_height; + cairo_t *cr; + + page_height = gtk_print_context_get_height(ctx); + cr = gtk_print_context_get_cairo_context(ctx); + + h = 0.0; + for ( i=ps->start_paras[page]; i<ps->top->n_paras; i++ ) { + + /* Will this paragraph fit? */ + h += paragraph_height(ps->top->paras[i]); + if ( h > page_height ) return; + + cairo_save(cr); + render_paragraph(cr, ps->top->paras[i], ps->p->is); + cairo_restore(cr); + + cairo_translate(cr, 0.0, paragraph_height(ps->top->paras[i])); + + } + + +} + + + +static void print_begin(GtkPrintOperation *op, GtkPrintContext *ctx, void *vp) +{ + struct print_stuff *ps = vp; + + if ( ps->slides_only ) { + gtk_print_operation_set_n_pages(op, num_slides(ps->p)); + ps->slide = first_slide(ps->p); + } else { + begin_narrative_print(op, ctx, ps); + } +} + + +static void print_draw(GtkPrintOperation *op, GtkPrintContext *ctx, gint page, + void *vp) +{ + struct print_stuff *ps = vp; + if ( ps->slides_only ) { + print_slide_only(op, ctx, ps, page); + } else { + print_narrative(op, ctx, ps, page); + } +} + + +void run_printing(struct presentation *p, GtkWidget *parent) +{ + GtkPrintOperation *print; + GtkPrintOperationResult res; + struct print_stuff *ps; + + ps = malloc(sizeof(struct print_stuff)); + if ( ps == NULL ) return; + ps->p = p; + ps->nar_line = 0; + + print = gtk_print_operation_new(); + if ( print_settings != NULL ) { + gtk_print_operation_set_print_settings(print, print_settings); + } + + g_signal_connect(print, "create-custom-widget", + G_CALLBACK(print_widget), ps); + g_signal_connect(print, "custom-widget-apply", + G_CALLBACK(print_widget_apply), ps); + g_signal_connect(print, "begin_print", G_CALLBACK(print_begin), ps); + g_signal_connect(print, "draw_page", G_CALLBACK(print_draw), ps); + + res = gtk_print_operation_run(print, + GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG, + GTK_WINDOW(parent), NULL); + + if ( res == GTK_PRINT_OPERATION_RESULT_APPLY ) { + if ( print_settings != NULL ) { + g_object_unref(print_settings); + } + print_settings = g_object_ref( + gtk_print_operation_get_print_settings(print)); + } + g_object_unref(print); +} + diff --git a/src-old/print.h b/src-old/print.h new file mode 100644 index 0000000..265b7c1 --- /dev/null +++ b/src-old/print.h @@ -0,0 +1,32 @@ +/* + * print.h + * + * Copyright © 2016-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 PRINT_H +#define PRINT_H + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +extern void run_printing(struct presentation *p, GtkWidget *parent); + +#endif /* PRINT_H */ diff --git a/src-old/render.c b/src-old/render.c new file mode 100644 index 0000000..6ac09fc --- /dev/null +++ b/src-old/render.c @@ -0,0 +1,332 @@ +/* + * render.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 <cairo.h> +#include <cairo-pdf.h> +#include <pango/pangocairo.h> +#include <assert.h> +#include <string.h> +#include <stdlib.h> +#include <math.h> +#include <gdk-pixbuf/gdk-pixbuf.h> +#include <gdk/gdk.h> + +#include "sc_parse.h" +#include "sc_interp.h" +#include "presentation.h" +#include "frame.h" +#include "render.h" +#include "imagestore.h" +#include "utils.h" + + +static void do_background(cairo_t *cr, struct frame *fr) +{ + cairo_pattern_t *patt = NULL; + + cairo_new_path(cr); + cairo_rectangle(cr, 0.0, 0.0, fr->w, fr->h); + + switch ( fr->grad ) { + + case GRAD_NONE: + cairo_set_source_rgba(cr, fr->bgcol[0], + fr->bgcol[1], + fr->bgcol[2], + fr->bgcol[3]); + break; + + case GRAD_VERT: + patt = cairo_pattern_create_linear(0.0, 0.0, + 0.0, fr->h); + cairo_pattern_add_color_stop_rgba(patt, 0.0, fr->bgcol[0], + fr->bgcol[1], + fr->bgcol[2], + fr->bgcol[3]); + cairo_pattern_add_color_stop_rgba(patt, 1.0, fr->bgcol2[0], + fr->bgcol2[1], + fr->bgcol2[2], + fr->bgcol2[3]); + cairo_set_source(cr, patt); + break; + + case GRAD_HORIZ: + patt = cairo_pattern_create_linear(0.0, 0.0, + fr->w, 0.0); + cairo_pattern_add_color_stop_rgba(patt, 0.0, fr->bgcol[0], + fr->bgcol[1], + fr->bgcol[2], + fr->bgcol[3]); + cairo_pattern_add_color_stop_rgba(patt, 1.0, fr->bgcol2[0], + fr->bgcol2[1], + fr->bgcol2[2], + fr->bgcol2[3]); + cairo_set_source(cr, patt); + break; + + } + + cairo_fill(cr); + if ( patt != NULL ) cairo_pattern_destroy(patt); +} + + +static int draw_frame(cairo_t *cr, struct frame *fr, ImageStore *is, + double min_y, double max_y) +{ + int i; + double hpos = 0.0; + + cairo_save(cr); + do_background(cr, fr); + + /* Actually render the contents */ + cairo_translate(cr, fr->pad_l, fr->pad_t); + for ( i=0; i<fr->n_paras; i++ ) { + + double cur_h = paragraph_height(fr->paras[i]); + + cairo_save(cr); + cairo_translate(cr, 0.0, hpos); + + if ( (hpos + cur_h > min_y) && (hpos < max_y) ) { + render_paragraph(cr, fr->paras[i], is); + } /* else paragraph is not visible */ + + hpos += cur_h; + cairo_restore(cr); + + } + cairo_restore(cr); + + return 0; +} + + +int recursive_draw(struct frame *fr, cairo_t *cr, + ImageStore *is, + double min_y, double max_y) +{ + int i; + + draw_frame(cr, fr, is, min_y, max_y); + + for ( i=0; i<fr->num_children; i++ ) { + cairo_save(cr); + cairo_translate(cr, fr->children[i]->x, fr->children[i]->y); + recursive_draw(fr->children[i], cr, is, + min_y - fr->children[i]->y, + max_y - fr->children[i]->y); + cairo_restore(cr); + } + + return 0; +} + + +void wrap_frame(struct frame *fr, PangoContext *pc) +{ + int i; + double w; + + w = fr->w - fr->pad_l - fr->pad_r; + + for ( i=0; i<fr->n_paras; i++ ) { + wrap_paragraph(fr->paras[i], pc, w, 0, 0); + } +} + + +int recursive_wrap(struct frame *fr, PangoContext *pc) +{ + int i; + + wrap_frame(fr, pc); + + for ( i=0; i<fr->num_children; i++ ) { + recursive_wrap(fr->children[i], pc); + } + + return 0; +} + + +struct frame *interp_and_shape(SCBlock *scblocks, Stylesheet *stylesheet, + SCCallbackList *cbl, ImageStore *is, + int slide_number, + PangoContext *pc, double w, double h, + PangoLanguage *lang) +{ + SCInterpreter *scin; + char snum[64]; + struct frame *top; + + top = frame_new(); + top->resizable = 0; + top->x = 0.0; + top->y = 0.0; + top->w = w; + top->h = h; + top->scblocks = scblocks; + + scin = sc_interp_new(pc, lang, is, top); + if ( scin == NULL ) { + fprintf(stderr, "Failed to set up interpreter.\n"); + frame_free(top); + return NULL; + } + + sc_interp_set_callbacks(scin, cbl); + + snprintf(snum, 63, "%i", slide_number); + sc_interp_set_constant(scin, SCCONST_SLIDENUMBER, snum); + + top->fontdesc = pango_font_description_copy(sc_interp_get_fontdesc(scin)); + top->col[0] = sc_interp_get_fgcol(scin)[0]; + top->col[1] = sc_interp_get_fgcol(scin)[1]; + top->col[2] = sc_interp_get_fgcol(scin)[2]; + top->col[3] = sc_interp_get_fgcol(scin)[3]; + + sc_interp_add_block(scin, scblocks, stylesheet); + + sc_interp_destroy(scin); + + return top; +} + + +static struct frame *render_sc_with_context(SCBlock *scblocks, + cairo_t *cr, double log_w, double log_h, + Stylesheet *stylesheet, SCCallbackList *cbl, + ImageStore *is, + int slide_number, PangoLanguage *lang, + PangoContext *pc) +{ + struct frame *top; + + cairo_rectangle(cr, 0.0, 0.0, log_w, log_h); + cairo_set_source_rgb(cr, 1.0, 1.0, 1.0); + cairo_fill(cr); + + top = interp_and_shape(scblocks, stylesheet, cbl, is, + slide_number, pc, log_w, log_h, lang); + + recursive_wrap(top, pc); + + recursive_draw(top, cr, is, 0.0, log_h); + + return top; +} + + +cairo_surface_t *render_sc(SCBlock *scblocks, int w, int h, + double log_w, double log_h, + Stylesheet *stylesheet, SCCallbackList *cbl, + ImageStore *is, + int slide_number, struct frame **ptop, + PangoLanguage *lang) +{ + cairo_surface_t *surf; + cairo_t *cr; + struct frame *top; + PangoContext *pc; + + surf = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w, h); + cr = cairo_create(surf); + pc = pango_cairo_create_context(cr); + cairo_scale(cr, w/log_w, h/log_h); + top = render_sc_with_context(scblocks, cr, log_w, log_h, + stylesheet, cbl, is, slide_number, + lang, pc); + g_object_unref(pc); + cairo_destroy(cr); + + *ptop = top; + + return surf; +} + + +int export_pdf(struct presentation *p, const char *filename) +{ + double r; + double w = 2048.0; + double scale; + cairo_surface_t *surf; + cairo_t *cr; + SCBlock *bl; + int i; + PangoContext *pc; + + r = p->slide_height / p->slide_width; + + surf = cairo_pdf_surface_create(filename, w, w*r); + if ( cairo_surface_status(surf) != CAIRO_STATUS_SUCCESS ) { + fprintf(stderr, _("Couldn't create Cairo surface\n")); + return 1; + } + + cr = cairo_create(surf); + scale = w / p->slide_width; + pc = pango_cairo_create_context(cr); + + i = 1; + bl = p->scblocks; + while ( bl != NULL ) { + + if ( safe_strcmp(sc_block_name(bl), "slide") != 0 ) { + bl = sc_block_next(bl); + continue; + } + + cairo_save(cr); + + cairo_scale(cr, scale, scale); + + cairo_rectangle(cr, 0.0, 0.0, p->slide_width, p->slide_height); + cairo_set_source_rgb(cr, 1.0, 1.0, 1.0); + cairo_fill(cr); + + render_sc_with_context(bl, cr, p->slide_width, + p->slide_height, p->stylesheet, NULL, + p->is, i, p->lang, pc); + + cairo_restore(cr); + + cairo_show_page(cr); + + bl = sc_block_next(bl); + i++; + + } + + g_object_unref(pc); + cairo_surface_finish(surf); + cairo_destroy(cr); + + return 0; +} diff --git a/src-old/render.h b/src-old/render.h new file mode 100644 index 0000000..0cfae26 --- /dev/null +++ b/src-old/render.h @@ -0,0 +1,62 @@ +/* + * render.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 RENDER_H +#define RENDER_H + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "presentation.h" +#include "imagestore.h" +#include "sc_interp.h" +#include "frame.h" + +/* Convienience function to run the entire pipeline */ +extern cairo_surface_t *render_sc(SCBlock *scblocks, int w, int h, + double log_w, double log_h, + Stylesheet *stylesheet, SCCallbackList *cbl, + ImageStore *is, + int slide_number, struct frame **ptop, + PangoLanguage *lang); + +/* Interpret StoryCode and measure boxes. + * Needs to be followed by: wrap_contents() (recursively) + * recursive_draw() + */ +extern struct frame *interp_and_shape(SCBlock *scblocks, Stylesheet *stylesheet, + SCCallbackList *cbl, + ImageStore *is, + int slide_number, PangoContext *pc, + double w, double h, PangoLanguage *lang); + +extern void wrap_frame(struct frame *fr, PangoContext *pc); +extern int recursive_wrap(struct frame *fr, PangoContext *pc); + +extern int export_pdf(struct presentation *p, const char *filename); + +extern int recursive_draw(struct frame *fr, cairo_t *cr, + ImageStore *is, + double min_y, double max_y); + +#endif /* RENDER_H */ diff --git a/src-old/sc_editor.c b/src-old/sc_editor.c new file mode 100644 index 0000000..8e79fd8 --- /dev/null +++ b/src-old/sc_editor.c @@ -0,0 +1,2197 @@ +/* + * sc_editor.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 <gtk/gtk.h> +#include <assert.h> +#include <gdk/gdkkeysyms.h> +#include <gdk-pixbuf/gdk-pixbuf.h> +#include <math.h> + +#include "colloquium.h" +#include "presentation.h" +#include "slide_window.h" +#include "render.h" +#include "frame.h" +#include "sc_parse.h" +#include "sc_interp.h" +#include "sc_editor.h" +#include "slideshow.h" +#include "debugger.h" +#include "utils.h" + + +static void scroll_interface_init(GtkScrollable *iface) +{ +} + + +enum +{ + SCEDITOR_0, + SCEDITOR_VADJ, + SCEDITOR_HADJ, + SCEDITOR_VPOL, + SCEDITOR_HPOL, +}; + + +G_DEFINE_TYPE_WITH_CODE(SCEditor, sc_editor, GTK_TYPE_DRAWING_AREA, + G_IMPLEMENT_INTERFACE(GTK_TYPE_SCROLLABLE, + scroll_interface_init)) + +static void debug_paragraphs(SCEditor *e) +{ + struct frame *fr = e->cursor_frame; + int i; + + printf("Paragraphs in current frame:\n"); + for ( i=0; i<fr->n_paras; i++ ) { + show_para(fr->paras[i]); + } +} + + +static void horizontal_adjust(GtkAdjustment *adj, SCEditor *e) +{ + e->h_scroll_pos = gtk_adjustment_get_value(adj); + sc_editor_redraw(e); +} + + +static void set_horizontal_params(SCEditor *e) +{ + if ( e->hadj == NULL ) return; + gtk_adjustment_configure(e->hadj, e->h_scroll_pos, 0, e->w, 100, + e->visible_width, e->visible_width); +} + + +static void vertical_adjust(GtkAdjustment *adj, SCEditor *e) +{ + e->scroll_pos = gtk_adjustment_get_value(adj); + sc_editor_redraw(e); +} + + +static void set_vertical_params(SCEditor *e) +{ + double page; + + if ( e->vadj == NULL ) return; + + /* Ensure we do not scroll off the top of the document */ + if ( e->scroll_pos < 0.0 ) e->scroll_pos = 0.0; + + /* Ensure we do not scroll off the bottom of the document */ + if ( e->scroll_pos > e->h - e->visible_height ) { + e->scroll_pos = e->h - e->visible_height; + } + + /* If we can show the whole document, show it at the top */ + if ( e->h < e->visible_height ) { + e->scroll_pos = 0.0; + } + + if ( e->h > e->visible_height ) { + page = e->visible_height; + } else { + page = e->h; + } + + gtk_adjustment_configure(e->vadj, e->scroll_pos, 0, e->h, 100, + e->visible_height, page); +} + + +static void update_size(SCEditor *e) +{ + if ( e->flow ) { + + double total = total_height(e->top); + + e->w = e->top->w; + e->h = total + e->top->pad_t + e->top->pad_b; + + e->log_w = e->w; + e->log_h = e->h; + e->top->h = e->h; + } else { + e->top->w = e->log_w; + e->top->h = e->log_h; + } + + if ( e->flow && (e->top->h < e->visible_height) ) { + e->top->h = e->visible_height; + } + + set_vertical_params(e); + set_horizontal_params(e); +} + + +static gboolean resize_sig(GtkWidget *widget, GdkEventConfigure *event, + SCEditor *e) +{ + PangoContext *pc; + + pc = gdk_pango_context_get(); + + if ( e->scale ) { + + double sx, sy; + double aw, ah; + + e->w = event->width; + e->h = event->height; + sx = (double)e->w / e->log_w; + sy = (double)e->h / e->log_h; + e->view_scale = (sx < sy) ? sx : sy; + + /* Actual size (in device units) */ + aw = e->view_scale * e->log_w; + ah = e->view_scale * e->log_h; + + e->border_offs_x = (event->width - aw)/2.0; + e->border_offs_y = (event->height - ah)/2.0; + + } + + e->visible_height = event->height; + e->visible_width = event->width; + + /* Interpret and shape, if not already done */ + if ( e->top == NULL ) { + double w, h; + if ( e->flow ) { + w = event->width; + h = 0.0; + } else { + w = e->log_w; + h = e->log_h; + } + e->top = interp_and_shape(e->scblocks, e->stylesheet, e->cbl, + e->is, e->slidenum, pc, + w, h, e->lang); + e->top->scblocks = e->scblocks; + recursive_wrap(e->top, pc); + } + + if ( e->flow ) { + /* Wrap using current width */ + e->top->w = event->width; + e->top->h = 0.0; /* To be updated in a moment */ + e->top->x = 0.0; + e->top->y = 0.0; + /* Only the top level needs to be wrapped */ + wrap_frame(e->top, pc); + } + + update_size(e); + + g_object_unref(pc); + + return FALSE; +} + + +static void emit_change_sig(SCEditor *e) +{ + g_signal_emit_by_name(e, "changed"); +} + + +void sc_editor_set_flow(SCEditor *e, int flow) +{ + e->flow = flow; +} + + +static void sc_editor_set_property(GObject *obj, guint id, const GValue *val, + GParamSpec *spec) +{ + SCEditor *e = SC_EDITOR(obj); + + switch ( id ) { + + case SCEDITOR_VPOL : + e->vpol = g_value_get_enum(val); + break; + + case SCEDITOR_HPOL : + e->hpol = g_value_get_enum(val); + break; + + case SCEDITOR_VADJ : + e->vadj = g_value_get_object(val); + set_vertical_params(e); + if ( e->vadj != NULL ) { + g_signal_connect(G_OBJECT(e->vadj), "value-changed", + G_CALLBACK(vertical_adjust), e); + } + break; + + case SCEDITOR_HADJ : + e->hadj = g_value_get_object(val); + set_horizontal_params(e); + if ( e->hadj != NULL ) { + g_signal_connect(G_OBJECT(e->hadj), "value-changed", + G_CALLBACK(horizontal_adjust), e); + } + break; + + default : + printf("setting %i\n", id); + break; + + } +} + + +static void sc_editor_get_property(GObject *obj, guint id, GValue *val, + GParamSpec *spec) +{ + SCEditor *e = SC_EDITOR(obj); + + switch ( id ) { + + case SCEDITOR_VADJ : + g_value_set_object(val, e->vadj); + break; + + case SCEDITOR_HADJ : + g_value_set_object(val, e->hadj); + break; + + case SCEDITOR_VPOL : + g_value_set_enum(val, e->vpol); + break; + + case SCEDITOR_HPOL : + g_value_set_enum(val, e->hpol); + break; + + default : + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, id, spec); + break; + + } +} + + +static GtkSizeRequestMode get_request_mode(GtkWidget *widget) +{ + return GTK_SIZE_REQUEST_CONSTANT_SIZE; +} + + +static void get_preferred_width(GtkWidget *widget, gint *min, gint *natural) +{ + SCEditor *e = SC_EDITOR(widget); + if ( e->flow ) { + *min = 100; + *natural = 640; + } else { + *min = e->w; + *natural = e->w; + } +} + + +static void get_preferred_height(GtkWidget *widget, gint *min, gint *natural) +{ + SCEditor *e = SC_EDITOR(widget); + if ( e->flow ) { + *min = 1000; + *natural = 1000; + } else { + *min = e->h; + *natural = e->h; + } +} + + +static void sc_editor_class_init(SCEditorClass *klass) +{ + GObjectClass *goc = G_OBJECT_CLASS(klass); + goc->set_property = sc_editor_set_property; + goc->get_property = sc_editor_get_property; + g_object_class_override_property(goc, SCEDITOR_VADJ, "vadjustment"); + g_object_class_override_property(goc, SCEDITOR_HADJ, "hadjustment"); + g_object_class_override_property(goc, SCEDITOR_VPOL, "vscroll-policy"); + g_object_class_override_property(goc, SCEDITOR_HPOL, "hscroll-policy"); + + GTK_WIDGET_CLASS(klass)->get_request_mode = get_request_mode; + GTK_WIDGET_CLASS(klass)->get_preferred_width = get_preferred_width; + GTK_WIDGET_CLASS(klass)->get_preferred_height = get_preferred_height; + GTK_WIDGET_CLASS(klass)->get_preferred_height_for_width = NULL; + + g_signal_new("changed", SC_TYPE_EDITOR, G_SIGNAL_RUN_LAST, 0, + NULL, NULL, NULL, G_TYPE_NONE, 0); +} + + +static void sc_editor_init(SCEditor *e) +{ + e->vpol = GTK_SCROLL_NATURAL; + e->hpol = GTK_SCROLL_NATURAL; + e->vadj = gtk_adjustment_new(0, 0, 100, 1, 10, 10); + e->hadj = gtk_adjustment_new(0, 0, 100, 1, 10, 10); +} + + +void sc_editor_set_background(SCEditor *e, double r, double g, double b) +{ + e->bgcol[0] = r; + e->bgcol[1] = g; + e->bgcol[2] = b; +} + + +void sc_editor_ensure_cursor(SCEditor *e) +{ + if ( e->cursor_frame != NULL ) return; + e->cursor_frame = e->top; + e->cpos.para = 0; + e->cpos.pos = 0; + e->cpos.trail = 0; + e->selection = NULL; +} + + +static void sc_editor_remove_cursor(SCEditor *e) +{ + e->cursor_frame = NULL; + e->cpos.para = 0; + e->cpos.pos = 0; + e->cpos.trail = 0; + e->selection = NULL; +} + + +/* (Re-)run the entire rendering pipeline. + * NB "full" means "full". All frame, line and box handles will become + * invalid. The cursor position will be unset. */ +static void full_rerender(SCEditor *e) +{ + PangoContext *pc; + + frame_free(e->top); + sc_editor_remove_cursor(e); + + pc = gdk_pango_context_get(); + + e->top = interp_and_shape(e->scblocks, e->stylesheet, e->cbl, + e->is, e->slidenum, + pc, e->log_w, 0.0, e->lang); + + e->top->x = 0.0; + e->top->y = 0.0; + e->top->w = e->w; + e->top->h = 0.0; /* To be updated in a moment */ + + recursive_wrap(e->top, pc); + update_size(e); + + sc_editor_redraw(e); + + g_object_unref(pc); +} + + +void sc_editor_redraw(SCEditor *e) +{ + gint w, h; + + w = gtk_widget_get_allocated_width(GTK_WIDGET(e)); + h = gtk_widget_get_allocated_height(GTK_WIDGET(e)); + + gtk_widget_queue_draw_area(GTK_WIDGET(e), 0, 0, w, h); +} + + +static void paste_storycode_received(GtkClipboard *cb, GtkSelectionData *seldata, + gpointer vp) +{ + SCEditor *e = vp; + SCBlock *nf; + const guchar *t; + + t = gtk_selection_data_get_data(seldata); + + printf("received storycode paste\n"); + printf("'%s'\n", t); + if ( t == NULL ) return; + + /* FIXME: It might not be a new frame */ + nf = sc_parse((char *)t); + show_sc_blocks(nf); + sc_block_append_block(sc_block_child(e->scblocks), nf); + full_rerender(e); +} + + +static void paste_text_received(GtkClipboard *cb, GtkSelectionData *seldata, + gpointer vp) +{ + SCEditor *e = vp; + SCBlock *bl; + guchar *t; + SCBlock *cur_bl; + size_t cur_sc_pos; + size_t offs; + Paragraph *para; + + t = gtk_selection_data_get_text(seldata); + + printf("received text paste\n"); + printf("'%s'\n", t); + if ( t == NULL ) return; + + bl = sc_parse((char *)t); + + if ( e->cursor_frame == NULL ) { + fprintf(stderr, _("No frame selected for paste\n")); + return; + } + + para = e->cursor_frame->paras[e->cpos.para]; + offs = pos_trail_to_offset(para, e->cpos.pos, e->cpos.trail); + + get_sc_pos(e->cursor_frame, e->cpos.para, offs, &cur_bl, &cur_sc_pos); + sc_insert_block(cur_bl, cur_sc_pos, bl); + full_rerender(e); +} + + +static void paste_targets_received(GtkClipboard *cb, GdkAtom *targets, + gint n_targets, gpointer vp) +{ + SCEditor *e = vp; + int i; + int have_sc = 0; + int index_sc, index_text; + int have_text = 0; + + if ( targets == NULL ) { + fprintf(stderr, "No paste targets offered.\n"); + return; + } + + for ( i=0; i<n_targets; i++ ) { + gchar *name = gdk_atom_name(targets[i]); + if ( g_strcmp0(name, "text/x-storycode") == 0 ) { + have_sc = 1; + index_sc = i; + } + if ( g_strcmp0(name, "text/plain") == 0 ) { + have_text = 1; + index_text = i; + } + g_free(name); + } + + if ( have_sc ) { + printf("storycode is offered\n"); + gtk_clipboard_request_contents(cb, targets[index_sc], + paste_storycode_received, e); + } else if ( have_text ) { + printf("text is offered\n"); + gtk_clipboard_request_contents(cb, targets[index_text], + paste_text_received, e); + } else { + printf("nothing useful is offered\n"); + } +} + + +void sc_editor_paste(SCEditor *e) +{ + GtkClipboard *cb; + GdkAtom atom; + + printf("pasting\n"); + + atom = gdk_atom_intern("CLIPBOARD", FALSE); + if ( atom == GDK_NONE ) return; + cb = gtk_clipboard_get(atom); + gtk_clipboard_request_targets(cb, paste_targets_received, e); +} + + +void sc_editor_add_storycode(SCEditor *e, const char *sc) +{ + SCBlock *nf; + nf = sc_parse(sc); + sc_block_append_block(sc_block_child(e->scblocks), nf); + full_rerender(e); +} + + +static void clipboard_get(GtkClipboard *cb, GtkSelectionData *seldata, + guint info, gpointer data) +{ + char *t = data; + + printf("clipboard get\n"); + + if ( info == 0 ) { + printf("sending SC frame\n"); + gtk_selection_data_set(seldata, + gtk_selection_data_get_target(seldata), + 8, (const guchar *)t, strlen(t)+1); + } else { + GdkAtom target; + gchar *name; + target = gtk_selection_data_get_target(seldata); + name = gdk_atom_name(target); + fprintf(stderr, "Don't know what to send for %s\n", name); + g_free(name); + } +} + + +static void clipboard_clear(GtkClipboard *cb, gpointer data) +{ + free(data); +} + + +void sc_editor_copy_selected_frame(SCEditor *e) +{ + char *t; + GtkClipboard *cb; + GdkAtom atom; + GtkTargetEntry targets[1]; + + if ( e->selection == NULL ) return; + + atom = gdk_atom_intern("CLIPBOARD", FALSE); + if ( atom == GDK_NONE ) return; + + cb = gtk_clipboard_get(atom); + + targets[0].target = "text/x-storycode"; + targets[0].flags = 0; + targets[0].info = 0; + + /* FIXME: Offer image, PDF etc? */ + + printf("copying frame\n"); + + t = serialise_sc_block(e->selection->scblocks); + + gtk_clipboard_set_with_data(cb, targets, 1, + clipboard_get, clipboard_clear, t); +} + + +static void copy_selection(SCEditor *e) +{ + char *t; + GtkClipboard *cb; + GdkAtom atom; + GtkTargetEntry targets[1]; + SCBlock *bl; + + if ( e->selection == NULL ) return; + + atom = gdk_atom_intern("CLIPBOARD", FALSE); + if ( atom == GDK_NONE ) return; + + cb = gtk_clipboard_get(atom); + + + targets[0].target = "text/x-storycode"; + targets[0].flags = 0; + targets[0].info = 0; + + printf("copying selection\n"); + + bl = block_at_cursor(e->cursor_frame, e->cpos.para, 0); + if ( bl == NULL ) return; + + t = serialise_sc_block(bl); + + gtk_clipboard_set_with_data(cb, targets, 1, + clipboard_get, clipboard_clear, t); +} + + +void sc_editor_delete_selected_frame(SCEditor *e) +{ + SCBlock *scb_old = e->scblocks; + sc_block_delete(&e->scblocks, e->selection->scblocks); + assert(scb_old == e->scblocks); + full_rerender(e); + emit_change_sig(e); +} + + +static gint destroy_sig(GtkWidget *window, SCEditor *e) +{ + return 0; +} + + +static void draw_editing_box(cairo_t *cr, struct frame *fr) +{ + const double dash[] = {2.0, 2.0}; + double xmin, ymin, width, height; + double ptot_w, ptot_h; + + xmin = fr->x; + ymin = fr->y; + width = fr->w; + height = fr->h; + + cairo_new_path(cr); + cairo_rectangle(cr, xmin, ymin, width, height); + cairo_set_source_rgb(cr, 0.0, 0.69, 1.0); + cairo_set_line_width(cr, 0.5); + cairo_stroke(cr); + + cairo_new_path(cr); + ptot_w = fr->pad_l + fr->pad_r; + ptot_h = fr->pad_t + fr->pad_b; + cairo_rectangle(cr, xmin+fr->pad_l, ymin+fr->pad_t, + width-ptot_w, height-ptot_h); + cairo_set_dash(cr, dash, 2, 0.0); + cairo_set_source_rgb(cr, 0.0, 0.0, 0.0); + cairo_set_line_width(cr, 0.1); + cairo_stroke(cr); + + cairo_set_dash(cr, NULL, 0, 0.0); +} + + +static void draw_para_highlight(cairo_t *cr, struct frame *fr, int cursor_para) +{ + double cx, cy, w, h; + + if ( get_para_highlight(fr, cursor_para, &cx, &cy, &w, &h) != 0 ) { + return; + } + + cairo_new_path(cr); + cairo_rectangle(cr, cx+fr->x, cy+fr->y, w, h); + cairo_set_source_rgba(cr, 0.7, 0.7, 1.0, 0.5); + cairo_set_line_width(cr, 5.0); + cairo_stroke(cr); +} + + +static void draw_caret(cairo_t *cr, struct frame *fr, struct edit_pos cpos, + int hgh) +{ + double cx, clow, chigh, h; + const double t = 1.8; + size_t offs; + Paragraph *para; + + if ( hgh ) { + draw_para_highlight(cr, fr, cpos.para); + return; + } + + assert(fr != NULL); + + para = fr->paras[cpos.para]; + if ( para_type(para) != PARA_TYPE_TEXT ) { + draw_para_highlight(cr, fr, cpos.para); + return; + } + + offs = pos_trail_to_offset(para, cpos.pos, cpos.trail); + get_cursor_pos(fr, cpos.para, offs, &cx, &clow, &h); + + cx += fr->x; + clow += fr->y; + chigh = clow + h; + + cairo_move_to(cr, cx, clow); + cairo_line_to(cr, cx, chigh); + + cairo_move_to(cr, cx-t, clow-t); + cairo_line_to(cr, cx, clow); + cairo_move_to(cr, cx+t, clow-t); + cairo_line_to(cr, cx, clow); + + cairo_move_to(cr, cx-t, chigh+t); + cairo_line_to(cr, cx, chigh); + cairo_move_to(cr, cx+t, chigh+t); + cairo_line_to(cr, cx, chigh); + + cairo_set_source_rgb(cr, 0.86, 0.0, 0.0); + cairo_set_line_width(cr, 1.0); + cairo_stroke(cr); +} + + +static void draw_resize_handle(cairo_t *cr, double x, double y) +{ + cairo_new_path(cr); + cairo_rectangle(cr, x, y, 20.0, 20.0); + cairo_set_source_rgba(cr, 0.9, 0.9, 0.9, 0.5); + cairo_fill(cr); +} + + +static void draw_overlay(cairo_t *cr, SCEditor *e) +{ + if ( e->selection != NULL ) { + + double x, y, w, h; + + draw_editing_box(cr, e->selection); + + x = e->selection->x; + y = e->selection->y; + w = e->selection->w; + h = e->selection->h; + + if ( e->selection->resizable ) { + /* Draw resize handles */ + draw_resize_handle(cr, x, y+h-20.0); + draw_resize_handle(cr, x+w-20.0, y); + draw_resize_handle(cr, x, y); + draw_resize_handle(cr, x+w-20.0, y+h-20.0); + } + + draw_caret(cr, e->cursor_frame, e->cpos, e->para_highlight); + + } + + if ( (e->drag_status == DRAG_STATUS_DRAGGING) + && ((e->drag_reason == DRAG_REASON_CREATE) + || (e->drag_reason == DRAG_REASON_IMPORT)) ) + { + cairo_new_path(cr); + cairo_rectangle(cr, e->start_corner_x, e->start_corner_y, + e->drag_corner_x - e->start_corner_x, + e->drag_corner_y - e->start_corner_y); + cairo_set_source_rgb(cr, 0.5, 0.5, 0.5); + cairo_set_line_width(cr, 0.5); + cairo_stroke(cr); + } + + if ( (e->drag_status == DRAG_STATUS_DRAGGING) + && ((e->drag_reason == DRAG_REASON_RESIZE) + || (e->drag_reason == DRAG_REASON_MOVE)) ) + { + cairo_new_path(cr); + cairo_rectangle(cr, e->box_x, e->box_y, + e->box_width, e->box_height); + cairo_set_source_rgb(cr, 0.5, 0.5, 0.5); + cairo_set_line_width(cr, 0.5); + cairo_stroke(cr); + } +} + + +static gboolean draw_sig(GtkWidget *da, cairo_t *cr, SCEditor *e) +{ + /* Ultimate background */ + if ( e->bg_pixbuf != NULL ) { + gdk_cairo_set_source_pixbuf(cr, e->bg_pixbuf, 0.0, 0.0); + cairo_pattern_t *patt = cairo_get_source(cr); + cairo_pattern_set_extend(patt, CAIRO_EXTEND_REPEAT); + cairo_paint(cr); + } else { + cairo_set_source_rgba(cr, 0.8, 0.8, 1.0, 1.0); + cairo_paint(cr); + } + + cairo_translate(cr, e->border_offs_x, e->border_offs_y); + cairo_translate(cr, -e->h_scroll_pos, -e->scroll_pos); + cairo_scale(cr, e->view_scale, e->view_scale); + + /* Rendering background */ + cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 1.0); + cairo_rectangle(cr, 0.0, 0.0, e->log_w, e->log_h); + cairo_fill(cr); + + /* Contents */ + recursive_draw(e->top, cr, e->is, + e->scroll_pos/e->view_scale, + (e->scroll_pos + e->visible_height)/e->view_scale); + + /* Editing overlay */ + draw_overlay(cr, e); + + return FALSE; +} + + +SCBlock *split_paragraph_at_cursor(SCEditor *e) +{ + size_t offs; + Paragraph *para; + + if ( e->cursor_frame == NULL ) return NULL; + + para = e->cursor_frame->paras[e->cpos.para]; + offs = pos_trail_to_offset(para, e->cpos.pos, e->cpos.trail); + return split_paragraph(e->cursor_frame, e->cpos.para, offs, e->pc); +} + + +static void check_cursor_visible(SCEditor *e) +{ + double x, y, h; + size_t offs; + Paragraph *para; + + if ( e->cursor_frame == NULL ) return; + + para = e->cursor_frame->paras[e->cpos.para]; + offs = pos_trail_to_offset(para, e->cpos.pos, e->cpos.trail); + get_cursor_pos(e->cursor_frame, e->cpos.para, offs, &x, &y, &h); + + /* Off the bottom? */ + if ( y - e->scroll_pos + h > e->visible_height ) { + e->scroll_pos = y + h - e->visible_height; + e->scroll_pos += e->cursor_frame->pad_b; + } + + /* Off the top? */ + if ( y < e->scroll_pos ) { + e->scroll_pos = y - e->cursor_frame->pad_t; + } +} + + +static void do_backspace(struct frame *fr, SCEditor *e) +{ + double wrapw = e->cursor_frame->w - e->cursor_frame->pad_l - e->cursor_frame->pad_r; + + if ( e->sel_active ) { + + /* Delete the selected block */ + delete_text_from_frame(e->cursor_frame, e->sel_start, e->sel_end, wrapw); + + /* Cursor goes at start of deletion */ + sort_positions(&e->sel_start, &e->sel_end); + e->cpos = e->sel_start; + e->sel_active = 0; + + } else { + + if ( para_type(e->cursor_frame->paras[e->cpos.para]) == PARA_TYPE_TEXT ) { + + /* Delete one character */ + struct edit_pos p1, p2; + + p1 = e->cpos; + + p2 = p1; + + cursor_moveh(e->cursor_frame, &p2, -1); + show_edit_pos(p1); + show_edit_pos(p2); + + delete_text_from_frame(e->cursor_frame, p1, p2, wrapw); + e->cpos = p2; + + } else { + + /* FIXME: Implement this */ + fprintf(stderr, "Deleting non-text paragraph\n"); + + } + + } + + emit_change_sig(e); + sc_editor_redraw(e); +} + + +static void insert_text(char *t, SCEditor *e) +{ + Paragraph *para; + + if ( e->cursor_frame == NULL ) return; + + if ( e->sel_active ) { + do_backspace(e->cursor_frame, e); + } + + if ( strcmp(t, "\n") == 0 ) { + split_paragraph_at_cursor(e); + if ( e->flow ) update_size(e); + cursor_moveh(e->cursor_frame, &e->cpos, +1); + check_cursor_visible(e); + emit_change_sig(e); + sc_editor_redraw(e); + return; + } + + para = e->cursor_frame->paras[e->cpos.para]; + + /* Is this paragraph even a text one? */ + if ( para_type(para) == PARA_TYPE_TEXT ) { + + size_t off; + + /* Yes. The "easy" case */ + + if ( !position_editable(e->cursor_frame, e->cpos) ) { + fprintf(stderr, "Position not editable\n"); + return; + } + + off = pos_trail_to_offset(para, e->cpos.pos, e->cpos.trail); + insert_text_in_paragraph(para, off, t); + wrap_paragraph(para, NULL, + e->cursor_frame->w - e->cursor_frame->pad_l + - e->cursor_frame->pad_r, 0, 0); + if ( e->flow ) update_size(e); + + cursor_moveh(e->cursor_frame, &e->cpos, +1); + + } else { + + SCBlock *bd; + SCBlock *ad; + Paragraph *pnew; + + bd = para_scblock(para); + if ( bd == NULL ) { + fprintf(stderr, "No SCBlock for para\n"); + return; + } + + /* No. Create a new text paragraph straight afterwards */ + ad = sc_block_insert_after(bd, NULL, NULL, strdup(t)); + if ( ad == NULL ) { + fprintf(stderr, "Failed to add SCBlock\n"); + return; + } + + pnew = insert_paragraph(e->cursor_frame, e->cpos.para); + if ( pnew == NULL ) { + fprintf(stderr, "Failed to insert paragraph\n"); + return; + } + add_run(pnew, ad, e->cursor_frame->fontdesc, + e->cursor_frame->col, NULL); + + wrap_frame(e->cursor_frame, e->pc); + + e->cpos.para += 1; + e->cpos.pos = 0; + e->cpos.trail = 1; + + } + + emit_change_sig(e); + check_cursor_visible(e); + sc_editor_redraw(e); +} + + +static gboolean im_commit_sig(GtkIMContext *im, gchar *str, + SCEditor *e) +{ + insert_text(str, e); + return FALSE; +} + + +static int within_frame(struct frame *fr, double x, double y) +{ + if ( fr == NULL ) return 0; + if ( x < fr->x ) return 0; + if ( y < fr->y ) return 0; + if ( x > fr->x + fr->w ) return 0; + if ( y > fr->y + fr->h ) return 0; + return 1; +} + + +static struct frame *find_frame_at_position(struct frame *fr, + double x, double y) +{ + int i; + + for ( i=0; i<fr->num_children; i++ ) { + + if ( within_frame(fr->children[i], x, y) ) { + return find_frame_at_position(fr->children[i], x, y); + } + + } + + if ( within_frame(fr, x, y) ) return fr; + return NULL; +} + + +static enum corner which_corner(double xp, double yp, struct frame *fr) +{ + double x, y; /* Relative to object position */ + + x = xp - fr->x; + y = yp - fr->y; + + if ( x < 0.0 ) return CORNER_NONE; + if ( y < 0.0 ) return CORNER_NONE; + if ( x > fr->w ) return CORNER_NONE; + if ( y > fr->h ) return CORNER_NONE; + + /* Top left? */ + if ( (x<20.0) && (y<20.0) ) return CORNER_TL; + if ( (x>fr->w-20.0) && (y<20.0) ) return CORNER_TR; + if ( (x<20.0) && (y>fr->h-20.0) ) return CORNER_BL; + if ( (x>fr->w-20.0) && (y>fr->h-20.0) ) return CORNER_BR; + + return CORNER_NONE; +} + + +static void calculate_box_size(struct frame *fr, SCEditor *e, + double x, double y) +{ + double ddx, ddy, dlen, mult; + double vx, vy, dbx, dby; + + ddx = x - e->start_corner_x; + ddy = y - e->start_corner_y; + + if ( !fr->is_image ) { + + switch ( e->drag_corner ) { + + case CORNER_BR : + e->box_x = fr->x; + e->box_y = fr->y; + e->box_width = fr->w + ddx; + e->box_height = fr->h + ddy; + break; + + case CORNER_BL : + e->box_x = fr->x + ddx; + e->box_y = fr->y; + e->box_width = fr->w - ddx; + e->box_height = fr->h + ddy; + break; + + case CORNER_TL : + e->box_x = fr->x + ddx; + e->box_y = fr->y + ddy; + e->box_width = fr->w - ddx; + e->box_height = fr->h - ddy; + break; + + case CORNER_TR : + e->box_x = fr->x; + e->box_y = fr->y + ddy; + e->box_width = fr->w + ddx; + e->box_height = fr->h - ddy; + break; + + case CORNER_NONE : + break; + + } + return; + + + } + + switch ( e->drag_corner ) { + + case CORNER_BR : + vx = fr->w; + vy = fr->h; + break; + + case CORNER_BL : + vx = -fr->w; + vy = fr->h; + break; + + case CORNER_TL : + vx = -fr->w; + vy = -fr->h; + break; + + case CORNER_TR : + vx = fr->w; + vy = -fr->h; + break; + + case CORNER_NONE : + default: + vx = 0.0; + vy = 0.0; + break; + + } + + dlen = (ddx*vx + ddy*vy) / e->diagonal_length; + mult = (dlen+e->diagonal_length) / e->diagonal_length; + + e->box_width = fr->w * mult; + e->box_height = fr->h * mult; + dbx = e->box_width - fr->w; + dby = e->box_height - fr->h; + + if ( e->box_width < 40.0 ) { + mult = 40.0 / fr->w; + } + if ( e->box_height < 40.0 ) { + mult = 40.0 / fr->h; + } + e->box_width = fr->w * mult; + e->box_height = fr->h * mult; + dbx = e->box_width - fr->w; + dby = e->box_height - fr->h; + + switch ( e->drag_corner ) { + + case CORNER_BR : + e->box_x = fr->x; + e->box_y = fr->y; + break; + + case CORNER_BL : + e->box_x = fr->x - dbx; + e->box_y = fr->y; + break; + + case CORNER_TL : + e->box_x = fr->x - dbx; + e->box_y = fr->y - dby; + break; + + case CORNER_TR : + e->box_x = fr->x; + e->box_y = fr->y - dby; + break; + + case CORNER_NONE : + break; + + } +} + + +static void check_paragraph(struct frame *fr, PangoContext *pc, + SCBlock *scblocks) +{ + if ( fr->n_paras > 0 ) return; + Paragraph *para = last_para(fr); + + if ( scblocks == NULL ) { + /* We have no SCBlocks at all! Better create one... */ + scblocks = sc_parse(""); + fr->scblocks = scblocks; + } + + /* We are creating the first paragraph. It uses the last SCBlock + * in the chain */ + while ( sc_block_next(scblocks) != NULL ) { + scblocks = sc_block_next(scblocks); + } + scblocks = sc_block_append(scblocks, NULL, NULL, strdup(""), NULL); + + add_run(para, scblocks, fr->fontdesc, fr->col, NULL); + wrap_paragraph(para, pc, fr->w - fr->pad_l - fr->pad_r, 0, 0); +} + + +static void rewrap_paragraph_range(struct frame *fr, int a, int b, + struct edit_pos sel_start, + struct edit_pos sel_end, + int sel_active) +{ + int i; + int sel_s, sel_e; + Paragraph *para; + + if ( a > b ) { + int t = a; + a = b; b = t; + } + + if ( fr == NULL ) return; + if ( fr->paras == NULL ) return; + + sort_positions(&sel_start, &sel_end); + + //printf("frame %p\n", fr); + //printf("start: "); + //show_edit_pos(sel_start); + //printf(" end: "); + //show_edit_pos(sel_end); + + para = fr->paras[sel_start.para]; + sel_s = pos_trail_to_offset(para, sel_start.pos, sel_start.trail); + para = fr->paras[sel_end.para]; + sel_e = pos_trail_to_offset(para, sel_end.pos, sel_end.trail); + + for ( i=a; i<=b; i++ ) { + size_t srt, end; + if ( sel_active ) { + if ( i == sel_start.para ) { + srt = sel_s; + } else { + srt = 0; + } + if ( i == sel_end.para ) { + end = sel_e; + } else { + end = G_MAXUINT; + } + if ( i > sel_start.para && i < sel_end.para ) { + end = G_MAXUINT; + } + } else { + srt = 0; + end = 0; + } + wrap_paragraph(fr->paras[i], NULL, + fr->w - fr->pad_l - fr->pad_r, srt, end); + } +} + + +static void unset_selection(SCEditor *e) +{ + int a, b; + + if ( !e->sel_active ) return; + + a = e->sel_start.para; + b = e->sel_end.para; + if ( a > b ) { + a = e->sel_end.para; + b = e->sel_start.para; + } + e->sel_active = 0; + rewrap_paragraph_range(e->cursor_frame, a, b, e->sel_start, e->sel_end, 0); +} + + +static gboolean button_press_sig(GtkWidget *da, GdkEventButton *event, + SCEditor *e) +{ + enum corner c; + gdouble x, y; + struct frame *clicked; + int shift; + + x = event->x - e->border_offs_x; + y = event->y - e->border_offs_y + e->scroll_pos; + x /= e->view_scale; + y /= e->view_scale; + shift = event->state & GDK_SHIFT_MASK; + + if ( within_frame(e->selection, x, y) ) { + clicked = e->selection; + } else { + clicked = find_frame_at_position(e->top, x, y); + } + + /* Clicked within the currently selected frame + * -> resize, move or select text */ + if ( (e->selection != NULL) && (clicked == e->selection) ) { + + struct frame *fr; + + fr = e->selection; + + /* Within the resizing region? */ + c = which_corner(x, y, fr); + if ( (c != CORNER_NONE) && fr->resizable && shift ) { + + e->drag_reason = DRAG_REASON_RESIZE; + e->drag_corner = c; + + e->start_corner_x = x; + e->start_corner_y = y; + e->diagonal_length = pow(fr->w, 2.0); + e->diagonal_length += pow(fr->h, 2.0); + e->diagonal_length = sqrt(e->diagonal_length); + + calculate_box_size(fr, e, x, y); + + e->drag_status = DRAG_STATUS_COULD_DRAG; + e->drag_reason = DRAG_REASON_RESIZE; + + } else { + + /* Position cursor and prepare for possible drag */ + e->cursor_frame = clicked; + check_paragraph(e->cursor_frame, e->pc, sc_block_child(fr->scblocks)); + find_cursor(clicked, x-fr->x, y-fr->y, &e->cpos); + ensure_run(e->cursor_frame, e->cpos); + + e->start_corner_x = x; + e->start_corner_y = y; + + if ( event->type == GDK_2BUTTON_PRESS ) { + check_callback_click(e->cursor_frame, e->cpos.para); + } + + if ( fr->resizable && shift ) { + e->drag_status = DRAG_STATUS_COULD_DRAG; + e->drag_reason = DRAG_REASON_MOVE; + } else if ( !e->para_highlight ) { + e->drag_status = DRAG_STATUS_COULD_DRAG; + e->drag_reason = DRAG_REASON_TEXTSEL; + unset_selection(e); + find_cursor(clicked, x-fr->x, y-fr->y, &e->sel_start); + } + + } + + } else if ( (clicked == NULL) + || ( !e->top_editable && (clicked == e->top) ) ) + { + /* Clicked no object. Deselect old object. + * If shift held, set up for creating a new one. */ + e->selection = NULL; + unset_selection(e); + + if ( shift ) { + e->start_corner_x = x; + e->start_corner_y = y; + e->drag_status = DRAG_STATUS_COULD_DRAG; + e->drag_reason = DRAG_REASON_CREATE; + } else { + e->drag_status = DRAG_STATUS_NONE; + e->drag_reason = DRAG_REASON_NONE; + } + + } else { + + /* Clicked an existing frame, no immediate dragging */ + e->drag_status = DRAG_STATUS_COULD_DRAG; + e->drag_reason = DRAG_REASON_TEXTSEL; + unset_selection(e); + find_cursor(clicked, x-clicked->x, y-clicked->y, + &e->sel_start); + find_cursor(clicked, x-clicked->x, y-clicked->y, + &e->sel_end); + e->selection = clicked; + e->cursor_frame = clicked; + if ( clicked == e->top ) { + check_paragraph(e->cursor_frame, e->pc, clicked->scblocks); + } else { + check_paragraph(e->cursor_frame, e->pc, + sc_block_child(clicked->scblocks)); + } + find_cursor(clicked, x-clicked->x, y-clicked->y, &e->cpos); + ensure_run(e->cursor_frame, e->cpos); + + } + + gtk_widget_grab_focus(GTK_WIDGET(da)); + sc_editor_redraw(e); + return FALSE; +} + + +static gboolean motion_sig(GtkWidget *da, GdkEventMotion *event, + SCEditor *e) +{ + struct frame *fr = e->selection; + gdouble x, y; + + x = event->x - e->border_offs_x; + y = event->y - e->border_offs_y + e->scroll_pos; + x /= e->view_scale; + y /= e->view_scale; + + if ( e->drag_status == DRAG_STATUS_COULD_DRAG ) { + + /* We just got a motion signal, and the status was "could drag", + * therefore the drag has started. */ + e->drag_status = DRAG_STATUS_DRAGGING; + + } + + switch ( e->drag_reason ) { + + case DRAG_REASON_NONE : + break; + + case DRAG_REASON_CREATE : + e->drag_corner_x = x; + e->drag_corner_y = y; + sc_editor_redraw(e); + break; + + case DRAG_REASON_IMPORT : + /* Do nothing, handled by dnd_motion() */ + break; + + case DRAG_REASON_RESIZE : + calculate_box_size(fr, e, x, y); + sc_editor_redraw(e); + break; + + case DRAG_REASON_MOVE : + e->box_x = (fr->x - e->start_corner_x) + x; + e->box_y = (fr->y - e->start_corner_y) + y; + e->box_width = fr->w; + e->box_height = fr->h; + sc_editor_redraw(e); + break; + + case DRAG_REASON_TEXTSEL : + unset_selection(e); + find_cursor(fr, x-fr->x, y-fr->y, &e->sel_end); + rewrap_paragraph_range(fr, e->sel_start.para, e->sel_end.para, + e->sel_start, e->sel_end, 1); + find_cursor(fr, x-fr->x, y-fr->y, &e->cpos); + e->sel_active = !positions_equal(e->sel_start, e->sel_end); + sc_editor_redraw(e); + break; + + } + + gdk_event_request_motions(event); + return FALSE; +} + + +static struct frame *create_frame(SCEditor *e, double x, double y, + double w, double h) +{ + struct frame *parent; + struct frame *fr; + SCBlock *scblocks; + + parent = e->top; + + if ( w < 0.0 ) { + x += w; + w = -w; + } + + if ( h < 0.0 ) { + y += h; + h = -h; + } + + /* Add to frame structure */ + fr = add_subframe(parent); + + /* Add to SC */ + scblocks = sc_block_append_end(sc_block_child(e->scblocks), "f", NULL, NULL); + fr->scblocks = scblocks; + sc_block_append_inside(scblocks, NULL, NULL, strdup("")); + + fr->x = x; + fr->y = y; + fr->w = w; + fr->h = h; + fr->is_image = 0; + fr->empty = 1; + fr->resizable = 1; + + update_geom(fr); + + full_rerender(e); + return find_frame_with_scblocks(e->top, scblocks); +} + + +static void do_resize(SCEditor *e, double x, double y, double w, double h) +{ + struct frame *fr; + + assert(e->selection != NULL); + + if ( w < 0.0 ) { + w = -w; + x -= w; + } + + if ( h < 0.0 ) { + h = -h; + y -= h; + } + + fr = e->selection; + fr->x = x; + fr->y = y; + fr->w = w; + fr->h = h; + update_geom(fr); + + full_rerender(e); + sc_editor_redraw(e); +} + + +static gboolean button_release_sig(GtkWidget *da, GdkEventButton *event, + SCEditor *e) +{ + gdouble x, y; + struct frame *fr; + + x = event->x - e->border_offs_x; + y = event->y - e->border_offs_y; + x /= e->view_scale; + y /= e->view_scale; + + /* Not dragging? Then I don't care. */ + if ( e->drag_status != DRAG_STATUS_DRAGGING ) return FALSE; + + e->drag_corner_x = x; + e->drag_corner_y = y; + e->drag_status = DRAG_STATUS_NONE; + + switch ( e->drag_reason ) + { + + case DRAG_REASON_NONE : + printf("Release on pointless drag.\n"); + break; + + case DRAG_REASON_CREATE : + fr = create_frame(e, e->start_corner_x, e->start_corner_y, + e->drag_corner_x - e->start_corner_x, + e->drag_corner_y - e->start_corner_y); + if ( fr != NULL ) { + check_paragraph(fr, e->pc, sc_block_child(fr->scblocks)); + e->selection = fr; + e->cursor_frame = fr; + e->cpos.para = 0; + e->cpos.pos = 0; + e->cpos.trail = 0; + } else { + fprintf(stderr, _("Failed to create frame!\n")); + } + break; + + case DRAG_REASON_IMPORT : + /* Do nothing, handled in dnd_drop() or dnd_leave() */ + break; + + case DRAG_REASON_RESIZE : + do_resize(e, e->box_x, e->box_y, e->box_width, e->box_height); + break; + + case DRAG_REASON_MOVE : + do_resize(e, e->box_x, e->box_y, e->box_width, e->box_height); + break; + + case DRAG_REASON_TEXTSEL : + /* Do nothing (text is already selected) */ + break; + + } + + e->drag_reason = DRAG_REASON_NONE; + + gtk_widget_grab_focus(GTK_WIDGET(da)); + sc_editor_redraw(e); + return FALSE; +} + + +static gboolean key_press_sig(GtkWidget *da, GdkEventKey *event, + SCEditor *e) +{ + gboolean r; + int claim = 0; + + /* Throw the event to the IM context and let it sort things out */ + r = gtk_im_context_filter_keypress(GTK_IM_CONTEXT(e->im_context), + event); + if ( r ) return FALSE; /* IM ate it */ + + switch ( event->keyval ) { + + case GDK_KEY_Escape : + if ( !e->para_highlight ) { + sc_editor_remove_cursor(e); + sc_editor_redraw(e); + claim = 1; + } + break; + + case GDK_KEY_Left : + if ( e->selection != NULL ) { + cursor_moveh(e->cursor_frame, &e->cpos, -1); + sc_editor_redraw(e); + } + claim = 1; + break; + + case GDK_KEY_Right : + if ( e->selection != NULL ) { + cursor_moveh(e->cursor_frame, &e->cpos, +1); + sc_editor_redraw(e); + } + claim = 1; + break; + + case GDK_KEY_Up : + if ( e->selection != NULL ) { + cursor_moveh(e->cursor_frame, &e->cpos, -1); + sc_editor_redraw(e); + } + claim = 1; + break; + + case GDK_KEY_Down : + if ( e->selection != NULL ) { + cursor_moveh(e->cursor_frame, &e->cpos, +1); + sc_editor_redraw(e); + } + claim = 1; + break; + + + case GDK_KEY_Return : + im_commit_sig(NULL, "\n", e); + claim = 1; + break; + + case GDK_KEY_BackSpace : + if ( e->selection != NULL ) { + do_backspace(e->selection, e); + claim = 1; + } + break; + + case GDK_KEY_F5 : + full_rerender(e); + break; + + case GDK_KEY_F6 : + show_edit_pos(e->cpos); + break; + + case GDK_KEY_F7 : + if ( e->cursor_frame != NULL ) { + if ( event->state & GDK_CONTROL_MASK ) { + debug_paragraphs(e); + } else if ( event->state & GDK_SHIFT_MASK ) { + printf("Cursor frame block = %p\n", e->cursor_frame->scblocks); + printf("Editor top block = %p\n", e->scblocks); + show_sc_block(e->cursor_frame->scblocks, ""); + } else { + open_debugger(e->cursor_frame); + } + } else { + if ( event->state & GDK_SHIFT_MASK ) { + printf("Debugging the top frame:\n"); + printf("Editor top block = %p\n", e->scblocks); + show_sc_block(e->top->scblocks, ""); + } + } + break; + + case GDK_KEY_C : + case GDK_KEY_c : + if ( event->state == GDK_CONTROL_MASK ) { + copy_selection(e); + } + break; + + case GDK_KEY_V : + case GDK_KEY_v : + if ( event->state == GDK_CONTROL_MASK ) { + sc_editor_paste(e); + } + break; + + + } + + if ( claim ) return TRUE; + return FALSE; +} + + +static gboolean dnd_motion(GtkWidget *widget, GdkDragContext *drag_context, + gint x, gint y, guint time, SCEditor *e) +{ + GdkAtom target; + + /* If we haven't already requested the data, do so now */ + if ( !e->drag_preview_pending && !e->have_drag_data ) { + + target = gtk_drag_dest_find_target(widget, drag_context, NULL); + + if ( target != GDK_NONE ) { + gtk_drag_get_data(widget, drag_context, target, time); + e->drag_preview_pending = 1; + } else { + e->import_acceptable = 0; + gdk_drag_status(drag_context, 0, time); + } + + } + + if ( e->have_drag_data && e->import_acceptable ) { + + gdk_drag_status(drag_context, GDK_ACTION_LINK, time); + e->start_corner_x = x - e->import_width/2.0; + e->start_corner_y = y - e->import_height/2.0; + e->drag_corner_x = x + e->import_width/2.0; + e->drag_corner_y = y + e->import_height/2.0; + + sc_editor_redraw(e); + + } + + return TRUE; +} + + +static gboolean dnd_drop(GtkWidget *widget, GdkDragContext *drag_context, + gint x, gint y, guint time, SCEditor *e) +{ + GdkAtom target; + + target = gtk_drag_dest_find_target(widget, drag_context, NULL); + + if ( target == GDK_NONE ) { + gtk_drag_finish(drag_context, FALSE, FALSE, time); + } else { + gtk_drag_get_data(widget, drag_context, target, time); + } + + return TRUE; +} + + +/* Scale the image down if it's a silly size */ +static void check_import_size(SCEditor *e) +{ + if ( e->import_width > e->w ) { + + int new_import_width; + + new_import_width = e->w/2; + e->import_height = (new_import_width * e->import_height) / + e->import_width; + e->import_width = new_import_width; + + } + + if ( e->import_height > e->h ) { + + int new_import_height; + + new_import_height = e->w/2; + e->import_width = (new_import_height*e->import_width) / + e->import_height; + e->import_height = new_import_height; + + } +} + + +static void dnd_receive(GtkWidget *widget, GdkDragContext *drag_context, + gint x, gint y, GtkSelectionData *seldata, + guint info, guint time, SCEditor *e) +{ + if ( e->drag_preview_pending ) { + + gchar *filename = NULL; + GdkPixbufFormat *f; + gchar **uris; + int w, h; + + e->have_drag_data = 1; + e->drag_preview_pending = 0; + uris = gtk_selection_data_get_uris(seldata); + if ( uris != NULL ) { + filename = g_filename_from_uri(uris[0], NULL, NULL); + } + g_strfreev(uris); + + if ( filename == NULL ) { + + /* This doesn't even look like a sensible URI. + * Bail out. */ + gdk_drag_status(drag_context, 0, time); + if ( e->drag_highlight ) { + gtk_drag_unhighlight(widget); + e->drag_highlight = 0; + } + e->import_acceptable = 0; + return; + + } + chomp(filename); + + f = gdk_pixbuf_get_file_info(filename, &w, &h); + g_free(filename); + + e->import_width = w; + e->import_height = h; + + if ( f == NULL ) { + + gdk_drag_status(drag_context, 0, time); + if ( e->drag_highlight ) { + gtk_drag_unhighlight(widget); + e->drag_highlight = 0; + } + e->drag_status = DRAG_STATUS_NONE; + e->drag_reason = DRAG_REASON_NONE; + e->import_acceptable = 0; + + } else { + + /* Looks like a sensible image */ + gdk_drag_status(drag_context, GDK_ACTION_PRIVATE, time); + e->import_acceptable = 1; + + if ( !e->drag_highlight ) { + gtk_drag_highlight(widget); + e->drag_highlight = 1; + } + + check_import_size(e); + e->drag_reason = DRAG_REASON_IMPORT; + e->drag_status = DRAG_STATUS_DRAGGING; + + } + + } else { + + gchar **uris; + char *filename = NULL; + + uris = gtk_selection_data_get_uris(seldata); + if ( uris != NULL ) { + filename = g_filename_from_uri(uris[0], NULL, NULL); + } + g_strfreev(uris); + + if ( filename != NULL ) { + + struct frame *fr; + char *opts; + size_t len; + int w, h; + + gtk_drag_finish(drag_context, TRUE, FALSE, time); + chomp(filename); + + w = e->drag_corner_x - e->start_corner_x; + h = e->drag_corner_y - e->start_corner_y; + + len = strlen(filename)+64; + opts = malloc(len); + if ( opts == NULL ) { + free(filename); + fprintf(stderr, "Failed to allocate SC\n"); + return; + } + snprintf(opts, len, "1fx1f+0+0,filename=\"%s\"", + filename); + + fr = create_frame(e, e->start_corner_x, + e->start_corner_y, w, h); + fr->is_image = 1; + fr->empty = 0; + sc_block_set_name(sc_block_child(fr->scblocks), strdup("image")); + sc_block_set_options(sc_block_child(fr->scblocks), opts); + full_rerender(e); + sc_editor_remove_cursor(e); + sc_editor_redraw(e); + free(filename); + + } else { + + gtk_drag_finish(drag_context, FALSE, FALSE, time); + + } + + } +} + + +static void dnd_leave(GtkWidget *widget, GdkDragContext *drag_context, + guint time, SCEditor *sceditor) +{ + if ( sceditor->drag_highlight ) { + gtk_drag_unhighlight(widget); + } + sceditor->have_drag_data = 0; + sceditor->drag_highlight = 0; + sceditor->drag_status = DRAG_STATUS_NONE; + sceditor->drag_reason = DRAG_REASON_NONE; +} + + +static gint realise_sig(GtkWidget *da, SCEditor *e) +{ + GdkWindow *win; + + /* Keyboard and input method stuff */ + e->im_context = gtk_im_multicontext_new(); + win = gtk_widget_get_window(GTK_WIDGET(e)); + gtk_im_context_set_client_window(GTK_IM_CONTEXT(e->im_context), win); + gdk_window_set_accept_focus(win, TRUE); + g_signal_connect(G_OBJECT(e->im_context), "commit", G_CALLBACK(im_commit_sig), e); + g_signal_connect(G_OBJECT(e), "key-press-event", G_CALLBACK(key_press_sig), e); + + /* FIXME: Can do this "properly" by setting up a separate font map */ + e->pc = gtk_widget_get_pango_context(GTK_WIDGET(e)); + + return FALSE; +} + + +void sc_editor_set_scblock(SCEditor *e, SCBlock *scblocks) +{ + e->scblocks = scblocks; + full_rerender(e); +} + + +static void update_size_request(SCEditor *e) +{ + gtk_widget_set_size_request(GTK_WIDGET(e), 0, e->h + 2.0*e->min_border); +} + + +void sc_editor_set_logical_size(SCEditor *e, double w, double h) +{ + e->log_w = w; + e->log_h = h; + if ( gtk_widget_get_mapped(GTK_WIDGET(e)) ) { + full_rerender(e); + sc_editor_redraw(e); + } +} + + +void sc_editor_set_slidenum(SCEditor *e, int slidenum) +{ + e->slidenum = slidenum; +} + + +void sc_editor_set_min_border(SCEditor *e, double min_border) +{ + e->min_border = min_border; + update_size_request(e); +} + + +void sc_editor_set_top_frame_editable(SCEditor *e, int top_frame_editable) +{ + e->top_editable = top_frame_editable; +} + + +void sc_editor_set_stylesheet(SCEditor *e, Stylesheet *stylesheet) +{ + e->stylesheet = stylesheet; +} + + +void sc_editor_set_callbacks(SCEditor *e, SCCallbackList *cbl) +{ + if ( e->cbl != NULL ) sc_callback_list_free(e->cbl); + e->cbl = cbl; +} + + +void sc_editor_set_para_highlight(SCEditor *e, int para_highlight) +{ + e->para_highlight = para_highlight; + sc_editor_redraw(e); +} + +int sc_editor_get_cursor_para(SCEditor *e) +{ + if ( e->cursor_frame == NULL ) return 0; + return e->cpos.para; +} + + +void *sc_editor_get_cursor_bvp(SCEditor *e) +{ + Paragraph *para; + if ( e->cursor_frame == NULL ) return 0; + para = e->cursor_frame->paras[e->cpos.para]; + return get_para_bvp(para); +} + + +void sc_editor_set_cursor_para(SCEditor *e, signed int pos) +{ + double h; + int i; + + if ( e->cursor_frame == NULL ) { + e->cursor_frame = e->top; + e->selection = e->top; + } + + if ( pos < 0 ) { + e->cpos.para = e->cursor_frame->n_paras - 1; + } else if ( pos >= e->cursor_frame->n_paras ) { + e->cpos.para = e->cursor_frame->n_paras - 1; + } else { + e->cpos.para = pos; + } + e->cpos.pos = 0; + e->cpos.trail = 0; + + h = 0; + for ( i=0; i<e->cpos.para; i++ ) { + h += paragraph_height(e->cursor_frame->paras[i]); + } + h += (paragraph_height(e->cursor_frame->paras[e->cpos.para]))/2; + e->scroll_pos = h - (e->visible_height/2); + set_vertical_params(e); + + sc_editor_redraw(e); +} + + +int sc_editor_get_num_paras(SCEditor *e) +{ + if ( e->cursor_frame == NULL ) return 1; + return e->cursor_frame->n_paras; +} + + +void sc_editor_set_scale(SCEditor *e, int scale) +{ + e->scale = scale; + if ( !scale ) { + e->view_scale = 1.0; + } +} + + +void sc_editor_set_imagestore(SCEditor *e, ImageStore *is) +{ + if ( e->is != NULL ) { + fprintf(stderr, "WARNING: Changing imagestore\n"); + } + e->is = is; +} + + +SCEditor *sc_editor_new(SCBlock *scblocks, Stylesheet *stylesheet, + PangoLanguage *lang, const char *storename) +{ + SCEditor *sceditor; + GtkTargetEntry targets[1]; + GError *err; + + sceditor = g_object_new(SC_TYPE_EDITOR, NULL); + + sceditor->scblocks = scblocks; + sceditor->w = 100; + sceditor->h = 100; + sceditor->log_w = 100; + sceditor->log_h = 100; + sceditor->border_offs_x = 0; + sceditor->border_offs_y = 0; + sceditor->is = NULL; + sceditor->slidenum = 0; + sceditor->min_border = 0.0; + sceditor->top_editable = 0; + sceditor->cbl = NULL; + sceditor->scroll_pos = 0; + sceditor->flow = 0; + sceditor->scale = 0; + sceditor->view_scale = 1.0; + sceditor->lang = lang; + + sceditor->para_highlight = 0; + sc_editor_remove_cursor(sceditor); + + sceditor->stylesheet = stylesheet; + + err = NULL; + sceditor->bg_pixbuf = gdk_pixbuf_new_from_resource("/uk/me/bitwiz/Colloquium/sky.png", + &err); + if ( sceditor->bg_pixbuf == NULL ) { + fprintf(stderr, _("Failed to load background: %s\n"), + err->message); + } + + gtk_widget_set_size_request(GTK_WIDGET(sceditor), + sceditor->w, sceditor->h); + + g_signal_connect(G_OBJECT(sceditor), "destroy", + G_CALLBACK(destroy_sig), sceditor); + g_signal_connect(G_OBJECT(sceditor), "realize", + G_CALLBACK(realise_sig), sceditor); + g_signal_connect(G_OBJECT(sceditor), "button-press-event", + G_CALLBACK(button_press_sig), sceditor); + g_signal_connect(G_OBJECT(sceditor), "button-release-event", + G_CALLBACK(button_release_sig), sceditor); + g_signal_connect(G_OBJECT(sceditor), "motion-notify-event", + G_CALLBACK(motion_sig), sceditor); + g_signal_connect(G_OBJECT(sceditor), "configure-event", + G_CALLBACK(resize_sig), sceditor); + + /* Drag and drop */ + targets[0].target = "text/uri-list"; + targets[0].flags = 0; + targets[0].info = 1; + gtk_drag_dest_set(GTK_WIDGET(sceditor), 0, targets, 1, + GDK_ACTION_PRIVATE); + g_signal_connect(sceditor, "drag-data-received", + G_CALLBACK(dnd_receive), sceditor); + g_signal_connect(sceditor, "drag-motion", + G_CALLBACK(dnd_motion), sceditor); + g_signal_connect(sceditor, "drag-drop", + G_CALLBACK(dnd_drop), sceditor); + g_signal_connect(sceditor, "drag-leave", + G_CALLBACK(dnd_leave), sceditor); + + gtk_widget_set_can_focus(GTK_WIDGET(sceditor), TRUE); + gtk_widget_add_events(GTK_WIDGET(sceditor), + GDK_POINTER_MOTION_HINT_MASK + | GDK_BUTTON1_MOTION_MASK + | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + | GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK + | GDK_SCROLL_MASK); + + g_signal_connect(G_OBJECT(sceditor), "draw", + G_CALLBACK(draw_sig), sceditor); + + gtk_widget_grab_focus(GTK_WIDGET(sceditor)); + + gtk_widget_show(GTK_WIDGET(sceditor)); + + return sceditor; +} diff --git a/src-old/sc_editor.h b/src-old/sc_editor.h new file mode 100644 index 0000000..d3c111b --- /dev/null +++ b/src-old/sc_editor.h @@ -0,0 +1,199 @@ +/* + * sc_editor.h + * + * Copyright © 2014-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 SC_EDITOR_H +#define SC_EDITOR_H + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <gtk/gtk.h> +#include <glib-object.h> + +#include "frame.h" +#include "sc_interp.h" +#include "stylesheet.h" + +struct presentation; + + +#define SC_TYPE_EDITOR (sc_editor_get_type()) + +#define SC_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + SC_TYPE_EDITOR, SCEditor)) + +#define SC_IS_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), \ + SC_TYPE_EDITOR)) + +#define SC_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((obj), \ + SC_TYPE_EDITOR, SCEditorClass)) + +#define SC_IS_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((obj), \ + SC_TYPE_EDITOR)) + +#define SC_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), \ + SC_TYPE_EDITOR, SCEditorClass)) + +enum drag_reason +{ + DRAG_REASON_NONE, + DRAG_REASON_CREATE, + DRAG_REASON_IMPORT, + DRAG_REASON_RESIZE, + DRAG_REASON_MOVE, + DRAG_REASON_TEXTSEL +}; + + +enum corner +{ + CORNER_NONE, + CORNER_TL, + CORNER_TR, + CORNER_BL, + CORNER_BR +}; + + +enum drag_status +{ + DRAG_STATUS_NONE, + DRAG_STATUS_COULD_DRAG, + DRAG_STATUS_DRAGGING, +}; + + +struct _sceditor +{ + GtkDrawingArea parent_instance; + PangoLanguage *lang; + + /*< private >*/ + GtkIMContext *im_context; + int w; /* Surface size in pixels */ + int h; + double log_w; /* Size of surface in "SC units" */ + double log_h; + SCBlock *scblocks; + Stylesheet *stylesheet; + ImageStore *is; + SCCallbackList *cbl; + struct frame *top; + int para_highlight; + + /* Redraw/scroll stuff */ + GtkScrollablePolicy hpol; + GtkScrollablePolicy vpol; + GtkAdjustment *hadj; + GtkAdjustment *vadj; + double scroll_pos; + double h_scroll_pos; + int visible_height; + int visible_width; + int flow; + int scale; /* Whether the SCEditor should scale to fit */ + double view_scale; /* The scale factor, if scale=1 */ + + /* Pointers to the frame currently being edited */ + struct frame *selection; + int top_editable; + + PangoContext *pc; + + /* Location of the cursor */ + struct frame *cursor_frame; + struct edit_pos cpos; + + /* Border surrounding actual slide within drawingarea */ + double border_offs_x; + double border_offs_y; + double min_border; + double bgcol[3]; + GdkPixbuf *bg_pixbuf; + + /* Rubber band boxes and related stuff */ + double start_corner_x; + double start_corner_y; + double drag_corner_x; + double drag_corner_y; + double diagonal_length; + double box_x; + double box_y; + double box_width; + double box_height; + enum drag_reason drag_reason; + enum drag_status drag_status; + enum corner drag_corner; + int sel_active; + struct edit_pos sel_start; /* Where the user dragged from */ + struct edit_pos sel_end; + + /* Stuff to do with drag and drop import of "content" */ + int drag_preview_pending; + int have_drag_data; + int drag_highlight; + double import_width; + double import_height; + int import_acceptable; + + /* Stuff that doesn't really belong here */ + int slidenum; +}; + +struct _sceditorclass +{ + GtkDrawingAreaClass parent_class; +}; + +typedef struct _sceditor SCEditor; +typedef struct _sceditorclass SCEditorClass; + +extern void sc_editor_set_scblock(SCEditor *e, SCBlock *scblocks); +extern void sc_editor_set_stylesheet(SCEditor *e, Stylesheet *stylesheet); +extern SCEditor *sc_editor_new(SCBlock *scblocks, Stylesheet *stylesheet, + PangoLanguage *lang, const char *storename); +extern void sc_editor_set_logical_size(SCEditor *e, double w, double h); +extern void sc_editor_set_flow(SCEditor *e, int flow); +extern void sc_editor_set_scale(SCEditor *e, int scale); +extern void sc_editor_redraw(SCEditor *e); +extern void sc_editor_set_background(SCEditor *e, double r, double g, double b); +extern void sc_editor_set_slidenum(SCEditor *e, int slidenum); +extern void sc_editor_set_min_border(SCEditor *e, double min_border); +extern void sc_editor_set_top_frame_editable(SCEditor *e, + int top_frame_editable); +extern void sc_editor_set_callbacks(SCEditor *e, SCCallbackList *cbl); +extern void sc_editor_paste(SCEditor *e); +extern void sc_editor_add_storycode(SCEditor *e, const char *sc); +extern void sc_editor_copy_selected_frame(SCEditor *e); +extern void sc_editor_delete_selected_frame(SCEditor *e); +extern void sc_editor_ensure_cursor(SCEditor *e); +extern SCBlock *split_paragraph_at_cursor(SCEditor *e); + +extern void sc_editor_set_imagestore(SCEditor *e, ImageStore *is); +extern void sc_editor_set_para_highlight(SCEditor *e, int para_highlight); +extern int sc_editor_get_cursor_para(SCEditor *e); +extern void *sc_editor_get_cursor_bvp(SCEditor *e); +extern void sc_editor_set_cursor_para(SCEditor *e, signed int pos); +extern int sc_editor_get_num_paras(SCEditor *e); + +#endif /* SC_EDITOR_H */ diff --git a/src-old/sc_interp.c b/src-old/sc_interp.c new file mode 100644 index 0000000..07f09a5 --- /dev/null +++ b/src-old/sc_interp.c @@ -0,0 +1,1148 @@ +/* + * sc_interp.c + * + * Copyright © 2014-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 <assert.h> +#include <stdlib.h> +#include <string.h> +#include <pango/pangocairo.h> +#include <gdk/gdk.h> + +#include "imagestore.h" +#include "sc_parse.h" +#include "sc_interp.h" +#include "presentation.h" +#include "utils.h" + + +struct sc_state +{ + PangoFontDescription *fontdesc; + PangoFont *font; + PangoAlignment alignment; + double col[4]; + int ascent; + int height; + float paraspace[4]; + char *constants[NUM_SC_CONSTANTS]; + + struct frame *fr; /* The current frame */ +}; + +struct _scinterp +{ + PangoContext *pc; + PangoLanguage *lang; + ImageStore *is; + + struct slide_constants *s_constants; + struct presentation_constants *p_constants; + + struct sc_state *state; + int j; /* Index of the current state */ + int max_state; + + SCCallbackList *cbl; +}; + +struct _sccallbacklist +{ + int n_callbacks; + int max_callbacks; + char **names; + SCCallbackBoxFunc *box_funcs; + SCCallbackDrawFunc *draw_funcs; + SCCallbackClickFunc *click_funcs; + void **vps; +}; + + +static int sc_interp_add_blocks(SCInterpreter *scin, SCBlock *bl, Stylesheet *ss) +{ + while ( bl != NULL ) { + if ( sc_interp_add_block(scin, bl, ss) ) return 1; + bl = sc_block_next(bl); + } + + return 0; +} + + +SCCallbackList *sc_callback_list_new() +{ + SCCallbackList *cbl; + + cbl = malloc(sizeof(struct _sccallbacklist)); + if ( cbl == NULL ) return NULL; + + cbl->names = calloc(8, sizeof(char *)); + if ( cbl->names == NULL ) { + free(cbl); + return NULL; + } + + cbl->box_funcs = calloc(8, sizeof(cbl->box_funcs[0])); + if ( cbl->box_funcs == NULL ) { + free(cbl->names); + free(cbl); + return NULL; + } + + cbl->draw_funcs = calloc(8, sizeof(cbl->draw_funcs[0])); + if ( cbl->draw_funcs == NULL ) { + free(cbl->box_funcs); + free(cbl->names); + free(cbl); + return NULL; + } + + cbl->click_funcs = calloc(8, sizeof(cbl->click_funcs[0])); + if ( cbl->click_funcs == NULL ) { + free(cbl->draw_funcs); + free(cbl->box_funcs); + free(cbl->names); + free(cbl); + return NULL; + } + + cbl->vps = calloc(8, sizeof(cbl->vps[0])); + if ( cbl->vps == NULL ) { + free(cbl->click_funcs); + free(cbl->draw_funcs); + free(cbl->box_funcs); + free(cbl->names); + free(cbl); + return NULL; + } + + cbl->max_callbacks = 8; + cbl->n_callbacks = 0; + + return cbl; +} + + +void sc_callback_list_free(SCCallbackList *cbl) +{ + int i; + + if ( cbl == NULL ) return; + + for ( i=0; i<cbl->n_callbacks; i++ ) { + free(cbl->names[i]); + } + + free(cbl->names); + free(cbl->box_funcs); + free(cbl->draw_funcs); + free(cbl->vps); + free(cbl); + +} + + +void sc_callback_list_add_callback(SCCallbackList *cbl, const char *name, + SCCallbackBoxFunc box_func, + SCCallbackDrawFunc draw_func, + SCCallbackClickFunc click_func, + void *vp) +{ + if ( cbl->n_callbacks == cbl->max_callbacks ) { + + SCCallbackBoxFunc *box_funcs_new; + SCCallbackDrawFunc *draw_funcs_new; + SCCallbackClickFunc *click_funcs_new; + char **names_new; + void **vps_new; + int mcn = cbl->max_callbacks + 8; + + names_new = realloc(cbl->names, mcn*sizeof(char *)); + box_funcs_new = realloc(cbl->box_funcs, + mcn*sizeof(SCCallbackBoxFunc)); + draw_funcs_new = realloc(cbl->draw_funcs, + mcn*sizeof(SCCallbackDrawFunc)); + click_funcs_new = realloc(cbl->click_funcs, + mcn*sizeof(SCCallbackClickFunc)); + vps_new = realloc(cbl->vps, mcn*sizeof(void *)); + + if ( (names_new == NULL) || (box_funcs_new == NULL) + || (vps_new == NULL) || (draw_funcs_new == NULL) + || (click_funcs_new == NULL) ) { + fprintf(stderr, "Failed to grow callback list\n"); + return; + } + + cbl->names = names_new; + cbl->box_funcs = box_funcs_new; + cbl->draw_funcs = draw_funcs_new; + cbl->click_funcs = click_funcs_new; + cbl->vps = vps_new; + cbl->max_callbacks = mcn; + + } + + cbl->names[cbl->n_callbacks] = strdup(name); + cbl->box_funcs[cbl->n_callbacks] = box_func; + cbl->draw_funcs[cbl->n_callbacks] = draw_func; + cbl->click_funcs[cbl->n_callbacks] = click_func; + cbl->vps[cbl->n_callbacks] = vp; + cbl->n_callbacks++; +} + + +void sc_interp_set_callbacks(SCInterpreter *scin, SCCallbackList *cbl) +{ + if ( scin->cbl != NULL ) { + fprintf(stderr, "WARNING: Interpreter already has a callback " + "list.\n"); + } + scin->cbl = cbl; +} + + +static int check_callback(SCInterpreter *scin, SCBlock *bl) +{ + int i; + const char *name = sc_block_name(bl); + SCCallbackList *cbl = scin->cbl; + + /* No callback list -> easy */ + if ( cbl == NULL ) return 0; + + /* No name -> definitely not a callback */ + if ( name == NULL ) return 0; + + for ( i=0; i<cbl->n_callbacks; i++ ) { + + double w, h; + int r; + void *bvp; + + if ( strcmp(cbl->names[i], name) != 0 ) continue; + r = cbl->box_funcs[i](scin, bl, &w, &h, &bvp, cbl->vps[i]); + if ( r ) { + struct sc_state *st = &scin->state[scin->j]; + Paragraph *pnew; + pnew = add_callback_para(sc_interp_get_frame(scin), + bl, w, h, + cbl->draw_funcs[i], + cbl->click_funcs[i], + bvp, cbl->vps[i]); + if ( pnew != NULL ) { + set_para_spacing(pnew, st->paraspace); + } + + } + return 1; + + } + + return 0; +} + + +PangoFontDescription *sc_interp_get_fontdesc(SCInterpreter *scin) +{ + struct sc_state *st = &scin->state[scin->j]; + return st->fontdesc; +} + + +double *sc_interp_get_fgcol(SCInterpreter *scin) +{ + struct sc_state *st = &scin->state[scin->j]; + return st->col; +} + + +static void set_frame_default_style(struct frame *fr, SCInterpreter *scin) +{ + if ( fr == NULL ) return; + + if ( fr->fontdesc != NULL ) { + pango_font_description_free(fr->fontdesc); + } + fr->fontdesc = pango_font_description_copy(sc_interp_get_fontdesc(scin)); + fr->col[0] = sc_interp_get_fgcol(scin)[0]; + fr->col[1] = sc_interp_get_fgcol(scin)[1]; + fr->col[2] = sc_interp_get_fgcol(scin)[2]; + fr->col[3] = sc_interp_get_fgcol(scin)[3]; +} + + +static void update_font(SCInterpreter *scin) +{ + PangoFontMetrics *metrics; + struct sc_state *st = &scin->state[scin->j]; + + if ( scin->pc == NULL ) return; + + st->font = pango_font_map_load_font(pango_context_get_font_map(scin->pc), + scin->pc, st->fontdesc); + if ( st->font == NULL ) { + char *f = pango_font_description_to_string(st->fontdesc); + fprintf(stderr, "Couldn't load font '%s' (font map %p, pc %p)\n", + f, pango_context_get_font_map(scin->pc), scin->pc); + g_free(f); + return; + } + + /* FIXME: Language for box */ + metrics = pango_font_get_metrics(st->font, NULL); + st->ascent = pango_font_metrics_get_ascent(metrics); + st->height = st->ascent + pango_font_metrics_get_descent(metrics); + pango_font_metrics_unref(metrics); + set_frame_default_style(sc_interp_get_frame(scin), scin); +} + + +static void set_font(SCInterpreter *scin, const char *font_name) +{ + struct sc_state *st = &scin->state[scin->j]; + + st->fontdesc = pango_font_description_from_string(font_name); + if ( st->fontdesc == NULL ) { + fprintf(stderr, "Couldn't describe font.\n"); + return; + } + + update_font(scin); +} + + +static void copy_top_fontdesc(SCInterpreter *scin) +{ + struct sc_state *st = &scin->state[scin->j]; + + /* If this is the first stack frame, don't even check */ + if ( scin->j == 0 ) return; + + /* If the fontdesc at the top of the stack is the same as the one + * below, make a copy because we're about to do something to it (which + * should not affect the next level up). */ + if ( st->fontdesc == scin->state[scin->j-1].fontdesc ) { + st->fontdesc = pango_font_description_copy(st->fontdesc); + } +} + + +static void set_fontsize(SCInterpreter *scin, const char *size_str) +{ + struct sc_state *st = &scin->state[scin->j]; + int size; + char *end; + + if ( size_str[0] == '\0' ) return; + + size = strtoul(size_str, &end, 10); + if ( end[0] != '\0' ) { + fprintf(stderr, _("Invalid font size '%s'\n"), size_str); + return; + } + + copy_top_fontdesc(scin); + pango_font_description_set_size(st->fontdesc, size*PANGO_SCALE); + update_font(scin); +} + + +static void set_bold(SCInterpreter *scin) +{ + struct sc_state *st = &scin->state[scin->j]; + copy_top_fontdesc(scin); + pango_font_description_set_weight(st->fontdesc, PANGO_WEIGHT_BOLD); + update_font(scin); +} + + +static void set_oblique(SCInterpreter *scin) +{ + struct sc_state *st = &scin->state[scin->j]; + copy_top_fontdesc(scin); + pango_font_description_set_style(st->fontdesc, PANGO_STYLE_OBLIQUE); + update_font(scin); +} + + +static void set_italic(SCInterpreter *scin) +{ + struct sc_state *st = &scin->state[scin->j]; + copy_top_fontdesc(scin); + pango_font_description_set_style(st->fontdesc, PANGO_STYLE_ITALIC); + update_font(scin); +} + + +static void set_alignment(SCInterpreter *scin, PangoAlignment align) +{ + struct sc_state *st = &scin->state[scin->j]; + st->alignment = align; +} + + +/* This sets the colour for the font at the top of the stack */ +static void set_colour(SCInterpreter *scin, const char *colour) +{ + GdkRGBA col; + struct sc_state *st = &scin->state[scin->j]; + + if ( colour == NULL ) { + printf(_("Invalid colour\n")); + st->col[0] = 0.0; + st->col[1] = 0.0; + st->col[2] = 0.0; + st->col[3] = 1.0; + return; + } + + gdk_rgba_parse(&col, colour); + + st->col[0] = col.red; + st->col[1] = col.green; + st->col[2] = col.blue; + st->col[3] = col.alpha; + set_frame_default_style(sc_interp_get_frame(scin), scin); +} + + +static void set_bgcol(SCInterpreter *scin, const char *colour) +{ + GdkRGBA col; + struct frame *fr = sc_interp_get_frame(scin); + + if ( fr == NULL ) return; + + if ( colour == NULL ) { + printf(_("Invalid colour\n")); + return; + } + + gdk_rgba_parse(&col, colour); + + fr->bgcol[0] = col.red; + fr->bgcol[1] = col.green; + fr->bgcol[2] = col.blue; + fr->bgcol[3] = col.alpha; + fr->grad = GRAD_NONE; +} + + +static void set_bggrad(SCInterpreter *scin, const char *options, + GradientType grad) +{ + struct frame *fr = sc_interp_get_frame(scin); + GdkRGBA col1, col2; + + if ( fr == NULL ) return; + + if ( options == NULL ) { + printf(_("Invalid colour\n")); + return; + } + + if ( parse_colour_duo(options, &col1, &col2) == 0 ) { + + fr->bgcol[0] = col1.red; + fr->bgcol[1] = col1.green; + fr->bgcol[2] = col1.blue; + fr->bgcol[3] = col1.alpha; + + fr->bgcol2[0] = col2.red; + fr->bgcol2[1] = col2.green; + fr->bgcol2[2] = col2.blue; + fr->bgcol2[3] = col2.alpha; + + fr->grad = grad; + + } +} + + +static char *get_constant(SCInterpreter *scin, unsigned int constant) +{ + struct sc_state *st = &scin->state[scin->j]; + if ( constant >= NUM_SC_CONSTANTS ) return NULL; + return st->constants[constant]; +} + + +void sc_interp_set_constant(SCInterpreter *scin, unsigned int constant, + const char *val) +{ + struct sc_state *st = &scin->state[scin->j]; + if ( constant >= NUM_SC_CONSTANTS ) return; + if ( val == NULL ) return; + st->constants[constant] = strdup(val); +} + + +void sc_interp_save(SCInterpreter *scin) +{ + if ( scin->j+1 == scin->max_state ) { + + struct sc_state *stack_new; + + stack_new = realloc(scin->state, sizeof(struct sc_state) + * (scin->max_state+8)); + if ( stack_new == NULL ) { + fprintf(stderr, "Failed to add to stack.\n"); + return; + } + + scin->state = stack_new; + scin->max_state += 8; + + } + + /* When n_fonts=0, we leave the first font uninitialised. This allows + * the stack to be "bootstrapped", but requires the first caller to do + * set_font and set_colour straight away. */ + scin->state[scin->j+1] = scin->state[scin->j]; + + scin->j++; +} + + +void sc_interp_restore(SCInterpreter *scin) +{ + struct sc_state *st = &scin->state[scin->j]; + + if ( scin->j > 0 ) { + + int i; + + if ( st->fontdesc != scin->state[scin->j-1].fontdesc ) + { + pango_font_description_free(st->fontdesc); + } /* else the font is the same as the previous one, and we + * don't need to free it just yet */ + + for ( i=0; i<NUM_SC_CONSTANTS; i++ ) { + if ( st->constants[i] != scin->state[scin->j-1].constants[i] ) { + free(st->constants[i]); + } /* same logic as above */ + } + } + + scin->j--; +} + + +struct frame *sc_interp_get_frame(SCInterpreter *scin) +{ + struct sc_state *st = &scin->state[scin->j]; + return st->fr; +} + + +static void set_frame(SCInterpreter *scin, struct frame *fr) +{ + struct sc_state *st = &scin->state[scin->j]; + st->fr = fr; +} + + +SCInterpreter *sc_interp_new(PangoContext *pc, PangoLanguage *lang, + ImageStore *is, struct frame *top) +{ + SCInterpreter *scin; + struct sc_state *st; + int i; + + scin = malloc(sizeof(SCInterpreter)); + if ( scin == NULL ) return NULL; + + scin->state = malloc(8*sizeof(struct sc_state)); + if ( scin->state == NULL ) { + free(scin); + return NULL; + } + scin->j = 0; + scin->max_state = 8; + + scin->pc = pc; + scin->is = is; + scin->s_constants = NULL; + scin->p_constants = NULL; + scin->cbl = NULL; + scin->lang = lang; + + /* Initial state */ + st = &scin->state[0]; + st->fr = NULL; + st->paraspace[0] = 0.0; + st->paraspace[1] = 0.0; + st->paraspace[2] = 0.0; + st->paraspace[3] = 0.0; + st->fontdesc = NULL; + st->col[0] = 0.0; + st->col[1] = 0.0; + st->col[2] = 0.0; + st->col[3] = 1.0; + st->alignment = PANGO_ALIGN_LEFT; + for ( i=0; i<NUM_SC_CONSTANTS; i++ ) { + st->constants[i] = NULL; + } + + /* The "ultimate" default font */ + if ( scin->pc != NULL ) { + set_frame(scin, top); + set_font(scin, "Cantarell Regular 14"); + set_colour(scin, "#000000"); + } + + return scin; +} + + +void sc_interp_destroy(SCInterpreter *scin) +{ + /* Empty the stack */ + while ( scin->j > 0 ) { + sc_interp_restore(scin); + } + + if ( scin->state[0].fontdesc != NULL ) { + pango_font_description_free(scin->state[0].fontdesc); + } + + free(scin->state); + free(scin); +} + + +static void set_padding(struct frame *fr, const char *opts) +{ + float p[4]; + + if ( parse_tuple(opts, p) ) return; + + if ( fr == NULL ) return; + + fr->pad_l = p[0]; + fr->pad_r = p[1]; + fr->pad_t = p[2]; + fr->pad_b = p[3]; +} + + +static void set_paraspace(SCInterpreter *scin, const char *opts) +{ + float p[4]; + struct sc_state *st = &scin->state[scin->j]; + + if ( parse_tuple(opts, p) ) return; + + st->paraspace[0] = p[0]; + st->paraspace[1] = p[1]; + st->paraspace[2] = p[2]; + st->paraspace[3] = p[3]; + + set_para_spacing(last_para(sc_interp_get_frame(scin)), p); +} + + +void update_geom(struct frame *fr) +{ + char geom[256]; + snprintf(geom, 255, "%.1fux%.1fu+%.1f+%.1f", + fr->w, fr->h, fr->x, fr->y); + + /* FIXME: What if there are other options? */ + sc_block_set_options(fr->scblocks, strdup(geom)); +} + + +static int calculate_dims(const char *opt, struct frame *parent, + double *wp, double *hp, double *xp, double *yp) +{ + LengthUnits h_units, w_units; + + if ( parse_dims(opt, wp, hp, &w_units, &h_units, xp, yp) ) { + return 1; + } + + if ( w_units == UNITS_FRAC ) { + if ( parent != NULL ) { + double pw = parent->w; + pw -= parent->pad_l; + pw -= parent->pad_r; + *wp = pw * *wp; + } else { + *wp = -1.0; + } + + } + if ( h_units == UNITS_FRAC ) { + if ( parent != NULL ) { + double ph = parent->h; + ph -= parent->pad_t; + ph -= parent->pad_b; + *hp = ph * *hp; + } else { + *hp = -1.0; + } + } + + return 0; +} + + +static int parse_frame_option(const char *opt, struct frame *fr, + struct frame *parent) +{ + if ( (index(opt, 'x') != NULL) && (index(opt, '+') != NULL) + && (index(opt, '+') != rindex(opt, '+')) ) { + return calculate_dims(opt, parent, &fr->w, &fr->h, &fr->x, &fr->y); + } + + fprintf(stderr, _("Unrecognised frame option '%s'\n"), opt); + + return 1; +} + + +static int parse_frame_options(struct frame *fr, struct frame *parent, + const char *opth) +{ + int i; + size_t len; + size_t start; + char *opt; + + if ( opth == NULL ) return 1; + + opt = strdup(opth); + + len = strlen(opt); + start = 0; + + for ( i=0; i<len; i++ ) { + + /* FIXME: comma might be escaped or quoted */ + if ( opt[i] == ',' ) { + opt[i] = '\0'; + if ( parse_frame_option(opt+start, fr, parent) ) { + return 1; + } + start = i+1; + } + + } + + if ( start != len ) { + if ( parse_frame_option(opt+start, fr, parent) ) return 1; + } + + free(opt); + + return 0; +} + + +static int parse_image_option(const char *opt, struct frame *parent, + double *wp, double *hp, char **filenamep) +{ + if ( (index(opt, 'x') != NULL) && (index(opt, '+') != NULL) + && (index(opt, '+') != rindex(opt, '+')) ) { + double dum; + return calculate_dims(opt, NULL, wp, hp, &dum, &dum); + } + + if ( strncmp(opt, "filename=\"", 10) == 0 ) { + char *fn; + fn = strdup(opt+10); + if ( fn[strlen(fn)-1] != '\"' ) { + fprintf(stderr, "Unterminated filename?\n"); + free(fn); + return 1; + } + fn[strlen(fn)-1] = '\0'; + *filenamep = fn; + return 0; + } + + fprintf(stderr, _("Unrecognised image option '%s'\n"), opt); + + return 1; +} + + +static int parse_image_options(const char *opth, struct frame *parent, + double *wp, double *hp, char **filenamep) +{ + int i; + size_t len; + size_t start; + char *opt; + + if ( opth == NULL ) return 1; + + opt = strdup(opth); + + len = strlen(opt); + start = 0; + + for ( i=0; i<len; i++ ) { + + /* FIXME: comma might be escaped or quoted */ + if ( opt[i] == ',' ) { + opt[i] = '\0'; + if ( parse_image_option(opt+start, parent, + wp, hp, filenamep) ) return 1; + start = i+1; + } + + } + + if ( start != len ) { + if ( parse_image_option(opt+start, parent, + wp, hp, filenamep) ) return 1; + } + + free(opt); + + return 0; +} + + +static void maybe_recurse_before(SCInterpreter *scin, SCBlock *child) +{ + if ( child == NULL ) return; + + sc_interp_save(scin); +} + + +static void maybe_recurse_after(SCInterpreter *scin, SCBlock *child, + Stylesheet *ss) +{ + if ( child == NULL ) return; + + sc_interp_add_blocks(scin, child, ss); + sc_interp_restore(scin); +} + + +static void add_newpara(SCBlock *bl, SCInterpreter *scin) +{ + Paragraph *last_para; + Paragraph *para; + struct sc_state *st = &scin->state[scin->j]; + struct frame *fr = sc_interp_get_frame(scin); + + if ( fr->paras == NULL ) return; + last_para = fr->paras[fr->n_paras-1]; + + set_newline_at_end(last_para, bl); + + /* The block after the \newpara will always be the first one of the + * next paragraph, by definition, even if it's \f or another \newpara */ + para = create_paragraph(fr, sc_block_next(bl)); + set_para_alignment(para, st->alignment); + set_para_spacing(para, st->paraspace); +} + + +/* Add the SCBlock to the text in 'frame', at the end */ +static int add_text(struct frame *fr, PangoContext *pc, SCBlock *bl, + PangoLanguage *lang, int editable, SCInterpreter *scin, + const char *real_text) +{ + const char *text = sc_block_contents(bl); + PangoFontDescription *fontdesc; + double *col; + struct sc_state *st = &scin->state[scin->j]; + Paragraph *para; + + /* Empty block? */ + if ( text == NULL && real_text == NULL ) return 1; + + fontdesc = sc_interp_get_fontdesc(scin); + col = sc_interp_get_fgcol(scin); + + para = last_para(fr); + if ( (para == NULL) || (para_type(para) != PARA_TYPE_TEXT) ) { + /* Last paragraph is not text. + * or: no paragraphs yet. + * Either way: Create the first one */ + para = create_paragraph(fr, bl); + } + + set_para_alignment(para, st->alignment); + add_run(para, bl, fontdesc, col, real_text); + set_para_spacing(para, st->paraspace); + + return 0; +} + + +static void apply_style(SCInterpreter *scin, Stylesheet *ss, const char *path) +{ + char *result; + + if ( ss == NULL ) return; + + /* Font */ + result = stylesheet_lookup(ss, path, "font"); + if ( result != NULL ) set_font(scin, result); + + /* Foreground colour */ + result = stylesheet_lookup(ss, path, "fgcol"); + if ( result != NULL ) set_colour(scin, result); + + /* Background (vertical gradient) */ + result = stylesheet_lookup(ss, path, "bggradv"); + if ( result != NULL ) set_bggrad(scin, result, GRAD_VERT); + + /* Background (horizontal gradient) */ + result = stylesheet_lookup(ss, path, "bggradh"); + if ( result != NULL ) set_bggrad(scin, result, GRAD_HORIZ); + + /* Background (solid colour) */ + result = stylesheet_lookup(ss, path, "bgcol"); + if ( result != NULL ) set_bgcol(scin, result); + + /* Padding */ + result = stylesheet_lookup(ss, path, "pad"); + if ( result != NULL ) set_padding(sc_interp_get_frame(scin), result); + + /* Paragraph spacing */ + result = stylesheet_lookup(ss, path, "paraspace"); + if ( result != NULL ) set_paraspace(scin, result); + + /* Alignment */ + result = stylesheet_lookup(ss, path, "alignment"); + if ( result != NULL ) { + if ( strcmp(result, "center") == 0 ) { + set_alignment(scin, PANGO_ALIGN_CENTER); + } + if ( strcmp(result, "left") == 0 ) { + set_alignment(scin, PANGO_ALIGN_LEFT); + } + if ( strcmp(result, "right") == 0 ) { + set_alignment(scin, PANGO_ALIGN_RIGHT); + } + } +} + + +static void output_frame(SCInterpreter *scin, SCBlock *bl, Stylesheet *ss, + const char *stylename) +{ + struct frame *fr; + SCBlock *child = sc_block_child(bl); + const char *options = sc_block_options(bl); + char *result; + + fr = add_subframe(sc_interp_get_frame(scin)); + if ( fr == NULL ) { + fprintf(stderr, _("Failed to add frame.\n")); + return; + } + + fr->scblocks = bl; + fr->resizable = 1; + + /* Lowest priority: current state of interpreter */ + set_frame_default_style(fr, scin); + + /* Next priority: geometry from stylesheet */ + result = stylesheet_lookup(ss, stylename, "geometry"); + if ( result != NULL ) { + parse_frame_options(fr, sc_interp_get_frame(scin), result); + } + + /* Highest priority: parameters to \f (or \slidetitle etc) */ + parse_frame_options(fr, sc_interp_get_frame(scin), options); + + maybe_recurse_before(scin, child); + set_frame(scin, fr); + apply_style(scin, ss, stylename); + maybe_recurse_after(scin, child, ss); +} + + +static int check_outputs(SCBlock *bl, SCInterpreter *scin, Stylesheet *ss) +{ + const char *name = sc_block_name(bl); + const char *options = sc_block_options(bl); + + if ( name == NULL ) { + add_text(sc_interp_get_frame(scin), + scin->pc, bl, scin->lang, 1, scin, NULL); + + } else if ( strcmp(name, "image")==0 ) { + double w, h; + char *filename; + if ( parse_image_options(options, sc_interp_get_frame(scin), + &w, &h, &filename) == 0 ) + { + add_image_para(sc_interp_get_frame(scin), bl, + filename, scin->is, w, h, 1); + free(filename); + } else { + fprintf(stderr, _("Invalid image options '%s'\n"), + options); + } + + } else if ( strcmp(name, "f")==0 ) { + output_frame(scin, bl, ss, "$.slide.frame"); + + } else if ( strcmp(name, "slidetitle")==0 ) { + output_frame(scin, bl, ss, "$.slide.slidetitle"); + + } else if ( strcmp(name, "prestitle")==0 ) { + output_frame(scin, bl, ss, "$.slide.prestitle"); + + } else if ( strcmp(name, "author")==0 ) { + output_frame(scin, bl, ss, "$.slide.author"); + + } else if ( strcmp(name, "footer")==0 ) { + output_frame(scin, bl, ss, "$.slide.footer"); + + } else if ( strcmp(name, "newpara")==0 ) { + add_newpara(bl, scin); + + } else if ( strcmp(name, "slidenumber")==0 ) { + char *con = get_constant(scin, SCCONST_SLIDENUMBER); + if ( con != NULL ) { + add_text(sc_interp_get_frame(scin), scin->pc, bl, + scin->lang, 1, scin, con); + } + + } else { + return 0; + } + + return 1; /* handled */ +} + + +int sc_interp_add_block(SCInterpreter *scin, SCBlock *bl, Stylesheet *ss) +{ + const char *name = sc_block_name(bl); + const char *options = sc_block_options(bl); + SCBlock *child = sc_block_child(bl); + + //printf("Running this --------->\n"); + //show_sc_blocks(bl); + //printf("<------------\n"); + + if ( check_callback(scin, bl) ) { + /* Handled in check_callback, don't do anything else */ + + } else if ((sc_interp_get_frame(scin) != NULL) + && check_outputs(bl, scin, ss) ) { + /* Block handled as output thing */ + + } else if ( name == NULL ) { + /* Dummy to ensure name != NULL below */ + + } else if ( strcmp(name, "presentation") == 0 ) { + maybe_recurse_before(scin, child); + apply_style(scin, ss, "$.narrative"); + maybe_recurse_after(scin, child, ss); + + } else if ( strcmp(name, "slide") == 0 ) { + maybe_recurse_before(scin, child); + apply_style(scin, ss, "$.slide"); + maybe_recurse_after(scin, child, ss); + + } else if ( strcmp(name, "font") == 0 ) { + maybe_recurse_before(scin, child); + set_font(scin, options); + maybe_recurse_after(scin, child, ss); + + } else if ( strcmp(name, "fontsize") == 0 ) { + maybe_recurse_before(scin, child); + set_fontsize(scin, options); + maybe_recurse_after(scin, child, ss); + + } else if ( strcmp(name, "bold") == 0 ) { + maybe_recurse_before(scin, child); + set_bold(scin); + maybe_recurse_after(scin, child, ss); + + } else if ( strcmp(name, "oblique") == 0 ) { + maybe_recurse_before(scin, child); + set_oblique(scin); + maybe_recurse_after(scin, child, ss); + + } else if ( strcmp(name, "italic") == 0 ) { + maybe_recurse_before(scin, child); + set_italic(scin); + maybe_recurse_after(scin, child, ss); + + } else if ( strcmp(name, "lalign") == 0 ) { + maybe_recurse_before(scin, child); + set_alignment(scin, PANGO_ALIGN_LEFT); + maybe_recurse_after(scin, child, ss); + + } else if ( strcmp(name, "ralign") == 0 ) { + maybe_recurse_before(scin, child); + set_alignment(scin, PANGO_ALIGN_RIGHT); + maybe_recurse_after(scin, child, ss); + + } else if ( strcmp(name, "center") == 0 ) { + maybe_recurse_before(scin, child); + set_alignment(scin, PANGO_ALIGN_CENTER); + maybe_recurse_after(scin, child, ss); + + } else if ( strcmp(name, "fgcol") == 0 ) { + maybe_recurse_before(scin, child); + set_colour(scin, options); + maybe_recurse_after(scin, child, ss); + + } else if ( strcmp(name, "pad") == 0 ) { + maybe_recurse_before(scin, child); + set_padding(sc_interp_get_frame(scin), options); + maybe_recurse_after(scin, child, ss); + + } else if ( strcmp(name, "bgcol") == 0 ) { + set_bgcol(scin, options); + + } else if ( strcmp(name, "bggradh") == 0 ) { + set_bggrad(scin, options, GRAD_HORIZ); + + } else if ( strcmp(name, "bggradv") == 0 ) { + set_bggrad(scin, options, GRAD_VERT); + + } else if ( strcmp(name, "paraspace") == 0 ) { + maybe_recurse_before(scin, child); + set_paraspace(scin, options); + maybe_recurse_after(scin, child, ss); + + } else { + + fprintf(stderr, "Don't know what to do with this:\n"); + show_sc_block(bl, ""); + + } + + return 0; +} + diff --git a/src-old/sc_interp.h b/src-old/sc_interp.h new file mode 100644 index 0000000..764b532 --- /dev/null +++ b/src-old/sc_interp.h @@ -0,0 +1,80 @@ +/* + * sc_interp.h + * + * Copyright © 2014-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 SC_INTERP_H +#define SC_INTERP_H + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pango/pangocairo.h> + +struct frame; + +#define SCCONST_SLIDENUMBER (0) +#define NUM_SC_CONSTANTS (1) + +struct presentation; +typedef struct _scinterp SCInterpreter; +typedef struct _sccallbacklist SCCallbackList; +typedef int (*SCCallbackBoxFunc)(SCInterpreter *scin, SCBlock *bl, + double *w, double *h, void **, void *); +typedef cairo_surface_t *(*SCCallbackDrawFunc)(int w, int h, void *, void *); +typedef int (*SCCallbackClickFunc)(double x, double y, void *, void *); + +#include "frame.h" +#include "imagestore.h" +#include "stylesheet.h" + +extern SCInterpreter *sc_interp_new(PangoContext *pc, PangoLanguage *lang, + ImageStore *is, struct frame *top); +extern void sc_interp_destroy(SCInterpreter *scin); + +extern void sc_interp_save(SCInterpreter *scin); +extern void sc_interp_restore(SCInterpreter *scin); + +extern int sc_interp_add_block(SCInterpreter *scin, SCBlock *bl, Stylesheet *ss); + +extern void sc_interp_set_constant(SCInterpreter *scin, unsigned int constant, + const char *val); + +/* Callback lists */ +extern SCCallbackList *sc_callback_list_new(); +extern void sc_callback_list_free(SCCallbackList *cbl); +extern void sc_callback_list_add_callback(SCCallbackList *cbl, const char *name, + SCCallbackBoxFunc box_func, + SCCallbackDrawFunc draw_func, + SCCallbackClickFunc click_func, + void *vp); +extern void sc_interp_set_callbacks(SCInterpreter *scin, SCCallbackList *cbl); + +/* Get the current state of the interpreter */ +extern struct frame *sc_interp_get_frame(SCInterpreter *scin); +extern PangoFont *sc_interp_get_font(SCInterpreter *scin); +extern PangoFontDescription *sc_interp_get_fontdesc(SCInterpreter *scin); +extern double *sc_interp_get_fgcol(SCInterpreter *scin); + +extern void update_geom(struct frame *fr); + + +#endif /* SC_INTERP_H */ diff --git a/src-old/sc_parse.c b/src-old/sc_parse.c new file mode 100644 index 0000000..78f5799 --- /dev/null +++ b/src-old/sc_parse.c @@ -0,0 +1,856 @@ +/* + * sc_parse.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 <assert.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <ctype.h> +#include <glib.h> +#include <gio/gio.h> + +#include "sc_parse.h" +#include "utils.h" + +struct _scblock +{ + char *name; + char *options; + char *contents; + + SCBlock *next; + SCBlock *child; +}; + + +SCBlock *sc_block_new() +{ + SCBlock *bl; + + bl = calloc(1, sizeof(SCBlock)); + if ( bl == NULL ) return NULL; + + return bl; +} + + +SCBlock *sc_block_next(const SCBlock *bl) +{ + assert(bl != NULL); + return bl->next; +} + + +SCBlock *sc_block_child(const SCBlock *bl) +{ + return bl->child; +} + + +const char *sc_block_name(const SCBlock *bl) +{ + return bl->name; +} + + +const char *sc_block_options(const SCBlock *bl) +{ + return bl->options; +} + + +const char *sc_block_contents(const SCBlock *bl) +{ + return bl->contents; +} + + +static SCBlock *sc_find_previous(SCBlock *top, SCBlock *find) +{ + if ( top->next == find ) return top; + + if ( top->child != NULL ) { + SCBlock *t = sc_find_previous(top->child, find); + if ( t != NULL ) return t; + } + if ( top->next != NULL ) { + SCBlock *t = sc_find_previous(top->next, find); + if ( t != NULL ) return t; + } + return NULL; +} + + +/* Add a new block before "bl" */ +SCBlock *sc_block_prepend(SCBlock *bl, SCBlock *top) +{ + SCBlock *bln; + SCBlock *prev; + + prev = sc_find_previous(top, bl); + if ( prev == NULL ) { + fprintf(stderr, "Couldn't find previous\n"); + return NULL; + } + + bln = sc_block_new(); + if ( bln == NULL ) return NULL; + + prev->next = bln; + bln->next = bl; + return bln; +} + + +/* Append "bln" after "bl" */ +void sc_block_append_p(SCBlock *bl, SCBlock *bln) +{ + if ( bl != NULL ) { + bln->next = bl->next; + bl->next = bln; + } +} + + +/* Insert a new block after "bl". "name", "options" and "contents" + * will not be copied. Returns the block just created, or NULL on error. + * If *blfp points to NULL, it will updated to point at the new block. */ +SCBlock *sc_block_append(SCBlock *bl, char *name, char *opt, char *contents, + SCBlock **blfp) +{ + SCBlock *bln = sc_block_new(); + + if ( bln == NULL ) return NULL; + + bln->name = name; + bln->options = opt; + bln->contents = contents; + bln->child = NULL; + bln->next = NULL; + + sc_block_append_p(bl, bln); + + if ( (blfp != NULL) && (*blfp == NULL) ) { + *blfp = bln; + } + + return bln; +} + + +/* Insert a new block at the end of the chain starting 'bl'. + * "name", "options" and "contents" will not be copied. Returns the block just + * created, or NULL on error. */ +SCBlock *sc_block_append_end(SCBlock *bl, char *name, char *opt, char *contents) +{ + SCBlock *bln = sc_block_new(); + + if ( bln == NULL ) return NULL; + + while ( bl->next != NULL ) { + bln->next = bl->next; + bl = bl->next; + }; + + return sc_block_append(bl, name, opt, contents, NULL); +} + + +void sc_block_append_block(SCBlock *bl, SCBlock *bln) +{ + + if ( bl == NULL ) return; + + while ( bl->next != NULL ) bl = bl->next; + + bl->next = bln; + bln->next = NULL; +} + + +/* Append a new block to the chain inside "parent". + * "name", "options" and "contents" will not be copied. Returns the block just + * created, or NULL on error. */ +SCBlock *sc_block_append_inside(SCBlock *parent, + char *name, char *opt, char *contents) +{ + SCBlock *bln; + SCBlock *bl; + SCBlock **ptr; + + bl = parent->child; + if ( bl != NULL ) { + while ( bl->next != NULL ) bl = bl->next; + ptr = &bl->next; + } else { + ptr = &parent->child; + } + + bln = sc_block_new(); + if ( bln == NULL ) return NULL; + + bln->name = name; + bln->options = opt; + bln->contents = contents; + bln->child = NULL; + bln->next = NULL; + + *ptr = bln; + + return bln; +} + + +/* Insert a new block to the chain, just after "afterme". + * "name", "options" and "contents" will not be copied. Returns the block just + * created, or NULL on error. */ +SCBlock *sc_block_insert_after(SCBlock *afterme, + char *name, char *opt, char *contents) +{ + SCBlock *bl; + + bl = sc_block_new(); + if ( bl == NULL ) return NULL; + + bl->name = name; + bl->options = opt; + bl->contents = contents; + bl->child = NULL; + bl->next = afterme->next; + afterme->next = bl; + + return bl; +} + + +static SCBlock *sc_find_parent(SCBlock *top, SCBlock *find) +{ + if ( top->child == find ) return top; + if ( top->next == find ) return top; + + if ( top->child != NULL ) { + SCBlock *t = sc_find_parent(top->child, find); + if ( t != NULL ) return t; + } + if ( top->next != NULL ) { + SCBlock *t = sc_find_parent(top->next, find); + if ( t != NULL ) return t; + } + return NULL; +} + + +/* Unlink "deleteme", which is somewhere under "top" */ +static int sc_block_unlink(SCBlock **top, SCBlock *deleteme) +{ + SCBlock *parent = sc_find_parent(*top, deleteme); + if ( parent == NULL ) { + /* Maybe it's the first block? */ + if ( *top == deleteme ) { + fprintf(stderr, "Unlinking at top\n"); + *top = (*top)->next; + return 0; + } else { + fprintf(stderr, "Couldn't find block parent!\n"); + return 1; + } + } + + if ( parent->next == deleteme ) { + parent->next = deleteme->next; + } + + if ( parent->child == deleteme ) { + parent->child = deleteme->next; + } + return 0; +} + + +/* Delete "deleteme", which is somewhere under "top" */ +int sc_block_delete(SCBlock **top, SCBlock *deleteme) +{ + int r; + r = sc_block_unlink(top, deleteme); + if ( !r ) { + sc_block_free(deleteme); + } + return r; +} + + +/* Frees "bl" and all its children (but not the blocks following it) */ +void sc_block_free(SCBlock *bl) +{ + if ( bl == NULL ) return; + + if ( bl->child != NULL ) { + SCBlock *ch = bl->child; + while ( ch != NULL ) { + SCBlock *next = ch->next; + sc_block_free(ch); + ch = next; + } + } + + free(bl); +} + + +/* Serialise one block (including children) */ +char *serialise_sc_block(const SCBlock *bl) +{ + char *a; + SCBlock *ch; + size_t len = 3; + + if ( bl == NULL ) return strdup(""); + + if ( bl->name != NULL ) len += 1+strlen(bl->name); + if ( bl->options != NULL ) len += 2+strlen(bl->options); + if ( bl->contents != NULL ) len += 2+strlen(bl->contents); + a = malloc(len); + if ( a == NULL ) return NULL; + a[0] = '\0'; + + if ( bl->name == NULL ) { + strcat(a, bl->contents); + } else if ( strcmp(bl->name, "newpara") == 0 ) { + strcat(a, "\n"); + + } else { + + strcat(a, "\\"); + strcat(a, bl->name); + if ( bl->options != NULL ) { + strcat(a, "["); + strcat(a, bl->options); + strcat(a, "]"); + } + if ( (bl->contents != NULL) || (bl->child != NULL) ) { + strcat(a, "{"); + } + if ( bl->contents != NULL ) { + strcat(a, bl->contents); + } + + /* Special case to prevent "\somethingSome text" */ + if ( (bl->name != NULL) && (bl->options == NULL) + && (bl->contents == NULL) && (bl->next != NULL) + && (bl->next->name == NULL) && (bl->child == NULL) ) + { + strcat(a, "{}"); + } + + } + + /* Add ALL child blocks of this one */ + ch = bl->child; + while ( ch != NULL ) { + + char *anew; + char *c = serialise_sc_block(ch); + if ( c == NULL ) { + free(a); + return NULL; + } + + len += strlen(c); + + anew = realloc(a, len); + if ( anew == NULL ) { + return NULL; + } else { + a = anew; + } + + strcat(a, c); + free(c); + + ch = ch->next; + + } + + if ( (bl->name != NULL) && + ((bl->contents != NULL) || (bl->child != NULL)) ) { + strcat(a, "}"); + } + + return a; +} + + +int save_sc_block(GOutputStream *fh, const SCBlock *bl) +{ + while ( bl != NULL ) { + GError *error = NULL; + char *a = serialise_sc_block(bl); + gssize r; + if ( a == NULL ) { + fprintf(stderr, "Failed to serialise block\n"); + return 1; + } + r = g_output_stream_write(fh, a, strlen(a), NULL, &error); + if ( r == -1 ) { + fprintf(stderr, "Write failed: %s\n", error->message); + return 1; + } + free(a); + bl = bl->next; + } + return 0; +} + + +static void recursive_show_sc_blocks(const char *prefix, const SCBlock *bl) +{ + while ( bl != NULL ) { + show_sc_block(bl, prefix); + bl = bl->next; + } +} + + +void show_sc_block(const SCBlock *bl, const char *prefix) +{ + printf("%s (%p) ", prefix, bl); + if ( bl == NULL ) return; + if ( bl->name != NULL ) printf("\\%s ", bl->name); + if ( bl->options != NULL ) printf("[%s] ", bl->options); + if ( bl->contents != NULL ) printf("{%s} ", bl->contents); + printf("\n"); + + if ( bl->child != NULL ) { + char new_prefix[strlen(prefix)+3]; + strcpy(new_prefix, " "); + strcat(new_prefix, prefix); + recursive_show_sc_blocks(new_prefix, bl->child); + } +} + + +void show_sc_blocks(const SCBlock *bl) +{ + recursive_show_sc_blocks("", bl); +} + + +static int get_subexpr(const char *sc, char *bk, char **pcontents, int *err) +{ + size_t ml; + int i; + int bct = 1; + int found = 0; + char *contents; + + *err = 0; + + ml = strlen(sc); + contents = malloc(ml+1); + if ( contents == NULL ) { + *err = -1; + return 0; + } + *pcontents = contents; + + for ( i=0; i<ml; i++ ) { + if ( sc[i] == bk[0] ) { + bct++; + } else if ( sc[i] == bk[1] ) { + bct--; + } + if ( bct == 0 ) { + found = 1; + break; + } + contents[i] = sc[i]; + } + + if ( (!found) || (bct != 0) ) { + *err = 1; + return 0; + } + + contents[i] = '\0'; + return i+1; +} + + +static size_t read_block(const char *sc, char **pname, char **options, + char **contents, int *err) +{ + size_t l, i, j; + char *name; + int done; + + *err = 0; + + l = strlen(sc); + i = 0; j = 0; + name = malloc(l+1); + if ( name == NULL ) { + *err = 1; + return 0; + } + + done = 0; + do { + + char c = sc[i]; + + if ( isalnum(c) ) { + name[j++] = c; + i++; + } else { + /* Found the end of the name */ + done = 1; + } + + } while ( !done && (i<l) ); + + name[j] = '\0'; + *pname = name; + + if ( sc[i] == '[' ) { + + i += get_subexpr(sc+i+1, "[]", options, err) + 1; + if ( *err ) { + printf("Couldn't find end of options '%s'\n", sc+i); + return 0; + } + + } else { + *options = NULL; + } + + if ( sc[i] == '{' ) { + + i += get_subexpr(sc+i+1, "{}", contents, err) + 1; + if ( *err ) { + printf("Couldn't find end of content '%s'\n", sc+i); + return 0; + } + + } else { + *contents = NULL; + } + + return i+1; +} + + +static void separate_newlines(SCBlock *bl) +{ + while ( bl != NULL ) { + + const char *contents = sc_block_contents(bl); + + if ( contents != NULL ) { + char *npos = strchr(contents, '\n'); + if ( npos != NULL ) { + SCBlock *nb = NULL; + if ( npos == contents ) { + bl->name = strdup("newpara"); + bl->contents = NULL; + nb = bl; + } else { + sc_block_append(bl, strdup("newpara"), + NULL, NULL, &nb); + } + + /* Add any text after the \n */ + if ( strlen(npos+1) > 0 ) { + sc_block_append(nb, NULL, NULL, + strdup(npos+1), &nb); + } + npos[0] = '\0'; + } + } + + if ( sc_block_child(bl) != NULL ) { + separate_newlines(sc_block_child(bl)); + } + + bl = sc_block_next(bl); + + } +} + + +SCBlock *sc_parse(const char *sc) +{ + SCBlock *bl; + SCBlock *blf = NULL; + char *tbuf; + size_t len, i, j; + + if ( sc == NULL ) return NULL; + + if ( strlen(sc) == 0 ) { + SCBlock *bl = sc_block_new(); + sc_block_set_contents(bl, g_strdup("")); + return bl; + } + + bl = NULL; + + len = strlen(sc); + tbuf = malloc(len+1); + if ( tbuf == NULL ) { + sc_block_free(bl); + return NULL; + } + + i = 0; j = 0; + do { + + if ( sc[i] == '\\' ) { + + int err; + char *name = NULL; + char *opt = NULL; + char *contents = NULL; + + /* Is this an escaped backslash? */ + if ( sc[i+1] == '\\' ) { + tbuf[j++] = '\\'; + i += 2; /* Skip both backslashes */ + continue; + } + + /* No, it's a real block. Dispatch the previous block */ + if ( j != 0 ) { + tbuf[j] = '\0'; + bl = sc_block_append(bl, NULL, NULL, + strdup(tbuf), &blf); + if ( bl == NULL ) { + fprintf(stderr, "Block add failed.\n"); + sc_block_free(blf); + free(tbuf); + return NULL; + } + j = 0; + } + + i += read_block(sc+i+1, &name, &opt, &contents, &err); + if ( err ) { + printf(_("Parse error\n")); + sc_block_free(blf); + free(tbuf); + return NULL; + } + + bl = sc_block_append(bl, name, opt, contents, &blf); + if ( bl == NULL ) { + fprintf(stderr, "Block add failed.\n"); + sc_block_free(blf); + free(tbuf); + return NULL; + } + bl->child = sc_parse(contents); + free(bl->contents); + bl->contents = NULL; + + } else { + + tbuf[j++] = sc[i++]; + } + + } while ( i<len ); + + /* Add final block, if it exists */ + if ( j > 0 ) { + + /* Leftover buffer is empty? */ + if ( (j==1) && (tbuf[0]=='\0') ) return bl; + + tbuf[j] = '\0'; + bl = sc_block_append(bl, NULL, NULL, tbuf, &blf); + if ( bl == NULL ) { + fprintf(stderr, "Block add failed.\n"); + sc_block_free(blf); + free(tbuf); + return NULL; + } + j = 0; + } + + separate_newlines(blf); + + return blf; +} + + +void sc_block_set_name(SCBlock *bl, char *nam) +{ + if ( bl == NULL ) { + fprintf(stderr, "sc_block_set_name: NULL block\n"); + return; + } + free(bl->name); + bl->name = nam; +} + + +void sc_block_set_options(SCBlock *bl, char *opt) +{ + free(bl->options); + bl->options = opt; +} + + +void sc_block_set_contents(SCBlock *bl, char *con) +{ + g_free(bl->contents); + bl->contents = con; +} + + +void sc_insert_text(SCBlock *b1, size_t o1, const char *t) +{ + size_t len; + char *cnew; + char *tmp; + char *p1; + + if ( b1->contents == NULL ) { + b1->contents = strdup(t); + return; + } + len = strlen(b1->contents)+1+strlen(t); + + cnew = realloc(b1->contents, len); + if ( cnew == NULL ) return; + + tmp = malloc(len); + if ( tmp == NULL ) { + free(cnew); + return; + } + + p1 = cnew + o1; + strcpy(tmp, p1); + strcpy(p1, t); + strcpy(p1+strlen(t), tmp); + free(tmp); + b1->contents = cnew; +} + + +void sc_insert_block(SCBlock *b1, int o1, SCBlock *ins) +{ + SCBlock *second; + char *p1 = g_utf8_offset_to_pointer(b1->contents, o1); + + /* Create a new block containing the second half of b1 */ + second = sc_block_new(); + sc_block_set_contents(second, g_strdup(p1)); + + /* Chop off b1 at the insertion point */ + sc_block_set_contents(b1, g_utf8_substring(b1->contents, 0, o1)); + + /* Link the new block into the chain */ + SCBlock *old_next = b1->next; + b1->next = ins; + ins->next = second; + second->next = old_next; +} + + +/* Delete text from SCBlock contents. o2=-1 means "to the end". + * Returns the number of bytes deleted. */ +size_t scblock_delete_text(SCBlock *b, ssize_t o1, ssize_t o2) +{ + size_t len; + + if ( b->contents == NULL ) { + fprintf(stderr, "Deleting text from block \\%s\n", b->name); + return 0; + } + + if ( (o2 != -1) && (o1 > o2) ) { + ssize_t t = o2; + o2 = o1; + o1 = t; + } + + len = strlen(b->contents); + if ( o2 < 0 ) o2 = len; + if ( (o1 >= o2) || (o1 > len) || (o2 > len) ) { + fprintf(stderr, "Invalid delete: %i %i %i\n", + (int)o1, (int)o2, (int)len); + return 0; + } + memmove(b->contents+o1, b->contents+o2, len-o2+1); + + return o2-o1; +} + + +static char *s_strdup(const char *a) +{ + if ( a == NULL ) return NULL; + return strdup(a); +} + + +SCBlock *sc_block_split(SCBlock *bl, size_t pos) +{ + SCBlock *n = sc_block_new(); + + if ( bl->child != NULL ) { + fprintf(stderr, "Splitting a block with a child!\n"); + return NULL; + } + + /* Second block */ + n->name = s_strdup(bl->name); + n->options = s_strdup(bl->options); + if ( bl->contents != NULL ) { + n->contents = strdup(bl->contents+pos); + /* Truncate the first block */ + bl->contents[pos] = '\0'; + } else { + n->contents = NULL; + } + + n->next = bl->next; + bl->next = n; + + return n; +} + + +/* Return a new block which is the parent for "bl" */ +SCBlock *sc_block_new_parent(SCBlock *bl, const char *name) +{ + SCBlock *n = sc_block_new(); + if ( n == NULL ) return NULL; + n->name = s_strdup(name); + n->child = bl; + return n; +} diff --git a/src-old/sc_parse.h b/src-old/sc_parse.h new file mode 100644 index 0000000..17ce2dd --- /dev/null +++ b/src-old/sc_parse.h @@ -0,0 +1,85 @@ +/* + * sc_parse.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 SC_PARSE_H +#define SC_PARSE_H + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <gio/gio.h> + +typedef struct _scblock SCBlock; + +extern SCBlock *sc_parse(const char *sc); + +extern SCBlock *sc_block_new(void); +extern void sc_block_free(SCBlock *bl); + +extern SCBlock *sc_block_next(const SCBlock *bl); +extern SCBlock *sc_block_child(const SCBlock *bl); +extern const char *sc_block_name(const SCBlock *bl); +extern const char *sc_block_options(const SCBlock *bl); +extern const char *sc_block_contents(const SCBlock *bl); + +extern SCBlock *sc_block_append(SCBlock *bl, + char *name, char *opt, char *contents, + SCBlock **blfp); + +extern SCBlock *sc_block_new_parent(SCBlock *bl, const char *name); + +extern SCBlock *sc_block_prepend(SCBlock *bl, SCBlock *top); + +extern void sc_block_append_p(SCBlock *bl, SCBlock *bln); + +extern void sc_block_append_block(SCBlock *bl, SCBlock *bln); + +extern SCBlock *sc_block_append_end(SCBlock *bl, + char *name, char *opt, char *contents); + +extern SCBlock *sc_block_append_inside(SCBlock *parent, + char *name, char *opt, char *contents); + +extern SCBlock *sc_block_insert_after(SCBlock *afterme, + char *name, char *opt, char *contents); + +extern int sc_block_delete(SCBlock **top, SCBlock *deleteme); + + +extern void sc_block_set_name(SCBlock *bl, char *nam); +extern void sc_block_set_options(SCBlock *bl, char *opt); +extern void sc_block_set_contents(SCBlock *bl, char *con); +extern void sc_insert_text(SCBlock *b1, size_t o1, const char *t); +extern void sc_insert_block(SCBlock *b1, int o1, SCBlock *ins); +extern SCBlock *sc_block_split(SCBlock *bl, size_t pos); + +extern void show_sc_blocks(const SCBlock *bl); +extern void show_sc_block(const SCBlock *bl, const char *prefix); + +extern char *serialise_sc_block(const SCBlock *bl); +extern int save_sc_block(GOutputStream *fh, const SCBlock *bl); + +extern size_t scblock_delete_text(SCBlock *b, ssize_t o1, ssize_t o2); + +#endif /* SC_PARSE_H */ diff --git a/src-old/slide_window.c b/src-old/slide_window.c new file mode 100644 index 0000000..ed37a50 --- /dev/null +++ b/src-old/slide_window.c @@ -0,0 +1,282 @@ +/* + * slide_window.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 <gtk/gtk.h> +#include <assert.h> +#include <gdk/gdkkeysyms.h> +#include <gdk-pixbuf/gdk-pixbuf.h> +#include <math.h> + +#include "colloquium.h" +#include "presentation.h" +#include "slide_window.h" +#include "render.h" +#include "frame.h" +#include "slideshow.h" +#include "pr_clock.h" +#include "sc_parse.h" +#include "sc_interp.h" +#include "sc_editor.h" +#include "utils.h" + + +struct _slidewindow +{ + struct presentation *p; + GtkWidget *window; + GtkToolItem *bfirst; + GtkToolItem *bprev; + GtkToolItem *bnext; + GtkToolItem *blast; + + /* The slide being displayed. Note that the SCEditor is using the + * child of this block, but we need to keep a record of it for changing + * the slide. */ + SCBlock *scblocks; + + SCEditor *sceditor; +}; + + +static void insert_slidetitle_sig(GSimpleAction *action, GVariant *parameter, + gpointer vp) +{ + SlideWindow *sw = vp; + sc_editor_add_storycode(sw->sceditor, "\\slidetitle{Slide title}"); +} + + +static void paste_sig(GSimpleAction *action, GVariant *parameter, + gpointer vp) +{ + SlideWindow *sw = vp; + sc_editor_paste(sw->sceditor); +} + + +static void copy_frame_sig(GSimpleAction *action, GVariant *parameter, + gpointer vp) +{ + SlideWindow *sw = vp; + sc_editor_copy_selected_frame(sw->sceditor); +} + + +static void delete_frame_sig(GSimpleAction *action, GVariant *parameter, + gpointer vp) +{ + SlideWindow *sw = vp; + sc_editor_delete_selected_frame(sw->sceditor); +} + + +void slide_window_update(SlideWindow *sw) +{ + sc_editor_set_scblock(sw->sceditor, sw->scblocks); +} + + +/* Change the editor's slide to "np" */ +static void change_edit_slide(SlideWindow *sw, SCBlock *np) +{ + sc_editor_set_slidenum(sw->sceditor, slide_number(sw->p, np)); + sc_editor_set_scblock(sw->sceditor, np); + sw->scblocks = np; +} + + +static void change_slide_first(SlideWindow *sw) +{ + change_edit_slide(sw, first_slide(sw->p)); +} + + +static void change_slide_backwards(SlideWindow *sw) +{ + SCBlock *tt; + tt = prev_slide(sw->p, sw->scblocks); + if ( tt == NULL ) return; + change_edit_slide(sw, tt); +} + + +static void change_slide_forwards(SlideWindow *sw) +{ + SCBlock *tt; + tt = next_slide(sw->p, sw->scblocks); + if ( tt == NULL ) return; + change_edit_slide(sw, tt); +} + + +static void change_slide_last(SlideWindow *sw) +{ + change_edit_slide(sw, last_slide(sw->p)); +} + + +static void first_slide_sig(GSimpleAction *action, GVariant *parameter, + gpointer vp) +{ + SlideWindow *sw = vp; + change_slide_first(sw); +} + + +static void prev_slide_sig(GSimpleAction *action, GVariant *parameter, + gpointer vp) +{ + SlideWindow *sw = vp; + change_slide_backwards(sw); +} + + +static void next_slide_sig(GSimpleAction *action, GVariant *parameter, + gpointer vp) +{ + SlideWindow *sw = vp; + change_slide_forwards(sw); +} + + +static void last_slide_sig(GSimpleAction *action, GVariant *parameter, + gpointer vp) +{ + SlideWindow *sw = vp; + change_slide_last(sw); +} + + +static gboolean sw_close_sig(GtkWidget *w, SlideWindow *sw) +{ + narrative_window_sw_closed(sw->p->narrative_window, sw); + return FALSE; +} + + +static gboolean sw_key_press_sig(GtkWidget *da, GdkEventKey *event, + SlideWindow *sw) +{ + switch ( event->keyval ) { + + case GDK_KEY_Page_Up : + change_slide_backwards(sw); + break; + + case GDK_KEY_Page_Down : + change_slide_forwards(sw); + break; + + } + + return FALSE; +} + + +static void about_sig(GSimpleAction *action, GVariant *parameter, gpointer vp) +{ + SlideWindow *sw = vp; + open_about_dialog(sw->window); +} + + +GActionEntry sw_entries[] = { + + { "about", about_sig, NULL, NULL, NULL }, + { "paste", paste_sig, NULL, NULL, NULL }, + { "copyframe", copy_frame_sig, NULL, NULL, NULL }, + { "deleteframe", delete_frame_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 }, + { "slidetitle", insert_slidetitle_sig, NULL, NULL, NULL }, +}; + + +SlideWindow *slide_window_open(struct presentation *p, SCBlock *scblocks, + GApplication *papp) +{ + GtkWidget *window; + SlideWindow *sw; + Colloquium *app = COLLOQUIUM(papp); + + sw = calloc(1, sizeof(SlideWindow)); + if ( sw == NULL ) return NULL; + + window = gtk_application_window_new(GTK_APPLICATION(app)); + gtk_window_set_role(GTK_WINDOW(window), "slide"); + sw->window = window; + sw->p = p; + + g_action_map_add_action_entries(G_ACTION_MAP(window), sw_entries, + G_N_ELEMENTS(sw_entries), sw); + + g_signal_connect(G_OBJECT(window), "destroy", + G_CALLBACK(sw_close_sig), sw); + + sw->scblocks = scblocks; + sw->sceditor = sc_editor_new(scblocks, p->stylesheet, p->lang, + colloquium_get_imagestore(app)); + sc_editor_set_slidenum(sw->sceditor, slide_number(sw->p, scblocks)); + sc_editor_set_scale(sw->sceditor, 1); + sc_editor_set_imagestore(sw->sceditor, p->is); + +// scroll = gtk_scrolled_window_new(NULL, NULL); +// gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), +// GTK_POLICY_AUTOMATIC, +// GTK_POLICY_AUTOMATIC); +// gtk_container_add(GTK_CONTAINER(scroll), GTK_WIDGET(sw->sceditor)); +// gtk_window_set_focus(GTK_WINDOW(window), GTK_WIDGET(sw->sceditor)); + g_signal_connect(G_OBJECT(sw->sceditor), "key-press-event", + G_CALLBACK(sw_key_press_sig), sw); + + sc_editor_set_logical_size(sw->sceditor, + p->slide_width, p->slide_height); + + gtk_window_set_default_size(GTK_WINDOW(window), + p->slide_width, p->slide_height); + + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(sw->sceditor)); + + /* Default size */ +// gtk_scrolled_window_set_min_content_width(GTK_SCROLLED_WINDOW(scroll), +// 1024); +// gtk_scrolled_window_set_min_content_height(GTK_SCROLLED_WINDOW(scroll), +// 768); + gtk_window_set_resizable(GTK_WINDOW(sw->window), TRUE); + + gtk_widget_show_all(window); +// gtk_scrolled_window_set_min_content_width(GTK_SCROLLED_WINDOW(scroll), +// 100); +// gtk_scrolled_window_set_min_content_height(GTK_SCROLLED_WINDOW(scroll), +// 100); + + return sw; +} diff --git a/src-old/slide_window.h b/src-old/slide_window.h new file mode 100644 index 0000000..681ec39 --- /dev/null +++ b/src-old/slide_window.h @@ -0,0 +1,37 @@ +/* + * slide_window.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 SLIDEWINDOW_H +#define SLIDEWINDOW_H + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +typedef struct _slidewindow SlideWindow; + +extern SlideWindow *slide_window_open(struct presentation *p, SCBlock *scblocks, + GApplication *app); + +extern void slide_window_update(SlideWindow *sw); + +#endif /* SLIDEWINDOW_H */ diff --git a/src-old/slideshow.c b/src-old/slideshow.c new file mode 100644 index 0000000..6e1c49d --- /dev/null +++ b/src-old/slideshow.c @@ -0,0 +1,240 @@ +/* + * 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 "colloquium.h" +#include "presentation.h" +#include "render.h" +#include "pr_clock.h" +#include "frame.h" +#include "utils.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 slideshow_rerender(SCSlideshow *ss) +{ + int n; + gint w, h; + + if ( ss->cur_slide == NULL ) return; + + if ( ss->surface != NULL ) { + cairo_surface_destroy(ss->surface); + } + + n = slide_number(ss->p, ss->cur_slide); + ss->surface = render_sc(ss->cur_slide, + ss->slide_width, ss->slide_height, + ss->p->slide_width, ss->p->slide_height, + ss->p->stylesheet, NULL, ss->p->is, n, + &ss->top, ss->p->lang); + + 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->surface != NULL ) { + cairo_surface_destroy(ss->surface); + } + 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 width, height; + + width = gtk_widget_get_allocated_width(GTK_WIDGET(da)); + height = gtk_widget_get_allocated_height(GTK_WIDGET(da)); + + /* Overall background */ + cairo_rectangle(cr, 0.0, 0.0, width, height); + cairo_set_source_rgb(cr, 0.0, 0.0, 0.0); + cairo_fill(cr); + + if ( !ss->blank ) { + + /* Draw the slide from the cache */ + cairo_rectangle(cr, ss->xoff, ss->yoff, + ss->slide_width, ss->slide_height); + cairo_set_source_surface(cr, ss->surface, ss->xoff, ss->yoff); + cairo_fill(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; + } + + slideshow_rerender(ss); + + return FALSE; +} + + +static void ss_size_sig(GtkWidget *widget, GdkRectangle *rect, SCSlideshow *ss) +{ + const double sw = ss->p->slide_width; + const double sh = ss->p->slide_height; + + if ( sw/sh > (double)rect->width/rect->height ) { + /* Slide is too wide. Letterboxing top/bottom */ + ss->slide_width = rect->width; + ss->slide_height = rect->width * sh/sw; + } else { + /* Letterboxing at sides */ + ss->slide_width = rect->height * sw/sh; + ss->slide_height = rect->height; + } + + ss->xoff = (rect->width - ss->slide_width)/2.0; + ss->yoff = (rect->height - ss->slide_height)/2.0; + + printf("screen %i %i\n", rect->width, rect->height); + printf("slide %f %f\n", sw, sh); + printf("rendering slide at %i %i\n", ss->slide_width, ss->slide_height); + printf("offset %i %i\n", ss->xoff, ss->yoff); + + slideshow_rerender(ss); +} + + +void sc_slideshow_set_slide(SCSlideshow *ss, SCBlock *ns) +{ + ss->cur_slide = ns; + slideshow_rerender(ss); +} + + +SCSlideshow *sc_slideshow_new(struct 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->surface = 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), "size-allocate", + G_CALLBACK(ss_size_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)); + + ss->linked = 1; + + 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-old/slideshow.h b/src-old/slideshow.h new file mode 100644 index 0000000..ff16d73 --- /dev/null +++ b/src-old/slideshow.h @@ -0,0 +1,83 @@ +/* + * slideshow.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 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> */ + struct presentation *p; + SCBlock *cur_slide; + GtkWidget *drawingarea; + GdkCursor *blank_cursor; + int blank; + int slide_width; + int slide_height; + int xoff; + int yoff; + int linked; + cairo_surface_t *surface; + struct frame *top; + int single_monitor; + uint inhibit_cookie; + GtkApplication *app; +}; + + +struct _scslideshowclass +{ + GtkWindowClass parent_class; +}; + +typedef struct _scslideshow SCSlideshow; +typedef struct _scslideshowclass SCSlideshowClass; + +extern SCSlideshow *sc_slideshow_new(struct presentation *p, GtkApplication *app); +extern void sc_slideshow_set_slide(SCSlideshow *ss, SCBlock *ns); + +#endif /* SLIDESHOW_H */ diff --git a/src-old/stylesheet.c b/src-old/stylesheet.c new file mode 100644 index 0000000..a6e2531 --- /dev/null +++ b/src-old/stylesheet.c @@ -0,0 +1,288 @@ +/* + * stylesheet.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 <json-glib/json-glib.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <gio/gio.h> +#include <gdk/gdk.h> + +#include "stylesheet.h" +#include "utils.h" + + +struct _stylesheet { + JsonNode *root; +}; + + +static int find_comma(const char *a) +{ + int i = 0; + int in_brackets = 0; + size_t len = strlen(a); + + do { + if ( (a[i] == ',') && !in_brackets ) return i; + if ( a[i] == '(' ) in_brackets++; + if ( a[i] == ')' ) in_brackets--; + i++; + } while ( i < len ); + return 0; +} + + +int parse_colour_duo(const char *a, GdkRGBA *col1, GdkRGBA *col2) +{ + char *acopy; + int cpos; + + acopy = strdup(a); + if ( acopy == NULL ) return 1; + + cpos = find_comma(acopy); + if ( cpos == 0 ) { + fprintf(stderr, _("Invalid background gradient '%s'\n"), a); + return 1; + } + + acopy[cpos] = '\0'; + + if ( gdk_rgba_parse(col1, acopy) != TRUE ) { + fprintf(stderr, _("Failed to parse colour: %s\n"), acopy); + } + if ( gdk_rgba_parse(col2, &acopy[cpos+1]) != TRUE ) { + fprintf(stderr, _("Failed to parse colour: %s\n"), &acopy[cpos+1]); + } + + free(acopy); + return 0; +} + + +Stylesheet *stylesheet_load(GFile *file) +{ + JsonParser *parser; + gboolean r; + GError *err = NULL; + Stylesheet *ss; + char *everything; + gsize len; + + printf("Trying stylesheet '%s'\n", g_file_get_uri(file)); + + ss = calloc(1, sizeof(Stylesheet)); + if ( ss == NULL ) return NULL; + + parser = json_parser_new(); + + if ( !g_file_load_contents(file, NULL, &everything, &len, NULL, NULL) ) { + fprintf(stderr, _("Failed to load stylesheet '%s'\n"), + g_file_get_uri(file)); + free(ss); + return NULL; + } + + r = json_parser_load_from_data(parser, everything, len, &err); + if ( r == FALSE ) { + fprintf(stderr, _("Failed to load stylesheet: %s\n"), + err->message); + free(ss); + return NULL; + } + + ss->root = json_parser_steal_root(parser); + g_object_unref(parser); + + return ss; +} + + +static JsonObject *find_stylesheet_object(Stylesheet *ss, const char *path, + JsonNode **freeme) +{ + JsonNode *node; + JsonObject *obj; + JsonArray *array; + GError *err = NULL; + + node = json_path_query(path, ss->root, &err); + array = json_node_get_array(node); + + if ( json_array_get_length(array) == 0 ) { + json_node_unref(node); + return NULL; + } + + if ( json_array_get_length(array) > 1 ) { + json_node_unref(node); + fprintf(stderr, "More than one result in SS lookup (%s)!\n", path); + return NULL; + } + + obj = json_array_get_object_element(array, 0); + if ( obj == NULL ) { + printf("%s not a JSON object\n", path); + json_node_unref(node); + return NULL; + } + + *freeme = node; + return obj; +} + + +char *stylesheet_lookup(Stylesheet *ss, const char *path, const char *key) +{ + JsonObject *obj; + char *ret = NULL; + JsonNode *node = NULL; + + if ( ss == NULL ) { + fprintf(stderr, "No stylesheet!\n"); + return NULL; + } + + obj = find_stylesheet_object(ss, path, &node); + if ( obj == NULL ) return NULL; + + if ( json_object_has_member(obj, key) ) { + + const gchar *v; + v = json_object_get_string_member(obj, key); + if ( v != NULL ) { + ret = strdup(v); + } else { + fprintf(stderr, "Error retrieving %s.%s\n", path, key); + } + + } /* else not found, too bad */ + + if ( node != NULL ) json_node_unref(node); + return ret; +} + + +int stylesheet_set(Stylesheet *ss, const char *path, const char *key, + const char *new_val) +{ + JsonObject *obj; + JsonNode *node = NULL; + int r = 1; + + if ( ss == NULL ) { + fprintf(stderr, "No stylesheet!\n"); + return 1; + } + + obj = find_stylesheet_object(ss, path, &node); + if ( obj != NULL ) { + json_object_set_string_member(obj, key, new_val); + r = 0; + } /* else most likely the object (e.g. "$.slide", "$.slide.frame", + * "$.narrative" etc doesn't exist */ + + if ( node != NULL ) json_node_unref(node); + return r; +} + + +int stylesheet_delete(Stylesheet *ss, const char *path, const char *key) +{ + JsonObject *obj; + JsonNode *node = NULL; + int r = 1; + + if ( ss == NULL ) { + fprintf(stderr, "No stylesheet!\n"); + return 1; + } + + obj = find_stylesheet_object(ss, path, &node); + if ( obj != NULL ) { + json_object_remove_member(obj, key); + r = 0; + } /* else most likely the object (e.g. "$.slide", "$.slide.frame", + * "$.narrative" etc doesn't exist */ + + if ( node != NULL ) json_node_unref(node); + return r; +} + + +void stylesheet_free(Stylesheet *ss) +{ + json_node_unref(ss->root); + free(ss); +} + + +int stylesheet_save(Stylesheet *ss, GFile *file) +{ + JsonGenerator *gen; + GError *error = NULL; + GFileOutputStream *fh; + + fh = g_file_replace(file, NULL, FALSE, G_FILE_CREATE_NONE, NULL, &error); + if ( fh == NULL ) { + fprintf(stderr, _("Open failed: %s\n"), error->message); + return 1; + } + + gen = json_generator_new(); + json_generator_set_root(gen, ss->root); + json_generator_set_pretty(gen, TRUE); + json_generator_set_indent(gen, 1); + json_generator_set_indent_char(gen, '\t'); + if ( !json_generator_to_stream(gen, G_OUTPUT_STREAM(fh), NULL, &error) ) { + fprintf(stderr, _("Open failed: %s\n"), error->message); + return 1; + } + g_object_unref(fh); + return 0; +} + + +char *stylesheet_data(Stylesheet *ss) +{ + return json_to_string(ss->root, FALSE); +} + + +void stylesheet_set_data(Stylesheet *ss, const char *data) +{ + JsonNode *new_root; + GError *err = NULL; + new_root = json_from_string(data, &err); + if ( new_root == NULL ) { + fprintf(stderr, "Internal parse error: %s\n", err->message); + return; + } + json_node_unref(ss->root); + ss->root = new_root; +} diff --git a/src-old/stylesheet.h b/src-old/stylesheet.h new file mode 100644 index 0000000..16d0a0a --- /dev/null +++ b/src-old/stylesheet.h @@ -0,0 +1,54 @@ +/* + * stylesheet.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 STYLESHEET_H +#define STYLESHEET_H + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <gio/gio.h> +#include <gdk/gdk.h> + +typedef struct _stylesheet Stylesheet; + +extern Stylesheet *stylesheet_load(GFile *file); + +extern int stylesheet_save(Stylesheet *ss, GFile *file); + +extern char *stylesheet_data(Stylesheet *ss); + +extern void stylesheet_set_data(Stylesheet *ss, const char *data); + +extern int parse_colour_duo(const char *a, GdkRGBA *col1, GdkRGBA *col2); + +extern char *stylesheet_lookup(Stylesheet *ss, const char *path, const char *key); + +extern int stylesheet_set(Stylesheet *ss, const char *path, const char *key, + const char *new_val); + +extern int stylesheet_delete(Stylesheet *ss, const char *path, const char *key); + +extern void stylesheet_free(Stylesheet *ss); + +#endif /* STYLESHEET_H */ diff --git a/src-old/stylesheet_editor.c b/src-old/stylesheet_editor.c new file mode 100644 index 0000000..292bbf1 --- /dev/null +++ b/src-old/stylesheet_editor.c @@ -0,0 +1,794 @@ +/* + * stylesheet_editor.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 "stylesheet_editor.h" +#include "presentation.h" +#include "sc_interp.h" +#include "stylesheet.h" +#include "utils.h" + + +G_DEFINE_TYPE_WITH_CODE(StylesheetEditor, stylesheet_editor, + GTK_TYPE_DIALOG, NULL) + + +struct _sspriv +{ + struct presentation *p; + const gchar *furniture; + char *ssdata; +}; + + +static void set_font_from_ss(Stylesheet *ss, const char *path, GtkWidget *w) +{ + char *result = stylesheet_lookup(ss, path, "font"); + if ( result != NULL ) { + gtk_font_button_set_font_name(GTK_FONT_BUTTON(w), result); + } +} + + +static void set_col_from_ss(Stylesheet *ss, const char *path, GtkWidget *w) +{ + char *result = stylesheet_lookup(ss, path, "fgcol"); + if ( result != NULL ) { + GdkRGBA rgba; + if ( gdk_rgba_parse(&rgba, result) == TRUE ) { + gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(w), &rgba); + } + } +} + + +static void set_vals_from_ss(Stylesheet *ss, const char *path, const char *key, + GtkWidget *wl, GtkWidget *wr, + GtkWidget *wt, GtkWidget *wb) +{ + char *result = stylesheet_lookup(ss, path, key); + if ( result != NULL ) { + float v[4]; + if ( parse_tuple(result, v) == 0 ) { + gtk_spin_button_set_value(GTK_SPIN_BUTTON(wl), v[0]); + gtk_spin_button_set_value(GTK_SPIN_BUTTON(wr), v[1]); + gtk_spin_button_set_value(GTK_SPIN_BUTTON(wt), v[2]); + gtk_spin_button_set_value(GTK_SPIN_BUTTON(wb), v[3]); + } else { + fprintf(stderr, _("Failed to parse quad: %s\n"), result); + } + } else { + printf("Not found %s.%s\n", path, key); + } +} + + +static void set_geom_from_ss(Stylesheet *ss, const char *path, const char *key, + GtkWidget *ww, GtkWidget *wh, + GtkWidget *wx, GtkWidget *wy, + GtkWidget *wwu, GtkWidget *whu) +{ + char *result = stylesheet_lookup(ss, path, key); + if ( result != NULL ) { + double x, y, w, h; + LengthUnits wu, hu; + if ( parse_dims(result, &w, &h, &wu, &hu, &x, &y) == 0 ) { + if ( wu == UNITS_FRAC ) { + w *= 100; + gtk_combo_box_set_active_id(GTK_COMBO_BOX(wwu), "percent"); + } else { + gtk_combo_box_set_active_id(GTK_COMBO_BOX(wwu), "units"); + } + if ( hu == UNITS_FRAC ) { + h *= 100; + gtk_combo_box_set_active_id(GTK_COMBO_BOX(whu), "percent"); + } else { + gtk_combo_box_set_active_id(GTK_COMBO_BOX(whu), "units"); + } + gtk_spin_button_set_value(GTK_SPIN_BUTTON(ww), w); + gtk_spin_button_set_value(GTK_SPIN_BUTTON(wh), h); + gtk_spin_button_set_value(GTK_SPIN_BUTTON(wx), x); + gtk_spin_button_set_value(GTK_SPIN_BUTTON(wy), y); + } else { + fprintf(stderr, _("Failed to parse dims: %s\n"), result); + } + } else { + printf("Not found %s.%s\n", path, key); + } +} + +static void set_size_from_ss(Stylesheet *ss, const char *path, + GtkWidget *ww, GtkWidget *wh) +{ + char *result = stylesheet_lookup(ss, path, "size"); + if ( result != NULL ) { + float v[2]; + if ( parse_double(result, v) == 0 ) { + gtk_spin_button_set_value(GTK_SPIN_BUTTON(ww), v[0]); + gtk_spin_button_set_value(GTK_SPIN_BUTTON(wh), v[1]); + } else { + fprintf(stderr, _("Failed to parse double: %s\n"), result); + } + } else { + printf("Not found %s.size\n", path); + } +} + + +static int alignment_ok(const char *a) +{ + if ( a == NULL ) return 0; + if ( strcmp(a, "left") == 0 ) return 1; + if ( strcmp(a, "center") == 0 ) return 1; + if ( strcmp(a, "right") == 0 ) return 1; + return 0; +} + + +static void set_alignment_from_ss(Stylesheet *ss, const char *path, + GtkWidget *d) +{ + char *result = stylesheet_lookup(ss, path, "alignment"); + if ( alignment_ok(result) ) { + gtk_combo_box_set_active_id(GTK_COMBO_BOX(d), result); + } +} + + +static void set_bg_from_ss(Stylesheet *ss, const char *path, GtkWidget *wcol, + GtkWidget *wcol2, GtkWidget *wgrad) +{ + char *result; + int found = 0; + + result = stylesheet_lookup(ss, path, "bgcol"); + if ( result != NULL ) { + GdkRGBA rgba; + found = 1; + if ( gdk_rgba_parse(&rgba, result) == TRUE ) { + gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(wcol), &rgba); + gtk_combo_box_set_active_id(GTK_COMBO_BOX(wgrad), "flat"); + gtk_widget_set_sensitive(wcol, TRUE); + gtk_widget_set_sensitive(wcol2, FALSE); + } else { + fprintf(stderr, _("Failed to parse colour: %s\n"), result); + } + } + + result = stylesheet_lookup(ss, path, "bggradv"); + if ( result != NULL ) { + GdkRGBA rgba1, rgba2; + found = 1; + if ( parse_colour_duo(result, &rgba1, &rgba2) == 0 ) { + gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(wcol), &rgba1); + gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(wcol2), &rgba2); + gtk_combo_box_set_active_id(GTK_COMBO_BOX(wgrad), "vert"); + gtk_widget_set_sensitive(wcol, TRUE); + gtk_widget_set_sensitive(wcol2, TRUE); + } + } + + result = stylesheet_lookup(ss, path, "bggradh"); + if ( result != NULL ) { + GdkRGBA rgba1, rgba2; + found = 1; + if ( parse_colour_duo(result, &rgba1, &rgba2) == 0 ) { + gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(wcol), &rgba1); + gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(wcol2), &rgba2); + gtk_combo_box_set_active_id(GTK_COMBO_BOX(wgrad), "horiz"); + gtk_widget_set_sensitive(wcol, TRUE); + gtk_widget_set_sensitive(wcol2, TRUE); + } + } + + if ( !found ) { + GdkRGBA rgba; + rgba.red = 1.0; + rgba.green = 1.0; + rgba.blue = 1.0; + rgba.alpha = 0.0; + gtk_combo_box_set_active_id(GTK_COMBO_BOX(wgrad), "flat"); + gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(wcol), &rgba); + gtk_widget_set_sensitive(wcol, TRUE); + gtk_widget_set_sensitive(wcol2, FALSE); + } +} + + +static void set_furniture(StylesheetEditor *se, const char *furniture) +{ + set_geom_from_ss(se->priv->p->stylesheet, furniture, "geometry", + se->furniture_w, + se->furniture_h, + se->furniture_x, + se->furniture_y, + se->furniture_w_units, + se->furniture_h_units); + + set_vals_from_ss(se->priv->p->stylesheet, furniture, "pad", + se->furniture_padding_l, + se->furniture_padding_r, + se->furniture_padding_t, + se->furniture_padding_b); + + set_vals_from_ss(se->priv->p->stylesheet, furniture, "paraspace", + se->furniture_paraspace_l, + se->furniture_paraspace_r, + se->furniture_paraspace_t, + se->furniture_paraspace_b); + + set_font_from_ss(se->priv->p->stylesheet, furniture, se->furniture_font); + set_col_from_ss(se->priv->p->stylesheet, furniture, se->furniture_fgcol); + set_alignment_from_ss(se->priv->p->stylesheet, furniture, + se->furniture_alignment); + set_bg_from_ss(se->priv->p->stylesheet, furniture, se->furniture_bgcol, + se->furniture_bgcol2, se->furniture_bggrad); +} + + +static void set_values_from_presentation(StylesheetEditor *se) +{ + Stylesheet *ss = se->priv->p->stylesheet; + + /* Narrative */ + set_font_from_ss(ss, "$.narrative", se->narrative_style_font); + set_col_from_ss(ss, "$.narrative", se->narrative_style_fgcol); + set_alignment_from_ss(ss, "$.narrative", se->narrative_style_alignment); + set_bg_from_ss(ss, "$.narrative", se->narrative_style_bgcol, + se->narrative_style_bgcol2, + se->narrative_style_bggrad); + set_vals_from_ss(ss, "$.narrative", "pad", se->narrative_style_padding_l, + se->narrative_style_padding_r, + se->narrative_style_padding_t, + se->narrative_style_padding_b); + set_vals_from_ss(ss, "$.narrative", "paraspace", se->narrative_style_paraspace_l, + se->narrative_style_paraspace_r, + se->narrative_style_paraspace_t, + se->narrative_style_paraspace_b); + + /* Slides */ + set_size_from_ss(ss, "$.slide", se->slide_size_w, se->slide_size_h); + set_bg_from_ss(ss, "$.slide", se->slide_style_bgcol, + se->slide_style_bgcol2, + se->slide_style_bggrad); + + + /* Frames */ + set_font_from_ss(ss, "$.slide.frame", se->frame_style_font); + set_col_from_ss(ss, "$.slide.frame", se->frame_style_fgcol); + set_alignment_from_ss(ss, "$.slide.frame", se->frame_style_alignment); + set_bg_from_ss(ss, "$.slide.frame", se->frame_style_bgcol, + se->frame_style_bgcol2, + se->frame_style_bggrad); + set_vals_from_ss(ss, "$.slide.frame", "pad", se->frame_style_padding_l, + se->frame_style_padding_r, + se->frame_style_padding_t, + se->frame_style_padding_b); + set_vals_from_ss(ss, "$.slide.frame", "paraspace", se->frame_style_paraspace_l, + se->frame_style_paraspace_r, + se->frame_style_paraspace_t, + se->frame_style_paraspace_b); + + set_furniture(se, se->priv->furniture); +} + + +static GradientType id_to_gradtype(const gchar *id) +{ + assert(id != NULL); + if ( strcmp(id, "flat") == 0 ) return GRAD_NONE; + if ( strcmp(id, "horiz") == 0 ) return GRAD_HORIZ; + if ( strcmp(id, "vert") == 0 ) return GRAD_VERT; + return GRAD_NONE; +} + + +static void update_bg(struct presentation *p, const char *style_name, + GtkWidget *bggradw, GtkWidget *col1w, GtkWidget*col2w) +{ + GradientType g; + const gchar *id; + GdkRGBA rgba; + gchar *col1; + gchar *col2; + gchar *gradient; + + id = gtk_combo_box_get_active_id(GTK_COMBO_BOX(bggradw)); + g = id_to_gradtype(id); + + gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(col1w), &rgba); + if ( rgba.alpha < 0.000001 ) rgba.alpha = 0.0; + col1 = gdk_rgba_to_string(&rgba); + + gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(col2w), &rgba); + col2 = gdk_rgba_to_string(&rgba); + + gradient = g_strconcat(col1, ",", col2, NULL); + + switch ( g ) { + + case GRAD_NONE : + stylesheet_set(p->stylesheet, style_name, "bgcol", + col1); + stylesheet_delete(p->stylesheet, style_name, "bggradv"); + stylesheet_delete(p->stylesheet, style_name, "bggradh"); + break; + + case GRAD_HORIZ : + stylesheet_set(p->stylesheet, style_name, "bggradh", + gradient); + stylesheet_delete(p->stylesheet, style_name, "bggradv"); + stylesheet_delete(p->stylesheet, style_name, "bgcol"); + break; + + case GRAD_VERT : + stylesheet_set(p->stylesheet, style_name, "bggradv", + gradient); + stylesheet_delete(p->stylesheet, style_name, "bggradh"); + stylesheet_delete(p->stylesheet, style_name, "bgcol"); + break; + + } + + g_free(gradient); + g_free(col1); + g_free(col2); +} + + +static void update_spacing(struct presentation *p, const char *style_name, + const char *key, GtkWidget *wl, GtkWidget *wr, + GtkWidget *wt, GtkWidget *wb) +{ + int v[4]; + char tmp[256]; + + v[0] = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(wl)); + v[1] = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(wr)); + v[2] = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(wt)); + v[3] = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(wb)); + + if ( snprintf(tmp, 256, "%i,%i,%i,%i", v[0], v[1], v[2], v[3]) >= 256 ) { + fprintf(stderr, "Spacing too long\n"); + } else { + stylesheet_set(p->stylesheet, style_name, key, tmp); + } +} + + +static char units_id_to_char(const char *id) +{ + if ( strcmp(id, "units") == 0 ) return 'u'; + if ( strcmp(id, "percent") == 0 ) return 'f'; + return 'u'; +} + + +static void update_ss_dims(struct presentation *p, const char *style_name, + const char *key, GtkWidget *ww, GtkWidget *wh, + GtkWidget *wx, GtkWidget *wy, + GtkWidget *wwu, GtkWidget *whu) +{ + float w, h, x, y; + char w_units, h_units; + const gchar *uid; + char tmp[256]; + + w = gtk_spin_button_get_value(GTK_SPIN_BUTTON(ww)); + h = gtk_spin_button_get_value(GTK_SPIN_BUTTON(wh)); + x = gtk_spin_button_get_value(GTK_SPIN_BUTTON(wx)); + y = gtk_spin_button_get_value(GTK_SPIN_BUTTON(wy)); + uid = gtk_combo_box_get_active_id(GTK_COMBO_BOX(wwu)); + w_units = units_id_to_char(uid); + uid = gtk_combo_box_get_active_id(GTK_COMBO_BOX(whu)); + h_units = units_id_to_char(uid); + + if ( w_units == 'f' ) w /= 100.0; + if ( h_units == 'f' ) h /= 100.0; + + if ( snprintf(tmp, 256, "%.2f%cx%.2f%c+%.0f+%0.f", + w, w_units, h, h_units, x, y) >= 256 ) + { + fprintf(stderr, "Spacing too long\n"); + } else { + stylesheet_set(p->stylesheet, style_name, key, tmp); + } +} + + +static void revert_sig(GtkButton *button, StylesheetEditor *se) +{ + stylesheet_set_data(se->priv->p->stylesheet, + se->priv->ssdata); + set_values_from_presentation(se); + g_signal_emit_by_name(se, "changed"); +} + + +static void set_font(GtkFontButton *widget, StylesheetEditor *se, + const char *style_name) +{ + const gchar *font; + font = gtk_font_button_get_font_name(GTK_FONT_BUTTON(widget)); + + stylesheet_set(se->priv->p->stylesheet, style_name, "font", font); + set_values_from_presentation(se); + g_signal_emit_by_name(se, "changed"); +} + + +static void set_col(GtkColorButton *widget, StylesheetEditor *se, + const char *style_name, const char *col_name) +{ + GdkRGBA rgba; + gchar *col; + gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(widget), &rgba); + col = gdk_rgba_to_string(&rgba); + stylesheet_set(se->priv->p->stylesheet, style_name, "fgcol", col); + g_free(col); + set_values_from_presentation(se); + g_signal_emit_by_name(se, "changed"); +} + + +static void narrative_font_sig(GtkFontButton *widget, StylesheetEditor *se) +{ + set_font(widget, se, "$.narrative"); +} + + +static void narrative_fgcol_sig(GtkColorButton *widget, StylesheetEditor *se) +{ + set_col(widget, se, "$.narrative", "fgcol"); +} + + +static void narrative_bg_sig(GtkColorButton *widget, StylesheetEditor *se) +{ + update_bg(se->priv->p, "$.narrative", + se->narrative_style_bggrad, + se->narrative_style_bgcol, + se->narrative_style_bgcol2); + set_values_from_presentation(se); + g_signal_emit_by_name(se, "changed"); +} + + +static void narrative_alignment_sig(GtkComboBoxText *widget, StylesheetEditor *se) +{ + const gchar *id = gtk_combo_box_get_active_id(GTK_COMBO_BOX(widget)); + stylesheet_set(se->priv->p->stylesheet, "$.narrative", "alignment", id); + set_values_from_presentation(se); + g_signal_emit_by_name(se, "changed"); +} + + +static void slide_size_sig(GtkSpinButton *widget, StylesheetEditor *se) +{ + int w, h; + char tmp[256]; + + w = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(se->slide_size_w)); + h = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(se->slide_size_h)); + + if ( snprintf(tmp, 256, "%ix%i", w, h) >= 256 ) { + fprintf(stderr, _("Slide size too long\n")); + } else { + stylesheet_set(se->priv->p->stylesheet, "$.slide", "size", tmp); + se->priv->p->slide_width = w; + se->priv->p->slide_height = h; + } + + set_values_from_presentation(se); + g_signal_emit_by_name(se, "changed"); +} + + +static void slide_bg_sig(GtkColorButton *widget, StylesheetEditor *se) +{ + update_bg(se->priv->p, "$.slide", + se->slide_style_bggrad, + se->slide_style_bgcol, + se->slide_style_bgcol2); + set_values_from_presentation(se); + g_signal_emit_by_name(se, "changed"); +} + + +static void frame_font_sig(GtkFontButton *widget, StylesheetEditor *se) +{ + set_font(widget, se, "$.slide.frame"); +} + + +static void frame_fgcol_sig(GtkColorButton *widget, StylesheetEditor *se) +{ + set_col(widget, se, "$.slide.frame", "fgcol"); +} + + +static void frame_bg_sig(GtkColorButton *widget, StylesheetEditor *se) +{ + update_bg(se->priv->p, "$.slide.frame", + se->frame_style_bggrad, + se->frame_style_bgcol, + se->frame_style_bgcol2); + set_values_from_presentation(se); + g_signal_emit_by_name(se, "changed"); +} + + +static void frame_padding_sig(GtkSpinButton *widget, StylesheetEditor *se) +{ + update_spacing(se->priv->p, "$.slide.frame", "pad", + se->frame_style_padding_l, + se->frame_style_padding_r, + se->frame_style_padding_t, + se->frame_style_padding_b); + set_values_from_presentation(se); + g_signal_emit_by_name(se, "changed"); +} + + +static void frame_paraspace_sig(GtkSpinButton *widget, StylesheetEditor *se) +{ + update_spacing(se->priv->p, "$.slide.frame", "paraspace", + se->frame_style_paraspace_l, + se->frame_style_paraspace_r, + se->frame_style_paraspace_t, + se->frame_style_paraspace_b); + set_values_from_presentation(se); + g_signal_emit_by_name(se, "changed"); +} + + +static void frame_alignment_sig(GtkComboBoxText *widget, StylesheetEditor *se) +{ + const gchar *id = gtk_combo_box_get_active_id(GTK_COMBO_BOX(widget)); + stylesheet_set(se->priv->p->stylesheet, "$.slide.frame", "alignment", id); + set_values_from_presentation(se); + g_signal_emit_by_name(se, "changed"); +} + + +static void narrative_padding_sig(GtkSpinButton *widget, StylesheetEditor *se) +{ + update_spacing(se->priv->p, "$.narrative", "pad", + se->narrative_style_padding_l, + se->narrative_style_padding_r, + se->narrative_style_padding_t, + se->narrative_style_padding_b); + set_values_from_presentation(se); + g_signal_emit_by_name(se, "changed"); +} + + +static void narrative_paraspace_sig(GtkSpinButton *widget, StylesheetEditor *se) +{ + update_spacing(se->priv->p, "$.narrative", "paraspace", + se->narrative_style_paraspace_l, + se->narrative_style_paraspace_r, + se->narrative_style_paraspace_t, + se->narrative_style_paraspace_b); + set_values_from_presentation(se); + g_signal_emit_by_name(se, "changed"); +} + + +static void furniture_selector_change_sig(GtkComboBoxText *widget, StylesheetEditor *se) +{ + se->priv->furniture = gtk_combo_box_get_active_id(GTK_COMBO_BOX(widget)); + set_furniture(se, se->priv->furniture); +} + + +static void furniture_font_sig(GtkFontButton *widget, StylesheetEditor *se) +{ + set_font(widget, se, se->priv->furniture); +} + + +static void furniture_fgcol_sig(GtkColorButton *widget, StylesheetEditor *se) +{ + set_col(widget, se,se->priv->furniture, "fgcol"); +} + + +static void furniture_bg_sig(GtkColorButton *widget, StylesheetEditor *se) +{ + update_bg(se->priv->p, se->priv->furniture, se->furniture_bggrad, + se->furniture_bgcol, se->furniture_bgcol2); + set_values_from_presentation(se); + g_signal_emit_by_name(se, "changed"); +} + + +static void furniture_paraspace_sig(GtkSpinButton *widget, StylesheetEditor *se) +{ + update_spacing(se->priv->p, se->priv->furniture, "pad", + se->furniture_padding_l, + se->furniture_padding_r, + se->furniture_padding_t, + se->furniture_padding_b); + set_values_from_presentation(se); + g_signal_emit_by_name(se, "changed"); +} + + +static void furniture_padding_sig(GtkSpinButton *widget, StylesheetEditor *se) +{ + update_spacing(se->priv->p, se->priv->furniture, "pad", + se->furniture_padding_l, + se->furniture_padding_r, + se->furniture_padding_t, + se->furniture_padding_b); + set_values_from_presentation(se); + g_signal_emit_by_name(se, "changed"); +} + + +static void furniture_alignment_sig(GtkComboBoxText *widget, StylesheetEditor *se) +{ + const gchar *id = gtk_combo_box_get_active_id(GTK_COMBO_BOX(widget)); + stylesheet_set(se->priv->p->stylesheet, se->priv->furniture, + "alignment", id); + set_values_from_presentation(se); + g_signal_emit_by_name(se, "changed"); +} + + +static void furniture_dims_sig(GtkSpinButton *widget, StylesheetEditor *se) +{ + update_ss_dims(se->priv->p, se->priv->furniture, "geometry", + se->furniture_w, se->furniture_h, + se->furniture_x, se->furniture_y, + se->furniture_w_units, se->furniture_h_units); + set_values_from_presentation(se); + g_signal_emit_by_name(se, "changed"); +} + + +static void stylesheet_editor_finalize(GObject *obj) +{ + StylesheetEditor *se = COLLOQUIUM_STYLESHEET_EDITOR(obj); + free(se->priv->ssdata); + G_OBJECT_CLASS(stylesheet_editor_parent_class)->finalize(obj); +} + + +static void stylesheet_editor_init(StylesheetEditor *se) +{ + se->priv = G_TYPE_INSTANCE_GET_PRIVATE(se, COLLOQUIUM_TYPE_STYLESHEET_EDITOR, + StylesheetEditorPrivate); + gtk_widget_init_template(GTK_WIDGET(se)); +} + + +#define SE_BIND_CHILD(a, b) \ + gtk_widget_class_bind_template_child(widget_class, StylesheetEditor, a); \ + gtk_widget_class_bind_template_callback(widget_class, b); + +void stylesheet_editor_class_init(StylesheetEditorClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass); + GObjectClass *gobject_class = G_OBJECT_CLASS(klass); + + gtk_widget_class_set_template_from_resource(widget_class, + "/uk/me/bitwiz/Colloquium/stylesheeteditor.ui"); + + g_type_class_add_private(gobject_class, sizeof(StylesheetEditorPrivate)); + gobject_class->finalize = stylesheet_editor_finalize; + + /* Narrative style */ + SE_BIND_CHILD(narrative_style_font, narrative_font_sig); + SE_BIND_CHILD(narrative_style_fgcol, narrative_fgcol_sig); + SE_BIND_CHILD(narrative_style_bgcol, narrative_bg_sig); + SE_BIND_CHILD(narrative_style_bgcol2, narrative_bg_sig); + SE_BIND_CHILD(narrative_style_bggrad, narrative_bg_sig); + SE_BIND_CHILD(narrative_style_paraspace_l, narrative_paraspace_sig); + SE_BIND_CHILD(narrative_style_paraspace_r, narrative_paraspace_sig); + SE_BIND_CHILD(narrative_style_paraspace_t, narrative_paraspace_sig); + SE_BIND_CHILD(narrative_style_paraspace_b, narrative_paraspace_sig); + SE_BIND_CHILD(narrative_style_padding_l, narrative_padding_sig); + SE_BIND_CHILD(narrative_style_padding_r, narrative_padding_sig); + SE_BIND_CHILD(narrative_style_padding_t, narrative_padding_sig); + SE_BIND_CHILD(narrative_style_padding_b, narrative_padding_sig); + SE_BIND_CHILD(narrative_style_alignment, narrative_alignment_sig); + + /* Slide style */ + SE_BIND_CHILD(slide_size_w, slide_size_sig); + SE_BIND_CHILD(slide_size_h, slide_size_sig); + SE_BIND_CHILD(slide_style_bgcol, slide_bg_sig); + SE_BIND_CHILD(slide_style_bgcol2, slide_bg_sig); + SE_BIND_CHILD(slide_style_bggrad, slide_bg_sig); + + /* Slide->frame style */ + SE_BIND_CHILD(frame_style_font, frame_font_sig); + SE_BIND_CHILD(frame_style_fgcol, frame_fgcol_sig); + SE_BIND_CHILD(frame_style_bgcol, frame_bg_sig); + SE_BIND_CHILD(frame_style_bgcol2, frame_bg_sig); + SE_BIND_CHILD(frame_style_bggrad, frame_bg_sig); + SE_BIND_CHILD(frame_style_paraspace_l, frame_paraspace_sig); + SE_BIND_CHILD(frame_style_paraspace_r, frame_paraspace_sig); + SE_BIND_CHILD(frame_style_paraspace_t, frame_paraspace_sig); + SE_BIND_CHILD(frame_style_paraspace_b, frame_paraspace_sig); + SE_BIND_CHILD(frame_style_padding_l, frame_padding_sig); + SE_BIND_CHILD(frame_style_padding_r, frame_padding_sig); + SE_BIND_CHILD(frame_style_padding_t, frame_padding_sig); + SE_BIND_CHILD(frame_style_padding_b, frame_padding_sig); + SE_BIND_CHILD(frame_style_alignment, frame_alignment_sig); + + /* Furniture */ + SE_BIND_CHILD(furniture_selector, furniture_selector_change_sig); + SE_BIND_CHILD(furniture_paraspace_l, furniture_paraspace_sig); + SE_BIND_CHILD(furniture_paraspace_r, furniture_paraspace_sig); + SE_BIND_CHILD(furniture_paraspace_t, furniture_paraspace_sig); + SE_BIND_CHILD(furniture_paraspace_b, furniture_paraspace_sig); + SE_BIND_CHILD(furniture_padding_l, furniture_padding_sig); + SE_BIND_CHILD(furniture_padding_r, furniture_padding_sig); + SE_BIND_CHILD(furniture_padding_t, furniture_padding_sig); + SE_BIND_CHILD(furniture_padding_b, furniture_padding_sig); + SE_BIND_CHILD(furniture_font, furniture_font_sig); + SE_BIND_CHILD(furniture_fgcol, furniture_fgcol_sig); + SE_BIND_CHILD(furniture_bgcol, furniture_bg_sig); + SE_BIND_CHILD(furniture_bgcol2, furniture_bg_sig); + SE_BIND_CHILD(furniture_bggrad, furniture_bg_sig); + SE_BIND_CHILD(furniture_alignment, furniture_alignment_sig); + SE_BIND_CHILD(furniture_w, furniture_dims_sig); + SE_BIND_CHILD(furniture_h, furniture_dims_sig); + SE_BIND_CHILD(furniture_x, furniture_dims_sig); + SE_BIND_CHILD(furniture_y, furniture_dims_sig); + SE_BIND_CHILD(furniture_w_units, furniture_dims_sig); + SE_BIND_CHILD(furniture_h_units, furniture_dims_sig); + + gtk_widget_class_bind_template_callback(widget_class, revert_sig); + + g_signal_new("changed", COLLOQUIUM_TYPE_STYLESHEET_EDITOR, + G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); +} + + +StylesheetEditor *stylesheet_editor_new(struct presentation *p) +{ + StylesheetEditor *se; + + se = g_object_new(COLLOQUIUM_TYPE_STYLESHEET_EDITOR, NULL); + if ( se == NULL ) return NULL; + + se->priv->p = p; + se->priv->furniture = gtk_combo_box_get_active_id(GTK_COMBO_BOX(se->furniture_selector)); + set_values_from_presentation(se); + + se->priv->ssdata = stylesheet_data(p->stylesheet); + + return se; +} + diff --git a/src-old/stylesheet_editor.h b/src-old/stylesheet_editor.h new file mode 100644 index 0000000..a7c77b6 --- /dev/null +++ b/src-old/stylesheet_editor.h @@ -0,0 +1,128 @@ +/* + * stylesheet_editor.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 STYLESHEET_EDITOR_H +#define STYLESHEET_EDITOR_H + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <gtk/gtk.h> + +#include "presentation.h" +#include "frame.h" + +#define COLLOQUIUM_TYPE_STYLESHEET_EDITOR (stylesheet_editor_get_type()) + +#define COLLOQUIUM_STYLESHEET_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + COLLOQUIUM_TYPE_STYLESHEET_EDITOR, \ + StylesheetEditor)) + +#define COLLOQUIUM_IS_STYLESHEET_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), \ + COLLOQUIUM_TYPE_STYLESHEET_EDITOR)) + +#define COLLOQUIUM_STYLESHEET_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((obj), \ + COLLOQUIUM_TYPE_STYLESHEET_EDITOR, \ + StylesheetEditorClass)) + +#define COLLOQUIUM_IS_STYLESHEET_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((obj), \ + COLLOQUIUM_TYPE_STYLESHEET_EDITOR)) + +#define COLLOQUIUM_STYLESHEET_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), \ + COLLOQUIUM_TYPE_STYLESHEET_EDITOR, \ + StylesheetEditorClass)) + + +typedef struct _sspriv StylesheetEditorPrivate; + +struct _stylesheeteditor +{ + GtkDialog parent_instance; + GtkWidget *narrative_style_font; + GtkWidget *narrative_style_fgcol; + GtkWidget *narrative_style_bgcol; + GtkWidget *narrative_style_bgcol2; + GtkWidget *narrative_style_bggrad; + GtkWidget *narrative_style_paraspace_l; + GtkWidget *narrative_style_paraspace_r; + GtkWidget *narrative_style_paraspace_t; + GtkWidget *narrative_style_paraspace_b; + GtkWidget *narrative_style_padding_l; + GtkWidget *narrative_style_padding_r; + GtkWidget *narrative_style_padding_t; + GtkWidget *narrative_style_padding_b; + GtkWidget *narrative_style_alignment; + GtkWidget *slide_size_w; + GtkWidget *slide_size_h; + GtkWidget *slide_style_bgcol; + GtkWidget *slide_style_bgcol2; + GtkWidget *slide_style_bggrad; + GtkWidget *frame_style_font; + GtkWidget *frame_style_fgcol; + GtkWidget *frame_style_bgcol; + GtkWidget *frame_style_bgcol2; + GtkWidget *frame_style_bggrad; + GtkWidget *frame_style_paraspace_l; + GtkWidget *frame_style_paraspace_r; + GtkWidget *frame_style_paraspace_t; + GtkWidget *frame_style_paraspace_b; + GtkWidget *frame_style_padding_l; + GtkWidget *frame_style_padding_r; + GtkWidget *frame_style_padding_t; + GtkWidget *frame_style_padding_b; + GtkWidget *frame_style_alignment; + GtkWidget *furniture_selector; + GtkWidget *furniture_paraspace_l; + GtkWidget *furniture_paraspace_r; + GtkWidget *furniture_paraspace_t; + GtkWidget *furniture_paraspace_b; + GtkWidget *furniture_padding_l; + GtkWidget *furniture_padding_r; + GtkWidget *furniture_padding_t; + GtkWidget *furniture_padding_b; + GtkWidget *furniture_font; + GtkWidget *furniture_fgcol; + GtkWidget *furniture_bgcol; + GtkWidget *furniture_bgcol2; + GtkWidget *furniture_bggrad; + GtkWidget *furniture_alignment; + GtkWidget *furniture_w; + GtkWidget *furniture_h; + GtkWidget *furniture_x; + GtkWidget *furniture_y; + GtkWidget *furniture_w_units; + GtkWidget *furniture_h_units; + StylesheetEditorPrivate *priv; +}; + +struct _stylesheeteditorclass +{ + GtkDialogClass parent_class; +}; + +typedef struct _stylesheeteditor StylesheetEditor; +typedef struct _stylesheeteditorclass StylesheetEditorClass; + +extern StylesheetEditor *stylesheet_editor_new(struct presentation *p); + +#endif /* STYLESHEET_EDITOR_H */ diff --git a/src-old/testcard.c b/src-old/testcard.c new file mode 100644 index 0000000..2b6598f --- /dev/null +++ b/src-old/testcard.c @@ -0,0 +1,288 @@ +/* + * testcard.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 "presentation.h" +#include "utils.h" + + +struct testcard +{ + GtkWidget *window; + char geom[256]; + int slide_width; + int slide_height; + GtkWidget *drawingarea; + struct presentation *p; +}; + +static gint tc_destroy_sig(GtkWidget *widget, struct testcard *tc) +{ + free(tc); + return FALSE; +} + + +static void arrow_left(cairo_t *cr, double size) +{ + cairo_rel_line_to(cr, size, size); + cairo_rel_line_to(cr, 0.0, -2*size); + cairo_rel_line_to(cr, -size, size); +} + + +static void arrow_right(cairo_t *cr, double size) +{ + cairo_rel_line_to(cr, -size, size); + cairo_rel_line_to(cr, 0.0, -2*size); + cairo_rel_line_to(cr, size, size); +} + + +static void arrow_down(cairo_t *cr, double size) +{ + cairo_rel_line_to(cr, -size, -size); + cairo_rel_line_to(cr, 2*size, 0.0); + cairo_rel_line_to(cr, -size, size); +} + + +static void arrow_up(cairo_t *cr, double size) +{ + cairo_rel_line_to(cr, -size, size); + cairo_rel_line_to(cr, 2*size, 0.0); + cairo_rel_line_to(cr, -size, -size); +} + + +static void colour_box(cairo_t *cr, double x, double y, + double r, double g, double b, const char *label) +{ + const double w = 50.0; + const double h = 50.0; + cairo_text_extents_t size; + + cairo_rectangle(cr, x+0.5, y+0.5, w, h); + cairo_set_source_rgb(cr, r, g, b); + cairo_fill_preserve(cr); + cairo_set_source_rgb(cr, 0.0, 0.0, 0.0); + cairo_set_line_width(cr, 1.0); + cairo_stroke(cr); + + cairo_set_font_size(cr, 24.0); + cairo_text_extents(cr, label, &size); + cairo_move_to(cr, x+(w/2.0)-(size.width/2.0), y-10.0); + cairo_show_text(cr, label); + +} + + +static gboolean tc_draw_sig(GtkWidget *da, cairo_t *cr, struct testcard *tc) +{ + double xoff, yoff; + double width, height; + int h; + PangoLayout *pl; + PangoFontDescription *desc; + char tmp[1024]; + int plw, plh; + double xp, yp; + + width = gtk_widget_get_allocated_width(GTK_WIDGET(da)); + height = gtk_widget_get_allocated_height(GTK_WIDGET(da)); + + /* Overall background */ + cairo_rectangle(cr, 0.0, 0.0, width, height); + cairo_set_source_rgb(cr, 0.0, 0.0, 1.0); + cairo_fill(cr); + + /* FIXME: Assumes that monitor and slide sizes are such that + * letterboxing at sides. This needn't be the case. */ + h = tc->slide_width * tc->p->slide_height / tc->p->slide_width; + + /* Get the overall size */ + xoff = (width - tc->slide_width)/2.0; + yoff = (height - h)/2.0; + + /* Background of slide */ + cairo_rectangle(cr, xoff, yoff, tc->slide_width, h); + cairo_set_source_rgb(cr, 1.0, 1.0, 1.0); + cairo_fill(cr); + + /* Arrows showing edges of screen */ + cairo_set_source_rgb(cr, 0.0, 0.0, 0.0); + cairo_move_to(cr, 0.0, height/2); + arrow_left(cr, 100.0); + cairo_fill(cr); + cairo_move_to(cr, width, height/2); + arrow_right(cr, 100.0); + cairo_fill(cr); + cairo_move_to(cr, width/2, height); + arrow_down(cr, 100.0); + cairo_fill(cr); + cairo_move_to(cr, width/2, 0.0); + arrow_up(cr, 100.0); + cairo_fill(cr); + + /* Arrows showing edges of slide */ + cairo_translate(cr, xoff, yoff); + cairo_set_source_rgb(cr, 0.5, 0.0, 0.0); + cairo_move_to(cr, 0.0, 100+h/2); + arrow_left(cr, 80.0); + cairo_fill(cr); + cairo_move_to(cr, tc->slide_width, 100+h/2); + arrow_right(cr, 80.0); + cairo_fill(cr); + cairo_move_to(cr, 100+tc->slide_width/2, h); + arrow_down(cr, 80.0); + cairo_fill(cr); + cairo_move_to(cr, 100+tc->slide_width/2, 0.0); + arrow_up(cr, 80.0); + cairo_fill(cr); + + /* Stuff in the middle */ + yp = (tc->slide_height-400)/2.0; + cairo_save(cr); + cairo_translate(cr, 0.0, yp); + + snprintf(tmp, 1024, _("Colloquium %s test card\n" + "Screen resolution %.0f × %.0f\n" + "Slide resolution %i × %i"), + PACKAGE_VERSION, width, height, + tc->slide_width, h); + + pl = pango_cairo_create_layout(cr); + desc = pango_font_description_from_string("Sans 24"); + pango_layout_set_font_description(pl, desc); + pango_layout_set_text(pl, tmp, -1); + + pango_layout_get_size(pl, &plw, &plh); + plw = pango_units_to_double(plw); + plh = pango_units_to_double(plh); + cairo_move_to(cr, (tc->slide_width-plw)/2, 0.0); + cairo_set_source_rgb(cr, 0.0, 0.0, 0.0); + pango_cairo_show_layout(cr, pl); + + /* Colour boxes */ + xp = (tc->slide_width-450)/2.0; + colour_box(cr, xp+0, 200, 1.0, 0.0, 0.0, _("Red")); + colour_box(cr, xp+80, 200, 0.0, 1.0, 0.0, _("Green")); + colour_box(cr, xp+160, 200, 0.0, 0.0, 1.0, _("Blue")); + colour_box(cr, xp+240, 200, 1.0, 1.0, 0.0, _("Yellow")); + colour_box(cr, xp+320, 200, 1.0, 0.0, 1.0, _("Pink")); + colour_box(cr, xp+400, 200, 0.0, 1.0, 1.0, _("Cyan")); + + /* Shades of grey */ + double i; + for ( i=0; i<=1.0; i+=0.2 ) { + char label[32]; + snprintf(label, 31, "%.0f%%", i*100.0); + colour_box(cr, xp+(i*5*80), 300, i, i, i, label); + } + cairo_restore(cr); + + return FALSE; +} + + +static void size_sig(GtkWidget *widget, GdkRectangle *rect, struct testcard *ss) +{ + int w; + + w = rect->height * ss->p->slide_width/ss->p->slide_height; + if ( w > rect->width ) w = rect->width; + ss->slide_width = w; + ss->slide_height = rect->height; +} + + + +static gboolean tc_key_press_sig(GtkWidget *da, GdkEventKey *event, + struct testcard *tc) +{ + if ( !event->is_modifier ) gtk_widget_destroy(tc->window); + return FALSE; +} + + +void show_testcard(struct presentation *p) +{ + GdkDisplay *display; + int n_monitors; + struct testcard *tc; + + tc = calloc(1, sizeof(struct testcard)); + if ( tc == NULL ) return; + + tc->p = p; + + tc->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + + tc->drawingarea = gtk_drawing_area_new(); + gtk_container_add(GTK_CONTAINER(tc->window), tc->drawingarea); + + gtk_widget_set_can_focus(GTK_WIDGET(tc->drawingarea), TRUE); + gtk_widget_add_events(GTK_WIDGET(tc->drawingarea), GDK_KEY_PRESS_MASK); + + g_signal_connect(G_OBJECT(tc->drawingarea), "key-press-event", + G_CALLBACK(tc_key_press_sig), tc); + g_signal_connect(G_OBJECT(tc->window), "destroy", + G_CALLBACK(tc_destroy_sig), tc); + g_signal_connect(G_OBJECT(tc->window), "size-allocate", + G_CALLBACK(size_sig), tc); + g_signal_connect(G_OBJECT(tc->drawingarea), "draw", + G_CALLBACK(tc_draw_sig), tc); + + gtk_widget_grab_focus(GTK_WIDGET(tc->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")); + } else { + mon_ss = gdk_display_get_monitor(display, 1); + printf(_("Dual monitor mode\n")); + } + + /* Workaround because gtk_window_fullscreen_on_monitor doesn't work */ + GdkRectangle rect; + gdk_monitor_get_geometry(mon_ss, &rect); + gtk_window_move(GTK_WINDOW(tc->window), rect.x, rect.y); + gtk_window_fullscreen(GTK_WINDOW(tc->window)); + + gtk_widget_show_all(GTK_WIDGET(tc->window)); + +} + diff --git a/src-old/testcard.h b/src-old/testcard.h new file mode 100644 index 0000000..bb1ce2b --- /dev/null +++ b/src-old/testcard.h @@ -0,0 +1,32 @@ +/* + * testcard.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 TESTCARD_H +#define TESTCARD_H + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +extern void show_testcard(struct presentation *p); + +#endif /* TESTCARD_H */ diff --git a/src-old/utils.c b/src-old/utils.c new file mode 100644 index 0000000..b41f344 --- /dev/null +++ b/src-old/utils.c @@ -0,0 +1,137 @@ +/* + * utils.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 <string.h> +#include <stdio.h> +#include <stdlib.h> + +#include "utils.h" + +void chomp(char *s) +{ + size_t i; + + if ( !s ) return; + + for ( i=0; i<strlen(s); i++ ) { + if ( (s[i] == '\n') || (s[i] == '\r') ) { + s[i] = '\0'; + return; + } + } +} + + +int safe_strcmp(const char *a, const char *b) +{ + if ( a == NULL ) return 1; + if ( b == NULL ) return 1; + return strcmp(a, b); +} + + +int parse_double(const char *a, float v[2]) +{ + int nn; + + nn = sscanf(a, "%fx%f", &v[0], &v[1]); + if ( nn != 2 ) { + fprintf(stderr, _("Invalid size '%s'\n"), a); + return 1; + } + + return 0; +} + + +int parse_tuple(const char *a, float v[4]) +{ + int nn; + + nn = sscanf(a, "%f,%f,%f,%f", &v[0], &v[1], &v[2], &v[3]); + if ( nn != 4 ) { + fprintf(stderr, _("Invalid tuple '%s'\n"), a); + return 1; + } + + return 0; +} + + +static LengthUnits get_units(const char *t) +{ + size_t len = strlen(t); + + if ( t[len-1] == 'f' ) return UNITS_FRAC; + if ( t[len-1] == 'u' ) return UNITS_SLIDE; + + fprintf(stderr, _("Invalid units in '%s'\n"), t); + return UNITS_SLIDE; +} + + +int parse_dims(const char *opt, double *wp, double *hp, + LengthUnits *wup, LengthUnits *hup, + double *xp, double *yp) +{ + char *w; + char *h; + char *x; + char *y; + char *check; + + w = strdup(opt); + h = index(w, 'x'); + h[0] = '\0'; h++; + + x = index(h, '+'); + if ( x == NULL ) goto invalid; + x[0] = '\0'; x++; + + y = index(x, '+'); + if ( x == NULL ) goto invalid; + y[0] = '\0'; y++; + + *wp = strtod(w, &check); + if ( check == w ) goto invalid; + *wup = get_units(w); + + *hp = strtod(h, &check); + if ( check == h ) goto invalid; + *hup = get_units(h); + + *xp= strtod(x, &check); + if ( check == x ) goto invalid; + *yp = strtod(y, &check); + if ( check == y ) goto invalid; + + return 0; + +invalid: + fprintf(stderr, _("Invalid dimensions '%s'\n"), opt); + return 1; +} diff --git a/src-old/utils.h b/src-old/utils.h new file mode 100644 index 0000000..af3c7b8 --- /dev/null +++ b/src-old/utils.h @@ -0,0 +1,47 @@ +/* + * utils.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 UTILS_H +#define UTILS_H + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +typedef enum +{ + UNITS_SLIDE, + UNITS_FRAC +} LengthUnits; + +extern void chomp(char *s); +extern int safe_strcmp(const char *a, const char *b); +extern int parse_double(const char *a, float v[2]); +extern int parse_tuple(const char *a, float v[4]); +extern int parse_dims(const char *opt, double *wp, double *hp, + LengthUnits *wup, LengthUnits *hup, + double *xp, double *yp); + +#include <libintl.h> +#define _(x) gettext(x) + +#endif /* UTILS_H */ |