aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/presentation.h2
-rw-r--r--src/render.c202
-rw-r--r--src/render.h37
-rw-r--r--src/tool_text.c984
4 files changed, 1225 insertions, 0 deletions
diff --git a/src/presentation.h b/src/presentation.h
index 423ea42..8707151 100644
--- a/src/presentation.h
+++ b/src/presentation.h
@@ -28,6 +28,7 @@
#endif
#include <cairo.h>
+#include <pango/pango.h>
struct slide
{
@@ -50,6 +51,7 @@ struct slide
struct frame
{
struct frame_class *cl;
+ PangoContext *pc;
struct frame **rendering_order;
int num_ro;
diff --git a/src/render.c b/src/render.c
new file mode 100644
index 0000000..1e8a697
--- /dev/null
+++ b/src/render.c
@@ -0,0 +1,202 @@
+/*
+ * render.c
+ *
+ * Colloquium - A tiny presentation program
+ *
+ * Copyright (c) 2011 Thomas White <taw@bitwiz.org.uk>
+ *
+ * This program 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 <gdk/gdk.h>
+
+#include "storycode.h"
+#include "stylesheet.h"
+#include "presentation.h"
+
+
+static void render_bgblock(cairo_t *cr, struct bgblock *b)
+{
+ GdkColor col1;
+ GdkColor col2;
+ cairo_pattern_t *patt = NULL;
+ double cx, cy, r, r1, r2;
+
+ cairo_rectangle(cr, b->min_x, b->min_y,
+ b->max_x - b->min_x,
+ b->max_y - b->min_y);
+
+ switch ( b->type ) {
+
+ case BGBLOCK_SOLID :
+ gdk_color_parse(b->colour1, &col1);
+ gdk_cairo_set_source_color(cr, &col1);
+ /* FIXME: Honour alpha as well */
+ cairo_fill(cr);
+ break;
+
+ case BGBLOCK_GRADIENT_CIRCULAR :
+ cx = b->min_x + (b->max_x-b->min_x)/2.0;
+ cy = b->min_y + (b->max_y-b->min_y)/2.0;
+ r1 = (b->max_x-b->min_x)/2.0;
+ r2 = (b->max_y-b->min_y)/2.0;
+ r = r1 > r2 ? r1 : r2;
+ patt = cairo_pattern_create_radial(cx, cy, r, cx, cy, 0.0);
+ /* Fall-through */
+
+ case BGBLOCK_GRADIENT_X :
+ if ( patt == NULL ) {
+ patt = cairo_pattern_create_linear(b->min_x, 0.0,
+ b->max_y, 0.0);
+ }
+ /* Fall-through */
+
+ case BGBLOCK_GRADIENT_Y :
+ if ( patt == NULL ) {
+ patt = cairo_pattern_create_linear(0.0, b->min_y,
+ 0.0, b->max_y);
+ }
+
+ gdk_color_parse(b->colour1, &col1);
+ gdk_color_parse(b->colour2, &col2);
+ cairo_pattern_add_color_stop_rgba(patt, 0.0, col1.red/65535.0,
+ col1.green/65535.0,
+ col1.blue/65535.0,
+ b->alpha1);
+ cairo_pattern_add_color_stop_rgba(patt, 1.0, col2.red/65535.0,
+ col2.green/65535.0,
+ col2.blue/65535.0,
+ b->alpha2);
+ cairo_set_source(cr, patt);
+ cairo_fill(cr);
+ cairo_pattern_destroy(patt);
+ break;
+
+ case BGBLOCK_IMAGE :
+ cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
+ cairo_fill(cr);
+
+ }
+}
+
+
+/* Render Level 1 Storycode */
+int render_sc(const char *sc, cairo_t *cr, double w, double h, PangoContext *pc)
+{
+ PangoLayout *layout;
+ PangoFontDescription *fontdesc;
+ GdkColor col;
+
+ /* FIXME: Check for Level 1 markup e.g. image */
+
+ layout = pango_layout_new(pc);
+
+ pango_cairo_update_layout(cr, layout);
+ pango_layout_set_width(layout, w*PANGO_SCALE);
+ pango_layout_set_height(layout, h*PANGO_SCALE);
+
+ pango_layout_set_text(layout, sc, -1);
+ fontdesc = pango_font_description_from_string("Sans 12");
+
+ pango_layout_set_font_description(layout, fontdesc);
+
+ cairo_move_to(cr, 0.0, 0.0);
+
+ /* FIXME: Honour alpha as well */
+ gdk_color_parse("#000000", &col);
+ gdk_cairo_set_source_color(cr, &col);
+
+ pango_cairo_show_layout(cr, layout);
+
+ pango_font_description_free(fontdesc);
+ g_object_unref(G_OBJECT(layout));
+
+ return 0;
+}
+
+
+static int render_frame(struct frame *fr, cairo_t *cr, double w, double h)
+{
+ int i;
+
+ /* The rendering order is a list of children, but it also contains the
+ * current frame. In this way, it contains information about which
+ * layer the current frame is to be rendered as relative to its
+ * children. */
+ for ( i=0; i<fr->num_ro; i++ ) {
+
+ double nw, nh;
+
+ if ( fr->rendering_order[i] == fr ) {
+ render_sc(fr->sc, cr, w, h, fr->pc);
+ continue;
+ }
+
+ /* Sort out the transformation for the margins */
+ cairo_translate(cr, fr->rendering_order[i]->cl->margin_left,
+ fr->rendering_order[i]->cl->margin_top);
+
+ nw = w - fr->rendering_order[i]->cl->margin_left;
+ nw -= fr->rendering_order[i]->cl->margin_right;
+
+ nh = h - fr->rendering_order[i]->cl->margin_top;
+ nh -= fr->rendering_order[i]->cl->margin_bottom;
+
+ render_frame(fr->rendering_order[i], cr, nw, nh);
+
+ }
+
+ return 0;
+}
+
+
+cairo_surface_t *render_slide(struct slide *s, int w, int h)
+{
+ cairo_surface_t *surf;
+ cairo_t *cr;
+ cairo_font_options_t *fopts;
+ int i;
+
+ surf = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w, h);
+
+ cr = cairo_create(surf);
+ cairo_scale(cr, w, h);
+
+ fopts = cairo_font_options_create();
+ cairo_font_options_set_hint_style(fopts, CAIRO_HINT_STYLE_NONE);
+ cairo_font_options_set_hint_metrics(fopts, CAIRO_HINT_METRICS_OFF);
+ cairo_font_options_set_antialias(fopts, CAIRO_ANTIALIAS_SUBPIXEL);
+ cairo_set_font_options(cr, fopts);
+
+ for ( i=0; i<s->st->n_bgblocks; i++ ) {
+ render_bgblock(cr, s->st->bgblocks[i]);
+ }
+
+ render_frame(s->top, cr, w, h);
+
+ cairo_font_options_destroy(fopts);
+ cairo_destroy(cr);
+
+ return surf;
+}
diff --git a/src/render.h b/src/render.h
new file mode 100644
index 0000000..d5054cd
--- /dev/null
+++ b/src/render.h
@@ -0,0 +1,37 @@
+/*
+ * render.h
+ *
+ * Colloquium - A tiny presentation program
+ *
+ * Copyright (c) 2011 Thomas White <taw@bitwiz.org.uk>
+ *
+ * This program 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"
+
+extern cairo_surface_t *render_slide(struct slide *s, int w, int h);
+
+extern int render_sc(const char *sc, cairo_t *cr, double w, double h,
+ PangoContext *pc);
+
+#endif /* RENDER_H */
diff --git a/src/tool_text.c b/src/tool_text.c
new file mode 100644
index 0000000..1b62f91
--- /dev/null
+++ b/src/tool_text.c
@@ -0,0 +1,984 @@
+/*
+ * tool_text.c
+ *
+ * Colloquium - A tiny presentation program
+ *
+ * Copyright (c) 2011 Thomas White <taw@bitwiz.org.uk>
+ *
+ * This program 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 <math.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "presentation.h"
+#include "objects.h"
+#include "mainwindow.h"
+#include "slide_render.h"
+#include "loadsave.h"
+
+
+enum text_drag_reason
+{
+ TEXT_DRAG_REASON_NONE,
+ TEXT_DRAG_REASON_RESIZE,
+};
+
+
+struct text_toolinfo
+{
+ struct toolinfo base;
+ PangoContext *pc;
+ enum text_drag_reason drag_reason;
+ enum corner drag_corner;
+ double box_x;
+ double box_y;
+ double box_width;
+ double box_height;
+ double drag_initial_x;
+ double drag_initial_y;
+};
+
+
+struct text_object
+{
+ struct object base;
+
+ char *text;
+ size_t text_len;
+ int insertion_point;
+ int insertion_trail;
+ PangoLayout *layout;
+ PangoFontDescription *fontdesc;
+ double offs_x;
+ double offs_y;
+ int furniture;
+
+ PangoContext **pc;
+};
+
+
+static void calculate_size_from_style(struct text_object *o,
+ double *peright, double *pebottom,
+ double *pmw, double *pmh)
+{
+ double max_width, max_height;
+ double ebottom, eright, mw, mh;
+
+ eright = o->base.parent->parent->slide_width
+ - o->base.style->margin_right;
+ ebottom = o->base.parent->parent->slide_height
+ - o->base.style->margin_bottom;
+ mw = o->base.parent->parent->slide_width;
+ mh = o->base.parent->parent->slide_height;
+
+ *peright = eright; *pebottom = ebottom;
+ *pmw = mw; *pmh = mh;
+
+ max_width = mw - o->base.style->margin_left
+ - o->base.style->margin_right;
+
+
+ /* Use the provided maximum width if it exists and is smaller */
+ if ( o->base.style->use_max_width
+ && (o->base.style->max_width < max_width) )
+ {
+ max_width = o->base.style->max_width;
+ }
+
+ max_height = mh - o->base.style->margin_top
+ - o->base.style->margin_bottom;
+
+ pango_layout_set_width(o->layout, max_width*PANGO_SCALE);
+ pango_layout_set_height(o->layout, max_height*PANGO_SCALE);
+ pango_layout_set_wrap(o->layout, PANGO_WRAP_WORD_CHAR);
+ pango_layout_set_ellipsize(o->layout, PANGO_ELLIPSIZE_MIDDLE);
+
+ switch ( o->base.style->halign ) {
+ case J_LEFT :
+ pango_layout_set_alignment(o->layout, PANGO_ALIGN_LEFT);
+ break;
+ case J_RIGHT :
+ pango_layout_set_alignment(o->layout, PANGO_ALIGN_RIGHT);
+ break;
+ case J_CENTER :
+ pango_layout_set_alignment(o->layout, PANGO_ALIGN_CENTER);
+ break;
+ }
+}
+
+
+static void calculate_position_from_style(struct text_object *o,
+ double eright, double ebottom,
+ double mw, double mh)
+{
+ double xo, yo;
+
+ xo = o->base.bb_width; yo = o->base.bb_height;
+
+ switch ( o->base.style->halign ) {
+ case J_LEFT :
+ o->base.x = o->base.style->margin_left;
+ break;
+ case J_RIGHT :
+ o->base.x = eright - xo;
+ break;
+ case J_CENTER :
+ o->base.x = mw/2.0 - xo/2.0 + o->base.style->offset_x;
+ break;
+ }
+
+ /* Correct if centering crashes into opposite margin */
+ if ( o->base.style->halign == J_CENTER )
+ {
+ if ( o->base.x < o->base.style->margin_left ) {
+ o->base.x = o->base.style->margin_left;
+ }
+
+ if ( o->base.x + xo > eright ) {
+ o->base.x = eright - xo;
+ }
+ }
+
+ switch ( o->base.style->valign ) {
+ case V_TOP :
+ o->base.y = o->base.style->margin_top;
+ break;
+ case V_BOTTOM :
+ o->base.y = ebottom - yo;
+ break;
+ case V_CENTER :
+ o->base.y = mh/2.0 - yo/2.0 + o->base.style->offset_y;
+ break;
+ }
+
+ if ( o->base.style->valign == V_CENTER ) {
+
+ if ( o->base.y < o->base.style->margin_top ) {
+ o->base.y = o->base.style->margin_top;
+ }
+
+ if ( o->base.y+yo + yo > ebottom )
+ {
+ o->base.y = ebottom - yo;
+ }
+ }
+}
+
+
+static void update_text(struct text_object *o, cairo_t *cr)
+{
+ PangoRectangle logical;
+ double eright = 0.0;
+ double ebottom = 0.0;
+ double mw = 0.0;
+ double mh = 0.0;
+ struct object *ex;
+ struct text_object *ext;
+
+ switch ( o->base.style->role ) {
+
+ case S_ROLE_SLIDENUMBER:
+ if ( o->text_len < 32 ) {
+ free(o->text);
+ o->text = malloc(32);
+ o->text_len = 32;
+ }
+ snprintf(o->text, o->text_len-1, "Slide %i",
+ 1+slide_number(o->base.parent->parent, o->base.parent));
+ break;
+
+ case S_ROLE_PTITLE:
+ ex = o->base.parent->roles[S_ROLE_PTITLE_REF];
+ ext = (struct text_object *)ex;
+ if ( (ext != NULL) && (ext->text != NULL) ) {
+ free(o->text);
+ o->text = strdup(ext->text);
+ o->text_len = strlen(o->text);
+ }
+ if ( ext == NULL ) {
+ free(o->text);
+ o->text = strdup("<presentation title>");
+ o->text_len = strlen(o->text);
+ }
+ break;
+
+ case S_ROLE_PDATE:
+ ex = o->base.parent->roles[S_ROLE_PDATE_REF];
+ ext = (struct text_object *)ex;
+ if ( (ext != NULL) && (ext->text != NULL) ) {
+ free(o->text);
+ o->text = strdup(ext->text);
+ o->text_len = strlen(o->text);
+ }
+ if ( ext == NULL ) {
+ free(o->text);
+ o->text = strdup("<date>");
+ o->text_len = strlen(o->text);
+ }
+ break;
+
+ default:
+ break;
+
+ }
+
+ if ( o->layout == NULL ) {
+ if ( *o->pc != NULL ) {
+ o->layout = pango_layout_new(*o->pc);
+ } else {
+ /* Can't render yet */
+ return;
+ }
+ }
+
+ if ( cr != NULL ) pango_cairo_update_layout(cr, o->layout);
+
+ o->furniture = o->base.style != o->base.parent->parent->ss->styles[0];
+
+ pango_layout_set_text(o->layout, o->text, -1);
+ o->fontdesc = pango_font_description_from_string(o->base.style->font);
+ pango_layout_set_font_description(o->layout, o->fontdesc);
+
+ if ( o->furniture ) {
+
+ calculate_size_from_style(o, &eright, &ebottom, &mw, &mh);
+
+ pango_layout_get_extents(o->layout, NULL, &logical);
+
+ o->base.bb_width = logical.width / PANGO_SCALE;
+ o->base.bb_height = logical.height / PANGO_SCALE;
+ o->offs_x = logical.x / PANGO_SCALE;
+ o->offs_y = logical.y / PANGO_SCALE;
+
+ } else {
+
+ pango_layout_set_width(o->layout,
+ o->base.bb_width*PANGO_SCALE);
+ pango_layout_set_height(o->layout,
+ o->base.bb_height*PANGO_SCALE);
+ pango_layout_set_wrap(o->layout, PANGO_WRAP_WORD_CHAR);
+ pango_layout_set_ellipsize(o->layout, PANGO_ELLIPSIZE_MIDDLE);
+
+ }
+
+ if ( o->furniture ) {
+ calculate_position_from_style(o, eright, ebottom, mw, mh);
+ }
+}
+
+
+void insert_text(struct object *op, char *t)
+{
+ struct text_object *o = (struct text_object *)op;
+ char *tmp;
+ size_t tlen, olen, offs;
+ int i;
+
+ assert(o->base.type == OBJ_TEXT);
+ tlen = strlen(t);
+ olen = strlen(o->text);
+
+ if ( tlen + olen + 1 > o->text_len ) {
+
+ char *try;
+
+ try = realloc(o->text, o->text_len + tlen + 64);
+ if ( try == NULL ) return; /* Failed to insert */
+ o->text = try;
+ o->text_len += 64;
+ o->text_len += tlen;
+
+ }
+
+ tmp = malloc(o->text_len);
+ if ( tmp == NULL ) return;
+
+ offs = o->insertion_point + o->insertion_trail;
+
+ for ( i=0; i<offs; i++ ) {
+ tmp[i] = o->text[i];
+ }
+ for ( i=0; i<tlen; i++ ) {
+ tmp[i+offs] = t[i];
+ }
+ for ( i=0; i<olen-o->insertion_point; i++ ) {
+ tmp[i+offs+tlen] = o->text[i+offs];
+ }
+ tmp[olen+tlen] = '\0';
+ memcpy(o->text, tmp, o->text_len);
+ free(tmp);
+
+ redraw_slide(op->parent);
+ o->insertion_point += tlen;
+ o->base.empty = 0;
+}
+
+
+void move_cursor(struct object *op, int dir)
+{
+ struct text_object *o = (struct text_object *)op;
+ int new_idx, new_trail;
+
+ pango_layout_move_cursor_visually(o->layout, TRUE, o->insertion_point,
+ o->insertion_trail,
+ dir, &new_idx, &new_trail);
+
+ if ( (new_idx >= 0) && (new_idx < G_MAXINT) ) {
+ o->insertion_point = new_idx;
+ o->insertion_trail = new_trail;
+ }
+}
+
+
+void move_cursor_left(struct object *op)
+{
+ move_cursor(op, -1);
+ redraw_overlay(op->parent->parent);
+}
+
+
+void move_cursor_right(struct object *op)
+{
+ move_cursor(op, +1);
+ redraw_overlay(op->parent->parent);
+}
+
+
+void handle_text_backspace(struct object *op)
+{
+ int old_idx, new_idx;
+ struct text_object *o = (struct text_object *)op;
+
+ assert(o->base.type == OBJ_TEXT);
+
+ if ( o->insertion_point == 0 ) return; /* Nothing to delete */
+
+ old_idx = o->insertion_point + o->insertion_trail;
+ move_cursor_left(op);
+ new_idx = o->insertion_point + o->insertion_trail;
+
+ memmove(o->text+new_idx, o->text+old_idx,
+ o->text_len-new_idx);
+
+ if ( strlen(o->text) == 0 ) o->base.empty = 1;
+
+ redraw_slide(op->parent);
+}
+
+
+static void render_text_object(cairo_t *cr, struct object *op)
+{
+ struct text_object *o = (struct text_object *)op;
+ GdkColor col;
+
+ if ( o->layout == NULL ) {
+ return;
+ }
+
+ update_text(o, cr);
+
+ cairo_move_to(cr, o->base.x - o->offs_x, o->base.y - o->offs_y);
+ gdk_color_parse(o->base.style->colour, &col);
+ gdk_cairo_set_source_color(cr, &col); /* FIXME: Honour alpha as well */
+ pango_cairo_show_layout(cr, o->layout);
+}
+
+
+static void draw_caret(cairo_t *cr, struct text_object *o)
+{
+ double xposd, yposd, cx;
+ double clow, chigh;
+ PangoRectangle pos;
+ const double t = 1.8;
+
+ assert(o->base.type == OBJ_TEXT);
+
+ pango_layout_get_cursor_pos(o->layout,
+ o->insertion_point+o->insertion_trail,
+ &pos, NULL);
+
+ xposd = pos.x/PANGO_SCALE;
+ cx = o->base.x - o->offs_x + xposd;
+ yposd = pos.y/PANGO_SCALE;
+ clow = o->base.y - o->offs_y + yposd;
+ chigh = clow + (pos.height/PANGO_SCALE);
+
+ 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 update_text_object(struct object *op)
+{
+ struct text_object *o = (struct text_object *)op;
+ update_text(o, NULL);
+}
+
+
+static void delete_text_object(struct object *op)
+{
+ struct text_object *o = (struct text_object *)op;
+
+ if ( o->layout != NULL ) g_object_unref(o->layout);
+ if ( o->fontdesc != NULL ) pango_font_description_free(o->fontdesc);
+}
+
+
+static void serialize(struct object *op, struct serializer *ser)
+{
+ struct text_object *o = (struct text_object *)op;
+
+ serialize_s(ser, "style", op->style->name);
+ if ( op->style == op->parent->parent->ss->styles[0] ) {
+ serialize_f(ser, "x", op->x);
+ serialize_f(ser, "y", op->y);
+ serialize_f(ser, "w", op->bb_width);
+ serialize_f(ser, "h", op->bb_height);
+ }
+
+ serialize_s(ser, "text", o->text);
+}
+
+
+static struct object *new_text_object(double x, double y, struct style *sty,
+ struct text_toolinfo *ti)
+{
+ struct text_object *new;
+
+ new = calloc(1, sizeof(*new));
+ if ( new == NULL ) return NULL;
+
+ /* Base properties */
+ new->base.x = x; new->base.y = y;
+ new->base.bb_width = 10.0;
+ new->base.bb_height = 40.0;
+ new->base.type = OBJ_TEXT;
+ new->base.empty = 1;
+ new->base.parent = NULL;
+ new->base.style = sty;
+
+ /* Text-specific stuff */
+ new->text = malloc(1);
+ new->text[0] = '\0';
+ new->text_len = 1;
+ new->insertion_point = 0;
+ new->insertion_trail = 0;
+ if ( ti->pc != NULL ) {
+ new->layout = pango_layout_new(ti->pc);
+ new->pc = NULL;
+ } else {
+ new->layout = NULL;
+ new->pc = &ti->pc;
+ }
+ new->fontdesc = NULL;
+
+ /* Methods for this object */
+ new->base.render_object = render_text_object;
+ new->base.delete_object = delete_text_object;
+ new->base.update_object = update_text_object;
+ new->base.serialize = serialize;
+
+ return (struct object *)new;
+}
+
+
+static struct object *add_text_object(struct slide *s, double x, double y,
+ struct style *sty,
+ struct text_toolinfo *ti)
+{
+ struct object *o;
+
+ o = new_text_object(x, y, sty, ti);
+
+ if ( add_object_to_slide(s, o) ) {
+ delete_object(o);
+ return NULL;
+ }
+
+ redraw_slide(o->parent);
+
+ return o;
+}
+
+
+static void calculate_box_size(struct object *o, double x, double y,
+ struct text_toolinfo *ti)
+{
+ double ddx, ddy;
+
+ ddx = x - ti->drag_initial_x;
+ ddy = y - ti->drag_initial_y;
+
+ switch ( ti->drag_corner ) {
+
+ case CORNER_BR :
+ ti->box_x = o->x;
+ ti->box_y = o->y;
+ ti->box_width = o->bb_width + ddx;
+ ti->box_height = o->bb_height + ddy;
+ break;
+
+ case CORNER_BL :
+ ti->box_x = o->x + ddx;
+ ti->box_y = o->y;
+ ti->box_width = o->bb_width - ddx;
+ ti->box_height = o->bb_height + ddy;
+ break;
+
+ case CORNER_TL :
+ ti->box_x = o->x + ddx;
+ ti->box_y = o->y + ddy;
+ ti->box_width = o->bb_width - ddx;
+ ti->box_height = o->bb_height - ddy;
+ break;
+
+ case CORNER_TR :
+ ti->box_x = o->x;
+ ti->box_y = o->y + ddy;
+ ti->box_width = o->bb_width + ddx;
+ ti->box_height = o->bb_height - ddy;
+ break;
+
+ case CORNER_NONE :
+ break;
+
+ }
+
+ if ( ti->box_width < 20.0 ) ti->box_width = 20.0;
+ if ( ti->box_height < 20.0 ) ti->box_height = 20.0;
+}
+
+
+static void click_select(struct presentation *p, struct toolinfo *tip,
+ double x, double y, GdkEventButton *event,
+ enum drag_status *drag_status,
+ enum drag_reason *drag_reason)
+{
+ int xp, yp;
+ double xo, yo;
+ gboolean v;
+ enum corner c;
+ struct text_toolinfo *ti = (struct text_toolinfo *)tip;
+ struct text_object *o = (struct text_object *)p->editing_object;
+ int idx, trail;
+
+ assert(o->base.type == OBJ_TEXT);
+
+ xo = x - o->base.x; yo = y - o->base.y;
+
+ /* Within the resizing region? */
+ c = which_corner(x, y, &o->base);
+ if ( (c != CORNER_NONE) && !o->furniture )
+ {
+ ti->drag_reason = TEXT_DRAG_REASON_RESIZE;
+ ti->drag_corner = c;
+
+ ti->drag_initial_x = x;
+ ti->drag_initial_y = y;
+
+ calculate_box_size((struct object *)o, x, y, ti);
+
+ /* Tell the MCP what we did, and return */
+ *drag_status = DRAG_STATUS_DRAGGING;
+ *drag_reason = DRAG_REASON_TOOL;
+ return;
+ }
+
+ xp = (xo + o->offs_x)*PANGO_SCALE;
+ yp = (yo + o->offs_y)*PANGO_SCALE;
+
+ v = pango_layout_xy_to_index(o->layout, xp, yp, &idx, &trail);
+
+ o->insertion_point = idx;
+ o->insertion_trail = trail;
+}
+
+
+static void drag(struct toolinfo *tip, struct presentation *p,
+ struct object *o, double x, double y)
+{
+ struct text_toolinfo *ti = (struct text_toolinfo *)tip;
+
+ if ( ti->drag_reason == TEXT_DRAG_REASON_RESIZE ) {
+
+ calculate_box_size(o, x, y, ti);
+ redraw_overlay(p);
+
+ }
+}
+
+
+static void end_drag(struct toolinfo *tip, struct presentation *p,
+ struct object *o, double x, double y)
+{
+ struct text_toolinfo *ti = (struct text_toolinfo *)tip;
+
+ calculate_box_size((struct object *)o, x, y, ti);
+
+ o->x = ti->box_x;
+ o->y = ti->box_y;
+ o->bb_width = ti->box_width;
+ o->bb_height = ti->box_height;
+ update_text((struct text_object *)o, NULL);
+ redraw_slide(o->parent);
+
+ ti->drag_reason = TEXT_DRAG_REASON_NONE;
+}
+
+
+static void update_subsequent_refs(struct presentation *p, int start,
+ enum object_role role, struct object *o)
+{
+ int i;
+
+ for ( i=start; i<p->num_slides; i++ )
+ {
+ struct slide *s = p->slides[i];
+ s->roles[role] = o;
+ }
+}
+
+
+static void create_default(struct presentation *p, struct style *sty,
+ struct slide *s, struct toolinfo *tip)
+{
+ struct object *n;
+ struct text_object *o;
+ struct text_toolinfo *ti = (struct text_toolinfo *)tip;
+
+ n = add_text_object(s, 0.0, 0.0, sty, ti);
+ o = (struct text_object *)n;
+ o->furniture = 1;
+ n->empty = 0;
+
+ if ( sty->role != S_ROLE_NONE ) {
+ s->roles[sty->role] = n;
+ }
+
+ switch ( sty->role )
+ {
+ case S_ROLE_SLIDENUMBER:
+ case S_ROLE_PTITLE:
+ case S_ROLE_PAUTHOR:
+ case S_ROLE_PDATE:
+ break;
+
+ default:
+ p->editing_object = n;
+ break;
+ }
+
+ /* If we just created a new presentation title (etc), set this object as
+ * the presentation title reference for all subsequent slides.
+ * Any relevant objects will get updated when the current object gets
+ * deselected. */
+ if ( sty->role == S_ROLE_PTITLE_REF ) {
+ update_subsequent_refs(p, slide_number(p, s)+1,
+ S_ROLE_PTITLE_REF, n);
+ }
+ if ( sty->role == S_ROLE_PAUTHOR_REF ) {
+ update_subsequent_refs(p, slide_number(p, s)+1,
+ S_ROLE_PAUTHOR_REF, n);
+ }
+ if ( sty->role == S_ROLE_PDATE_REF ) {
+ update_subsequent_refs(p, slide_number(p, s)+1,
+ S_ROLE_PDATE_REF, n);
+ }
+
+ redraw_slide(((struct object *)o)->parent);
+}
+
+
+static void create_region(struct toolinfo *tip, struct presentation *p,
+ double x1, double y1, double x2, double y2)
+{
+ struct object *n;
+ struct text_toolinfo *ti = (struct text_toolinfo *)tip;
+ struct text_object *o;
+
+ n = add_text_object(p->cur_edit_slide, 0.0, 0.0, p->ss->styles[0], ti);
+ n->x = x1<x2 ? x1 : x2;
+ n->y = y1<y2 ? y1 : y2;
+ n->bb_width = fabs(x1-x2);
+ n->bb_height = fabs(y1-y2);
+
+ o = (struct text_object *)n;
+ o->furniture = 0;
+
+ redraw_slide(((struct object *)o)->parent);
+ p->editing_object = n;
+}
+
+
+static void select_object(struct object *o, struct toolinfo *tip)
+{
+ /* Do nothing */
+}
+
+
+static void update_subsequent(struct presentation *p, int start,
+ enum object_role role, struct object *o)
+{
+ int i;
+
+ for ( i=start; i<p->num_slides; i++ ) {
+
+ struct slide *s = p->slides[i];
+
+ if ( s->roles[role] != NULL ) {
+ s->roles[role]->update_object(s->roles[role]);
+ }
+ }
+}
+
+
+static int deselect_object(struct object *o, struct toolinfo *tip)
+{
+ struct slide *s;
+ struct presentation *p;
+
+ if ( o == NULL ) return 0;
+
+ s = o->parent;
+ p = s->parent;
+
+ if ( o->empty ) {
+ delete_object(o);
+ return 1;
+ }
+
+ if ( o->style->role == S_ROLE_PTITLE_REF ) {
+ update_subsequent(p, slide_number(p, s)+1, S_ROLE_PTITLE, o);
+ }
+ if ( o->style->role == S_ROLE_PAUTHOR_REF ) {
+ update_subsequent(p, slide_number(p, s)+1, S_ROLE_PAUTHOR, o);
+ }
+ if ( o->style->role == S_ROLE_PDATE_REF ) {
+ update_subsequent(p, slide_number(p, s)+1, S_ROLE_PDATE, o);
+ }
+
+ return 0;
+}
+
+
+static void draw_overlay(struct toolinfo *tip, cairo_t *cr, struct object *n)
+{
+ struct text_toolinfo *ti = (struct text_toolinfo *)tip;
+ struct text_object *o = (struct text_object *)n;
+
+ if ( n != NULL ) {
+
+ draw_editing_box(cr, n->x, n->y, n->bb_width, n->bb_height);
+
+ if ( !o->furniture ) {
+
+ /* Draw resize handles */
+ draw_resize_handle(cr, n->x, n->y+n->bb_height-20.0);
+ draw_resize_handle(cr, n->x+n->bb_width-20.0, n->y);
+ draw_resize_handle(cr, n->x, n->y);
+ draw_resize_handle(cr, n->x+n->bb_width-20.0,
+ n->y+n->bb_height-20.0);
+
+ }
+
+ draw_caret(cr, o);
+ }
+
+ if ( ti->drag_reason == TEXT_DRAG_REASON_RESIZE ) {
+ draw_rubberband_box(cr, ti->box_x, ti->box_y,
+ ti->box_width, ti->box_height);
+ }
+
+}
+
+
+static void key_pressed(struct object *o, guint keyval, struct toolinfo *tip)
+{
+ if ( o == NULL ) return;
+
+ switch ( keyval ) {
+
+ case GDK_KEY_BackSpace :
+ handle_text_backspace(o);
+ break;
+
+ case GDK_KEY_Left :
+ move_cursor_left(o);
+ break;
+
+ case GDK_KEY_Right :
+ move_cursor_right(o);
+ break;
+
+ }
+}
+
+
+static void im_commit(struct object *o, gchar *str, struct toolinfo *tip)
+{
+ insert_text(o, str);
+}
+
+
+static int valid_object(struct object *o)
+{
+ if ( o->type == OBJ_TEXT ) return 1;
+ return 0;
+}
+
+
+static void realise(struct toolinfo *ti, GtkWidget *w, struct presentation *p)
+{
+ struct text_toolinfo *tip = (struct text_toolinfo *)ti;
+ tip->pc = gtk_widget_get_pango_context(w);
+
+ ti->tbox = gtk_label_new("Text tool");
+ g_object_ref(ti->tbox);
+ gtk_widget_show(ti->tbox);
+}
+
+
+static struct object *deserialize(struct presentation *p, struct ds_node *root,
+ struct toolinfo *tip)
+{
+ struct object *o;
+ struct text_object *to;
+ char *style;
+ char *text;
+ struct style *sty;
+ double x, y, w, h;
+ struct text_toolinfo *ti = (struct text_toolinfo *)tip;
+
+ if ( get_field_s(root, "style", &style) ) {
+ fprintf(stderr, "Couldn't find style for object '%s'\n",
+ root->key);
+ return NULL;
+ }
+
+ sty = find_style(p->ss, style);
+ if ( sty == NULL ) {
+ fprintf(stderr, "Style '%s' not found in style sheet.\n",
+ style);
+ free(style);
+ return NULL;
+ }
+ free(style);
+
+ if ( sty == p->ss->styles[0] ) {
+
+ if ( get_field_f(root, "x", &x) ) {
+ fprintf(stderr,
+ "Couldn't find x position for object '%s'\n",
+ root->key);
+ return NULL;
+ }
+ if ( get_field_f(root, "y", &y) ) {
+ fprintf(stderr,
+ "Couldn't find y position for object '%s'\n",
+ root->key);
+ return NULL;
+ }
+ if ( get_field_f(root, "w", &w) ) {
+ fprintf(stderr,
+ "Couldn't find width for object '%s'\n",
+ root->key);
+ return NULL;
+ }
+ if ( get_field_f(root, "h", &h) ) {
+ fprintf(stderr,
+ "Couldn't find height for object '%s'\n",
+ root->key);
+ return NULL;
+ }
+
+ } else {
+
+ /* Furniture */
+ x = 0.0;
+ y = 0.0;
+ w = 0.0;
+ h = 0.0;
+
+ }
+
+ o = new_text_object(x, y, sty, ti);
+ o->bb_width = w;
+ o->bb_height = h;
+
+ /* Apply the correct text */
+ if ( get_field_s(root, "text", &text) ) {
+ fprintf(stderr, "Couldn't find text for object '%s'\n",
+ root->key);
+ return NULL;
+ }
+ to = (struct text_object *)o;
+ free(to->text);
+ to->text = text;
+ to->text_len = strlen(text);
+ o->empty = 0;
+
+ return o;
+}
+
+
+struct toolinfo *initialise_text_tool(GtkWidget *w)
+{
+ struct text_toolinfo *ti;
+
+ ti = malloc(sizeof(*ti));
+
+ ti->base.click_select = click_select;
+ ti->base.create_default = create_default;
+ ti->base.select = select_object;
+ ti->base.deselect = deselect_object;
+ ti->base.drag = drag;
+ ti->base.end_drag = end_drag;
+ ti->base.create_region = create_region;
+ ti->base.draw_editing_overlay = draw_overlay;
+ ti->base.key_pressed = key_pressed;
+ ti->base.im_commit = im_commit;
+ ti->base.valid_object = valid_object;
+ ti->base.realise = realise;
+ ti->base.deserialize = deserialize;
+
+ ti->pc = NULL;
+
+ ti->drag_reason = DRAG_REASON_NONE;
+
+ return (struct toolinfo *)ti;
+}